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
// BSD-style license that can be found in the LICENSE file.
import 'dart:collection';
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/file_system/file_system.dart'
show File, Folder, ResourceProvider, ResourceUriResolver;
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
import 'package:analyzer/src/context/packages.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/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/sdk.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/project.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/util/sdk.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';
AnalysisOptionsProvider _optionsProvider = AnalysisOptionsProvider();
@ -47,8 +34,10 @@ void printAndFail(String message, {int exitCode = 15}) {
io.exit(exitCode);
}
AnalysisOptionsImpl _buildAnalyzerOptions(LinterOptions options) {
AnalysisOptionsImpl analysisOptions = AnalysisOptionsImpl();
void _updateAnalyzerOptions(
AnalysisOptionsImpl analysisOptions,
LinterOptions options,
) {
if (options.analysisOptions != null) {
YamlMap map =
_optionsProvider.getOptionsFromString(options.analysisOptions);
@ -59,7 +48,6 @@ AnalysisOptionsImpl _buildAnalyzerOptions(LinterOptions options) {
analysisOptions.lint = options.enableLints;
analysisOptions.enableTiming = options.enableTiming;
analysisOptions.lintRules = options.enabledLints.toList(growable: false);
return analysisOptions;
}
class DriverOptions {
@ -110,172 +98,78 @@ class DriverOptions {
}
class LintDriver {
/// 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.
final Set<Source> _sourcesAnalyzed = HashSet<Source>();
/// The files which have been analyzed so far. This is used to compute the
/// total number of files analyzed for statistics.
final Set<String> _filesAnalyzed = {};
final LinterOptions options;
LintDriver(this.options);
/// 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;
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();
}
int get numSourcesAnalyzed => _filesAnalyzed.length;
Future<List<AnalysisErrorInfo>> analyze(Iterable<io.File> files) async {
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);
AnalysisDriverScheduler scheduler = AnalysisDriverScheduler(log);
AnalysisDriver analysisDriver = AnalysisDriver.tmp1(
scheduler: scheduler,
logger: log,
resourceProvider: resourceProvider,
byteStore: MemoryByteStore(),
sourceFactory: sourceFactory,
analysisOptions: _buildAnalyzerOptions(options),
packages: Packages.empty,
var contextCollection = AnalysisContextCollectionImpl(
resourceProvider: options.resourceProvider,
packagesFile: packageConfigPath,
sdkPath: options.dartSdkPath,
includedPaths:
files.map((file) => _absoluteNormalizedPath(file.path)).toList(),
updateAnalysisOptions: (analysisOptions) {
_updateAnalyzerOptions(analysisOptions, options);
},
);
_setAnalysisDriverAnalysisContext(analysisDriver, files);
analysisDriver.results.listen((_) {});
analysisDriver.exceptions.listen((_) {});
scheduler.start();
List<Source> sources = [];
AnalysisSession? projectAnalysisSession;
for (io.File file in files) {
File sourceFile =
resourceProvider.getFile(p.normalize(file.absolute.path));
Source source = sourceFile.createSource();
var uri = 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);
analysisDriver.addFile(source.fullName);
var path = _absoluteNormalizedPath(file.path);
_filesAnalyzed.add(path);
var analysisContext = contextCollection.contextFor(path);
var analysisSession = analysisContext.currentSession;
projectAnalysisSession = analysisSession;
}
DartProject project = await DartProject.create(analysisDriver, sources);
Registry.ruleRegistry.forEach((lint) {
if (lint is ProjectVisitor) {
(lint as ProjectVisitor).visit(project);
}
});
if (projectAnalysisSession != null) {
var project = await DartProject.create(
projectAnalysisSession,
_filesAnalyzed.toList(),
);
Registry.ruleRegistry.forEach((lint) {
if (lint is ProjectVisitor) {
(lint as ProjectVisitor).visit(project);
}
});
}
List<AnalysisErrorInfo> errors = [];
for (Source source in sources) {
var errorsResult = await analysisDriver.getErrors2(source.fullName);
var result = <AnalysisErrorInfo>[];
for (var path in _filesAnalyzed) {
var analysisContext = contextCollection.contextFor(path);
var analysisSession = analysisContext.currentSession;
var errorsResult = await analysisSession.getErrors(path);
if (errorsResult is ErrorsResult) {
errors.add(
result.add(
AnalysisErrorInfoImpl(
errorsResult.errors,
errorsResult.lineInfo,
),
);
_sourcesAnalyzed.add(source);
}
}
return errors;
return result;
}
void registerLinters(AnalysisContext context) {
if (options.enableLints) {
setLints(context, options.enabledLints.toList(growable: false));
}
}
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,
),
);
String _absoluteNormalizedPath(String path) {
var pathContext = options.resourceProvider.pathContext;
path = pathContext.absolute(path);
path = pathContext.normalize(path);
return path;
}
}
@ -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 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.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/generated/source.dart';
import 'package:analyzer/src/lint/io.dart';
import 'package:analyzer/src/lint/pub.dart';
import 'package:collection/collection.dart';
@ -44,15 +43,16 @@ class DartProject {
/// Project 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
/// used.
///
/// 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 {
_pubspec = _findAndParsePubspec(root);
_apiModel = _ApiModel(driver, sources, root);
_apiModel = _ApiModel(analysisSession, files, root);
}
/// The project's name.
@ -84,13 +84,14 @@ class DartProject {
return p.basename(root.path);
}
/// Create an initialized Dart project for the corresponding [driver] and
/// [sources].
/// Create an initialized Dart project for the corresponding [analysisSession]
/// and [files].
/// If a [dir] is unspecified the current working directory will be
/// used.
static Future<DartProject> create(AnalysisDriver driver, List<Source> sources,
static Future<DartProject> create(
AnalysisSession analysisSession, List<String> files,
{Directory? dir}) async {
DartProject project = DartProject._(driver, sources, dir: dir);
DartProject project = DartProject._(analysisSession, files, dir: dir);
await project._apiModel._calculate();
return project;
}
@ -103,12 +104,12 @@ abstract class ProjectVisitor<T> {
/// Captures the project's API as defined by pub package layout standards.
class _ApiModel {
final AnalysisDriver driver;
final List<Source>? sources;
final AnalysisSession analysisSession;
final List<String> files;
final Directory root;
final Set<Element> elements = {};
_ApiModel(this.driver, this.sources, this.root) {
_ApiModel(this.analysisSession, this.files, this.root) {
_calculate();
}
@ -124,17 +125,16 @@ class _ApiModel {
}
Future<void> _calculate() async {
if (sources == null || sources!.isEmpty) {
if (files.isEmpty) {
return;
}
String libDir = root.path + '/lib';
String libSrcDir = libDir + '/src';
for (Source source in sources!) {
String path = source.uri.path;
if (path.startsWith(libDir) && !path.startsWith(libSrcDir)) {
var result = await driver.getResult2(source.fullName);
for (var file in files) {
if (file.startsWith(libDir) && !file.startsWith(libSrcDir)) {
var result = await analysisSession.getResolvedUnit(file);
if (result is ResolvedUnitResult) {
LibraryElement library = result.libraryElement;

View file

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