Chromium Code Reviews| 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_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 } |
| OLD | NEW |