| 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 final Map<String, int> _severityCompare = { |
| 15 'error': 5, |
| 16 'warning': 4, |
| 17 'info': 3, |
| 18 'lint': 2, |
| 19 'hint': 1, |
| 20 }; |
| 21 |
| 22 String _pluralize(String word, int count) => count == 1 ? word : word + "s"; |
| 23 |
| 24 /// Given an absolute path, return a relative path if the file is contained in |
| 25 /// the current directory; return the original path otherwise. |
| 26 String _relative(String file) { |
| 27 return file.startsWith(path.current) ? path.relative(file) : file; |
| 28 } |
| 29 |
| 14 /// Returns the given error's severity. | 30 /// Returns the given error's severity. |
| 15 ErrorSeverity _severityIdentity(AnalysisError error) => | 31 ErrorSeverity _severityIdentity(AnalysisError error) => |
| 16 error.errorCode.errorSeverity; | 32 error.errorCode.errorSeverity; |
| 17 | 33 |
| 18 String _pluralize(String word, int count) => count == 1 ? word : word + "s"; | |
| 19 | |
| 20 /// Returns desired severity for the given [error] (or `null` if it's to be | 34 /// Returns desired severity for the given [error] (or `null` if it's to be |
| 21 /// suppressed). | 35 /// suppressed). |
| 22 typedef ErrorSeverity SeverityProcessor(AnalysisError error); | 36 typedef ErrorSeverity SeverityProcessor(AnalysisError error); |
| 23 | 37 |
| 24 /// Analysis statistics counter. | 38 /// Analysis statistics counter. |
| 25 class AnalysisStats { | 39 class AnalysisStats { |
| 26 /// The total number of diagnostics sent to [formatErrors]. | 40 /// The total number of diagnostics sent to [formatErrors]. |
| 27 int unfilteredCount = 0; | 41 int unfilteredCount = 0; |
| 28 | 42 |
| 29 int errorCount = 0; | 43 int errorCount = 0; |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 81 hasContent = true; | 95 hasContent = true; |
| 82 } | 96 } |
| 83 if (hasContent) { | 97 if (hasContent) { |
| 84 out.writeln(" found."); | 98 out.writeln(" found."); |
| 85 } else { | 99 } else { |
| 86 out.writeln("No issues found!"); | 100 out.writeln("No issues found!"); |
| 87 } | 101 } |
| 88 } | 102 } |
| 89 } | 103 } |
| 90 | 104 |
| 105 /// An [AnalysisError] with line and column information. |
| 106 class CLIError implements Comparable<CLIError> { |
| 107 final String severity; |
| 108 final String sourcePath; |
| 109 final int offset; |
| 110 final int line; |
| 111 final int column; |
| 112 final String message; |
| 113 final String errorCode; |
| 114 final String correction; |
| 115 |
| 116 CLIError({ |
| 117 this.severity, |
| 118 this.sourcePath, |
| 119 this.offset, |
| 120 this.line, |
| 121 this.column, |
| 122 this.message, |
| 123 this.errorCode, |
| 124 this.correction, |
| 125 }); |
| 126 |
| 127 @override |
| 128 int get hashCode => |
| 129 severity.hashCode ^ sourcePath.hashCode ^ errorCode.hashCode ^ offset; |
| 130 bool get isError => severity == 'error'; |
| 131 bool get isHint => severity == 'hint'; |
| 132 bool get isLint => severity == 'lint'; |
| 133 |
| 134 bool get isWarning => severity == 'warning'; |
| 135 |
| 136 @override |
| 137 bool operator ==(other) { |
| 138 if (other is! CLIError) return false; |
| 139 |
| 140 return severity == other.severity && |
| 141 sourcePath == other.sourcePath && |
| 142 errorCode == other.errorCode && |
| 143 offset == other.offset; |
| 144 } |
| 145 |
| 146 @override |
| 147 int compareTo(CLIError other) { |
| 148 // severity |
| 149 int compare = |
| 150 _severityCompare[other.severity] - _severityCompare[this.severity]; |
| 151 if (compare != 0) return compare; |
| 152 |
| 153 // path |
| 154 compare = Comparable.compare( |
| 155 this.sourcePath.toLowerCase(), other.sourcePath.toLowerCase()); |
| 156 if (compare != 0) return compare; |
| 157 |
| 158 // offset |
| 159 return this.offset - other.offset; |
| 160 } |
| 161 } |
| 162 |
| 91 /// Helper for formatting [AnalysisError]s. | 163 /// Helper for formatting [AnalysisError]s. |
| 92 /// | 164 /// |
| 93 /// The two format options are a user consumable format and a machine consumable | 165 /// The two format options are a user consumable format and a machine consumable |
| 94 /// format. | 166 /// format. |
| 95 abstract class ErrorFormatter { | 167 abstract class ErrorFormatter { |
| 96 final StringSink out; | 168 final StringSink out; |
| 97 final CommandLineOptions options; | 169 final CommandLineOptions options; |
| 98 final AnalysisStats stats; | 170 final AnalysisStats stats; |
| 99 SeverityProcessor _severityProcessor; | 171 SeverityProcessor _severityProcessor; |
| 100 | 172 |
| 101 ErrorFormatter(this.out, this.options, this.stats, | 173 ErrorFormatter(this.out, this.options, this.stats, |
| 102 {SeverityProcessor severityProcessor}) { | 174 {SeverityProcessor severityProcessor}) { |
| 103 _severityProcessor = | 175 _severityProcessor = |
| 104 severityProcessor == null ? _severityIdentity : severityProcessor; | 176 severityProcessor == null ? _severityIdentity : severityProcessor; |
| 105 } | 177 } |
| 106 | 178 |
| 107 /// Compute the severity for this [error] or `null` if this error should be | 179 /// Call to write any batched up errors from [formatErrors]. |
| 108 /// filtered. | 180 void flush(); |
| 109 ErrorSeverity _computeSeverity(AnalysisError error) => | 181 |
| 110 _severityProcessor(error); | 182 void formatError( |
| 183 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error); |
| 111 | 184 |
| 112 void formatErrors(List<AnalysisErrorInfo> errorInfos) { | 185 void formatErrors(List<AnalysisErrorInfo> errorInfos) { |
| 113 stats.unfilteredCount += errorInfos.length; | 186 stats.unfilteredCount += errorInfos.length; |
| 114 | 187 |
| 115 List<AnalysisError> errors = new List<AnalysisError>(); | 188 List<AnalysisError> errors = new List<AnalysisError>(); |
| 116 Map<AnalysisError, LineInfo> errorToLine = | 189 Map<AnalysisError, LineInfo> errorToLine = |
| 117 new Map<AnalysisError, LineInfo>(); | 190 new Map<AnalysisError, LineInfo>(); |
| 118 for (AnalysisErrorInfo errorInfo in errorInfos) { | 191 for (AnalysisErrorInfo errorInfo in errorInfos) { |
| 119 for (AnalysisError error in errorInfo.errors) { | 192 for (AnalysisError error in errorInfo.errors) { |
| 120 if (_computeSeverity(error) != null) { | 193 if (_computeSeverity(error) != null) { |
| 121 errors.add(error); | 194 errors.add(error); |
| 122 errorToLine[error] = errorInfo.lineInfo; | 195 errorToLine[error] = errorInfo.lineInfo; |
| 123 } | 196 } |
| 124 } | 197 } |
| 125 } | 198 } |
| 126 | 199 |
| 127 for (AnalysisError error in errors) { | 200 for (AnalysisError error in errors) { |
| 128 formatError(errorToLine, error); | 201 formatError(errorToLine, error); |
| 129 } | 202 } |
| 130 } | 203 } |
| 131 | 204 |
| 132 void formatError( | 205 /// Compute the severity for this [error] or `null` if this error should be |
| 133 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error); | 206 /// filtered. |
| 134 | 207 ErrorSeverity _computeSeverity(AnalysisError error) => |
| 135 /// Call to write any batched up errors from [formatErrors]. | 208 _severityProcessor(error); |
| 136 void flush(); | |
| 137 } | |
| 138 | |
| 139 class MachineErrorFormatter extends ErrorFormatter { | |
| 140 static final int _pipeCodeUnit = '|'.codeUnitAt(0); | |
| 141 static final int _slashCodeUnit = '\\'.codeUnitAt(0); | |
| 142 static final int _newline = '\n'.codeUnitAt(0); | |
| 143 static final int _return = '\r'.codeUnitAt(0); | |
| 144 | |
| 145 MachineErrorFormatter( | |
| 146 StringSink out, CommandLineOptions options, AnalysisStats stats, | |
| 147 {SeverityProcessor severityProcessor}) | |
| 148 : super(out, options, stats, severityProcessor: severityProcessor); | |
| 149 | |
| 150 void formatError( | |
| 151 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) { | |
| 152 Source source = error.source; | |
| 153 LineInfo_Location location = errorToLine[error].getLocation(error.offset); | |
| 154 int length = error.length; | |
| 155 | |
| 156 ErrorSeverity severity = _severityProcessor(error); | |
| 157 | |
| 158 if (severity == ErrorSeverity.ERROR) { | |
| 159 stats.errorCount++; | |
| 160 } else if (severity == ErrorSeverity.WARNING) { | |
| 161 stats.warnCount++; | |
| 162 } else if (error.errorCode.type == ErrorType.HINT) { | |
| 163 stats.hintCount++; | |
| 164 } else if (error.errorCode.type == ErrorType.LINT) { | |
| 165 stats.lintCount++; | |
| 166 } | |
| 167 | |
| 168 out.write(severity); | |
| 169 out.write('|'); | |
| 170 out.write(error.errorCode.type); | |
| 171 out.write('|'); | |
| 172 out.write(error.errorCode.name); | |
| 173 out.write('|'); | |
| 174 out.write(_escapeForMachineMode(source.fullName)); | |
| 175 out.write('|'); | |
| 176 out.write(location.lineNumber); | |
| 177 out.write('|'); | |
| 178 out.write(location.columnNumber); | |
| 179 out.write('|'); | |
| 180 out.write(length); | |
| 181 out.write('|'); | |
| 182 out.write(_escapeForMachineMode(error.message)); | |
| 183 out.writeln(); | |
| 184 } | |
| 185 | |
| 186 static String _escapeForMachineMode(String input) { | |
| 187 StringBuffer result = new StringBuffer(); | |
| 188 for (int c in input.codeUnits) { | |
| 189 if (c == _newline) { | |
| 190 result.write(r'\n'); | |
| 191 } else if (c == _return) { | |
| 192 result.write(r'\r'); | |
| 193 } else { | |
| 194 if (c == _slashCodeUnit || c == _pipeCodeUnit) { | |
| 195 result.write('\\'); | |
| 196 } | |
| 197 result.writeCharCode(c); | |
| 198 } | |
| 199 } | |
| 200 return result.toString(); | |
| 201 } | |
| 202 | |
| 203 void flush() {} | |
| 204 } | 209 } |
| 205 | 210 |
| 206 class HumanErrorFormatter extends ErrorFormatter { | 211 class HumanErrorFormatter extends ErrorFormatter { |
| 207 AnsiLogger ansi; | 212 AnsiLogger ansi; |
| 208 | 213 |
| 209 // This is a Set in order to de-dup CLI errors. | 214 // This is a Set in order to de-dup CLI errors. |
| 210 Set<CLIError> batchedErrors = new Set(); | 215 Set<CLIError> batchedErrors = new Set(); |
| 211 | 216 |
| 212 HumanErrorFormatter( | 217 HumanErrorFormatter( |
| 213 StringSink out, CommandLineOptions options, AnalysisStats stats, | 218 StringSink out, CommandLineOptions options, AnalysisStats stats, |
| 214 {SeverityProcessor severityProcessor}) | 219 {SeverityProcessor severityProcessor}) |
| 215 : super(out, options, stats, severityProcessor: severityProcessor) { | 220 : super(out, options, stats, severityProcessor: severityProcessor) { |
| 216 ansi = new AnsiLogger(this.options.color); | 221 ansi = new AnsiLogger(this.options.color); |
| 217 } | 222 } |
| 218 | 223 |
| 224 void flush() { |
| 225 // sort |
| 226 List<CLIError> sortedErrors = batchedErrors.toList()..sort(); |
| 227 |
| 228 // print |
| 229 for (CLIError error in sortedErrors) { |
| 230 if (error.isError) { |
| 231 stats.errorCount++; |
| 232 } else if (error.isWarning) { |
| 233 stats.warnCount++; |
| 234 } else if (error.isLint) { |
| 235 stats.lintCount++; |
| 236 } else if (error.isHint) { |
| 237 stats.hintCount++; |
| 238 } |
| 239 |
| 240 // warning • 'foo' is not a bar at lib/foo.dart:1:2 • foo_warning |
| 241 String issueColor = (error.isError == ErrorSeverity.ERROR || |
| 242 error.isWarning == ErrorSeverity.WARNING) |
| 243 ? ansi.red |
| 244 : ''; |
| 245 out.write(' $issueColor${error.severity}${ansi.none} ' |
| 246 '${ansi.bullet} ${ansi.bold}${error.message}${ansi.none} '); |
| 247 out.write('at ${error.sourcePath}'); |
| 248 out.write(':${error.line}:${error.column} '); |
| 249 out.write('${ansi.bullet} ${error.errorCode}'); |
| 250 out.writeln(); |
| 251 |
| 252 // If verbose, also print any associated correction. |
| 253 if (options.verbose && error.correction != null) { |
| 254 out.writeln( |
| 255 '${' '.padLeft(error.severity.length + 2)}${error.correction}'); |
| 256 } |
| 257 } |
| 258 |
| 259 // clear out batched errors |
| 260 batchedErrors.clear(); |
| 261 } |
| 262 |
| 219 void formatError( | 263 void formatError( |
| 220 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) { | 264 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) { |
| 221 Source source = error.source; | 265 Source source = error.source; |
| 222 LineInfo_Location location = errorToLine[error].getLocation(error.offset); | 266 LineInfo_Location location = errorToLine[error].getLocation(error.offset); |
| 223 | 267 |
| 224 ErrorSeverity severity = _severityProcessor(error); | 268 ErrorSeverity severity = _severityProcessor(error); |
| 225 | 269 |
| 226 // Get display name; translate INFOs into LINTS and HINTS. | 270 // Get display name; translate INFOs into LINTS and HINTS. |
| 227 String errorType = severity.displayName; | 271 String errorType = severity.displayName; |
| 228 if (severity == ErrorSeverity.INFO) { | 272 if (severity == ErrorSeverity.INFO) { |
| (...skipping 26 matching lines...) Expand all Loading... |
| 255 severity: errorType, | 299 severity: errorType, |
| 256 sourcePath: sourcePath, | 300 sourcePath: sourcePath, |
| 257 offset: error.offset, | 301 offset: error.offset, |
| 258 line: location.lineNumber, | 302 line: location.lineNumber, |
| 259 column: location.columnNumber, | 303 column: location.columnNumber, |
| 260 message: message, | 304 message: message, |
| 261 errorCode: error.errorCode.name.toLowerCase(), | 305 errorCode: error.errorCode.name.toLowerCase(), |
| 262 correction: error.correction, | 306 correction: error.correction, |
| 263 )); | 307 )); |
| 264 } | 308 } |
| 309 } |
| 265 | 310 |
| 266 void flush() { | 311 class MachineErrorFormatter extends ErrorFormatter { |
| 267 // sort | 312 static final int _pipeCodeUnit = '|'.codeUnitAt(0); |
| 268 List<CLIError> sortedErrors = batchedErrors.toList()..sort(); | 313 static final int _slashCodeUnit = '\\'.codeUnitAt(0); |
| 314 static final int _newline = '\n'.codeUnitAt(0); |
| 315 static final int _return = '\r'.codeUnitAt(0); |
| 269 | 316 |
| 270 // print | 317 MachineErrorFormatter( |
| 271 for (CLIError error in sortedErrors) { | 318 StringSink out, CommandLineOptions options, AnalysisStats stats, |
| 272 if (error.isError) { | 319 {SeverityProcessor severityProcessor}) |
| 273 stats.errorCount++; | 320 : super(out, options, stats, severityProcessor: severityProcessor); |
| 274 } else if (error.isWarning) { | |
| 275 stats.warnCount++; | |
| 276 } else if (error.isLint) { | |
| 277 stats.lintCount++; | |
| 278 } else if (error.isHint) { | |
| 279 stats.hintCount++; | |
| 280 } | |
| 281 | 321 |
| 282 // warning • 'foo' is not a bar at lib/foo.dart:1:2 • foo_warning | 322 void flush() {} |
| 283 String issueColor = (error.isError == ErrorSeverity.ERROR || | |
| 284 error.isWarning == ErrorSeverity.WARNING) | |
| 285 ? ansi.red | |
| 286 : ''; | |
| 287 out.write(' $issueColor${error.severity}${ansi.none} ' | |
| 288 '${ansi.bullet} ${ansi.bold}${error.message}${ansi.none} '); | |
| 289 out.write('at ${error.sourcePath}'); | |
| 290 out.write(':${error.line}:${error.column} '); | |
| 291 out.write('${ansi.bullet} ${error.errorCode}'); | |
| 292 out.writeln(); | |
| 293 | 323 |
| 294 // If verbose, also print any associated correction. | 324 void formatError( |
| 295 if (options.verbose && error.correction != null) { | 325 Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) { |
| 296 out.writeln( | 326 Source source = error.source; |
| 297 '${' '.padLeft(error.severity.length + 2)}${error.correction}'); | 327 LineInfo_Location location = errorToLine[error].getLocation(error.offset); |
| 328 int length = error.length; |
| 329 |
| 330 ErrorSeverity severity = _severityProcessor(error); |
| 331 |
| 332 if (severity == ErrorSeverity.ERROR) { |
| 333 stats.errorCount++; |
| 334 } else if (severity == ErrorSeverity.WARNING) { |
| 335 stats.warnCount++; |
| 336 } else if (error.errorCode.type == ErrorType.HINT) { |
| 337 stats.hintCount++; |
| 338 } else if (error.errorCode.type == ErrorType.LINT) { |
| 339 stats.lintCount++; |
| 340 } |
| 341 |
| 342 out.write(severity); |
| 343 out.write('|'); |
| 344 out.write(error.errorCode.type); |
| 345 out.write('|'); |
| 346 out.write(error.errorCode.name); |
| 347 out.write('|'); |
| 348 out.write(_escapeForMachineMode(source.fullName)); |
| 349 out.write('|'); |
| 350 out.write(location.lineNumber); |
| 351 out.write('|'); |
| 352 out.write(location.columnNumber); |
| 353 out.write('|'); |
| 354 out.write(length); |
| 355 out.write('|'); |
| 356 out.write(_escapeForMachineMode(error.message)); |
| 357 out.writeln(); |
| 358 } |
| 359 |
| 360 static String _escapeForMachineMode(String input) { |
| 361 StringBuffer result = new StringBuffer(); |
| 362 for (int c in input.codeUnits) { |
| 363 if (c == _newline) { |
| 364 result.write(r'\n'); |
| 365 } else if (c == _return) { |
| 366 result.write(r'\r'); |
| 367 } else { |
| 368 if (c == _slashCodeUnit || c == _pipeCodeUnit) { |
| 369 result.write('\\'); |
| 370 } |
| 371 result.writeCharCode(c); |
| 298 } | 372 } |
| 299 } | 373 } |
| 300 | 374 return result.toString(); |
| 301 // clear out batched errors | |
| 302 batchedErrors.clear(); | |
| 303 } | 375 } |
| 304 } | 376 } |
| 305 | |
| 306 final Map<String, int> _severityCompare = { | |
| 307 'error': 5, | |
| 308 'warning': 4, | |
| 309 'info': 3, | |
| 310 'lint': 2, | |
| 311 'hint': 1, | |
| 312 }; | |
| 313 | |
| 314 /// An [AnalysisError] with line and column information. | |
| 315 class CLIError implements Comparable<CLIError> { | |
| 316 final String severity; | |
| 317 final String sourcePath; | |
| 318 final int offset; | |
| 319 final int line; | |
| 320 final int column; | |
| 321 final String message; | |
| 322 final String errorCode; | |
| 323 final String correction; | |
| 324 | |
| 325 CLIError({ | |
| 326 this.severity, | |
| 327 this.sourcePath, | |
| 328 this.offset, | |
| 329 this.line, | |
| 330 this.column, | |
| 331 this.message, | |
| 332 this.errorCode, | |
| 333 this.correction, | |
| 334 }); | |
| 335 | |
| 336 bool get isError => severity == 'error'; | |
| 337 bool get isWarning => severity == 'warning'; | |
| 338 bool get isLint => severity == 'lint'; | |
| 339 bool get isHint => severity == 'hint'; | |
| 340 | |
| 341 @override | |
| 342 int get hashCode => | |
| 343 severity.hashCode ^ sourcePath.hashCode ^ errorCode.hashCode ^ offset; | |
| 344 | |
| 345 @override | |
| 346 bool operator ==(other) { | |
| 347 if (other is! CLIError) return false; | |
| 348 | |
| 349 return severity == other.severity && | |
| 350 sourcePath == other.sourcePath && | |
| 351 errorCode == other.errorCode && | |
| 352 offset == other.offset; | |
| 353 } | |
| 354 | |
| 355 @override | |
| 356 int compareTo(CLIError other) { | |
| 357 // severity | |
| 358 int compare = | |
| 359 _severityCompare[other.severity] - _severityCompare[this.severity]; | |
| 360 if (compare != 0) return compare; | |
| 361 | |
| 362 // path | |
| 363 compare = Comparable.compare( | |
| 364 this.sourcePath.toLowerCase(), other.sourcePath.toLowerCase()); | |
| 365 if (compare != 0) return compare; | |
| 366 | |
| 367 // offset | |
| 368 return this.offset - other.offset; | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 /// Given an absolute path, return a relative path if the file is contained in | |
| 373 /// the current directory; return the original path otherwise. | |
| 374 String _relative(String file) { | |
| 375 return file.startsWith(path.current) ? path.relative(file) : file; | |
| 376 } | |
| OLD | NEW |