OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library analyzer.src.task.options; | 5 library analyzer.src.task.options; |
6 | 6 |
7 import 'package:analyzer/analyzer.dart'; | 7 import 'package:analyzer/analyzer.dart'; |
8 import 'package:analyzer/plugin/options.dart'; | 8 import 'package:analyzer/plugin/options.dart'; |
9 import 'package:analyzer/source/analysis_options_provider.dart'; | 9 import 'package:analyzer/source/analysis_options_provider.dart'; |
10 import 'package:analyzer/src/generated/engine.dart'; | 10 import 'package:analyzer/src/generated/engine.dart'; |
11 import 'package:analyzer/src/generated/java_engine.dart'; | 11 import 'package:analyzer/src/generated/java_engine.dart'; |
12 import 'package:analyzer/src/generated/source.dart'; | 12 import 'package:analyzer/src/generated/source.dart'; |
| 13 import 'package:analyzer/src/generated/utilities_general.dart'; |
13 import 'package:analyzer/src/task/general.dart'; | 14 import 'package:analyzer/src/task/general.dart'; |
14 import 'package:analyzer/task/general.dart'; | 15 import 'package:analyzer/task/general.dart'; |
15 import 'package:analyzer/task/model.dart'; | 16 import 'package:analyzer/task/model.dart'; |
16 import 'package:source_span/source_span.dart'; | 17 import 'package:source_span/source_span.dart'; |
17 import 'package:yaml/yaml.dart'; | 18 import 'package:yaml/yaml.dart'; |
18 | 19 |
19 /// The errors produced while parsing `.analysis_options` files. | 20 /// The errors produced while parsing `.analysis_options` files. |
20 /// | 21 /// |
21 /// The list will be empty if there were no errors, but will not be `null`. | 22 /// The list will be empty if there were no errors, but will not be `null`. |
22 final ListResultDescriptor<AnalysisError> ANALYSIS_OPTIONS_ERRORS = | 23 final ListResultDescriptor<AnalysisError> ANALYSIS_OPTIONS_ERRORS = |
23 new ListResultDescriptor<AnalysisError>( | 24 new ListResultDescriptor<AnalysisError>( |
24 'ANALYSIS_OPTIONS_ERRORS', AnalysisError.NO_ERRORS); | 25 'ANALYSIS_OPTIONS_ERRORS', AnalysisError.NO_ERRORS); |
25 | 26 |
| 27 final _OptionsProcessor _processor = new _OptionsProcessor(); |
| 28 |
| 29 /// Configure this [context] based on configuration details specified in |
| 30 /// the given [options]. |
| 31 void configureContextOptions( |
| 32 AnalysisContext context, Map<String, YamlNode> options) => |
| 33 _processor.configure(context, options); |
| 34 |
26 /// `analyzer` analysis options constants. | 35 /// `analyzer` analysis options constants. |
27 class AnalyzerOptions { | 36 class AnalyzerOptions { |
28 static const String analyzer = 'analyzer'; | 37 static const String analyzer = 'analyzer'; |
| 38 static const String enableSuperMixins = 'enableSuperMixins'; |
29 static const String errors = 'errors'; | 39 static const String errors = 'errors'; |
30 static const String exclude = 'exclude'; | 40 static const String exclude = 'exclude'; |
| 41 static const String language = 'language'; |
31 static const String plugins = 'plugins'; | 42 static const String plugins = 'plugins'; |
32 static const String strong_mode = 'strong-mode'; | 43 static const String strong_mode = 'strong-mode'; |
33 | 44 |
34 /// Ways to say `ignore`. | 45 /// Ways to say `ignore`. |
35 static const List<String> ignoreSynonyms = const ['ignore', 'false']; | 46 static const List<String> ignoreSynonyms = const ['ignore', 'false']; |
36 | 47 |
37 /// Ways to say `include`. | 48 /// Ways to say `include`. |
38 static const List<String> includeSynonyms = const ['include', 'true']; | 49 static const List<String> includeSynonyms = const ['include', 'true']; |
39 | 50 |
| 51 /// Ways to say `true` or `false`. |
| 52 static const List<String> trueOrFalse = const ['true', 'false']; |
| 53 |
40 /// Supported top-level `analyzer` options. | 54 /// Supported top-level `analyzer` options. |
41 static const List<String> top_level = const [ | 55 static const List<String> topLevel = const [ |
42 errors, | 56 errors, |
43 exclude, | 57 exclude, |
| 58 language, |
44 plugins, | 59 plugins, |
45 strong_mode | 60 strong_mode |
46 ]; | 61 ]; |
| 62 |
| 63 /// Supported `analyzer` language configuration options. |
| 64 static const List<String> languageOptions = const [enableSuperMixins]; |
| 65 } |
| 66 |
| 67 /// Validates `analyzer` options. |
| 68 class AnalyzerOptionsValidator extends CompositeValidator { |
| 69 AnalyzerOptionsValidator() |
| 70 : super([ |
| 71 new TopLevelAnalyzerOptionsValidator(), |
| 72 new ErrorFilterOptionValidator(), |
| 73 new LanguageOptionValidator() |
| 74 ]); |
| 75 } |
| 76 |
| 77 /// Convenience class for composing validators. |
| 78 class CompositeValidator extends OptionsValidator { |
| 79 final List<OptionsValidator> validators; |
| 80 CompositeValidator(this.validators); |
| 81 |
| 82 @override |
| 83 void validate(ErrorReporter reporter, Map<String, YamlNode> options) => |
| 84 validators.forEach((v) => v.validate(reporter, options)); |
| 85 } |
| 86 |
| 87 /// Builds error reports with value proposals. |
| 88 class ErrorBuilder { |
| 89 String proposal; |
| 90 AnalysisOptionsWarningCode code; |
| 91 |
| 92 /// Create a builder for the given [supportedOptions]. |
| 93 ErrorBuilder(List<String> supportedOptions) { |
| 94 assert(supportedOptions != null && !supportedOptions.isEmpty); |
| 95 if (supportedOptions.length > 1) { |
| 96 proposal = StringUtilities.printListOfQuotedNames(supportedOptions); |
| 97 code = pluralProposalCode; |
| 98 } else { |
| 99 proposal = "'${supportedOptions.join()}'"; |
| 100 code = singularProposalCode; |
| 101 } |
| 102 } |
| 103 AnalysisOptionsWarningCode get pluralProposalCode => |
| 104 AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES; |
| 105 |
| 106 AnalysisOptionsWarningCode get singularProposalCode => |
| 107 AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUE; |
| 108 |
| 109 /// Report an unsupported [node] value, defined in the given [scopeName]. |
| 110 void reportError(ErrorReporter reporter, String scopeName, YamlNode node) { |
| 111 reporter.reportErrorForSpan( |
| 112 code, node.span, [scopeName, node.value, proposal]); |
| 113 } |
47 } | 114 } |
48 | 115 |
49 /// Validates `analyzer` error filter options. | 116 /// Validates `analyzer` error filter options. |
50 class ErrorFilterOptionValidator extends OptionsValidator { | 117 class ErrorFilterOptionValidator extends OptionsValidator { |
51 /// Pretty list of legal includes. | 118 /// Pretty list of legal includes. |
52 static final String legalIncludes = StringUtilities.printListOfQuotedNames( | 119 static final String legalIncludes = StringUtilities.printListOfQuotedNames( |
53 new List.from(AnalyzerOptions.ignoreSynonyms) | 120 new List.from(AnalyzerOptions.ignoreSynonyms) |
54 ..addAll(AnalyzerOptions.includeSynonyms)); | 121 ..addAll(AnalyzerOptions.includeSynonyms)); |
55 | 122 |
56 @override | 123 @override |
57 void validate(ErrorReporter reporter, Map<String, YamlNode> options) { | 124 void validate(ErrorReporter reporter, Map<String, YamlNode> options) { |
58 YamlMap analyzer = options[AnalyzerOptions.analyzer]; | 125 YamlMap analyzer = options[AnalyzerOptions.analyzer]; |
59 if (analyzer == null) { | 126 if (analyzer == null) { |
60 // No options for analyzer. | |
61 return; | 127 return; |
62 } | 128 } |
63 | 129 |
64 YamlNode filters = analyzer[AnalyzerOptions.errors]; | 130 YamlNode filters = analyzer[AnalyzerOptions.errors]; |
65 | |
66 if (filters is YamlMap) { | 131 if (filters is YamlMap) { |
67 String value; | 132 String value; |
68 filters.nodes.forEach((k, v) { | 133 filters.nodes.forEach((k, v) { |
69 if (k is YamlScalar) { | 134 if (k is YamlScalar) { |
70 value = k.value?.toString()?.toUpperCase(); | 135 value = toUpperCase(k.value); |
71 if (!ErrorCode.values.any((ErrorCode code) => code.name == value)) { | 136 if (!ErrorCode.values.any((ErrorCode code) => code.name == value)) { |
72 reporter.reportErrorForSpan( | 137 reporter.reportErrorForSpan( |
73 AnalysisOptionsWarningCode.UNRECOGNIZED_ERROR_CODE, | 138 AnalysisOptionsWarningCode.UNRECOGNIZED_ERROR_CODE, |
74 k.span, | 139 k.span, |
75 [k.value?.toString()]); | 140 [k.value?.toString()]); |
76 } | 141 } |
77 } | 142 } |
78 if (v is YamlScalar) { | 143 if (v is YamlScalar) { |
79 value = v.value?.toString()?.toLowerCase(); | 144 value = toLowerCase(v.value); |
80 if (!AnalyzerOptions.ignoreSynonyms.contains(value) && | 145 if (!AnalyzerOptions.ignoreSynonyms.contains(value) && |
81 !AnalyzerOptions.includeSynonyms.contains(value)) { | 146 !AnalyzerOptions.includeSynonyms.contains(value)) { |
82 reporter.reportErrorForSpan( | 147 reporter.reportErrorForSpan( |
83 AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES, | 148 AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES, |
84 v.span, | 149 v.span, |
85 [AnalyzerOptions.errors, v.value?.toString(), legalIncludes]); | 150 [AnalyzerOptions.errors, v.value?.toString(), legalIncludes]); |
86 } | 151 } |
87 | |
88 value = v.value?.toString()?.toLowerCase(); | |
89 } | 152 } |
90 }); | 153 }); |
91 } | 154 } |
92 } | 155 } |
93 } | 156 } |
94 | 157 |
95 /// Validates `analyzer` top-level options. | |
96 class TopLevelAnalyzerOptionsValidator extends TopLevelOptionValidator { | |
97 TopLevelAnalyzerOptionsValidator() | |
98 : super(AnalyzerOptions.analyzer, AnalyzerOptions.top_level); | |
99 } | |
100 | |
101 /// Validates `analyzer` options. | |
102 class AnalyzerOptionsValidator extends CompositeValidator { | |
103 AnalyzerOptionsValidator() | |
104 : super([ | |
105 new TopLevelAnalyzerOptionsValidator(), | |
106 new ErrorFilterOptionValidator() | |
107 ]); | |
108 } | |
109 | |
110 /// Convenience class for composing validators. | |
111 class CompositeValidator extends OptionsValidator { | |
112 final List<OptionsValidator> validators; | |
113 CompositeValidator(this.validators); | |
114 | |
115 @override | |
116 void validate(ErrorReporter reporter, Map<String, YamlNode> options) => | |
117 validators.forEach((v) => v.validate(reporter, options)); | |
118 } | |
119 | |
120 /// A task that generates errors for an `.analysis_options` file. | 158 /// A task that generates errors for an `.analysis_options` file. |
121 class GenerateOptionsErrorsTask extends SourceBasedAnalysisTask { | 159 class GenerateOptionsErrorsTask extends SourceBasedAnalysisTask { |
122 /// The name of the input whose value is the content of the file. | 160 /// The name of the input whose value is the content of the file. |
123 static const String CONTENT_INPUT_NAME = 'CONTENT_INPUT_NAME'; | 161 static const String CONTENT_INPUT_NAME = 'CONTENT_INPUT_NAME'; |
124 | 162 |
125 /// The task descriptor describing this kind of task. | 163 /// The task descriptor describing this kind of task. |
126 static final TaskDescriptor DESCRIPTOR = new TaskDescriptor( | 164 static final TaskDescriptor DESCRIPTOR = new TaskDescriptor( |
127 'GenerateOptionsErrorsTask', | 165 'GenerateOptionsErrorsTask', |
128 createTask, | 166 createTask, |
129 buildInputs, | 167 buildInputs, |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
177 List<int> lineStarts = StringUtilities.computeLineStarts(content); | 215 List<int> lineStarts = StringUtilities.computeLineStarts(content); |
178 return new LineInfo(lineStarts); | 216 return new LineInfo(lineStarts); |
179 } | 217 } |
180 | 218 |
181 /// Create a task based on the given [target] in the given [context]. | 219 /// Create a task based on the given [target] in the given [context]. |
182 static GenerateOptionsErrorsTask createTask( | 220 static GenerateOptionsErrorsTask createTask( |
183 AnalysisContext context, AnalysisTarget target) => | 221 AnalysisContext context, AnalysisTarget target) => |
184 new GenerateOptionsErrorsTask(context, target); | 222 new GenerateOptionsErrorsTask(context, target); |
185 } | 223 } |
186 | 224 |
| 225 /// Validates `analyzer` language configuration options. |
| 226 class LanguageOptionValidator extends OptionsValidator { |
| 227 ErrorBuilder builder = new ErrorBuilder(AnalyzerOptions.languageOptions); |
| 228 ErrorBuilder trueOrFalseBuilder = new TrueOrFalseValueErrorBuilder(); |
| 229 |
| 230 @override |
| 231 void validate(ErrorReporter reporter, Map<String, YamlNode> options) { |
| 232 YamlMap analyzer = options[AnalyzerOptions.analyzer]; |
| 233 if (analyzer == null) { |
| 234 return; |
| 235 } |
| 236 |
| 237 YamlNode language = analyzer[AnalyzerOptions.language]; |
| 238 if (language is YamlMap) { |
| 239 language.nodes.forEach((k, v) { |
| 240 String key, value; |
| 241 bool validKey = false; |
| 242 if (k is YamlScalar) { |
| 243 key = k.value?.toString(); |
| 244 if (!AnalyzerOptions.languageOptions.contains(key)) { |
| 245 builder.reportError(reporter, AnalyzerOptions.language, k); |
| 246 } else { |
| 247 // If we have a valid key, go on and check the value. |
| 248 validKey = true; |
| 249 } |
| 250 } |
| 251 if (validKey && v is YamlScalar) { |
| 252 value = toLowerCase(v.value); |
| 253 if (!AnalyzerOptions.trueOrFalse.contains(value)) { |
| 254 trueOrFalseBuilder.reportError(reporter, key, v); |
| 255 } |
| 256 } |
| 257 }); |
| 258 } |
| 259 } |
| 260 } |
| 261 |
187 /// Validates `linter` top-level options. | 262 /// Validates `linter` top-level options. |
188 /// TODO(pq): move into `linter` package and plugin. | 263 /// TODO(pq): move into `linter` package and plugin. |
189 class LinterOptionsValidator extends TopLevelOptionValidator { | 264 class LinterOptionsValidator extends TopLevelOptionValidator { |
190 LinterOptionsValidator() : super('linter', const ['rules']); | 265 LinterOptionsValidator() : super('linter', const ['rules']); |
191 } | 266 } |
192 | 267 |
193 /// Validates options defined in an `.analysis_options` file. | 268 /// Validates options defined in an `.analysis_options` file. |
194 class OptionsFileValidator { | 269 class OptionsFileValidator { |
195 // TODO(pq): move to an extension point. | 270 // TODO(pq): move to an extension point. |
196 final List<OptionsValidator> _validators = [ | 271 final List<OptionsValidator> _validators = [ |
197 new AnalyzerOptionsValidator(), | 272 new AnalyzerOptionsValidator(), |
198 new LinterOptionsValidator() | 273 new LinterOptionsValidator() |
199 ]; | 274 ]; |
200 | 275 |
201 final Source source; | 276 final Source source; |
202 OptionsFileValidator(this.source) { | 277 OptionsFileValidator(this.source) { |
203 _validators.addAll(AnalysisEngine.instance.optionsPlugin.optionsValidators); | 278 _validators.addAll(AnalysisEngine.instance.optionsPlugin.optionsValidators); |
204 } | 279 } |
205 | 280 |
206 List<AnalysisError> validate(Map<String, YamlNode> options) { | 281 List<AnalysisError> validate(Map<String, YamlNode> options) { |
207 RecordingErrorListener recorder = new RecordingErrorListener(); | 282 RecordingErrorListener recorder = new RecordingErrorListener(); |
208 ErrorReporter reporter = new ErrorReporter(recorder, source); | 283 ErrorReporter reporter = new ErrorReporter(recorder, source); |
209 _validators.forEach((OptionsValidator v) => v.validate(reporter, options)); | 284 _validators.forEach((OptionsValidator v) => v.validate(reporter, options)); |
210 return recorder.errors; | 285 return recorder.errors; |
211 } | 286 } |
212 } | 287 } |
213 | 288 |
| 289 /// Validates `analyzer` top-level options. |
| 290 class TopLevelAnalyzerOptionsValidator extends TopLevelOptionValidator { |
| 291 TopLevelAnalyzerOptionsValidator() |
| 292 : super(AnalyzerOptions.analyzer, AnalyzerOptions.topLevel); |
| 293 } |
| 294 |
214 /// Validates top-level options. For example, | 295 /// Validates top-level options. For example, |
215 /// plugin: | 296 /// plugin: |
216 /// top-level-option: true | 297 /// top-level-option: true |
217 class TopLevelOptionValidator extends OptionsValidator { | 298 class TopLevelOptionValidator extends OptionsValidator { |
218 final String pluginName; | 299 final String pluginName; |
219 final List<String> supportedOptions; | 300 final List<String> supportedOptions; |
220 String _valueProposal; | 301 String _valueProposal; |
221 AnalysisOptionsWarningCode _warningCode; | 302 AnalysisOptionsWarningCode _warningCode; |
222 TopLevelOptionValidator(this.pluginName, this.supportedOptions) { | 303 TopLevelOptionValidator(this.pluginName, this.supportedOptions) { |
223 assert(supportedOptions != null && !supportedOptions.isEmpty); | 304 assert(supportedOptions != null && !supportedOptions.isEmpty); |
(...skipping 17 matching lines...) Expand all Loading... |
241 if (!supportedOptions.contains(k.value)) { | 322 if (!supportedOptions.contains(k.value)) { |
242 reporter.reportErrorForSpan( | 323 reporter.reportErrorForSpan( |
243 _warningCode, k.span, [pluginName, k.value, _valueProposal]); | 324 _warningCode, k.span, [pluginName, k.value, _valueProposal]); |
244 } | 325 } |
245 } | 326 } |
246 //TODO(pq): consider an error if the node is not a Scalar. | 327 //TODO(pq): consider an error if the node is not a Scalar. |
247 }); | 328 }); |
248 } | 329 } |
249 } | 330 } |
250 } | 331 } |
| 332 |
| 333 /// An error-builder that knows about `true` and `false` legal values. |
| 334 class TrueOrFalseValueErrorBuilder extends ErrorBuilder { |
| 335 TrueOrFalseValueErrorBuilder() : super(AnalyzerOptions.trueOrFalse); |
| 336 @override |
| 337 AnalysisOptionsWarningCode get pluralProposalCode => |
| 338 AnalysisOptionsWarningCode.UNSUPPORTED_VALUE; |
| 339 } |
| 340 |
| 341 class _OptionsProcessor { |
| 342 void configure(AnalysisContext context, Map<String, YamlNode> options) { |
| 343 if (options == null) { |
| 344 return; |
| 345 } |
| 346 |
| 347 YamlMap analyzer = options[AnalyzerOptions.analyzer]; |
| 348 if (analyzer == null) { |
| 349 return; |
| 350 } |
| 351 |
| 352 // Set strong mode (default is false). |
| 353 bool strongMode = analyzer[AnalyzerOptions.strong_mode] ?? false; |
| 354 setStrongMode(context, strongMode); |
| 355 |
| 356 // Set filters. |
| 357 YamlNode filters = analyzer[AnalyzerOptions.errors]; |
| 358 setFilters(context, filters); |
| 359 |
| 360 // Process language options. |
| 361 YamlNode language = analyzer[AnalyzerOptions.language]; |
| 362 setLanguageOptions(context, language); |
| 363 } |
| 364 |
| 365 void setFilters(AnalysisContext context, YamlNode codes) { |
| 366 List<ErrorFilter> filters = <ErrorFilter>[]; |
| 367 // If codes are enumerated, collect them as filters; else leave filters |
| 368 // empty to overwrite previous value. |
| 369 if (codes is YamlMap) { |
| 370 String value; |
| 371 codes.nodes.forEach((k, v) { |
| 372 if (k is YamlScalar && v is YamlScalar) { |
| 373 value = toLowerCase(v.value); |
| 374 if (AnalyzerOptions.ignoreSynonyms.contains(value)) { |
| 375 // Case-insensitive. |
| 376 String code = toUpperCase(k.value); |
| 377 filters.add((AnalysisError error) => error.errorCode.name == code); |
| 378 } |
| 379 } |
| 380 }); |
| 381 } |
| 382 context.setConfigurationData(CONFIGURED_ERROR_FILTERS, filters); |
| 383 } |
| 384 |
| 385 void setLanguageOptions(AnalysisContext context, YamlNode configs) { |
| 386 if (configs is YamlMap) { |
| 387 configs.nodes.forEach((k, v) { |
| 388 String feature; |
| 389 if (k is YamlScalar && v is YamlScalar) { |
| 390 feature = k.value?.toString(); |
| 391 if (feature == AnalyzerOptions.enableSuperMixins) { |
| 392 if (isTrue(v.value)) { |
| 393 AnalysisOptionsImpl options = |
| 394 new AnalysisOptionsImpl.from(context.analysisOptions); |
| 395 options.enableSuperMixins = true; |
| 396 context.analysisOptions = options; |
| 397 } |
| 398 } |
| 399 } |
| 400 }); |
| 401 } |
| 402 } |
| 403 |
| 404 void setStrongMode(AnalysisContext context, bool strongMode) { |
| 405 if (context.analysisOptions.strongMode != strongMode) { |
| 406 AnalysisOptionsImpl options = |
| 407 new AnalysisOptionsImpl.from(context.analysisOptions); |
| 408 options.strongMode = strongMode; |
| 409 context.analysisOptions = options; |
| 410 } |
| 411 } |
| 412 } |
OLD | NEW |