diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index 205ca86f7e..9ea76318cf 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -51,6 +51,7 @@ import 'src/globals.dart' as globals; import 'src/isolated/mustache_template.dart'; import 'src/isolated/resident_web_runner.dart'; import 'src/pre_run_validator.dart'; +import 'src/project_validator.dart'; import 'src/resident_runner.dart'; import 'src/runner/flutter_command.dart'; import 'src/web/web_runner.dart'; @@ -141,6 +142,7 @@ List generateCommands({ logger: globals.logger, terminal: globals.terminal, artifacts: globals.artifacts!, + allProjectValidators: [], ), AssembleCommand(verboseHelp: verboseHelp, buildSystem: globals.buildSystem), AttachCommand(verboseHelp: verboseHelp), diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart index 928c5b2079..62c109d44d 100644 --- a/packages/flutter_tools/lib/src/commands/analyze.dart +++ b/packages/flutter_tools/lib/src/commands/analyze.dart @@ -5,13 +5,17 @@ import 'package:process/process.dart'; import '../artifacts.dart'; +import '../base/common.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; import '../base/platform.dart'; import '../base/terminal.dart'; +import '../project_validator.dart'; import '../runner/flutter_command.dart'; +import 'analyze_base.dart'; import 'analyze_continuously.dart'; import 'analyze_once.dart'; +import 'validate_project.dart'; class AnalyzeCommand extends FlutterCommand { AnalyzeCommand({ @@ -23,11 +27,13 @@ class AnalyzeCommand extends FlutterCommand { required Logger logger, required ProcessManager processManager, required Artifacts artifacts, + required List allProjectValidators, }) : _artifacts = artifacts, _fileSystem = fileSystem, _processManager = processManager, _logger = logger, _terminal = terminal, + _allProjectValidators = allProjectValidators, _platform = platform { argParser.addFlag('flutter-repo', negatable: false, @@ -56,6 +62,9 @@ class AnalyzeCommand extends FlutterCommand { help: 'The path to write the request and response protocol. This is ' 'only intended to be used for debugging the tooling.', hide: !verboseHelp); + argParser.addFlag('suggestions', + help: 'Show suggestions about the current flutter project.' + ); // Hidden option to enable a benchmarking mode. argParser.addFlag('benchmark', @@ -92,6 +101,7 @@ class AnalyzeCommand extends FlutterCommand { final Terminal _terminal; final ProcessManager _processManager; final Platform _platform; + final List _allProjectValidators; @override String get name => 'analyze'; @@ -119,7 +129,29 @@ class AnalyzeCommand extends FlutterCommand { @override Future runCommand() async { - if (boolArgDeprecated('watch')) { + final bool? suggestionFlag = boolArg('suggestions'); + if (suggestionFlag != null && suggestionFlag == true) { + final String directoryPath; + final bool? watchFlag = boolArg('watch'); + if (watchFlag != null && watchFlag) { + throwToolExit('flag --watch is not compatible with --suggestions'); + } + if (workingDirectory == null) { + final Set items = findDirectories(argResults!, _fileSystem); + if (items.isEmpty || items.length > 1) { + throwToolExit('The suggestions flags needs one directory path'); + } + directoryPath = items.first; + } else { + directoryPath = workingDirectory!.path; + } + return ValidateProject( + fileSystem: _fileSystem, + logger: _logger, + allProjectValidators: _allProjectValidators, + userPath: directoryPath, + ).run(); + } else if (boolArgDeprecated('watch')) { await AnalyzeContinuously( argResults!, runner!.getRepoRoots(), diff --git a/packages/flutter_tools/lib/src/commands/analyze_base.dart b/packages/flutter_tools/lib/src/commands/analyze_base.dart index dbb55c9d67..54a9ff6fe0 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_base.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_base.dart @@ -286,3 +286,20 @@ class PackageDependencyTracker { return result; } } + +/// Find directories or files from argResults.rest. +Set findDirectories(ArgResults argResults, FileSystem fileSystem) { + final Set items = Set.of(argResults.rest + .map((String path) => fileSystem.path.canonicalize(path))); + if (items.isNotEmpty) { + for (final String item in items) { + final FileSystemEntityType type = fileSystem.typeSync(item); + + if (type == FileSystemEntityType.notFound) { + throwToolExit("You provided the path '$item', however it does not exist on disk"); + } + } + } + + return items; +} diff --git a/packages/flutter_tools/lib/src/commands/analyze_once.dart b/packages/flutter_tools/lib/src/commands/analyze_once.dart index 5af200054c..35808c8fd9 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_once.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_once.dart @@ -35,19 +35,7 @@ class AnalyzeOnce extends AnalyzeBase { Future analyze() async { final String currentDirectory = (workingDirectory ?? fileSystem.currentDirectory).path; - - // find directories or files from argResults.rest - final Set items = Set.of(argResults.rest - .map((String path) => fileSystem.path.canonicalize(path))); - if (items.isNotEmpty) { - for (final String item in items) { - final FileSystemEntityType type = fileSystem.typeSync(item); - - if (type == FileSystemEntityType.notFound) { - throwToolExit("'$item' does not exist"); - } - } - } + final Set items = findDirectories(argResults, fileSystem); if (isFlutterRepo) { // check for conflicting dependencies diff --git a/packages/flutter_tools/lib/src/commands/validate_project.dart b/packages/flutter_tools/lib/src/commands/validate_project.dart index 0f7569ff63..9117f7cab4 100644 --- a/packages/flutter_tools/lib/src/commands/validate_project.dart +++ b/packages/flutter_tools/lib/src/commands/validate_project.dart @@ -9,31 +9,22 @@ import '../project_validator.dart'; import '../project_validator_result.dart'; import '../runner/flutter_command.dart'; -class ValidateProjectCommand extends FlutterCommand { - ValidateProjectCommand({ +class ValidateProject { + ValidateProject({ required this.fileSystem, required this.logger, required this.allProjectValidators, + required this.userPath, this.verbose = false }); final FileSystem fileSystem; final Logger logger; final bool verbose; + final String userPath; final List allProjectValidators; - @override - final String name = 'validate-project'; - - @override - final String description = 'Show information about the current project.'; - - @override - final String category = FlutterCommandCategory.project; - - @override - Future runCommand() async { - final String userPath = getUserPath(); + Future run() async { final Directory workingDirectory = userPath.isEmpty ? fileSystem.currentDirectory : fileSystem.directory(userPath); final FlutterProject project = FlutterProject.fromDirectory(workingDirectory); @@ -94,8 +85,4 @@ class ValidateProjectCommand extends FlutterCommand { return '$icon $result'; } - - String getUserPath(){ - return (argResults == null || argResults!.rest.isEmpty) ? '' : argResults!.rest[0]; - } } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/analyze_continuously_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/analyze_continuously_test.dart index 87cd6dc6cd..0205a9a723 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/analyze_continuously_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/analyze_continuously_test.dart @@ -19,6 +19,7 @@ import 'package:flutter_tools/src/commands/analyze.dart'; import 'package:flutter_tools/src/dart/analysis.dart'; import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/project_validator.dart'; import 'package:process/process.dart'; import '../../src/common.dart'; @@ -199,6 +200,7 @@ void main() { platform: FakePlatform(), fileSystem: MemoryFileSystem.test(), processManager: processManager, + allProjectValidators: [], ); final TestFlutterCommandRunner commandRunner = TestFlutterCommandRunner(); @@ -246,6 +248,7 @@ void main() { platform: FakePlatform(), fileSystem: fileSystem, processManager: processManager, + allProjectValidators: [], ); await FakeAsync().run((FakeAsync time) async { @@ -298,6 +301,7 @@ void main() { platform: FakePlatform(), fileSystem: MemoryFileSystem.test(), processManager: processManager, + allProjectValidators: [], ); await FakeAsync().run((FakeAsync time) async { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/project_validator_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/analyze_suggestion_test.dart similarity index 66% rename from packages/flutter_tools/test/commands.shard/hermetic/project_validator_test.dart rename to packages/flutter_tools/test/commands.shard/hermetic/analyze_suggestion_test.dart index bc387e5516..1411060073 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/project_validator_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/analyze_suggestion_test.dart @@ -6,13 +6,17 @@ import 'package:args/command_runner.dart'; import 'package:file/memory.dart'; +import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/commands/validate_project.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/base/terminal.dart'; +import 'package:flutter_tools/src/commands/analyze.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project_validator.dart'; import 'package:flutter_tools/src/project_validator_result.dart'; +import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/test_flutter_command_runner.dart'; @@ -70,26 +74,36 @@ class ProjectValidatorCrash extends ProjectValidator { void main() { FileSystem fileSystem; + Terminal terminal; + ProcessManager processManager; + Platform platform; - group('analyze project command', () { + group('analyze --suggestions command', () { setUp(() { fileSystem = MemoryFileSystem.test(); + terminal = Terminal.test(); + processManager = FakeProcessManager.empty(); + platform = FakePlatform(); }); testUsingContext('success, error and warning', () async { final BufferLogger loggerTest = BufferLogger.test(); - final ValidateProjectCommand command = ValidateProjectCommand( - fileSystem: fileSystem, - logger: loggerTest, - allProjectValidators: [ - ProjectValidatorDummy(), - ProjectValidatorSecondDummy() - ] + final AnalyzeCommand command = AnalyzeCommand( + artifacts: Artifacts.test(), + fileSystem: fileSystem, + logger: loggerTest, + platform: platform, + terminal: terminal, + processManager: processManager, + allProjectValidators: [ + ProjectValidatorDummy(), + ProjectValidatorSecondDummy() + ] ); final CommandRunner runner = createTestCommandRunner(command); - await runner.run(['validate-project']); + await runner.run(['analyze', '--suggestions', './']); const String expected = '\n' '┌──────────────────────────────────────────┐\n' @@ -107,18 +121,41 @@ void main() { testUsingContext('crash', () async { final BufferLogger loggerTest = BufferLogger.test(); - final ValidateProjectCommand command = ValidateProjectCommand( + final AnalyzeCommand command = AnalyzeCommand( + artifacts: Artifacts.test(), fileSystem: fileSystem, logger: loggerTest, - allProjectValidators: [ProjectValidatorCrash()] + platform: platform, + terminal: terminal, + processManager: processManager, + allProjectValidators: [ + ProjectValidatorCrash(), + ] ); final CommandRunner runner = createTestCommandRunner(command); - await runner.run(['validate-project']); + await runner.run(['analyze', '--suggestions', './']); const String expected = '[☠] Exception: my exception: #0 ProjectValidatorCrash.start'; expect(loggerTest.statusText, contains(expected)); }); + + testUsingContext('--watch and --suggestions not compatible together', () async { + final BufferLogger loggerTest = BufferLogger.test(); + final AnalyzeCommand command = AnalyzeCommand( + artifacts: Artifacts.test(), + fileSystem: fileSystem, + logger: loggerTest, + platform: platform, + terminal: terminal, + processManager: processManager, + allProjectValidators: [] + ); + final CommandRunner runner = createTestCommandRunner(command); + Future result () => runner.run(['analyze', '--suggestions', '--watch']); + + expect(result, throwsToolExit(message: 'flag --watch is not compatible with --suggestions')); + }); }); } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/analyze_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/analyze_test.dart index 88a9aa33f3..b8424914fe 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/analyze_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/analyze_test.dart @@ -15,6 +15,7 @@ import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/analyze.dart'; import 'package:flutter_tools/src/commands/analyze_base.dart'; import 'package:flutter_tools/src/dart/analysis.dart'; +import 'package:flutter_tools/src/project_validator.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -70,6 +71,7 @@ void main() { platform: platform, processManager: processManager, terminal: terminal, + allProjectValidators: [], ); runner = createTestCommandRunner(command); diff --git a/packages/flutter_tools/test/integration.shard/analyze_once_test.dart b/packages/flutter_tools/test/integration.shard/analyze_once_test.dart index 7fa3a6638b..3691058160 100644 --- a/packages/flutter_tools/test/integration.shard/analyze_once_test.dart +++ b/packages/flutter_tools/test/integration.shard/analyze_once_test.dart @@ -160,7 +160,7 @@ void main() { testWithoutContext('file not found', () async { await runCommand( arguments: ['analyze', '--no-pub', 'not_found.abc'], - exitMessageContains: "not_found.abc' does not exist", + exitMessageContains: "not_found.abc', however it does not exist on disk", exitCode: 1 ); }); diff --git a/packages/flutter_tools/test/integration.shard/project_validator_integration_test.dart b/packages/flutter_tools/test/integration.shard/analyze_suggestions_integration_test.dart similarity index 79% rename from packages/flutter_tools/test/integration.shard/project_validator_integration_test.dart rename to packages/flutter_tools/test/integration.shard/analyze_suggestions_integration_test.dart index 33a372724c..52cbb17691 100644 --- a/packages/flutter_tools/test/integration.shard/project_validator_integration_test.dart +++ b/packages/flutter_tools/test/integration.shard/analyze_suggestions_integration_test.dart @@ -5,7 +5,7 @@ import 'package:args/command_runner.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/commands/validate_project.dart'; +import 'package:flutter_tools/src/commands/analyze.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project_validator.dart'; @@ -15,7 +15,7 @@ import '../src/test_flutter_command_runner.dart'; void main() { late FileSystem fileSystem; - group('analyze project command', () { + group('analyze --suggestions command integration', () { setUp(() { fileSystem = globals.localFileSystem; @@ -23,14 +23,24 @@ void main() { testUsingContext('General Info Project Validator', () async { final BufferLogger loggerTest = BufferLogger.test(); - final ValidateProjectCommand command = ValidateProjectCommand( + final AnalyzeCommand command = AnalyzeCommand( + artifacts: globals.artifacts!, fileSystem: fileSystem, logger: loggerTest, - allProjectValidators: [GeneralInfoProjectValidator()] + platform: globals.platform, + terminal: globals.terminal, + processManager: globals.processManager, + allProjectValidators: [GeneralInfoProjectValidator()], ); final CommandRunner runner = createTestCommandRunner(command); - await runner.run(['validate-project', '../../dev/integration_tests/flutter_gallery']); + await runner.run([ + 'analyze', + '--no-pub', + '--no-current-package', + '--suggestions', + '../../dev/integration_tests/flutter_gallery', + ]); const String expected = '\n' '┌────────────────────────────────────────────────────────────────────────────┐\n'