perf(ngcc): link segment markers for faster traversal (#36027)

The merging algorithm needs to find, for a given segment, what the next
segment in the source file is. This change modifies the `generatedSegment`
properties in the mappings so that they have a link directly to the following
segment.

PR Close #36027
This commit is contained in:
Pete Bacon Darwin 2020-03-11 16:57:03 +00:00 committed by Andrew Kushnir
parent e8900824dd
commit 47025e07ce
4 changed files with 232 additions and 213 deletions

View File

@ -16,6 +16,7 @@
export interface SegmentMarker { export interface SegmentMarker {
readonly line: number; readonly line: number;
readonly column: number; readonly column: number;
next: SegmentMarker|undefined;
} }
/** /**
@ -64,5 +65,5 @@ export function offsetSegment(
line--; line--;
} }
const column = newPos - startOfLinePositions[line]; const column = newPos - startOfLinePositions[line];
return {line, column}; return {line, column, next: undefined};
} }

View File

@ -90,7 +90,7 @@ export class SourceFile {
*/ */
private flattenMappings(): Mapping[] { private flattenMappings(): Mapping[] {
const mappings = parseMappings(this.rawMap, this.sources); const mappings = parseMappings(this.rawMap, this.sources);
const originalSegmentsBySource = extractOriginalSegments(mappings); ensureOriginalSegmentLinks(mappings);
const flattenedMappings: Mapping[] = []; const flattenedMappings: Mapping[] = [];
for (let mappingIndex = 0; mappingIndex < mappings.length; mappingIndex++) { for (let mappingIndex = 0; mappingIndex < mappings.length; mappingIndex++) {
const aToBmapping = mappings[mappingIndex]; const aToBmapping = mappings[mappingIndex];
@ -120,13 +120,8 @@ export class SourceFile {
// For mapping [0,0] the incoming start and end are 0 and 2 (i.e. the range a, b, c) // For mapping [0,0] the incoming start and end are 0 and 2 (i.e. the range a, b, c)
// For mapping [4,2] the incoming start and end are 2 and 5 (i.e. the range c, d, e, f) // For mapping [4,2] the incoming start and end are 2 and 5 (i.e. the range c, d, e, f)
// //
const originalSegments = originalSegmentsBySource.get(bSource) !;
const incomingStart = aToBmapping.originalSegment; const incomingStart = aToBmapping.originalSegment;
const incomingEndIndex = originalSegments.indexOf(incomingStart) + 1; const incomingEnd = incomingStart.next;
const incomingEnd = incomingEndIndex < originalSegments.length ?
originalSegments[incomingEndIndex] :
undefined;
// The `outgoingStartIndex` and `outgoingEndIndex` are the indices of the range of mappings // The `outgoingStartIndex` and `outgoingEndIndex` are the indices of the range of mappings
// that leave `b` that we are interested in merging with the aToBmapping. // that leave `b` that we are interested in merging with the aToBmapping.
@ -330,12 +325,19 @@ export function parseMappings(
} }
const generatedColumn = rawMapping[0]; const generatedColumn = rawMapping[0];
const name = rawMapping.length === 5 ? rawMap.names[rawMapping[4]] : undefined; const name = rawMapping.length === 5 ? rawMap.names[rawMapping[4]] : undefined;
const mapping: Mapping = { const line = rawMapping[2] !;
generatedSegment: {line: generatedLine, column: generatedColumn}, const column = rawMapping[3] !;
originalSource, const generatedSegment: SegmentMarker = {
originalSegment: {line: rawMapping[2] !, column: rawMapping[3] !}, name line: generatedLine,
column: generatedColumn,
next: undefined,
}; };
mappings.push(mapping); const originalSegment: SegmentMarker = {
line,
column,
next: undefined,
};
mappings.push({name, generatedSegment, originalSegment, originalSource});
} }
} }
} }
@ -364,6 +366,21 @@ export function extractOriginalSegments(mappings: Mapping[]): Map<SourceFile, Se
return originalSegments; return originalSegments;
} }
/**
* Update the original segments of each of the given `mappings` to include a link to the next
* segment in the source file.
*
* @param mappings the mappings whose segments should be updated
*/
export function ensureOriginalSegmentLinks(mappings: Mapping[]): void {
const segmentsBySource = extractOriginalSegments(mappings);
segmentsBySource.forEach(markers => {
for (let i = 0; i < markers.length - 1; i++) {
markers[i].next = markers[i + 1];
}
});
}
export function computeStartOfLinePositions(str: string) { export function computeStartOfLinePositions(str: string) {
// The `1` is to indicate a newline character between the lines. // The `1` is to indicate a newline character between the lines.
// Note that in the actual contents there could be more than one character that indicates a // Note that in the actual contents there could be more than one character that indicates a

View File

@ -11,122 +11,88 @@ import {computeStartOfLinePositions} from '../../src/sourcemaps/source_file';
describe('SegmentMarker utils', () => { describe('SegmentMarker utils', () => {
describe('compareSegments()', () => { describe('compareSegments()', () => {
it('should return 0 if the segments are the same', () => { it('should return 0 if the segments are the same', () => {
expect(compareSegments({line: 0, column: 0}, {line: 0, column: 0})).toEqual(0); expect(compareSegments(
expect(compareSegments({line: 123, column: 0}, {line: 123, column: 0})).toEqual(0); {line: 0, column: 0, next: undefined}, {line: 0, column: 0, next: undefined}))
expect(compareSegments({line: 0, column: 45}, {line: 0, column: 45})).toEqual(0); .toEqual(0);
expect(compareSegments({line: 123, column: 45}, {line: 123, column: 45})).toEqual(0); expect(compareSegments(
{line: 123, column: 0, next: undefined}, {line: 123, column: 0, next: undefined}))
.toEqual(0);
expect(compareSegments(
{line: 0, column: 45, next: undefined}, {line: 0, column: 45, next: undefined}))
.toEqual(0);
expect(
compareSegments(
{line: 123, column: 45, next: undefined}, {line: 123, column: 45, next: undefined}))
.toEqual(0);
}); });
it('should return a negative number if the first segment is before the second segment', () => { it('should return a negative number if the first segment is before the second segment', () => {
expect(compareSegments({line: 0, column: 0}, {line: 0, column: 45})).toBeLessThan(0); expect(compareSegments(
expect(compareSegments({line: 123, column: 0}, {line: 123, column: 45})).toBeLessThan(0); {line: 0, column: 0, next: undefined}, {line: 0, column: 45, next: undefined}))
expect(compareSegments({line: 13, column: 45}, {line: 123, column: 45})).toBeLessThan(0); .toBeLessThan(0);
expect(compareSegments({line: 13, column: 45}, {line: 123, column: 9})).toBeLessThan(0); expect(compareSegments(
{line: 123, column: 0, next: undefined}, {line: 123, column: 45, next: undefined}))
.toBeLessThan(0);
expect(compareSegments(
{line: 13, column: 45, next: undefined}, {line: 123, column: 45, next: undefined}))
.toBeLessThan(0);
expect(compareSegments(
{line: 13, column: 45, next: undefined}, {line: 123, column: 9, next: undefined}))
.toBeLessThan(0);
}); });
it('should return a positive number if the first segment is after the second segment', () => { it('should return a positive number if the first segment is after the second segment', () => {
expect(compareSegments({line: 0, column: 45}, {line: 0, column: 0})).toBeGreaterThan(0); expect(compareSegments(
expect(compareSegments({line: 123, column: 45}, {line: 123, column: 0})).toBeGreaterThan(0); {line: 0, column: 45, next: undefined}, {line: 0, column: 0, next: undefined}))
expect(compareSegments({line: 123, column: 45}, {line: 13, column: 45})).toBeGreaterThan(0); .toBeGreaterThan(0);
expect(compareSegments({line: 123, column: 9}, {line: 13, column: 45})).toBeGreaterThan(0); expect(compareSegments(
{line: 123, column: 45, next: undefined}, {line: 123, column: 0, next: undefined}))
.toBeGreaterThan(0);
expect(compareSegments(
{line: 123, column: 45, next: undefined}, {line: 13, column: 45, next: undefined}))
.toBeGreaterThan(0);
expect(compareSegments(
{line: 123, column: 9, next: undefined}, {line: 13, column: 45, next: undefined}))
.toBeGreaterThan(0);
}); });
}); });
describe('segmentDiff()', () => {
it('should return 0 if the segments are the same', () => {
const startOfLinePositions =
computeStartOfLinePositions('abcdef\nabcdefghj\nabcdefghijklm\nabcdef');
expect(segmentDiff(startOfLinePositions, {line: 0, column: 0}, {line: 0, column: 0}))
.toEqual(0);
expect(segmentDiff(startOfLinePositions, {line: 3, column: 0}, {line: 3, column: 0}))
.toEqual(0);
expect(segmentDiff(startOfLinePositions, {line: 0, column: 5}, {line: 0, column: 5}))
.toEqual(0);
expect(segmentDiff(startOfLinePositions, {line: 3, column: 5}, {line: 3, column: 5}))
.toEqual(0);
});
it('should return the column difference if the markers are on the same line', () => {
const startOfLinePositions =
computeStartOfLinePositions('abcdef\nabcdefghj\nabcdefghijklm\nabcdef');
expect(segmentDiff(startOfLinePositions, {line: 0, column: 0}, {line: 0, column: 3}))
.toEqual(3);
expect(segmentDiff(startOfLinePositions, {line: 1, column: 1}, {line: 1, column: 5}))
.toEqual(4);
expect(segmentDiff(startOfLinePositions, {line: 2, column: 5}, {line: 2, column: 1}))
.toEqual(-4);
expect(segmentDiff(startOfLinePositions, {line: 3, column: 3}, {line: 3, column: 0}))
.toEqual(-3);
});
it('should return the number of actual characters difference (including newline markers) if not on the same line',
() => {
let startOfLinePositions: number[];
startOfLinePositions = computeStartOfLinePositions('A12345\nB123456789');
expect(segmentDiff(startOfLinePositions, {line: 0, column: 0}, {line: 1, column: 0}))
.toEqual(6 + 1);
startOfLinePositions = computeStartOfLinePositions('012A45\n01234B6789');
expect(segmentDiff(startOfLinePositions, {line: 0, column: 3}, {line: 1, column: 5}))
.toEqual(3 + 1 + 5);
startOfLinePositions =
computeStartOfLinePositions('012345\n012345A789\n01234567\nB123456');
expect(segmentDiff(startOfLinePositions, {line: 1, column: 6}, {line: 3, column: 0}))
.toEqual(4 + 1 + 8 + 1 + 0);
startOfLinePositions =
computeStartOfLinePositions('012345\nA123456789\n01234567\n012B456');
expect(segmentDiff(startOfLinePositions, {line: 1, column: 0}, {line: 3, column: 3}))
.toEqual(10 + 1 + 8 + 1 + 3);
startOfLinePositions =
computeStartOfLinePositions('012345\nB123456789\nA1234567\n0123456');
expect(segmentDiff(startOfLinePositions, {line: 2, column: 0}, {line: 1, column: 0}))
.toEqual(0 - 1 - 10 + 0);
startOfLinePositions =
computeStartOfLinePositions('012345\n0123B56789\n01234567\n012A456');
expect(segmentDiff(startOfLinePositions, {line: 3, column: 3}, {line: 1, column: 4}))
.toEqual(-3 - 1 - 8 - 1 - 10 + 4);
startOfLinePositions =
computeStartOfLinePositions('B12345\n0123456789\n0123A567\n0123456');
expect(segmentDiff(startOfLinePositions, {line: 2, column: 4}, {line: 0, column: 0}))
.toEqual(-4 - 1 - 10 - 1 - 6 + 0);
startOfLinePositions =
computeStartOfLinePositions('0123B5\n0123456789\nA1234567\n0123456');
expect(segmentDiff(startOfLinePositions, {line: 2, column: 0}, {line: 0, column: 4}))
.toEqual(0 - 1 - 10 - 1 - 6 + 4);
});
});
describe('offsetSegment()', () => { describe('offsetSegment()', () => {
it('should return an identical marker if offset is 0', () => { it('should return an identical marker if offset is 0', () => {
const startOfLinePositions = const startOfLinePositions =
computeStartOfLinePositions('012345\n0123456789\r\n01234567\n0123456'); computeStartOfLinePositions('012345\n0123456789\r\n012*4567\n0123456');
const marker = {line: 2, column: 3}; const marker = {line: 2, column: 3, next: undefined};
expect(offsetSegment(startOfLinePositions, marker, 0)).toBe(marker); expect(offsetSegment(startOfLinePositions, marker, 0)).toBe(marker);
}); });
it('should return a new marker offset by the given chars', () => { it('should return a new marker offset by the given chars', () => {
const startOfLinePositions = const startOfLinePositions =
computeStartOfLinePositions('012345\n0123456789\r\n012*4567\n0123456'); computeStartOfLinePositions('012345\n0123456789\r\n012*4567\n0123456');
const marker = {line: 2, column: 3}; const marker = {line: 2, column: 3, next: undefined};
expect(offsetSegment(startOfLinePositions, marker, 1)).toEqual({line: 2, column: 4}); expect(offsetSegment(startOfLinePositions, marker, 1))
expect(offsetSegment(startOfLinePositions, marker, 2)).toEqual({line: 2, column: 5}); .toEqual({line: 2, column: 4, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, 4)).toEqual({line: 2, column: 7}); expect(offsetSegment(startOfLinePositions, marker, 2))
expect(offsetSegment(startOfLinePositions, marker, 6)).toEqual({line: 3, column: 0}); .toEqual({line: 2, column: 5, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, 8)).toEqual({line: 3, column: 2}); expect(offsetSegment(startOfLinePositions, marker, 4))
expect(offsetSegment(startOfLinePositions, marker, 20)).toEqual({line: 3, column: 14}); .toEqual({line: 2, column: 7, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -1)).toEqual({line: 2, column: 2}); expect(offsetSegment(startOfLinePositions, marker, 6))
expect(offsetSegment(startOfLinePositions, marker, -2)).toEqual({line: 2, column: 1}); .toEqual({line: 3, column: 0, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -3)).toEqual({line: 2, column: 0}); expect(offsetSegment(startOfLinePositions, marker, 8))
expect(offsetSegment(startOfLinePositions, marker, -4)).toEqual({line: 1, column: 10}); .toEqual({line: 3, column: 2, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -6)).toEqual({line: 1, column: 8}); expect(offsetSegment(startOfLinePositions, marker, 20))
expect(offsetSegment(startOfLinePositions, marker, -16)).toEqual({line: 0, column: 5}); .toEqual({line: 3, column: 14, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -1))
.toEqual({line: 2, column: 2, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -2))
.toEqual({line: 2, column: 1, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -3))
.toEqual({line: 2, column: 0, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -4))
.toEqual({line: 1, column: 10, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -6))
.toEqual({line: 1, column: 8, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -16))
.toEqual({line: 0, column: 5, next: undefined});
}); });
}); });
}); });

View File

@ -11,7 +11,7 @@ import {absoluteFrom} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {RawSourceMap} from '../../src/sourcemaps/raw_source_map'; import {RawSourceMap} from '../../src/sourcemaps/raw_source_map';
import {SegmentMarker} from '../../src/sourcemaps/segment_marker'; import {SegmentMarker} from '../../src/sourcemaps/segment_marker';
import {Mapping, SourceFile, computeStartOfLinePositions, extractOriginalSegments, findLastMappingIndexBefore, parseMappings} from '../../src/sourcemaps/source_file'; import {Mapping, SourceFile, computeStartOfLinePositions, ensureOriginalSegmentLinks, extractOriginalSegments, findLastMappingIndexBefore, parseMappings} from '../../src/sourcemaps/source_file';
runInEachFileSystem(() => { runInEachFileSystem(() => {
describe('SourceFile and utilities', () => { describe('SourceFile and utilities', () => {
@ -42,15 +42,15 @@ runInEachFileSystem(() => {
const mappings = parseMappings(rawSourceMap, [originalSource]); const mappings = parseMappings(rawSourceMap, [originalSource]);
expect(mappings).toEqual([ expect(mappings).toEqual([
{ {
generatedSegment: {line: 0, column: 0}, generatedSegment: {line: 0, column: 0, next: undefined},
originalSource, originalSource,
originalSegment: {line: 0, column: 0}, originalSegment: {line: 0, column: 0, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 6}, generatedSegment: {line: 0, column: 6, next: undefined},
originalSource, originalSource,
originalSegment: {line: 0, column: 3}, originalSegment: {line: 0, column: 3, next: undefined},
name: undefined name: undefined
}, },
]); ]);
@ -78,9 +78,9 @@ runInEachFileSystem(() => {
const originalSegments = const originalSegments =
extractOriginalSegments(parseMappings(rawSourceMap, [originalSource])); extractOriginalSegments(parseMappings(rawSourceMap, [originalSource]));
expect(originalSegments.get(originalSource)).toEqual([ expect(originalSegments.get(originalSource)).toEqual([
{line: 0, column: 0}, {line: 0, column: 0, next: undefined},
{line: 0, column: 2}, {line: 0, column: 2, next: undefined},
{line: 0, column: 3}, {line: 0, column: 3, next: undefined},
]); ]);
}); });
@ -97,13 +97,13 @@ runInEachFileSystem(() => {
const originalSegments = const originalSegments =
extractOriginalSegments(parseMappings(rawSourceMap, [sourceA, sourceB])); extractOriginalSegments(parseMappings(rawSourceMap, [sourceA, sourceB]));
expect(originalSegments.get(sourceA)).toEqual([ expect(originalSegments.get(sourceA)).toEqual([
{line: 0, column: 0}, {line: 0, column: 0, next: undefined},
{line: 0, column: 2}, {line: 0, column: 2, next: undefined},
]); ]);
expect(originalSegments.get(sourceB)).toEqual([ expect(originalSegments.get(sourceB)).toEqual([
{line: 0, column: 2}, {line: 0, column: 2, next: undefined},
{line: 0, column: 3}, {line: 0, column: 3, next: undefined},
{line: 0, column: 5}, {line: 0, column: 5, next: undefined},
]); ]);
}); });
}); });
@ -111,59 +111,59 @@ runInEachFileSystem(() => {
describe('findLastMappingIndexBefore', () => { describe('findLastMappingIndexBefore', () => {
it('should find the highest mapping index that has a segment marker below the given one if there is not an exact match', it('should find the highest mapping index that has a segment marker below the given one if there is not an exact match',
() => { () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40}; const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 35}; const marker: SegmentMarker = {line: 0, column: 35, next: undefined};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0); const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0);
expect(index).toEqual(2); expect(index).toEqual(2);
}); });
it('should find the highest mapping index that has a segment marker (when there are duplicates) below the given one if there is not an exact match', it('should find the highest mapping index that has a segment marker (when there are duplicates) below the given one if there is not an exact match',
() => { () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30}; const marker4: SegmentMarker = {line: 0, column: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 35}; const marker: SegmentMarker = {line: 0, column: 35, next: undefined};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0); const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0);
expect(index).toEqual(3); expect(index).toEqual(3);
}); });
it('should find the last mapping if the segment marker is higher than all of them', () => { it('should find the last mapping if the segment marker is higher than all of them', () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40}; const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 60}; const marker: SegmentMarker = {line: 0, column: 60, next: undefined};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0); const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0);
expect(index).toEqual(4); expect(index).toEqual(4);
}); });
it('should return -1 if the segment marker is lower than all of them', () => { it('should return -1 if the segment marker is lower than all of them', () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40}; const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 5}; const marker: SegmentMarker = {line: 0, column: 5, next: undefined};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0); const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0);
expect(index).toEqual(-1); expect(index).toEqual(-1);
@ -172,11 +172,11 @@ runInEachFileSystem(() => {
describe('[exact match inclusive]', () => { describe('[exact match inclusive]', () => {
it('should find the matching segment marker mapping index if there is only one of them', it('should find the matching segment marker mapping index if there is only one of them',
() => { () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40}; const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
@ -186,11 +186,11 @@ runInEachFileSystem(() => {
it('should find the highest matching segment marker mapping index if there is more than one of them', it('should find the highest matching segment marker mapping index if there is more than one of them',
() => { () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30}; const marker4: SegmentMarker = {line: 0, column: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
@ -201,11 +201,11 @@ runInEachFileSystem(() => {
describe('[exact match exclusive]', () => { describe('[exact match exclusive]', () => {
it('should find the preceding mapping index if there is a matching segment marker', () => { it('should find the preceding mapping index if there is a matching segment marker', () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40}; const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
@ -215,11 +215,11 @@ runInEachFileSystem(() => {
it('should find the highest preceding mapping index if there is more than one matching segment marker', it('should find the highest preceding mapping index if there is more than one matching segment marker',
() => { () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30}; const marker4: SegmentMarker = {line: 0, column: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
@ -231,59 +231,59 @@ runInEachFileSystem(() => {
describe('[with lowerIndex hint', () => { describe('[with lowerIndex hint', () => {
it('should find the highest mapping index above the lowerIndex hint that has a segment marker below the given one if there is not an exact match', it('should find the highest mapping index above the lowerIndex hint that has a segment marker below the given one if there is not an exact match',
() => { () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40}; const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 35}; const marker: SegmentMarker = {line: 0, column: 35, next: undefined};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 1); const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 1);
expect(index).toEqual(2); expect(index).toEqual(2);
}); });
it('should return the lowerIndex mapping index if there is a single exact match and we are not exclusive', it('should return the lowerIndex mapping index if there is a single exact match and we are not exclusive',
() => { () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40}; const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 30}; const marker: SegmentMarker = {line: 0, column: 30, next: undefined};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 2); const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 2);
expect(index).toEqual(2); expect(index).toEqual(2);
}); });
it('should return the lowerIndex mapping index if there are multiple exact matches and we are not exclusive', it('should return the lowerIndex mapping index if there are multiple exact matches and we are not exclusive',
() => { () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30}; const marker4: SegmentMarker = {line: 0, column: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 30}; const marker: SegmentMarker = {line: 0, column: 30, next: undefined};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 3); const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 3);
expect(index).toEqual(3); expect(index).toEqual(3);
}); });
it('should return -1 if the segment marker is lower than the lowerIndex hint', () => { it('should return -1 if the segment marker is lower than the lowerIndex hint', () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40}; const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 25}; const marker: SegmentMarker = {line: 0, column: 25, next: undefined};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 2); const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 2);
expect(index).toEqual(-1); expect(index).toEqual(-1);
@ -291,15 +291,15 @@ runInEachFileSystem(() => {
it('should return -1 if the segment marker is equal to the lowerIndex hint and we are exclusive', it('should return -1 if the segment marker is equal to the lowerIndex hint and we are exclusive',
() => { () => {
const marker5: SegmentMarker = {line: 0, column: 50}; const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40}; const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30}; const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20}; const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10}; const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping)); marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 30}; const marker: SegmentMarker = {line: 0, column: 30, next: undefined};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ true, 2); const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ true, 2);
expect(index).toEqual(-1); expect(index).toEqual(-1);
@ -307,6 +307,28 @@ runInEachFileSystem(() => {
}); });
}); });
describe('ensureOriginalSegmentLinks', () => {
it('should add `next` properties to each segment that point to the next segment in the same source file',
() => {
const sourceA = new SourceFile(_('/foo/src/a.js'), 'abcdefg', null, false, []);
const sourceB = new SourceFile(_('/foo/src/b.js'), '1234567', null, false, []);
const rawSourceMap: RawSourceMap = {
mappings:
encode([[[0, 0, 0, 0], [2, 1, 0, 3], [4, 0, 0, 2], [5, 1, 0, 5], [6, 1, 0, 2]]]),
names: [],
sources: ['a.js', 'b.js'],
version: 3
};
const mappings = parseMappings(rawSourceMap, [sourceA, sourceB]);
ensureOriginalSegmentLinks(mappings);
expect(mappings[0].originalSegment.next).toBe(mappings[2].originalSegment);
expect(mappings[1].originalSegment.next).toBe(mappings[3].originalSegment);
expect(mappings[2].originalSegment.next).toBeUndefined();
expect(mappings[3].originalSegment.next).toBeUndefined();
expect(mappings[4].originalSegment.next).toBe(mappings[1].originalSegment);
});
});
describe('SourceFile', () => { describe('SourceFile', () => {
describe('flattenedMappings', () => { describe('flattenedMappings', () => {
it('should be an empty array for source files with no source map', () => { it('should be an empty array for source files with no source map', () => {
@ -333,7 +355,7 @@ runInEachFileSystem(() => {
const originalSource = new SourceFile(_('/foo/src/a.js'), 'abcdefg', null, false, []); const originalSource = new SourceFile(_('/foo/src/a.js'), 'abcdefg', null, false, []);
const sourceFile = new SourceFile( const sourceFile = new SourceFile(
_('/foo/src/index.js'), 'abc123defg', rawSourceMap, false, [originalSource]); _('/foo/src/index.js'), 'abc123defg', rawSourceMap, false, [originalSource]);
expect(sourceFile.flattenedMappings) expect(removeOriginalSegmentLinks(sourceFile.flattenedMappings))
.toEqual(parseMappings(rawSourceMap, [originalSource])); .toEqual(parseMappings(rawSourceMap, [originalSource]));
}); });
@ -359,41 +381,41 @@ runInEachFileSystem(() => {
const aSource = const aSource =
new SourceFile(_('/foo/src/a.js'), 'abdecf', aSourceMap, false, [bSource]); new SourceFile(_('/foo/src/a.js'), 'abdecf', aSourceMap, false, [bSource]);
expect(aSource.flattenedMappings).toEqual([ expect(removeOriginalSegmentLinks(aSource.flattenedMappings)).toEqual([
{ {
generatedSegment: {line: 0, column: 0}, generatedSegment: {line: 0, column: 0, next: undefined},
originalSource: dSource, originalSource: dSource,
originalSegment: {line: 0, column: 0}, originalSegment: {line: 0, column: 0, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 1}, generatedSegment: {line: 0, column: 1, next: undefined},
originalSource: cSource, originalSource: cSource,
originalSegment: {line: 0, column: 0}, originalSegment: {line: 0, column: 0, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 2}, generatedSegment: {line: 0, column: 2, next: undefined},
originalSource: cSource, originalSource: cSource,
originalSegment: {line: 0, column: 2}, originalSegment: {line: 0, column: 2, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 3}, generatedSegment: {line: 0, column: 3, next: undefined},
originalSource: dSource, originalSource: dSource,
originalSegment: {line: 0, column: 1}, originalSegment: {line: 0, column: 1, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 4}, generatedSegment: {line: 0, column: 4, next: undefined},
originalSource: cSource, originalSource: cSource,
originalSegment: {line: 0, column: 1}, originalSegment: {line: 0, column: 1, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 5}, generatedSegment: {line: 0, column: 5, next: undefined},
originalSource: dSource, originalSource: dSource,
originalSegment: {line: 0, column: 2}, originalSegment: {line: 0, column: 2, next: undefined},
name: undefined name: undefined
}, },
]); ]);
@ -418,8 +440,21 @@ runInEachFileSystem(() => {
// These flattened mappings are just the mappings from a to b. // These flattened mappings are just the mappings from a to b.
// (The mappings to c are dropped since there is no source file to map to.) // (The mappings to c are dropped since there is no source file to map to.)
expect(aSource.flattenedMappings).toEqual(parseMappings(aSourceMap, [bSource])); expect(removeOriginalSegmentLinks(aSource.flattenedMappings))
.toEqual(parseMappings(aSourceMap, [bSource]));
}); });
/**
* Clean out the links between original segments of each of the given `mappings`.
*
* @param mappings the mappings whose segments are to be cleaned.
*/
function removeOriginalSegmentLinks(mappings: Mapping[]) {
for (const mapping of mappings) {
mapping.originalSegment.next = undefined;
}
return mappings;
}
}); });
describe('renderFlattenedSourceMap()', () => { describe('renderFlattenedSourceMap()', () => {