share options with a common context root

Lays the stage to acquire options from files during error verification.

Change-Id: I3b715a93e99fc3bc6f33f736e8b38c06b8c1cd4b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333126
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Phil Quitslund <pquitslund@google.com>
This commit is contained in:
pq 2023-11-01 22:03:21 +00:00 committed by Commit Queue
parent e29ae69148
commit 66569eb894
8 changed files with 152 additions and 69 deletions

View file

@ -3,13 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/context_locator.dart';
import 'package:analyzer/dart/analysis/context_root.dart';
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/context_builder.dart';
import 'package:analyzer/src/dart/analysis/context_builder2.dart';
import 'package:analyzer/src/dart/analysis/context_locator2.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
@ -81,7 +81,7 @@ class AnalysisContextCollectionImpl2 implements AnalysisContextCollection {
this.macroSupport = macroSupport ??= KernelMacroSupport();
var contextLocator = ContextLocator(
var contextLocator = ContextLocatorImpl2(
resourceProvider: this.resourceProvider,
);
var roots = contextLocator.locateRoots(
@ -91,7 +91,7 @@ class AnalysisContextCollectionImpl2 implements AnalysisContextCollection {
packagesFile: packagesFile,
);
for (var root in roots) {
var contextBuilder = ContextBuilderImpl(
var contextBuilder = ContextBuilderImpl2(
resourceProvider: this.resourceProvider,
);
var context = contextBuilder.createContext(

View file

@ -11,8 +11,10 @@ import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
import 'package:analyzer/src/analysis_options/apply_options.dart';
import 'package:analyzer/src/context/builder.dart' show EmbedderYamlLocator;
import 'package:analyzer/src/context/packages.dart';
import 'package:analyzer/src/dart/analysis/analysis_options_map.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart'
show ByteStore, MemoryByteStore;
import 'package:analyzer/src/dart/analysis/context_root.dart';
import 'package:analyzer/src/dart/analysis/driver.dart'
show
AnalysisDriver,
@ -118,6 +120,7 @@ class ContextBuilderImpl2 implements ContextBuilder {
var sourceFactory = workspace.createSourceFactory(sdk, summaryData);
// todo(pq): remove
var options = _getAnalysisOptions(contextRoot, sourceFactory);
if (updateAnalysisOptions != null) {
updateAnalysisOptions(options);
@ -132,6 +135,9 @@ class ContextBuilderImpl2 implements ContextBuilder {
final analysisContext =
DriverBasedAnalysisContext(resourceProvider, contextRoot);
var analysisOptionsMap = _createOptionsMap(contextRoot, sourceFactory,
updateAnalysisOptions, updateAnalysisOptions2, sdk);
var driver = AnalysisDriver(
scheduler: scheduler,
logger: performanceLog,
@ -139,6 +145,7 @@ class ContextBuilderImpl2 implements ContextBuilder {
byteStore: byteStore,
sourceFactory: sourceFactory,
analysisOptions: options,
analysisOptionsMap: analysisOptionsMap,
packages: _createPackageMap(
contextRoot: contextRoot,
),
@ -165,6 +172,48 @@ class ContextBuilderImpl2 implements ContextBuilder {
return analysisContext;
}
AnalysisOptionsMap _createOptionsMap(
ContextRoot contextRoot,
SourceFactory sourceFactory,
void Function(AnalysisOptionsImpl p1)? updateAnalysisOptions,
void Function(
{required AnalysisOptionsImpl analysisOptions,
required ContextRoot contextRoot,
required DartSdk sdk})?
updateAnalysisOptions2,
DartSdk sdk) {
var map = AnalysisOptionsMap();
var provider = AnalysisOptionsProvider(sourceFactory);
var pubspecFile = _findPubspecFile(contextRoot);
for (var entry in (contextRoot as ContextRootImpl).optionsFileMap.entries) {
var options = AnalysisOptionsImpl();
var optionsYaml = provider.getOptionsFromFile(entry.value);
options.applyOptions(optionsYaml);
if (pubspecFile != null) {
var extractor = SdkConstraintExtractor(pubspecFile);
var sdkVersionConstraint = extractor.constraint();
if (sdkVersionConstraint != null) {
options.sdkVersionConstraint = sdkVersionConstraint;
}
}
if (updateAnalysisOptions != null) {
updateAnalysisOptions(options);
} else if (updateAnalysisOptions2 != null) {
updateAnalysisOptions2(
analysisOptions: options,
contextRoot: contextRoot,
sdk: sdk,
);
}
map.add(entry.key, options);
}
return map;
}
/// Return [Packages] to analyze the [contextRoot].
///
/// TODO(scheglov) Get [Packages] from [Workspace]?

View file

@ -99,7 +99,6 @@ class ContextLocatorImpl2 implements ContextLocator {
defaultPackagesFile: defaultPackagesFile,
defaultRootFolder: () => folder,
);
ContextRootImpl? root;
for (var existingRoot in roots) {
if (existingRoot.root.isOrContains(folder.path) &&
@ -251,6 +250,10 @@ class ContextLocatorImpl2 implements ContextLocator {
var root = ContextRootImpl(resourceProvider, rootFolder, workspace);
root.packagesFile = packagesFile;
root.optionsFile = optionsFile;
if (optionsFile != null) {
root.optionsFileMap[rootFolder] = optionsFile;
}
root.excludedGlobs = _getExcludedGlobs(root);
roots.add(root);
return root;
@ -290,13 +293,18 @@ class ContextLocatorImpl2 implements ContextLocator {
localPackagesFile = _getPackagesFile(folder);
}
var buildGnFile = folder.getExistingFile(file_paths.buildGn);
if (localOptionsFile != null) {
// todo(pq): remove cast when API is finalized.
(containingRoot as ContextRootImpl).optionsFileMap[folder] =
localOptionsFile;
}
//
// Create a context root for the given [folder] if at least one of the
// options and packages file is locally specified.
// Create a context root for the given [folder] if a packages or build file
// is locally specified.
//
if (localPackagesFile != null ||
localOptionsFile != null ||
buildGnFile != null) {
if (localPackagesFile != null || buildGnFile != null) {
if (optionsFile != null) {
localOptionsFile = optionsFile;
}

View file

@ -6,6 +6,7 @@ import 'package:analyzer/dart/analysis/context_root.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:glob/glob.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart';
/// An implementation of a context root.
@ -32,6 +33,10 @@ class ContextRootImpl implements ContextRoot {
@override
File? optionsFile;
/// Maintains a mapping of folders to associated analysis options files.
@experimental
final Map<Folder, File> optionsFileMap = {};
@override
File? packagesFile;

View file

@ -13,6 +13,7 @@ import 'package:analyzer/error/listener.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/packages.dart';
import 'package:analyzer/src/dart/analysis/analysis_options_map.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:analyzer/src/dart/analysis/feature_set_provider.dart';
@ -49,6 +50,7 @@ import 'package:analyzer/src/summary2/package_bundle_format.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/utilities/uri_cache.dart';
import 'package:meta/meta.dart';
/// This class computes [AnalysisResult]s for Dart files.
///
@ -265,6 +267,10 @@ class AnalysisDriver implements AnalysisDriverGeneric {
bool _disposed = false;
/// A map that associates files to corresponding analysis options.
/// todo(pq): his will replace the single [_analysisOptions] instance.
final AnalysisOptionsMap? _analysisOptionsMap;
/// Create a new instance of [AnalysisDriver].
///
/// The given [SourceFactory] is cloned to ensure that it does not contain a
@ -280,6 +286,8 @@ class AnalysisDriver implements AnalysisDriverGeneric {
this.macroSupport,
this.ownedFiles,
this.analysisContext,
// todo(pq): to replace analysis options instance
AnalysisOptionsMap? analysisOptionsMap,
FileContentCache? fileContentCache,
UnlinkedUnitStore? unlinkedUnitStore,
InfoDeclarationStore? infoDeclarationStore,
@ -297,6 +305,7 @@ class AnalysisDriver implements AnalysisDriverGeneric {
_infoDeclarationStore =
infoDeclarationStore ?? NoOpInfoDeclarationStore(),
_analysisOptions = analysisOptions,
_analysisOptionsMap = analysisOptionsMap,
_logger = logger,
_packages = packages,
_sourceFactory = sourceFactory,
@ -318,6 +327,7 @@ class AnalysisDriver implements AnalysisDriverGeneric {
Set<String> get addedFiles => _fileTracker.addedFiles;
/// Return the analysis options used to control analysis.
//todo(pq): @Deprecated("Use 'getAnalysisOptionsForFile(file)' instead")
AnalysisOptions get analysisOptions => _analysisOptions;
/// Return the current analysis session.
@ -658,6 +668,10 @@ class AnalysisDriver implements AnalysisDriverGeneric {
return completer.future;
}
@experimental
AnalysisOptions? getAnalysisOptionsForFile(File file) =>
_analysisOptionsMap?.getOptions(file);
/// Return the cached [ResolvedUnitResult] for the Dart file with the given
/// [path]. If there is no cached result, return `null`. Usually only results
/// of priority files are cached.

View file

@ -9,6 +9,7 @@ import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/driver.dart' show AnalysisDriver;
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptions;
import 'package:meta/meta.dart';
/// An analysis context whose implementation is based on an analysis driver.
class DriverBasedAnalysisContext implements AnalysisContext {
@ -55,4 +56,8 @@ class DriverBasedAnalysisContext implements AnalysisContext {
void changeFile(String path) {
driver.changeFile(path);
}
@experimental
AnalysisOptions? getAnalysisOptionsForFile(File file) =>
driver.getAnalysisOptionsForFile(file);
}

View file

@ -2,6 +2,7 @@
// 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 'package:analyzer/dart/analysis/analysis_options.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection2.dart';
@ -15,12 +16,12 @@ import '../resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(AnalysisContextCollectionTest);
defineReflectiveTests(AnalysisContextCollection2Test);
});
}
@reflectiveTest
class AnalysisContextCollectionTest with ResourceProviderMixin {
class AnalysisContextCollection2Test with ResourceProviderMixin {
Folder get sdkRoot => newFolder('/sdk');
void setUp() {
@ -124,34 +125,52 @@ linter:
}
test_new_outer_inner() {
// OUTER
var outerFolder = newFolder('/test/outer');
newFile('/test/outer/lib/outer.dart', '');
newAnalysisOptionsYamlFile('/test/outer', r'''
linter:
rules:
- always_specify_types
''');
var outerFile = newFile('/test/outer/lib/outer.dart', '');
var innerFolder = newFolder('/test/outer/inner');
newAnalysisOptionsYamlFile('/test/outer/inner', '');
newFile('/test/outer/inner/inner.dart', '');
// INNER
newFolder('/test/outer/inner');
newAnalysisOptionsYamlFile('/test/outer/inner', r'''
linter:
rules:
- camel_case_types
''');
var innerFile = newFile('/test/outer/inner/inner.dart', '');
var collection = _newCollection(includedPaths: [outerFolder.path]);
expect(collection.contexts, hasLength(1));
var context = collection.contexts.first;
expect(collection.contexts, hasLength(2));
var outerContext = collection.contexts
.singleWhere((c) => c.contextRoot.root == outerFolder);
var innerContext = collection.contexts
.singleWhere((c) => c.contextRoot.root == innerFolder);
expect(innerContext, isNot(same(outerContext)));
// Outer and inner contexts own corresponding files.
// Files with different analysis options, share a single context.
expect(collection.contextFor(convertPath('/test/outer/lib/outer.dart')),
same(outerContext));
same(context));
expect(collection.contextFor(convertPath('/test/outer/inner/inner.dart')),
same(innerContext));
same(context));
// The file does not have to exist, during creation, or at all.
// But have their own analysis options.
var outerOptions = context.getAnalysisOptionsForFile(outerFile);
expectContainsExactly(
outerOptions,
lints: ['always_specify_types'],
);
var innerOptions = context.getAnalysisOptionsForFile(innerFile);
expectContainsExactly(
innerOptions,
lints: ['camel_case_types'],
);
// Files do not have to exist, during creation, or at all.
expect(collection.contextFor(convertPath('/test/outer/lib/outer2.dart')),
same(outerContext));
same(context));
expect(collection.contextFor(convertPath('/test/outer/inner/inner2.dart')),
same(innerContext));
same(context));
}
test_new_sdkPath_notAbsolute() {
@ -178,4 +197,13 @@ linter:
sdkPath: sdkRoot.path,
);
}
static void expectContainsExactly(AnalysisOptions? options,
{List<String>? lints}) {
expect(options, isNotNull);
if (lints != null) {
var rules = options!.lintRules.map((e) => e.name);
expect(rules, unorderedEquals(lints));
}
}
}

View file

@ -920,30 +920,22 @@ ${getFolder(outPath).path}
Folder outerRootFolder = newFolder('/test/outer');
File outerOptionsFile = newAnalysisOptionsYamlFile('/test/outer', '');
File outerPackagesFile = newPackageConfigJsonFile('/test/outer', '');
Folder inner1RootFolder = newFolder('/test/outer/examples/inner1');
File inner1OptionsFile =
newAnalysisOptionsYamlFile('/test/outer/examples/inner1', '');
newFolder('/test/outer/examples/inner1');
newAnalysisOptionsYamlFile('/test/outer/examples/inner1', '');
Folder inner2RootFolder = newFolder('/test/outer/examples/inner2');
File inner2PackagesFile =
newPackageConfigJsonFile('/test/outer/examples/inner2', '');
List<ContextRoot> roots =
contextLocator.locateRoots(includedPaths: [outerRootFolder.path]);
expect(roots, hasLength(3));
expect(roots, hasLength(2));
ContextRoot outerRoot = findRoot(roots, outerRootFolder);
expect(outerRoot.includedPaths, unorderedEquals([outerRootFolder.path]));
expect(outerRoot.excludedPaths,
unorderedEquals([inner1RootFolder.path, inner2RootFolder.path]));
expect(outerRoot.excludedPaths, unorderedEquals([inner2RootFolder.path]));
expect(outerRoot.optionsFile, outerOptionsFile);
expect(outerRoot.packagesFile, outerPackagesFile);
ContextRoot inner1Root = findRoot(roots, inner1RootFolder);
expect(inner1Root.includedPaths, unorderedEquals([inner1RootFolder.path]));
expect(inner1Root.excludedPaths, isEmpty);
expect(inner1Root.optionsFile, inner1OptionsFile);
expect(inner1Root.packagesFile, outerPackagesFile);
ContextRoot inner2Root = findRoot(roots, inner2RootFolder);
expect(inner2Root.includedPaths, unorderedEquals([inner2RootFolder.path]));
expect(inner2Root.excludedPaths, isEmpty);
@ -955,25 +947,17 @@ ${getFolder(outPath).path}
Folder outerRootFolder = newFolder('/test/outer');
File outerOptionsFile = newAnalysisOptionsYamlFile('/test/outer', '');
File outerPackagesFile = newPackageConfigJsonFile('/test/outer', '');
Folder innerRootFolder = newFolder('/test/outer/examples/inner');
File innerOptionsFile =
newAnalysisOptionsYamlFile('/test/outer/examples/inner', '');
newAnalysisOptionsYamlFile('/test/outer/examples/inner', '');
List<ContextRoot> roots =
contextLocator.locateRoots(includedPaths: [outerRootFolder.path]);
expect(roots, hasLength(2));
expect(roots, hasLength(1));
ContextRoot outerRoot = findRoot(roots, outerRootFolder);
expect(outerRoot.includedPaths, unorderedEquals([outerRootFolder.path]));
expect(outerRoot.excludedPaths, unorderedEquals([innerRootFolder.path]));
expect(outerRoot.excludedPaths, isEmpty);
expect(outerRoot.optionsFile, outerOptionsFile);
expect(outerRoot.packagesFile, outerPackagesFile);
ContextRoot innerRoot = findRoot(roots, innerRootFolder);
expect(innerRoot.includedPaths, unorderedEquals([innerRootFolder.path]));
expect(innerRoot.excludedPaths, isEmpty);
expect(innerRoot.optionsFile, innerOptionsFile);
expect(innerRoot.packagesFile, outerPackagesFile);
}
void test_locateRoots_nested_options_overriddenOptions() {
@ -1000,27 +984,19 @@ ${getFolder(outPath).path}
Folder outerRootFolder = newFolder('/test/outer');
File outerOptionsFile = newAnalysisOptionsYamlFile('/test/outer', '');
newPackageConfigJsonFile('/test/outer', '');
Folder innerRootFolder = newFolder('/test/outer/examples/inner');
File innerOptionsFile =
newAnalysisOptionsYamlFile('/test/outer/examples/inner', '');
newAnalysisOptionsYamlFile('/test/outer/examples/inner', '');
File overridePackagesFile = newPackageConfigJsonFile('/test/override', '');
List<ContextRoot> roots = contextLocator.locateRoots(
includedPaths: [outerRootFolder.path],
packagesFile: overridePackagesFile.path);
expect(roots, hasLength(2));
expect(roots, hasLength(1));
ContextRoot outerRoot = findRoot(roots, outerRootFolder);
expect(outerRoot.includedPaths, unorderedEquals([outerRootFolder.path]));
expect(outerRoot.excludedPaths, unorderedEquals([innerRootFolder.path]));
expect(outerRoot.excludedPaths, isEmpty);
expect(outerRoot.optionsFile, outerOptionsFile);
expect(outerRoot.packagesFile, overridePackagesFile);
ContextRoot innerRoot = findRoot(roots, innerRootFolder);
expect(innerRoot.includedPaths, unorderedEquals([innerRootFolder.path]));
expect(innerRoot.excludedPaths, isEmpty);
expect(innerRoot.optionsFile, innerOptionsFile);
expect(innerRoot.packagesFile, overridePackagesFile);
}
void test_locateRoots_nested_optionsAndPackages() {
@ -1175,17 +1151,15 @@ ${getFolder(outPath).path}
Folder outerRootFolder = newFolder('/test/outer');
File outerOptionsFile = newAnalysisOptionsYamlFile('/test/outer', '');
File outerPackagesFile = newPackageConfigJsonFile('/test/outer', '');
File innerOptionsFile =
newAnalysisOptionsYamlFile('/test/outer/packages/inner', '');
newAnalysisOptionsYamlFile('/test/outer/packages/inner', '');
List<ContextRoot> roots =
contextLocator.locateRoots(includedPaths: [outerRootFolder.path]);
expect(roots, hasLength(2));
expect(roots, hasLength(1));
ContextRoot outerRoot = findRoot(roots, outerRootFolder);
expect(outerRoot.includedPaths, unorderedEquals([outerRootFolder.path]));
expect(outerRoot.excludedPaths,
unorderedEquals([innerOptionsFile.parent.path]));
expect(outerRoot.excludedPaths, isEmpty);
expect(outerRoot.optionsFile, outerOptionsFile);
expect(outerRoot.packagesFile, outerPackagesFile);
}