| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, 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_impl; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 import 'dart:io'; | |
| 10 | |
| 11 import 'package:analyzer/file_system/file_system.dart' show Folder; | |
| 12 import 'package:analyzer/file_system/physical_file_system.dart'; | |
| 13 import 'package:analyzer/source/package_map_provider.dart'; | |
| 14 import 'package:analyzer/source/package_map_resolver.dart'; | |
| 15 import 'package:analyzer/source/pub_package_map_provider.dart'; | |
| 16 import 'package:analyzer/src/error_formatter.dart'; | |
| 17 import 'package:analyzer/src/generated/java_core.dart' show JavaSystem; | |
| 18 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 19 import 'package:analyzer/src/generated/utilities_general.dart'; | |
| 20 | |
| 21 import '../options.dart'; | |
| 22 import 'generated/constant.dart'; | |
| 23 import 'generated/element.dart'; | |
| 24 import 'generated/engine.dart'; | |
| 25 import 'generated/error.dart'; | |
| 26 import 'generated/java_io.dart'; | |
| 27 import 'generated/sdk_io.dart'; | |
| 28 import 'generated/source_io.dart'; | |
| 29 | |
| 30 DirectoryBasedDartSdk sdk; | |
| 31 | |
| 32 /** | |
| 33 * The maximum number of sources for which AST structures should be kept in the
cache. | |
| 34 */ | |
| 35 const int _MAX_CACHE_SIZE = 512; | |
| 36 | |
| 37 /// Analyzes single library [File]. | |
| 38 class AnalyzerImpl { | |
| 39 final String sourcePath; | |
| 40 | |
| 41 final CommandLineOptions options; | |
| 42 final int startTime; | |
| 43 | |
| 44 /** | |
| 45 * True if the analyzer is running in batch mode. | |
| 46 */ | |
| 47 final bool isBatch; | |
| 48 | |
| 49 ContentCache contentCache = new ContentCache(); | |
| 50 | |
| 51 SourceFactory sourceFactory; | |
| 52 AnalysisContext context; | |
| 53 Source librarySource; | |
| 54 /// All [Source]s references by the analyzed library. | |
| 55 final Set<Source> sources = new Set<Source>(); | |
| 56 | |
| 57 /// All [AnalysisErrorInfo]s in the analyzed library. | |
| 58 final List<AnalysisErrorInfo> errorInfos = new List<AnalysisErrorInfo>(); | |
| 59 | |
| 60 /// [HashMap] between sources and analysis error infos. | |
| 61 final HashMap<Source, AnalysisErrorInfo> sourceErrorsMap = | |
| 62 new HashMap<Source, AnalysisErrorInfo>(); | |
| 63 | |
| 64 /** | |
| 65 * If the file specified on the command line is part of a package, the name | |
| 66 * of that package. Otherwise `null`. This allows us to analyze the file | |
| 67 * specified on the command line as though it is reached via a "package:" | |
| 68 * URI, but avoid suppressing its output in the event that the user has not | |
| 69 * specified the "--package-warnings" option. | |
| 70 */ | |
| 71 String _selfPackageName; | |
| 72 | |
| 73 AnalyzerImpl(String sourcePath, this.options, this.startTime, this.isBatch) | |
| 74 : sourcePath = _normalizeSourcePath(sourcePath) { | |
| 75 if (sdk == null) { | |
| 76 sdk = new DirectoryBasedDartSdk(new JavaFile(options.dartSdkPath)); | |
| 77 } | |
| 78 } | |
| 79 | |
| 80 /// Returns the maximal [ErrorSeverity] of the recorded errors. | |
| 81 ErrorSeverity get maxErrorSeverity { | |
| 82 var status = ErrorSeverity.NONE; | |
| 83 for (AnalysisErrorInfo errorInfo in errorInfos) { | |
| 84 for (AnalysisError error in errorInfo.errors) { | |
| 85 if (!_isDesiredError(error)) { | |
| 86 continue; | |
| 87 } | |
| 88 var severity = computeSeverity(error, options.enableTypeChecks); | |
| 89 status = status.max(severity); | |
| 90 } | |
| 91 } | |
| 92 return status; | |
| 93 } | |
| 94 | |
| 95 void addCompilationUnitSource(CompilationUnitElement unit, | |
| 96 Set<LibraryElement> libraries, Set<CompilationUnitElement> units) { | |
| 97 if (unit == null || units.contains(unit)) { | |
| 98 return; | |
| 99 } | |
| 100 units.add(unit); | |
| 101 sources.add(unit.source); | |
| 102 } | |
| 103 | |
| 104 void addLibrarySources(LibraryElement library, Set<LibraryElement> libraries, | |
| 105 Set<CompilationUnitElement> units) { | |
| 106 if (library == null || !libraries.add(library)) { | |
| 107 return; | |
| 108 } | |
| 109 // may be skip library | |
| 110 { | |
| 111 UriKind uriKind = library.source.uriKind; | |
| 112 // Optionally skip package: libraries. | |
| 113 if (!options.showPackageWarnings && _isOtherPackage(library.source.uri)) { | |
| 114 return; | |
| 115 } | |
| 116 // Optionally skip SDK libraries. | |
| 117 if (!options.showSdkWarnings && uriKind == UriKind.DART_URI) { | |
| 118 return; | |
| 119 } | |
| 120 } | |
| 121 // add compilation units | |
| 122 addCompilationUnitSource(library.definingCompilationUnit, libraries, units); | |
| 123 for (CompilationUnitElement child in library.parts) { | |
| 124 addCompilationUnitSource(child, libraries, units); | |
| 125 } | |
| 126 // add referenced libraries | |
| 127 for (LibraryElement child in library.importedLibraries) { | |
| 128 addLibrarySources(child, libraries, units); | |
| 129 } | |
| 130 for (LibraryElement child in library.exportedLibraries) { | |
| 131 addLibrarySources(child, libraries, units); | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 /** | |
| 136 * Treats the [sourcePath] as the top level library and analyzes it using a | |
| 137 * asynchronous algorithm over the analysis engine. | |
| 138 */ | |
| 139 void analyzeAsync() { | |
| 140 setupForAnalysis(); | |
| 141 _analyzeAsync(); | |
| 142 } | |
| 143 | |
| 144 /** | |
| 145 * Treats the [sourcePath] as the top level library and analyzes it using a | |
| 146 * synchronous algorithm over the analysis engine. If [printMode] is `0`, | |
| 147 * then no error or performance information is printed. If [printMode] is `1`, | |
| 148 * then both will be printed. If [printMode] is `2`, then only performance | |
| 149 * information will be printed, and it will be marked as being for a cold VM. | |
| 150 */ | |
| 151 ErrorSeverity analyzeSync({int printMode: 1}) { | |
| 152 setupForAnalysis(); | |
| 153 return _analyzeSync(printMode); | |
| 154 } | |
| 155 | |
| 156 Source computeLibrarySource() { | |
| 157 JavaFile sourceFile = new JavaFile(sourcePath); | |
| 158 Source source = sdk.fromFileUri(sourceFile.toURI()); | |
| 159 if (source != null) { | |
| 160 return source; | |
| 161 } | |
| 162 source = new FileBasedSource.con2(sourceFile.toURI(), sourceFile); | |
| 163 Uri uri = context.sourceFactory.restoreUri(source); | |
| 164 if (uri == null) { | |
| 165 return source; | |
| 166 } | |
| 167 return new FileBasedSource.con2(uri, sourceFile); | |
| 168 } | |
| 169 | |
| 170 /** | |
| 171 * Create and return the source factory to be used by the analysis context. | |
| 172 */ | |
| 173 SourceFactory createSourceFactory() { | |
| 174 List<UriResolver> resolvers = [ | |
| 175 new CustomUriResolver(options.customUrlMappings), | |
| 176 new DartUriResolver(sdk) | |
| 177 ]; | |
| 178 if (options.packageRootPath != null) { | |
| 179 JavaFile packageDirectory = new JavaFile(options.packageRootPath); | |
| 180 resolvers.add(new PackageUriResolver([packageDirectory])); | |
| 181 } else { | |
| 182 PubPackageMapProvider pubPackageMapProvider = | |
| 183 new PubPackageMapProvider(PhysicalResourceProvider.INSTANCE, sdk); | |
| 184 PackageMapInfo packageMapInfo = pubPackageMapProvider.computePackageMap( | |
| 185 PhysicalResourceProvider.INSTANCE.getResource('.')); | |
| 186 Map<String, List<Folder>> packageMap = packageMapInfo.packageMap; | |
| 187 if (packageMap != null) { | |
| 188 resolvers.add(new PackageMapUriResolver( | |
| 189 PhysicalResourceProvider.INSTANCE, packageMap)); | |
| 190 } | |
| 191 } | |
| 192 resolvers.add(new FileUriResolver()); | |
| 193 return new SourceFactory(resolvers); | |
| 194 } | |
| 195 | |
| 196 void prepareAnalysisContext() { | |
| 197 sourceFactory = createSourceFactory(); | |
| 198 context = AnalysisEngine.instance.createAnalysisContext(); | |
| 199 context.sourceFactory = sourceFactory; | |
| 200 Map<String, String> definedVariables = options.definedVariables; | |
| 201 if (!definedVariables.isEmpty) { | |
| 202 DeclaredVariables declaredVariables = context.declaredVariables; | |
| 203 definedVariables.forEach((String variableName, String value) { | |
| 204 declaredVariables.define(variableName, value); | |
| 205 }); | |
| 206 } | |
| 207 // Uncomment the following to have errors reported on stdout and stderr | |
| 208 AnalysisEngine.instance.logger = new StdLogger(options.log); | |
| 209 | |
| 210 // set options for context | |
| 211 AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl(); | |
| 212 contextOptions.cacheSize = _MAX_CACHE_SIZE; | |
| 213 contextOptions.hint = !options.disableHints; | |
| 214 contextOptions.enableNullAwareOperators = options.enableNullAwareOperators; | |
| 215 contextOptions.enableStrictCallChecks = options.enableStrictCallChecks; | |
| 216 contextOptions.analyzeFunctionBodiesPredicate = | |
| 217 _analyzeFunctionBodiesPredicate; | |
| 218 contextOptions.generateImplicitErrors = options.showPackageWarnings; | |
| 219 contextOptions.generateSdkErrors = options.showSdkWarnings; | |
| 220 context.analysisOptions = contextOptions; | |
| 221 | |
| 222 librarySource = computeLibrarySource(); | |
| 223 | |
| 224 Uri libraryUri = librarySource.uri; | |
| 225 if (libraryUri.scheme == 'package' && libraryUri.pathSegments.length > 0) { | |
| 226 _selfPackageName = libraryUri.pathSegments[0]; | |
| 227 } | |
| 228 | |
| 229 // Create and add a ChangeSet | |
| 230 ChangeSet changeSet = new ChangeSet(); | |
| 231 changeSet.addedSource(librarySource); | |
| 232 context.applyChanges(changeSet); | |
| 233 } | |
| 234 | |
| 235 /// Fills [errorInfos] using [sources]. | |
| 236 void prepareErrors() { | |
| 237 for (Source source in sources) { | |
| 238 context.computeErrors(source); | |
| 239 var sourceErrors = context.getErrors(source); | |
| 240 errorInfos.add(sourceErrors); | |
| 241 } | |
| 242 } | |
| 243 | |
| 244 /// Fills [sources]. | |
| 245 void prepareSources(LibraryElement library) { | |
| 246 var units = new Set<CompilationUnitElement>(); | |
| 247 var libraries = new Set<LibraryElement>(); | |
| 248 addLibrarySources(library, libraries, units); | |
| 249 } | |
| 250 | |
| 251 /** | |
| 252 * Setup local fields such as the analysis context for analysis. | |
| 253 */ | |
| 254 void setupForAnalysis() { | |
| 255 sources.clear(); | |
| 256 errorInfos.clear(); | |
| 257 if (sourcePath == null) { | |
| 258 throw new ArgumentError("sourcePath cannot be null"); | |
| 259 } | |
| 260 // prepare context | |
| 261 prepareAnalysisContext(); | |
| 262 } | |
| 263 | |
| 264 /// The async version of the analysis | |
| 265 void _analyzeAsync() { | |
| 266 new Future(context.performAnalysisTask).then((AnalysisResult result) { | |
| 267 List<ChangeNotice> notices = result.changeNotices; | |
| 268 if (result.hasMoreWork) { | |
| 269 // There is more work, record the set of sources, and then call self | |
| 270 // again to perform next task | |
| 271 for (ChangeNotice notice in notices) { | |
| 272 sources.add(notice.source); | |
| 273 sourceErrorsMap[notice.source] = notice; | |
| 274 } | |
| 275 return _analyzeAsync(); | |
| 276 } | |
| 277 // | |
| 278 // There are not any more tasks, set error code and print performance | |
| 279 // numbers. | |
| 280 // | |
| 281 // prepare errors | |
| 282 sourceErrorsMap.forEach((k, v) { | |
| 283 errorInfos.add(sourceErrorsMap[k]); | |
| 284 }); | |
| 285 | |
| 286 // print errors and performance numbers | |
| 287 _printErrorsAndPerf(); | |
| 288 | |
| 289 // compute max severity and set exitCode | |
| 290 ErrorSeverity status = maxErrorSeverity; | |
| 291 if (status == ErrorSeverity.WARNING && options.warningsAreFatal) { | |
| 292 status = ErrorSeverity.ERROR; | |
| 293 } | |
| 294 exitCode = status.ordinal; | |
| 295 }).catchError((ex, st) { | |
| 296 AnalysisEngine.instance.logger.logError("$ex\n$st"); | |
| 297 }); | |
| 298 } | |
| 299 | |
| 300 bool _analyzeFunctionBodiesPredicate(Source source) { | |
| 301 // TODO(paulberry): This function will need to be updated when we add the | |
| 302 // ability to suppress errors, warnings, and hints for files reached via | |
| 303 // custom URI's using the "--url-mapping" flag. | |
| 304 if (source.uri.scheme == 'dart') { | |
| 305 if (isBatch) { | |
| 306 // When running in batch mode, the SDK files are cached from one | |
| 307 // analysis run to the next. So we need to parse function bodies even | |
| 308 // if the user hasn't asked for errors/warnings from the SDK, since | |
| 309 // they might ask for errors/warnings from the SDK in the future. | |
| 310 return true; | |
| 311 } | |
| 312 return options.showSdkWarnings; | |
| 313 } | |
| 314 if (_isOtherPackage(source.uri)) { | |
| 315 return options.showPackageWarnings; | |
| 316 } | |
| 317 return true; | |
| 318 } | |
| 319 | |
| 320 /// The sync version of analysis. | |
| 321 ErrorSeverity _analyzeSync(int printMode) { | |
| 322 // don't try to analyze parts | |
| 323 if (context.computeKindOf(librarySource) == SourceKind.PART) { | |
| 324 print("Only libraries can be analyzed."); | |
| 325 print("$sourcePath is a part and can not be analyzed."); | |
| 326 return ErrorSeverity.ERROR; | |
| 327 } | |
| 328 // resolve library | |
| 329 var libraryElement = context.computeLibraryElement(librarySource); | |
| 330 // prepare source and errors | |
| 331 prepareSources(libraryElement); | |
| 332 prepareErrors(); | |
| 333 | |
| 334 // print errors and performance numbers | |
| 335 if (printMode == 1) { | |
| 336 _printErrorsAndPerf(); | |
| 337 } else if (printMode == 2) { | |
| 338 _printColdPerf(); | |
| 339 } | |
| 340 | |
| 341 // compute max severity and set exitCode | |
| 342 ErrorSeverity status = maxErrorSeverity; | |
| 343 if (status == ErrorSeverity.WARNING && options.warningsAreFatal) { | |
| 344 status = ErrorSeverity.ERROR; | |
| 345 } | |
| 346 return status; | |
| 347 } | |
| 348 | |
| 349 bool _isDesiredError(AnalysisError error) { | |
| 350 if (error.errorCode.type == ErrorType.TODO) { | |
| 351 return false; | |
| 352 } | |
| 353 if (computeSeverity(error, options.enableTypeChecks) == | |
| 354 ErrorSeverity.INFO && | |
| 355 options.disableHints) { | |
| 356 return false; | |
| 357 } | |
| 358 return true; | |
| 359 } | |
| 360 | |
| 361 /** | |
| 362 * Determine whether the given URI refers to a package other than the package | |
| 363 * being analyzed. | |
| 364 */ | |
| 365 bool _isOtherPackage(Uri uri) { | |
| 366 if (uri.scheme != 'package') { | |
| 367 return false; | |
| 368 } | |
| 369 if (_selfPackageName != null && | |
| 370 uri.pathSegments.length > 0 && | |
| 371 uri.pathSegments[0] == _selfPackageName) { | |
| 372 return false; | |
| 373 } | |
| 374 return true; | |
| 375 } | |
| 376 | |
| 377 _printColdPerf() { | |
| 378 // print cold VM performance numbers | |
| 379 int totalTime = JavaSystem.currentTimeMillis() - startTime; | |
| 380 int otherTime = totalTime; | |
| 381 for (PerformanceTag tag in PerformanceTag.all) { | |
| 382 if (tag != PerformanceTag.UNKNOWN) { | |
| 383 int tagTime = tag.elapsedMs; | |
| 384 stdout.writeln('${tag.label}-cold:$tagTime'); | |
| 385 otherTime -= tagTime; | |
| 386 } | |
| 387 } | |
| 388 stdout.writeln('other-cold:$otherTime'); | |
| 389 stdout.writeln("total-cold:$totalTime"); | |
| 390 } | |
| 391 | |
| 392 _printErrorsAndPerf() { | |
| 393 // The following is a hack. We currently print out to stderr to ensure that | |
| 394 // when in batch mode we print to stderr, this is because the prints from | |
| 395 // batch are made to stderr. The reason that options.shouldBatch isn't used | |
| 396 // is because when the argument flags are constructed in BatchRunner and | |
| 397 // passed in from batch mode which removes the batch flag to prevent the | |
| 398 // "cannot have the batch flag and source file" error message. | |
| 399 IOSink sink = options.machineFormat ? stderr : stdout; | |
| 400 | |
| 401 // print errors | |
| 402 ErrorFormatter formatter = | |
| 403 new ErrorFormatter(sink, options, _isDesiredError); | |
| 404 formatter.formatErrors(errorInfos); | |
| 405 | |
| 406 // print performance numbers | |
| 407 if (options.perf || options.warmPerf) { | |
| 408 int totalTime = JavaSystem.currentTimeMillis() - startTime; | |
| 409 int otherTime = totalTime; | |
| 410 for (PerformanceTag tag in PerformanceTag.all) { | |
| 411 if (tag != PerformanceTag.UNKNOWN) { | |
| 412 int tagTime = tag.elapsedMs; | |
| 413 stdout.writeln('${tag.label}:$tagTime'); | |
| 414 otherTime -= tagTime; | |
| 415 } | |
| 416 } | |
| 417 stdout.writeln('other:$otherTime'); | |
| 418 stdout.writeln("total:$totalTime"); | |
| 419 } | |
| 420 } | |
| 421 | |
| 422 /** | |
| 423 * Compute the severity of the error; however, if | |
| 424 * [enableTypeChecks] is false, then de-escalate checked-mode compile time | |
| 425 * errors to a severity of [ErrorSeverity.INFO]. | |
| 426 */ | |
| 427 static ErrorSeverity computeSeverity( | |
| 428 AnalysisError error, bool enableTypeChecks) { | |
| 429 if (!enableTypeChecks && | |
| 430 error.errorCode.type == ErrorType.CHECKED_MODE_COMPILE_TIME_ERROR) { | |
| 431 return ErrorSeverity.INFO; | |
| 432 } | |
| 433 return error.errorCode.errorSeverity; | |
| 434 } | |
| 435 | |
| 436 static JavaFile getPackageDirectoryFor(JavaFile sourceFile) { | |
| 437 // we are going to ask parent file, so get absolute path | |
| 438 sourceFile = sourceFile.getAbsoluteFile(); | |
| 439 // look in the containing directories | |
| 440 JavaFile dir = sourceFile.getParentFile(); | |
| 441 while (dir != null) { | |
| 442 JavaFile packagesDir = new JavaFile.relative(dir, "packages"); | |
| 443 if (packagesDir.exists()) { | |
| 444 return packagesDir; | |
| 445 } | |
| 446 dir = dir.getParentFile(); | |
| 447 } | |
| 448 // not found | |
| 449 return null; | |
| 450 } | |
| 451 | |
| 452 /** | |
| 453 * Convert [sourcePath] into an absolute path. | |
| 454 */ | |
| 455 static String _normalizeSourcePath(String sourcePath) { | |
| 456 return new File(sourcePath).absolute.path; | |
| 457 } | |
| 458 } | |
| 459 | |
| 460 /** | |
| 461 * This [Logger] prints out information comments to [stdout] and error messages | |
| 462 * to [stderr]. | |
| 463 */ | |
| 464 class StdLogger extends Logger { | |
| 465 final bool log; | |
| 466 | |
| 467 StdLogger(this.log); | |
| 468 | |
| 469 @override | |
| 470 void logError(String message, [CaughtException exception]) { | |
| 471 stderr.writeln(message); | |
| 472 if (exception != null) { | |
| 473 stderr.writeln(exception); | |
| 474 } | |
| 475 } | |
| 476 | |
| 477 @override | |
| 478 void logError2(String message, Object exception) { | |
| 479 stderr.writeln(message); | |
| 480 } | |
| 481 | |
| 482 @override | |
| 483 void logInformation(String message, [CaughtException exception]) { | |
| 484 if (log) { | |
| 485 stdout.writeln(message); | |
| 486 if (exception != null) { | |
| 487 stderr.writeln(exception); | |
| 488 } | |
| 489 } | |
| 490 } | |
| 491 | |
| 492 @override | |
| 493 void logInformation2(String message, Object exception) { | |
| 494 if (log) { | |
| 495 stdout.writeln(message); | |
| 496 } | |
| 497 } | |
| 498 } | |
| OLD | NEW |