Index: pkg/analyzer/lib/src/task/options.dart |
diff --git a/pkg/analyzer/lib/src/task/options.dart b/pkg/analyzer/lib/src/task/options.dart |
index fb24b0e47215d0cb05f50ad98b271aa13197130c..689bff2eebbd5b212bf0decb42562883051f97a4 100644 |
--- a/pkg/analyzer/lib/src/task/options.dart |
+++ b/pkg/analyzer/lib/src/task/options.dart |
@@ -10,6 +10,7 @@ import 'package:analyzer/source/analysis_options_provider.dart'; |
import 'package:analyzer/src/generated/engine.dart'; |
import 'package:analyzer/src/generated/java_engine.dart'; |
import 'package:analyzer/src/generated/source.dart'; |
+import 'package:analyzer/src/generated/utilities_general.dart'; |
import 'package:analyzer/src/task/general.dart'; |
import 'package:analyzer/task/general.dart'; |
import 'package:analyzer/task/model.dart'; |
@@ -23,11 +24,21 @@ final ListResultDescriptor<AnalysisError> ANALYSIS_OPTIONS_ERRORS = |
new ListResultDescriptor<AnalysisError>( |
'ANALYSIS_OPTIONS_ERRORS', AnalysisError.NO_ERRORS); |
+final _OptionsProcessor _processor = new _OptionsProcessor(); |
+ |
+/// Configure this [context] based on configuration details specified in |
+/// the given [options]. |
+void configureContextOptions( |
+ AnalysisContext context, Map<String, YamlNode> options) => |
+ _processor.configure(context, options); |
+ |
/// `analyzer` analysis options constants. |
class AnalyzerOptions { |
static const String analyzer = 'analyzer'; |
+ static const String enableSuperMixins = 'enableSuperMixins'; |
static const String errors = 'errors'; |
static const String exclude = 'exclude'; |
+ static const String language = 'language'; |
static const String plugins = 'plugins'; |
static const String strong_mode = 'strong-mode'; |
@@ -37,13 +48,69 @@ class AnalyzerOptions { |
/// Ways to say `include`. |
static const List<String> includeSynonyms = const ['include', 'true']; |
+ /// Ways to say `true` or `false`. |
+ static const List<String> trueOrFalse = const ['true', 'false']; |
+ |
/// Supported top-level `analyzer` options. |
- static const List<String> top_level = const [ |
+ static const List<String> topLevel = const [ |
errors, |
exclude, |
+ language, |
plugins, |
strong_mode |
]; |
+ |
+ /// Supported `analyzer` language configuration options. |
+ static const List<String> languageOptions = const [enableSuperMixins]; |
+} |
+ |
+/// Validates `analyzer` options. |
+class AnalyzerOptionsValidator extends CompositeValidator { |
+ AnalyzerOptionsValidator() |
+ : super([ |
+ new TopLevelAnalyzerOptionsValidator(), |
+ new ErrorFilterOptionValidator(), |
+ new LanguageOptionValidator() |
+ ]); |
+} |
+ |
+/// Convenience class for composing validators. |
+class CompositeValidator extends OptionsValidator { |
+ final List<OptionsValidator> validators; |
+ CompositeValidator(this.validators); |
+ |
+ @override |
+ void validate(ErrorReporter reporter, Map<String, YamlNode> options) => |
+ validators.forEach((v) => v.validate(reporter, options)); |
+} |
+ |
+/// Builds error reports with value proposals. |
+class ErrorBuilder { |
+ String proposal; |
+ AnalysisOptionsWarningCode code; |
+ |
+ /// Create a builder for the given [supportedOptions]. |
+ ErrorBuilder(List<String> supportedOptions) { |
+ assert(supportedOptions != null && !supportedOptions.isEmpty); |
+ if (supportedOptions.length > 1) { |
+ proposal = StringUtilities.printListOfQuotedNames(supportedOptions); |
+ code = pluralProposalCode; |
+ } else { |
+ proposal = "'${supportedOptions.join()}'"; |
+ code = singularProposalCode; |
+ } |
+ } |
+ AnalysisOptionsWarningCode get pluralProposalCode => |
+ AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES; |
+ |
+ AnalysisOptionsWarningCode get singularProposalCode => |
+ AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUE; |
+ |
+ /// Report an unsupported [node] value, defined in the given [scopeName]. |
+ void reportError(ErrorReporter reporter, String scopeName, YamlNode node) { |
+ reporter.reportErrorForSpan( |
+ code, node.span, [scopeName, node.value, proposal]); |
+ } |
} |
/// Validates `analyzer` error filter options. |
@@ -57,17 +124,15 @@ class ErrorFilterOptionValidator extends OptionsValidator { |
void validate(ErrorReporter reporter, Map<String, YamlNode> options) { |
YamlMap analyzer = options[AnalyzerOptions.analyzer]; |
if (analyzer == null) { |
- // No options for analyzer. |
return; |
} |
YamlNode filters = analyzer[AnalyzerOptions.errors]; |
- |
if (filters is YamlMap) { |
String value; |
filters.nodes.forEach((k, v) { |
if (k is YamlScalar) { |
- value = k.value?.toString()?.toUpperCase(); |
+ value = toUpperCase(k.value); |
if (!ErrorCode.values.any((ErrorCode code) => code.name == value)) { |
reporter.reportErrorForSpan( |
AnalysisOptionsWarningCode.UNRECOGNIZED_ERROR_CODE, |
@@ -76,7 +141,7 @@ class ErrorFilterOptionValidator extends OptionsValidator { |
} |
} |
if (v is YamlScalar) { |
- value = v.value?.toString()?.toLowerCase(); |
+ value = toLowerCase(v.value); |
if (!AnalyzerOptions.ignoreSynonyms.contains(value) && |
!AnalyzerOptions.includeSynonyms.contains(value)) { |
reporter.reportErrorForSpan( |
@@ -84,39 +149,12 @@ class ErrorFilterOptionValidator extends OptionsValidator { |
v.span, |
[AnalyzerOptions.errors, v.value?.toString(), legalIncludes]); |
} |
- |
- value = v.value?.toString()?.toLowerCase(); |
} |
}); |
} |
} |
} |
-/// Validates `analyzer` top-level options. |
-class TopLevelAnalyzerOptionsValidator extends TopLevelOptionValidator { |
- TopLevelAnalyzerOptionsValidator() |
- : super(AnalyzerOptions.analyzer, AnalyzerOptions.top_level); |
-} |
- |
-/// Validates `analyzer` options. |
-class AnalyzerOptionsValidator extends CompositeValidator { |
- AnalyzerOptionsValidator() |
- : super([ |
- new TopLevelAnalyzerOptionsValidator(), |
- new ErrorFilterOptionValidator() |
- ]); |
-} |
- |
-/// Convenience class for composing validators. |
-class CompositeValidator extends OptionsValidator { |
- final List<OptionsValidator> validators; |
- CompositeValidator(this.validators); |
- |
- @override |
- void validate(ErrorReporter reporter, Map<String, YamlNode> options) => |
- validators.forEach((v) => v.validate(reporter, options)); |
-} |
- |
/// A task that generates errors for an `.analysis_options` file. |
class GenerateOptionsErrorsTask extends SourceBasedAnalysisTask { |
/// The name of the input whose value is the content of the file. |
@@ -184,6 +222,43 @@ class GenerateOptionsErrorsTask extends SourceBasedAnalysisTask { |
new GenerateOptionsErrorsTask(context, target); |
} |
+/// Validates `analyzer` language configuration options. |
+class LanguageOptionValidator extends OptionsValidator { |
+ ErrorBuilder builder = new ErrorBuilder(AnalyzerOptions.languageOptions); |
+ ErrorBuilder trueOrFalseBuilder = new TrueOrFalseValueErrorBuilder(); |
+ |
+ @override |
+ void validate(ErrorReporter reporter, Map<String, YamlNode> options) { |
+ YamlMap analyzer = options[AnalyzerOptions.analyzer]; |
+ if (analyzer == null) { |
+ return; |
+ } |
+ |
+ YamlNode language = analyzer[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.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); |
+ if (!AnalyzerOptions.trueOrFalse.contains(value)) { |
+ trueOrFalseBuilder.reportError(reporter, key, v); |
+ } |
+ } |
+ }); |
+ } |
+ } |
+} |
+ |
/// Validates `linter` top-level options. |
/// TODO(pq): move into `linter` package and plugin. |
class LinterOptionsValidator extends TopLevelOptionValidator { |
@@ -211,6 +286,12 @@ class OptionsFileValidator { |
} |
} |
+/// 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 |
@@ -248,3 +329,84 @@ class TopLevelOptionValidator extends OptionsValidator { |
} |
} |
} |
+ |
+/// An error-builder that knows about `true` and `false` legal values. |
+class TrueOrFalseValueErrorBuilder extends ErrorBuilder { |
+ TrueOrFalseValueErrorBuilder() : super(AnalyzerOptions.trueOrFalse); |
+ @override |
+ AnalysisOptionsWarningCode get pluralProposalCode => |
+ AnalysisOptionsWarningCode.UNSUPPORTED_VALUE; |
+} |
+ |
+class _OptionsProcessor { |
+ void configure(AnalysisContext context, Map<String, YamlNode> options) { |
+ if (options == null) { |
+ return; |
+ } |
+ |
+ YamlMap analyzer = options[AnalyzerOptions.analyzer]; |
+ if (analyzer == null) { |
+ return; |
+ } |
+ |
+ // Set strong mode (default is false). |
+ bool strongMode = analyzer[AnalyzerOptions.strong_mode] ?? false; |
+ setStrongMode(context, strongMode); |
+ |
+ // Set filters. |
+ YamlNode filters = analyzer[AnalyzerOptions.errors]; |
+ setFilters(context, filters); |
+ |
+ // Process language options. |
+ YamlNode language = analyzer[AnalyzerOptions.language]; |
+ setLanguageOptions(context, language); |
+ } |
+ |
+ void setFilters(AnalysisContext context, YamlNode codes) { |
+ List<ErrorFilter> filters = <ErrorFilter>[]; |
+ // If codes are enumerated, collect them as filters; else leave filters |
+ // empty to overwrite previous value. |
+ if (codes is YamlMap) { |
+ String value; |
+ codes.nodes.forEach((k, v) { |
+ if (k is YamlScalar && v is YamlScalar) { |
+ value = toLowerCase(v.value); |
+ if (AnalyzerOptions.ignoreSynonyms.contains(value)) { |
+ // Case-insensitive. |
+ String code = toUpperCase(k.value); |
+ filters.add((AnalysisError error) => error.errorCode.name == code); |
+ } |
+ } |
+ }); |
+ } |
+ context.setConfigurationData(CONFIGURED_ERROR_FILTERS, filters); |
+ } |
+ |
+ void setLanguageOptions(AnalysisContext context, YamlNode configs) { |
+ if (configs is YamlMap) { |
+ configs.nodes.forEach((k, v) { |
+ String feature; |
+ if (k is YamlScalar && v is YamlScalar) { |
+ feature = k.value?.toString(); |
+ if (feature == AnalyzerOptions.enableSuperMixins) { |
+ if (isTrue(v.value)) { |
+ AnalysisOptionsImpl options = |
+ new AnalysisOptionsImpl.from(context.analysisOptions); |
+ options.enableSuperMixins = true; |
+ context.analysisOptions = options; |
+ } |
+ } |
+ } |
+ }); |
+ } |
+ } |
+ |
+ void setStrongMode(AnalysisContext context, bool strongMode) { |
+ if (context.analysisOptions.strongMode != strongMode) { |
+ AnalysisOptionsImpl options = |
+ new AnalysisOptionsImpl.from(context.analysisOptions); |
+ options.strongMode = strongMode; |
+ context.analysisOptions = options; |
+ } |
+ } |
+} |