Skip to content

Commit fec2d76

Browse files
authored
Throw ToolExit when flutter downgrade receives positional arguments (#183375)
## Description The `flutter downgrade` command currently ignores positional arguments (such as a specific version number) and silently falls back to its default behavior. This leads to a confusing developer experience when a user assumes they can type `flutter downgrade 3.19.0`. This PR intercepts unexpected positional arguments in `DowngradeCommand` and throws a clear `ToolExit`. It instructs the user that the command does not support specifying a version and provides the link to the official documentation for switching Flutter versions using `git checkout`. ## Issues Fixes #182820 ## Tests * Added a hermetic test in `downgrade_test.dart` to verify that a `ToolExit` is thrown with the correct exit code and error message when a positional argument is passed. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent aeb9dd6 commit fec2d76

2 files changed

Lines changed: 49 additions & 0 deletions

File tree

‎packages/flutter_tools/lib/src/commands/downgrade.dart‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:meta/meta.dart';
56
import 'package:process/process.dart';
67

78
import '../base/common.dart';
@@ -17,6 +18,18 @@ import '../persistent_tool_state.dart';
1718
import '../runner/flutter_command.dart';
1819
import '../version.dart';
1920

21+
@visibleForTesting
22+
String downgradePositionalArgumentErrorMessage(List<String> args) {
23+
final String argString = args.join(' ');
24+
final pluralized = args.length > 1 ? 'arguments' : 'argument';
25+
26+
return 'Unexpected positional $pluralized "$argString".\n\n'
27+
'"flutter downgrade" does not support specifying a version.\n'
28+
'It only undoes the last "flutter upgrade" on the current channel.\n\n'
29+
'To switch to a specific Flutter version, see: '
30+
'https://flutter.dev/to/switch-flutter-version';
31+
}
32+
2033
/// The flutter downgrade command returns the SDK to the last recorded version
2134
/// for a particular branch.
2235
///
@@ -80,6 +93,10 @@ class DowngradeCommand extends FlutterCommand {
8093

8194
@override
8295
Future<FlutterCommandResult> runCommand() async {
96+
if (argResults!.rest.isNotEmpty) {
97+
throwToolExit(downgradePositionalArgumentErrorMessage(argResults!.rest), exitCode: 2);
98+
}
99+
83100
// Commands do not necessarily have access to the correct zone injected
84101
// values when being created. Fields must be lazily instantiated in runCommand,
85102
// at least until the zone injection is refactored.

‎packages/flutter_tools/test/commands.shard/hermetic/downgrade_test.dart‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,38 @@ void main() {
6262
);
6363
}, overrides: {ProcessManager: () => processManager});
6464

65+
for (final positionalArguments in <List<String>>[
66+
<String>['3.19.0'],
67+
<String>['3.19.0', 'extra'],
68+
<String>['more', 'additional', 'arguments'],
69+
]) {
70+
testUsingContext(
71+
'Downgrade exits on unexpected positional arguments: ${positionalArguments.join(' ')}',
72+
() async {
73+
final fakeFlutterVersion = FakeFlutterVersion();
74+
final command = DowngradeCommand(
75+
persistentToolState: PersistentToolState.test(
76+
directory: fileSystem.currentDirectory,
77+
logger: bufferLogger,
78+
),
79+
terminal: terminal,
80+
stdio: stdio,
81+
flutterVersion: fakeFlutterVersion,
82+
logger: bufferLogger,
83+
);
84+
85+
expect(
86+
createTestCommandRunner(command).run(<String>['downgrade', ...positionalArguments]),
87+
throwsToolExit(
88+
message: downgradePositionalArgumentErrorMessage(positionalArguments),
89+
exitCode: 2,
90+
),
91+
);
92+
},
93+
overrides: {ProcessManager: () => processManager},
94+
);
95+
}
96+
6597
testUsingContext('Downgrade exits on no recorded version', () async {
6698
final fakeFlutterVersion = FakeFlutterVersion(branch: 'beta');
6799
fileSystem.currentDirectory

0 commit comments

Comments
 (0)