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'; |
| 10 |
| 11 import 'package:analyzer/file_system/file_system.dart' as fileSystem; |
| 12 import 'package:analyzer/file_system/physical_file_system.dart'; |
| 13 import 'package:analyzer/plugin/options.dart'; |
| 14 import 'package:analyzer/source/analysis_options_provider.dart'; |
| 15 import 'package:analyzer/source/package_map_provider.dart'; |
| 16 import 'package:analyzer/source/package_map_resolver.dart'; |
| 17 import 'package:analyzer/source/pub_package_map_provider.dart'; |
| 18 import 'package:analyzer/source/sdk_ext.dart'; |
| 19 import 'package:analyzer/src/generated/constant.dart'; |
| 20 import 'package:analyzer/src/generated/engine.dart'; |
| 21 import 'package:analyzer/src/generated/error.dart'; |
| 22 import 'package:analyzer/src/generated/interner.dart'; |
| 23 import 'package:analyzer/src/generated/java_engine.dart'; |
| 24 import 'package:analyzer/src/generated/java_io.dart'; |
| 25 import 'package:analyzer/src/generated/sdk_io.dart'; |
| 26 import 'package:analyzer/src/generated/source.dart'; |
| 27 import 'package:analyzer/src/generated/source_io.dart'; |
| 28 import 'package:analyzer/src/services/lint.dart'; |
| 29 import 'package:analyzer/src/task/options.dart'; |
| 30 import 'package:analyzer_cli/src/analyzer_impl.dart'; |
| 31 import 'package:analyzer_cli/src/options.dart'; |
| 32 import 'package:linter/src/plugin/linter_plugin.dart'; |
| 33 import 'package:package_config/discovery.dart' as pkgDiscovery; |
| 34 import 'package:package_config/packages.dart' show Packages; |
| 35 import 'package:package_config/packages_file.dart' as pkgfile show parse; |
| 36 import 'package:package_config/src/packages_impl.dart' show MapPackages; |
| 37 import 'package:path/path.dart' as path; |
| 38 import 'package:plugin/plugin.dart'; |
| 39 import 'package:yaml/yaml.dart'; |
| 40 |
| 41 /// The maximum number of sources for which AST structures should be kept in the |
| 42 /// cache. |
| 43 const int _maxCacheSize = 512; |
| 44 |
| 45 /// Shared IO sink for standard error reporting. |
| 46 /// |
| 47 /// *Visible for testing.* |
| 48 StringSink errorSink = stderr; |
| 49 |
| 50 /// Shared IO sink for standard out reporting. |
| 51 /// |
| 52 /// *Visible for testing.* |
| 53 StringSink outSink = stdout; |
| 54 |
| 55 /// Test this option map to see if it specifies lint rules. |
| 56 bool containsLintRuleEntry(Map<String, YamlNode> options) { |
| 57 var linterNode = options['linter']; |
| 58 return linterNode is YamlMap && linterNode.containsKey('rules'); |
| 59 } |
| 60 |
| 61 typedef ErrorSeverity _BatchRunnerHandler(List<String> args); |
| 62 |
| 63 class Driver { |
| 64 /// The plugins that are defined outside the `analyzer_cli` package. |
| 65 List<Plugin> _userDefinedPlugins = <Plugin>[]; |
| 66 |
| 67 /// Indicates whether the analyzer is running in batch mode. |
| 68 bool _isBatch; |
| 69 |
| 70 /// The context that was most recently created by a call to [_analyzeAll], or |
| 71 /// `null` if [_analyzeAll] hasn't been called yet. |
| 72 AnalysisContext _context; |
| 73 |
| 74 /// If [_context] is not `null`, the [CommandLineOptions] that guided its |
| 75 /// creation. |
| 76 CommandLineOptions _previousOptions; |
| 77 |
| 78 /// This Driver's current analysis context. |
| 79 /// |
| 80 /// *Visible for testing.* |
| 81 AnalysisContext get context => _context; |
| 82 |
| 83 /// Set the [plugins] that are defined outside the `analyzer_cli` package. |
| 84 void set userDefinedPlugins(List<Plugin> plugins) { |
| 85 _userDefinedPlugins = plugins == null ? <Plugin>[] : plugins; |
| 86 } |
| 87 |
| 88 /// Use the given command-line [args] to start this analysis driver. |
| 89 void start(List<String> args) { |
| 90 StringUtilities.INTERNER = new MappedInterner(); |
| 91 |
| 92 _processPlugins(); |
| 93 |
| 94 // Parse commandline options. |
| 95 CommandLineOptions options = CommandLineOptions.parse(args); |
| 96 |
| 97 // Cache options of interest to inform analysis. |
| 98 _setupEnv(options); |
| 99 |
| 100 // Do analysis. |
| 101 if (_isBatch) { |
| 102 _BatchRunner.runAsBatch(args, (List<String> args) { |
| 103 CommandLineOptions options = CommandLineOptions.parse(args); |
| 104 return _analyzeAll(options); |
| 105 }); |
| 106 } else { |
| 107 ErrorSeverity severity = _analyzeAll(options); |
| 108 // In case of error propagate exit code. |
| 109 if (severity == ErrorSeverity.ERROR) { |
| 110 exitCode = severity.ordinal; |
| 111 } |
| 112 } |
| 113 } |
| 114 |
| 115 /// Perform analysis according to the given [options]. |
| 116 ErrorSeverity _analyzeAll(CommandLineOptions options) { |
| 117 if (!options.machineFormat) { |
| 118 outSink.writeln("Analyzing ${options.sourceFiles}..."); |
| 119 } |
| 120 |
| 121 // Create a context, or re-use the previous one. |
| 122 try { |
| 123 _createAnalysisContext(options); |
| 124 } on _DriverError catch (error) { |
| 125 outSink.writeln(error.msg); |
| 126 return ErrorSeverity.ERROR; |
| 127 } |
| 128 |
| 129 // Add all the files to be analyzed en masse to the context. Skip any |
| 130 // files that were added earlier (whether explicitly or implicitly) to |
| 131 // avoid causing those files to be unnecessarily re-read. |
| 132 Set<Source> knownSources = _context.sources.toSet(); |
| 133 List<Source> sourcesToAnalyze = <Source>[]; |
| 134 ChangeSet changeSet = new ChangeSet(); |
| 135 for (String sourcePath in options.sourceFiles) { |
| 136 sourcePath = sourcePath.trim(); |
| 137 // Check that file exists. |
| 138 if (!new File(sourcePath).existsSync()) { |
| 139 errorSink.writeln('File not found: $sourcePath'); |
| 140 exitCode = ErrorSeverity.ERROR.ordinal; |
| 141 //Fail fast; don't analyze more files |
| 142 return ErrorSeverity.ERROR; |
| 143 } |
| 144 // Check that file is Dart file. |
| 145 if (!AnalysisEngine.isDartFileName(sourcePath)) { |
| 146 errorSink.writeln('$sourcePath is not a Dart file'); |
| 147 exitCode = ErrorSeverity.ERROR.ordinal; |
| 148 // Fail fast; don't analyze more files. |
| 149 return ErrorSeverity.ERROR; |
| 150 } |
| 151 Source source = _computeLibrarySource(sourcePath); |
| 152 if (!knownSources.contains(source)) { |
| 153 changeSet.addedSource(source); |
| 154 } |
| 155 sourcesToAnalyze.add(source); |
| 156 } |
| 157 _context.applyChanges(changeSet); |
| 158 |
| 159 // Analyze the libraries. |
| 160 ErrorSeverity allResult = ErrorSeverity.NONE; |
| 161 var libUris = <Uri>[]; |
| 162 var parts = <Source>[]; |
| 163 for (Source source in sourcesToAnalyze) { |
| 164 if (context.computeKindOf(source) == SourceKind.PART) { |
| 165 parts.add(source); |
| 166 continue; |
| 167 } |
| 168 ErrorSeverity status = _runAnalyzer(source, options); |
| 169 allResult = allResult.max(status); |
| 170 libUris.add(source.uri); |
| 171 } |
| 172 |
| 173 // Check that each part has a corresponding source in the input list. |
| 174 for (Source part in parts) { |
| 175 bool found = false; |
| 176 for (var lib in context.getLibrariesContaining(part)) { |
| 177 if (libUris.contains(lib.uri)) { |
| 178 found = true; |
| 179 } |
| 180 } |
| 181 if (!found) { |
| 182 errorSink.writeln("${part.fullName} is a part and cannot be analyzed."); |
| 183 errorSink.writeln("Please pass in a library that contains this part."); |
| 184 exitCode = ErrorSeverity.ERROR.ordinal; |
| 185 allResult = allResult.max(ErrorSeverity.ERROR); |
| 186 } |
| 187 } |
| 188 |
| 189 return allResult; |
| 190 } |
| 191 |
| 192 /// Determine whether the context created during a previous call to |
| 193 /// [_analyzeAll] can be re-used in order to analyze using [options]. |
| 194 bool _canContextBeReused(CommandLineOptions options) { |
| 195 // TODO(paulberry): add a command-line option that disables context re-use. |
| 196 if (_context == null) { |
| 197 return false; |
| 198 } |
| 199 if (options.packageRootPath != _previousOptions.packageRootPath) { |
| 200 return false; |
| 201 } |
| 202 if (options.packageConfigPath != _previousOptions.packageConfigPath) { |
| 203 return false; |
| 204 } |
| 205 if (!_equalMaps( |
| 206 options.definedVariables, _previousOptions.definedVariables)) { |
| 207 return false; |
| 208 } |
| 209 if (options.log != _previousOptions.log) { |
| 210 return false; |
| 211 } |
| 212 if (options.disableHints != _previousOptions.disableHints) { |
| 213 return false; |
| 214 } |
| 215 if (options.enableStrictCallChecks != |
| 216 _previousOptions.enableStrictCallChecks) { |
| 217 return false; |
| 218 } |
| 219 if (options.showPackageWarnings != _previousOptions.showPackageWarnings) { |
| 220 return false; |
| 221 } |
| 222 if (options.showSdkWarnings != _previousOptions.showSdkWarnings) { |
| 223 return false; |
| 224 } |
| 225 if (options.lints != _previousOptions.lints) { |
| 226 return false; |
| 227 } |
| 228 if (options.strongMode != _previousOptions.strongMode) { |
| 229 return false; |
| 230 } |
| 231 if (options.enableSuperMixins != _previousOptions.enableSuperMixins) { |
| 232 return false; |
| 233 } |
| 234 return true; |
| 235 } |
| 236 |
| 237 /// Decide on the appropriate policy for which files need to be fully parsed |
| 238 /// and which files need to be diet parsed, based on [options], and return an |
| 239 /// [AnalyzeFunctionBodiesPredicate] that implements this policy. |
| 240 AnalyzeFunctionBodiesPredicate _chooseDietParsingPolicy( |
| 241 CommandLineOptions options) { |
| 242 if (_isBatch) { |
| 243 // As analyzer is currently implemented, once a file has been diet |
| 244 // parsed, it can't easily be un-diet parsed without creating a brand new |
| 245 // context and losing caching. In batch mode, we can't predict which |
| 246 // files we'll need to generate errors and warnings for in the future, so |
| 247 // we can't safely diet parse anything. |
| 248 return (Source source) => true; |
| 249 } |
| 250 |
| 251 // Determine the set of packages requiring a full parse. Use null to |
| 252 // represent the case where all packages require a full parse. |
| 253 Set<String> packagesRequiringFullParse; |
| 254 if (options.showPackageWarnings) { |
| 255 // We are showing warnings from all packages so all packages require a |
| 256 // full parse. |
| 257 packagesRequiringFullParse = null; |
| 258 } else { |
| 259 // We aren't showing warnings for dependent packages, but we may still |
| 260 // need to show warnings for "self" packages, so we need to do a full |
| 261 // parse in any package containing files mentioned on the command line. |
| 262 // TODO(paulberry): implement this. As a temporary workaround, we're |
| 263 // fully parsing all packages. |
| 264 packagesRequiringFullParse = null; |
| 265 } |
| 266 return (Source source) { |
| 267 if (source.uri.scheme == 'dart') { |
| 268 return options.showSdkWarnings; |
| 269 } else if (source.uri.scheme == 'package') { |
| 270 if (packagesRequiringFullParse == null) { |
| 271 return true; |
| 272 } else if (source.uri.pathSegments.length == 0) { |
| 273 // We should never see a URI like this, but fully parse it to be |
| 274 // safe. |
| 275 return true; |
| 276 } else { |
| 277 return packagesRequiringFullParse |
| 278 .contains(source.uri.pathSegments[0]); |
| 279 } |
| 280 } else { |
| 281 return true; |
| 282 } |
| 283 }; |
| 284 } |
| 285 |
| 286 /// Decide on the appropriate method for resolving URIs based on the given |
| 287 /// [options] and [customUrlMappings] settings, and return a |
| 288 /// [SourceFactory] that has been configured accordingly. |
| 289 SourceFactory _chooseUriResolutionPolicy(CommandLineOptions options) { |
| 290 Packages packages; |
| 291 Map<String, List<fileSystem.Folder>> packageMap; |
| 292 UriResolver packageUriResolver; |
| 293 |
| 294 // Process options, caching package resolution details. |
| 295 if (options.packageConfigPath != null) { |
| 296 String packageConfigPath = options.packageConfigPath; |
| 297 Uri fileUri = new Uri.file(packageConfigPath); |
| 298 try { |
| 299 File configFile = new File.fromUri(fileUri).absolute; |
| 300 List<int> bytes = configFile.readAsBytesSync(); |
| 301 Map<String, Uri> map = pkgfile.parse(bytes, configFile.uri); |
| 302 packages = new MapPackages(map); |
| 303 packageMap = _getPackageMap(packages); |
| 304 } catch (e) { |
| 305 printAndFail( |
| 306 'Unable to read package config data from $packageConfigPath: $e'); |
| 307 } |
| 308 } else if (options.packageRootPath != null) { |
| 309 packageMap = _PackageRootPackageMapBuilder |
| 310 .buildPackageMap(options.packageRootPath); |
| 311 |
| 312 JavaFile packageDirectory = new JavaFile(options.packageRootPath); |
| 313 packageUriResolver = new PackageUriResolver([packageDirectory]); |
| 314 } else { |
| 315 fileSystem.Resource cwd = |
| 316 PhysicalResourceProvider.INSTANCE.getResource('.'); |
| 317 |
| 318 // Look for .packages. |
| 319 packages = _discoverPackagespec(new Uri.directory(cwd.path)); |
| 320 |
| 321 if (packages != null) { |
| 322 packageMap = _getPackageMap(packages); |
| 323 } else { |
| 324 // Fall back to pub list-package-dirs. |
| 325 |
| 326 PubPackageMapProvider pubPackageMapProvider = |
| 327 new PubPackageMapProvider(PhysicalResourceProvider.INSTANCE, sdk); |
| 328 PackageMapInfo packageMapInfo = |
| 329 pubPackageMapProvider.computePackageMap(cwd); |
| 330 packageMap = packageMapInfo.packageMap; |
| 331 |
| 332 // Only create a packageUriResolver if pub list-package-dirs succeeded. |
| 333 // If it failed, that's not a problem; it simply means we have no way |
| 334 // to resolve packages. |
| 335 if (packageMapInfo.packageMap != null) { |
| 336 packageUriResolver = new PackageMapUriResolver( |
| 337 PhysicalResourceProvider.INSTANCE, packageMap); |
| 338 } |
| 339 } |
| 340 } |
| 341 |
| 342 // Now, build our resolver list. |
| 343 |
| 344 // 'dart:' URIs come first. |
| 345 List<UriResolver> resolvers = [new DartUriResolver(sdk)]; |
| 346 |
| 347 // Next SdkExts. |
| 348 if (packageMap != null) { |
| 349 resolvers.add(new SdkExtUriResolver(packageMap)); |
| 350 } |
| 351 |
| 352 // Then package URIs. |
| 353 if (packageUriResolver != null) { |
| 354 resolvers.add(packageUriResolver); |
| 355 } |
| 356 |
| 357 // Finally files. |
| 358 resolvers.add(new FileUriResolver()); |
| 359 |
| 360 return new SourceFactory(resolvers, packages); |
| 361 } |
| 362 |
| 363 /// Convert the given [sourcePath] (which may be relative to the current |
| 364 /// working directory) to a [Source] object that can be fed to the analysis |
| 365 /// context. |
| 366 Source _computeLibrarySource(String sourcePath) { |
| 367 sourcePath = _normalizeSourcePath(sourcePath); |
| 368 JavaFile sourceFile = new JavaFile(sourcePath); |
| 369 Source source = sdk.fromFileUri(sourceFile.toURI()); |
| 370 if (source != null) { |
| 371 return source; |
| 372 } |
| 373 source = new FileBasedSource(sourceFile, sourceFile.toURI()); |
| 374 Uri uri = _context.sourceFactory.restoreUri(source); |
| 375 if (uri == null) { |
| 376 return source; |
| 377 } |
| 378 return new FileBasedSource(sourceFile, uri); |
| 379 } |
| 380 |
| 381 /// Create an analysis context that is prepared to analyze sources according |
| 382 /// to the given [options], and store it in [_context]. |
| 383 void _createAnalysisContext(CommandLineOptions options) { |
| 384 if (_canContextBeReused(options)) { |
| 385 return; |
| 386 } |
| 387 _previousOptions = options; |
| 388 // Choose a package resolution policy and a diet parsing policy based on |
| 389 // the command-line options. |
| 390 SourceFactory sourceFactory = _chooseUriResolutionPolicy(options); |
| 391 AnalyzeFunctionBodiesPredicate dietParsingPolicy = |
| 392 _chooseDietParsingPolicy(options); |
| 393 // Create a context using these policies. |
| 394 AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); |
| 395 |
| 396 context.sourceFactory = sourceFactory; |
| 397 |
| 398 Map<String, String> definedVariables = options.definedVariables; |
| 399 if (!definedVariables.isEmpty) { |
| 400 DeclaredVariables declaredVariables = context.declaredVariables; |
| 401 definedVariables.forEach((String variableName, String value) { |
| 402 declaredVariables.define(variableName, value); |
| 403 }); |
| 404 } |
| 405 |
| 406 if (options.log) { |
| 407 AnalysisEngine.instance.logger = new StdLogger(); |
| 408 } |
| 409 |
| 410 // Set context options. |
| 411 AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl(); |
| 412 contextOptions.cacheSize = _maxCacheSize; |
| 413 contextOptions.hint = !options.disableHints; |
| 414 contextOptions.enableStrictCallChecks = options.enableStrictCallChecks; |
| 415 contextOptions.enableSuperMixins = options.enableSuperMixins; |
| 416 contextOptions.analyzeFunctionBodiesPredicate = dietParsingPolicy; |
| 417 contextOptions.generateImplicitErrors = options.showPackageWarnings; |
| 418 contextOptions.generateSdkErrors = options.showSdkWarnings; |
| 419 contextOptions.lint = options.lints; |
| 420 contextOptions.strongMode = options.strongMode; |
| 421 context.analysisOptions = contextOptions; |
| 422 _context = context; |
| 423 |
| 424 // Process analysis options file (and notify all interested parties). |
| 425 _processAnalysisOptions(options, context); |
| 426 } |
| 427 |
| 428 /// Return discovered packagespec, or `null` if none is found. |
| 429 Packages _discoverPackagespec(Uri root) { |
| 430 try { |
| 431 Packages packages = pkgDiscovery.findPackagesFromFile(root); |
| 432 if (packages != Packages.noPackages) { |
| 433 return packages; |
| 434 } |
| 435 } catch (_) { |
| 436 // Ignore and fall through to null. |
| 437 } |
| 438 |
| 439 return null; |
| 440 } |
| 441 |
| 442 fileSystem.File _getOptionsFile(CommandLineOptions options) { |
| 443 fileSystem.File file; |
| 444 String filePath = options.analysisOptionsFile; |
| 445 if (filePath != null) { |
| 446 file = PhysicalResourceProvider.INSTANCE.getFile(filePath); |
| 447 if (!file.exists) { |
| 448 printAndFail('Options file not found: $filePath', |
| 449 exitCode: ErrorSeverity.ERROR.ordinal); |
| 450 } |
| 451 } else { |
| 452 filePath = AnalysisEngine.ANALYSIS_OPTIONS_FILE; |
| 453 file = PhysicalResourceProvider.INSTANCE.getFile(filePath); |
| 454 } |
| 455 return file; |
| 456 } |
| 457 |
| 458 Map<String, List<fileSystem.Folder>> _getPackageMap(Packages packages) { |
| 459 if (packages == null) { |
| 460 return null; |
| 461 } |
| 462 |
| 463 Map<String, List<fileSystem.Folder>> folderMap = |
| 464 new Map<String, List<fileSystem.Folder>>(); |
| 465 packages.asMap().forEach((String packagePath, Uri uri) { |
| 466 folderMap[packagePath] = [ |
| 467 PhysicalResourceProvider.INSTANCE.getFolder(path.fromUri(uri)) |
| 468 ]; |
| 469 }); |
| 470 return folderMap; |
| 471 } |
| 472 |
| 473 void _processAnalysisOptions( |
| 474 CommandLineOptions options, AnalysisContext context) { |
| 475 fileSystem.File file = _getOptionsFile(options); |
| 476 List<OptionsProcessor> optionsProcessors = |
| 477 AnalysisEngine.instance.optionsPlugin.optionsProcessors; |
| 478 try { |
| 479 AnalysisOptionsProvider analysisOptionsProvider = |
| 480 new AnalysisOptionsProvider(); |
| 481 Map<String, YamlNode> optionMap = |
| 482 analysisOptionsProvider.getOptionsFromFile(file); |
| 483 optionsProcessors.forEach( |
| 484 (OptionsProcessor p) => p.optionsProcessed(context, optionMap)); |
| 485 |
| 486 // Fill in lint rule defaults in case lints are enabled and rules are |
| 487 // not specified in an options file. |
| 488 if (options.lints && !containsLintRuleEntry(optionMap)) { |
| 489 setLints(context, linterPlugin.contributedRules); |
| 490 } |
| 491 |
| 492 // Ask engine to further process options. |
| 493 if (optionMap != null) { |
| 494 configureContextOptions(context, optionMap); |
| 495 } |
| 496 } on Exception catch (e) { |
| 497 optionsProcessors.forEach((OptionsProcessor p) => p.onError(e)); |
| 498 } |
| 499 } |
| 500 |
| 501 void _processPlugins() { |
| 502 List<Plugin> plugins = <Plugin>[]; |
| 503 plugins.add(linterPlugin); |
| 504 plugins.addAll(_userDefinedPlugins); |
| 505 AnalysisEngine.instance.userDefinedPlugins = plugins; |
| 506 |
| 507 // This ensures that AE extension manager processes plugins. |
| 508 AnalysisEngine.instance.taskManager; |
| 509 } |
| 510 |
| 511 /// Analyze a single source. |
| 512 ErrorSeverity _runAnalyzer(Source source, CommandLineOptions options) { |
| 513 int startTime = currentTimeMillis(); |
| 514 AnalyzerImpl analyzer = |
| 515 new AnalyzerImpl(_context, source, options, startTime); |
| 516 var errorSeverity = analyzer.analyzeSync(); |
| 517 if (errorSeverity == ErrorSeverity.ERROR) { |
| 518 exitCode = errorSeverity.ordinal; |
| 519 } |
| 520 if (options.warningsAreFatal && errorSeverity == ErrorSeverity.WARNING) { |
| 521 exitCode = errorSeverity.ordinal; |
| 522 } |
| 523 return errorSeverity; |
| 524 } |
| 525 |
| 526 void _setupEnv(CommandLineOptions options) { |
| 527 // In batch mode, SDK is specified on the main command line rather than in |
| 528 // the command lines sent to stdin. So process it before deciding whether |
| 529 // to activate batch mode. |
| 530 if (sdk == null) { |
| 531 sdk = new DirectoryBasedDartSdk(new JavaFile(options.dartSdkPath)); |
| 532 } |
| 533 _isBatch = options.shouldBatch; |
| 534 } |
| 535 |
| 536 /// Perform a deep comparison of two string maps. |
| 537 static bool _equalMaps(Map<String, String> m1, Map<String, String> m2) { |
| 538 if (m1.length != m2.length) { |
| 539 return false; |
| 540 } |
| 541 for (String key in m1.keys) { |
| 542 if (!m2.containsKey(key) || m1[key] != m2[key]) { |
| 543 return false; |
| 544 } |
| 545 } |
| 546 return true; |
| 547 } |
| 548 |
| 549 /// Convert [sourcePath] into an absolute path. |
| 550 static String _normalizeSourcePath(String sourcePath) => |
| 551 path.normalize(new File(sourcePath).absolute.path); |
| 552 } |
| 553 |
| 554 /// Provides a framework to read command line options from stdin and feed them |
| 555 /// to a callback. |
| 556 class _BatchRunner { |
| 557 /// Run the tool in 'batch' mode, receiving command lines through stdin and |
| 558 /// returning pass/fail status through stdout. This feature is intended for |
| 559 /// use in unit testing. |
| 560 static void runAsBatch(List<String> sharedArgs, _BatchRunnerHandler handler) { |
| 561 outSink.writeln('>>> BATCH START'); |
| 562 Stopwatch stopwatch = new Stopwatch(); |
| 563 stopwatch.start(); |
| 564 int testsFailed = 0; |
| 565 int totalTests = 0; |
| 566 ErrorSeverity batchResult = ErrorSeverity.NONE; |
| 567 // Read line from stdin. |
| 568 Stream cmdLine = |
| 569 stdin.transform(UTF8.decoder).transform(new LineSplitter()); |
| 570 cmdLine.listen((String line) { |
| 571 // Maybe finish. |
| 572 if (line.isEmpty) { |
| 573 var time = stopwatch.elapsedMilliseconds; |
| 574 outSink.writeln( |
| 575 '>>> BATCH END (${totalTests - testsFailed}/$totalTests) ${time}ms')
; |
| 576 exitCode = batchResult.ordinal; |
| 577 } |
| 578 // Prepare aruments. |
| 579 var args; |
| 580 { |
| 581 var lineArgs = line.split(new RegExp('\\s+')); |
| 582 args = new List<String>(); |
| 583 args.addAll(sharedArgs); |
| 584 args.addAll(lineArgs); |
| 585 args.remove('-b'); |
| 586 args.remove('--batch'); |
| 587 } |
| 588 // Analyze single set of arguments. |
| 589 try { |
| 590 totalTests++; |
| 591 ErrorSeverity result = handler(args); |
| 592 bool resultPass = result != ErrorSeverity.ERROR; |
| 593 if (!resultPass) { |
| 594 testsFailed++; |
| 595 } |
| 596 batchResult = batchResult.max(result); |
| 597 // Write stderr end token and flush. |
| 598 errorSink.writeln('>>> EOF STDERR'); |
| 599 String resultPassString = resultPass ? 'PASS' : 'FAIL'; |
| 600 outSink.writeln( |
| 601 '>>> TEST $resultPassString ${stopwatch.elapsedMilliseconds}ms'); |
| 602 } catch (e, stackTrace) { |
| 603 errorSink.writeln(e); |
| 604 errorSink.writeln(stackTrace); |
| 605 errorSink.writeln('>>> EOF STDERR'); |
| 606 outSink.writeln('>>> TEST CRASH'); |
| 607 } |
| 608 }); |
| 609 } |
| 610 } |
| 611 |
| 612 class _DriverError implements Exception { |
| 613 String msg; |
| 614 _DriverError(this.msg); |
| 615 } |
| 616 |
| 617 /// [SdkExtUriResolver] needs a Map from package name to folder. In the case |
| 618 /// that the analyzer is invoked with a --package-root option, we need to |
| 619 /// manually create this mapping. Given [packageRootPath], |
| 620 /// [_PackageRootPackageMapBuilder] creates a simple mapping from package name |
| 621 /// to full path on disk (resolving any symbolic links). |
| 622 class _PackageRootPackageMapBuilder { |
| 623 static Map<String, List<fileSystem.Folder>> buildPackageMap( |
| 624 String packageRootPath) { |
| 625 var packageRoot = new Directory(packageRootPath); |
| 626 if (!packageRoot.existsSync()) { |
| 627 throw new _DriverError( |
| 628 'Package root directory ($packageRootPath) does not exist.'); |
| 629 } |
| 630 var packages = packageRoot.listSync(followLinks: false); |
| 631 var result = new Map<String, List<fileSystem.Folder>>(); |
| 632 for (var package in packages) { |
| 633 var packageName = path.basename(package.path); |
| 634 var realPath = package.resolveSymbolicLinksSync(); |
| 635 result[packageName] = [ |
| 636 PhysicalResourceProvider.INSTANCE.getFolder(realPath) |
| 637 ]; |
| 638 } |
| 639 return result; |
| 640 } |
| 641 } |
OLD | NEW |