mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 03:27:43 +00:00
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:
parent
b553298024
commit
aad3389eb0
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue