| // 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; | |
| } |