blob: 56eaa2fbec13b396221be7e6333f8b1270788a61 [file] [log] [blame]
// 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.
library analyzer_cli.src.analyzer_impl;
import 'dart:collection';
import 'dart:io';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/source/error_processor.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/java_engine.dart';
import 'package:analyzer/src/generated/java_io.dart';
import 'package:analyzer/src/generated/sdk_io.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/generated/utilities_general.dart';
import 'package:analyzer_cli/src/driver.dart';
import 'package:analyzer_cli/src/error_formatter.dart';
import 'package:analyzer_cli/src/options.dart';
/// The maximum number of sources for which AST structures should be kept in the cache.
const int _maxCacheSize = 512;
DirectoryBasedDartSdk sdk;
int currentTimeMillis() => new DateTime.now().millisecondsSinceEpoch;
/// Analyzes single library [File].
class AnalyzerImpl {
static final PerformanceTag _prepareErrorsTag =
new PerformanceTag("AnalyzerImpl.prepareErrors");
static final PerformanceTag _resolveLibraryTag =
new PerformanceTag("AnalyzerImpl._resolveLibrary");
final CommandLineOptions options;
final int startTime;
final AnalysisContext context;
final Source librarySource;
/// All [Source]s references by the analyzed library.
final Set<Source> sources = new Set<Source>();
/// All [AnalysisErrorInfo]s in the analyzed library.
final List<AnalysisErrorInfo> errorInfos = new List<AnalysisErrorInfo>();
/// [HashMap] between sources and analysis error infos.
final HashMap<Source, AnalysisErrorInfo> sourceErrorsMap =
new HashMap<Source, AnalysisErrorInfo>();
/// 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.context, this.librarySource, this.options, this.startTime);
/// Returns the maximal [ErrorSeverity] of the recorded errors.
ErrorSeverity get maxErrorSeverity {
var status = ErrorSeverity.NONE;
for (AnalysisErrorInfo errorInfo in errorInfos) {
for (AnalysisError error in errorInfo.errors) {
if (_processError(error) == null) {
continue;
}
var severity = computeSeverity(error, options);
status = status.max(severity);
}
}
return status;
}
void addCompilationUnitSource(CompilationUnitElement unit,
Set<LibraryElement> libraries, Set<CompilationUnitElement> units) {
if (unit == null || units.contains(unit)) {
return;
}
units.add(unit);
sources.add(unit.source);
}
void addLibrarySources(LibraryElement library, Set<LibraryElement> libraries,
Set<CompilationUnitElement> units) {
if (library == null || !libraries.add(library)) {
return;
}
// Maybe skip library.
{
UriKind uriKind = library.source.uriKind;
// Optionally skip package: libraries.
if (!options.showPackageWarnings && _isOtherPackage(library.source.uri)) {
return;
}
// Optionally skip SDK libraries.
if (!options.showSdkWarnings && uriKind == UriKind.DART_URI) {
return;
}
}
// Add compilation units.
addCompilationUnitSource(library.definingCompilationUnit, libraries, units);
for (CompilationUnitElement child in library.parts) {
addCompilationUnitSource(child, libraries, units);
}
// Add referenced libraries.
for (LibraryElement child in library.importedLibraries) {
addLibrarySources(child, libraries, units);
}
for (LibraryElement child in library.exportedLibraries) {
addLibrarySources(child, libraries, units);
}
}
/// Treats the [sourcePath] as the top level library and analyzes it using a
/// synchronous algorithm over the analysis engine. If [printMode] is `0`,
/// then no error or performance information is printed. If [printMode] is `1`,
/// then both will be printed. If [printMode] is `2`, then only performance
/// information will be printed, and it will be marked as being for a cold VM.
ErrorSeverity analyzeSync({int printMode: 1}) {
setupForAnalysis();
return _analyzeSync(printMode);
}
/// Fills [errorInfos] using [sources].
void prepareErrors() {
return _prepareErrorsTag.makeCurrentWhile(() {
for (Source source in sources) {
context.computeErrors(source);
errorInfos.add(context.getErrors(source));
}
});
}
/// Fills [sources].
void prepareSources(LibraryElement library) {
var units = new Set<CompilationUnitElement>();
var libraries = new Set<LibraryElement>();
addLibrarySources(library, libraries, units);
}
/// Setup local fields such as the analysis context for analysis.
void setupForAnalysis() {
sources.clear();
errorInfos.clear();
Uri libraryUri = librarySource.uri;
if (libraryUri.scheme == 'package' && libraryUri.pathSegments.length > 0) {
_selfPackageName = libraryUri.pathSegments[0];
}
}
/// The sync version of analysis.
ErrorSeverity _analyzeSync(int printMode) {
// Don't try to analyze parts.
if (context.computeKindOf(librarySource) == SourceKind.PART) {
stderr.writeln("Only libraries can be analyzed.");
stderr.writeln(
"${librarySource.fullName} is a part and can not be analyzed.");
return ErrorSeverity.ERROR;
}
var libraryElement = _resolveLibrary();
prepareSources(libraryElement);
prepareErrors();
// Print errors and performance numbers.
if (printMode == 1) {
_printErrors();
} else if (printMode == 2) {
_printColdPerf();
}
// Compute max severity and set exitCode.
ErrorSeverity status = maxErrorSeverity;
if (status == ErrorSeverity.WARNING && options.warningsAreFatal) {
status = ErrorSeverity.ERROR;
}
return status;
}
/// Determine whether the given URI refers to a package other than the package
/// being analyzed.
bool _isOtherPackage(Uri uri) {
if (uri.scheme != 'package') {
return false;
}
if (_selfPackageName != null &&
uri.pathSegments.length > 0 &&
uri.pathSegments[0] == _selfPackageName) {
return false;
}
return true;
}
_printColdPerf() {
// Print cold VM performance numbers.
int totalTime = currentTimeMillis() - startTime;
int otherTime = totalTime;
for (PerformanceTag tag in PerformanceTag.all) {
if (tag != PerformanceTag.UNKNOWN) {
int tagTime = tag.elapsedMs;
outSink.writeln('${tag.label}-cold:$tagTime');
otherTime -= tagTime;
}
}
outSink.writeln('other-cold:$otherTime');
outSink.writeln("total-cold:$totalTime");
}
_printErrors() {
// The following is a hack. We currently print out to stderr to ensure that
// when in batch mode we print to stderr, this is because the prints from
// batch are made to stderr. The reason that options.shouldBatch isn't used
// is because when the argument flags are constructed in BatchRunner and
// passed in from batch mode which removes the batch flag to prevent the
// "cannot have the batch flag and source file" error message.
StringSink sink = options.machineFormat ? errorSink : outSink;
// Print errors.
ErrorFormatter formatter = new ErrorFormatter(sink, options, _processError);
formatter.formatErrors(errorInfos);
}
ProcessedSeverity _processError(AnalysisError error) =>
processError(error, options, context);
LibraryElement _resolveLibrary() {
return _resolveLibraryTag.makeCurrentWhile(() {
return context.computeLibraryElement(librarySource);
});
}
/// Compute the severity of the error; however:
/// * if [options.enableTypeChecks] is false, then de-escalate checked-mode
/// compile time errors to a severity of [ErrorSeverity.INFO].
/// * if [options.hintsAreFatal] is true, escalate hints to errors.
static ErrorSeverity computeSeverity(
AnalysisError error, CommandLineOptions options,
[AnalysisContext context]) {
if (context != null) {
ErrorProcessor processor = ErrorProcessor.getProcessor(context, error);
// If there is a processor for this error, defer to it.
if (processor != null) {
return processor.severity;
}
}
if (!options.enableTypeChecks &&
error.errorCode.type == ErrorType.CHECKED_MODE_COMPILE_TIME_ERROR) {
return ErrorSeverity.INFO;
}
if (options.hintsAreFatal && error.errorCode is HintCode) {
return ErrorSeverity.ERROR;
}
return error.errorCode.errorSeverity;
}
/// Return the corresponding package directory or `null` if none is found.
static JavaFile getPackageDirectoryFor(JavaFile sourceFile) {
// We are going to ask parent file, so get absolute path.
sourceFile = sourceFile.getAbsoluteFile();
// Look in the containing directories.
JavaFile dir = sourceFile.getParentFile();
while (dir != null) {
JavaFile packagesDir = new JavaFile.relative(dir, "packages");
if (packagesDir.exists()) {
return packagesDir;
}
dir = dir.getParentFile();
}
// Not found.
return null;
}
/// Check various configuration options to get a desired severity for this
/// [error] (or `null` if it's to be suppressed).
static ProcessedSeverity processError(AnalysisError error,
CommandLineOptions options, AnalysisContext context) {
ErrorSeverity severity = computeSeverity(error, options, context);
bool isOverridden = false;
// First check for a filter.
if (severity == null) {
// Null severity means the error has been explicitly ignored.
return null;
} else {
isOverridden = true;
}
// If not overridden, some "natural" severities get globally filtered.
if (!isOverridden) {
// Check for global hint filtering.
if (severity == ErrorSeverity.INFO && options.disableHints) {
return null;
}
// Skip TODOs.
if (severity == ErrorType.TODO) {
return null;
}
}
return new ProcessedSeverity(severity, isOverridden);
}
}
/// This [Logger] prints out information comments to [outSink] and error messages
/// to [errorSink].
class StdLogger extends Logger {
StdLogger();
@override
void logError(String message, [CaughtException exception]) {
errorSink.writeln(message);
if (exception != null) {
errorSink.writeln(exception);
}
}
@override
void logInformation(String message, [CaughtException exception]) {
outSink.writeln(message);
if (exception != null) {
outSink.writeln(exception);
}
}
}