Fix a bug when highlighting an end-of-file point span (dart-lang/source_span#30)
diff --git a/pkgs/source_span/CHANGELOG.md b/pkgs/source_span/CHANGELOG.md index afba8b7..6e79fbc 100644 --- a/pkgs/source_span/CHANGELOG.md +++ b/pkgs/source_span/CHANGELOG.md
@@ -1,3 +1,9 @@ +# 1.5.3 + +* Fix an edge case where `FileSpan.highlight()` would put the highlight + indicator in the wrong position when highlighting a point span after the end + of a file. + # 1.5.2 * `SourceFile.span()` now goes to the end of the file by default, rather than
diff --git a/pkgs/source_span/lib/src/highlighter.dart b/pkgs/source_span/lib/src/highlighter.dart index f29a89d..8100f18 100644 --- a/pkgs/source_span/lib/src/highlighter.dart +++ b/pkgs/source_span/lib/src/highlighter.dart
@@ -82,7 +82,7 @@ new SourceLocation(span.end.offset, sourceUrl: span.sourceUrl, line: countCodeUnits(span.text, $lf), - column: _lastColumn(span.text)), + column: _lastLineLength(span.text)), span.text, span.text); @@ -126,34 +126,55 @@ end = new SourceLocation(span.end.offset - 1, sourceUrl: span.sourceUrl, line: span.end.line - 1, - column: _lastColumn(text)); + column: _lastLineLength(text)); start = span.start.offset == span.end.offset ? end : span.start; } return new SourceSpanWithContext(start, end, text, context); } - /// Normalizes [span] so that the end location is at the end of a line, rather - /// than on the beginning of the next line. + /// Normalizes [span] so that the end location is at the end of a line rather + /// than at the beginning of the next line. static SourceSpanWithContext _normalizeEndOfLine(SourceSpanWithContext span) { if (span.end.column != 0) return span; - if (span.end.line == span.start.line) return span; - assert(span.text.endsWith("\n")); + if (span.length == 0) { + if (span.end.offset == 0) return span; - var text = span.text.substring(0, span.text.length - 1); - return new SourceSpanWithContext( - span.start, - new SourceLocation(span.end.offset - 1, - sourceUrl: span.sourceUrl, - line: span.end.line - 1, - column: _lastColumn(text)), - text, - span.context); + // If [span] is a point span with an empty context, there's no useful + // adjustment we can do. + if (span.context.isEmpty) return span; + + var location = new SourceLocation(span.end.offset - 1, + sourceUrl: span.sourceUrl, + line: span.end.line - 1, + column: _lastLineLength(span.context)); + return new SourceSpanWithContext(location, location, "", span.context); + } else { + if (span.end.line == span.start.line) return span; + + var text = span.text.substring(0, span.text.length - 1); + + return new SourceSpanWithContext( + span.start, + new SourceLocation(span.end.offset - 1, + sourceUrl: span.sourceUrl, + line: span.end.line - 1, + column: _lastLineLength(text)), + text, + span.context); + } } - /// Returns the (0-based) column number of the last column of the last line in [text]. - static int _lastColumn(String text) => - text.length - text.lastIndexOf("\n") + 1; + /// Returns the length of the last line in [text], whether or not it ends in a + /// newline. + static int _lastLineLength(String text) { + if (text.isEmpty) return 0; + + // The "- 1" here avoids counting the newline itself. + return text.codeUnitAt(text.length - 1) == $lf + ? text.length - text.lastIndexOf("\n", text.length - 2) - 1 + : text.length - text.lastIndexOf("\n") - 1; + } /// Returns whether [span]'s text runs all the way to the end of its context. static bool _isTextAtEndOfContext(SourceSpanWithContext span) =>
diff --git a/pkgs/source_span/pubspec.yaml b/pkgs/source_span/pubspec.yaml index fd80bc8..c4b4ccf 100644 --- a/pkgs/source_span/pubspec.yaml +++ b/pkgs/source_span/pubspec.yaml
@@ -1,5 +1,5 @@ name: source_span -version: 1.5.2 +version: 1.5.3 description: A library for identifying source spans and locations. author: Dart Team <misc@dartlang.org>
diff --git a/pkgs/source_span/test/highlight_test.dart b/pkgs/source_span/test/highlight_test.dart index 8418367..7832e51 100644 --- a/pkgs/source_span/test/highlight_test.dart +++ b/pkgs/source_span/test/highlight_test.dart
@@ -53,6 +53,14 @@ '""")); }); + test("works for a point span at the beginning of the file", () { + expect(file.location(0).pointSpan().highlight(), equals(""" + , +1 | foo bar baz + | ^ + '""")); + }); + test("works for a point span at the end of a line", () { expect(file.location(11).pointSpan().highlight(), equals(""" , @@ -69,9 +77,28 @@ '""")); }); + test("works for a point span after the end of the file", () { + expect(file.location(39).pointSpan().highlight(), equals(""" + , +3 | zip zap zop + | ^ + '""")); + }); + test("works for a point span at the end of the file with no trailing newline", () { file = new SourceFile.fromString("zip zap zop"); + expect(file.location(10).pointSpan().highlight(), equals(""" + , +1 | zip zap zop + | ^ + '""")); + }); + + test( + "works for a point span after the end of the file with no trailing newline", + () { + file = new SourceFile.fromString("zip zap zop"); expect(file.location(11).pointSpan().highlight(), equals(""" , 1 | zip zap zop @@ -157,7 +184,7 @@ '""")); }); - test("highlights the full last line if it's empty", () { + test("highlights the full first line if it's empty", () { var file = new SourceFile.fromString(""" foo