Skip to content

Commit 76ae5ef

Browse files
authored
Allow passing a language version to the formatter CLI. (#1507)
This option is hidden for now because support for traversing the surrounding directories to look for a package config isn't implemented yet. Once that's working too, I'll make the option visible. This just adds support for explicitly specifying a version through the command line.
1 parent db1271e commit 76ae5ef

File tree

6 files changed

+96
-1
lines changed

6 files changed

+96
-1
lines changed

lib/src/cli/format_command.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import 'dart:io';
55

66
import 'package:args/command_runner.dart';
7+
import 'package:pub_semver/pub_semver.dart';
78

9+
import '../dart_formatter.dart';
810
import '../io.dart';
911
import '../short/style_fix.dart';
1012
import 'formatter_options.dart';
@@ -87,6 +89,20 @@ class FormatCommand extends Command<int> {
8789
usageException('Cannot print a summary with JSON output.');
8890
}
8991

92+
Version? languageVersion;
93+
if (argResults['language-version'] case String version) {
94+
var versionPattern = RegExp(r'^([0-9]+)\.([0-9]+)$');
95+
if (version == 'latest') {
96+
languageVersion = DartFormatter.latestLanguageVersion;
97+
} else if (versionPattern.firstMatch(version) case var match?) {
98+
languageVersion =
99+
Version(int.parse(match[1]!), int.parse(match[2]!), 0);
100+
} else {
101+
usageException('--language-version must be a version like "3.2" or '
102+
'"latest", was "$version".');
103+
}
104+
}
105+
90106
var pageWidth = int.tryParse(argResults['line-length'] as String) ??
91107
usageException('--line-length must be an integer, was '
92108
'"${argResults['line-length']}".');
@@ -140,6 +156,7 @@ class FormatCommand extends Command<int> {
140156
var stdinName = argResults['stdin-name'] as String;
141157

142158
var options = FormatterOptions(
159+
languageVersion: languageVersion,
143160
indent: indent,
144161
pageWidth: pageWidth,
145162
followLinks: followLinks,

lib/src/cli/formatter_options.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import 'dart:io';
66

7+
import 'package:pub_semver/pub_semver.dart';
8+
79
import '../short/style_fix.dart';
810
import '../source_code.dart';
911
import 'output.dart';
@@ -15,6 +17,10 @@ const dartStyleVersion = '2.3.6';
1517

1618
/// Global options that affect how the formatter produces and uses its outputs.
1719
class FormatterOptions {
20+
/// The language version formatted code should be parsed at or `null` if not
21+
/// specified.
22+
final Version? languageVersion;
23+
1824
/// The number of spaces of indentation to prefix the output with.
1925
final int indent;
2026

@@ -45,7 +51,8 @@ class FormatterOptions {
4551
final List<String> experimentFlags;
4652

4753
FormatterOptions(
48-
{this.indent = 0,
54+
{this.languageVersion,
55+
this.indent = 0,
4956
this.pageWidth = 80,
5057
this.followLinks = false,
5158
Iterable<StyleFix>? fixes,

lib/src/cli/options.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ void defineOptions(ArgParser parser,
6767
},
6868
defaultsTo: 'line',
6969
hide: !verbose);
70+
71+
parser.addOption('language-version',
72+
help: 'Language version of formatted code.\n'
73+
'Use "latest" to parse as the latest supported version.\n'
74+
'Omit to look for a surrounding package config.',
75+
// TODO(rnystrom): Show this when package config support is implemented.
76+
hide: true);
7077
}
7178

7279
parser.addFlag('set-exit-if-changed',

lib/src/io.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Future<void> formatStdin(
2727
var input = StringBuffer();
2828
stdin.transform(const Utf8Decoder()).listen(input.write, onDone: () {
2929
var formatter = DartFormatter(
30+
languageVersion: options.languageVersion,
3031
indent: options.indent,
3132
pageWidth: options.pageWidth,
3233
fixes: options.fixes,
@@ -137,6 +138,7 @@ bool processFile(FormatterOptions options, File file, {String? displayPath}) {
137138
displayPath ??= file.path;
138139

139140
var formatter = DartFormatter(
141+
languageVersion: options.languageVersion,
140142
indent: options.indent,
141143
pageWidth: options.pageWidth,
142144
fixes: options.fixes,

test/command_test.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,67 @@ void main() {
454454
});
455455
});
456456

457+
group('language version', () {
458+
// It's hard to validate that the formatter uses the *exact* latest
459+
// language version supported by the formatter, but at least test that a
460+
// new-ish language feature can be parsed.
461+
const extensionTypeBefore = '''
462+
extension type Meters(int value) {
463+
Meters operator+(Meters other) => Meters(value+other.value);
464+
}''';
465+
466+
const extensionTypeAfter = '''
467+
extension type Meters(int value) {
468+
Meters operator +(Meters other) => Meters(value + other.value);
469+
}
470+
''';
471+
472+
test('defaults to latest language version if omitted', () async {
473+
await d.dir('code', [d.file('a.dart', extensionTypeBefore)]).create();
474+
475+
var process = await runCommandOnDir();
476+
await process.shouldExit(0);
477+
478+
await d.dir('code', [d.file('a.dart', extensionTypeAfter)]).validate();
479+
});
480+
481+
test('uses the given language version', () async {
482+
const before = 'main() { switch (o) { case 1+2: break; } }';
483+
484+
const after = '''
485+
main() {
486+
switch (o) {
487+
case 1 + 2:
488+
break;
489+
}
490+
}
491+
''';
492+
493+
await d.dir('code', [d.file('a.dart', before)]).create();
494+
495+
// Use an older language version where `1 + 2` was still a valid switch
496+
// case.
497+
var process = await runCommandOnDir(['--language-version=2.19']);
498+
await process.shouldExit(0);
499+
500+
await d.dir('code', [d.file('a.dart', after)]).validate();
501+
});
502+
503+
test('uses the latest language version if "latest"', () async {
504+
await d.dir('code', [d.file('a.dart', extensionTypeBefore)]).create();
505+
506+
var process = await runCommandOnDir(['--language-version=latest']);
507+
await process.shouldExit(0);
508+
509+
await d.dir('code', [d.file('a.dart', extensionTypeAfter)]).validate();
510+
});
511+
512+
test("errors if the language version can't be parsed", () async {
513+
var process = await runCommand(['--language-version=123']);
514+
await process.shouldExit(64);
515+
});
516+
});
517+
457518
group('--enable-experiment', () {
458519
test('passes experiment flags to parser', () async {
459520
var process =

test/dart_formatter_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ void main() async {
3333
expect(() => formatter.format('main() {switch (o) {case 1+2: break;}}'),
3434
throwsA(isA<FormatterException>()));
3535
});
36+
3637
test('@dart comment overrides version', () {
3738
// Use a language version after patterns were supported and `1 + 2` is an
3839
// error.

0 commit comments

Comments
 (0)