| // 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 'package:analyzer/dart/analysis/code_style_options.dart'; |
| import 'package:analyzer/dart/analysis/features.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/source/error_processor.dart'; |
| import 'package:analyzer/src/analysis_options/analysis_options_provider.dart'; |
| import 'package:analyzer/src/analysis_options/code_style_options.dart'; |
| import 'package:analyzer/src/analysis_options/error/option_codes.dart'; |
| import 'package:analyzer/src/dart/analysis/experiments.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/generated/utilities_general.dart'; |
| import 'package:analyzer/src/lint/config.dart'; |
| import 'package:analyzer/src/lint/linter.dart'; |
| import 'package:analyzer/src/lint/options_rule_validator.dart'; |
| import 'package:analyzer/src/lint/registry.dart'; |
| import 'package:analyzer/src/plugin/options.dart'; |
| import 'package:analyzer/src/util/yaml.dart'; |
| import 'package:analyzer/src/utilities/extensions/string.dart'; |
| import 'package:source_span/source_span.dart'; |
| import 'package:yaml/yaml.dart'; |
| |
| final _OptionsProcessor _processor = _OptionsProcessor(); |
| |
| List<AnalysisError> analyzeAnalysisOptions( |
| Source source, |
| String content, |
| SourceFactory sourceFactory, |
| String contextRoot, |
| ) { |
| List<AnalysisError> errors = <AnalysisError>[]; |
| Source initialSource = source; |
| SourceSpan? initialIncludeSpan; |
| AnalysisOptionsProvider optionsProvider = |
| AnalysisOptionsProvider(sourceFactory); |
| |
| // Validate the specified options and any included option files |
| void validate(Source source, YamlMap options) { |
| List<AnalysisError> validationErrors = |
| OptionsFileValidator(source).validate(options); |
| if (initialIncludeSpan != null && validationErrors.isNotEmpty) { |
| for (AnalysisError error in validationErrors) { |
| var args = [ |
| source.fullName, |
| error.offset.toString(), |
| (error.offset + error.length - 1).toString(), |
| error.message, |
| ]; |
| errors.add(AnalysisError( |
| initialSource, |
| initialIncludeSpan!.start.offset, |
| initialIncludeSpan!.length, |
| AnalysisOptionsWarningCode.INCLUDED_FILE_WARNING, |
| args)); |
| } |
| } else { |
| errors.addAll(validationErrors); |
| } |
| |
| var node = options.valueAt(AnalyzerOptions.include); |
| if (node == null) { |
| return; |
| } |
| SourceSpan span = node.span; |
| initialIncludeSpan ??= span; |
| String includeUri = span.text; |
| var includedSource = sourceFactory.resolveUri(source, includeUri); |
| if (includedSource == null || !includedSource.exists()) { |
| errors.add(AnalysisError( |
| initialSource, |
| initialIncludeSpan!.start.offset, |
| initialIncludeSpan!.length, |
| AnalysisOptionsWarningCode.INCLUDE_FILE_NOT_FOUND, |
| [includeUri, source.fullName, contextRoot])); |
| return; |
| } |
| try { |
| YamlMap options = |
| optionsProvider.getOptionsFromString(includedSource.contents.data); |
| validate(includedSource, options); |
| } on OptionsFormatException catch (e) { |
| var args = [ |
| includedSource.fullName, |
| e.span!.start.offset.toString(), |
| e.span!.end.offset.toString(), |
| e.message, |
| ]; |
| // Report errors for included option files |
| // on the include directive located in the initial options file. |
| errors.add(AnalysisError( |
| initialSource, |
| initialIncludeSpan!.start.offset, |
| initialIncludeSpan!.length, |
| AnalysisOptionsErrorCode.INCLUDED_FILE_PARSE_ERROR, |
| args)); |
| } |
| } |
| |
| try { |
| YamlMap options = optionsProvider.getOptionsFromString(content); |
| validate(source, options); |
| } on OptionsFormatException catch (e) { |
| SourceSpan span = e.span!; |
| errors.add(AnalysisError(source, span.start.offset, span.length, |
| AnalysisOptionsErrorCode.PARSE_ERROR, [e.message])); |
| } |
| return errors; |
| } |
| |
| void applyToAnalysisOptions(AnalysisOptionsImpl options, YamlMap optionMap) { |
| _processor.applyToAnalysisOptions(options, optionMap); |
| } |
| |
| /// `analyzer` analysis options constants. |
| class AnalyzerOptions { |
| static const String analyzer = 'analyzer'; |
| static const String enableSuperMixins = 'enableSuperMixins'; |
| static const String enablePreviewDart2 = 'enablePreviewDart2'; |
| |
| static const String cannotIgnore = 'cannot-ignore'; |
| static const String codeStyle = 'code-style'; |
| static const String enableExperiment = 'enable-experiment'; |
| static const String errors = 'errors'; |
| static const String exclude = 'exclude'; |
| static const String include = 'include'; |
| static const String language = 'language'; |
| static const String optionalChecks = 'optional-checks'; |
| static const String plugins = 'plugins'; |
| static const String strong_mode = 'strong-mode'; |
| |
| // Optional checks options. |
| static const String chromeOsManifestChecks = 'chrome-os-manifest-checks'; |
| |
| // Strong mode options (see AnalysisOptionsImpl for documentation). |
| static const String declarationCasts = 'declaration-casts'; |
| static const String implicitCasts = 'implicit-casts'; |
| static const String implicitDynamic = 'implicit-dynamic'; |
| |
| // Language options (see AnalysisOptionsImpl for documentation). |
| static const String strictCasts = 'strict-casts'; |
| static const String strictInference = 'strict-inference'; |
| static const String strictRawTypes = 'strict-raw-types'; |
| |
| // Code style options |
| static const String format = 'format'; |
| |
| /// Ways to say `ignore`. |
| static const List<String> ignoreSynonyms = ['ignore', 'false']; |
| |
| /// Valid error `severity`s. |
| static final List<String> severities = List.unmodifiable(severityMap.keys); |
| |
| /// Ways to say `include`. |
| static const List<String> includeSynonyms = ['include', 'true']; |
| |
| static const String propagateLinterExceptions = 'propagate-linter-exceptions'; |
| |
| /// Ways to say `true` or `false`. |
| static const List<String> trueOrFalse = ['true', 'false']; |
| |
| /// Supported top-level `analyzer` options. |
| static const List<String> topLevel = [ |
| cannotIgnore, |
| enableExperiment, |
| errors, |
| exclude, |
| language, |
| optionalChecks, |
| plugins, |
| propagateLinterExceptions, |
| strong_mode, |
| ]; |
| |
| /// Supported `analyzer` strong-mode options. |
| static const List<String> strongModeOptions = [ |
| declarationCasts, // deprecated |
| implicitCasts, |
| implicitDynamic, |
| ]; |
| |
| /// Supported `analyzer` language options. |
| static const List<String> languageOptions = [ |
| strictCasts, |
| strictInference, |
| strictRawTypes, |
| ]; |
| |
| /// Supported 'analyzer' optional checks options. |
| static const List<String> optionalChecksOptions = [ |
| chromeOsManifestChecks, |
| ]; |
| |
| /// Supported 'code-style' options. |
| static const List<String> codeStyleOptions = [ |
| format, |
| ]; |
| |
| /// Proposed values for a `true` or `false` option. |
| static String get trueOrFalseProposal => |
| AnalyzerOptions.trueOrFalse.quotedAndCommaSeparatedWithAnd; |
| } |
| |
| /// Validates `analyzer` options. |
| class AnalyzerOptionsValidator extends CompositeValidator { |
| AnalyzerOptionsValidator() |
| : super([ |
| TopLevelAnalyzerOptionsValidator(), |
| StrongModeOptionValueValidator(), |
| ErrorFilterOptionValidator(), |
| EnabledExperimentsValidator(), |
| LanguageOptionValidator(), |
| OptionalChecksValueValidator(), |
| CannotIgnoreOptionValidator(), |
| ]); |
| } |
| |
| /// Validates the `analyzer` `cannot-ignore` option. |
| /// |
| /// This includes the format of the `cannot-ignore` section, the format of |
| /// values in the section, and whether each value is a valid string. |
| class CannotIgnoreOptionValidator extends OptionsValidator { |
| /// Lazily populated set of error codes. |
| static final Set<String> _errorCodes = |
| errorCodeValues.map((ErrorCode code) => code.name).toSet(); |
| |
| /// Lazily populated set of lint codes. |
| late final Set<String> _lintCodes = Registry.ruleRegistry.rules |
| .map((rule) => rule.name.toUpperCase()) |
| .toSet(); |
| |
| @override |
| void validate(ErrorReporter reporter, YamlMap options) { |
| var analyzer = options.valueAt(AnalyzerOptions.analyzer); |
| if (analyzer is YamlMap) { |
| var unignorableNames = analyzer.valueAt(AnalyzerOptions.cannotIgnore); |
| if (unignorableNames is YamlList) { |
| var listedNames = <String>{}; |
| for (var unignorableNameNode in unignorableNames.nodes) { |
| var unignorableName = unignorableNameNode.value; |
| if (unignorableName is String) { |
| if (AnalyzerOptions.severities.contains(unignorableName)) { |
| listedNames.add(unignorableName); |
| continue; |
| } |
| var upperCaseName = unignorableName.toUpperCase(); |
| if (!_errorCodes.contains(upperCaseName) && |
| !_lintCodes.contains(upperCaseName)) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.UNRECOGNIZED_ERROR_CODE, |
| unignorableNameNode.span, |
| [unignorableName]); |
| } else if (listedNames.contains(upperCaseName)) { |
| // TODO(srawlins): Create a "duplicate value" code and report it |
| // here. |
| } else { |
| listedNames.add(upperCaseName); |
| } |
| } else { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| unignorableNameNode.span, |
| [AnalyzerOptions.cannotIgnore]); |
| } |
| } |
| } else if (unignorableNames != null) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| unignorableNames.span, |
| [AnalyzerOptions.cannotIgnore]); |
| } |
| } |
| } |
| } |
| |
| /// Validates `code-style` options. |
| class CodeStyleOptionsValidator extends OptionsValidator { |
| @override |
| void validate(ErrorReporter reporter, YamlMap options) { |
| var codeStyle = options.valueAt(AnalyzerOptions.codeStyle); |
| if (codeStyle is YamlMap) { |
| codeStyle.nodeMap.forEach((keyNode, valueNode) { |
| var key = keyNode.value; |
| if (key == AnalyzerOptions.format) { |
| _validateFormat(reporter, valueNode); |
| } else { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES, |
| keyNode.span, |
| [AnalyzerOptions.codeStyle, keyNode.toString()]); |
| } |
| }); |
| } else if (codeStyle is YamlScalar && codeStyle.value != null) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| codeStyle.span, |
| [AnalyzerOptions.codeStyle]); |
| } else if (codeStyle is YamlList) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| codeStyle.span, |
| [AnalyzerOptions.codeStyle]); |
| } |
| } |
| |
| void _validateFormat(ErrorReporter reporter, YamlNode format) { |
| if (format is YamlMap) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| format.span, |
| [AnalyzerOptions.format]); |
| } else if (format is YamlScalar) { |
| var formatValue = toBool(format.value); |
| if (formatValue == null) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.UNSUPPORTED_VALUE, format.span, [ |
| AnalyzerOptions.format, |
| format.value, |
| AnalyzerOptions.trueOrFalseProposal |
| ]); |
| } |
| } else if (format is YamlList) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| format.span, |
| [AnalyzerOptions.format]); |
| } |
| } |
| } |
| |
| /// Convenience class for composing validators. |
| class CompositeValidator extends OptionsValidator { |
| final List<OptionsValidator> validators; |
| |
| CompositeValidator(this.validators); |
| |
| @override |
| void validate(ErrorReporter reporter, YamlMap options) { |
| for (var validator in validators) { |
| validator.validate(reporter, options); |
| } |
| } |
| } |
| |
| /// Validates `analyzer` language configuration options. |
| class EnabledExperimentsValidator extends OptionsValidator { |
| @override |
| void validate(ErrorReporter reporter, YamlMap options) { |
| var analyzer = options.valueAt(AnalyzerOptions.analyzer); |
| if (analyzer is YamlMap) { |
| var experimentNames = analyzer.valueAt(AnalyzerOptions.enableExperiment); |
| if (experimentNames is YamlList) { |
| var flags = |
| experimentNames.nodes.map((node) => node.toString()).toList(); |
| for (var validationResult in validateFlags(flags)) { |
| var flagIndex = validationResult.stringIndex; |
| var span = experimentNames.nodes[flagIndex].span; |
| if (validationResult is UnrecognizedFlag) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES, |
| span, |
| [AnalyzerOptions.enableExperiment, flags[flagIndex]]); |
| } else { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_OPTION, |
| span, |
| [AnalyzerOptions.enableExperiment, validationResult.message]); |
| } |
| } |
| } else if (experimentNames != null) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| experimentNames.span, |
| [AnalyzerOptions.enableExperiment]); |
| } |
| } |
| } |
| } |
| |
| /// Builds error reports with value proposals. |
| class ErrorBuilder { |
| static AnalysisOptionsWarningCode get noProposalCode => |
| AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES; |
| |
| static AnalysisOptionsWarningCode get pluralProposalCode => |
| AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES; |
| |
| static AnalysisOptionsWarningCode get singularProposalCode => |
| AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUE; |
| |
| final String proposal; |
| |
| final AnalysisOptionsWarningCode code; |
| |
| /// Create a builder for the given [supportedOptions]. |
| factory ErrorBuilder(List<String> supportedOptions) { |
| var proposal = supportedOptions.quotedAndCommaSeparatedWithAnd; |
| if (supportedOptions.isEmpty) { |
| return ErrorBuilder._(proposal: proposal, code: noProposalCode); |
| } else if (supportedOptions.length == 1) { |
| return ErrorBuilder._(proposal: proposal, code: singularProposalCode); |
| } else { |
| return ErrorBuilder._(proposal: proposal, code: pluralProposalCode); |
| } |
| } |
| |
| ErrorBuilder._({ |
| required this.proposal, |
| required this.code, |
| }); |
| |
| /// Report an unsupported [node] value, defined in the given [scopeName]. |
| void reportError(ErrorReporter reporter, String scopeName, YamlNode node) { |
| if (proposal.isNotEmpty) { |
| reporter.reportErrorForSpan( |
| code, node.span, [scopeName, node.value, proposal]); |
| } else { |
| reporter.reportErrorForSpan(code, node.span, [scopeName, node.value]); |
| } |
| } |
| } |
| |
| /// Validates `analyzer` error filter options. |
| class ErrorFilterOptionValidator extends OptionsValidator { |
| /// Legal values. |
| static final List<String> legalValues = |
| List.from(AnalyzerOptions.ignoreSynonyms) |
| ..addAll(AnalyzerOptions.includeSynonyms) |
| ..addAll(AnalyzerOptions.severities); |
| |
| /// Pretty String listing legal values. |
| static final String legalValueString = |
| legalValues.quotedAndCommaSeparatedWithAnd; |
| |
| /// Lazily populated set of error codes. |
| static final Set<String> _errorCodes = |
| errorCodeValues.map((ErrorCode code) => code.name).toSet(); |
| |
| /// Lazily populated set of lint codes. |
| late final Set<String> _lintCodes = Registry.ruleRegistry.rules |
| .map((rule) => rule.name.toUpperCase()) |
| .toSet(); |
| |
| @override |
| void validate(ErrorReporter reporter, YamlMap options) { |
| var analyzer = options.valueAt(AnalyzerOptions.analyzer); |
| if (analyzer is YamlMap) { |
| var filters = analyzer.valueAt(AnalyzerOptions.errors); |
| if (filters is YamlMap) { |
| filters.nodes.forEach((k, v) { |
| String? value; |
| if (k is YamlScalar) { |
| value = toUpperCase(k.value); |
| if (!_errorCodes.contains(value) && !_lintCodes.contains(value)) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.UNRECOGNIZED_ERROR_CODE, |
| k.span, |
| [k.value.toString()]); |
| } |
| } |
| if (v is YamlScalar) { |
| value = toLowerCase(v.value); |
| if (!legalValues.contains(value)) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode |
| .UNSUPPORTED_OPTION_WITH_LEGAL_VALUES, |
| v.span, |
| [ |
| AnalyzerOptions.errors, |
| v.value.toString(), |
| legalValueString |
| ]); |
| } |
| } else { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| v.span, |
| [AnalyzerOptions.enableExperiment]); |
| } |
| }); |
| } else if (filters != null) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| filters.span, |
| [AnalyzerOptions.enableExperiment]); |
| } |
| } |
| } |
| } |
| |
| /// Validates `analyzer` language configuration options. |
| class LanguageOptionValidator extends OptionsValidator { |
| final ErrorBuilder _builder = ErrorBuilder(AnalyzerOptions.languageOptions); |
| |
| @override |
| void validate(ErrorReporter reporter, YamlMap options) { |
| var analyzer = options.valueAt(AnalyzerOptions.analyzer); |
| if (analyzer is YamlMap) { |
| var language = analyzer.valueAt(AnalyzerOptions.language); |
| if (language is YamlMap) { |
| language.nodes.forEach((k, v) { |
| String? key, value; |
| bool validKey = false; |
| if (k is YamlScalar) { |
| key = k.value?.toString(); |
| if (AnalyzerOptions.enablePreviewDart2 == key) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsHintCode.PREVIEW_DART_2_SETTING_DEPRECATED, |
| k.span); |
| } else if (AnalyzerOptions.enableSuperMixins == key) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsHintCode.SUPER_MIXINS_SETTING_DEPRECATED, |
| k.span); |
| } else if (!AnalyzerOptions.languageOptions.contains(key)) { |
| _builder.reportError(reporter, AnalyzerOptions.language, k); |
| } else { |
| // If we have a valid key, go on and check the value. |
| validKey = true; |
| } |
| } |
| if (validKey && v is YamlScalar) { |
| value = toLowerCase(v.value); |
| // `null` is not a valid key, so we can safely assume `key` is |
| // non-`null`. |
| if (!AnalyzerOptions.trueOrFalse.contains(value)) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.UNSUPPORTED_VALUE, |
| v.span, |
| [key!, v.value, AnalyzerOptions.trueOrFalseProposal]); |
| } |
| } |
| }); |
| } else if (language is YamlScalar && language.value != null) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| language.span, |
| [AnalyzerOptions.language]); |
| } else if (language is YamlList) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| language.span, |
| [AnalyzerOptions.language]); |
| } |
| } |
| } |
| } |
| |
| /// Validates `linter` top-level options. |
| /// TODO(pq): move into `linter` package and plugin. |
| class LinterOptionsValidator extends TopLevelOptionValidator { |
| LinterOptionsValidator() : super('linter', const ['rules']); |
| } |
| |
| /// Validates `analyzer` optional-checks value configuration options. |
| class OptionalChecksValueValidator extends OptionsValidator { |
| final ErrorBuilder _builder = |
| ErrorBuilder(AnalyzerOptions.optionalChecksOptions); |
| |
| @override |
| void validate(ErrorReporter reporter, YamlMap options) { |
| var analyzer = options.valueAt(AnalyzerOptions.analyzer); |
| if (analyzer is YamlMap) { |
| var v = analyzer.valueAt(AnalyzerOptions.optionalChecks); |
| if (v is YamlScalar) { |
| var value = toLowerCase(v.value); |
| if (value != AnalyzerOptions.chromeOsManifestChecks) { |
| _builder.reportError( |
| reporter, AnalyzerOptions.chromeOsManifestChecks, v); |
| } |
| } else if (v is YamlMap) { |
| v.nodes.forEach((k, v) { |
| String? key, value; |
| if (k is YamlScalar) { |
| key = k.value?.toString(); |
| if (key != AnalyzerOptions.chromeOsManifestChecks) { |
| _builder.reportError( |
| reporter, AnalyzerOptions.chromeOsManifestChecks, k); |
| } else { |
| value = toLowerCase(v.value); |
| if (!AnalyzerOptions.trueOrFalse.contains(value)) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.UNSUPPORTED_VALUE, |
| v.span, |
| [key!, v.value, AnalyzerOptions.trueOrFalseProposal]); |
| } |
| } |
| } |
| }); |
| } else if (v != null) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| v.span, |
| [AnalyzerOptions.enableExperiment]); |
| } |
| } |
| } |
| } |
| |
| /// Validates options defined in an analysis options file. |
| class OptionsFileValidator { |
| /// The source being validated. |
| final Source source; |
| |
| final List<OptionsValidator> _validators = [ |
| AnalyzerOptionsValidator(), |
| CodeStyleOptionsValidator(), |
| LinterOptionsValidator(), |
| LinterRuleOptionsValidator() |
| ]; |
| |
| OptionsFileValidator(this.source); |
| |
| List<AnalysisError> validate(YamlMap options) { |
| RecordingErrorListener recorder = RecordingErrorListener(); |
| ErrorReporter reporter = ErrorReporter( |
| recorder, |
| source, |
| isNonNullableByDefault: false, |
| ); |
| for (var validator in _validators) { |
| validator.validate(reporter, options); |
| } |
| return recorder.errors; |
| } |
| } |
| |
| /// Validates `analyzer` strong-mode value configuration options. |
| class StrongModeOptionValueValidator extends OptionsValidator { |
| final ErrorBuilder _builder = ErrorBuilder(AnalyzerOptions.strongModeOptions); |
| |
| @override |
| void validate(ErrorReporter reporter, YamlMap options) { |
| var analyzer = options.valueAt(AnalyzerOptions.analyzer); |
| if (analyzer is YamlMap) { |
| var v = analyzer.valueAt(AnalyzerOptions.strong_mode); |
| if (v is YamlScalar) { |
| var value = toLowerCase(v.value); |
| if (!AnalyzerOptions.trueOrFalse.contains(value)) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.UNSUPPORTED_VALUE, v.span, [ |
| AnalyzerOptions.strong_mode, |
| v.value, |
| AnalyzerOptions.trueOrFalseProposal |
| ]); |
| } else if (value == 'false') { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.SPEC_MODE_REMOVED, v.span); |
| } else if (value == 'true') { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsHintCode.STRONG_MODE_SETTING_DEPRECATED, v.span); |
| } |
| } else if (v is YamlMap) { |
| v.nodes.forEach((k, v) { |
| String? key, value; |
| bool validKey = false; |
| if (k is YamlScalar) { |
| key = k.value?.toString(); |
| if (!AnalyzerOptions.strongModeOptions.contains(key)) { |
| _builder.reportError(reporter, AnalyzerOptions.strong_mode, k); |
| } else if (key == AnalyzerOptions.declarationCasts) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED, |
| k.span, |
| [AnalyzerOptions.declarationCasts]); |
| } else { |
| // If we have a valid key, go on and check the value. |
| validKey = true; |
| } |
| } |
| if (validKey && v is YamlScalar) { |
| value = toLowerCase(v.value); |
| if (!AnalyzerOptions.trueOrFalse.contains(value)) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.UNSUPPORTED_VALUE, |
| v.span, |
| [key!, v.value, AnalyzerOptions.trueOrFalseProposal]); |
| } |
| } |
| }); |
| } else if (v != null) { |
| reporter.reportErrorForSpan( |
| AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT, |
| v.span, |
| [AnalyzerOptions.enableExperiment]); |
| } |
| } |
| } |
| } |
| |
| /// Validates `analyzer` top-level options. |
| class TopLevelAnalyzerOptionsValidator extends TopLevelOptionValidator { |
| TopLevelAnalyzerOptionsValidator() |
| : super(AnalyzerOptions.analyzer, AnalyzerOptions.topLevel); |
| } |
| |
| /// Validates top-level options. For example, |
| /// plugin: |
| /// top-level-option: true |
| class TopLevelOptionValidator extends OptionsValidator { |
| final String pluginName; |
| final List<String> supportedOptions; |
| final String _valueProposal; |
| final AnalysisOptionsWarningCode _warningCode; |
| |
| TopLevelOptionValidator(this.pluginName, this.supportedOptions) |
| : assert(supportedOptions.isNotEmpty), |
| _valueProposal = supportedOptions.quotedAndCommaSeparatedWithAnd, |
| _warningCode = supportedOptions.length == 1 |
| ? AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUE |
| : AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES; |
| |
| @override |
| void validate(ErrorReporter reporter, YamlMap options) { |
| var node = options.valueAt(pluginName); |
| if (node is YamlMap) { |
| node.nodes.forEach((k, v) { |
| if (k is YamlScalar) { |
| if (!supportedOptions.contains(k.value)) { |
| reporter.reportErrorForSpan( |
| _warningCode, k.span, [pluginName, k.value, _valueProposal]); |
| } |
| } |
| //TODO(pq): consider an error if the node is not a Scalar. |
| }); |
| } |
| // TODO(srawlins): Report non-Map with |
| // AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT. |
| } |
| } |
| |
| class _OptionsProcessor { |
| /// Apply the options in the given [optionMap] to the given analysis |
| /// [options]. |
| void applyToAnalysisOptions(AnalysisOptionsImpl options, YamlMap? optionMap) { |
| if (optionMap == null) { |
| return; |
| } |
| var analyzer = optionMap.valueAt(AnalyzerOptions.analyzer); |
| if (analyzer is YamlMap) { |
| // Process strong mode option. |
| var strongMode = analyzer.valueAt(AnalyzerOptions.strong_mode); |
| _applyStrongOptions(options, strongMode); |
| |
| // Process filters. |
| var filters = analyzer.valueAt(AnalyzerOptions.errors); |
| _applyProcessors(options, filters); |
| |
| // Process enabled experiments. |
| var experimentNames = analyzer.valueAt(AnalyzerOptions.enableExperiment); |
| if (experimentNames is YamlList) { |
| List<String> enabledExperiments = <String>[]; |
| for (var element in experimentNames.nodes) { |
| var experimentName = _toString(element); |
| if (experimentName != null) { |
| enabledExperiments.add(experimentName); |
| } |
| } |
| options.contextFeatures = FeatureSet.fromEnableFlags2( |
| sdkLanguageVersion: ExperimentStatus.currentVersion, |
| flags: enabledExperiments, |
| ); |
| } |
| |
| // Process optional checks options. |
| var optionalChecks = analyzer.valueAt(AnalyzerOptions.optionalChecks); |
| _applyOptionalChecks(options, optionalChecks); |
| |
| // Process language options. |
| var language = analyzer.valueAt(AnalyzerOptions.language); |
| _applyLanguageOptions(options, language); |
| |
| // Process excludes. |
| var excludes = analyzer.valueAt(AnalyzerOptions.exclude); |
| _applyExcludes(options, excludes); |
| |
| var cannotIgnore = analyzer.valueAt(AnalyzerOptions.cannotIgnore); |
| _applyUnignorables(options, cannotIgnore); |
| |
| // Process plugins. |
| var names = analyzer.valueAt(AnalyzerOptions.plugins); |
| List<String> pluginNames = <String>[]; |
| var pluginName = _toString(names); |
| if (pluginName != null) { |
| pluginNames.add(pluginName); |
| } else if (names is YamlList) { |
| for (var element in names.nodes) { |
| var pluginName = _toString(element); |
| if (pluginName != null) { |
| pluginNames.add(pluginName); |
| } |
| } |
| } else if (names is YamlMap) { |
| for (var key in names.nodes.keys) { |
| var pluginName = _toString(key); |
| if (pluginName != null) { |
| pluginNames.add(pluginName); |
| } |
| } |
| } |
| options.enabledPluginNames = pluginNames; |
| } |
| |
| // Process the 'code-style' option. |
| var codeStyle = optionMap.valueAt(AnalyzerOptions.codeStyle); |
| options.codeStyleOptions = _buildCodeStyleOptions(options, codeStyle); |
| |
| var config = parseConfig(optionMap); |
| if (config != null) { |
| Iterable<LintRule> lintRules = Registry.ruleRegistry.enabled(config); |
| if (lintRules.isNotEmpty) { |
| options.lint = true; |
| options.lintRules = lintRules.toList(); |
| } |
| } |
| } |
| |
| void _applyExcludes(AnalysisOptionsImpl options, YamlNode? excludes) { |
| if (excludes is YamlList) { |
| // TODO(srawlins): Report non-String items |
| options.excludePatterns = excludes.whereType<String>().toList(); |
| } |
| // TODO(srawlins): Report non-List with |
| // AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT. |
| } |
| |
| void _applyLanguageOption( |
| AnalysisOptionsImpl options, Object? feature, Object value) { |
| var boolValue = toBool(value); |
| if (boolValue != null) { |
| if (feature == AnalyzerOptions.strictCasts) { |
| options.strictCasts = boolValue; |
| } |
| if (feature == AnalyzerOptions.strictInference) { |
| options.strictInference = boolValue; |
| } |
| if (feature == AnalyzerOptions.strictRawTypes) { |
| options.strictRawTypes = boolValue; |
| } |
| } |
| } |
| |
| void _applyLanguageOptions(AnalysisOptionsImpl options, YamlNode? configs) { |
| if (configs is YamlMap) { |
| configs.nodes.forEach((key, value) { |
| if (key is YamlScalar && value is YamlScalar) { |
| var feature = key.value?.toString(); |
| _applyLanguageOption(options, feature, value.value); |
| } |
| }); |
| } |
| } |
| |
| void _applyOptionalChecks(AnalysisOptionsImpl options, YamlNode? config) { |
| if (config is YamlMap) { |
| config.nodes.forEach((k, v) { |
| if (k is YamlScalar && v is YamlScalar) { |
| _applyOptionalChecksOption(options, k.value?.toString(), v.value); |
| } |
| }); |
| } |
| if (config is YamlScalar) { |
| if (config.value?.toString() == AnalyzerOptions.chromeOsManifestChecks) { |
| options.chromeOsManifestChecks = true; |
| } |
| } |
| } |
| |
| void _applyOptionalChecksOption( |
| AnalysisOptionsImpl options, String? feature, Object value) { |
| var boolValue = toBool(value); |
| if (boolValue != null) { |
| if (feature == AnalyzerOptions.chromeOsManifestChecks) { |
| options.chromeOsManifestChecks = boolValue; |
| } |
| } |
| } |
| |
| void _applyProcessors(AnalysisOptionsImpl options, YamlNode? codes) { |
| ErrorConfig config = ErrorConfig(codes); |
| options.errorProcessors = config.processors; |
| } |
| |
| void _applyStrongModeOption( |
| AnalysisOptionsImpl options, String? feature, Object value) { |
| var boolValue = toBool(value); |
| if (boolValue != null) { |
| if (feature == AnalyzerOptions.implicitCasts) { |
| options.implicitCasts = boolValue; |
| } |
| if (feature == AnalyzerOptions.implicitDynamic) { |
| options.implicitDynamic = boolValue; |
| } |
| if (feature == AnalyzerOptions.propagateLinterExceptions) { |
| options.propagateLinterExceptions = boolValue; |
| } |
| } |
| } |
| |
| void _applyStrongOptions(AnalysisOptionsImpl options, YamlNode? config) { |
| if (config is YamlMap) { |
| config.nodes.forEach((k, v) { |
| if (k is YamlScalar && v is YamlScalar) { |
| _applyStrongModeOption(options, k.value?.toString(), v.value); |
| } |
| }); |
| } |
| } |
| |
| void _applyUnignorables(AnalysisOptionsImpl options, YamlNode? cannotIgnore) { |
| if (cannotIgnore is YamlList) { |
| var names = <String>{}; |
| var stringValues = cannotIgnore.whereType<String>().toSet(); |
| for (var severity in AnalyzerOptions.severities) { |
| if (stringValues.contains(severity)) { |
| // [severity] is a marker denoting all error codes with severity |
| // equal to [severity]. |
| stringValues.remove(severity); |
| // Replace name like 'error' with error codes with this named |
| // severity. |
| for (var e in errorCodeValues) { |
| // If the severity of [error] is also changed in this options file |
| // to be [severity], we add [error] to the un-ignorable list. |
| var processors = options.errorProcessors |
| .where((processor) => processor.code == e.name); |
| if (processors.isNotEmpty && |
| processors.first.severity?.displayName == severity) { |
| names.add(e.name); |
| continue; |
| } |
| // Otherwise, add [error] if its default severity is [severity]. |
| if (e.errorSeverity.displayName == severity) { |
| names.add(e.name); |
| } |
| } |
| } |
| } |
| names.addAll(stringValues.map((name) => name.toUpperCase())); |
| options.unignorableNames = names; |
| } |
| } |
| |
| CodeStyleOptions _buildCodeStyleOptions( |
| AnalysisOptionsImpl options, YamlNode? config) { |
| var useFormatter = false; |
| if (config is YamlMap) { |
| var formatNode = config.valueAt(AnalyzerOptions.format); |
| if (formatNode != null) { |
| var formatValue = toBool(formatNode); |
| if (formatValue is bool) { |
| useFormatter = formatValue; |
| } |
| } |
| } |
| return CodeStyleOptionsImpl(options, useFormatter: useFormatter); |
| } |
| |
| String? _toString(YamlNode? node) { |
| if (node is YamlScalar) { |
| var value = node.value; |
| if (value is String) { |
| return value; |
| } |
| } |
| return null; |
| } |
| } |