Implement LintDriver using AnalysisContextCollection.

This change requires a change to linter to be rolled.
https://github.com/dart-lang/linter/pull/2949

Change-Id: Ib49438e0dcd6145d79f2914fef30af5e3009e14f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/213290
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2021-09-23 06:45:03 +00:00 committed by commit-bot@chromium.org
parent b553298024
commit aad3389eb0
3 changed files with 82 additions and 177 deletions

View file

@ -2,23 +2,14 @@
// for details. All rights reserved. Use of this source code is governed by a // 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. // BSD-style license that can be found in the LICENSE file.
import 'dart:collection';
import 'dart:io' as io; import 'dart:io' as io;
import 'package:analyzer/dart/analysis/context_locator.dart' as api;
import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/file_system/file_system.dart' import 'package:analyzer/dart/analysis/session.dart';
show File, Folder, ResourceProvider, ResourceUriResolver;
import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/instrumentation/instrumentation.dart'; import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/src/analysis_options/analysis_options_provider.dart'; import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
import 'package:analyzer/src/context/packages.dart'; import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'
as api;
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/sdk.dart'; import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/generated/source.dart';
@ -26,11 +17,7 @@ import 'package:analyzer/src/lint/io.dart';
import 'package:analyzer/src/lint/linter.dart'; import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/project.dart'; import 'package:analyzer/src/lint/project.dart';
import 'package:analyzer/src/lint/registry.dart'; import 'package:analyzer/src/lint/registry.dart';
import 'package:analyzer/src/services/lint.dart';
import 'package:analyzer/src/source/package_map_resolver.dart';
import 'package:analyzer/src/task/options.dart'; import 'package:analyzer/src/task/options.dart';
import 'package:analyzer/src/util/sdk.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
AnalysisOptionsProvider _optionsProvider = AnalysisOptionsProvider(); AnalysisOptionsProvider _optionsProvider = AnalysisOptionsProvider();
@ -47,8 +34,10 @@ void printAndFail(String message, {int exitCode = 15}) {
io.exit(exitCode); io.exit(exitCode);
} }
AnalysisOptionsImpl _buildAnalyzerOptions(LinterOptions options) { void _updateAnalyzerOptions(
AnalysisOptionsImpl analysisOptions = AnalysisOptionsImpl(); AnalysisOptionsImpl analysisOptions,
LinterOptions options,
) {
if (options.analysisOptions != null) { if (options.analysisOptions != null) {
YamlMap map = YamlMap map =
_optionsProvider.getOptionsFromString(options.analysisOptions); _optionsProvider.getOptionsFromString(options.analysisOptions);
@ -59,7 +48,6 @@ AnalysisOptionsImpl _buildAnalyzerOptions(LinterOptions options) {
analysisOptions.lint = options.enableLints; analysisOptions.lint = options.enableLints;
analysisOptions.enableTiming = options.enableTiming; analysisOptions.enableTiming = options.enableTiming;
analysisOptions.lintRules = options.enabledLints.toList(growable: false); analysisOptions.lintRules = options.enabledLints.toList(growable: false);
return analysisOptions;
} }
class DriverOptions { class DriverOptions {
@ -110,172 +98,78 @@ class DriverOptions {
} }
class LintDriver { class LintDriver {
/// The sources which have been analyzed so far. This is used to avoid /// The files which have been analyzed so far. This is used to compute the
/// analyzing a source more than once, and to compute the total number of /// total number of files analyzed for statistics.
/// sources analyzed for statistics. final Set<String> _filesAnalyzed = {};
final Set<Source> _sourcesAnalyzed = HashSet<Source>();
final LinterOptions options; final LinterOptions options;
LintDriver(this.options); LintDriver(this.options);
/// Return the number of sources that have been analyzed so far. /// Return the number of sources that have been analyzed so far.
int get numSourcesAnalyzed => _sourcesAnalyzed.length; int get numSourcesAnalyzed => _filesAnalyzed.length;
List<UriResolver> get resolvers {
// TODO(brianwilkerson) Use the context builder to compute all of the resolvers.
ResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE;
DartSdk sdk = options.mockSdk ??
FolderBasedDartSdk(
resourceProvider, resourceProvider.getFolder(sdkDir));
List<UriResolver> resolvers = [DartUriResolver(sdk)];
var packageUriResolver = _getPackageUriResolver();
if (packageUriResolver != null) {
resolvers.add(packageUriResolver);
}
// File URI resolver must come last so that files inside "/lib" are
// are analyzed via "package:" URI's.
resolvers.add(ResourceUriResolver(resourceProvider));
return resolvers;
}
ResourceProvider get resourceProvider => options.resourceProvider;
String get sdkDir {
// In case no SDK has been specified, fall back to inferring it.
return options.dartSdkPath ?? getSdkPath();
}
Future<List<AnalysisErrorInfo>> analyze(Iterable<io.File> files) async { Future<List<AnalysisErrorInfo>> analyze(Iterable<io.File> files) async {
AnalysisEngine.instance.instrumentationService = StdInstrumentation(); AnalysisEngine.instance.instrumentationService = StdInstrumentation();
SourceFactory sourceFactory = SourceFactory(resolvers); // TODO(scheglov) Enforce normalized absolute paths in the config.
var packageConfigPath = options.packageConfigPath;
packageConfigPath = _absoluteNormalizedPath.ifNotNull(packageConfigPath);
PerformanceLog log = PerformanceLog(null); var contextCollection = AnalysisContextCollectionImpl(
AnalysisDriverScheduler scheduler = AnalysisDriverScheduler(log); resourceProvider: options.resourceProvider,
AnalysisDriver analysisDriver = AnalysisDriver.tmp1( packagesFile: packageConfigPath,
scheduler: scheduler, sdkPath: options.dartSdkPath,
logger: log, includedPaths:
resourceProvider: resourceProvider, files.map((file) => _absoluteNormalizedPath(file.path)).toList(),
byteStore: MemoryByteStore(), updateAnalysisOptions: (analysisOptions) {
sourceFactory: sourceFactory, _updateAnalyzerOptions(analysisOptions, options);
analysisOptions: _buildAnalyzerOptions(options), },
packages: Packages.empty,
); );
_setAnalysisDriverAnalysisContext(analysisDriver, files); AnalysisSession? projectAnalysisSession;
analysisDriver.results.listen((_) {});
analysisDriver.exceptions.listen((_) {});
scheduler.start();
List<Source> sources = [];
for (io.File file in files) { for (io.File file in files) {
File sourceFile = var path = _absoluteNormalizedPath(file.path);
resourceProvider.getFile(p.normalize(file.absolute.path)); _filesAnalyzed.add(path);
Source source = sourceFile.createSource(); var analysisContext = contextCollection.contextFor(path);
var uri = sourceFactory.restoreUri(source); var analysisSession = analysisContext.currentSession;
if (uri != null) { projectAnalysisSession = analysisSession;
// 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);
analysisDriver.addFile(source.fullName);
} }
DartProject project = await DartProject.create(analysisDriver, sources); if (projectAnalysisSession != null) {
Registry.ruleRegistry.forEach((lint) { var project = await DartProject.create(
if (lint is ProjectVisitor) { projectAnalysisSession,
(lint as ProjectVisitor).visit(project); _filesAnalyzed.toList(),
} );
}); Registry.ruleRegistry.forEach((lint) {
if (lint is ProjectVisitor) {
(lint as ProjectVisitor).visit(project);
}
});
}
List<AnalysisErrorInfo> errors = []; var result = <AnalysisErrorInfo>[];
for (Source source in sources) { for (var path in _filesAnalyzed) {
var errorsResult = await analysisDriver.getErrors2(source.fullName); var analysisContext = contextCollection.contextFor(path);
var analysisSession = analysisContext.currentSession;
var errorsResult = await analysisSession.getErrors(path);
if (errorsResult is ErrorsResult) { if (errorsResult is ErrorsResult) {
errors.add( result.add(
AnalysisErrorInfoImpl( AnalysisErrorInfoImpl(
errorsResult.errors, errorsResult.errors,
errorsResult.lineInfo, errorsResult.lineInfo,
), ),
); );
_sourcesAnalyzed.add(source);
} }
} }
return result;
return errors;
} }
void registerLinters(AnalysisContext context) { String _absoluteNormalizedPath(String path) {
if (options.enableLints) { var pathContext = options.resourceProvider.pathContext;
setLints(context, options.enabledLints.toList(growable: false)); path = pathContext.absolute(path);
} path = pathContext.normalize(path);
} return path;
PackageMapUriResolver? _getPackageUriResolver() {
var packageConfigPath = options.packageConfigPath;
if (packageConfigPath != null) {
var resourceProvider = PhysicalResourceProvider.INSTANCE;
var pathContext = resourceProvider.pathContext;
packageConfigPath = pathContext.absolute(packageConfigPath);
packageConfigPath = pathContext.normalize(packageConfigPath);
try {
var packages = parsePackagesFile(
resourceProvider,
resourceProvider.getFile(packageConfigPath),
);
var packageMap = <String, List<Folder>>{};
for (var package in packages.packages) {
packageMap[package.name] = [package.libFolder];
}
return PackageMapUriResolver(resourceProvider, packageMap);
} catch (e) {
printAndFail(
'Unable to read package config data from $packageConfigPath: $e',
);
}
}
return null;
}
void _setAnalysisDriverAnalysisContext(
AnalysisDriver analysisDriver,
Iterable<io.File> files,
) {
if (files.isEmpty) {
return;
}
var rootPath = p.normalize(files.first.absolute.path);
var apiContextRoots = api.ContextLocator(
resourceProvider: resourceProvider,
).locateRoots(
includedPaths: [rootPath],
excludedPaths: [],
);
if (apiContextRoots.isEmpty) {
return;
}
analysisDriver.configure(
analysisContext: api.DriverBasedAnalysisContext(
resourceProvider,
apiContextRoots.first,
analysisDriver,
),
);
} }
} }
@ -306,3 +200,14 @@ class StdInstrumentation extends NoopInstrumentationService {
} }
} }
} }
extension _UnaryFunctionExtension<T, R> on R Function(T) {
/// Invoke this function if [t] is not `null`, otherwise return `null`.
R? ifNotNull(T? t) {
if (t != null) {
return this(t);
} else {
return null;
}
}
}

