| // Copyright 2014 The Flutter Authors. 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:meta/meta.dart'; |
| 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.dart'; |
| import '../project_validator.dart'; |
| import '../runner/flutter_command.dart'; |
| import 'analyze_base.dart'; |
| import 'analyze_continuously.dart'; |
| import 'analyze_once.dart'; |
| import 'android_analyze.dart'; |
| import 'ios_analyze.dart'; |
| import 'validate_project.dart'; |
| |
| class AnalyzeCommand extends FlutterCommand { |
| AnalyzeCommand({ |
| bool verboseHelp = false, |
| this.workingDirectory, |
| required FileSystem fileSystem, |
| required Platform platform, |
| required Terminal terminal, |
| required Logger logger, |
| required ProcessManager processManager, |
| required Artifacts artifacts, |
| required List<ProjectValidator> allProjectValidators, |
| required bool suppressAnalytics, |
| }) : _artifacts = artifacts, |
| _fileSystem = fileSystem, |
| _processManager = processManager, |
| _logger = logger, |
| _terminal = terminal, |
| _allProjectValidators = allProjectValidators, |
| _platform = platform, |
| _suppressAnalytics = suppressAnalytics { |
| argParser.addFlag('flutter-repo', |
| negatable: false, |
| help: 'Include all the examples and tests from the Flutter repository.', |
| hide: !verboseHelp); |
| argParser.addFlag('current-package', |
| help: 'Analyze the current project, if applicable.', defaultsTo: true); |
| argParser.addFlag('dartdocs', |
| negatable: false, |
| help: '(deprecated) List every public member that is lacking documentation. ' |
| 'This command will be removed in a future version of Flutter.', |
| hide: !verboseHelp); |
| argParser.addFlag('watch', |
| help: 'Run analysis continuously, watching the filesystem for changes.', |
| negatable: false); |
| argParser.addOption('write', |
| valueHelp: 'file', |
| help: 'Also output the results to a file. This is useful with "--watch" ' |
| 'if you want a file to always contain the latest results.'); |
| argParser.addOption('dart-sdk', |
| valueHelp: 'path-to-sdk', |
| help: 'The path to the Dart SDK.', |
| hide: !verboseHelp); |
| argParser.addOption('protocol-traffic-log', |
| valueHelp: 'path-to-protocol-traffic-log', |
| 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.' |
| ); |
| argParser.addFlag('machine', |
| negatable: false, |
| help: 'Dumps a JSON with a subset of relevant data about the tool, project, ' |
| 'and environment.', |
| hide: !verboseHelp, |
| ); |
| |
| // Hidden option to enable a benchmarking mode. |
| argParser.addFlag('benchmark', |
| negatable: false, |
| hide: !verboseHelp, |
| help: 'Also output the analysis time.'); |
| |
| usesPubOption(); |
| |
| // Not used by analyze --watch |
| argParser.addFlag('congratulate', |
| help: 'Show output even when there are no errors, warnings, hints, or lints. ' |
| 'Ignored if "--watch" is specified.', |
| defaultsTo: true); |
| argParser.addFlag('preamble', |
| defaultsTo: true, |
| help: 'When analyzing the flutter repository, display the number of ' |
| 'files that will be analyzed.\n' |
| 'Ignored if "--watch" is specified.'); |
| argParser.addFlag('fatal-infos', |
| help: 'Treat info level issues as fatal.', |
| defaultsTo: true); |
| argParser.addFlag('fatal-warnings', |
| help: 'Treat warning level issues as fatal.', |
| defaultsTo: true); |
| |
| argParser.addFlag('android', |
| negatable: false, |
| help: 'Analyze Android sub-project. Used by internal tools only.', |
| hide: !verboseHelp, |
| ); |
| |
| argParser.addFlag('ios', |
| negatable: false, |
| help: 'Analyze iOS Xcode sub-project. Used by internal tools only.', |
| hide: !verboseHelp, |
| ); |
| |
| if (verboseHelp) { |
| argParser.addSeparator('Usage: flutter analyze --android [arguments]'); |
| } |
| |
| argParser.addFlag('list-build-variants', |
| negatable: false, |
| help: 'Print out a list of available build variants for the ' |
| 'Android sub-project.', |
| hide: !verboseHelp, |
| ); |
| |
| argParser.addFlag('output-app-link-settings', |
| negatable: false, |
| help: 'Output a JSON with Android app link settings into a file. ' |
| 'The "--build-variant" must also be set.', |
| hide: !verboseHelp, |
| ); |
| |
| argParser.addOption('build-variant', |
| help: 'Sets the Android build variant to be analyzed.', |
| valueHelp: 'build variant', |
| hide: !verboseHelp, |
| ); |
| |
| if (verboseHelp) { |
| argParser.addSeparator('Usage: flutter analyze --ios [arguments]'); |
| } |
| |
| argParser.addFlag('list-build-options', |
| help: 'Print out a list of available build options for the ' |
| 'iOS Xcode sub-project.', |
| hide: !verboseHelp, |
| ); |
| |
| argParser.addFlag('output-universal-link-settings', |
| negatable: false, |
| help: 'Output a JSON with iOS Xcode universal link settings into a file. ' |
| 'The "--configuration" and "--target" must be set.', |
| hide: !verboseHelp, |
| ); |
| |
| argParser.addOption('configuration', |
| help: 'Sets the iOS build configuration to be analyzed.', |
| valueHelp: 'configuration', |
| hide: !verboseHelp, |
| ); |
| |
| argParser.addOption('target', |
| help: 'Sets the iOS build target to be analyzed.', |
| valueHelp: 'target', |
| hide: !verboseHelp, |
| ); |
| } |
| |
| /// The working directory for testing analysis using dartanalyzer. |
| final Directory? workingDirectory; |
| |
| final Artifacts _artifacts; |
| final FileSystem _fileSystem; |
| final Logger _logger; |
| final Terminal _terminal; |
| final ProcessManager _processManager; |
| final Platform _platform; |
| final List<ProjectValidator> _allProjectValidators; |
| final bool _suppressAnalytics; |
| |
| @override |
| String get name => 'analyze'; |
| |
| @override |
| String get description => "Analyze the project's Dart code."; |
| |
| @override |
| String get category => FlutterCommandCategory.project; |
| |
| @visibleForTesting |
| List<ProjectValidator> allProjectValidators() => _allProjectValidators; |
| |
| @override |
| bool get shouldRunPub { |
| // If they're not analyzing the current project. |
| if (!boolArg('current-package')) { |
| return false; |
| } |
| |
| // Or we're not in a project directory. |
| if (!_fileSystem.file('pubspec.yaml').existsSync()) { |
| return false; |
| } |
| |
| // Don't run pub if asking for machine output. |
| if (boolArg('machine')) { |
| return false; |
| } |
| |
| // Don't run pub if asking for android analysis. |
| if (boolArg('android')) { |
| return false; |
| } |
| |
| return super.shouldRunPub; |
| } |
| |
| @override |
| Future<FlutterCommandResult> runCommand() async { |
| if (boolArg('android')) { |
| final AndroidAnalyzeOption option; |
| final String? buildVariant; |
| if (argResults!['list-build-variants'] as bool && argResults!['output-app-link-settings'] as bool) { |
| throwToolExit('Only one of "--list-build-variants" or "--output-app-link-settings" can be provided'); |
| } |
| if (argResults!['list-build-variants'] as bool) { |
| option = AndroidAnalyzeOption.listBuildVariant; |
| buildVariant = null; |
| } else if (argResults!['output-app-link-settings'] as bool) { |
| option = AndroidAnalyzeOption.outputAppLinkSettings; |
| buildVariant = argResults!['build-variant'] as String?; |
| if (buildVariant == null) { |
| throwToolExit('"--build-variant" must be provided'); |
| } |
| } else { |
| throwToolExit('No argument is provided to analyze. Use -h to see available commands.'); |
| } |
| final Set<String> items = findDirectories(argResults!, _fileSystem); |
| final String directoryPath; |
| if (items.isEmpty) { // user did not specify any path |
| directoryPath = _fileSystem.currentDirectory.path; |
| } else if (items.length > 1) { // if the user sends more than one path |
| throwToolExit('The Android analyze can process only one directory path'); |
| } else { |
| directoryPath = items.first; |
| } |
| await AndroidAnalyze( |
| fileSystem: _fileSystem, |
| option: option, |
| userPath: directoryPath, |
| buildVariant: buildVariant, |
| logger: _logger, |
| ).analyze(); |
| } else if (boolArg('ios')) { |
| final IOSAnalyzeOption option; |
| final String? configuration; |
| final String? target; |
| if (argResults!['list-build-options'] as bool && argResults!['output-universal-link-settings'] as bool) { |
| throwToolExit('Only one of "--list-build-options" or "--output-universal-link-settings" can be provided'); |
| } |
| if (argResults!['list-build-options'] as bool) { |
| option = IOSAnalyzeOption.listBuildOptions; |
| configuration = null; |
| target = null; |
| } else if (argResults!['output-universal-link-settings'] as bool) { |
| option = IOSAnalyzeOption.outputUniversalLinkSettings; |
| configuration = argResults!['configuration'] as String?; |
| if (configuration == null) { |
| throwToolExit('"--configuration" must be provided'); |
| } |
| target = argResults!['target'] as String?; |
| if (target == null) { |
| throwToolExit('"--target" must be provided'); |
| } |
| } else { |
| throwToolExit('No argument is provided to analyze. Use -h to see available commands.'); |
| } |
| final Set<String> items = findDirectories(argResults!, _fileSystem); |
| final String directoryPath; |
| if (items.isEmpty) { // user did not specify any path |
| directoryPath = _fileSystem.currentDirectory.path; |
| } else if (items.length > 1) { // if the user sends more than one path |
| throwToolExit('The iOS analyze can process only one directory path'); |
| } else { |
| directoryPath = items.first; |
| } |
| await IOSAnalyze( |
| project: FlutterProject.fromDirectory(_fileSystem.directory(directoryPath)), |
| option: option, |
| configuration: configuration, |
| target: target, |
| logger: _logger, |
| ).analyze(); |
| } else if (boolArg('suggestions')) { |
| final String directoryPath; |
| if (boolArg('watch')) { |
| throwToolExit('flag --watch is not compatible with --suggestions'); |
| } |
| if (workingDirectory == null) { |
| final Set<String> items = findDirectories(argResults!, _fileSystem); |
| if (items.isEmpty) { // user did not specify any path |
| directoryPath = _fileSystem.currentDirectory.path; |
| _logger.printTrace('Showing suggestions for current directory: $directoryPath'); |
| } else if (items.length > 1) { // if the user sends more than one path |
| throwToolExit('The suggestions flag can process only one directory path'); |
| } else { |
| directoryPath = items.first; |
| } |
| } else { |
| directoryPath = workingDirectory!.path; |
| } |
| return ValidateProject( |
| fileSystem: _fileSystem, |
| logger: _logger, |
| allProjectValidators: _allProjectValidators, |
| userPath: directoryPath, |
| processManager: _processManager, |
| machine: boolArg('machine'), |
| ).run(); |
| } else if (boolArg('watch')) { |
| await AnalyzeContinuously( |
| argResults!, |
| runner!.getRepoPackages(), |
| fileSystem: _fileSystem, |
| logger: _logger, |
| platform: _platform, |
| processManager: _processManager, |
| terminal: _terminal, |
| artifacts: _artifacts, |
| suppressAnalytics: _suppressAnalytics, |
| ).analyze(); |
| } else { |
| await AnalyzeOnce( |
| argResults!, |
| runner!.getRepoPackages(), |
| workingDirectory: workingDirectory, |
| fileSystem: _fileSystem, |
| logger: _logger, |
| platform: _platform, |
| processManager: _processManager, |
| terminal: _terminal, |
| artifacts: _artifacts, |
| suppressAnalytics: _suppressAnalytics, |
| ).analyze(); |
| } |
| return FlutterCommandResult.success(); |
| } |
| } |