From 51dbec5e648adc8f00c77c3ad9d0d32f05e27f67 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 7 Nov 2016 18:31:51 -0800 Subject: [PATCH] Add SourceSpan.highlight(). This is useful for constructing a message with a non-standard file/line/column display. --- CHANGELOG.md | 5 ++ lib/src/span.dart | 13 +++++ lib/src/span_mixin.dart | 26 ++++++---- pubspec.yaml | 2 +- ..._message_test.dart => highlight_test.dart} | 52 ++++++++++--------- test/span_test.dart | 36 +------------ 6 files changed, 65 insertions(+), 69 deletions(-) rename test/{file_message_test.dart => highlight_test.dart} (59%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6da8487..afcc493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.3.0 + +* Add `SourceSpan.highlight()`, which returns just the highlighted text that + would be included in `SourceSpan.message()`. + # 1.2.4 * Fix a new strong mode error. diff --git a/lib/src/span.dart b/lib/src/span.dart index fe1ac39..599d668 100644 --- a/lib/src/span.dart +++ b/lib/src/span.dart @@ -53,6 +53,19 @@ abstract class SourceSpan implements Comparable { /// should be highlighted using the default color. If it's `false` or `null`, /// it indicates that the text shouldn't be highlighted. String message(String message, {color}); + + /// Prints the text associated with this span in a user-friendly way. + /// + /// This is identical to [message], except that it doesn't print the file + /// name, line number, column number, or message. If [length] is 0 and this + /// isn't a [SourceSpanWithContext], returns an empty string. + /// + /// [color] may either be a [String], a [bool], or `null`. If it's a string, + /// it indicates an ANSII terminal color escape that should be used to + /// highlight the span's text. If it's `true`, it indicates that the text + /// should be highlighted using the default color. If it's `false` or `null`, + /// it indicates that the text shouldn't be highlighted. + String highlight({color}); } /// A base class for source spans with [start], [end], and [text] known at diff --git a/lib/src/span_mixin.dart b/lib/src/span_mixin.dart index a258cf5..8d84cea 100644 --- a/lib/src/span_mixin.dart +++ b/lib/src/span_mixin.dart @@ -46,20 +46,26 @@ abstract class SourceSpanMixin implements SourceSpan { } String message(String message, {color}) { - if (color == true) color = colors.RED; - if (color == false) color = null; - - var line = start.line; - var column = start.column; - var buffer = new StringBuffer(); - buffer.write('line ${line + 1}, column ${column + 1}'); + buffer.write('line ${start.line + 1}, column ${start.column + 1}'); if (sourceUrl != null) buffer.write(' of ${p.prettyUri(sourceUrl)}'); buffer.write(': $message'); - if (length == 0 && this is! SourceSpanWithContext) return buffer.toString(); - buffer.write("\n"); + var highlight = this.highlight(color: color); + if (!highlight.isEmpty) { + buffer.writeln(); + buffer.write(highlight); + } + + return buffer.toString(); + } + String highlight({color}) { + if (color == true) color = colors.RED; + if (color == false) color = null; + + var column = start.column; + var buffer = new StringBuffer(); String textLine; if (this is SourceSpanWithContext) { var context = (this as SourceSpanWithContext).context; @@ -71,6 +77,8 @@ abstract class SourceSpanMixin implements SourceSpan { var endIndex = context.indexOf('\n'); textLine = endIndex == -1 ? context : context.substring(0, endIndex + 1); column = math.min(column, textLine.length); + } else if (length == 0) { + return ""; } else { textLine = text.split("\n").first; column = 0; diff --git a/pubspec.yaml b/pubspec.yaml index 9e41fdd..8fa8711 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: source_span -version: 1.2.4 +version: 1.3.0 author: Dart Team description: A library for identifying source spans and locations. homepage: https://github.com/dart-lang/source_span diff --git a/test/file_message_test.dart b/test/highlight_test.dart similarity index 59% rename from test/file_message_test.dart rename to test/highlight_test.dart index d78a651..32b09ff 100644 --- a/test/file_message_test.dart +++ b/test/highlight_test.dart @@ -13,48 +13,42 @@ main() { foo bar baz whiz bang boom zip zap zop -""", url: "foo.dart"); +"""); }); test("points to the span in the source", () { - expect(file.span(4, 7).message("oh no"), equals(""" -line 1, column 5 of foo.dart: oh no + expect(file.span(4, 7).highlight(), equals(""" foo bar baz ^^^""")); }); test("gracefully handles a missing source URL", () { var span = new SourceFile("foo bar baz").span(4, 7); - expect(span.message("oh no"), equals(""" -line 1, column 5: oh no + expect(span.highlight(), equals(""" foo bar baz ^^^""")); }); test("highlights the first line of a multiline span", () { - expect(file.span(4, 20).message("oh no"), equals(""" -line 1, column 5 of foo.dart: oh no + expect(file.span(4, 20).highlight(), equals(""" foo bar baz ^^^^^^^^""")); }); test("works for a point span", () { - expect(file.location(4).pointSpan().message("oh no"), equals(""" -line 1, column 5 of foo.dart: oh no + expect(file.location(4).pointSpan().highlight(), equals(""" foo bar baz ^""")); }); test("works for a point span at the end of a line", () { - expect(file.location(11).pointSpan().message("oh no"), equals(""" -line 1, column 12 of foo.dart: oh no + expect(file.location(11).pointSpan().highlight(), equals(""" foo bar baz ^""")); }); test("works for a point span at the end of the file", () { - expect(file.location(38).pointSpan().message("oh no"), equals(""" -line 3, column 12 of foo.dart: oh no + expect(file.location(38).pointSpan().highlight(), equals(""" zip zap zop ^""")); }); @@ -62,46 +56,54 @@ zip zap zop test("works for a point span at the end of the file with no trailing newline", () { file = new SourceFile("zip zap zop"); - expect(file.location(11).pointSpan().message("oh no"), equals(""" -line 1, column 12: oh no + expect(file.location(11).pointSpan().highlight(), equals(""" zip zap zop ^""")); }); test("works for a point span in an empty file", () { - expect(new SourceFile("").location(0).pointSpan().message("oh no"), + expect(new SourceFile("").location(0).pointSpan().highlight(), equals(""" -line 1, column 1: oh no ^""")); }); test("works for a single-line file without a newline", () { - expect(new SourceFile("foo bar").span(0, 7).message("oh no"), + expect(new SourceFile("foo bar").span(0, 7).highlight(), equals(""" -line 1, column 1: oh no foo bar ^^^^^^^""")); }); + test("supports lines of preceding context", () { + var span = new SourceSpanWithContext( + new SourceLocation(5, line: 3, column: 5, sourceUrl: "foo.dart"), + new SourceLocation(12, line: 3, column: 12, sourceUrl: "foo.dart"), + "foo bar", + "previous\nlines\n-----foo bar-----\nfollowing line\n"); + + expect(span.highlight(color: colors.YELLOW), equals(""" +previous +lines +-----${colors.YELLOW}foo bar${colors.NONE}----- + ${colors.YELLOW}^^^^^^^${colors.NONE}""")); + }); + group("colors", () { test("doesn't colorize if color is false", () { - expect(file.span(4, 7).message("oh no", color: false), equals(""" -line 1, column 5 of foo.dart: oh no + expect(file.span(4, 7).highlight(color: false), equals(""" foo bar baz ^^^""")); }); test("colorizes if color is true", () { - expect(file.span(4, 7).message("oh no", color: true), equals(""" -line 1, column 5 of foo.dart: oh no + expect(file.span(4, 7).highlight(color: true), equals(""" foo ${colors.RED}bar${colors.NONE} baz ${colors.RED}^^^${colors.NONE}""")); }); test("uses the given color if it's passed", () { - expect(file.span(4, 7).message("oh no", color: colors.YELLOW), equals(""" -line 1, column 5 of foo.dart: oh no + expect(file.span(4, 7).highlight(color: colors.YELLOW), equals(""" foo ${colors.YELLOW}bar${colors.NONE} baz ${colors.YELLOW}^^^${colors.NONE}""")); }); diff --git a/test/span_test.dart b/test/span_test.dart index 113848a..f980f30 100644 --- a/test/span_test.dart +++ b/test/span_test.dart @@ -234,48 +234,16 @@ line 1, column 6 of foo.dart: oh no ${colors.YELLOW}foo bar${colors.NONE} ${colors.YELLOW}^^^^^^^${colors.NONE}""")); }); - }); - group("message() with context", () { - var spanWithContext; - setUp(() { - spanWithContext = new SourceSpanWithContext( + test("with context, underlines the right column", () { + var spanWithContext = new SourceSpanWithContext( new SourceLocation(5, sourceUrl: "foo.dart"), new SourceLocation(12, sourceUrl: "foo.dart"), "foo bar", "-----foo bar-----"); - }); - test("underlines under the right column", () { expect(spanWithContext.message("oh no", color: colors.YELLOW), equals(""" line 1, column 6 of foo.dart: oh no ------${colors.YELLOW}foo bar${colors.NONE}----- - ${colors.YELLOW}^^^^^^^${colors.NONE}""")); - }); - - test("underlines correctly when text appears twice", () { - var span = new SourceSpanWithContext( - new SourceLocation(9, column: 9, sourceUrl: "foo.dart"), - new SourceLocation(12, column: 12, sourceUrl: "foo.dart"), - "foo", - "-----foo foo-----"); - expect(span.message("oh no", color: colors.YELLOW), equals(""" -line 1, column 10 of foo.dart: oh no ------foo ${colors.YELLOW}foo${colors.NONE}----- - ${colors.YELLOW}^^^${colors.NONE}""")); - }); - - test("supports lines of preceeding context", () { - var span = new SourceSpanWithContext( - new SourceLocation(5, line: 3, column: 5, sourceUrl: "foo.dart"), - new SourceLocation(12, line: 3, column: 12, sourceUrl: "foo.dart"), - "foo bar", - "previous\nlines\n-----foo bar-----\nfollowing line\n"); - - expect(span.message("oh no", color: colors.YELLOW), equals(""" -line 4, column 6 of foo.dart: oh no -previous -lines -----${colors.YELLOW}foo bar${colors.NONE}----- ${colors.YELLOW}^^^^^^^${colors.NONE}""")); });