View file

@ -5,10 +5,9 @@
import 'dart:io'; import 'dart:io';
import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/resolver/scope.dart'; import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/lint/io.dart'; import 'package:analyzer/src/lint/io.dart';
import 'package:analyzer/src/lint/pub.dart'; import 'package:analyzer/src/lint/pub.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@ -44,15 +43,16 @@ class DartProject {
/// Project root. /// Project root.
final Directory root; final Directory root;
/// Create a Dart project for the corresponding [driver] and [sources]. /// Create a Dart project for the corresponding [analysisSession] and [files].
/// If a [dir] is unspecified the current working directory will be /// If a [dir] is unspecified the current working directory will be
/// used. /// used.
/// ///
/// Note: clients should call [create] which performs API model initialization. /// Note: clients should call [create] which performs API model initialization.
DartProject._(AnalysisDriver driver, List<Source> sources, {Directory? dir}) DartProject._(AnalysisSession analysisSession, List<String> files,
{Directory? dir})
: root = dir ?? Directory.current { : root = dir ?? Directory.current {
_pubspec = _findAndParsePubspec(root); _pubspec = _findAndParsePubspec(root);
_apiModel = _ApiModel(driver, sources, root); _apiModel = _ApiModel(analysisSession, files, root);
} }
/// The project's name. /// The project's name.
@ -84,13 +84,14 @@ class DartProject {
return p.basename(root.path); return p.basename(root.path);
} }
/// Create an initialized Dart project for the corresponding [driver] and /// Create an initialized Dart project for the corresponding [analysisSession]
/// [sources]. /// and [files].
/// If a [dir] is unspecified the current working directory will be /// If a [dir] is unspecified the current working directory will be
/// used. /// used.
static Future<DartProject> create(AnalysisDriver driver, List<Source> sources, static Future<DartProject> create(
AnalysisSession analysisSession, List<String> files,
{Directory? dir}) async { {Directory? dir}) async {
DartProject project = DartProject._(driver, sources, dir: dir); DartProject project = DartProject._(analysisSession, files, dir: dir);
await project._apiModel._calculate(); await project._apiModel._calculate();
return project; return project;
} }
@ -103,12 +104,12 @@ abstract class ProjectVisitor<T> {
/// Captures the project's API as defined by pub package layout standards. /// Captures the project's API as defined by pub package layout standards.
class _ApiModel { class _ApiModel {
final AnalysisDriver driver; final AnalysisSession analysisSession;
final List<Source>? sources; final List<String> files;
final Directory root; final Directory root;
final Set<Element> elements = {}; final Set<Element> elements = {};
_ApiModel(this.driver, this.sources, this.root) { _ApiModel(this.analysisSession, this.files, this.root) {
_calculate(); _calculate();
} }
@ -124,17 +125,16 @@ class _ApiModel {
} }
Future<void> _calculate() async { Future<void> _calculate() async {
if (sources == null || sources!.isEmpty) { if (files.isEmpty) {
return; return;
} }
String libDir = root.path + '/lib'; String libDir = root.path + '/lib';
String libSrcDir = libDir + '/src'; String libSrcDir = libDir + '/src';
for (Source source in sources!) { for (var file in files) {
String path = source.uri.path; if (file.startsWith(libDir) && !file.startsWith(libSrcDir)) {
if (path.startsWith(libDir) && !path.startsWith(libSrcDir)) { var result = await analysisSession.getResolvedUnit(file);
var result = await driver.getResult2(source.fullName);
if (result is ResolvedUnitResult) { if (result is ResolvedUnitResult) {
LibraryElement library = result.libraryElement; LibraryElement library = result.libraryElement;

View file

@ -4,7 +4,7 @@
import 'dart:io'; import 'dart:io';
import 'package:analyzer/src/dart/analysis/driver.dart'; import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/src/lint/project.dart'; import 'package:analyzer/src/lint/project.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -18,7 +18,7 @@ defineTests() {
// TODO(brianwilkerson) These tests fail on the bots because the cwd is // TODO(brianwilkerson) These tests fail on the bots because the cwd is
// not the same there as when we run tests locally. // not the same there as when we run tests locally.
group('cwd', () async { group('cwd', () async {
var project = await DartProject.create(_AnalysisDriverMock(), []); var project = await DartProject.create(_AnalysisSessionMock(), []);
test('name', () { test('name', () {
expect(project.name, 'analyzer'); expect(project.name, 'analyzer');
}); });
@ -61,7 +61,7 @@ defineTests() {
}); });
} }
class _AnalysisDriverMock implements AnalysisDriver { class _AnalysisSessionMock implements AnalysisSession {
@override @override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
} }