blob: 3aabe6107fdfaa46de9ceb2c79a773b4dc3b02e4 [file] [log] [blame]
/// This file contains code to generate experimental flags
/// based on the information in tools/experimental_features.yaml.
import 'dart:io';
import 'package:_fe_analyzer_shared/src/scanner/characters.dart'
show $MINUS, $_;
import 'package:analyzer_utilities/package_root.dart' as pkg_root;
import 'package:analyzer_utilities/tools.dart';
import 'package:path/path.dart';
import 'package:yaml/yaml.dart' show YamlMap, loadYaml;
main() async {
await GeneratedContent.generateAll(
normalize(join(pkg_root.packageRoot, 'analyzer')), allTargets);
}
List<GeneratedContent> get allTargets {
Map<dynamic, dynamic> experimentsYaml = loadYaml(File(join(
normalize(join(pkg_root.packageRoot, '../tools')),
'experimental_features.yaml'))
.readAsStringSync());
return <GeneratedContent>[
GeneratedFile('lib/src/dart/analysis/experiments.g.dart',
(String pkgPath) async {
var generator = _ExperimentsGenerator(experimentsYaml);
generator.generateFormatCode();
return generator.out.toString();
}),
];
}
String keyToIdentifier(String key) {
var identifier = StringBuffer();
for (int index = 0; index < key.length; ++index) {
var code = key.codeUnitAt(index);
if (code == $MINUS) {
code = $_;
}
identifier.writeCharCode(code);
}
return identifier.toString();
}
class _ExperimentsGenerator {
final Map experimentsYaml;
late List<String> keysSorted;
final out = StringBuffer('''
//
// THIS FILE IS GENERATED. DO NOT EDIT.
//
// Instead modify 'tools/experimental_features.yaml' and run
// 'dart pkg/analyzer/tool/experiments/generate.dart' to update.
part of 'experiments.dart';
''');
Map<String, dynamic>? _features;
_ExperimentsGenerator(this.experimentsYaml);
Map<String, dynamic> get features {
var features = _features;
if (features != null) return features;
features = <String, dynamic>{};
Map yamlFeatures = experimentsYaml['features'];
for (MapEntry entry in yamlFeatures.entries) {
String category = (entry.value as YamlMap)['category'] ?? 'language';
if (category != "language") {
// Skip a feature with a category that's not language. In the future
// possibly allow e.g. 'analyzer' etc.
continue;
}
features[entry.key] = entry.value;
}
return _features = features;
}
void generateFormatCode() {
keysSorted = features.keys.toList()..sort();
generateSection_CurrentVersion();
generateSection_KnownFeatures();
generateSection_EnableString();
generateSection_ExperimentalFeature();
generateSection_IsEnabledByDefault();
generateSection_IsExpired();
generateSection_CurrentState();
}
void generateSection_CurrentState() {
out.write('''
mixin _CurrentState {
''');
for (var key in keysSorted) {
var id = keyToIdentifier(key);
out.write('''
/// Current state for the flag "$key"
bool get $id => isEnabled(ExperimentalFeatures.$id);
''');
}
out.write('''
bool isEnabled(covariant ExperimentalFeature feature);
}''');
}
void generateSection_CurrentVersion() {
var version = _versionNumberAsString(experimentsYaml['current-version']);
out.write('''
/// The current version of the Dart language (or, for non-stable releases, the
/// version of the language currently in the process of being developed).
const _currentVersion = '$version';
''');
}
void generateSection_EnableString() {
out.write('''
/// Constant strings for enabling each of the currently known experimental
/// flags.
class EnableString {
''');
for (var key in keysSorted) {
out.write('''
/// String to enable the experiment "$key"
static const String ${keyToIdentifier(key)} = '$key';
''');
}
out.write('''
}''');
}
void generateSection_ExperimentalFeature() {
out.write('''
class ExperimentalFeatures {
''');
int index = 0;
for (var key in keysSorted) {
var id = keyToIdentifier(key);
var help = (features[key] as YamlMap)['help'] ?? '';
var experimentalReleaseVersion =
(features[key] as YamlMap)['experimentalReleaseVersion'];
var enabledIn = (features[key] as YamlMap)['enabledIn'];
out.write('''
static final $id = ExperimentalFeature(
index: $index,
enableString: EnableString.$id,
isEnabledByDefault: IsEnabledByDefault.$id,
isExpired: IsExpired.$id,
documentation: '$help',
''');
if (experimentalReleaseVersion != null) {
experimentalReleaseVersion =
_versionNumberAsString(experimentalReleaseVersion);
out.write("experimentalReleaseVersion: ");
out.write("Version.parse('$experimentalReleaseVersion'),");
} else {
out.write("experimentalReleaseVersion: null,");
}
if (enabledIn != null) {
enabledIn = _versionNumberAsString(enabledIn);
out.write("releaseVersion: Version.parse('$enabledIn'),");
} else {
out.write("releaseVersion: null,");
}
out.writeln(');');
++index;
}
out.write('''
}''');
}
void generateSection_IsEnabledByDefault() {
out.write('''
/// Constant bools indicating whether each experimental flag is currently
/// enabled by default.
class IsEnabledByDefault {
''');
for (var key in keysSorted) {
var entry = features[key] as YamlMap;
bool shipped = entry['enabledIn'] != null;
out.write('''
/// Default state of the experiment "$key"
static const bool ${keyToIdentifier(key)} = $shipped;
''');
}
out.write('''
}''');
}
void generateSection_IsExpired() {
out.write('''
/// Constant bools indicating whether each experimental flag is currently
/// expired (meaning its enable/disable status can no longer be altered from the
/// value in [IsEnabledByDefault]).
class IsExpired {
''');
for (var key in keysSorted) {
var entry = features[key] as YamlMap;
bool shipped = entry['enabledIn'] != null;
bool? expired = entry['expired'];
out.write('''
/// Expiration status of the experiment "$key"
static const bool ${keyToIdentifier(key)} = ${expired == true};
''');
if (shipped && expired == false) {
throw 'Cannot mark shipped feature as "expired: false"';
}
}
out.write('''
}''');
}
void generateSection_KnownFeatures() {
out.write('''
/// A map containing information about all known experimental flags.
final _knownFeatures = <String, ExperimentalFeature>{
''');
for (var key in keysSorted) {
var id = keyToIdentifier(key);
out.write('''
EnableString.$id: ExperimentalFeatures.$id,
''');
}
out.write('''
};
''');
}
String _versionNumberAsString(dynamic enabledIn) {
if (enabledIn is double) {
return '$enabledIn.0';
} else {
return enabledIn.toString();
}
}
}