| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library analyzer_cli.src.driver; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:convert'; | |
| 9 import 'dart:io' as io; | |
| 10 | |
| 11 import 'package:analyzer/error/error.dart'; | |
| 12 import 'package:analyzer/file_system/file_system.dart' as file_system; | |
| 13 import 'package:analyzer/file_system/file_system.dart'; | |
| 14 import 'package:analyzer/file_system/physical_file_system.dart'; | |
| 15 import 'package:analyzer/plugin/resolver_provider.dart'; | |
| 16 import 'package:analyzer/source/package_map_provider.dart'; | |
| 17 import 'package:analyzer/source/package_map_resolver.dart'; | |
| 18 import 'package:analyzer/source/pub_package_map_provider.dart'; | |
| 19 import 'package:analyzer/source/sdk_ext.dart'; | |
| 20 import 'package:analyzer/src/context/builder.dart'; | |
| 21 import 'package:analyzer/src/dart/analysis/byte_store.dart'; | |
| 22 import 'package:analyzer/src/dart/analysis/driver.dart'; | |
| 23 import 'package:analyzer/src/dart/analysis/file_state.dart'; | |
| 24 import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
| 25 import 'package:analyzer/src/generated/constant.dart'; | |
| 26 import 'package:analyzer/src/generated/engine.dart'; | |
| 27 import 'package:analyzer/src/generated/interner.dart'; | |
| 28 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 29 import 'package:analyzer/src/generated/sdk.dart'; | |
| 30 import 'package:analyzer/src/generated/source.dart'; | |
| 31 import 'package:analyzer/src/generated/source_io.dart'; | |
| 32 import 'package:analyzer/src/generated/utilities_general.dart' | |
| 33 show PerformanceTag; | |
| 34 import 'package:analyzer/src/source/source_resource.dart'; | |
| 35 import 'package:analyzer/src/summary/idl.dart'; | |
| 36 import 'package:analyzer/src/summary/package_bundle_reader.dart'; | |
| 37 import 'package:analyzer/src/summary/summary_sdk.dart' show SummaryBasedDartSdk; | |
| 38 import 'package:analyzer_cli/src/analyzer_impl.dart'; | |
| 39 import 'package:analyzer_cli/src/build_mode.dart'; | |
| 40 import 'package:analyzer_cli/src/error_formatter.dart'; | |
| 41 import 'package:analyzer_cli/src/error_severity.dart'; | |
| 42 import 'package:analyzer_cli/src/options.dart'; | |
| 43 import 'package:analyzer_cli/src/perf_report.dart'; | |
| 44 import 'package:analyzer_cli/starter.dart' show CommandLineStarter; | |
| 45 import 'package:linter/src/rules.dart' as linter; | |
| 46 import 'package:package_config/discovery.dart' as pkg_discovery; | |
| 47 import 'package:package_config/packages.dart' show Packages; | |
| 48 import 'package:package_config/packages_file.dart' as pkgfile show parse; | |
| 49 import 'package:package_config/src/packages_impl.dart' show MapPackages; | |
| 50 import 'package:path/path.dart' as path; | |
| 51 import 'package:plugin/manager.dart'; | |
| 52 import 'package:plugin/plugin.dart'; | |
| 53 import 'package:yaml/yaml.dart'; | |
| 54 | |
| 55 /// Shared IO sink for standard error reporting. | |
| 56 /// | |
| 57 /// *Visible for testing.* | |
| 58 StringSink errorSink = io.stderr; | |
| 59 | |
| 60 /// Shared IO sink for standard out reporting. | |
| 61 /// | |
| 62 /// *Visible for testing.* | |
| 63 StringSink outSink = io.stdout; | |
| 64 | |
| 65 /// Test this option map to see if it specifies lint rules. | |
| 66 bool containsLintRuleEntry(Map<String, YamlNode> options) { | |
| 67 var linterNode = options['linter']; | |
| 68 return linterNode is YamlMap && linterNode.containsKey('rules'); | |
| 69 } | |
| 70 | |
| 71 typedef Future<ErrorSeverity> _BatchRunnerHandler(List<String> args); | |
| 72 | |
| 73 class Driver implements CommandLineStarter { | |
| 74 static final PerformanceTag _analyzeAllTag = | |
| 75 new PerformanceTag("Driver._analyzeAll"); | |
| 76 | |
| 77 static ByteStore analysisDriverMemoryByteStore = new MemoryByteStore(); | |
| 78 | |
| 79 /// The plugins that are defined outside the `analyzer_cli` package. | |
| 80 List<Plugin> _userDefinedPlugins = <Plugin>[]; | |
| 81 | |
| 82 /// The context that was most recently created by a call to [_analyzeAll], or | |
| 83 /// `null` if [_analyzeAll] hasn't been called yet. | |
| 84 InternalAnalysisContext _context; | |
| 85 | |
| 86 AnalysisDriver analysisDriver; | |
| 87 | |
| 88 /// The total number of source files loaded by an AnalysisContext. | |
| 89 int _analyzedFileCount = 0; | |
| 90 | |
| 91 /// If [_context] is not `null`, the [CommandLineOptions] that guided its | |
| 92 /// creation. | |
| 93 CommandLineOptions _previousOptions; | |
| 94 | |
| 95 @override | |
| 96 ResolverProvider packageResolverProvider; | |
| 97 | |
| 98 /// SDK instance. | |
| 99 DartSdk sdk; | |
| 100 | |
| 101 /** | |
| 102 * The resource provider used to access the file system. | |
| 103 */ | |
| 104 file_system.ResourceProvider resourceProvider = | |
| 105 PhysicalResourceProvider.INSTANCE; | |
| 106 | |
| 107 /// Collected analysis statistics. | |
| 108 final AnalysisStats stats = new AnalysisStats(); | |
| 109 | |
| 110 /// This Driver's current analysis context. | |
| 111 /// | |
| 112 /// *Visible for testing.* | |
| 113 AnalysisContext get context => _context; | |
| 114 | |
| 115 @override | |
| 116 void set userDefinedPlugins(List<Plugin> plugins) { | |
| 117 _userDefinedPlugins = plugins ?? <Plugin>[]; | |
| 118 } | |
| 119 | |
| 120 @override | |
| 121 Future<Null> start(List<String> args) async { | |
| 122 if (_context != null) { | |
| 123 throw new StateError("start() can only be called once"); | |
| 124 } | |
| 125 int startTime = new DateTime.now().millisecondsSinceEpoch; | |
| 126 | |
| 127 StringUtilities.INTERNER = new MappedInterner(); | |
| 128 | |
| 129 _processPlugins(); | |
| 130 | |
| 131 // Parse commandline options. | |
| 132 CommandLineOptions options = CommandLineOptions.parse(args); | |
| 133 | |
| 134 // Do analysis. | |
| 135 if (options.buildMode) { | |
| 136 ErrorSeverity severity = _buildModeAnalyze(options); | |
| 137 // In case of error propagate exit code. | |
| 138 if (severity == ErrorSeverity.ERROR) { | |
| 139 io.exitCode = severity.ordinal; | |
| 140 } | |
| 141 } else if (options.shouldBatch) { | |
| 142 _BatchRunner.runAsBatch(args, (List<String> args) async { | |
| 143 CommandLineOptions options = CommandLineOptions.parse(args); | |
| 144 return await _analyzeAll(options); | |
| 145 }); | |
| 146 } else { | |
| 147 ErrorSeverity severity = await _analyzeAll(options); | |
| 148 // In case of error propagate exit code. | |
| 149 if (severity == ErrorSeverity.ERROR) { | |
| 150 io.exitCode = severity.ordinal; | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 if (_context != null) { | |
| 155 _analyzedFileCount += _context.sources.length; | |
| 156 } | |
| 157 | |
| 158 if (options.perfReport != null) { | |
| 159 String json = makePerfReport( | |
| 160 startTime, currentTimeMillis, options, _analyzedFileCount, stats); | |
| 161 new io.File(options.perfReport).writeAsStringSync(json); | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 Future<ErrorSeverity> _analyzeAll(CommandLineOptions options) async { | |
| 166 PerformanceTag previous = _analyzeAllTag.makeCurrent(); | |
| 167 try { | |
| 168 return await _analyzeAllImpl(options); | |
| 169 } finally { | |
| 170 previous.makeCurrent(); | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 /// Perform analysis according to the given [options]. | |
| 175 Future<ErrorSeverity> _analyzeAllImpl(CommandLineOptions options) async { | |
| 176 if (!options.machineFormat) { | |
| 177 List<String> fileNames = options.sourceFiles.map((String file) { | |
| 178 file = path.normalize(file); | |
| 179 if (file == '.') { | |
| 180 file = path.basename(path.current); | |
| 181 } else if (file == '..') { | |
| 182 file = path.basename(path.normalize(path.absolute(file))); | |
| 183 } | |
| 184 return file; | |
| 185 }).toList(); | |
| 186 | |
| 187 outSink.writeln("Analyzing ${fileNames.join(', ')}..."); | |
| 188 } | |
| 189 | |
| 190 // Create a context, or re-use the previous one. | |
| 191 try { | |
| 192 _createAnalysisContext(options); | |
| 193 } on _DriverError catch (error) { | |
| 194 outSink.writeln(error.msg); | |
| 195 return ErrorSeverity.ERROR; | |
| 196 } | |
| 197 | |
| 198 // Add all the files to be analyzed en masse to the context. Skip any | |
| 199 // files that were added earlier (whether explicitly or implicitly) to | |
| 200 // avoid causing those files to be unnecessarily re-read. | |
| 201 Set<Source> knownSources = context.sources.toSet(); | |
| 202 Set<Source> sourcesToAnalyze = new Set<Source>(); | |
| 203 ChangeSet changeSet = new ChangeSet(); | |
| 204 for (String sourcePath in options.sourceFiles) { | |
| 205 sourcePath = sourcePath.trim(); | |
| 206 | |
| 207 // Collect files for analysis. | |
| 208 // Note that these files will all be analyzed in the same context. | |
| 209 // This should be updated when the ContextManager re-work is complete | |
| 210 // (See: https://github.com/dart-lang/sdk/issues/24133) | |
| 211 Iterable<io.File> files = _collectFiles(sourcePath); | |
| 212 if (files.isEmpty) { | |
| 213 errorSink.writeln('No dart files found at: $sourcePath'); | |
| 214 io.exitCode = ErrorSeverity.ERROR.ordinal; | |
| 215 return ErrorSeverity.ERROR; | |
| 216 } | |
| 217 | |
| 218 for (io.File file in files) { | |
| 219 Source source = _computeLibrarySource(file.absolute.path); | |
| 220 if (!knownSources.contains(source)) { | |
| 221 changeSet.addedSource(source); | |
| 222 } | |
| 223 sourcesToAnalyze.add(source); | |
| 224 } | |
| 225 | |
| 226 if (analysisDriver == null) { | |
| 227 context.applyChanges(changeSet); | |
| 228 } | |
| 229 } | |
| 230 | |
| 231 // Analyze the libraries. | |
| 232 ErrorSeverity allResult = ErrorSeverity.NONE; | |
| 233 List<Uri> libUris = <Uri>[]; | |
| 234 Set<Source> partSources = new Set<Source>(); | |
| 235 | |
| 236 SeverityProcessor defaultSeverityProcessor = (AnalysisError error) { | |
| 237 return determineProcessedSeverity( | |
| 238 error, options, _context.analysisOptions); | |
| 239 }; | |
| 240 | |
| 241 // We currently print out to stderr to ensure that when in batch mode we | |
| 242 // print to stderr, this is because the prints from batch are made to | |
| 243 // stderr. The reason that options.shouldBatch isn't used is because when | |
| 244 // the argument flags are constructed in BatchRunner and passed in from | |
| 245 // batch mode which removes the batch flag to prevent the "cannot have the | |
| 246 // batch flag and source file" error message. | |
| 247 ErrorFormatter formatter; | |
| 248 if (options.machineFormat) { | |
| 249 formatter = new MachineErrorFormatter(errorSink, options, stats, | |
| 250 severityProcessor: defaultSeverityProcessor); | |
| 251 } else { | |
| 252 formatter = new HumanErrorFormatter(outSink, options, stats, | |
| 253 severityProcessor: defaultSeverityProcessor); | |
| 254 } | |
| 255 | |
| 256 for (Source source in sourcesToAnalyze) { | |
| 257 SourceKind sourceKind = analysisDriver != null | |
| 258 ? await analysisDriver.getSourceKind(source.fullName) | |
| 259 : context.computeKindOf(source); | |
| 260 if (sourceKind == SourceKind.PART) { | |
| 261 partSources.add(source); | |
| 262 continue; | |
| 263 } | |
| 264 ErrorSeverity status = await _runAnalyzer(source, options, formatter); | |
| 265 allResult = allResult.max(status); | |
| 266 libUris.add(source.uri); | |
| 267 } | |
| 268 | |
| 269 formatter.flush(); | |
| 270 | |
| 271 // Check that each part has a corresponding source in the input list. | |
| 272 for (Source partSource in partSources) { | |
| 273 bool found = false; | |
| 274 if (analysisDriver != null) { | |
| 275 var partFile = | |
| 276 analysisDriver.fsState.getFileForPath(partSource.fullName); | |
| 277 if (libUris.contains(partFile.library?.uri)) { | |
| 278 found = true; | |
| 279 } | |
| 280 } else { | |
| 281 for (var lib in context.getLibrariesContaining(partSource)) { | |
| 282 if (libUris.contains(lib.uri)) { | |
| 283 found = true; | |
| 284 } | |
| 285 } | |
| 286 } | |
| 287 if (!found) { | |
| 288 errorSink.writeln( | |
| 289 "${partSource.fullName} is a part and cannot be analyzed."); | |
| 290 errorSink.writeln("Please pass in a library that contains this part."); | |
| 291 io.exitCode = ErrorSeverity.ERROR.ordinal; | |
| 292 allResult = allResult.max(ErrorSeverity.ERROR); | |
| 293 } | |
| 294 } | |
| 295 | |
| 296 if (!options.machineFormat) { | |
| 297 stats.print(outSink); | |
| 298 } | |
| 299 | |
| 300 return allResult; | |
| 301 } | |
| 302 | |
| 303 /// Perform analysis in build mode according to the given [options]. | |
| 304 ErrorSeverity _buildModeAnalyze(CommandLineOptions options) { | |
| 305 return _analyzeAllTag.makeCurrentWhile(() { | |
| 306 if (options.buildModePersistentWorker) { | |
| 307 new AnalyzerWorkerLoop.std(resourceProvider, | |
| 308 dartSdkPath: options.dartSdkPath) | |
| 309 .run(); | |
| 310 } else { | |
| 311 return new BuildMode(resourceProvider, options, stats).analyze(); | |
| 312 } | |
| 313 }); | |
| 314 } | |
| 315 | |
| 316 /// Determine whether the context created during a previous call to | |
| 317 /// [_analyzeAll] can be re-used in order to analyze using [options]. | |
| 318 bool _canContextBeReused(CommandLineOptions options) { | |
| 319 // TODO(paulberry): add a command-line option that disables context re-use. | |
| 320 if (_context == null) { | |
| 321 return false; | |
| 322 } | |
| 323 if (options.packageRootPath != _previousOptions.packageRootPath) { | |
| 324 return false; | |
| 325 } | |
| 326 if (options.packageConfigPath != _previousOptions.packageConfigPath) { | |
| 327 return false; | |
| 328 } | |
| 329 if (!_equalMaps( | |
| 330 options.definedVariables, _previousOptions.definedVariables)) { | |
| 331 return false; | |
| 332 } | |
| 333 if (options.log != _previousOptions.log) { | |
| 334 return false; | |
| 335 } | |
| 336 if (options.disableHints != _previousOptions.disableHints) { | |
| 337 return false; | |
| 338 } | |
| 339 if (options.enableStrictCallChecks != | |
| 340 _previousOptions.enableStrictCallChecks) { | |
| 341 return false; | |
| 342 } | |
| 343 if (options.enableAssertInitializer != | |
| 344 _previousOptions.enableAssertInitializer) { | |
| 345 return false; | |
| 346 } | |
| 347 if (options.showPackageWarnings != _previousOptions.showPackageWarnings) { | |
| 348 return false; | |
| 349 } | |
| 350 if (options.showPackageWarningsPrefix != | |
| 351 _previousOptions.showPackageWarningsPrefix) { | |
| 352 return false; | |
| 353 } | |
| 354 if (options.showSdkWarnings != _previousOptions.showSdkWarnings) { | |
| 355 return false; | |
| 356 } | |
| 357 if (options.lints != _previousOptions.lints) { | |
| 358 return false; | |
| 359 } | |
| 360 if (options.strongMode != _previousOptions.strongMode) { | |
| 361 return false; | |
| 362 } | |
| 363 if (options.enableSuperMixins != _previousOptions.enableSuperMixins) { | |
| 364 return false; | |
| 365 } | |
| 366 if (!_equalLists( | |
| 367 options.buildSummaryInputs, _previousOptions.buildSummaryInputs)) { | |
| 368 return false; | |
| 369 } | |
| 370 if (options.disableCacheFlushing != _previousOptions.disableCacheFlushing) { | |
| 371 return false; | |
| 372 } | |
| 373 return true; | |
| 374 } | |
| 375 | |
| 376 /// Decide on the appropriate policy for which files need to be fully parsed | |
| 377 /// and which files need to be diet parsed, based on [options], and return an | |
| 378 /// [AnalyzeFunctionBodiesPredicate] that implements this policy. | |
| 379 AnalyzeFunctionBodiesPredicate _chooseDietParsingPolicy( | |
| 380 CommandLineOptions options) { | |
| 381 if (options.shouldBatch) { | |
| 382 // As analyzer is currently implemented, once a file has been diet | |
| 383 // parsed, it can't easily be un-diet parsed without creating a brand new | |
| 384 // context and losing caching. In batch mode, we can't predict which | |
| 385 // files we'll need to generate errors and warnings for in the future, so | |
| 386 // we can't safely diet parse anything. | |
| 387 return (Source source) => true; | |
| 388 } | |
| 389 | |
| 390 return (Source source) { | |
| 391 if (options.sourceFiles.contains(source.fullName)) { | |
| 392 return true; | |
| 393 } else if (source.uri.scheme == 'dart') { | |
| 394 return options.showSdkWarnings; | |
| 395 } else { | |
| 396 // TODO(paulberry): diet parse 'package:' imports when we don't want | |
| 397 // diagnostics. (Full parse is still needed for "self" packages.) | |
| 398 return true; | |
| 399 } | |
| 400 }; | |
| 401 } | |
| 402 | |
| 403 /// Decide on the appropriate method for resolving URIs based on the given | |
| 404 /// [options] and [customUrlMappings] settings, and return a | |
| 405 /// [SourceFactory] that has been configured accordingly. | |
| 406 /// When [includeSdkResolver] is `false`, return a temporary [SourceFactory] | |
| 407 /// for the purpose of resolved analysis options file `include:` directives. | |
| 408 /// In this situation, [analysisOptions] is ignored and can be `null`. | |
| 409 SourceFactory _chooseUriResolutionPolicy( | |
| 410 CommandLineOptions options, | |
| 411 Map<file_system.Folder, YamlMap> embedderMap, | |
| 412 _PackageInfo packageInfo, | |
| 413 SummaryDataStore summaryDataStore, | |
| 414 bool includeSdkResolver, | |
| 415 AnalysisOptions analysisOptions) { | |
| 416 // Create a custom package resolver if one has been specified. | |
| 417 if (packageResolverProvider != null) { | |
| 418 file_system.Folder folder = resourceProvider.getResource('.'); | |
| 419 UriResolver resolver = packageResolverProvider(folder); | |
| 420 if (resolver != null) { | |
| 421 // TODO(brianwilkerson) This doesn't handle sdk extensions. | |
| 422 List<UriResolver> resolvers = <UriResolver>[]; | |
| 423 if (includeSdkResolver) { | |
| 424 resolvers.add(new DartUriResolver(sdk)); | |
| 425 } | |
| 426 resolvers | |
| 427 .add(new InSummaryUriResolver(resourceProvider, summaryDataStore)); | |
| 428 resolvers.add(resolver); | |
| 429 resolvers.add(new file_system.ResourceUriResolver(resourceProvider)); | |
| 430 return new SourceFactory(resolvers); | |
| 431 } | |
| 432 } | |
| 433 | |
| 434 UriResolver packageUriResolver; | |
| 435 | |
| 436 if (options.packageRootPath != null) { | |
| 437 ContextBuilderOptions builderOptions = new ContextBuilderOptions(); | |
| 438 builderOptions.defaultPackagesDirectoryPath = options.packageRootPath; | |
| 439 ContextBuilder builder = new ContextBuilder(resourceProvider, null, null, | |
| 440 options: builderOptions); | |
| 441 packageUriResolver = new PackageMapUriResolver(resourceProvider, | |
| 442 builder.convertPackagesToMap(builder.createPackageMap(''))); | |
| 443 } else if (options.packageConfigPath == null) { | |
| 444 // TODO(pq): remove? | |
| 445 if (packageInfo.packageMap == null) { | |
| 446 // Fall back to pub list-package-dirs. | |
| 447 PubPackageMapProvider pubPackageMapProvider = | |
| 448 new PubPackageMapProvider(resourceProvider, sdk); | |
| 449 file_system.Resource cwd = resourceProvider.getResource('.'); | |
| 450 PackageMapInfo packageMapInfo = | |
| 451 pubPackageMapProvider.computePackageMap(cwd); | |
| 452 Map<String, List<file_system.Folder>> packageMap = | |
| 453 packageMapInfo.packageMap; | |
| 454 | |
| 455 // Only create a packageUriResolver if pub list-package-dirs succeeded. | |
| 456 // If it failed, that's not a problem; it simply means we have no way | |
| 457 // to resolve packages. | |
| 458 if (packageMapInfo.packageMap != null) { | |
| 459 packageUriResolver = | |
| 460 new PackageMapUriResolver(resourceProvider, packageMap); | |
| 461 } | |
| 462 } | |
| 463 } | |
| 464 | |
| 465 // Now, build our resolver list. | |
| 466 List<UriResolver> resolvers = []; | |
| 467 | |
| 468 // 'dart:' URIs come first. | |
| 469 | |
| 470 // Setup embedding. | |
| 471 if (includeSdkResolver) { | |
| 472 EmbedderSdk embedderSdk = new EmbedderSdk(resourceProvider, embedderMap); | |
| 473 if (embedderSdk.libraryMap.size() == 0) { | |
| 474 // The embedder uri resolver has no mappings. Use the default Dart SDK | |
| 475 // uri resolver. | |
| 476 resolvers.add(new DartUriResolver(sdk)); | |
| 477 } else { | |
| 478 // The embedder uri resolver has mappings, use it instead of the default | |
| 479 // Dart SDK uri resolver. | |
| 480 embedderSdk.analysisOptions = analysisOptions; | |
| 481 resolvers.add(new DartUriResolver(embedderSdk)); | |
| 482 } | |
| 483 } | |
| 484 | |
| 485 // Next SdkExts. | |
| 486 if (packageInfo.packageMap != null) { | |
| 487 resolvers.add(new SdkExtUriResolver(packageInfo.packageMap)); | |
| 488 } | |
| 489 | |
| 490 // Then package URIs from summaries. | |
| 491 resolvers.add(new InSummaryUriResolver(resourceProvider, summaryDataStore)); | |
| 492 | |
| 493 // Then package URIs. | |
| 494 if (packageUriResolver != null) { | |
| 495 resolvers.add(packageUriResolver); | |
| 496 } | |
| 497 | |
| 498 // Finally files. | |
| 499 resolvers.add(new file_system.ResourceUriResolver(resourceProvider)); | |
| 500 | |
| 501 return new SourceFactory(resolvers, packageInfo.packages); | |
| 502 } | |
| 503 | |
| 504 // TODO(devoncarew): This needs to respect analysis_options excludes. | |
| 505 | |
| 506 /// Collect all analyzable files at [filePath], recursively if it's a | |
| 507 /// directory, ignoring links. | |
| 508 Iterable<io.File> _collectFiles(String filePath) { | |
| 509 List<io.File> files = <io.File>[]; | |
| 510 io.File file = new io.File(filePath); | |
| 511 if (file.existsSync()) { | |
| 512 files.add(file); | |
| 513 } else { | |
| 514 io.Directory directory = new io.Directory(filePath); | |
| 515 if (directory.existsSync()) { | |
| 516 for (io.FileSystemEntity entry | |
| 517 in directory.listSync(recursive: true, followLinks: false)) { | |
| 518 String relative = path.relative(entry.path, from: directory.path); | |
| 519 if (AnalysisEngine.isDartFileName(entry.path) && | |
| 520 !_isInHiddenDir(relative)) { | |
| 521 files.add(entry); | |
| 522 } | |
| 523 } | |
| 524 } | |
| 525 } | |
| 526 return files; | |
| 527 } | |
| 528 | |
| 529 /// Convert the given [sourcePath] (which may be relative to the current | |
| 530 /// working directory) to a [Source] object that can be fed to the analysis | |
| 531 /// context. | |
| 532 Source _computeLibrarySource(String sourcePath) { | |
| 533 sourcePath = _normalizeSourcePath(sourcePath); | |
| 534 File sourceFile = resourceProvider.getFile(sourcePath); | |
| 535 Source source = sdk.fromFileUri(sourceFile.toUri()); | |
| 536 if (source != null) { | |
| 537 return source; | |
| 538 } | |
| 539 source = new FileSource(sourceFile, sourceFile.toUri()); | |
| 540 Uri uri = _context.sourceFactory.restoreUri(source); | |
| 541 if (uri == null) { | |
| 542 return source; | |
| 543 } | |
| 544 return new FileSource(sourceFile, uri); | |
| 545 } | |
| 546 | |
| 547 /// Create an analysis context that is prepared to analyze sources according | |
| 548 /// to the given [options], and store it in [_context]. | |
| 549 void _createAnalysisContext(CommandLineOptions options) { | |
| 550 if (_canContextBeReused(options)) { | |
| 551 return; | |
| 552 } | |
| 553 _previousOptions = options; | |
| 554 | |
| 555 // Save stats from previous context before clobbering it. | |
| 556 if (_context != null) { | |
| 557 _analyzedFileCount += _context.sources.length; | |
| 558 } | |
| 559 | |
| 560 // Find package info. | |
| 561 _PackageInfo packageInfo = _findPackages(options); | |
| 562 | |
| 563 // Process embedders. | |
| 564 Map<file_system.Folder, YamlMap> embedderMap = | |
| 565 new EmbedderYamlLocator(packageInfo.packageMap).embedderYamls; | |
| 566 | |
| 567 // Scan for SDK extenders. | |
| 568 bool hasSdkExt = _hasSdkExt(packageInfo.packageMap?.values); | |
| 569 | |
| 570 // No summaries in the presence of embedders or extenders. | |
| 571 bool useSummaries = embedderMap.isEmpty && !hasSdkExt; | |
| 572 | |
| 573 if (!useSummaries && options.buildSummaryInputs.isNotEmpty) { | |
| 574 throw new _DriverError( | |
| 575 'Summaries are not yet supported when using Flutter.'); | |
| 576 } | |
| 577 | |
| 578 // Read any input summaries. | |
| 579 SummaryDataStore summaryDataStore = new SummaryDataStore( | |
| 580 useSummaries ? options.buildSummaryInputs : <String>[]); | |
| 581 | |
| 582 AnalysisOptionsImpl analysisOptions = | |
| 583 createAnalysisOptionsForCommandLineOptions(resourceProvider, options); | |
| 584 analysisOptions.analyzeFunctionBodiesPredicate = | |
| 585 _chooseDietParsingPolicy(options); | |
| 586 | |
| 587 // Once options and embedders are processed, setup the SDK. | |
| 588 _setupSdk(options, useSummaries, analysisOptions); | |
| 589 | |
| 590 PackageBundle sdkBundle = sdk.getLinkedBundle(); | |
| 591 if (sdkBundle != null) { | |
| 592 summaryDataStore.addBundle(null, sdkBundle); | |
| 593 } | |
| 594 | |
| 595 // Choose a package resolution policy and a diet parsing policy based on | |
| 596 // the command-line options. | |
| 597 SourceFactory sourceFactory = _chooseUriResolutionPolicy(options, | |
| 598 embedderMap, packageInfo, summaryDataStore, true, analysisOptions); | |
| 599 | |
| 600 // Create a context. | |
| 601 _context = AnalysisEngine.instance.createAnalysisContext(); | |
| 602 setupAnalysisContext(_context, options, analysisOptions); | |
| 603 _context.sourceFactory = sourceFactory; | |
| 604 | |
| 605 if (options.enableNewAnalysisDriver) { | |
| 606 PerformanceLog log = new PerformanceLog(null); | |
| 607 AnalysisDriverScheduler scheduler = new AnalysisDriverScheduler(log); | |
| 608 analysisDriver = new AnalysisDriver( | |
| 609 scheduler, | |
| 610 log, | |
| 611 resourceProvider, | |
| 612 analysisDriverMemoryByteStore, | |
| 613 new FileContentOverlay(), | |
| 614 null, | |
| 615 context.sourceFactory, | |
| 616 context.analysisOptions); | |
| 617 analysisDriver.results.listen((_) {}); | |
| 618 analysisDriver.exceptions.listen((_) {}); | |
| 619 scheduler.start(); | |
| 620 } else { | |
| 621 if (sdkBundle != null) { | |
| 622 _context.resultProvider = | |
| 623 new InputPackagesResultProvider(_context, summaryDataStore); | |
| 624 } | |
| 625 } | |
| 626 } | |
| 627 | |
| 628 /// Return discovered packagespec, or `null` if none is found. | |
| 629 Packages _discoverPackagespec(Uri root) { | |
| 630 try { | |
| 631 Packages packages = pkg_discovery.findPackagesFromFile(root); | |
| 632 if (packages != Packages.noPackages) { | |
| 633 return packages; | |
| 634 } | |
| 635 } catch (_) { | |
| 636 // Ignore and fall through to null. | |
| 637 } | |
| 638 | |
| 639 return null; | |
| 640 } | |
| 641 | |
| 642 _PackageInfo _findPackages(CommandLineOptions options) { | |
| 643 if (packageResolverProvider != null) { | |
| 644 // The resolver provider will do all the work later. | |
| 645 return new _PackageInfo(null, null); | |
| 646 } | |
| 647 | |
| 648 Packages packages; | |
| 649 Map<String, List<file_system.Folder>> packageMap; | |
| 650 | |
| 651 if (options.packageConfigPath != null) { | |
| 652 String packageConfigPath = options.packageConfigPath; | |
| 653 Uri fileUri = new Uri.file(packageConfigPath); | |
| 654 try { | |
| 655 io.File configFile = new io.File.fromUri(fileUri).absolute; | |
| 656 List<int> bytes = configFile.readAsBytesSync(); | |
| 657 Map<String, Uri> map = pkgfile.parse(bytes, configFile.uri); | |
| 658 packages = new MapPackages(map); | |
| 659 packageMap = _getPackageMap(packages); | |
| 660 } catch (e) { | |
| 661 printAndFail( | |
| 662 'Unable to read package config data from $packageConfigPath: $e'); | |
| 663 } | |
| 664 } else if (options.packageRootPath != null) { | |
| 665 packageMap = _PackageRootPackageMapBuilder | |
| 666 .buildPackageMap(options.packageRootPath); | |
| 667 } else { | |
| 668 file_system.Resource cwd = resourceProvider.getResource('.'); | |
| 669 // Look for .packages. | |
| 670 packages = _discoverPackagespec(new Uri.directory(cwd.path)); | |
| 671 packageMap = _getPackageMap(packages); | |
| 672 } | |
| 673 | |
| 674 return new _PackageInfo(packages, packageMap); | |
| 675 } | |
| 676 | |
| 677 Map<String, List<file_system.Folder>> _getPackageMap(Packages packages) { | |
| 678 if (packages == null) { | |
| 679 return null; | |
| 680 } | |
| 681 | |
| 682 Map<String, List<file_system.Folder>> folderMap = | |
| 683 new Map<String, List<file_system.Folder>>(); | |
| 684 packages.asMap().forEach((String packagePath, Uri uri) { | |
| 685 folderMap[packagePath] = [resourceProvider.getFolder(path.fromUri(uri))]; | |
| 686 }); | |
| 687 return folderMap; | |
| 688 } | |
| 689 | |
| 690 bool _hasSdkExt(Iterable<List<file_system.Folder>> folders) { | |
| 691 if (folders != null) { | |
| 692 //TODO: ideally share this traversal with SdkExtUriResolver | |
| 693 for (Iterable<file_system.Folder> libDirs in folders) { | |
| 694 if (libDirs.any((file_system.Folder libDir) => | |
| 695 libDir.getChild(SdkExtUriResolver.SDK_EXT_NAME).exists)) { | |
| 696 return true; | |
| 697 } | |
| 698 } | |
| 699 } | |
| 700 return false; | |
| 701 } | |
| 702 | |
| 703 /// Returns `true` if this relative path is a hidden directory. | |
| 704 bool _isInHiddenDir(String relative) => | |
| 705 path.split(relative).any((part) => part.startsWith(".")); | |
| 706 | |
| 707 void _processPlugins() { | |
| 708 List<Plugin> plugins = <Plugin>[]; | |
| 709 plugins.addAll(AnalysisEngine.instance.requiredPlugins); | |
| 710 plugins.addAll(_userDefinedPlugins); | |
| 711 | |
| 712 ExtensionManager manager = new ExtensionManager(); | |
| 713 manager.processPlugins(plugins); | |
| 714 | |
| 715 linter.registerLintRules(); | |
| 716 } | |
| 717 | |
| 718 /// Analyze a single source. | |
| 719 Future<ErrorSeverity> _runAnalyzer(Source source, CommandLineOptions options, | |
| 720 ErrorFormatter formatter) async { | |
| 721 int startTime = currentTimeMillis; | |
| 722 AnalyzerImpl analyzer = new AnalyzerImpl(_context.analysisOptions, _context, | |
| 723 analysisDriver, source, options, stats, startTime); | |
| 724 ErrorSeverity errorSeverity = await analyzer.analyze(formatter); | |
| 725 if (errorSeverity == ErrorSeverity.ERROR) { | |
| 726 io.exitCode = errorSeverity.ordinal; | |
| 727 } | |
| 728 if (options.warningsAreFatal && errorSeverity == ErrorSeverity.WARNING) { | |
| 729 io.exitCode = errorSeverity.ordinal; | |
| 730 } | |
| 731 return errorSeverity; | |
| 732 } | |
| 733 | |
| 734 void _setupSdk(CommandLineOptions options, bool useSummaries, | |
| 735 AnalysisOptions analysisOptions) { | |
| 736 if (sdk == null) { | |
| 737 if (options.dartSdkSummaryPath != null) { | |
| 738 sdk = new SummaryBasedDartSdk( | |
| 739 options.dartSdkSummaryPath, options.strongMode); | |
| 740 } else { | |
| 741 String dartSdkPath = options.dartSdkPath; | |
| 742 FolderBasedDartSdk dartSdk = new FolderBasedDartSdk(resourceProvider, | |
| 743 resourceProvider.getFolder(dartSdkPath), options.strongMode); | |
| 744 dartSdk.useSummary = useSummaries && | |
| 745 options.sourceFiles.every((String sourcePath) { | |
| 746 sourcePath = path.absolute(sourcePath); | |
| 747 sourcePath = path.normalize(sourcePath); | |
| 748 return !path.isWithin(dartSdkPath, sourcePath); | |
| 749 }); | |
| 750 dartSdk.analysisOptions = analysisOptions; | |
| 751 sdk = dartSdk; | |
| 752 } | |
| 753 } | |
| 754 } | |
| 755 | |
| 756 static AnalysisOptionsImpl createAnalysisOptionsForCommandLineOptions( | |
| 757 ResourceProvider resourceProvider, CommandLineOptions options) { | |
| 758 if (options.analysisOptionsFile != null) { | |
| 759 file_system.File file = | |
| 760 resourceProvider.getFile(options.analysisOptionsFile); | |
| 761 if (!file.exists) { | |
| 762 printAndFail('Options file not found: ${options.analysisOptionsFile}', | |
| 763 exitCode: ErrorSeverity.ERROR.ordinal); | |
| 764 } | |
| 765 } | |
| 766 | |
| 767 String contextRoot; | |
| 768 if (options.sourceFiles.isEmpty) { | |
| 769 contextRoot = path.current; | |
| 770 } else { | |
| 771 contextRoot = options.sourceFiles[0]; | |
| 772 if (!path.isAbsolute(contextRoot)) { | |
| 773 contextRoot = path.absolute(contextRoot); | |
| 774 } | |
| 775 } | |
| 776 | |
| 777 void verbosePrint(String text) { | |
| 778 outSink.writeln(text); | |
| 779 } | |
| 780 | |
| 781 AnalysisOptionsImpl contextOptions = new ContextBuilder( | |
| 782 resourceProvider, null, null, | |
| 783 options: options.contextBuilderOptions) | |
| 784 .getAnalysisOptions(contextRoot, | |
| 785 verbosePrint: options.verbose ? verbosePrint : null); | |
| 786 | |
| 787 contextOptions.trackCacheDependencies = false; | |
| 788 contextOptions.disableCacheFlushing = options.disableCacheFlushing; | |
| 789 contextOptions.hint = !options.disableHints; | |
| 790 contextOptions.generateImplicitErrors = options.showPackageWarnings; | |
| 791 contextOptions.generateSdkErrors = options.showSdkWarnings; | |
| 792 contextOptions.enableAssertInitializer = options.enableAssertInitializer; | |
| 793 | |
| 794 return contextOptions; | |
| 795 } | |
| 796 | |
| 797 static void setAnalysisContextOptions( | |
| 798 file_system.ResourceProvider resourceProvider, | |
| 799 AnalysisContext context, | |
| 800 CommandLineOptions options, | |
| 801 void configureContextOptions(AnalysisOptionsImpl contextOptions)) { | |
| 802 AnalysisOptionsImpl analysisOptions = | |
| 803 createAnalysisOptionsForCommandLineOptions(resourceProvider, options); | |
| 804 configureContextOptions(analysisOptions); | |
| 805 setupAnalysisContext(context, options, analysisOptions); | |
| 806 } | |
| 807 | |
| 808 static void setupAnalysisContext(AnalysisContext context, | |
| 809 CommandLineOptions options, AnalysisOptionsImpl analysisOptions) { | |
| 810 Map<String, String> definedVariables = options.definedVariables; | |
| 811 if (definedVariables.isNotEmpty) { | |
| 812 DeclaredVariables declaredVariables = context.declaredVariables; | |
| 813 definedVariables.forEach((String variableName, String value) { | |
| 814 declaredVariables.define(variableName, value); | |
| 815 }); | |
| 816 } | |
| 817 | |
| 818 if (options.log) { | |
| 819 AnalysisEngine.instance.logger = new StdLogger(); | |
| 820 } | |
| 821 | |
| 822 // Set context options. | |
| 823 context.analysisOptions = analysisOptions; | |
| 824 } | |
| 825 | |
| 826 /// Perform a deep comparison of two string lists. | |
| 827 static bool _equalLists(List<String> l1, List<String> l2) { | |
| 828 if (l1.length != l2.length) { | |
| 829 return false; | |
| 830 } | |
| 831 for (int i = 0; i < l1.length; i++) { | |
| 832 if (l1[i] != l2[i]) { | |
| 833 return false; | |
| 834 } | |
| 835 } | |
| 836 return true; | |
| 837 } | |
| 838 | |
| 839 /// Perform a deep comparison of two string maps. | |
| 840 static bool _equalMaps(Map<String, String> m1, Map<String, String> m2) { | |
| 841 if (m1.length != m2.length) { | |
| 842 return false; | |
| 843 } | |
| 844 for (String key in m1.keys) { | |
| 845 if (!m2.containsKey(key) || m1[key] != m2[key]) { | |
| 846 return false; | |
| 847 } | |
| 848 } | |
| 849 return true; | |
| 850 } | |
| 851 | |
| 852 /// Convert [sourcePath] into an absolute path. | |
| 853 static String _normalizeSourcePath(String sourcePath) => | |
| 854 path.normalize(new io.File(sourcePath).absolute.path); | |
| 855 } | |
| 856 | |
| 857 /// Provides a framework to read command line options from stdin and feed them | |
| 858 /// to a callback. | |
| 859 class _BatchRunner { | |
| 860 /// Run the tool in 'batch' mode, receiving command lines through stdin and | |
| 861 /// returning pass/fail status through stdout. This feature is intended for | |
| 862 /// use in unit testing. | |
| 863 static void runAsBatch(List<String> sharedArgs, _BatchRunnerHandler handler) { | |
| 864 outSink.writeln('>>> BATCH START'); | |
| 865 Stopwatch stopwatch = new Stopwatch(); | |
| 866 stopwatch.start(); | |
| 867 int testsFailed = 0; | |
| 868 int totalTests = 0; | |
| 869 ErrorSeverity batchResult = ErrorSeverity.NONE; | |
| 870 // Read line from stdin. | |
| 871 Stream cmdLine = | |
| 872 io.stdin.transform(UTF8.decoder).transform(new LineSplitter()); | |
| 873 cmdLine.listen((String line) async { | |
| 874 // Maybe finish. | |
| 875 if (line.isEmpty) { | |
| 876 var time = stopwatch.elapsedMilliseconds; | |
| 877 outSink.writeln( | |
| 878 '>>> BATCH END (${totalTests - testsFailed}/$totalTests) ${time}ms')
; | |
| 879 io.exitCode = batchResult.ordinal; | |
| 880 } | |
| 881 // Prepare arguments. | |
| 882 var lineArgs = line.split(new RegExp('\\s+')); | |
| 883 var args = new List<String>(); | |
| 884 args.addAll(sharedArgs); | |
| 885 args.addAll(lineArgs); | |
| 886 args.remove('-b'); | |
| 887 args.remove('--batch'); | |
| 888 // Analyze single set of arguments. | |
| 889 try { | |
| 890 totalTests++; | |
| 891 ErrorSeverity result = await handler(args); | |
| 892 bool resultPass = result != ErrorSeverity.ERROR; | |
| 893 if (!resultPass) { | |
| 894 testsFailed++; | |
| 895 } | |
| 896 batchResult = batchResult.max(result); | |
| 897 // Write stderr end token and flush. | |
| 898 errorSink.writeln('>>> EOF STDERR'); | |
| 899 String resultPassString = resultPass ? 'PASS' : 'FAIL'; | |
| 900 outSink.writeln( | |
| 901 '>>> TEST $resultPassString ${stopwatch.elapsedMilliseconds}ms'); | |
| 902 } catch (e, stackTrace) { | |
| 903 errorSink.writeln(e); | |
| 904 errorSink.writeln(stackTrace); | |
| 905 errorSink.writeln('>>> EOF STDERR'); | |
| 906 outSink.writeln('>>> TEST CRASH'); | |
| 907 } | |
| 908 }); | |
| 909 } | |
| 910 } | |
| 911 | |
| 912 class _DriverError implements Exception { | |
| 913 String msg; | |
| 914 _DriverError(this.msg); | |
| 915 } | |
| 916 | |
| 917 class _PackageInfo { | |
| 918 Packages packages; | |
| 919 Map<String, List<file_system.Folder>> packageMap; | |
| 920 _PackageInfo(this.packages, this.packageMap); | |
| 921 } | |
| 922 | |
| 923 /// [SdkExtUriResolver] needs a Map from package name to folder. In the case | |
| 924 /// that the analyzer is invoked with a --package-root option, we need to | |
| 925 /// manually create this mapping. Given [packageRootPath], | |
| 926 /// [_PackageRootPackageMapBuilder] creates a simple mapping from package name | |
| 927 /// to full path on disk (resolving any symbolic links). | |
| 928 class _PackageRootPackageMapBuilder { | |
| 929 static Map<String, List<file_system.Folder>> buildPackageMap( | |
| 930 String packageRootPath) { | |
| 931 var packageRoot = new io.Directory(packageRootPath); | |
| 932 if (!packageRoot.existsSync()) { | |
| 933 throw new _DriverError( | |
| 934 'Package root directory ($packageRootPath) does not exist.'); | |
| 935 } | |
| 936 var packages = packageRoot.listSync(followLinks: false); | |
| 937 var result = new Map<String, List<file_system.Folder>>(); | |
| 938 for (var package in packages) { | |
| 939 var packageName = path.basename(package.path); | |
| 940 var realPath = package.resolveSymbolicLinksSync(); | |
| 941 result[packageName] = [ | |
| 942 PhysicalResourceProvider.INSTANCE.getFolder(realPath) | |
| 943 ]; | |
| 944 } | |
| 945 return result; | |
| 946 } | |
| 947 } | |
| OLD | NEW |