Embedded options discovery (#24943).

Merges analysis options extracted from `_embedder.yaml` files (potentially) with local overrides.

Background: https://github.com/dart-lang/sdk/issues/24943

For test coverage of merging, see: `analyzer/test/src/util/yaml_test.dart`.

BUG=24943
R=brianwilkerson@google.com, johnmccutchan@google.com

Review URL: https://codereview.chromium.org/1445363002 .
This commit is contained in:
pq 2015-11-18 16:41:27 -08:00
parent 3d43d6d51c
commit 3a1a3ee094
3 changed files with 207 additions and 99 deletions

View file

@ -28,6 +28,7 @@ import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/task/options.dart';
import 'package:analyzer/src/util/absolute_path.dart';
import 'package:analyzer/src/util/yaml.dart';
import 'package:package_config/packages.dart';
import 'package:package_config/packages_file.dart' as pkgfile show parse;
import 'package:package_config/src/packages_impl.dart' show MapPackages;
@ -515,15 +516,28 @@ class ContextManagerImpl implements ContextManager {
}
});
// In case options files are removed, revert to default options.
// In case options files are removed, revert to defaults.
if (optionsRemoved) {
// Start with defaults.
info.context.analysisOptions = new AnalysisOptionsImpl();
// Apply inherited options.
YamlMap embeddedOptions = _getEmbeddedOptions(info.context);
if (embeddedOptions != null) {
configureContextOptions(info.context, embeddedOptions);
}
return;
}
// Check for embedded options.
YamlMap embeddedOptions = _getEmbeddedOptions(info.context);
if (embeddedOptions != null) {
options = new Merger().merge(embeddedOptions, options);
}
// Analysis options are processed 'in-line'.
var analyzer = options[AnalyzerOptions.analyzer];
if (analyzer is! YamlMap) {
if (analyzer is! Map) {
// No options for analyzer.
return;
}
@ -1023,6 +1037,20 @@ class ContextManagerImpl implements ContextManager {
return packageSpec;
}
/// Get analysis options associated with an `_embedder.yaml`. If there is
/// more than one `_embedder.yaml` associated with the given context, `null`
/// is returned.
YamlMap _getEmbeddedOptions(AnalysisContext context) {
if (context is InternalAnalysisContext) {
EmbedderYamlLocator locator = context.embedderYamlLocator;
Iterable<YamlMap> maps = locator.embedderYamls.values;
if (maps.length == 1) {
return maps.first;
}
}
return null;
}
/**
* Return the [ContextInfo] for the "innermost" context whose associated
* folder is or contains the given path. ("innermost" refers to the nesting

View file

@ -93,6 +93,80 @@ class AbstractContextManagerTest {
resourceProvider.newFolder(projPath);
}
test_embedder_options() async {
// Create files.
String libPath = newFolder([projPath, LIB_NAME]);
String sdkExtPath = newFolder([projPath, 'sdk_ext']);
newFile([projPath, 'test', 'test.dart']);
newFile([sdkExtPath, 'entry.dart']);
// Setup _embedder.yaml.
newFile(
[libPath, '_embedder.yaml'],
r'''
embedder_libs:
"dart:foobar": "../sdk_ext/entry.dart"
analyzer:
strong-mode: true
language:
enableSuperMixins: true
''');
// Setup .packages file
newFile(
[projPath, '.packages'],
r'''
test_pack:lib/''');
// Setup .analysis_options
newFile(
[projPath, AnalysisEngine.ANALYSIS_OPTIONS_FILE],
r'''
analyzer:
exclude:
- 'test/**'
language:
enableGenericMethods: true
errors:
unused_local_variable: false
''');
// Setup context.
manager.setRoots(<String>[projPath], <String>[], <String, String>{});
await pumpEventQueue();
// Confirm that one context was created.
var contexts =
manager.contextsInAnalysisRoot(resourceProvider.newFolder(projPath));
expect(contexts, isNotNull);
expect(contexts, hasLength(1));
var context = contexts[0];
// Verify options.
// * from `_embedder.yaml`:
expect(context.analysisOptions.strongMode, isTrue);
expect(context.analysisOptions.enableSuperMixins, isTrue);
// * from `.analysis_options`:
expect(context.analysisOptions.enableGenericMethods, isTrue);
// verify tests are excluded
expect(callbacks.currentContextFilePaths[projPath].keys,
['/my/proj/sdk_ext/entry.dart']);
// Verify filter setup.
List<ErrorFilter> filters =
callbacks.currentContext.getConfigurationData(CONFIGURED_ERROR_FILTERS);
expect(filters, hasLength(1));
expect(
filters.first(new AnalysisError(
new TestSource(), 0, 1, HintCode.UNUSED_LOCAL_VARIABLE, [
['x']
])),
isTrue);
// Sanity check embedder libs.
var source = context.sourceFactory.forUri('dart:foobar');
expect(source, isNotNull);
expect(source.fullName, '/my/proj/sdk_ext/entry.dart');
}
test_analysis_options_parse_failure() async {
// Create files.
String libPath = newFolder([projPath, LIB_NAME]);
@ -138,6 +212,49 @@ class AbstractContextManagerTest {
expect(contexts, contains(subProjContextInfo.context));
}
test_embedder_packagespec() async {
// Create files.
String libPath = newFolder([projPath, LIB_NAME]);
newFile([libPath, 'main.dart']);
newFile([libPath, 'nope.dart']);
String sdkExtPath = newFolder([projPath, 'sdk_ext']);
newFile([sdkExtPath, 'entry.dart']);
String sdkExtSrcPath = newFolder([projPath, 'sdk_ext', 'src']);
newFile([sdkExtSrcPath, 'part.dart']);
// Setup _embedder.yaml.
newFile(
[libPath, '_embedder.yaml'],
r'''
embedder_libs:
"dart:foobar": "../sdk_ext/entry.dart"
"dart:typed_data": "../sdk_ext/src/part"
''');
// Setup .packages file
newFile(
[projPath, '.packages'],
r'''
test_pack:lib/''');
// Setup context.
manager.setRoots(<String>[projPath], <String>[], <String, String>{});
await pumpEventQueue();
// Confirm that one context was created.
var contexts =
manager.contextsInAnalysisRoot(resourceProvider.newFolder(projPath));
expect(contexts, isNotNull);
expect(contexts.length, equals(1));
var context = contexts[0];
var source = context.sourceFactory.forUri('dart:foobar');
expect(source, isNotNull);
expect(source.fullName, equals('/my/proj/sdk_ext/entry.dart'));
// We can't find dart:core because we didn't list it in our
// embedder_libs map.
expect(context.sourceFactory.forUri('dart:core'), isNull);
// We can find dart:typed_data because we listed it in our
// embedder_libs map.
expect(context.sourceFactory.forUri('dart:typed_data'), isNotNull);
}
test_error_filter_analysis_option() async {
// Create files.
newFile(
@ -568,49 +685,6 @@ analyzer:
});
}
test_embedder_packagespec() async {
// Create files.
String libPath = newFolder([projPath, LIB_NAME]);
newFile([libPath, 'main.dart']);
newFile([libPath, 'nope.dart']);
String sdkExtPath = newFolder([projPath, 'sdk_ext']);
newFile([sdkExtPath, 'entry.dart']);
String sdkExtSrcPath = newFolder([projPath, 'sdk_ext', 'src']);
newFile([sdkExtSrcPath, 'part.dart']);
// Setup _embedder.yaml.
newFile(
[libPath, '_embedder.yaml'],
r'''
embedder_libs:
"dart:foobar": "../sdk_ext/entry.dart"
"dart:typed_data": "../sdk_ext/src/part"
''');
// Setup .packages file
newFile(
[projPath, '.packages'],
r'''
test_pack:lib/''');
// Setup context.
manager.setRoots(<String>[projPath], <String>[], <String, String>{});
await pumpEventQueue();
// Confirm that one context was created.
var contexts =
manager.contextsInAnalysisRoot(resourceProvider.newFolder(projPath));
expect(contexts, isNotNull);
expect(contexts.length, equals(1));
var context = contexts[0];
var source = context.sourceFactory.forUri('dart:foobar');
expect(source, isNotNull);
expect(source.fullName, equals('/my/proj/sdk_ext/entry.dart'));
// We can't find dart:core because we didn't list it in our
// embedder_libs map.
expect(context.sourceFactory.forUri('dart:core'), isNull);
// We can find dart:typed_data because we listed it in our
// embedder_libs map.
expect(context.sourceFactory.forUri('dart:typed_data'), isNotNull);
}
test_sdk_ext_packagespec() async {
// Create files.
String libPath = newFolder([projPath, LIB_NAME]);
@ -1943,19 +2017,6 @@ class TestContextManagerCallbacks extends ContextManagerCallbacks {
*/
Iterable<String> get currentContextPaths => currentContextTimestamps.keys;
/// If [disposition] has a package map, attempt to locate `_embedder.yaml`
/// files.
void _locateEmbedderYamls(InternalAnalysisContext context,
FolderDisposition disposition) {
Map<String, List<Folder>> packageMap;
if (disposition is PackageMapDisposition) {
packageMap = disposition.packageMap;
} else if (disposition is PackagesFileDisposition) {
packageMap = disposition.buildPackageMap(resourceProvider);
}
context.embedderYamlLocator.refresh(packageMap);
}
@override
AnalysisContext addContext(Folder folder, FolderDisposition disposition) {
String path = folder.path;
@ -2046,6 +2107,19 @@ class TestContextManagerCallbacks extends ContextManagerCallbacks {
Folder contextFolder, FolderDisposition disposition) {
currentContextDispositions[contextFolder.path] = disposition;
}
/// If [disposition] has a package map, attempt to locate `_embedder.yaml`
/// files.
void _locateEmbedderYamls(
InternalAnalysisContext context, FolderDisposition disposition) {
Map<String, List<Folder>> packageMap;
if (disposition is PackageMapDisposition) {
packageMap = disposition.packageMap;
} else if (disposition is PackagesFileDisposition) {
packageMap = disposition.buildPackageMap(resourceProvider);
}
context.embedderYamlLocator.refresh(packageMap);
}
}
/**

View file

@ -227,28 +227,6 @@ class GenerateOptionsErrorsTask extends SourceBasedAnalysisTask {
new GenerateOptionsErrorsTask(context, target);
}
/// Validates `analyzer` strong-mode value configuration options.
class StrongModeOptionValueValidator extends OptionsValidator {
ErrorBuilder trueOrFalseBuilder = new TrueOrFalseValueErrorBuilder();
@override
void validate(ErrorReporter reporter, Map<String, YamlNode> options) {
var analyzer = options[AnalyzerOptions.analyzer];
if (analyzer is! YamlMap) {
return;
}
var v = analyzer.nodes[AnalyzerOptions.strong_mode];
if (v is YamlScalar) {
var value = toLowerCase(v.value);
if (!AnalyzerOptions.trueOrFalse.contains(value)) {
trueOrFalseBuilder.reportError(
reporter, AnalyzerOptions.strong_mode, v);
}
}
}
}
/// Validates `analyzer` language configuration options.
class LanguageOptionValidator extends OptionsValidator {
ErrorBuilder builder = new ErrorBuilder(AnalyzerOptions.languageOptions);
@ -313,6 +291,28 @@ class OptionsFileValidator {
}
}
/// Validates `analyzer` strong-mode value configuration options.
class StrongModeOptionValueValidator extends OptionsValidator {
ErrorBuilder trueOrFalseBuilder = new TrueOrFalseValueErrorBuilder();
@override
void validate(ErrorReporter reporter, Map<String, YamlNode> options) {
var analyzer = options[AnalyzerOptions.analyzer];
if (analyzer is! YamlMap) {
return;
}
var v = analyzer.nodes[AnalyzerOptions.strong_mode];
if (v is YamlScalar) {
var value = toLowerCase(v.value);
if (!AnalyzerOptions.trueOrFalse.contains(value)) {
trueOrFalseBuilder.reportError(
reporter, AnalyzerOptions.strong_mode, v);
}
}
}
}
/// Validates `analyzer` top-level options.
class TopLevelAnalyzerOptionsValidator extends TopLevelOptionValidator {
TopLevelAnalyzerOptionsValidator()
@ -372,7 +372,7 @@ class _OptionsProcessor {
}
var analyzer = options[AnalyzerOptions.analyzer];
if (analyzer is! YamlMap) {
if (analyzer is! Map) {
return;
}
@ -409,30 +409,36 @@ class _OptionsProcessor {
context.setConfigurationData(CONFIGURED_ERROR_FILTERS, filters);
}
void setLanguageOption(
AnalysisContext context, Object feature, Object value) {
if (feature == AnalyzerOptions.enableSuperMixins) {
if (isTrue(value)) {
AnalysisOptionsImpl options =
new AnalysisOptionsImpl.from(context.analysisOptions);
options.enableSuperMixins = true;
context.analysisOptions = options;
}
}
if (feature == AnalyzerOptions.enableGenericMethods) {
if (isTrue(value)) {
AnalysisOptionsImpl options =
new AnalysisOptionsImpl.from(context.analysisOptions);
options.enableGenericMethods = true;
context.analysisOptions = options;
}
}
}
void setLanguageOptions(AnalysisContext context, Object configs) {
if (configs is YamlMap) {
configs.nodes.forEach((k, v) {
String feature;
if (k is YamlScalar && v is YamlScalar) {
feature = k.value?.toString();
if (feature == AnalyzerOptions.enableSuperMixins) {
if (isTrue(v.value)) {
AnalysisOptionsImpl options =
new AnalysisOptionsImpl.from(context.analysisOptions);
options.enableSuperMixins = true;
context.analysisOptions = options;
}
}
if (feature == AnalyzerOptions.enableGenericMethods) {
if (isTrue(v.value)) {
AnalysisOptionsImpl options =
new AnalysisOptionsImpl.from(context.analysisOptions);
options.enableGenericMethods = true;
context.analysisOptions = options;
}
}
String feature = k.value?.toString();
setLanguageOption(context, feature, v.value);
}
});
} else if (configs is Map) {
configs.forEach((k, v) => setLanguageOption(context, k, v));
}
}