mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 16:41:07 +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
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue