| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| import 'static_error.dart'; |
| |
| /// Matches leading indentation in a string. |
| final _indentationRegExp = RegExp(r"^(\s*)"); |
| |
| /// Matches a line that contains only a line comment. |
| final _lineCommentRegExp = RegExp(r"^\s*//"); |
| |
| /// Removes existing static error marker comments in [source] and adds markers |
| /// for the given [errors]. |
| /// |
| /// If [removeAnalyzer] is `false`, then existing analyzer errors in [source] |
| /// are preserved. Likewise for [removeCfe] and CFE errors. |
| String updateErrorExpectations(String source, List<StaticError> errors, |
| {bool removeAnalyzer, bool removeCfe}) { |
| removeAnalyzer ??= true; |
| removeCfe ??= true; |
| |
| var existingErrors = StaticError.parseExpectations(source); |
| var lines = source.split("\n"); |
| |
| // Keep track of the indentation on any existing expectation markers. If |
| // found, it will try to preserve that indentation. |
| var indentation = <int, int>{}; |
| |
| // Remove existing markers that should be removed. |
| var preservedErrors = <StaticError>[]; |
| for (var error in existingErrors) { |
| for (var i = error.markerStartLine; i <= error.markerEndLine; i++) { |
| indentation[i] = _countIndentation(lines[i]); |
| |
| // Null the line instead of removing it so that line numbers in the |
| // reported errors are still correct. |
| lines[i] = null; |
| } |
| |
| // Re-add errors for the portions we intend to preserve. |
| var keepCode = !removeAnalyzer && error.code != null; |
| var keepMessage = !removeCfe && error.message != null; |
| |
| if (keepCode || keepMessage) { |
| preservedErrors.add(StaticError( |
| line: error.line, |
| column: error.column, |
| length: error.length, |
| code: keepCode ? error.code : null, |
| message: keepMessage ? error.message : null)); |
| } |
| } |
| |
| // Merge the new errors with the preserved ones. |
| errors = StaticError.simplify([...errors, ...preservedErrors]); |
| |
| var errorMap = <int, List<StaticError>>{}; |
| for (var error in errors) { |
| // -1 to translate from one-based to zero-based index. |
| errorMap.putIfAbsent(error.line - 1, () => []).add(error); |
| } |
| |
| // If there are multiple errors on the same line, order them |
| // deterministically. |
| for (var errorList in errorMap.values) { |
| errorList.sort(); |
| } |
| |
| var previousIndent = 0; |
| var codeLine = 1; |
| var result = <String>[]; |
| for (var i = 0; i < lines.length; i++) { |
| // Keep the code. |
| if (lines[i] != null) { |
| result.add(lines[i]); |
| previousIndent = _countIndentation(lines[i]); |
| |
| // Keep track of the resulting line number of the last line containing |
| // real code. We use this when outputting explicit line numbers instead |
| // the error's reported line to compensate for added or removed lines |
| // above the error. |
| codeLine = result.length; |
| } |
| |
| // Add expectations for any errors reported on this line. |
| var errorsHere = errorMap[i]; |
| if (errorsHere == null) continue; |
| for (var error in errorsHere) { |
| // Try to indent the line nicely to match either the existing expectation |
| // that is being regenerated, or, barring that, the previous line of code. |
| var indent = indentation[i + 1] ?? previousIndent; |
| |
| // If the error is to the left of the indent and the "//", sacrifice the |
| // indentation. |
| if (error.column - 1 < indent + 2) indent = 0; |
| |
| var comment = (" " * indent) + "//"; |
| |
| // If the error can't fit in a line comment, or no source location is |
| // sepcified, use an explicit location. |
| if (error.column <= 2 || error.length == 0) { |
| if (error.length == null) { |
| result.add("$comment [error line $codeLine, column " |
| "${error.column}]"); |
| } else { |
| result.add("$comment [error line $codeLine, column " |
| "${error.column}, length ${error.length}]"); |
| } |
| } else { |
| var spacing = " " * (error.column - 1 - 2 - indent); |
| // A CFE-only error may not have a length, so treat it as length 1. |
| var carets = "^" * (error.length ?? 1); |
| result.add("$comment$spacing$carets"); |
| } |
| |
| if (error.code != null) { |
| result.add("$comment [analyzer] ${error.code}"); |
| } |
| |
| if (error.message != null) { |
| var errorLines = error.message.split("\n"); |
| result.add("$comment [cfe] ${errorLines[0]}"); |
| for (var errorLine in errorLines.skip(1)) { |
| result.add("$comment $errorLine"); |
| } |
| |
| // If the very next line in the source is a line comment, it would |
| // become part of the inserted message. To prevent that, insert a blank |
| // line. |
| if (i < lines.length - 1 && |
| lines[i + 1] != null && |
| _lineCommentRegExp.hasMatch(lines[i + 1])) { |
| result.add(""); |
| } |
| } |
| } |
| } |
| |
| return result.join("\n"); |
| } |
| |
| /// Returns the number of characters of leading spaces in [line]. |
| int _countIndentation(String line) { |
| var match = _indentationRegExp.firstMatch(line); |
| return match.group(1).length; |
| } |