[dartdev] Implement --enable-experiments for dart run and dart pub run.
Related to https://github.com/dart-lang/sdk/issues/42339. Change-Id: I3fdeee33dcad0ca031f483e2e3692be300392958 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/152960 Reviewed-by: Jaime Wren <jwren@google.com> Commit-Queue: Devon Carew <devoncarew@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
d49a98b866
commit
fc1999de15
@@ -47,8 +47,7 @@ class LineInfo {
|
||||
/// Return the location information for the character at the given [offset].
|
||||
///
|
||||
/// A future version of this API will return a [CharacterLocation] rather than
|
||||
/// // ignore: deprecated_member_use_from_same_package
|
||||
/// a [LineInfo_Location].
|
||||
/// a [LineInfo_Location]. // ignore: deprecated_member_use_from_same_package
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
LineInfo_Location getLocation(int offset) {
|
||||
var min = 0;
|
||||
|
||||
@@ -33,7 +33,7 @@ class ExperimentStatus with _CurrentState implements FeatureSet {
|
||||
static final Version currentVersion = Version.parse(_currentVersion);
|
||||
|
||||
/// A map containing information about all known experimental flags.
|
||||
static const knownFeatures = _knownFeatures;
|
||||
static const Map<String, ExperimentalFeature> knownFeatures = _knownFeatures;
|
||||
|
||||
final List<bool> _enableFlags;
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ class ExperimentalFeatures {
|
||||
enableString: EnableString.nonfunction_type_aliases,
|
||||
isEnabledByDefault: IsEnabledByDefault.nonfunction_type_aliases,
|
||||
isExpired: IsExpired.nonfunction_type_aliases,
|
||||
documentation: 'Type aliases define a <type>, not just a <functionType>.',
|
||||
documentation: 'Type aliases define a <type>, not just a <functionType>',
|
||||
firstSupportedVersion: null,
|
||||
);
|
||||
|
||||
@@ -161,7 +161,7 @@ class ExperimentalFeatures {
|
||||
enableString: EnableString.variance,
|
||||
isEnabledByDefault: IsEnabledByDefault.variance,
|
||||
isExpired: IsExpired.variance,
|
||||
documentation: 'Sound variance.',
|
||||
documentation: 'Sound variance',
|
||||
firstSupportedVersion: null,
|
||||
);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:analyzer/src/dart/analysis/experiments.dart';
|
||||
import 'package:args/args.dart';
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:cli_util/cli_logging.dart';
|
||||
@@ -19,13 +20,14 @@ import 'src/commands/pub.dart';
|
||||
import 'src/commands/run.dart';
|
||||
import 'src/commands/test.dart';
|
||||
import 'src/core.dart';
|
||||
import 'src/experiments.dart';
|
||||
|
||||
/// This is typically called from bin/, but given the length of the method and
|
||||
/// analytics logic, it has been moved here. Also note that this method calls
|
||||
/// [io.exit(code)] directly.
|
||||
Future<void> runDartdev(List<String> args) async {
|
||||
final stopwatch = Stopwatch();
|
||||
dynamic result;
|
||||
int result;
|
||||
|
||||
// The Analytics instance used to report information back to Google Analytics,
|
||||
// see lib/src/analytics.dart.
|
||||
@@ -104,7 +106,7 @@ Future<void> runDartdev(List<String> args) async {
|
||||
stopwatch.stop();
|
||||
|
||||
// Set the exitCode, if it wasn't set in the catch block above.
|
||||
exitCode ??= result is int ? result : 0;
|
||||
exitCode ??= result ?? 0;
|
||||
|
||||
// Send analytics before exiting
|
||||
if (analytics.enabled) {
|
||||
@@ -151,8 +153,7 @@ class DartdevRunner<int> extends CommandRunner {
|
||||
argParser.addFlag('disable-analytics',
|
||||
negatable: false, help: 'Disable anonymous analytics.');
|
||||
|
||||
// TODO(jwren): hook up.
|
||||
argParser.addMultiOption('enable-experiment', hide: true);
|
||||
addExperimentalFlags(argParser, verbose);
|
||||
|
||||
// A hidden flag to disable analytics on this run, this constructor can be
|
||||
// called with this flag, but should be removed before run() is called as
|
||||
@@ -179,6 +180,7 @@ class DartdevRunner<int> extends CommandRunner {
|
||||
@override
|
||||
Future<int> runCommand(ArgResults topLevelResults) async {
|
||||
assert(!topLevelResults.arguments.contains('--disable-dartdev-analytics'));
|
||||
|
||||
if (topLevelResults.command == null &&
|
||||
topLevelResults.arguments.isNotEmpty) {
|
||||
final firstArg = topLevelResults.arguments.first;
|
||||
@@ -195,6 +197,37 @@ class DartdevRunner<int> extends CommandRunner {
|
||||
final Ansi ansi = Ansi(Ansi.terminalSupportsAnsi);
|
||||
log = isVerbose ? Logger.verbose(ansi: ansi) : Logger.standard(ansi: ansi);
|
||||
|
||||
if (wereExperimentsSpecified(topLevelResults)) {
|
||||
List<String> experimentIds = specifiedExperiments(topLevelResults);
|
||||
for (ExperimentalFeature feature in experimentalFeatures) {
|
||||
// We allow default true flags, but complain when they are passed in.
|
||||
if (feature.isEnabledByDefault &&
|
||||
experimentIds.contains(feature.enableString)) {
|
||||
print("'${feature.enableString}' is now enabled by default; this "
|
||||
'flag is no longer required.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await super.runCommand(topLevelResults);
|
||||
}
|
||||
|
||||
void addExperimentalFlags(ArgParser argParser, bool verbose) {
|
||||
List<ExperimentalFeature> features = experimentalFeatures;
|
||||
|
||||
Map<String, String> allowedHelp = {};
|
||||
for (ExperimentalFeature feature in features) {
|
||||
String suffix =
|
||||
feature.isEnabledByDefault ? ' (no-op - enabled by default)' : '';
|
||||
allowedHelp[feature.enableString] = '${feature.documentation}$suffix';
|
||||
}
|
||||
|
||||
argParser.addMultiOption(
|
||||
experimentFlagName,
|
||||
valueHelp: 'experiment',
|
||||
allowed: features.map((feature) => feature.enableString),
|
||||
allowedHelp: verbose ? allowedHelp : null,
|
||||
help: 'Enable one or more experimental features.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import '../sdk.dart';
|
||||
import '../utils.dart';
|
||||
import 'analyze_impl.dart';
|
||||
|
||||
// TODO: Support enable-experiment for 'dart analyze'.
|
||||
|
||||
class AnalyzeCommand extends DartdevCommand<int> {
|
||||
AnalyzeCommand({bool verbose = false})
|
||||
: super('analyze', "Analyze the project's Dart code.") {
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'dart:io';
|
||||
import 'package:args/args.dart';
|
||||
|
||||
import '../core.dart';
|
||||
import '../experiments.dart';
|
||||
import '../sdk.dart';
|
||||
|
||||
class PubCommand extends DartdevCommand<int> {
|
||||
@@ -42,7 +43,26 @@ class PubCommand extends DartdevCommand<int> {
|
||||
@override
|
||||
FutureOr<int> run() async {
|
||||
final command = sdk.pub;
|
||||
final args = argResults.arguments;
|
||||
var args = argResults.arguments;
|
||||
|
||||
// Pass any --enable-experiment options along.
|
||||
if (args.isNotEmpty && wereExperimentsSpecified(globalResults)) {
|
||||
List<String> experimentIds = specifiedExperiments(globalResults);
|
||||
|
||||
if (args.first == 'run') {
|
||||
args = [
|
||||
...args.sublist(0, 1),
|
||||
'--$experimentFlagName=${experimentIds.join(',')}',
|
||||
...args.sublist(1),
|
||||
];
|
||||
} else if (args.length > 1 && args[0] == 'global' && args[0] == 'run') {
|
||||
args = [
|
||||
...args.sublist(0, 2),
|
||||
'--$experimentFlagName=${experimentIds.join(',')}',
|
||||
...args.sublist(2),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
log.trace('$command ${args.join(' ')}');
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:dds/dds.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
import '../core.dart';
|
||||
import '../experiments.dart';
|
||||
import '../sdk.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
@@ -55,7 +56,7 @@ Run a Dart file.''');
|
||||
@override
|
||||
FutureOr<int> run() async {
|
||||
// The command line arguments after 'run'
|
||||
final args = argResults.arguments.toList();
|
||||
var args = argResults.arguments.toList();
|
||||
|
||||
var argsContainFileOrHelp = false;
|
||||
for (var arg in args) {
|
||||
@@ -70,6 +71,7 @@ Run a Dart file.''');
|
||||
}
|
||||
|
||||
final cwd = Directory.current;
|
||||
|
||||
if (!argsContainFileOrHelp && cwd.existsSync()) {
|
||||
var foundImplicitFileToRun = false;
|
||||
var cwdName = cwd.name;
|
||||
@@ -90,6 +92,7 @@ Run a Dart file.''');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundImplicitFileToRun) {
|
||||
log.stderr(
|
||||
'Could not find the implicit file to run: '
|
||||
@@ -98,6 +101,16 @@ Run a Dart file.''');
|
||||
}
|
||||
}
|
||||
|
||||
// Pass any --enable-experiment options along.
|
||||
// todo: test
|
||||
if (args.isNotEmpty && wereExperimentsSpecified(globalResults)) {
|
||||
List<String> experimentIds = specifiedExperiments(globalResults);
|
||||
args = [
|
||||
'--$experimentFlagName=${experimentIds.join(',')}',
|
||||
...args,
|
||||
];
|
||||
}
|
||||
|
||||
// If the user wants to start a debugging session we need to do some extra
|
||||
// work and spawn a Dart Development Service (DDS) instance. DDS is a VM
|
||||
// service intermediary which implements the VM service protocol and
|
||||
@@ -107,14 +120,14 @@ Run a Dart file.''');
|
||||
element.startsWith('--observe') ||
|
||||
element.startsWith('--enable-vm-service'))) {
|
||||
return await _DebuggingSession(this, args).start();
|
||||
} else {
|
||||
// Starting in ProcessStartMode.inheritStdio mode means the child process
|
||||
// can detect support for ansi chars.
|
||||
final process = await Process.start(
|
||||
sdk.dart, ['--disable-dart-dev', ...args],
|
||||
mode: ProcessStartMode.inheritStdio);
|
||||
return process.exitCode;
|
||||
}
|
||||
|
||||
// Starting in ProcessStartMode.inheritStdio mode means the child process
|
||||
// can detect support for ansi chars.
|
||||
final process = await Process.start(
|
||||
sdk.dart, ['--disable-dart-dev', ...args],
|
||||
mode: ProcessStartMode.inheritStdio);
|
||||
return process.exitCode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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 'package:analyzer/src/dart/analysis/experiments.dart';
|
||||
import 'package:args/args.dart';
|
||||
|
||||
const experimentFlagName = 'enable-experiment';
|
||||
|
||||
/// Return a list of all the non-expired Dart experiments.
|
||||
List<ExperimentalFeature> get experimentalFeatures {
|
||||
List<ExperimentalFeature> features = ExperimentStatus.knownFeatures.values
|
||||
.where((feature) => !feature.isExpired)
|
||||
.toList();
|
||||
features.sort((a, b) => a.enableString.compareTo(b.enableString));
|
||||
return features;
|
||||
}
|
||||
|
||||
/// Return whether any Dart experiments were specified by the user.
|
||||
bool wereExperimentsSpecified(ArgResults argResults) =>
|
||||
argResults.wasParsed(experimentFlagName);
|
||||
|
||||
/// Return the list of Dart experiment flags specified by the user.
|
||||
List<String> specifiedExperiments(ArgResults argResults) =>
|
||||
argResults[experimentFlagName];
|
||||
@@ -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:args/args.dart';
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:dartdev/dartdev.dart';
|
||||
import 'package:test/test.dart';
|
||||
@@ -37,6 +38,17 @@ void command() {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('enable experiments flag is supported', () {
|
||||
final args = [
|
||||
'--disable-dartdev-analytics',
|
||||
'--enable-experiment=non-nullable'
|
||||
];
|
||||
final runner = DartdevRunner(args);
|
||||
ArgResults results = runner.parse(args);
|
||||
expect(results['enable-experiment'], isNotEmpty);
|
||||
expect(results['enable-experiment'].first, 'non-nullable');
|
||||
});
|
||||
}
|
||||
|
||||
void help() {
|
||||
|
||||
@@ -34,6 +34,44 @@ void pub() {
|
||||
expect(result.stderr, isEmpty);
|
||||
});
|
||||
|
||||
test('--enable-experiment pub run', () {
|
||||
p = project();
|
||||
p.file('bin/main.dart',
|
||||
"void main() { int a; a = null; print('a is \$a.'); }");
|
||||
|
||||
// run 'pub get'
|
||||
p.runSync('pub', ['get']);
|
||||
|
||||
var result = p.runSync(
|
||||
'--enable-experiment=non-nullable', ['pub', 'run', 'main.dart']);
|
||||
|
||||
expect(result.exitCode, 254);
|
||||
expect(result.stdout, isEmpty);
|
||||
expect(
|
||||
result.stderr,
|
||||
contains("A value of type 'Null' can't be assigned to a variable of "
|
||||
"type 'int'"));
|
||||
});
|
||||
|
||||
test('pub run --enable-experiment', () {
|
||||
p = project();
|
||||
p.file('bin/main.dart',
|
||||
"void main() { int a; a = null; print('a is \$a.'); }");
|
||||
|
||||
// run 'pub get'
|
||||
p.runSync('pub', ['get']);
|
||||
|
||||
var result = p.runSync(
|
||||
'pub', ['run', '--enable-experiment=non-nullable', 'main.dart']);
|
||||
|
||||
expect(result.exitCode, 254);
|
||||
expect(result.stdout, isEmpty);
|
||||
expect(
|
||||
result.stderr,
|
||||
contains("A value of type 'Null' can't be assigned to a variable of "
|
||||
"type 'int'"));
|
||||
});
|
||||
|
||||
test('failure', () {
|
||||
p = project(mainSrc: 'int get foo => 1;\n');
|
||||
var result = p.runSync('pub', ['deps']);
|
||||
|
||||
@@ -36,6 +36,20 @@ void run() {
|
||||
expect(result.exitCode, 0);
|
||||
});
|
||||
|
||||
test('--enable-experiment', () {
|
||||
p = project();
|
||||
p.file('main.dart', "void main() { int a; a = null; print('a is \$a.'); }");
|
||||
var result =
|
||||
p.runSync('--enable-experiment=non-nullable', ['run', 'main.dart']);
|
||||
|
||||
expect(result.exitCode, 254);
|
||||
expect(result.stdout, isEmpty);
|
||||
expect(
|
||||
result.stderr,
|
||||
contains("A value of type 'Null' can't be assigned to a variable of "
|
||||
"type 'int'"));
|
||||
});
|
||||
|
||||
test('no such file', () {
|
||||
p = project(mainSrc: "void main() { print('Hello World'); }");
|
||||
ProcessResult result =
|
||||
@@ -47,7 +61,7 @@ void run() {
|
||||
|
||||
test('implicit packageName.dart', () {
|
||||
// TODO(jwren) circle back to reimplement this test if possible, the file
|
||||
// name (package name) will be the name of the temporary directory on disk
|
||||
// name (package name) will be the name of the temporary directory on disk
|
||||
p = project(mainSrc: "void main() { print('Hello World'); }");
|
||||
p.file('bin/main.dart', "void main() { print('Hello main.dart'); }");
|
||||
ProcessResult result = p.runSync('run', []);
|
||||
@@ -57,7 +71,7 @@ void run() {
|
||||
expect(result.exitCode, 0);
|
||||
}, skip: true);
|
||||
|
||||
//Could not find the implicit file to run: bin
|
||||
// Could not find the implicit file to run: bin
|
||||
test('missing implicit packageName.dart', () {
|
||||
p = project(mainSrc: "void main() { print('Hello World'); }");
|
||||
p.file('bin/foo.dart', "void main() { print('Hello main.dart'); }");
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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 'package:dartdev/src/experiments.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('experiments', () {
|
||||
test('experimentalFeatures', () {
|
||||
expect(experimentalFeatures, isNotEmpty);
|
||||
expect(
|
||||
experimentalFeatures.map((experiment) => experiment.enableString),
|
||||
contains('non-nullable'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -95,13 +95,13 @@ features:
|
||||
help: "Triple-shift operator"
|
||||
|
||||
variance:
|
||||
help: "Sound variance."
|
||||
help: "Sound variance"
|
||||
|
||||
nonfunction-type-aliases:
|
||||
help: "Type aliases define a <type>, not just a <functionType>."
|
||||
help: "Type aliases define a <type>, not just a <functionType>"
|
||||
|
||||
alternative-invalidation-strategy:
|
||||
help: "Alternative invalidation strategy for incremental compilation."
|
||||
help: "Alternative invalidation strategy for incremental compilation"
|
||||
category: "CFE"
|
||||
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user