blob: eb12e3012a5fb39a7adf2bdda596a718a89675c9 [file] [log] [blame]
// Copyright (c) 2025, 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.
import 'package:source_maps/source_maps.dart';
import 'package:source_span/source_span.dart';
import 'package:test/test.dart';
void main() {
/// This is a test for spans of the generated file that continue over several
/// lines.
///
/// In a sourcemap, a span continues from the start encoded position until the
/// next position, regardless of whether the second position in on the same
/// line in the generated file or a subsequent line.
void testSpans(int lineA, int columnA, int lineB, int columnB) {
// Create a sourcemap describing a 'rectangular' generated file with three
// spans, each potentially over several lines: (1) an initial span that is
// unmapped, (2) a span that maps to file 'A', the span continuing until (3)
// a span that maps to file 'B'.
//
// We can describe the mapping by an 'image' of the generated file, where
// the positions marked as 'A' in the 'image' correspond to locations in the
// generated file that map to locations in source file 'A'. Lines and
// columns are zero-based.
//
// 0123456789
// 0: ----------
// 1: ----AAAAAA lineA: 1, columnA: 4, i.e. locationA
// 2: AABBBBBBBB lineB: 2, columnB: 2, i.e. locationB
// 3: BBBBBBBBBB
//
// Once we have the mapping, we probe every position in a 8x10 rectangle to
// validate that it maps to the intended original source file.
expect(isBefore(lineB, columnB, lineA, columnA), isFalse,
reason: 'Test valid only for ordered positions');
SourceLocation location(Uri? uri, int line, int column) {
final offset = line * 10 + column;
return SourceLocation(offset, sourceUrl: uri, line: line, column: column);
}
// Locations in the generated file.
final uriMap = Uri.parse('output.js.map');
final locationA = location(uriMap, lineA, columnA);
final locationB = location(uriMap, lineB, columnB);
// Original source locations.
final sourceA = location(Uri.parse('A'), 0, 0);
final sourceB = location(Uri.parse('B'), 0, 0);
final json = (SourceMapBuilder()
..addLocation(sourceA, locationA, null)
..addLocation(sourceB, locationB, null))
.build(uriMap.toString());
final mapping = parseJson(json);
// Validate by comparing 'images' of the generate file.
final expectedImage = StringBuffer();
final actualImage = StringBuffer();
for (var line = 0; line < 8; line++) {
for (var column = 0; column < 10; column++) {
final span = mapping.spanFor(line, column);
final expected = isBefore(line, column, lineA, columnA)
? '-'
: isBefore(line, column, lineB, columnB)
? 'A'
: 'B';
final actual = span?.start.sourceUrl?.path ?? '-'; // Unmapped -> '-'.
expectedImage.write(expected);
actualImage.write(actual);
}
expectedImage.writeln();
actualImage.writeln();
}
expect(actualImage.toString(), expectedImage.toString());
}
test('continued span, same position', () {
testSpans(2, 4, 2, 4);
});
test('continued span, same line', () {
testSpans(2, 4, 2, 7);
});
test('continued span, next line, earlier column', () {
testSpans(2, 4, 3, 2);
});
test('continued span, next line, later column', () {
testSpans(2, 4, 3, 6);
});
test('continued span, later line, earlier column', () {
testSpans(2, 4, 5, 2);
});
test('continued span, later line, later column', () {
testSpans(2, 4, 5, 6);
});
}
bool isBefore(int line1, int column1, int line2, int column2) {
return line1 < line2 || line1 == line2 && column1 < column2;
}