feat(compiler-cli): add `SourceFile.getOriginalLocation()` to sourcemaps package (#32912)
This method will allow us to find the original location given a generated location, which is useful in fine grained work with source-mapping. E.g. in `$localize` tooling. PR Close #32912
This commit is contained in:
parent
2b2146bc58
commit
6abb8d0d91
|
@ -87,6 +87,48 @@ export class SourceFile {
|
|||
return sourceMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the original mapped location for the given `line` and `column` in the generated file.
|
||||
*
|
||||
* First we search for a mapping whose generated segment is at or directly before the given
|
||||
* location. Then we compute the offset between the given location and the matching generated
|
||||
* segment. Finally we apply this offset to the original source segment to get the desired
|
||||
* original location.
|
||||
*/
|
||||
getOriginalLocation(line: number, column: number):
|
||||
{file: AbsoluteFsPath, line: number, column: number}|null {
|
||||
if (this.flattenedMappings.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let position: number;
|
||||
if (line < this.startOfLinePositions.length) {
|
||||
position = this.startOfLinePositions[line] + column;
|
||||
} else {
|
||||
// The line is off the end of the file, so just assume we are at the end of the file.
|
||||
position = this.contents.length;
|
||||
}
|
||||
|
||||
const locationSegment: SegmentMarker = {line, column, position, next: undefined};
|
||||
|
||||
let mappingIndex =
|
||||
findLastMappingIndexBefore(this.flattenedMappings, locationSegment, false, 0);
|
||||
if (mappingIndex < 0) {
|
||||
mappingIndex = 0;
|
||||
}
|
||||
const {originalSegment, originalSource, generatedSegment} =
|
||||
this.flattenedMappings[mappingIndex];
|
||||
const offset = locationSegment.position - generatedSegment.position;
|
||||
const offsetOriginalSegment =
|
||||
offsetSegment(originalSource.startOfLinePositions, originalSegment, offset);
|
||||
|
||||
return {
|
||||
file: originalSource.sourcePath,
|
||||
line: offsetOriginalSegment.line,
|
||||
column: offsetOriginalSegment.column,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten the parsed mappings for this source file, so that all the mappings are to pure original
|
||||
* source files with no transitive source maps.
|
||||
|
|
|
@ -518,6 +518,149 @@ runInEachFileSystem(() => {
|
|||
expect(aTocSourceMap.mappings).toEqual(aToBSourceMap.mappings);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOriginalLocation()', () => {
|
||||
it('should return null for source files with no flattened mappings', () => {
|
||||
const sourceFile =
|
||||
new SourceFile(_('/foo/src/index.js'), 'index contents', null, false, []);
|
||||
expect(sourceFile.getOriginalLocation(1, 1)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return offset locations in multiple flattened original source files', () => {
|
||||
const cSource = new SourceFile(_('/foo/src/c.js'), 'bcd123', null, false, []);
|
||||
const dSource = new SourceFile(_('/foo/src/d.js'), 'aef', null, false, []);
|
||||
|
||||
const bSourceMap: RawSourceMap = {
|
||||
mappings: encode([
|
||||
[
|
||||
[0, 1, 0, 0], // "a" is in d.js [source 1]
|
||||
[1, 0, 0, 0], // "bcd" are in c.js [source 0]
|
||||
[4, 1, 0, 1], // "ef" are in d.js [source 1]
|
||||
],
|
||||
]),
|
||||
names: [],
|
||||
sources: ['c.js', 'd.js'],
|
||||
version: 3
|
||||
};
|
||||
const bSource =
|
||||
new SourceFile(_('/foo/src/b.js'), 'abcdef', bSourceMap, false, [cSource, dSource]);
|
||||
|
||||
const aSourceMap: RawSourceMap = {
|
||||
mappings: encode([
|
||||
[
|
||||
[0, 0, 0, 0], [2, 0, 0, 3], // "c" is missing from first line
|
||||
],
|
||||
[
|
||||
[4, 0, 0, 2], // second line has new indentation, and starts with "c"
|
||||
[5, 0, 0, 5], // "f" is here
|
||||
],
|
||||
]),
|
||||
names: [],
|
||||
sources: ['b.js'],
|
||||
version: 3
|
||||
};
|
||||
const aSource =
|
||||
new SourceFile(_('/foo/src/a.js'), 'abde\n cf', aSourceMap, false, [bSource]);
|
||||
|
||||
// Line 0
|
||||
expect(aSource.getOriginalLocation(0, 0)) // a
|
||||
.toEqual({file: dSource.sourcePath, line: 0, column: 0});
|
||||
expect(aSource.getOriginalLocation(0, 1)) // b
|
||||
.toEqual({file: cSource.sourcePath, line: 0, column: 0});
|
||||
expect(aSource.getOriginalLocation(0, 2)) // d
|
||||
.toEqual({file: cSource.sourcePath, line: 0, column: 2});
|
||||
expect(aSource.getOriginalLocation(0, 3)) // e
|
||||
.toEqual({file: dSource.sourcePath, line: 0, column: 1});
|
||||
expect(aSource.getOriginalLocation(0, 4)) // off the end of the line
|
||||
.toEqual({file: dSource.sourcePath, line: 0, column: 2});
|
||||
|
||||
// Line 1
|
||||
expect(aSource.getOriginalLocation(1, 0)) // indent
|
||||
.toEqual({file: dSource.sourcePath, line: 0, column: 3});
|
||||
expect(aSource.getOriginalLocation(1, 1)) // indent
|
||||
.toEqual({file: dSource.sourcePath, line: 0, column: 4});
|
||||
expect(aSource.getOriginalLocation(1, 2)) // indent
|
||||
.toEqual({file: dSource.sourcePath, line: 0, column: 5});
|
||||
expect(aSource.getOriginalLocation(1, 3)) // indent
|
||||
.toEqual({file: dSource.sourcePath, line: 0, column: 6});
|
||||
expect(aSource.getOriginalLocation(1, 4)) // c
|
||||
.toEqual({file: cSource.sourcePath, line: 0, column: 1});
|
||||
expect(aSource.getOriginalLocation(1, 5)) // f
|
||||
.toEqual({file: dSource.sourcePath, line: 0, column: 2});
|
||||
expect(aSource.getOriginalLocation(1, 6)) // off the end of the line
|
||||
.toEqual({file: dSource.sourcePath, line: 0, column: 3});
|
||||
});
|
||||
|
||||
it('should return offset locations across multiple lines', () => {
|
||||
const originalSource =
|
||||
new SourceFile(_('/foo/src/original.js'), 'abcdef\nghijk\nlmnop', null, false, []);
|
||||
const generatedSourceMap: RawSourceMap = {
|
||||
mappings: encode([
|
||||
[
|
||||
[0, 0, 0, 0], // "ABC" [0,0] => [0,0]
|
||||
],
|
||||
[
|
||||
[0, 0, 1, 0], // "GHIJ" [1, 0] => [1,0]
|
||||
[4, 0, 0, 3], // "DEF" [1, 4] => [0,3]
|
||||
[7, 0, 1, 4], // "K" [1, 7] => [1,4]
|
||||
],
|
||||
[
|
||||
[0, 0, 2, 0], // "LMNOP" [2,0] => [2,0]
|
||||
],
|
||||
]),
|
||||
names: [],
|
||||
sources: ['original.js'],
|
||||
version: 3
|
||||
};
|
||||
const generatedSource = new SourceFile(
|
||||
_('/foo/src/generated.js'), 'ABC\nGHIJDEFK\nLMNOP', generatedSourceMap, false,
|
||||
[originalSource]);
|
||||
|
||||
// Line 0
|
||||
expect(generatedSource.getOriginalLocation(0, 0)) // A
|
||||
.toEqual({file: originalSource.sourcePath, line: 0, column: 0});
|
||||
expect(generatedSource.getOriginalLocation(0, 1)) // B
|
||||
.toEqual({file: originalSource.sourcePath, line: 0, column: 1});
|
||||
expect(generatedSource.getOriginalLocation(0, 2)) // C
|
||||
.toEqual({file: originalSource.sourcePath, line: 0, column: 2});
|
||||
expect(generatedSource.getOriginalLocation(0, 3)) // off the end of line 0
|
||||
.toEqual({file: originalSource.sourcePath, line: 0, column: 3});
|
||||
|
||||
// Line 1
|
||||
expect(generatedSource.getOriginalLocation(1, 0)) // G
|
||||
.toEqual({file: originalSource.sourcePath, line: 1, column: 0});
|
||||
expect(generatedSource.getOriginalLocation(1, 1)) // H
|
||||
.toEqual({file: originalSource.sourcePath, line: 1, column: 1});
|
||||
expect(generatedSource.getOriginalLocation(1, 2)) // I
|
||||
.toEqual({file: originalSource.sourcePath, line: 1, column: 2});
|
||||
expect(generatedSource.getOriginalLocation(1, 3)) // J
|
||||
.toEqual({file: originalSource.sourcePath, line: 1, column: 3});
|
||||
expect(generatedSource.getOriginalLocation(1, 4)) // D
|
||||
.toEqual({file: originalSource.sourcePath, line: 0, column: 3});
|
||||
expect(generatedSource.getOriginalLocation(1, 5)) // E
|
||||
.toEqual({file: originalSource.sourcePath, line: 0, column: 4});
|
||||
expect(generatedSource.getOriginalLocation(1, 6)) // F
|
||||
.toEqual({file: originalSource.sourcePath, line: 0, column: 5});
|
||||
expect(generatedSource.getOriginalLocation(1, 7)) // K
|
||||
.toEqual({file: originalSource.sourcePath, line: 1, column: 4});
|
||||
expect(generatedSource.getOriginalLocation(1, 8)) // off the end of line 1
|
||||
.toEqual({file: originalSource.sourcePath, line: 1, column: 5});
|
||||
|
||||
// Line 2
|
||||
expect(generatedSource.getOriginalLocation(2, 0)) // L
|
||||
.toEqual({file: originalSource.sourcePath, line: 2, column: 0});
|
||||
expect(generatedSource.getOriginalLocation(2, 1)) // M
|
||||
.toEqual({file: originalSource.sourcePath, line: 2, column: 1});
|
||||
expect(generatedSource.getOriginalLocation(2, 2)) // N
|
||||
.toEqual({file: originalSource.sourcePath, line: 2, column: 2});
|
||||
expect(generatedSource.getOriginalLocation(2, 3)) // O
|
||||
.toEqual({file: originalSource.sourcePath, line: 2, column: 3});
|
||||
expect(generatedSource.getOriginalLocation(2, 4)) // P
|
||||
.toEqual({file: originalSource.sourcePath, line: 2, column: 4});
|
||||
expect(generatedSource.getOriginalLocation(2, 5)) // off the end of line 2
|
||||
.toEqual({file: originalSource.sourcePath, line: 2, column: 5});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeStartOfLinePositions()', () => {
|
||||
|
|
Loading…
Reference in New Issue