perf(ngcc): store the position of SegmentMarkers to avoid unnecessary computation (#36027)

Previously, calculations related to the position of and difference between
SegmentMarkers required extensive computation based around the line,
line start positions and columns of each segment.

PR Close #36027
This commit is contained in:
Pete Bacon Darwin 2020-03-11 19:57:38 +00:00 committed by Andrew Kushnir
parent 47025e07ce
commit 772bb5e742
4 changed files with 165 additions and 163 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;
readonly position: number;
next: SegmentMarker|undefined; next: SegmentMarker|undefined;
} }
@ -26,20 +27,7 @@ export interface SegmentMarker {
* and zero if they are at the same position. * and zero if they are at the same position.
*/ */
export function compareSegments(a: SegmentMarker, b: SegmentMarker): number { export function compareSegments(a: SegmentMarker, b: SegmentMarker): number {
return a.line === b.line ? a.column - b.column : a.line - b.line; return a.position - b.position;
}
/**
* Compute the difference between two segment markers in a source file.
*
* @param startOfLinePositions the position of the start of each line of content of the source file
* where we are computing the difference
* @param a the start marker
* @param b the end marker
* @returns the number of characters between the two segments `a` and `b`
*/
export function segmentDiff(startOfLinePositions: number[], a: SegmentMarker, b: SegmentMarker) {
return startOfLinePositions[b.line] - startOfLinePositions[a.line] + b.column - a.column;
} }
/** /**
@ -51,19 +39,19 @@ export function segmentDiff(startOfLinePositions: number[], a: SegmentMarker, b:
* @param offset the number of character to offset by. * @param offset the number of character to offset by.
*/ */
export function offsetSegment( export function offsetSegment(
startOfLinePositions: number[], marker: SegmentMarker, offset: number) { startOfLinePositions: number[], marker: SegmentMarker, offset: number): SegmentMarker {
if (offset === 0) { if (offset === 0) {
return marker; return marker;
} }
let line = marker.line; let line = marker.line;
const newPos = startOfLinePositions[line] + marker.column + offset; const position = marker.position + offset;
while (line < startOfLinePositions.length - 1 && startOfLinePositions[line + 1] <= newPos) { while (line < startOfLinePositions.length - 1 && startOfLinePositions[line + 1] <= position) {
line++; line++;
} }
while (line > 0 && startOfLinePositions[line] > newPos) { while (line > 0 && startOfLinePositions[line] > position) {
line--; line--;
} }
const column = newPos - startOfLinePositions[line]; const column = position - startOfLinePositions[line];
return {line, column, next: undefined}; return {line, column, position, next: undefined};
} }

View File

@ -9,7 +9,7 @@ import {removeComments, removeMapFileComments} from 'convert-source-map';
import {SourceMapMappings, SourceMapSegment, decode, encode} from 'sourcemap-codec'; import {SourceMapMappings, SourceMapSegment, decode, encode} from 'sourcemap-codec';
import {AbsoluteFsPath, dirname, relative} from '../../../src/ngtsc/file_system'; import {AbsoluteFsPath, dirname, relative} from '../../../src/ngtsc/file_system';
import {RawSourceMap} from './raw_source_map'; import {RawSourceMap} from './raw_source_map';
import {SegmentMarker, compareSegments, offsetSegment, segmentDiff} from './segment_marker'; import {SegmentMarker, compareSegments, offsetSegment} from './segment_marker';
export function removeSourceMapComments(contents: string): string { export function removeSourceMapComments(contents: string): string {
return removeMapFileComments(removeComments(contents)).replace(/\n\n$/, '\n'); return removeMapFileComments(removeComments(contents)).replace(/\n\n$/, '\n');
@ -89,7 +89,7 @@ export class SourceFile {
* source files with no transitive source maps. * source files with no transitive source maps.
*/ */
private flattenMappings(): Mapping[] { private flattenMappings(): Mapping[] {
const mappings = parseMappings(this.rawMap, this.sources); const mappings = parseMappings(this.rawMap, this.sources, this.startOfLinePositions);
ensureOriginalSegmentLinks(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++) {
@ -277,8 +277,7 @@ export function mergeMappings(generatedSource: SourceFile, ab: Mapping, bc: Mapp
// segment-marker" of B->C (4*): `1 - 4 = -3`. // segment-marker" of B->C (4*): `1 - 4 = -3`.
// Since it is negative we must increment the "generated segment-marker" with `3` to give [3,2]. // Since it is negative we must increment the "generated segment-marker" with `3` to give [3,2].
const diff = const diff = compareSegments(bc.generatedSegment, ab.originalSegment);
segmentDiff(ab.originalSource.startOfLinePositions, ab.originalSegment, bc.generatedSegment);
if (diff > 0) { if (diff > 0) {
return { return {
name, name,
@ -303,7 +302,8 @@ export function mergeMappings(generatedSource: SourceFile, ab: Mapping, bc: Mapp
* in the `sources` parameter. * in the `sources` parameter.
*/ */
export function parseMappings( export function parseMappings(
rawMap: RawSourceMap | null, sources: (SourceFile | null)[]): Mapping[] { rawMap: RawSourceMap | null, sources: (SourceFile | null)[],
generatedSourceStartOfLinePositions: number[]): Mapping[] {
if (rawMap === null) { if (rawMap === null) {
return []; return [];
} }
@ -330,11 +330,13 @@ export function parseMappings(
const generatedSegment: SegmentMarker = { const generatedSegment: SegmentMarker = {
line: generatedLine, line: generatedLine,
column: generatedColumn, column: generatedColumn,
position: generatedSourceStartOfLinePositions[generatedLine] + generatedColumn,
next: undefined, next: undefined,
}; };
const originalSegment: SegmentMarker = { const originalSegment: SegmentMarker = {
line, line,
column, column,
position: originalSource.startOfLinePositions[line] + column,
next: undefined, next: undefined,
}; };
mappings.push({name, generatedSegment, originalSegment, originalSource}); mappings.push({name, generatedSegment, originalSegment, originalSource});

View File

@ -5,54 +5,65 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {compareSegments, offsetSegment, segmentDiff} from '../../src/sourcemaps/segment_marker'; import {compareSegments, offsetSegment} from '../../src/sourcemaps/segment_marker';
import {computeStartOfLinePositions} from '../../src/sourcemaps/source_file'; 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( expect(compareSegments(
{line: 0, column: 0, next: undefined}, {line: 0, column: 0, next: undefined})) {line: 0, column: 0, position: 0, next: undefined},
{line: 0, column: 0, position: 0, next: undefined}))
.toEqual(0); .toEqual(0);
expect(compareSegments( expect(compareSegments(
{line: 123, column: 0, next: undefined}, {line: 123, column: 0, next: undefined})) {line: 123, column: 0, position: 200, next: undefined},
{line: 123, column: 0, position: 200, next: undefined}))
.toEqual(0); .toEqual(0);
expect(compareSegments( expect(compareSegments(
{line: 0, column: 45, next: undefined}, {line: 0, column: 45, next: undefined})) {line: 0, column: 45, position: 45, next: undefined},
{line: 0, column: 45, position: 45, next: undefined}))
.toEqual(0); .toEqual(0);
expect( expect(compareSegments(
compareSegments( {line: 123, column: 45, position: 245, next: undefined},
{line: 123, column: 45, next: undefined}, {line: 123, column: 45, next: undefined})) {line: 123, column: 45, position: 245, next: undefined}))
.toEqual(0); .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( expect(compareSegments(
{line: 0, column: 0, next: undefined}, {line: 0, column: 45, next: undefined})) {line: 0, column: 0, position: 0, next: undefined},
{line: 0, column: 45, position: 45, next: undefined}))
.toBeLessThan(0); .toBeLessThan(0);
expect(compareSegments( expect(compareSegments(
{line: 123, column: 0, next: undefined}, {line: 123, column: 45, next: undefined})) {line: 123, column: 0, position: 200, next: undefined},
{line: 123, column: 45, position: 245, next: undefined}))
.toBeLessThan(0); .toBeLessThan(0);
expect(compareSegments( expect(compareSegments(
{line: 13, column: 45, next: undefined}, {line: 123, column: 45, next: undefined})) {line: 13, column: 45, position: 75, next: undefined},
{line: 123, column: 45, position: 245, next: undefined}))
.toBeLessThan(0); .toBeLessThan(0);
expect(compareSegments( expect(compareSegments(
{line: 13, column: 45, next: undefined}, {line: 123, column: 9, next: undefined})) {line: 13, column: 45, position: 75, next: undefined},
{line: 123, column: 9, position: 209, next: undefined}))
.toBeLessThan(0); .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( expect(compareSegments(
{line: 0, column: 45, next: undefined}, {line: 0, column: 0, next: undefined})) {line: 0, column: 45, position: 45, next: undefined},
{line: 0, column: 0, position: 0, next: undefined}))
.toBeGreaterThan(0); .toBeGreaterThan(0);
expect(compareSegments( expect(compareSegments(
{line: 123, column: 45, next: undefined}, {line: 123, column: 0, next: undefined})) {line: 123, column: 45, position: 245, next: undefined},
{line: 123, column: 0, position: 200, next: undefined}))
.toBeGreaterThan(0); .toBeGreaterThan(0);
expect(compareSegments( expect(compareSegments(
{line: 123, column: 45, next: undefined}, {line: 13, column: 45, next: undefined})) {line: 123, column: 45, position: 245, next: undefined},
{line: 13, column: 45, position: 75, next: undefined}))
.toBeGreaterThan(0); .toBeGreaterThan(0);
expect(compareSegments( expect(compareSegments(
{line: 123, column: 9, next: undefined}, {line: 13, column: 45, next: undefined})) {line: 123, column: 9, position: 209, next: undefined},
{line: 13, column: 45, position: 75, next: undefined}))
.toBeGreaterThan(0); .toBeGreaterThan(0);
}); });
}); });
@ -61,38 +72,38 @@ describe('SegmentMarker utils', () => {
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\n012*4567\n0123456'); computeStartOfLinePositions('012345\n0123456789\r\n012*4567\n0123456');
const marker = {line: 2, column: 3, next: undefined}; const marker = {line: 2, column: 3, position: 20, 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, next: undefined}; const marker = {line: 2, column: 3, position: 21, next: undefined};
expect(offsetSegment(startOfLinePositions, marker, 1)) expect(offsetSegment(startOfLinePositions, marker, 1))
.toEqual({line: 2, column: 4, next: undefined}); .toEqual({line: 2, column: 4, position: 22, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, 2)) expect(offsetSegment(startOfLinePositions, marker, 2))
.toEqual({line: 2, column: 5, next: undefined}); .toEqual({line: 2, column: 5, position: 23, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, 4)) expect(offsetSegment(startOfLinePositions, marker, 4))
.toEqual({line: 2, column: 7, next: undefined}); .toEqual({line: 2, column: 7, position: 25, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, 6)) expect(offsetSegment(startOfLinePositions, marker, 6))
.toEqual({line: 3, column: 0, next: undefined}); .toEqual({line: 3, column: 0, position: 27, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, 8)) expect(offsetSegment(startOfLinePositions, marker, 8))
.toEqual({line: 3, column: 2, next: undefined}); .toEqual({line: 3, column: 2, position: 29, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, 20)) expect(offsetSegment(startOfLinePositions, marker, 20))
.toEqual({line: 3, column: 14, next: undefined}); .toEqual({line: 3, column: 14, position: 41, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -1)) expect(offsetSegment(startOfLinePositions, marker, -1))
.toEqual({line: 2, column: 2, next: undefined}); .toEqual({line: 2, column: 2, position: 20, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -2)) expect(offsetSegment(startOfLinePositions, marker, -2))
.toEqual({line: 2, column: 1, next: undefined}); .toEqual({line: 2, column: 1, position: 19, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -3)) expect(offsetSegment(startOfLinePositions, marker, -3))
.toEqual({line: 2, column: 0, next: undefined}); .toEqual({line: 2, column: 0, position: 18, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -4)) expect(offsetSegment(startOfLinePositions, marker, -4))
.toEqual({line: 1, column: 10, next: undefined}); .toEqual({line: 1, column: 10, position: 17, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -6)) expect(offsetSegment(startOfLinePositions, marker, -6))
.toEqual({line: 1, column: 8, next: undefined}); .toEqual({line: 1, column: 8, position: 15, next: undefined});
expect(offsetSegment(startOfLinePositions, marker, -16)) expect(offsetSegment(startOfLinePositions, marker, -16))
.toEqual({line: 0, column: 5, next: undefined}); .toEqual({line: 0, column: 5, position: 5, next: undefined});
}); });
}); });
}); });

