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 enable_super_mixins = 'enableSuperMixins'; | |
Brian Wilkerson
2015/10/29 20:24:03
Shouldn't we be using camel case for variable name
pquitslund
2015/10/29 21:06:18
Yes! Fixed.
| |
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> top_level = 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 [enable_super_mixins]; | |
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.top_level); | |
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.enable_super_mixins) { | |
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 |