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 |