blob: ca048a534fc6d7631ac820135661b16082c88667 [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 linter.src.analysis;
import 'dart:collection';
import 'dart:io';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart'
show Folder, ResourceUriResolver;
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/source/package_map_provider.dart';
import 'package:analyzer/source/package_map_resolver.dart';
import 'package:analyzer/source/pub_package_map_provider.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/java_io.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/services/lint.dart';
import 'package:cli_util/cli_util.dart' as cli_util;
import 'package:linter/src/io.dart';
import 'package:linter/src/linter.dart';
import 'package:linter/src/project.dart';
import 'package:linter/src/rules.dart';
import 'package:linter/src/sdk.dart';
import 'package:package_config/packages.dart' show Packages;
import 'package:package_config/packages_file.dart' as pkgfile show parse;
import 'package:package_config/src/packages_impl.dart' show MapPackages;
import 'package:path/path.dart' as p;
import 'package:plugin/manager.dart';
import 'package:plugin/plugin.dart';
Source createSource(Uri sourceUri) =>
new FileBasedSource(new JavaFile(sourceUri.toFilePath()));
/// Print the given message and exit with the given [exitCode]
void printAndFail(String message, {int exitCode: 15}) {
print(message);
exit(exitCode);
}
AnalysisOptions _buildAnalyzerOptions(DriverOptions options) {
AnalysisOptionsImpl analysisOptions = new AnalysisOptionsImpl();
analysisOptions.cacheSize = options.cacheSize;
analysisOptions.strongMode = options.strongMode;
analysisOptions.hint = false;
analysisOptions.lint = options.enableLints;
analysisOptions.generateSdkErrors = options.showSdkWarnings;
analysisOptions.enableTiming = options.enableTiming;
return analysisOptions;
}
class AnalysisDriver {
/// The sources which have been analyzed so far. This is used to avoid
/// analyzing a source more than once, and to compute the total number of
/// sources analyzed for statistics.
Set<Source> _sourcesAnalyzed = new HashSet<Source>();
final LinterOptions options;
AnalysisDriver(this.options) {
_processPlugins();
}
/// Return the number of sources that have been analyzed so far.
int get numSourcesAnalyzed => _sourcesAnalyzed.length;
List<UriResolver> get resolvers {
DartSdk sdk = options.useMockSdk
? new MockSdk()
: new FolderBasedDartSdk(PhysicalResourceProvider.INSTANCE,
PhysicalResourceProvider.INSTANCE.getFolder(sdkDir));
List<UriResolver> resolvers = [new DartUriResolver(sdk)];
if (options.packageRootPath != null) {
JavaFile packageDirectory = new JavaFile(options.packageRootPath);
resolvers.add(new PackageUriResolver([packageDirectory]));
} else {
PubPackageMapProvider pubPackageMapProvider = new PubPackageMapProvider(
PhysicalResourceProvider.INSTANCE, sdk, options.runPubList);
PackageMapInfo packageMapInfo = pubPackageMapProvider.computePackageMap(
PhysicalResourceProvider.INSTANCE.getResource('.'));
Map<String, List<Folder>> packageMap = packageMapInfo.packageMap;
if (packageMap != null) {
resolvers.add(new PackageMapUriResolver(
PhysicalResourceProvider.INSTANCE, packageMap));
}
}
// File URI resolver must come last so that files inside "/lib" are
// are analyzed via "package:" URI's.
resolvers.add(new ResourceUriResolver(PhysicalResourceProvider.INSTANCE));
return resolvers;
}
String get sdkDir {
if (options.dartSdkPath != null) {
return options.dartSdkPath;
}
// In case no SDK has been specified, fall back to inferring it
// TODO: pass args to cli_util
return cli_util.getSdkDir().path;
}
List<AnalysisErrorInfo> analyze(Iterable<File> files) {
AnalysisContext context = AnalysisEngine.instance.createAnalysisContext();
registerLinters(context);
context.analysisOptions = _buildAnalyzerOptions(options);
Packages packages = _getPackageConfig();
context.sourceFactory = new SourceFactory(resolvers, packages);
AnalysisEngine.instance.logger = new StdLogger();
List<Source> sources = [];
ChangeSet changeSet = new ChangeSet();
for (File file in files) {
JavaFile sourceFile = new JavaFile(p.normalize(file.absolute.path));
Source source = new FileBasedSource(sourceFile, sourceFile.toURI());
Uri uri = context.sourceFactory.restoreUri(source);
if (uri != null) {
// Ensure that we analyze the file using its canonical URI (e.g. if
// it's in "/lib", analyze it using a "package:" URI).
source = new FileBasedSource(sourceFile, uri);
}
sources.add(source);
changeSet.addedSource(source);
}
context.applyChanges(changeSet);
// Temporary location
var project = new DartProject(context, sources);
// This will get pushed into the generator (or somewhere comparable) when
// we have a proper plugin.
ruleRegistry.forEach((lint) {
if (lint is ProjectVisitor) {
(lint as ProjectVisitor).visit(project);
}
});
List<AnalysisErrorInfo> errors = [];
for (Source source in sources) {
context.computeErrors(source);
errors.add(context.getErrors(source));
_sourcesAnalyzed.add(source);
}
if (options.visitTransitiveClosure) {
// In the process of computing errors for all the sources in [sources],
// the analyzer has visited the transitive closure of all libraries
// referenced by those sources. So now we simply need to visit all
// library sources known to the analysis context, and all parts they
// refer to.
for (Source librarySource in context.librarySources) {
for (Source source in _getAllUnitSources(context, librarySource)) {
if (!_sourcesAnalyzed.contains(source)) {
context.computeErrors(source);
errors.add(context.getErrors(source));
_sourcesAnalyzed.add(source);
}
}
}
}
return errors;
}
void registerLinters(AnalysisContext context) {
if (options.enableLints) {
setLints(context, options.enabledLints?.toList(growable: false));
}
}
/// Yield the sources for all the compilation units constituting
/// [librarySource] (including the defining compilation unit).
Iterable<Source> _getAllUnitSources(
AnalysisContext context, Source librarySource) {
List<Source> result = <Source>[librarySource];
result.addAll(context
.getLibraryElement(librarySource)
.parts
.map((CompilationUnitElement e) => e.source));
return result;
}
Packages _getPackageConfig() {
if (options.packageConfigPath != null) {
String packageConfigPath = options.packageConfigPath;
Uri fileUri = new Uri.file(packageConfigPath);
try {
File configFile = new File.fromUri(fileUri).absolute;
List<int> bytes = configFile.readAsBytesSync();
Map<String, Uri> map = pkgfile.parse(bytes, configFile.uri);
return new MapPackages(map);
} catch (e) {
printAndFail(
'Unable to read package config data from $packageConfigPath: $e');
}
}
return null;
}
void _processPlugins() {
List<Plugin> plugins = <Plugin>[];
plugins.addAll(AnalysisEngine.instance.requiredPlugins);
plugins.add(AnalysisEngine.instance.commandLinePlugin);
plugins.add(AnalysisEngine.instance.optionsPlugin);
ExtensionManager manager = new ExtensionManager();
manager.processPlugins(plugins);
}
}
class DriverOptions {
/// The maximum number of sources for which AST structures should be kept
/// in the cache. The default is 512.
int cacheSize = 512;
/// The path to the dart SDK.
String dartSdkPath;
/// Whether to show lint warnings.
bool enableLints = true;
/// Whether to gather timing data during analysis.
bool enableTiming = false;
/// The path to a `.packages` configuration file
String packageConfigPath;
/// The path to the package root.
String packageRootPath;
/// If non-null, the function to use to run pub list. This is used to mock
/// out executions of pub list when testing the linter.
RunPubList runPubList;
/// Whether to show SDK warnings.
bool showSdkWarnings = false;
/// Whether to use Dart's Strong Mode analyzer.
bool strongMode = true;
/// Whether to use a mock SDK (to speed up testing).
bool useMockSdk = false;
/// Whether to show lints for the transitive closure of imported and exported
/// libraries.
bool visitTransitiveClosure = false;
}
/// Prints logging information comments to the [outSink] and error messages to
/// [errorSink].
class StdLogger extends Logger {
@override
void logError(String message, [exception]) => errorSink.writeln(message);
@override
void logInformation(String message, [exception]) => outSink.writeln(message);
}