From aad3389eb0658419d9d024ea3f1114866f58b0ab Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Thu, 23 Sep 2021 06:45:03 +0000 Subject: [PATCH] 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 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- pkg/analyzer/lib/src/lint/analysis.dart | 219 ++++++------------- pkg/analyzer/lib/src/lint/project.dart | 34 +-- pkg/analyzer/test/src/lint/project_test.dart | 6 +- 3 files changed, 82 insertions(+), 177 deletions(-) diff --git a/pkg/analyzer/lib/src/lint/analysis.dart b/pkg/analyzer/lib/src/lint/analysis.dart index d2754587267..b43bd0c46c7 100644 --- a/pkg/analyzer/lib/src/lint/analysis.dart +++ b/pkg/analyzer/lib/src/lint/analysis.dart @@ -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 _sourcesAnalyzed = HashSet(); + /// The files which have been analyzed so far. This is used to compute the + /// total number of files analyzed for statistics. + final Set _filesAnalyzed = {}; final LinterOptions options; LintDriver(this.options); /// Return the number of sources that have been analyzed so far. - int get numSourcesAnalyzed => _sourcesAnalyzed.length; - - List 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 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> analyze(Iterable 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 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 errors = []; - for (Source source in sources) { - var errorsResult = await analysisDriver.getErrors2(source.fullName); + var result = []; + 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 = >{}; - 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 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 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; + } + } +} diff --git a/pkg/analyzer/lib/src/lint/project.dart b/pkg/analyzer/lib/src/lint/project.dart index a461bc709f2..38da8500ae0 100644 --- a/pkg/analyzer/lib/src/lint/project.dart +++ b/pkg/analyzer/lib/src/lint/project.dart @@ -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 sources, {Directory? dir}) + DartProject._(AnalysisSession analysisSession, List 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 create(AnalysisDriver driver, List sources, + static Future create( + AnalysisSession analysisSession, List 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 { /// Captures the project's API as defined by pub package layout standards. class _ApiModel { - final AnalysisDriver driver; - final List? sources; + final AnalysisSession analysisSession; + final List files; final Directory root; final Set elements = {}; - _ApiModel(this.driver, this.sources, this.root) { + _ApiModel(this.analysisSession, this.files, this.root) { _calculate(); } @@ -124,17 +125,16 @@ class _ApiModel { } Future _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; diff --git a/pkg/analyzer/test/src/lint/project_test.dart b/pkg/analyzer/test/src/lint/project_test.dart index 00e6361ce9b..0c0c8dcd414 100644 --- a/pkg/analyzer/test/src/lint/project_test.dart +++ b/pkg/analyzer/test/src/lint/project_test.dart @@ -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); }