View File

@ -21,13 +21,13 @@ runInEachFileSystem(() => {
describe('parseMappings()', () => { describe('parseMappings()', () => {
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', () => {
const mappings = parseMappings(null, []); const mappings = parseMappings(null, [], []);
expect(mappings).toEqual([]); expect(mappings).toEqual([]);
}); });
it('should be empty array for source files with no source map mappings', () => { it('should be empty array for source files with no source map mappings', () => {
const rawSourceMap: RawSourceMap = {mappings: '', names: [], sources: [], version: 3}; const rawSourceMap: RawSourceMap = {mappings: '', names: [], sources: [], version: 3};
const mappings = parseMappings(rawSourceMap, []); const mappings = parseMappings(rawSourceMap, [], []);
expect(mappings).toEqual([]); expect(mappings).toEqual([]);
}); });
@ -39,18 +39,18 @@ runInEachFileSystem(() => {
version: 3 version: 3
}; };
const originalSource = new SourceFile(_('/foo/src/a.js'), 'abcdefg', null, false, []); const originalSource = new SourceFile(_('/foo/src/a.js'), 'abcdefg', null, false, []);
const mappings = parseMappings(rawSourceMap, [originalSource]); const mappings = parseMappings(rawSourceMap, [originalSource], [0, 8]);
expect(mappings).toEqual([ expect(mappings).toEqual([
{ {
generatedSegment: {line: 0, column: 0, next: undefined}, generatedSegment: {line: 0, column: 0, position: 0, next: undefined},
originalSource, originalSource,
originalSegment: {line: 0, column: 0, next: undefined}, originalSegment: {line: 0, column: 0, position: 0, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 6, next: undefined}, generatedSegment: {line: 0, column: 6, position: 6, next: undefined},
originalSource, originalSource,
originalSegment: {line: 0, column: 3, next: undefined}, originalSegment: {line: 0, column: 3, position: 3, next: undefined},
name: undefined name: undefined
}, },
]); ]);
@ -58,12 +58,13 @@ runInEachFileSystem(() => {
}); });
describe('extractOriginalSegments()', () => { describe('extractOriginalSegments()', () => {
it('should return an empty Map for source files with no source map', it('should return an empty Map for source files with no source map', () => {
() => { expect(extractOriginalSegments(parseMappings(null, []))).toEqual(new Map()); }); expect(extractOriginalSegments(parseMappings(null, [], []))).toEqual(new Map());
});
it('should be empty Map for source files with no source map mappings', () => { it('should be empty Map for source files with no source map mappings', () => {
const rawSourceMap: RawSourceMap = {mappings: '', names: [], sources: [], version: 3}; const rawSourceMap: RawSourceMap = {mappings: '', names: [], sources: [], version: 3};
expect(extractOriginalSegments(parseMappings(rawSourceMap, []))).toEqual(new Map()); expect(extractOriginalSegments(parseMappings(rawSourceMap, [], []))).toEqual(new Map());
}); });
it('should parse the segments in ascending order of original position from the raw source map', it('should parse the segments in ascending order of original position from the raw source map',
@ -76,11 +77,11 @@ runInEachFileSystem(() => {
version: 3 version: 3
}; };
const originalSegments = const originalSegments =
extractOriginalSegments(parseMappings(rawSourceMap, [originalSource])); extractOriginalSegments(parseMappings(rawSourceMap, [originalSource], [0, 8]));
expect(originalSegments.get(originalSource)).toEqual([ expect(originalSegments.get(originalSource)).toEqual([
{line: 0, column: 0, next: undefined}, {line: 0, column: 0, position: 0, next: undefined},
{line: 0, column: 2, next: undefined}, {line: 0, column: 2, position: 2, next: undefined},
{line: 0, column: 3, next: undefined}, {line: 0, column: 3, position: 3, next: undefined},
]); ]);
}); });
@ -95,15 +96,15 @@ runInEachFileSystem(() => {
version: 3 version: 3
}; };
const originalSegments = const originalSegments =
extractOriginalSegments(parseMappings(rawSourceMap, [sourceA, sourceB])); extractOriginalSegments(parseMappings(rawSourceMap, [sourceA, sourceB], [0, 8]));
expect(originalSegments.get(sourceA)).toEqual([ expect(originalSegments.get(sourceA)).toEqual([
{line: 0, column: 0, next: undefined}, {line: 0, column: 0, position: 0, next: undefined},
{line: 0, column: 2, next: undefined}, {line: 0, column: 2, position: 2, next: undefined},
]); ]);
expect(originalSegments.get(sourceB)).toEqual([ expect(originalSegments.get(sourceB)).toEqual([
{line: 0, column: 2, next: undefined}, {line: 0, column: 2, position: 2, next: undefined},
{line: 0, column: 3, next: undefined}, {line: 0, column: 3, position: 3, next: undefined},
{line: 0, column: 5, next: undefined}, {line: 0, column: 5, position: 5, next: undefined},
]); ]);
}); });
}); });
@ -111,59 +112,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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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, next: undefined}; const marker: SegmentMarker = {line: 0, column: 35, position: 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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 30, position: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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, next: undefined}; const marker: SegmentMarker = {line: 0, column: 35, position: 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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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, next: undefined}; const marker: SegmentMarker = {line: 0, column: 60, position: 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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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, next: undefined}; const marker: SegmentMarker = {line: 0, column: 5, position: 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 +173,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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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 +187,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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 30, position: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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 +202,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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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 +216,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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 30, position: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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 +232,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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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, next: undefined}; const marker: SegmentMarker = {line: 0, column: 35, position: 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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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, next: undefined}; const marker: SegmentMarker = {line: 0, column: 30, position: 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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 30, position: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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, next: undefined}; const marker: SegmentMarker = {line: 0, column: 30, position: 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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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, next: undefined}; const marker: SegmentMarker = {line: 0, column: 25, position: 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 +292,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, next: undefined}; const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5}; const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4}; const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3}; const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2}; const marker1: SegmentMarker = {line: 0, column: 10, position: 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, next: undefined}; const marker: SegmentMarker = {line: 0, column: 30, position: 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);
@ -319,7 +320,7 @@ runInEachFileSystem(() => {
sources: ['a.js', 'b.js'], sources: ['a.js', 'b.js'],
version: 3 version: 3
}; };
const mappings = parseMappings(rawSourceMap, [sourceA, sourceB]); const mappings = parseMappings(rawSourceMap, [sourceA, sourceB], [0, 8]);
ensureOriginalSegmentLinks(mappings); ensureOriginalSegmentLinks(mappings);
expect(mappings[0].originalSegment.next).toBe(mappings[2].originalSegment); expect(mappings[0].originalSegment.next).toBe(mappings[2].originalSegment);
expect(mappings[1].originalSegment.next).toBe(mappings[3].originalSegment); expect(mappings[1].originalSegment.next).toBe(mappings[3].originalSegment);
@ -356,7 +357,7 @@ runInEachFileSystem(() => {
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(removeOriginalSegmentLinks(sourceFile.flattenedMappings)) expect(removeOriginalSegmentLinks(sourceFile.flattenedMappings))
.toEqual(parseMappings(rawSourceMap, [originalSource])); .toEqual(parseMappings(rawSourceMap, [originalSource], [0, 11]));
}); });
it('should merge mappings from flattened original source files', () => { it('should merge mappings from flattened original source files', () => {
@ -383,39 +384,39 @@ runInEachFileSystem(() => {
expect(removeOriginalSegmentLinks(aSource.flattenedMappings)).toEqual([ expect(removeOriginalSegmentLinks(aSource.flattenedMappings)).toEqual([
{ {
generatedSegment: {line: 0, column: 0, next: undefined}, generatedSegment: {line: 0, column: 0, position: 0, next: undefined},
originalSource: dSource, originalSource: dSource,
originalSegment: {line: 0, column: 0, next: undefined}, originalSegment: {line: 0, column: 0, position: 0, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 1, next: undefined}, generatedSegment: {line: 0, column: 1, position: 1, next: undefined},
originalSource: cSource, originalSource: cSource,
originalSegment: {line: 0, column: 0, next: undefined}, originalSegment: {line: 0, column: 0, position: 0, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 2, next: undefined}, generatedSegment: {line: 0, column: 2, position: 2, next: undefined},
originalSource: cSource, originalSource: cSource,
originalSegment: {line: 0, column: 2, next: undefined}, originalSegment: {line: 0, column: 2, position: 2, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 3, next: undefined}, generatedSegment: {line: 0, column: 3, position: 3, next: undefined},
originalSource: dSource, originalSource: dSource,
originalSegment: {line: 0, column: 1, next: undefined}, originalSegment: {line: 0, column: 1, position: 1, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 4, next: undefined}, generatedSegment: {line: 0, column: 4, position: 4, next: undefined},
originalSource: cSource, originalSource: cSource,
originalSegment: {line: 0, column: 1, next: undefined}, originalSegment: {line: 0, column: 1, position: 1, next: undefined},
name: undefined name: undefined
}, },
{ {
generatedSegment: {line: 0, column: 5, next: undefined}, generatedSegment: {line: 0, column: 5, position: 5, next: undefined},
originalSource: dSource, originalSource: dSource,
originalSegment: {line: 0, column: 2, next: undefined}, originalSegment: {line: 0, column: 2, position: 2, next: undefined},
name: undefined name: undefined
}, },
]); ]);
@ -441,7 +442,7 @@ 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(removeOriginalSegmentLinks(aSource.flattenedMappings)) expect(removeOriginalSegmentLinks(aSource.flattenedMappings))
.toEqual(parseMappings(aSourceMap, [bSource])); .toEqual(parseMappings(aSourceMap, [bSource], [0, 7]));
}); });
/** /**