Introduce span with line context
R=nweiz@google.com
Review URL: https://codereview.chromium.org//1028813002
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 195bcf8..6ab52c8 100644
--- a/CHANGELOG.md
+++ b/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/lib/source_span.dart b/lib/source_span.dart
index e9646b1..89b1650 100644
--- a/lib/source_span.dart
+++ b/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/lib/src/file.dart b/lib/src/file.dart
index ed5f6a8..c7e5898 100644
--- a/lib/src/file.dart
+++ b/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/lib/src/span_mixin.dart b/lib/src/span_mixin.dart
index 716e6e0..a93723f 100644
--- a/lib/src/span_mixin.dart
+++ b/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/lib/src/span_with_context.dart b/lib/src/span_with_context.dart
new file mode 100644
index 0000000..4d279de
--- /dev/null
+++ b/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/pubspec.yaml b/pubspec.yaml
index 03b290b..887c040 100644
--- a/pubspec.yaml
+++ b/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/test/span_test.dart b/test/span_test.dart
index c62753b..39b0b94 100644
--- a/test/span_test.dart
+++ b/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/test/utils_test.dart b/test/utils_test.dart
index 3921111..a998847 100644
--- a/test/utils_test.dart
+++ b/test/utils_test.dart
@@ -48,4 +48,3 @@
}
return list.length;
}
-