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

Side by Side Diff: pkg/analyzer_cli/lib/src/error_formatter.dart

Issue 2793813002: Fix an issue with duplicate reported analysis errors. (Closed)
Patch Set: Created 3 years, 8 months 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_cli/lib/src/driver.dart ('k') | pkg/analyzer_cli/lib/src/error_severity.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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_cli.src.error_formatter; 5 library analyzer_cli.src.error_formatter;
6 6
7 import 'package:analyzer/error/error.dart'; 7 import 'package:analyzer/error/error.dart';
8 import 'package:analyzer/src/generated/engine.dart'; 8 import 'package:analyzer/src/generated/engine.dart';
9 import 'package:analyzer/src/generated/source.dart'; 9 import 'package:analyzer/src/generated/source.dart';
10 import 'package:analyzer_cli/src/ansi.dart'; 10 import 'package:analyzer_cli/src/ansi.dart';
11 import 'package:analyzer_cli/src/options.dart'; 11 import 'package:analyzer_cli/src/options.dart';
12 import 'package:path/path.dart' as path; 12 import 'package:path/path.dart' as path;
13 13
14 /// Returns the given error's severity. 14 /// Returns the given error's severity.
15 ProcessedSeverity _identity(AnalysisError error) => 15 ProcessedSeverity _severityIdentity(AnalysisError error) =>
16 new ProcessedSeverity(error.errorCode.errorSeverity); 16 new ProcessedSeverity(error.errorCode.errorSeverity);
17 17
18 String _pluralize(String word, int count) => count == 1 ? word : word + "s"; 18 String _pluralize(String word, int count) => count == 1 ? word : word + "s";
19 19
20 /// Returns desired severity for the given [error] (or `null` if it's to be 20 /// Returns desired severity for the given [error] (or `null` if it's to be
21 /// suppressed). 21 /// suppressed).
22 typedef ProcessedSeverity _SeverityProcessor(AnalysisError error); 22 typedef ProcessedSeverity SeverityProcessor(AnalysisError error);
23 23
24 /// Analysis statistics counter. 24 /// Analysis statistics counter.
25 class AnalysisStats { 25 class AnalysisStats {
26 /// The total number of diagnostics sent to [formatErrors]. 26 /// The total number of diagnostics sent to [formatErrors].
27 int unfilteredCount; 27 int unfilteredCount = 0;
28 28
29 int errorCount; 29 int errorCount = 0;
30 int hintCount; 30 int hintCount = 0;
31 int lintCount; 31 int lintCount = 0;
32 int warnCount; 32 int warnCount = 0;
33 33
34 AnalysisStats() { 34 AnalysisStats();
35 init();
36 }
37 35
38 /// The total number of diagnostics reported to the user. 36 /// The total number of diagnostics reported to the user.
39 int get filteredCount => errorCount + warnCount + hintCount + lintCount; 37 int get filteredCount => errorCount + warnCount + hintCount + lintCount;
40 38
41 /// (Re)set initial values.
42 void init() {
43 unfilteredCount = 0;
44 errorCount = 0;
45 hintCount = 0;
46 lintCount = 0;
47 warnCount = 0;
48 }
49
50 /// Print statistics to [out]. 39 /// Print statistics to [out].
51 void print(StringSink out) { 40 void print(StringSink out) {
52 var hasErrors = errorCount != 0; 41 bool hasErrors = errorCount != 0;
53 var hasWarns = warnCount != 0; 42 bool hasWarns = warnCount != 0;
54 var hasHints = hintCount != 0; 43 bool hasHints = hintCount != 0;
55 var hasLints = lintCount != 0; 44 bool hasLints = lintCount != 0;
56 bool hasContent = false; 45 bool hasContent = false;
57 if (hasErrors) { 46 if (hasErrors) {
58 out.write(errorCount); 47 out.write(errorCount);
59 out.write(' '); 48 out.write(' ');
60 out.write(_pluralize("error", errorCount)); 49 out.write(_pluralize("error", errorCount));
61 hasContent = true; 50 hasContent = true;
62 } 51 }
63 if (hasWarns) { 52 if (hasWarns) {
64 if (hasContent) { 53 if (hasContent) {
65 if (!hasHints && !hasLints) { 54 if (!hasHints && !hasLints) {
66 out.write(' and '); 55 out.write(' and ');
67 } else { 56 } else {
68 out.write(", "); 57 out.write(", ");
69 } 58 }
70 } 59 }
71 out.write(warnCount); 60 out.write(warnCount);
72 out.write(' '); 61 out.write(' ');
73 out.write(_pluralize("warning", warnCount)); 62 out.write(_pluralize("warning", warnCount));
74 hasContent = true; 63 hasContent = true;
75 } 64 }
65 if (hasLints) {
66 if (hasContent) {
67 out.write(hasHints ? ', ' : ' and ');
68 }
69 out.write(lintCount);
70 out.write(' ');
71 out.write(_pluralize("lint", lintCount));
72 hasContent = true;
73 }
76 if (hasHints) { 74 if (hasHints) {
77 if (hasContent) { 75 if (hasContent) {
78 if (!hasLints) { 76 out.write(" and ");
79 out.write(' and ');
80 } else {
81 out.write(", ");
82 }
83 } 77 }
84 out.write(hintCount); 78 out.write(hintCount);
85 out.write(' '); 79 out.write(' ');
86 out.write(_pluralize("hint", hintCount)); 80 out.write(_pluralize("hint", hintCount));
87 hasContent = true; 81 hasContent = true;
88 } 82 }
89 if (hasLints) {
90 if (hasContent) {
91 out.write(" and ");
92 }
93 out.write(lintCount);
94 out.write(' ');
95 out.write(_pluralize("lint", lintCount));
96 hasContent = true;
97 }
98 if (hasContent) { 83 if (hasContent) {
99 out.writeln(" found."); 84 out.writeln(" found.");
100 } else { 85 } else {
101 out.writeln("No issues found!"); 86 out.writeln("No issues found!");
102 } 87 }
103 } 88 }
104 } 89 }
105 90
106 /// Helper for formatting [AnalysisError]s. 91 /// Helper for formatting [AnalysisError]s.
107 /// 92 ///
108 /// The two format options are a user consumable format and a machine consumable 93 /// The two format options are a user consumable format and a machine consumable
109 /// format. 94 /// format.
110 class ErrorFormatter { 95 abstract class ErrorFormatter {
96 final StringSink out;
97 final CommandLineOptions options;
98 final AnalysisStats stats;
99 SeverityProcessor _severityProcessor;
100
101 ErrorFormatter(this.out, this.options, this.stats,
102 {SeverityProcessor severityProcessor}) {
103 _severityProcessor =
104 severityProcessor == null ? _severityIdentity : severityProcessor;
105 }
106
107 /// Compute the severity for this [error] or `null` if this error should be
108 /// filtered.
109 ErrorSeverity _computeSeverity(AnalysisError error) =>
110 _severityProcessor(error)?.severity;
111
112 void formatErrors(List<AnalysisErrorInfo> errorInfos) {
113 stats.unfilteredCount += errorInfos.length;
114
115 List<AnalysisError> errors = new List<AnalysisError>();
116 Map<AnalysisError, LineInfo> errorToLine =
117 new Map<AnalysisError, LineInfo>();
118 for (AnalysisErrorInfo errorInfo in errorInfos) {
119 for (AnalysisError error in errorInfo.errors) {
120 if (_computeSeverity(error) != null) {
121 errors.add(error);
122 errorToLine[error] = errorInfo.lineInfo;
123 }
124 }
125 }
126
127 for (AnalysisError error in errors) {
128 formatError(errorToLine, error);
129 }
130 }
131
132 void formatError(
133 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error);
134
135 /// Call to write any batched up errors from [formatErrors].
136 void flush();
137 }
138
139 class MachineErrorFormatter extends ErrorFormatter {
111 static final int _pipeCodeUnit = '|'.codeUnitAt(0); 140 static final int _pipeCodeUnit = '|'.codeUnitAt(0);
112 static final int _slashCodeUnit = '\\'.codeUnitAt(0); 141 static final int _slashCodeUnit = '\\'.codeUnitAt(0);
113 static final int _newline = '\n'.codeUnitAt(0); 142 static final int _newline = '\n'.codeUnitAt(0);
114 static final int _return = '\r'.codeUnitAt(0); 143 static final int _return = '\r'.codeUnitAt(0);
115 144
116 final StringSink out; 145 MachineErrorFormatter(
117 final CommandLineOptions options; 146 StringSink out, CommandLineOptions options, AnalysisStats stats,
118 final AnalysisStats stats; 147 {SeverityProcessor severityProcessor})
119 148 : super(out, options, stats, severityProcessor: severityProcessor);
120 final _SeverityProcessor processSeverity;
121
122 AnsiLogger ansi;
123
124 ErrorFormatter(this.out, this.options, this.stats,
125 [this.processSeverity = _identity]) {
126 ansi = new AnsiLogger(this.options.color);
127 }
128
129 /// Compute the severity for this [error] or `null` if this error should be
130 /// filtered.
131 ErrorSeverity computeSeverity(AnalysisError error) =>
132 processSeverity(error)?.severity;
133 149
134 void formatError( 150 void formatError(
135 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) { 151 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) {
136 Source source = error.source; 152 Source source = error.source;
137 LineInfo_Location location = errorToLine[error].getLocation(error.offset); 153 LineInfo_Location location = errorToLine[error].getLocation(error.offset);
138 int length = error.length; 154 int length = error.length;
139 155
140 ProcessedSeverity processedSeverity = processSeverity(error); 156 ProcessedSeverity processedSeverity = _severityProcessor(error);
141 ErrorSeverity severity = processedSeverity.severity; 157 ErrorSeverity severity = processedSeverity.severity;
142 158
143 if (options.machineFormat) { 159 if (!processedSeverity.overridden) {
144 if (!processedSeverity.overridden) { 160 if (severity == ErrorSeverity.WARNING && options.warningsAreFatal) {
145 if (severity == ErrorSeverity.WARNING && options.warningsAreFatal) { 161 severity = ErrorSeverity.ERROR;
146 severity = ErrorSeverity.ERROR;
147 }
148 }
149 out.write(severity);
150 out.write('|');
151 out.write(error.errorCode.type);
152 out.write('|');
153 out.write(error.errorCode.name);
154 out.write('|');
155 out.write(escapeForMachineMode(source.fullName));
156 out.write('|');
157 out.write(location.lineNumber);
158 out.write('|');
159 out.write(location.columnNumber);
160 out.write('|');
161 out.write(length);
162 out.write('|');
163 out.write(escapeForMachineMode(error.message));
164 out.writeln();
165 } else {
166 // Get display name.
167 String errorType = severity.displayName;
168
169 // Translate INFOs into LINTS and HINTS.
170 if (severity == ErrorSeverity.INFO) {
171 if (error.errorCode.type == ErrorType.HINT ||
172 error.errorCode.type == ErrorType.LINT) {
173 errorType = error.errorCode.type.displayName;
174 }
175 }
176
177 final int errLength = ErrorSeverity.WARNING.displayName.length;
178 final int indent = errLength + 5;
179
180 // warning • 'foo' is not a bar at lib/foo.dart:1:2 • foo_warning
181 String message = error.message;
182 // Remove any terminating '.' from the end of the message.
183 if (message.endsWith('.')) {
184 message = message.substring(0, message.length - 1);
185 }
186 String issueColor =
187 (severity == ErrorSeverity.ERROR || severity == ErrorSeverity.WARNING)
188 ? ansi.red
189 : '';
190 out.write(' $issueColor${errorType.padLeft(errLength)}${ansi.none} '
191 '${ansi.bullet} ${ansi.bold}$message${ansi.none} ');
192 String sourceName;
193 if (source.uriKind == UriKind.DART_URI) {
194 sourceName = source.uri.toString();
195 } else if (source.uriKind == UriKind.PACKAGE_URI) {
196 sourceName = _relative(source.fullName);
197 if (sourceName == source.fullName) {
198 // If we weren't able to shorten the path name, use the package: versi on.
199 sourceName = source.uri.toString();
200 }
201 } else {
202 sourceName = _relative(source.fullName);
203 }
204 out.write('at $sourceName');
205 out.write(':${location.lineNumber}:${location.columnNumber} ');
206 out.write('${ansi.bullet} ${error.errorCode.name.toLowerCase()}');
207 out.writeln();
208
209 // If verbose, also print any associated correction.
210 if (options.verbose && error.correction != null) {
211 out.writeln('${' '.padLeft(indent)}${error.correction}');
212 } 162 }
213 } 163 }
164
165 if (severity == ErrorSeverity.ERROR) {
166 stats.errorCount++;
167 } else if (severity == ErrorSeverity.WARNING) {
168 // Only treat a warning as an error if it's not been set by a processor.
169 if (!processedSeverity.overridden && options.warningsAreFatal) {
170 stats.errorCount++;
171 } else {
172 stats.warnCount++;
173 }
174 } else if (error.errorCode.type == ErrorType.HINT) {
175 stats.hintCount++;
176 } else if (error.errorCode.type == ErrorType.LINT) {
177 stats.lintCount++;
178 }
179
180 out.write(severity);
181 out.write('|');
182 out.write(error.errorCode.type);
183 out.write('|');
184 out.write(error.errorCode.name);
185 out.write('|');
186 out.write(_escapeForMachineMode(source.fullName));
187 out.write('|');
188 out.write(location.lineNumber);
189 out.write('|');
190 out.write(location.columnNumber);
191 out.write('|');
192 out.write(length);
193 out.write('|');
194 out.write(_escapeForMachineMode(error.message));
195 out.writeln();
214 } 196 }
215 197
216 void formatErrors(List<AnalysisErrorInfo> errorInfos) { 198 static String _escapeForMachineMode(String input) {
217 stats.unfilteredCount += errorInfos.length;
218
219 var errors = new List<AnalysisError>();
220 var errorToLine = new Map<AnalysisError, LineInfo>();
221 for (AnalysisErrorInfo errorInfo in errorInfos) {
222 for (AnalysisError error in errorInfo.errors) {
223 if (computeSeverity(error) != null) {
224 errors.add(error);
225 errorToLine[error] = errorInfo.lineInfo;
226 }
227 }
228 }
229 // Sort errors.
230 errors.sort((AnalysisError error1, AnalysisError error2) {
231 // Severity.
232 ErrorSeverity severity1 = computeSeverity(error1);
233 ErrorSeverity severity2 = computeSeverity(error2);
234 int compare = severity2.compareTo(severity1);
235 if (compare != 0) {
236 return compare;
237 }
238 // Path.
239 compare = Comparable.compare(error1.source.fullName.toLowerCase(),
240 error2.source.fullName.toLowerCase());
241 if (compare != 0) {
242 return compare;
243 }
244 // Offset.
245 return error1.offset - error2.offset;
246 });
247 // Format errors.
248 for (AnalysisError error in errors) {
249 ProcessedSeverity processedSeverity = processSeverity(error);
250 ErrorSeverity severity = processedSeverity.severity;
251 if (severity == ErrorSeverity.ERROR) {
252 stats.errorCount++;
253 } else if (severity == ErrorSeverity.WARNING) {
254 // Only treat a warning as an error if it's not been set by a processor.
255 if (!processedSeverity.overridden && options.warningsAreFatal) {
256 stats.errorCount++;
257 } else {
258 stats.warnCount++;
259 }
260 } else if (error.errorCode.type == ErrorType.HINT) {
261 stats.hintCount++;
262 } else if (error.errorCode.type == ErrorType.LINT) {
263 stats.lintCount++;
264 }
265 formatError(errorToLine, error);
266 }
267 }
268
269 static String escapeForMachineMode(String input) {
270 StringBuffer result = new StringBuffer(); 199 StringBuffer result = new StringBuffer();
271 for (int c in input.codeUnits) { 200 for (int c in input.codeUnits) {
272 if (c == _newline) { 201 if (c == _newline) {
273 result.write(r'\n'); 202 result.write(r'\n');
274 } else if (c == _return) { 203 } else if (c == _return) {
275 result.write(r'\r'); 204 result.write(r'\r');
276 } else { 205 } else {
277 if (c == _slashCodeUnit || c == _pipeCodeUnit) { 206 if (c == _slashCodeUnit || c == _pipeCodeUnit) {
278 result.write('\\'); 207 result.write('\\');
279 } 208 }
280 result.writeCharCode(c); 209 result.writeCharCode(c);
281 } 210 }
282 } 211 }
283 return result.toString(); 212 return result.toString();
284 } 213 }
214
215 void flush() {}
216 }
217
218 class HumanErrorFormatter extends ErrorFormatter {
219 AnsiLogger ansi;
220
221 List<CLIError> batchedErrors = [];
danrubel 2017/04/03 15:32:03 Is there a reason to batch errors first and then d
devoncarew 2017/04/03 15:36:10 Making it a Set would be better :) I'll do that.
222
223 HumanErrorFormatter(
224 StringSink out, CommandLineOptions options, AnalysisStats stats,
225 {SeverityProcessor severityProcessor})
226 : super(out, options, stats, severityProcessor: severityProcessor) {
227 ansi = new AnsiLogger(this.options.color);
228 }
229
230 void formatError(
231 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) {
232 Source source = error.source;
233 LineInfo_Location location = errorToLine[error].getLocation(error.offset);
234
235 ProcessedSeverity processedSeverity = _severityProcessor(error);
236 ErrorSeverity severity = processedSeverity.severity;
237
238 // Get display name; translate INFOs into LINTS and HINTS.
239 String errorType = severity.displayName;
240 if (severity == ErrorSeverity.INFO) {
241 if (error.errorCode.type == ErrorType.HINT ||
242 error.errorCode.type == ErrorType.LINT) {
243 errorType = error.errorCode.type.displayName;
244 }
245 }
246
247 // warning • 'foo' is not a bar at lib/foo.dart:1:2 • foo_warning
248 String message = error.message;
249 // Remove any terminating '.' from the end of the message.
250 if (message.endsWith('.')) {
251 message = message.substring(0, message.length - 1);
252 }
253 String sourcePath;
254 if (source.uriKind == UriKind.DART_URI) {
255 sourcePath = source.uri.toString();
256 } else if (source.uriKind == UriKind.PACKAGE_URI) {
257 sourcePath = _relative(source.fullName);
258 if (sourcePath == source.fullName) {
259 // If we weren't able to shorten the path name, use the package: version .
260 sourcePath = source.uri.toString();
261 }
262 } else {
263 sourcePath = _relative(source.fullName);
264 }
265
266 batchedErrors.add(new CLIError(
267 severity: errorType,
268 sourcePath: sourcePath,
269 offset: error.offset,
270 line: location.lineNumber,
271 column: location.columnNumber,
272 message: message,
273 errorCode: error.errorCode.name.toLowerCase(),
274 correction: error.correction,
275 ));
276 }
277
278 void flush() {
279 // de-dup CLI errors
280 Set<CLIError> uniqueErrors = new Set.from(batchedErrors);
281
282 // sort
283 List<CLIError> sortedErrors = uniqueErrors.toList()..sort();
284
285 // print
286 for (CLIError error in sortedErrors) {
287 if (error.isError) {
288 stats.errorCount++;
289 } else if (error.isWarning) {
290 stats.warnCount++;
291 } else if (error.isLint) {
292 stats.lintCount++;
293 } else if (error.isHint) {
294 stats.hintCount++;
295 }
296
297 // warning • 'foo' is not a bar at lib/foo.dart:1:2 • foo_warning
298 String issueColor = (error.isError == ErrorSeverity.ERROR ||
299 error.isWarning == ErrorSeverity.WARNING)
300 ? ansi.red
301 : '';
302 out.write(' $issueColor${error.severity}${ansi.none} '
303 '${ansi.bullet} ${ansi.bold}${error.message}${ansi.none} ');
304 out.write('at ${error.sourcePath}');
305 out.write(':${error.line}:${error.column} ');
306 out.write('${ansi.bullet} ${error.errorCode}');
307 out.writeln();
308
309 // If verbose, also print any associated correction.
310 if (options.verbose && error.correction != null) {
311 out.writeln(
312 '${' '.padLeft(error.severity.length + 2)}${error.correction}');
313 }
314 }
315
316 batchedErrors.clear();
317 }
318 }
319
320 final Map<String, int> _severityCompare = {
321 'error': 5,
322 'warning': 4,
323 'info': 3,
324 'lint': 2,
325 'hint': 1,
326 };
327
328 /// An [AnalysisError] with line and column information.
329 class CLIError implements Comparable<CLIError> {
330 final String severity;
331 final String sourcePath;
332 final int offset;
333 final int line;
334 final int column;
335 final String message;
336 final String errorCode;
337 final String correction;
338
339 CLIError({
340 this.severity,
341 this.sourcePath,
342 this.offset,
343 this.line,
344 this.column,
345 this.message,
346 this.errorCode,
347 this.correction,
348 });
349
350 bool get isError => severity == 'error';
351 bool get isWarning => severity == 'warning';
352 bool get isLint => severity == 'lint';
353 bool get isHint => severity == 'hint';
354
355 @override
356 int get hashCode =>
357 severity.hashCode ^ sourcePath.hashCode ^ errorCode.hashCode ^ offset;
358
359 @override
360 bool operator ==(other) {
361 if (other is! CLIError) return false;
362
363 return severity == other.severity &&
364 sourcePath == other.sourcePath &&
365 errorCode == other.errorCode &&
366 offset == other.offset;
367 }
368
369 @override
370 int compareTo(CLIError other) {
371 // severity
372 int compare =
373 _severityCompare[other.severity] - _severityCompare[this.severity];
374 if (compare != 0) return compare;
375
376 // path
377 compare = Comparable.compare(
378 this.sourcePath.toLowerCase(), other.sourcePath.toLowerCase());
379 if (compare != 0) return compare;
380
381 // offset
382 return this.offset - other.offset;
383 }
285 } 384 }
286 385
287 /// A severity with awareness of whether it was overridden by a processor. 386 /// A severity with awareness of whether it was overridden by a processor.
288 class ProcessedSeverity { 387 class ProcessedSeverity {
289 ErrorSeverity severity; 388 final ErrorSeverity severity;
290 bool overridden; 389 final bool overridden;
291 ProcessedSeverity(this.severity, [this.overridden = false]); 390 ProcessedSeverity(this.severity, [this.overridden = false]);
292 } 391 }
293 392
294 /// Given an absolute path, return a relative path if the file is contained in 393 /// Given an absolute path, return a relative path if the file is contained in
295 /// the current directory; return the original path otherwise. 394 /// the current directory; return the original path otherwise.
296 String _relative(String file) { 395 String _relative(String file) {
297 return file.startsWith(path.current) ? path.relative(file) : file; 396 return file.startsWith(path.current) ? path.relative(file) : file;
298 } 397 }
OLDNEW
« no previous file with comments | « pkg/analyzer_cli/lib/src/driver.dart ('k') | pkg/analyzer_cli/lib/src/error_severity.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698