mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 04:37:12 +00:00
AllowedExperiments and parser.
Change-Id: I9b05c1b9a58ac974dd4edf02db5ffb0d51d54484 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/148621 Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
parent
15df850fd3
commit
bb0b3cf23a
220
pkg/_fe_analyzer_shared/lib/src/sdk/allowed_experiments.dart
Normal file
220
pkg/_fe_analyzer_shared/lib/src/sdk/allowed_experiments.dart
Normal file
|
@ -0,0 +1,220 @@
|
|||
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
// 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:convert';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// Parse the given [jsonText] into the [AllowedExperiments].
|
||||
///
|
||||
/// Throw [FormatException] is any format issues are found.
|
||||
AllowedExperiments parseAllowedExperiments(String jsonText) {
|
||||
return new _AllowedExperimentsParser(jsonText).parse();
|
||||
}
|
||||
|
||||
/// The set of experiments enabled for SDK and packages.
|
||||
class AllowedExperiments {
|
||||
/// The set of experiments that are enabled for all SDK libraries other than
|
||||
/// for those which are specified in [sdkLibraryExperiments].
|
||||
final List<String> sdkDefaultExperiments;
|
||||
|
||||
/// Mapping from individual SDK libraries, e.g. 'core', to the set of
|
||||
/// experiments that are enabled for this library.
|
||||
final Map<String, List<String>> sdkLibraryExperiments;
|
||||
|
||||
/// Mapping from package names, e.g. 'path', to the set of experiments that
|
||||
/// are enabled for all files of this package.
|
||||
final Map<String, List<String>> packageExperiments;
|
||||
|
||||
AllowedExperiments({
|
||||
@required this.sdkDefaultExperiments,
|
||||
@required this.sdkLibraryExperiments,
|
||||
@required this.packageExperiments,
|
||||
});
|
||||
|
||||
/// Return the set of enabled experiments for the package with the [name],
|
||||
/// e.g. "path", possibly `null`.
|
||||
List<String> forPackage(String name) {
|
||||
return packageExperiments[name];
|
||||
}
|
||||
|
||||
/// Return the set of enabled experiments for the library with the [name],
|
||||
/// e.g. "core".
|
||||
List<String> forSdkLibrary(String name) {
|
||||
return sdkLibraryExperiments[name] ?? sdkDefaultExperiments;
|
||||
}
|
||||
}
|
||||
|
||||
class _AllowedExperimentsParser {
|
||||
final String _jsonText;
|
||||
final List<String> _parsePath = [];
|
||||
final Map<String, List<String>> _experimentSets = {};
|
||||
|
||||
_AllowedExperimentsParser(this._jsonText);
|
||||
|
||||
AllowedExperiments parse() {
|
||||
Object rootObject = json.decode(_jsonText);
|
||||
Map<String, Object> map = _mustBeMap(rootObject);
|
||||
|
||||
_ensureVersion(map);
|
||||
|
||||
_withParsePath('experimentSets', () {
|
||||
Map<String, Object> experimentSetMap =
|
||||
_requiredMap(map, 'experimentSets');
|
||||
for (MapEntry<String, Object> entry in experimentSetMap.entries) {
|
||||
String setName = entry.key;
|
||||
_withParsePath(setName, () {
|
||||
_experimentSets[setName] = _mustBeListOfStrings(entry.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
List<String> sdkDefaultExperimentSet = <String>[];
|
||||
Map<String, List<String>> sdkLibraryExperiments = <String, List<String>>{};
|
||||
_withParsePath('sdk', () {
|
||||
Map<String, Object> sdkMap = _requiredMap(map, 'sdk');
|
||||
|
||||
_withParsePath('default', () {
|
||||
sdkDefaultExperimentSet = _experimentList(
|
||||
_requiredMap(sdkMap, 'default'),
|
||||
);
|
||||
});
|
||||
|
||||
_withParsePath('libraries', () {
|
||||
Map<String, Object> sdkLibraryExperimentsMap =
|
||||
_optionalMap(sdkMap, 'libraries');
|
||||
if (sdkLibraryExperimentsMap != null) {
|
||||
for (MapEntry<String, Object> entry
|
||||
in sdkLibraryExperimentsMap.entries) {
|
||||
String libraryName = entry.key;
|
||||
_withParsePath(libraryName, () {
|
||||
Map<String, Object> libraryMap = _mustBeMap(entry.value);
|
||||
List<String> experimentList = _experimentList(libraryMap);
|
||||
sdkLibraryExperiments[libraryName] = experimentList;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Map<String, List<String>> packageExperiments = <String, List<String>>{};
|
||||
_withParsePath('packages', () {
|
||||
Map<String, Object> packageExperimentsMap = _optionalMap(map, 'packages');
|
||||
if (packageExperimentsMap != null) {
|
||||
for (MapEntry<String, Object> entry in packageExperimentsMap.entries) {
|
||||
String packageName = entry.key;
|
||||
_withParsePath(packageName, () {
|
||||
Map<String, Object> libraryMap = _mustBeMap(entry.value);
|
||||
List<String> experimentList = _experimentList(libraryMap);
|
||||
packageExperiments[packageName] = experimentList;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new AllowedExperiments(
|
||||
sdkDefaultExperiments: sdkDefaultExperimentSet,
|
||||
sdkLibraryExperiments: sdkLibraryExperiments,
|
||||
packageExperiments: packageExperiments,
|
||||
);
|
||||
}
|
||||
|
||||
void _ensureVersion(Map<String, Object> map) {
|
||||
Object versionObject = map['version'];
|
||||
if (versionObject is! int || versionObject != 1) {
|
||||
throw new FormatException(
|
||||
"Expected field 'version' with value '1'; "
|
||||
"actually ${versionObject.runtimeType}: $versionObject",
|
||||
_jsonText,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _experimentList(Map<String, Object> map) {
|
||||
String experimentSetName = _requiredString(map, 'experimentSet');
|
||||
List<String> result = _experimentSets[experimentSetName];
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new FormatException(
|
||||
"No experiment set '$experimentSetName in $_experimentSets",
|
||||
_jsonText,
|
||||
);
|
||||
}
|
||||
|
||||
List<String> _mustBeListOfStrings(Object object) {
|
||||
if (object is List<Object> && object.every((e) => e is String)) {
|
||||
return List.castFrom(object);
|
||||
}
|
||||
|
||||
String path = _parsePath.join(' / ');
|
||||
throw new FormatException(
|
||||
"Expected list of strings at $path, "
|
||||
"actually ${object.runtimeType}: $object",
|
||||
_jsonText,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, Object> _mustBeMap(Object object) {
|
||||
if (object is Map<String, Object>) {
|
||||
return object;
|
||||
}
|
||||
|
||||
String path = _parsePath.isNotEmpty ? _parsePath.join(' / ') : 'root';
|
||||
throw new FormatException(
|
||||
"Expected map at $path, "
|
||||
"actually ${object.runtimeType}: $object",
|
||||
_jsonText,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, Object> _optionalMap(Map<String, Object> map, String name) {
|
||||
Object result = map[name];
|
||||
if (result == null || result is Map<String, Object>) {
|
||||
return result;
|
||||
}
|
||||
|
||||
String path = _parsePath.join(' / ');
|
||||
throw new FormatException(
|
||||
"Expected map at $path, actually ${result.runtimeType}: $result",
|
||||
_jsonText,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, Object> _requiredMap(Map<String, Object> map, String name) {
|
||||
Object result = map[name];
|
||||
if (result is Map<String, Object>) {
|
||||
return result;
|
||||
}
|
||||
|
||||
String path = _parsePath.join(' / ');
|
||||
throw new FormatException(
|
||||
"Expected map at $path, actually ${result.runtimeType}: $result",
|
||||
_jsonText,
|
||||
);
|
||||
}
|
||||
|
||||
String _requiredString(Map<String, Object> map, String name) {
|
||||
Object result = map[name];
|
||||
if (result is String) {
|
||||
return result;
|
||||
}
|
||||
|
||||
String path = _parsePath.join(' / ');
|
||||
throw new FormatException(
|
||||
"Expected string at $path, actually ${result.runtimeType}: $result",
|
||||
_jsonText,
|
||||
);
|
||||
}
|
||||
|
||||
void _withParsePath(String name, void Function() f) {
|
||||
_parsePath.add(name);
|
||||
try {
|
||||
f();
|
||||
} finally {
|
||||
_parsePath.removeLast();
|
||||
}
|
||||
}
|
||||
}
|
279
pkg/_fe_analyzer_shared/test/sdk/allowed_experiments_test.dart
Normal file
279
pkg/_fe_analyzer_shared/test/sdk/allowed_experiments_test.dart
Normal file
|
@ -0,0 +1,279 @@
|
|||
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
||||
// 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:_fe_analyzer_shared/src/sdk/allowed_experiments.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
group('invalid', () {
|
||||
void assertFormalException(String text) {
|
||||
expect(() {
|
||||
return parseAllowedExperiments(text);
|
||||
}, throwsFormatException);
|
||||
}
|
||||
|
||||
test('not map', () {
|
||||
assertFormalException('42');
|
||||
});
|
||||
|
||||
test('no version', () {
|
||||
assertFormalException('{}');
|
||||
});
|
||||
|
||||
test('version not int', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": "abc"
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('version not int 1', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 2
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('no experiment sets', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 1,
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('experimentSet: not map', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": 42
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('experimentSet entry: not list', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"nullSafety": 42
|
||||
}
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('no sdk', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"nullSafety": ["non-nullable"]
|
||||
}
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('no sdk / default', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"nullSafety": ["non-nullable"]
|
||||
},
|
||||
"sdk": {}
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('experimentSet not string', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"nullSafety": ["non-nullable"]
|
||||
},
|
||||
"sdk": {
|
||||
"default": {
|
||||
"experimentSet": 42
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('experimentSet not defined', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"nullSafety": ["non-nullable"]
|
||||
},
|
||||
"sdk": {
|
||||
"default": {
|
||||
"experimentSet": "notDefined"
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('sdk libraries not map', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"nullSafety": ["non-nullable"]
|
||||
},
|
||||
"sdk": {
|
||||
"default": {
|
||||
"experimentSet": "nullSafety"
|
||||
},
|
||||
"libraries": 42
|
||||
}
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('packages not map', () {
|
||||
assertFormalException('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"nullSafety": ["non-nullable"]
|
||||
},
|
||||
"sdk": {
|
||||
"default": {
|
||||
"experimentSet": "nullSafety"
|
||||
}
|
||||
},
|
||||
"packages": 42
|
||||
}
|
||||
''');
|
||||
});
|
||||
});
|
||||
|
||||
group('valid', () {
|
||||
void assertExperiments(
|
||||
AllowedExperiments experiments, {
|
||||
@required List<String> sdkDefaultExperiments,
|
||||
@required Map<String, List<String>> sdkLibraryExperiments,
|
||||
@required Map<String, List<String>> packageExperiments,
|
||||
}) {
|
||||
expect(experiments.sdkDefaultExperiments, sdkDefaultExperiments);
|
||||
expect(experiments.sdkLibraryExperiments, sdkLibraryExperiments);
|
||||
expect(experiments.packageExperiments, packageExperiments);
|
||||
}
|
||||
|
||||
test('sdk default, no sdk libraries, no packages', () {
|
||||
var experiments = parseAllowedExperiments('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"foo": ["foo1"]
|
||||
},
|
||||
"sdk": {
|
||||
"default": {
|
||||
"experimentSet": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
assertExperiments(
|
||||
experiments,
|
||||
sdkDefaultExperiments: ['foo1'],
|
||||
sdkLibraryExperiments: {},
|
||||
packageExperiments: {},
|
||||
);
|
||||
});
|
||||
|
||||
test('sdk default, sdk libraries, no packages', () {
|
||||
var experiments = parseAllowedExperiments('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"foo": ["foo1"],
|
||||
"bar": ["bar1", "bar2"]
|
||||
},
|
||||
"sdk": {
|
||||
"default": {
|
||||
"experimentSet": "foo"
|
||||
},
|
||||
"libraries": {
|
||||
"sdkA": {
|
||||
"experimentSet": "foo"
|
||||
},
|
||||
"sdkB": {
|
||||
"experimentSet": "bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
assertExperiments(
|
||||
experiments,
|
||||
sdkDefaultExperiments: ['foo1'],
|
||||
sdkLibraryExperiments: {
|
||||
"sdkA": ['foo1'],
|
||||
"sdkB": ['bar1', 'bar2'],
|
||||
},
|
||||
packageExperiments: {},
|
||||
);
|
||||
});
|
||||
|
||||
test('sdk default, sdk libraries, packages', () {
|
||||
var experiments = parseAllowedExperiments('''
|
||||
{
|
||||
"version": 1,
|
||||
"experimentSets": {
|
||||
"foo": ["foo1"],
|
||||
"bar": ["bar1", "bar2"],
|
||||
"baz": ["baz1", "baz2"]
|
||||
},
|
||||
"sdk": {
|
||||
"default": {
|
||||
"experimentSet": "foo"
|
||||
},
|
||||
"libraries": {
|
||||
"sdkA": {
|
||||
"experimentSet": "bar"
|
||||
},
|
||||
"sdkB": {
|
||||
"experimentSet": "baz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packages": {
|
||||
"pkgA": {
|
||||
"experimentSet": "bar"
|
||||
},
|
||||
"pkgB": {
|
||||
"experimentSet": "baz"
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
assertExperiments(
|
||||
experiments,
|
||||
sdkDefaultExperiments: ['foo1'],
|
||||
sdkLibraryExperiments: {
|
||||
"sdkA": ['bar1', 'bar2'],
|
||||
"sdkB": ['baz1', 'baz2'],
|
||||
},
|
||||
packageExperiments: {
|
||||
"pkgA": ['bar1', 'bar2'],
|
||||
"pkgB": ['baz1', 'baz2'],
|
||||
},
|
||||
);
|
||||
expect(experiments.forSdkLibrary('core'), ['foo1']);
|
||||
expect(experiments.forSdkLibrary('sdkA'), ['bar1', 'bar2']);
|
||||
expect(experiments.forSdkLibrary('sdkB'), ['baz1', 'baz2']);
|
||||
expect(experiments.forPackage('pkgA'), ['bar1', 'bar2']);
|
||||
expect(experiments.forPackage('pkgB'), ['baz1', 'baz2']);
|
||||
expect(experiments.forPackage('pkgC'), isNull);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue