| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:io'; |
| |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/instrumentation/instrumentation.dart'; |
| import 'package:analyzer/src/dart/analysis/driver.dart'; |
| import 'package:analyzer/src/dart/analysis/file_state.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer_cli/src/driver.dart'; |
| import 'package:analyzer_cli/src/error_formatter.dart'; |
| import 'package:analyzer_cli/src/error_severity.dart'; |
| import 'package:analyzer_cli/src/options.dart'; |
| import 'package:path/path.dart' as path; |
| |
| int get currentTimeMillis => DateTime.now().millisecondsSinceEpoch; |
| |
| /// Analyzes single library [File]. |
| class AnalyzerImpl { |
| final CommandLineOptions options; |
| final int startTime; |
| |
| final AnalysisOptions analysisOptions; |
| final AnalysisDriver analysisDriver; |
| |
| /// Accumulated analysis statistics. |
| final AnalysisStats stats; |
| |
| /// The library file to analyze. |
| final FileState libraryFile; |
| |
| /// All files references by the analyzed library. |
| final Set<String> files = <String>{}; |
| |
| /// All [AnalysisErrorInfo]s in the analyzed library. |
| final List<ErrorsResult> errorsResults = []; |
| |
| /// If the file specified on the command line is part of a package, the name |
| /// of that package. Otherwise `null`. This allows us to analyze the file |
| /// specified on the command line as though it is reached via a "package:" |
| /// URI, but avoid suppressing its output in the event that the user has not |
| /// specified the "--package-warnings" option. |
| String? _selfPackageName; |
| |
| AnalyzerImpl(this.analysisOptions, this.analysisDriver, this.libraryFile, |
| this.options, this.stats, this.startTime); |
| |
| void addCompilationUnitSource( |
| CompilationUnitElement unit, Set<CompilationUnitElement> units) { |
| if (!units.add(unit)) { |
| return; |
| } |
| files.add(unit.source.fullName); |
| } |
| |
| void addLibrarySources(LibraryElement library, Set<LibraryElement> libraries, |
| Set<CompilationUnitElement> units) { |
| if (!libraries.add(library)) { |
| return; |
| } |
| // Maybe skip library. |
| if (!_isAnalyzedLibrary(library)) { |
| return; |
| } |
| // Add compilation units. |
| addCompilationUnitSource(library.definingCompilationUnit, units); |
| for (var child in library.parts) { |
| addCompilationUnitSource(child, units); |
| } |
| // Add referenced libraries. |
| for (var child in library.importedLibraries) { |
| addLibrarySources(child, libraries, units); |
| } |
| for (var child in library.exportedLibraries) { |
| addLibrarySources(child, libraries, units); |
| } |
| } |
| |
| /// Treats the [sourcePath] as the top level library and analyzes it using |
| /// the analysis engine. If [printMode] is `0`, then no error or performance |
| /// information is printed. If [printMode] is `1`, then errors will be printed. |
| /// If [printMode] is `2`, then performance information will be printed, and |
| /// it will be marked as being for a cold VM. |
| Future<ErrorSeverity> analyze(ErrorFormatter formatter, |
| {int printMode = 1}) async { |
| setupForAnalysis(); |
| return await _analyze(printMode, formatter); |
| } |
| |
| /// Returns the maximal [ErrorSeverity] of the recorded errors. |
| ErrorSeverity computeMaxErrorSeverity() { |
| var status = ErrorSeverity.NONE; |
| for (var result in errorsResults) { |
| for (var error in result.errors) { |
| if (_defaultSeverityProcessor(error) == null) { |
| continue; |
| } |
| status = status.max(computeSeverity(error, options, analysisOptions)!); |
| } |
| } |
| return status; |
| } |
| |
| /// Fills [errorsResults] using [files]. |
| Future<void> prepareErrors() async { |
| for (var path in files) { |
| var errorsResult = await analysisDriver.getErrors(path); |
| if (errorsResult is ErrorsResult) { |
| errorsResults.add(errorsResult); |
| } |
| } |
| } |
| |
| /// Fills [files]. |
| void prepareSources(LibraryElement library) { |
| var units = <CompilationUnitElement>{}; |
| var libraries = <LibraryElement>{}; |
| addLibrarySources(library, libraries, units); |
| } |
| |
| /// Setup local fields such as the analysis context for analysis. |
| void setupForAnalysis() { |
| files.clear(); |
| errorsResults.clear(); |
| var libraryUri = libraryFile.uri; |
| if (libraryUri.scheme == 'package' && libraryUri.pathSegments.isNotEmpty) { |
| _selfPackageName = libraryUri.pathSegments[0]; |
| } |
| } |
| |
| Future<ErrorSeverity> _analyze( |
| int printMode, ErrorFormatter formatter) async { |
| // Don't try to analyze parts. |
| if (libraryFile.isPart) { |
| var libraryPath = libraryFile.path; |
| stderr.writeln('Only libraries can be analyzed.'); |
| stderr.writeln('$libraryPath is a part and cannot be analyzed.'); |
| return ErrorSeverity.ERROR; |
| } |
| |
| var libraryElement = await _resolveLibrary(); |
| prepareSources(libraryElement); |
| await prepareErrors(); |
| |
| // Print errors and performance numbers. |
| if (printMode == 1) { |
| formatter.formatErrors(errorsResults); |
| } else if (printMode == 2) { |
| _printColdPerf(); |
| } |
| |
| // Compute and return max severity. |
| return computeMaxErrorSeverity(); |
| } |
| |
| ErrorSeverity? _defaultSeverityProcessor(AnalysisError error) => |
| determineProcessedSeverity(error, options, analysisOptions); |
| |
| /// Returns true if we want to report diagnostics for this library. |
| bool _isAnalyzedLibrary(LibraryElement library) { |
| var source = library.source; |
| if (source.uri.isScheme('dart')) { |
| return options.showSdkWarnings; |
| } else if (source.uri.isScheme('package')) { |
| if (_isPathInPubCache(source.fullName)) { |
| return false; |
| } |
| return _isAnalyzedPackage(source.uri); |
| } else { |
| return true; |
| } |
| } |
| |
| /// Determine whether the given URI refers to a package being analyzed. |
| bool _isAnalyzedPackage(Uri uri) { |
| if (uri.scheme != 'package' || uri.pathSegments.isEmpty) { |
| return false; |
| } |
| var packageName = uri.pathSegments.first; |
| if (packageName == _selfPackageName) { |
| return true; |
| } else if (!options.showPackageWarnings) { |
| return false; |
| } else if (options.showPackageWarningsPrefix == null) { |
| return true; |
| } else { |
| return packageName.startsWith(options.showPackageWarningsPrefix!); |
| } |
| } |
| |
| // TODO(devoncarew): This is never called. |
| void _printColdPerf() { |
| // Print cold VM performance numbers. |
| var totalTime = currentTimeMillis - startTime; |
| outSink.writeln('total-cold:$totalTime'); |
| } |
| |
| Future<LibraryElement> _resolveLibrary() async { |
| var libraryPath = libraryFile.path; |
| analysisDriver.priorityFiles = [libraryPath]; |
| var elementResult = |
| await analysisDriver.getUnitElement(libraryPath) as UnitElementResult; |
| return elementResult.element.library; |
| } |
| |
| /// Return `true` if the given [pathName] is in the Pub cache. |
| static bool _isPathInPubCache(String pathName) { |
| var parts = path.split(pathName); |
| if (parts.contains('.pub-cache')) { |
| return true; |
| } |
| for (var i = 0; i < parts.length - 2; i++) { |
| if (parts[i] == 'Pub' && parts[i + 1] == 'Cache') { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| /// This [InstrumentationService] prints out information comments to [outSink] |
| /// and error messages to [errorSink]. |
| class StdInstrumentation extends NoopInstrumentationService { |
| @override |
| void logError(String message) { |
| errorSink.writeln(message); |
| } |
| |
| @override |
| void logException(dynamic exception, |
| [StackTrace? stackTrace, |
| List<InstrumentationServiceAttachment>? attachments = const []]) { |
| errorSink.writeln(exception); |
| errorSink.writeln(stackTrace); |
| } |
| |
| @override |
| void logInfo(String message, [Object? exception]) { |
| outSink.writeln(message); |
| if (exception != null) { |
| outSink.writeln(exception); |
| } |
| } |
| } |