From 67dd01e59998f351b6bdf7d25d1046fe91e482e1 Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Fri, 24 Jun 2022 13:24:03 -0700 Subject: [PATCH] [flutter_tools] tool exit from flutter create when provided just a drive letter (#106451) --- .../lib/src/commands/create_base.dart | 18 +++++++++++ .../commands.shard/permeable/create_test.dart | 32 +++++++++++++++++++ .../general.shard/create_config_test.dart | 9 ++++++ 3 files changed, 59 insertions(+) diff --git a/packages/flutter_tools/lib/src/commands/create_base.dart b/packages/flutter_tools/lib/src/commands/create_base.dart index 9d272b786a..02e37fae64 100644 --- a/packages/flutter_tools/lib/src/commands/create_base.dart +++ b/packages/flutter_tools/lib/src/commands/create_base.dart @@ -132,9 +132,27 @@ abstract class CreateBase extends FlutterCommand { ); } + /// Pattern for a Windows file system drive (e.g. "D:"). + /// + /// `dart:io` does not recognize strings matching this pattern as absolute + /// paths, as they have no top level back-slash; however, users often specify + /// this + @visibleForTesting + static final RegExp kWindowsDrivePattern = RegExp(r'^[a-zA-Z]:$'); + /// The output directory of the command. @protected + @visibleForTesting Directory get projectDir { + final String argProjectDir = argResults!.rest.first; + if (globals.platform.isWindows && kWindowsDrivePattern.hasMatch(argProjectDir)) { + throwToolExit( + 'You attempted to create a flutter project at the path "$argProjectDir", which is the name of a drive. This ' + 'is usually a mistake--you probably want to specify a containing directory, like "$argProjectDir\\app_name". ' + 'If you really want it at the drive root, re-run the command with the root directory after the drive, like ' + '"$argProjectDir\\".', + ); + } return globals.fs.directory(argResults!.rest.first); } diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index 310dddb20d..a09b4efbdb 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io' as io; import 'package:args/command_runner.dart'; import 'package:file_testing/file_testing.dart'; @@ -113,6 +114,37 @@ void main() { expect(Uuid.isValidUUID(fromString: identifier), isTrue); }); + testUsingContext('tool exits on Windows if given a drive letter without a path', () async { + // Must use LocalFileSystem as it is dependent on dart:io handling of + // Windows paths, which the MemoryFileSystem does not implement + final Directory workingDir = globals.fs.directory(r'X:\path\to\working\dir'); + // Must use [io.IOOverrides] as directory.absolute depends on Directory.current + // from dart:io. + await io.IOOverrides.runZoned>( + () async { + // Verify IOOverrides is working + expect(io.Directory.current, workingDir); + final CreateCommand command = CreateCommand(); + final CommandRunner runner = createTestCommandRunner(command); + const String driveName = 'X:'; + await expectToolExitLater( + runner.run([ + 'create', + '--project-name', + 'test_app', + '--offline', + driveName, + ]), + contains('You attempted to create a flutter project at the path "$driveName"'), + ); + }, + getCurrentDirectory: () => workingDir, + ); + }, overrides: { + Logger: () => BufferLogger.test(), + }, skip: !io.Platform.isWindows // [intended] relies on Windows file system + ); + // Verify that we create a default project ('app') that is // well-formed. testUsingContext('can create a default project', () async { diff --git a/packages/flutter_tools/test/general.shard/create_config_test.dart b/packages/flutter_tools/test/general.shard/create_config_test.dart index 1699de1528..4d41ec9e96 100644 --- a/packages/flutter_tools/test/general.shard/create_config_test.dart +++ b/packages/flutter_tools/test/general.shard/create_config_test.dart @@ -19,4 +19,13 @@ void main() { expect(isValidPackageName('Foo_bar'), false); }); + + test('kWindowsDrivePattern', () { + expect(CreateBase.kWindowsDrivePattern.hasMatch(r'D:\'), isFalse); + expect(CreateBase.kWindowsDrivePattern.hasMatch(r'z:\'), isFalse); + expect(CreateBase.kWindowsDrivePattern.hasMatch(r'\d:'), isFalse); + expect(CreateBase.kWindowsDrivePattern.hasMatch(r'ef:'), isFalse); + expect(CreateBase.kWindowsDrivePattern.hasMatch(r'D:'), isTrue); + expect(CreateBase.kWindowsDrivePattern.hasMatch(r'c:'), isTrue); + }); }