Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(28)

Side by Side Diff: pkg/analyzer/lib/src/lint/linter.dart

Issue 2559773002: Reapply "Move the linter core to the analyzer package" (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « pkg/analyzer/lib/src/lint/io.dart ('k') | pkg/analyzer/lib/src/lint/project.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 import 'dart:io';
6
7 import 'package:analyzer/analyzer.dart';
8 import 'package:analyzer/dart/ast/token.dart';
9 import 'package:analyzer/src/generated/engine.dart'
10 show AnalysisErrorInfo, AnalysisErrorInfoImpl, Logger;
11 import 'package:analyzer/src/generated/java_engine.dart' show CaughtException;
12 import 'package:analyzer/src/generated/source.dart' show LineInfo;
13 import 'package:analyzer/src/generated/source_io.dart';
14 import 'package:analyzer/src/lint/analysis.dart';
15 import 'package:analyzer/src/lint/config.dart';
16 import 'package:analyzer/src/lint/io.dart';
17 import 'package:analyzer/src/lint/project.dart';
18 import 'package:analyzer/src/lint/pub.dart';
19 import 'package:analyzer/src/lint/registry.dart';
20 import 'package:analyzer/src/services/lint.dart' show Linter;
21 import 'package:glob/glob.dart';
22 import 'package:path/path.dart' as p;
23
24 typedef Printer(String msg);
25
26 /// Describes a String in valid camel case format.
27 class CamelCaseString {
28 static final _camelCaseMatcher = new RegExp(r'[A-Z][a-z]*');
29 static final _camelCaseTester = new RegExp(r'^([_$]*)([A-Z?$]+[a-z0-9]*)+$');
30
31 final String value;
32 CamelCaseString(this.value) {
33 if (!isCamelCase(value)) {
34 throw new ArgumentError('$value is not CamelCase');
35 }
36 }
37
38 String get humanized => _humanize(value);
39
40 @override
41 String toString() => value;
42
43 static bool isCamelCase(String name) => _camelCaseTester.hasMatch(name);
44
45 static String _humanize(String camelCase) =>
46 _camelCaseMatcher.allMatches(camelCase).map((m) => m.group(0)).join(' ');
47 }
48
49 /// Dart source linter.
50 class DartLinter implements AnalysisErrorListener {
51 final errors = <AnalysisError>[];
52
53 final LinterOptions options;
54 final Reporter reporter;
55
56 /// The total number of sources that were analyzed. Only valid after
57 /// [lintFiles] has been called.
58 int numSourcesAnalyzed;
59
60 /// Creates a new linter.
61 DartLinter(this.options, {this.reporter: const PrintingReporter()});
62
63 Iterable<AnalysisErrorInfo> lintFiles(List<File> files) {
64 List<AnalysisErrorInfo> errors = [];
65 var analysisDriver = new AnalysisDriver(options);
66 errors.addAll(analysisDriver.analyze(files.where((f) => isDartFile(f))));
67 numSourcesAnalyzed = analysisDriver.numSourcesAnalyzed;
68 files.where((f) => isPubspecFile(f)).forEach((p) {
69 numSourcesAnalyzed++;
70 return errors.addAll(_lintPubspecFile(p));
71 });
72 return errors;
73 }
74
75 Iterable<AnalysisErrorInfo> lintPubspecSource(
76 {String contents, String sourcePath}) {
77 var results = <AnalysisErrorInfo>[];
78
79 Uri sourceUrl = sourcePath == null ? null : p.toUri(sourcePath);
80
81 var spec = new Pubspec.parse(contents, sourceUrl: sourceUrl);
82
83 for (Linter lint in options.enabledLints) {
84 if (lint is LintRule) {
85 LintRule rule = lint;
86 var visitor = rule.getPubspecVisitor();
87 if (visitor != null) {
88 // Analyzer sets reporters; if this file is not being analyzed,
89 // we need to set one ourselves. (Needless to say, when pubspec
90 // processing gets pushed down, this hack can go away.)
91 if (rule.reporter == null && sourceUrl != null) {
92 var source = createSource(sourceUrl);
93 rule.reporter = new ErrorReporter(this, source);
94 }
95 try {
96 spec.accept(visitor);
97 } on Exception catch (e) {
98 reporter.exception(new LinterException(e.toString()));
99 }
100 if (rule._locationInfo != null && rule._locationInfo.isNotEmpty) {
101 results.addAll(rule._locationInfo);
102 rule._locationInfo.clear();
103 }
104 }
105 }
106 }
107
108 return results;
109 }
110
111 @override
112 onError(AnalysisError error) => errors.add(error);
113
114 Iterable<AnalysisErrorInfo> _lintPubspecFile(File sourceFile) =>
115 lintPubspecSource(
116 contents: sourceFile.readAsStringSync(), sourcePath: sourceFile.path);
117 }
118
119 class FileGlobFilter extends LintFilter {
120 Iterable<Glob> includes;
121 Iterable<Glob> excludes;
122
123 FileGlobFilter([Iterable<String> includeGlobs, Iterable<String> excludeGlobs])
124 : includes = includeGlobs.map((glob) => new Glob(glob)),
125 excludes = excludeGlobs.map((glob) => new Glob(glob));
126
127 @override
128 bool filter(AnalysisError lint) {
129 // TODO specify order
130 return excludes.any((glob) => glob.matches(lint.source.fullName)) &&
131 !includes.any((glob) => glob.matches(lint.source.fullName));
132 }
133 }
134
135 class Group implements Comparable<Group> {
136 /// Defined rule groups.
137 static const Group errors =
138 const Group._('errors', description: 'Possible coding errors.');
139 static const Group pub = const Group._('pub',
140 description: 'Pub-related rules.',
141 link: const Hyperlink('See the <strong>Pubspec Format</strong>',
142 'https://www.dartlang.org/tools/pub/pubspec.html'));
143 static const Group style = const Group._('style',
144 description:
145 'Matters of style, largely derived from the official Dart Style Guide. ',
146 link: const Hyperlink('See the <strong>Style Guide</strong>',
147 'https://www.dartlang.org/articles/style-guide/'));
148
149 /// List of builtin groups in presentation order.
150 static const Iterable<Group> builtin = const [errors, style, pub];
151
152 final String name;
153 final bool custom;
154 final String description;
155 final Hyperlink link;
156
157 factory Group(String name, {String description: '', Hyperlink link}) {
158 var n = name.toLowerCase();
159 return builtin.firstWhere((g) => g.name == n,
160 orElse: () => new Group._(name,
161 custom: true, description: description, link: link));
162 }
163
164 const Group._(this.name, {this.custom: false, this.description, this.link});
165
166 @override
167 int compareTo(Group other) => name.compareTo(other.name);
168 }
169
170 class Hyperlink {
171 final String label;
172 final String href;
173 final bool bold;
174 const Hyperlink(this.label, this.href, {this.bold: false});
175 String get html => '<a href="$href">${_emph(label)}</a>';
176 String _emph(msg) => bold ? '<strong>$msg</strong>' : msg;
177 }
178
179 /// Thrown when an error occurs in linting.
180 class LinterException implements Exception {
181 /// A message describing the error.
182 final String message;
183
184 /// Creates a new LinterException with an optional error [message].
185 const LinterException([this.message]);
186
187 @override
188 String toString() =>
189 message == null ? "LinterException" : "LinterException: $message";
190 }
191
192 /// Linter options.
193 class LinterOptions extends DriverOptions {
194 Iterable<LintRule> enabledLints;
195 LintFilter filter;
196 LinterOptions([this.enabledLints]) {
197 enabledLints ??= Registry.ruleRegistry;
198 }
199 void configure(LintConfig config) {
200 // TODO(pquitslund): revisit these default-to-on semantics.
201 enabledLints = Registry.ruleRegistry.where((LintRule rule) =>
202 !config.ruleConfigs.any((rc) => rc.disables(rule.name)));
203 filter = new FileGlobFilter(config.fileIncludes, config.fileExcludes);
204 }
205 }
206
207 /// Filtered lints are ommitted from linter output.
208 abstract class LintFilter {
209 bool filter(AnalysisError lint);
210 }
211
212 /// Describes a lint rule.
213 abstract class LintRule extends Linter implements Comparable<LintRule> {
214 /// Description (in markdown format) suitable for display in a detailed lint
215 /// description.
216 final String details;
217
218 /// Short description suitable for display in console output.
219 final String description;
220
221 /// Lint group (for example, 'style').
222 final Group group;
223
224 /// Lint maturity (stable|experimental).
225 final Maturity maturity;
226
227 /// Lint name.
228 @override
229 final String name;
230
231 /// Until pubspec analysis is pushed into the analyzer proper, we need to
232 /// do some extra book-keeping to keep track of details that will help us
233 /// constitute AnalysisErrorInfos.
234 final List<AnalysisErrorInfo> _locationInfo = <AnalysisErrorInfo>[];
235
236 LintRule(
237 {this.name,
238 this.group,
239 this.description,
240 this.details,
241 this.maturity: Maturity.stable});
242
243 LintCode get lintCode => new _LintCode(name, description);
244
245 @override
246 int compareTo(LintRule other) {
247 var g = group.compareTo(other.group);
248 if (g != 0) {
249 return g;
250 }
251 return name.compareTo(other.name);
252 }
253
254 /// Return a visitor to be passed to provide access to Dart project context
255 /// and to perform project-level analyses.
256 ProjectVisitor getProjectVisitor() => null;
257
258 /// Return a visitor to be passed to pubspecs to perform lint
259 /// analysis.
260 /// Lint errors are reported via this [Linter]'s error [reporter].
261 PubspecVisitor getPubspecVisitor() => null;
262
263 @override
264 AstVisitor getVisitor() => null;
265
266 void reportLint(AstNode node, {bool ignoreSyntheticNodes: true}) {
267 if (node != null && (!node.isSynthetic || !ignoreSyntheticNodes)) {
268 reporter.reportErrorForNode(lintCode, node, []);
269 }
270 }
271
272 void reportLintForToken(Token token, {bool ignoreSyntheticTokens: true}) {
273 if (token != null && (!token.isSynthetic || !ignoreSyntheticTokens)) {
274 reporter.reportErrorForToken(lintCode, token, []);
275 }
276 }
277
278 void reportPubLint(PSNode node) {
279 Source source = createSource(node.span.sourceUrl);
280
281 // Cache error and location info for creating AnalysisErrorInfos
282 // Note that error columns are 1-based
283 AnalysisError error = new AnalysisError(
284 source, node.span.start.column + 1, node.span.length, lintCode);
285 LineInfo lineInfo = new LineInfo.fromContent(source.contents.data);
286
287 _locationInfo.add(new AnalysisErrorInfoImpl([error], lineInfo));
288
289 // Then do the reporting
290 reporter?.reportError(error);
291 }
292 }
293
294 class Maturity implements Comparable<Maturity> {
295 static const Maturity stable = const Maturity._('stable', ordinal: 0);
296 static const Maturity experimental = const Maturity._('stable', ordinal: 1);
297
298 final String name;
299 final int ordinal;
300
301 factory Maturity(String name, {int ordinal}) {
302 switch (name.toLowerCase()) {
303 case 'stable':
304 return stable;
305 case 'experimental':
306 return experimental;
307 default:
308 return new Maturity._(name, ordinal: ordinal);
309 }
310 }
311
312 const Maturity._(this.name, {this.ordinal});
313
314 @override
315 int compareTo(Maturity other) => this.ordinal - other.ordinal;
316 }
317
318 class PrintingReporter implements Reporter, Logger {
319 final Printer _print;
320
321 const PrintingReporter([this._print = print]);
322
323 @override
324 void exception(LinterException exception) {
325 _print('EXCEPTION: $exception');
326 }
327
328 @override
329 void logError(String message, [CaughtException exception]) {
330 _print('ERROR: $message');
331 }
332
333 @override
334 void logInformation(String message, [CaughtException exception]) {
335 _print('INFO: $message');
336 }
337
338 @override
339 void warn(String message) {
340 _print('WARN: $message');
341 }
342 }
343
344 abstract class Reporter {
345 void exception(LinterException exception);
346 void warn(String message);
347 }
348
349 /// Linter implementation.
350 class SourceLinter implements DartLinter, AnalysisErrorListener {
351 @override
352 final errors = <AnalysisError>[];
353 @override
354 final LinterOptions options;
355 @override
356 final Reporter reporter;
357
358 @override
359 int numSourcesAnalyzed;
360
361 SourceLinter(this.options, {this.reporter: const PrintingReporter()});
362
363 @override
364 Iterable<AnalysisErrorInfo> lintFiles(List<File> files) {
365 List<AnalysisErrorInfo> errors = [];
366 var analysisDriver = new AnalysisDriver(options);
367 errors.addAll(analysisDriver.analyze(files.where((f) => isDartFile(f))));
368 numSourcesAnalyzed = analysisDriver.numSourcesAnalyzed;
369 files.where((f) => isPubspecFile(f)).forEach((p) {
370 numSourcesAnalyzed++;
371 return errors.addAll(_lintPubspecFile(p));
372 });
373 return errors;
374 }
375
376 @override
377 Iterable<AnalysisErrorInfo> lintPubspecSource(
378 {String contents, String sourcePath}) {
379 var results = <AnalysisErrorInfo>[];
380
381 Uri sourceUrl = sourcePath == null ? null : p.toUri(sourcePath);
382
383 var spec = new Pubspec.parse(contents, sourceUrl: sourceUrl);
384
385 for (Linter lint in options.enabledLints) {
386 if (lint is LintRule) {
387 LintRule rule = lint;
388 var visitor = rule.getPubspecVisitor();
389 if (visitor != null) {
390 // Analyzer sets reporters; if this file is not being analyzed,
391 // we need to set one ourselves. (Needless to say, when pubspec
392 // processing gets pushed down, this hack can go away.)
393 if (rule.reporter == null && sourceUrl != null) {
394 var source = createSource(sourceUrl);
395 rule.reporter = new ErrorReporter(this, source);
396 }
397 try {
398 spec.accept(visitor);
399 } on Exception catch (e) {
400 reporter.exception(new LinterException(e.toString()));
401 }
402 if (rule._locationInfo != null && rule._locationInfo.isNotEmpty) {
403 results.addAll(rule._locationInfo);
404 rule._locationInfo.clear();
405 }
406 }
407 }
408 }
409
410 return results;
411 }
412
413 @override
414 onError(AnalysisError error) => errors.add(error);
415
416 @override
417 Iterable<AnalysisErrorInfo> _lintPubspecFile(File sourceFile) =>
418 lintPubspecSource(
419 contents: sourceFile.readAsStringSync(), sourcePath: sourceFile.path);
420 }
421
422 class _LintCode extends LintCode {
423 static final registry = <String, LintCode>{};
424
425 factory _LintCode(String name, String message) => registry.putIfAbsent(
426 name + message, () => new _LintCode._(name, message));
427
428 _LintCode._(String name, String message) : super(name, message);
429 }
OLDNEW
« no previous file with comments | « pkg/analyzer/lib/src/lint/io.dart ('k') | pkg/analyzer/lib/src/lint/project.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698