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:
Konstantin Shcheglov 2020-05-20 15:33:31 +00:00 committed by commit-bot@chromium.org
parent 15df850fd3
commit bb0b3cf23a
2 changed files with 499 additions and 0 deletions

View 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();
}
}
}

View 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);
});
});
}