Fixes source map bug: getLocationMessage was incorrect on file segments
R=jmesserly@google.com
Review URL: https://codereview.chromium.org//18083030
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/source_maps@24759 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/lib/span.dart b/lib/span.dart
index fd302bc..e7bf5bf 100644
--- a/lib/span.dart
+++ b/lib/span.dart
@@ -6,7 +6,7 @@
library source_maps.span;
import 'dart:utf' show stringToCodepoints;
-import 'dart:math' show min;
+import 'dart:math' show min, max;
import 'src/utils.dart';
@@ -216,24 +216,18 @@
Location location(int offset) => new FileLocation(this, offset);
/// Gets the 0-based line corresponding to an offset.
- int getLine(int offset) {
- return binarySearch(_lineStarts, (o) => o > offset) - 1;
- }
+ int getLine(int offset) => binarySearch(_lineStarts, (o) => o > offset) - 1;
/// Gets the 0-based column corresponding to an offset.
- int getColumn(int line, int offset) {
- return offset - _lineStarts[line];
- }
+ int getColumn(int line, int offset) => offset - _lineStarts[line];
/// Get the offset for a given line and column
- int getOffset(int line, int column) {
- return _lineStarts[min(line, _lineStarts.length - 1)] + column;
- }
+ int getOffset(int line, int column) =>
+ _lineStarts[max(min(line, _lineStarts.length - 1), 0)] + column;
/// Gets the text at the given offsets.
- String getText(int start, [int end]) {
- return new String.fromCharCodes(_decodedChars.sublist(start, end));
- }
+ String getText(int start, [int end]) =>
+ new String.fromCharCodes(_decodedChars.sublist(max(start, 0), end));
/// Create a pretty string representation from a span.
String getLocationMessage(String message, int start, int end,
@@ -253,16 +247,12 @@
var buf = new StringBuffer(msg);
buf.write('\n');
- var textLine;
// +1 for 0-indexing, +1 again to avoid the last line
- if ((line + 2) < _lineStarts.length) {
- textLine = getText(_lineStarts[line], _lineStarts[line + 1]);
- } else {
- textLine = getText(_lineStarts[line]);
- textLine = '$textLine\n';
- }
+ var textLine = getText(getOffset(line, 0), getOffset(line + 1, 0));
+
+ column = min(column, textLine.length - 1);
int toColumn = min(column + end - start, textLine.length);
if (useColors) {
if (color == null) {
@@ -301,30 +291,52 @@
final int _baseLine;
final int _baseColumn;
+ // TODO(sigmund): consider providing an end-offset to correctly truncate
+ // values passed the end of the segment.
SourceFileSegment(String url, String textSegment, Location startOffset)
: _baseOffset = startOffset.offset,
_baseLine = startOffset.line,
_baseColumn = startOffset.column,
super.text(url, textSegment);
+ /// Craete a span, where [start] is relative to this segment's base offset.
+ /// The returned span stores the real offset on the file, so that error
+ /// messages are reported at the real location.
Span span(int start, [int end, bool isIdentifier = false]) =>
super.span(start + _baseOffset,
end == null ? null : end + _baseOffset, isIdentifier);
+ /// Create a location, where [offset] relative to this segment's base offset.
+ /// The returned span stores the real offset on the file, so that error
+ /// messages are reported at the real location.
Location location(int offset) => super.location(offset + _baseOffset);
+ /// Return the line on the underlying file associated with the [offset] of the
+ /// underlying file. This method operates on the real offsets from the
+ /// original file, so that error messages can be reported accurately.
int getLine(int offset) =>
- super.getLine(offset - _baseOffset) + _baseLine;
+ super.getLine(max(offset - _baseOffset, 0)) + _baseLine;
+ /// Return the column on the underlying file associated with [line] and
+ /// [offset], where [line] is absolute from the beginning of the underlying
+ /// file. This method operates on the real offsets from the original file, so
+ /// that error messages can be reported accurately.
int getColumn(int line, int offset) {
- var col = super.getColumn(line - _baseLine, offset - _baseOffset);
+ var col = super.getColumn(line - _baseLine, max(offset - _baseOffset, 0));
return line == _baseLine ? col + _baseColumn : col;
}
+ /// Return the offset associated with a line and column. This method operates
+ /// on the real offsets from the original file, so that error messages can be
+ /// reported accurately.
int getOffset(int line, int column) =>
super.getOffset(line - _baseLine,
line == _baseLine ? column - _baseColumn : column) + _baseOffset;
+ /// Retrieve the text associated with the specified range. This method
+ /// operates on the real offsets from the original file, so that error
+ /// messages can be reported accurately.
String getText(int start, [int end]) =>
- super.getText(start - _baseOffset, end - _baseOffset);
+ super.getText(start - _baseOffset,
+ end == null ? null : end - _baseOffset);
}
diff --git a/test/span_test.dart b/test/span_test.dart
index 8f61b58..66bd8a8 100644
--- a/test/span_test.dart
+++ b/test/span_test.dart
@@ -165,32 +165,96 @@
}
});
- test('file segment', () {
- var segment = new SourceFileSegment('file',
- TEST_FILE.substring(12), loc(12));
+ test('range check for large offsets', () {
+ var start = TEST_FILE.length;
+ expect(file.getLocationMessage('the message', start, start + 9),
+ 'file:13:1: the message\n');
+ });
+
+ group('file segment', () {
+ var baseOffset = 123;
+ var segmentText = TEST_FILE.substring(baseOffset, TEST_FILE.length - 100);
+ var segment = new SourceFileSegment('file', segmentText, loc(baseOffset));
sline(int n) => segment.getLine(n);
scol(int n) => segment.getColumn(segment.getLine(n), n);
-
line(int n) => file.getLine(n);
col(int n) => file.getColumn(file.getLine(n), n);
- int j = 0;
- int lineOffset = 0;
- for (int i = 12; i < TEST_FILE.length; i++) {
- if (i > lineOffset + newLines[j]) {
- lineOffset += newLines[j] + 1;
- j++;
- }
- expect(segment.location(i - 12).offset, i);
- expect(segment.location(i - 12).line, line(i));
- expect(segment.location(i - 12).column, col(i));
- expect(segment.span(i - 12).start.offset, i);
- expect(segment.span(i - 12).start.line, line(i));
- expect(segment.span(i - 12).start.column, col(i));
+ test('get line and column', () {
+ int j = 0;
+ int lineOffset = 0;
+ for (int i = baseOffset; i < segmentText.length; i++) {
+ if (i > lineOffset + newLines[j]) {
+ lineOffset += newLines[j] + 1;
+ j++;
+ }
+ expect(segment.location(i - baseOffset).offset, i);
+ expect(segment.location(i - baseOffset).line, line(i));
+ expect(segment.location(i - baseOffset).column, col(i));
+ expect(segment.span(i - baseOffset).start.offset, i);
+ expect(segment.span(i - baseOffset).start.line, line(i));
+ expect(segment.span(i - baseOffset).start.column, col(i));
- expect(sline(i), line(i));
- expect(scol(i), col(i));
- }
+ expect(sline(i), line(i));
+ expect(scol(i), col(i));
+ }
+ });
+
+ test('get text', () {
+ var start = 10 + 80 + 31 + 27 + 4 + 2;
+ expect(segment.getText(start, start + 9), file.getText(start, start + 9));
+ });
+
+ group('location message', () {
+ test('first line', () {
+ var start = baseOffset + 7;
+ expect(segment.getLocationMessage('the message', start, start + 2),
+ file.getLocationMessage('the message', start, start + 2));
+ });
+
+ test('in a middle line', () {
+ // Example from another test above:
+ var start = 10 + 80 + 31 + 27 + 4 + 2;
+ expect(segment.getLocationMessage('the message', start, start + 9),
+ file.getLocationMessage('the message', start, start + 9));
+ });
+
+ test('last segment line', () {
+ var start = segmentText.length - 4;
+ expect(segment.getLocationMessage('the message', start, start + 2),
+ file.getLocationMessage('the message', start, start + 2));
+ });
+
+ test('past segment, same as last segment line', () {
+ var start = segmentText.length;
+ expect(segment.getLocationMessage('the message', start, start + 2),
+ file.getLocationMessage('the message', start, start + 2));
+
+ start = segmentText.length + 20;
+ expect(segment.getLocationMessage('the message', start, start + 2),
+ file.getLocationMessage('the message', start, start + 2));
+ });
+
+ test('past segment, past its line', () {
+ var start = TEST_FILE.length - 2;
+ expect(file.getLocationMessage('the message', start, start + 1),
+ 'file:12:29: the message\n'
+ '123456789_1+3456789_123456789\n'
+ ' ^');
+
+ // TODO(sigmund): consider also fixing this and report file:10:4
+ // The answer below is different because the segment parsing only knows
+ // about the 10 lines it has (and nothing about the possible extra lines
+ // afterwards)
+ expect(segment.getLocationMessage('the message', start, start + 1),
+ 'file:10:112: the message\n');
+
+ // The number 112 includes the count of extra characters past the
+ // segment, which we verify here:
+ var lastSegmentLineStart = segmentText.lastIndexOf('\n');
+ expect(start - baseOffset - lastSegmentLineStart, 112);
+ });
+ });
});
test('span isIdentifier defaults to false', () {