|  | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import 'dart:io' as io; | 
|  |  | 
|  | import 'package:analyzer/dart/analysis/features.dart'; | 
|  | import 'package:analyzer/file_system/file_system.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/experiments.dart'; | 
|  | import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; | 
|  | import 'package:analyzer/src/util/file_paths.dart' as file_paths; | 
|  | import 'package:analyzer/src/util/sdk.dart'; | 
|  | import 'package:analyzer_cli/src/ansi.dart' as ansi; | 
|  | import 'package:analyzer_cli/src/driver.dart'; | 
|  | import 'package:args/args.dart'; | 
|  | import 'package:pub_semver/pub_semver.dart'; | 
|  |  | 
|  | const _analysisOptionsFileOption = 'options'; | 
|  | const _binaryName = 'dartanalyzer'; | 
|  | const _defaultLanguageVersionOption = 'default-language-version'; | 
|  | const _defineVariableOption = 'D'; | 
|  | const _enableExperimentOption = 'enable-experiment'; | 
|  | const _enableInitializingFormalAccessFlag = 'initializing-formal-access'; | 
|  | const _ignoreUnrecognizedFlagsFlag = 'ignore-unrecognized-flags'; | 
|  | const _packagesOption = 'packages'; | 
|  | const _sdkPathOption = 'dart-sdk'; | 
|  |  | 
|  | /// Shared exit handler. | 
|  | /// | 
|  | /// *Visible for testing.* | 
|  | ExitHandler exitHandler = io.exit; | 
|  |  | 
|  | T cast<T>(dynamic value) => value as T; | 
|  |  | 
|  | T? castNullable<T>(dynamic value) => value as T?; | 
|  |  | 
|  | /// Print the given [message] to stderr and exit with the given [exitCode]. | 
|  | void printAndFail(String message, {int exitCode = 15}) { | 
|  | errorSink.writeln(message); | 
|  | exitHandler(exitCode); | 
|  | } | 
|  |  | 
|  | /// Exit handler. | 
|  | /// | 
|  | /// *Visible for testing.* | 
|  | typedef ExitHandler = void Function(int code); | 
|  |  | 
|  | /// Analyzer commandline configuration options. | 
|  | class CommandLineOptions { | 
|  | final ArgResults _argResults; | 
|  |  | 
|  | /// The file path of the analysis options file that should be used in place of | 
|  | /// any file in the root directory or a parent of the root directory, | 
|  | /// or `null` if the normal lookup mechanism should be used. | 
|  | String? defaultAnalysisOptionsPath; | 
|  |  | 
|  | /// The file path of the .packages file that should be used in place of any | 
|  | /// file found using the normal (Package Specification DEP) lookup mechanism, | 
|  | /// or `null` if the normal lookup mechanism should be used. | 
|  | String? defaultPackagesPath; | 
|  |  | 
|  | /// A table mapping variable names to values for the declared variables. | 
|  | final Map<String, String> declaredVariables = {}; | 
|  |  | 
|  | /// The path to the dart SDK. | 
|  | String? dartSdkPath; | 
|  |  | 
|  | /// Whether to disable cache flushing. This option can improve analysis | 
|  | /// speed at the expense of memory usage. It may also be useful for working | 
|  | /// around bugs. | 
|  | final bool disableCacheFlushing; | 
|  |  | 
|  | /// Whether to display version information | 
|  | final bool displayVersion; | 
|  |  | 
|  | /// Whether to ignore unrecognized flags | 
|  | final bool ignoreUnrecognizedFlags; | 
|  |  | 
|  | /// Whether to log additional analysis messages and exceptions | 
|  | final bool log; | 
|  |  | 
|  | /// Whether to use 'json' format for error display | 
|  | final bool jsonFormat; | 
|  |  | 
|  | /// Whether to use 'machine' format for error display | 
|  | final bool machineFormat; | 
|  |  | 
|  | /// The path to a file to write a performance log. | 
|  | /// (Or null if not enabled.) | 
|  | final String? perfReport; | 
|  |  | 
|  | /// Batch mode (for unit testing) | 
|  | final bool batchMode; | 
|  |  | 
|  | /// The source files to analyze | 
|  | final List<String> sourceFiles; | 
|  |  | 
|  | /// Emit output in a verbose mode. | 
|  | final bool verbose; | 
|  |  | 
|  | /// Use ANSI color codes for output. | 
|  | final bool color; | 
|  |  | 
|  | /// Whether we should analyze the given source for the purposes of training a | 
|  | /// Dart analyzer snapshot. | 
|  | final bool trainSnapshot; | 
|  |  | 
|  | /// Initialize options from the given parsed [args]. | 
|  | CommandLineOptions._fromArgs( | 
|  | ResourceProvider resourceProvider, | 
|  | ArgResults args, | 
|  | )   : _argResults = args, | 
|  | dartSdkPath = castNullable(args[_sdkPathOption]), | 
|  | disableCacheFlushing = cast(args['disable-cache-flushing']), | 
|  | displayVersion = cast(args['version']), | 
|  | ignoreUnrecognizedFlags = cast(args[_ignoreUnrecognizedFlagsFlag]), | 
|  | log = cast(args['log']), | 
|  | jsonFormat = args['format'] == 'json', | 
|  | machineFormat = args['format'] == 'machine', | 
|  | perfReport = castNullable(args['x-perf-report']), | 
|  | batchMode = cast(args['batch']), | 
|  | sourceFiles = args.rest, | 
|  | trainSnapshot = cast(args['train-snapshot']), | 
|  | verbose = cast(args['verbose']), | 
|  | color = cast(args['color']) { | 
|  | // | 
|  | // File locations. | 
|  | // | 
|  | defaultAnalysisOptionsPath = _absoluteNormalizedPath( | 
|  | resourceProvider, | 
|  | castNullable(args[_analysisOptionsFileOption]), | 
|  | ); | 
|  | defaultPackagesPath = _absoluteNormalizedPath( | 
|  | resourceProvider, | 
|  | castNullable(args[_packagesOption]), | 
|  | ); | 
|  |  | 
|  | // | 
|  | // Declared variables. | 
|  | // | 
|  | var variables = (args[_defineVariableOption] as List).cast<String>(); | 
|  | for (var variable in variables) { | 
|  | var index = variable.indexOf('='); | 
|  | if (index < 0) { | 
|  | // TODO(brianwilkerson): Decide the semantics we want in this case. | 
|  | // The VM prints "No value given to -D option", then tries to load '-Dfoo' | 
|  | // as a file and dies. Unless there was nothing after the '-D', in which | 
|  | // case it prints the warning and ignores the option. | 
|  | } else { | 
|  | var name = variable.substring(0, index); | 
|  | if (name.isNotEmpty) { | 
|  | // TODO(brianwilkerson): Decide the semantics we want in the case where | 
|  | // there is no name. If there is no name, the VM tries to load a file | 
|  | // named '-D' and dies. | 
|  | declaredVariables[name] = variable.substring(index + 1); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// The default language version for files that are not in a package. | 
|  | /// (Or null if no default language version to force.) | 
|  | String? get defaultLanguageVersion { | 
|  | return castNullable(_argResults[_defaultLanguageVersionOption]); | 
|  | } | 
|  |  | 
|  | /// A list of the names of the experiments that are to be enabled. | 
|  | List<String>? get enabledExperiments { | 
|  | return castNullable(_argResults[_enableExperimentOption]); | 
|  | } | 
|  |  | 
|  | /// Update the [analysisOptions] with flags that the user specified | 
|  | /// explicitly. The [analysisOptions] are usually loaded from one of | 
|  | /// `analysis_options.yaml` files, possibly with includes. We consider | 
|  | /// flags that the user specified as command line options more important, | 
|  | /// so override the corresponding options. | 
|  | void updateAnalysisOptions(AnalysisOptionsImpl analysisOptions) { | 
|  | var defaultLanguageVersion = this.defaultLanguageVersion; | 
|  | if (defaultLanguageVersion != null) { | 
|  | var nonPackageLanguageVersion = | 
|  | Version.parse('$defaultLanguageVersion.0'); | 
|  | analysisOptions.nonPackageLanguageVersion = nonPackageLanguageVersion; | 
|  | analysisOptions.nonPackageFeatureSet = FeatureSet.latestLanguageVersion() | 
|  | .restrictToVersion(nonPackageLanguageVersion); | 
|  | } | 
|  |  | 
|  | var enabledExperiments = this.enabledExperiments!; | 
|  | if (enabledExperiments.isNotEmpty) { | 
|  | analysisOptions.contextFeatures = FeatureSet.fromEnableFlags2( | 
|  | sdkLanguageVersion: ExperimentStatus.currentVersion, | 
|  | flags: enabledExperiments, | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Return a list of command-line arguments containing all of the given [args] | 
|  | /// that are defined by the given [parser]. An argument is considered to be | 
|  | /// defined by the parser if | 
|  | /// - it starts with '--' and the rest of the argument (minus any value | 
|  | ///   introduced by '=') is the name of a known option, | 
|  | /// - it starts with '-' and the rest of the argument (minus any value | 
|  | ///   introduced by '=') is the name of a known abbreviation, or | 
|  | /// - it starts with something other than '--' or '-'. | 
|  | /// | 
|  | /// This function allows command-line tools to implement the | 
|  | /// '--ignore-unrecognized-flags' option. | 
|  | static List<String> filterUnknownArguments( | 
|  | List<String> args, ArgParser parser) { | 
|  | var knownOptions = <String>{}; | 
|  | var knownAbbreviations = <String>{}; | 
|  | parser.options.forEach((String name, Option option) { | 
|  | knownOptions.add(name); | 
|  | var abbreviation = option.abbr; | 
|  | if (abbreviation != null) { | 
|  | knownAbbreviations.add(abbreviation); | 
|  | } | 
|  | if (option.negatable ?? false) { | 
|  | knownOptions.add('no-$name'); | 
|  | } | 
|  | }); | 
|  | String optionName(int prefixLength, String argument) { | 
|  | var equalsOffset = argument.lastIndexOf('='); | 
|  | if (equalsOffset < 0) { | 
|  | return argument.substring(prefixLength); | 
|  | } | 
|  | return argument.substring(prefixLength, equalsOffset); | 
|  | } | 
|  |  | 
|  | var filtered = <String>[]; | 
|  | for (var i = 0; i < args.length; i++) { | 
|  | var argument = args[i]; | 
|  | if (argument.startsWith('--') && argument.length > 2) { | 
|  | if (knownOptions.contains(optionName(2, argument))) { | 
|  | filtered.add(argument); | 
|  | } | 
|  | } else if (argument.startsWith('-D') && argument.indexOf('=') > 0) { | 
|  | filtered.add(argument); | 
|  | } | 
|  | if (argument.startsWith('-') && argument.length > 1) { | 
|  | if (knownAbbreviations.contains(optionName(1, argument))) { | 
|  | filtered.add(argument); | 
|  | } | 
|  | } else { | 
|  | filtered.add(argument); | 
|  | } | 
|  | } | 
|  | return filtered; | 
|  | } | 
|  |  | 
|  | /// Parse [args] into [CommandLineOptions] describing the specified | 
|  | /// analyzer options. In case of a format error, calls [printAndFail], which | 
|  | /// by default prints an error message to stderr and exits. | 
|  | static CommandLineOptions? parse( | 
|  | ResourceProvider resourceProvider, List<String> args, | 
|  | {void Function(String msg) printAndFail = printAndFail}) { | 
|  | var options = _parse(resourceProvider, args); | 
|  |  | 
|  | /// Only happens in testing. | 
|  | if (options == null) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | // Check SDK. | 
|  | { | 
|  | var sdkPath = options.dartSdkPath; | 
|  |  | 
|  | // Check that SDK is existing directory. | 
|  | if (sdkPath != null) { | 
|  | if (!io.Directory(sdkPath).existsSync()) { | 
|  | printAndFail('Invalid Dart SDK path: $sdkPath'); | 
|  | return null; // Only reachable in testing. | 
|  | } | 
|  | } | 
|  |  | 
|  | // Infer if unspecified. | 
|  | sdkPath ??= getSdkPath(); | 
|  |  | 
|  | var pathContext = resourceProvider.pathContext; | 
|  | options.dartSdkPath = file_paths.absoluteNormalized(pathContext, sdkPath); | 
|  | } | 
|  |  | 
|  | return options; | 
|  | } | 
|  |  | 
|  | static String? _absoluteNormalizedPath( | 
|  | ResourceProvider resourceProvider, | 
|  | String? path, | 
|  | ) { | 
|  | if (path == null) { | 
|  | return null; | 
|  | } | 
|  | var pathContext = resourceProvider.pathContext; | 
|  | return pathContext.normalize( | 
|  | pathContext.absolute(path), | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// Add the standard flags and options to the given [parser]. The standard flags | 
|  | /// are those that are typically used to control the way in which the code is | 
|  | /// analyzed. | 
|  | /// | 
|  | // TODO(danrubel): Update DDC to support all the options defined in this method | 
|  | // then remove the [ddc] named argument from this method. | 
|  | static void _defineAnalysisArguments(ArgParser parser, | 
|  | {bool hide = true, bool ddc = false}) { | 
|  | parser.addOption(_sdkPathOption, | 
|  | help: 'The path to the Dart SDK.', hide: ddc && hide); | 
|  | parser.addOption(_analysisOptionsFileOption, | 
|  | help: 'Path to an analysis options file.', hide: ddc && hide); | 
|  | parser.addMultiOption(_enableExperimentOption, | 
|  | help: 'Enable one or more experimental features. If multiple features ' | 
|  | 'are being added, they should be comma separated.', | 
|  | splitCommas: true); | 
|  |  | 
|  | // | 
|  | // Hidden flags and options. | 
|  | // | 
|  | parser.addMultiOption(_defineVariableOption, | 
|  | abbr: 'D', | 
|  | help: | 
|  | 'Define an environment declaration. For example, "-Dfoo=bar" defines ' | 
|  | 'an environment declaration named "foo" whose value is "bar".', | 
|  | hide: hide); | 
|  | parser.addOption(_packagesOption, | 
|  | help: 'The path to the package resolution configuration file, which ' | 
|  | 'supplies a mapping of package names\ninto paths.', | 
|  | hide: ddc); | 
|  | parser.addFlag(_enableInitializingFormalAccessFlag, | 
|  | help: | 
|  | 'Enable support for allowing access to field formal parameters in a ' | 
|  | 'constructor\'s initializer list (deprecated).', | 
|  | defaultsTo: false, | 
|  | negatable: false, | 
|  | hide: hide || ddc); | 
|  | } | 
|  |  | 
|  | static String _getVersion() { | 
|  | try { | 
|  | // This is relative to bin/snapshot, so ../.. | 
|  | var versionPath = | 
|  | io.Platform.script.resolve('../../version').toFilePath(); | 
|  | var versionFile = io.File(versionPath); | 
|  | return versionFile.readAsStringSync().trim(); | 
|  | } catch (_) { | 
|  | // This happens when the script is not running in the context of an SDK. | 
|  | return '<unknown>'; | 
|  | } | 
|  | } | 
|  |  | 
|  | static CommandLineOptions? _parse( | 
|  | ResourceProvider resourceProvider, | 
|  | List<String> args, | 
|  | ) { | 
|  | var verbose = args.contains('-v') || args.contains('--verbose'); | 
|  | var hide = !verbose; | 
|  |  | 
|  | var parser = ArgParser(allowTrailingOptions: true); | 
|  |  | 
|  | if (!hide) { | 
|  | parser.addSeparator('General options:'); | 
|  | } | 
|  |  | 
|  | // TODO(devoncarew): This defines some hidden flags, which would be better | 
|  | // defined with the rest of the hidden flags below (to group well with the | 
|  | // other flags). | 
|  | _defineAnalysisArguments(parser, hide: hide); | 
|  |  | 
|  | parser | 
|  | ..addOption('format', | 
|  | help: 'Specifies the format in which errors are displayed; the only ' | 
|  | 'currently recognized values are \'json\' and \'machine\'.') | 
|  | ..addFlag('version', | 
|  | help: 'Print the analyzer version.', | 
|  | defaultsTo: false, | 
|  | negatable: false) | 
|  | ..addFlag('help', | 
|  | abbr: 'h', | 
|  | help: | 
|  | 'Display this help message. Add --verbose to show hidden options.', | 
|  | defaultsTo: false, | 
|  | negatable: false) | 
|  | ..addFlag('verbose', | 
|  | abbr: 'v', | 
|  | defaultsTo: false, | 
|  | help: 'Verbose output.', | 
|  | negatable: false); | 
|  |  | 
|  | parser.addFlag('color', | 
|  | help: 'Use ansi colors when printing messages.', | 
|  | defaultsTo: ansi.terminalSupportsAnsi(), | 
|  | hide: hide); | 
|  |  | 
|  | // Hidden flags. | 
|  | if (!hide) { | 
|  | parser.addSeparator('Less frequently used flags:'); | 
|  | } | 
|  |  | 
|  | parser | 
|  | ..addFlag('batch', | 
|  | help: 'Read commands from standard input (for testing).', | 
|  | defaultsTo: false, | 
|  | negatable: false, | 
|  | hide: hide) | 
|  | ..addFlag(_ignoreUnrecognizedFlagsFlag, | 
|  | help: 'Ignore unrecognized command line flags.', | 
|  | defaultsTo: false, | 
|  | negatable: false, | 
|  | hide: hide) | 
|  | ..addOption(_defaultLanguageVersionOption, | 
|  | help: 'The default language version when it is not specified via ' | 
|  | 'other ways (internal, tests only).', | 
|  | hide: false) | 
|  | ..addFlag('disable-cache-flushing', defaultsTo: false, hide: hide) | 
|  | ..addOption('x-perf-report', | 
|  | help: 'Writes a performance report to the given file (experimental).', | 
|  | hide: hide) | 
|  | ..addFlag('enable-conditional-directives', | 
|  | help: | 
|  | 'deprecated -- Enable support for conditional directives (DEP 40).', | 
|  | defaultsTo: false, | 
|  | negatable: false, | 
|  | hide: hide) | 
|  | ..addFlag('log', | 
|  | help: 'Log additional messages and exceptions.', | 
|  | defaultsTo: false, | 
|  | negatable: false, | 
|  | hide: hide) | 
|  | ..addFlag('use-analysis-driver-memory-byte-store', | 
|  | help: 'Use memory byte store, not the file system cache.', | 
|  | defaultsTo: false, | 
|  | negatable: false, | 
|  | hide: hide) | 
|  | ..addMultiOption('url-mapping', | 
|  | help: '--url-mapping=libraryUri,/path/to/library.dart directs the ' | 
|  | 'analyzer to use "library.dart" as the source for an import ' | 
|  | 'of "libraryUri".', | 
|  | splitCommas: false, | 
|  | hide: hide) | 
|  | ..addFlag('train-snapshot', | 
|  | help: 'Analyze the given source for the purposes of training a ' | 
|  | 'dartanalyzer snapshot.', | 
|  | hide: hide, | 
|  | negatable: false); | 
|  |  | 
|  | try { | 
|  | if (args.contains('--$_ignoreUnrecognizedFlagsFlag')) { | 
|  | args = filterUnknownArguments(args, parser); | 
|  | } | 
|  | var results = parser.parse(args); | 
|  |  | 
|  | // Help requests. | 
|  | if (cast(results['help'])) { | 
|  | _showUsage(parser, fromHelp: true); | 
|  | exitHandler(0); | 
|  | return null; // Only reachable in testing. | 
|  | } | 
|  |  | 
|  | // Batch mode and input files. | 
|  | if (cast(results['batch'])) { | 
|  | if (results.rest.isNotEmpty) { | 
|  | errorSink.writeln('No source files expected in the batch mode.'); | 
|  | _showUsage(parser); | 
|  | exitHandler(15); | 
|  | return null; // Only reachable in testing. | 
|  | } | 
|  | } else if (cast(results['version'])) { | 
|  | outSink.writeln('$_binaryName version ${_getVersion()}'); | 
|  | exitHandler(0); | 
|  | return null; // Only reachable in testing. | 
|  | } else { | 
|  | if (results.rest.isEmpty) { | 
|  | _showUsage(parser, fromHelp: true); | 
|  | exitHandler(15); | 
|  | return null; // Only reachable in testing. | 
|  | } | 
|  | } | 
|  |  | 
|  | if (results.wasParsed(_enableExperimentOption)) { | 
|  | var names = | 
|  | (results[_enableExperimentOption] as List).cast<String>().toList(); | 
|  | var errorFound = false; | 
|  | for (var validationResult in validateFlags(names)) { | 
|  | if (validationResult.isError) { | 
|  | errorFound = true; | 
|  | } | 
|  | var kind = validationResult.isError ? 'ERROR' : 'WARNING'; | 
|  | errorSink.writeln('$kind: ${validationResult.message}'); | 
|  | } | 
|  | if (errorFound) { | 
|  | _showUsage(parser); | 
|  | exitHandler(15); | 
|  | return null; // Only reachable in testing. | 
|  | } | 
|  | } | 
|  |  | 
|  | return CommandLineOptions._fromArgs(resourceProvider, results); | 
|  | } on FormatException catch (e) { | 
|  | errorSink.writeln(e.message); | 
|  | _showUsage(parser); | 
|  | exitHandler(15); | 
|  | return null; // Only reachable in testing. | 
|  | } | 
|  | } | 
|  |  | 
|  | static void _showUsage(ArgParser parser, {bool fromHelp = false}) { | 
|  | errorSink.writeln( | 
|  | 'Usage: $_binaryName [options...] <directory or list of files>'); | 
|  |  | 
|  | errorSink.writeln(''); | 
|  | errorSink.writeln(parser.usage); | 
|  |  | 
|  | errorSink.writeln(''); | 
|  | errorSink.writeln(''' | 
|  | Run "dartanalyzer -h -v" for verbose help output, including less commonly used options. | 
|  | For more information, see https://dart.dev/tools/dartanalyzer.\n'''); | 
|  | } | 
|  | } |