Index: pkg/analyzer/lib/src/lint/analysis.dart |
diff --git a/pkg/analyzer/lib/src/lint/analysis.dart b/pkg/analyzer/lib/src/lint/analysis.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f779e5451bfc32353ac607a33a049b782a189d20 |
--- /dev/null |
+++ b/pkg/analyzer/lib/src/lint/analysis.dart |
@@ -0,0 +1,262 @@ |
+// 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:collection'; |
+import 'dart:io' as io; |
+ |
+import 'package:analyzer/dart/element/element.dart'; |
+import 'package:analyzer/file_system/file_system.dart' |
+ show File, Folder, ResourceProvider, ResourceUriResolver; |
+import 'package:analyzer/file_system/physical_file_system.dart'; |
+import 'package:analyzer/source/package_map_resolver.dart'; |
+import 'package:analyzer/src/context/builder.dart'; |
+import 'package:analyzer/src/dart/sdk/sdk.dart'; |
+import 'package:analyzer/src/generated/engine.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/lint/io.dart'; |
+import 'package:analyzer/src/lint/linter.dart'; |
+import 'package:analyzer/src/lint/project.dart'; |
+import 'package:analyzer/src/lint/registry.dart'; |
+import 'package:analyzer/src/services/lint.dart'; |
+import 'package:cli_util/cli_util.dart' as cli_util; |
+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) { |
+ return PhysicalResourceProvider.INSTANCE |
+ .getFile(sourceUri.toFilePath()) |
+ .createSource(sourceUri); |
+} |
+ |
+/// Print the given message and exit with the given [exitCode] |
+void printAndFail(String message, {int exitCode: 15}) { |
+ print(message); |
+ io.exit(exitCode); |
+} |
+ |
+AnalysisOptions _buildAnalyzerOptions(DriverOptions options) { |
+ AnalysisOptionsImpl analysisOptions = new AnalysisOptionsImpl(); |
+ 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 { |
+ // TODO(brianwilkerson) Use the context builder to compute all of the resolvers. |
+ ResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE; |
+ ContextBuilder builder = new ContextBuilder(resourceProvider, null, null); |
+ |
+ DartSdk sdk = options.mockSdk ?? |
+ new FolderBasedDartSdk( |
+ resourceProvider, resourceProvider.getFolder(sdkDir)); |
+ |
+ List<UriResolver> resolvers = [new DartUriResolver(sdk)]; |
+ |
+ if (options.packageRootPath != null) { |
+ // TODO(brianwilkerson) After 0.30.0 is published, clean up the following. |
+ try { |
+ // Try to use the post 0.30.0 API. |
+ (builder as dynamic).builderOptions.defaultPackagesDirectoryPath = |
+ options.packageRootPath; |
+ } catch (_) { |
+ // If that fails, fall back to the pre 0.30.0 API. |
+ (builder as dynamic).defaultPackagesDirectoryPath = |
+ options.packageRootPath; |
+ } |
+ Map<String, List<Folder>> packageMap = |
+ builder.convertPackagesToMap(builder.createPackageMap(null)); |
+ resolvers.add(new PackageMapUriResolver(resourceProvider, packageMap)); |
+ } |
+ |
+ // File URI resolver must come last so that files inside "/lib" are |
+ // are analyzed via "package:" URI's. |
+ resolvers.add(new ResourceUriResolver(resourceProvider)); |
+ 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<io.File> files) { |
+ AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); |
+ context.analysisOptions = _buildAnalyzerOptions(options); |
+ registerLinters(context); |
+ |
+ Packages packages = _getPackageConfig(); |
+ |
+ context.sourceFactory = new SourceFactory(resolvers, packages); |
+ AnalysisEngine.instance.logger = new StdLogger(); |
+ |
+ List<Source> sources = []; |
+ ChangeSet changeSet = new ChangeSet(); |
+ for (io.File file in files) { |
+ File sourceFile = PhysicalResourceProvider.INSTANCE |
+ .getFile(p.normalize(file.absolute.path)); |
+ Source source = sourceFile.createSource(); |
+ 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 = sourceFile.createSource(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. |
+ Registry.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 { |
+ io.File configFile = new io.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; |
+ |
+ /// Whether to show SDK warnings. |
+ bool showSdkWarnings = false; |
+ |
+ /// Whether to use Dart's Strong Mode analyzer. |
+ bool strongMode = true; |
+ |
+ /// The mock SDK (to speed up testing) or `null` to use the actual SDK. |
+ DartSdk mockSdk; |
+ |
+ /// 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); |
+} |