Introduce span with line context R=nweiz@google.com Review URL: https://codereview.chromium.org//1028813002
diff --git a/pkgs/source_span/CHANGELOG.md b/pkgs/source_span/CHANGELOG.md index 195bcf8..6ab52c8 100644 --- a/pkgs/source_span/CHANGELOG.md +++ b/pkgs/source_span/CHANGELOG.md
@@ -1,3 +1,8 @@ +# 1.1.0 + +* Added `SourceSpanWithContext`: a span that also includes the full line of text + that contains the span. + # 1.0.3 * Cleanup equality operator to accept any Object rather than just a
diff --git a/pkgs/source_span/lib/source_span.dart b/pkgs/source_span/lib/source_span.dart index e9646b1..89b1650 100644 --- a/pkgs/source_span/lib/source_span.dart +++ b/pkgs/source_span/lib/source_span.dart
@@ -9,3 +9,4 @@ export "src/span.dart"; export "src/span_exception.dart"; export "src/span_mixin.dart"; +export "src/span_with_context.dart";
diff --git a/pkgs/source_span/lib/src/file.dart b/pkgs/source_span/lib/src/file.dart index ed5f6a8..c7e5898 100644 --- a/pkgs/source_span/lib/src/file.dart +++ b/pkgs/source_span/lib/src/file.dart
@@ -13,6 +13,7 @@ import 'location.dart'; import 'span.dart'; import 'span_mixin.dart'; +import 'span_with_context.dart'; import 'utils.dart'; // Constants to determine end-of-lines. @@ -183,7 +184,7 @@ /// [FileSpan.union] will return a [FileSpan] if possible. /// /// A [FileSpan] can be created using [SourceFile.span]. -class FileSpan extends SourceSpanMixin { +class FileSpan extends SourceSpanMixin implements SourceSpanWithContext { /// The [file] that [this] belongs to. final SourceFile file; @@ -205,6 +206,12 @@ FileLocation get end => new FileLocation._(file, _end); String get text => file.getText(_start, _end); + String get context { + var line = start.line; + return file.getText(file.getOffset(line), + line == file.lines - 1 ? null : file.getOffset(line + 1)); + } + FileSpan._(this.file, this._start, this._end) { if (_end < _start) { throw new ArgumentError('End $_end must come after start $_start.'); @@ -261,41 +268,4 @@ var end = math.max(this._end, other._end); return new FileSpan._(file, start, end); } - - 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 ${start.line + 1}, column ${start.column + 1}'); - if (sourceUrl != null) buffer.write(' of ${p.prettyUri(sourceUrl)}'); - buffer.write(': $message\n'); - - var textLine = file.getText(file.getOffset(line), - line == file.lines - 1 ? null : file.getOffset(line + 1)); - - column = math.min(column, textLine.length - 1); - var toColumn = - math.min(column + end.offset - start.offset, textLine.length); - - if (color != null) { - buffer.write(textLine.substring(0, column)); - buffer.write(color); - buffer.write(textLine.substring(column, toColumn)); - buffer.write(colors.NONE); - buffer.write(textLine.substring(toColumn)); - } else { - buffer.write(textLine); - } - if (!textLine.endsWith('\n')) buffer.write('\n'); - - buffer.write(' ' * column); - if (color != null) buffer.write(color); - buffer.write('^' * math.max(toColumn - column, 1)); - if (color != null) buffer.write(colors.NONE); - return buffer.toString(); - } }
diff --git a/pkgs/source_span/lib/src/span_mixin.dart b/pkgs/source_span/lib/src/span_mixin.dart index 716e6e0..a93723f 100644 --- a/pkgs/source_span/lib/src/span_mixin.dart +++ b/pkgs/source_span/lib/src/span_mixin.dart
@@ -4,10 +4,12 @@ library source_span.span_mixin; +import 'dart:math' as math; import 'package:path/path.dart' as p; import 'colors.dart' as colors; import 'span.dart'; +import 'span_with_context.dart'; import 'utils.dart'; /// A mixin for easily implementing [SourceSpan]. @@ -49,18 +51,49 @@ 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 ${start.line + 1}, column ${start.column + 1}'); + buffer.write('line ${line + 1}, column ${column + 1}'); if (sourceUrl != null) buffer.write(' of ${p.prettyUri(sourceUrl)}'); buffer.write(': $message'); - if (length == 0) return buffer.toString(); + if (length == 0 && this is! SourceSpanWithContext) return buffer.toString(); buffer.write("\n"); - var textLine = text.split("\n").first; + + var textLine; + if (this is SourceSpanWithContext) { + var context = (this as SourceSpanWithContext).context; + var textIndex = context.indexOf(text.split('\n').first); + var lineStart = context.lastIndexOf('\n', textIndex); + if (lineStart != -1) { + buffer.write(context.substring(0, lineStart + 1)); + context = context.substring(lineStart + 1); + } + var endIndex = context.indexOf('\n'); + textLine = endIndex == -1 ? context : context.substring(0, endIndex + 1); + column = math.min(column, textLine.length - 1); + } else { + textLine = text.split("\n").first; + column = 0; + } + + var toColumn = + math.min(column + end.offset - start.offset, textLine.length); + if (color != null) { + buffer.write(textLine.substring(0, column)); + buffer.write(color); + buffer.write(textLine.substring(column, toColumn)); + buffer.write(colors.NONE); + buffer.write(textLine.substring(toColumn)); + } else { + buffer.write(textLine); + } + if (!textLine.endsWith('\n')) buffer.write('\n'); + buffer.write(' ' * column); if (color != null) buffer.write(color); - buffer.write(textLine); - buffer.write("\n"); - buffer.write('^' * textLine.length); + buffer.write('^' * math.max(toColumn - column, 1)); if (color != null) buffer.write(colors.NONE); return buffer.toString(); }
diff --git a/pkgs/source_span/lib/src/span_with_context.dart b/pkgs/source_span/lib/src/span_with_context.dart new file mode 100644 index 0000000..4d279de --- /dev/null +++ b/pkgs/source_span/lib/src/span_with_context.dart
@@ -0,0 +1,38 @@ +// Copyright (c) 2014, 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. + +library source_span.span; + +import 'location.dart'; +import 'span.dart'; + +/// A class that describes a segment of source text with additional context. +class SourceSpanWithContext extends SourceSpanBase { + /// Text around the span, which includes the line containing this span. + final String context; + + /// Creates a new span from [start] to [end] (exclusive) containing [text], in + /// the given [context]. + /// + /// [start] and [end] must have the same source URL and [start] must come + /// before [end]. [text] must have a number of characters equal to the + /// distance between [start] and [end]. [context] must contain [text], and + /// [text] should start at `start.column` from the beginning of a line in + /// [context]. + SourceSpanWithContext( + SourceLocation start, SourceLocation end, String text, this.context) + : super(start, end, text) { + var index = context.indexOf(text); + if (index == -1) { + throw new ArgumentError( + 'The context line "$context" must contain "$text".'); + } + + var beginningOfLine = context.lastIndexOf('\n', index) + 1; + if (start.column != index - beginningOfLine) { + throw new ArgumentError('The span text "$text" must start at ' + 'column ${start.column + 1} in a line within "$context".'); + } + } +}
diff --git a/pkgs/source_span/pubspec.yaml b/pkgs/source_span/pubspec.yaml index 03b290b..887c040 100644 --- a/pkgs/source_span/pubspec.yaml +++ b/pkgs/source_span/pubspec.yaml
@@ -1,6 +1,5 @@ name: source_span - -version: 1.0.3 +version: 1.1.0 author: Dart Team <misc@dartlang.org> description: A library for identifying source spans and locations. homepage: http://github.com/dart-lang/source_span @@ -9,4 +8,4 @@ environment: sdk: '>=0.8.10+6 <2.0.0' dev_dependencies: - unittest: '>=0.9.0 <0.10.0' + unittest: '>=0.9.0 <0.12.0'
diff --git a/pkgs/source_span/test/span_test.dart b/pkgs/source_span/test/span_test.dart index c62753b..39b0b94 100644 --- a/pkgs/source_span/test/span_test.dart +++ b/pkgs/source_span/test/span_test.dart
@@ -36,6 +36,37 @@ }); }); + group('for new SourceSpanWithContext()', () { + test('context must contain text', () { + var start = new SourceLocation(2); + var end = new SourceLocation(5); + expect(() => new SourceSpanWithContext( + start, end, "abc", "--axc--"), throwsArgumentError); + }); + + test('text starts at start.column in context', () { + var start = new SourceLocation(3); + var end = new SourceLocation(5); + expect(() => new SourceSpanWithContext( + start, end, "abc", "--abc--"), throwsArgumentError); + }); + + test('text starts at start.column of line in multi-line context', () { + var start = new SourceLocation(4, line: 55, column: 3); + var end = new SourceLocation(7, line: 55, column: 6); + expect(() => new SourceSpanWithContext( + start, end, "abc", "\n--abc--"), throwsArgumentError); + expect(() => new SourceSpanWithContext( + start, end, "abc", "\n----abc--"), throwsArgumentError); + expect(() => new SourceSpanWithContext( + start, end, "abc", "\n\n--abc--"), throwsArgumentError); + + // However, these are valid: + new SourceSpanWithContext(start, end, "abc", "\n---abc--"); + new SourceSpanWithContext(start, end, "abc", "\n\n---abc--"); + }); + }); + group('for union()', () { test('source URLs must match', () { var other = new SourceSpan( @@ -178,15 +209,48 @@ expect(span.message("oh no", color: true), equals(""" line 1, column 6 of foo.dart: oh no -${colors.RED}foo bar -^^^^^^^${colors.NONE}""")); +${colors.RED}foo bar${colors.NONE} +${colors.RED}^^^^^^^${colors.NONE}""")); }); test("uses the given color if it's passed", () { expect(span.message("oh no", color: colors.YELLOW), equals(""" line 1, column 6 of foo.dart: oh no -${colors.YELLOW}foo bar -^^^^^^^${colors.NONE}""")); +${colors.YELLOW}foo bar${colors.NONE} +${colors.YELLOW}^^^^^^^${colors.NONE}""")); + }); + }); + + group("message() with context", () { + var spanWithContext; + setUp(() { + 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("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}""")); }); });
diff --git a/pkgs/source_span/test/utils_test.dart b/pkgs/source_span/test/utils_test.dart index 3921111..a998847 100644 --- a/pkgs/source_span/test/utils_test.dart +++ b/pkgs/source_span/test/utils_test.dart
@@ -48,4 +48,3 @@ } return list.length; } -