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

@ -21,13 +21,13 @@ runInEachFileSystem(() => {
describe('parseMappings()', () => {
it('should be an empty array for source files with no source map', () => {
const mappings = parseMappings(null, []);
const mappings = parseMappings(null, [], []);
expect(mappings).toEqual([]);
});
it('should be empty array for source files with no source map mappings', () => {
const rawSourceMap: RawSourceMap = {mappings: '', names: [], sources: [], version: 3};
const mappings = parseMappings(rawSourceMap, []);
const mappings = parseMappings(rawSourceMap, [], []);
expect(mappings).toEqual([]);
});
@ -39,18 +39,18 @@ runInEachFileSystem(() => {
version: 3
};
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([
{
generatedSegment: {line: 0, column: 0, next: undefined},
generatedSegment: {line: 0, column: 0, position: 0, next: undefined},
originalSource,
originalSegment: {line: 0, column: 0, next: undefined},
originalSegment: {line: 0, column: 0, position: 0, next: undefined},
name: undefined
},
{
generatedSegment: {line: 0, column: 6, next: undefined},
generatedSegment: {line: 0, column: 6, position: 6, next: undefined},
originalSource,
originalSegment: {line: 0, column: 3, next: undefined},
originalSegment: {line: 0, column: 3, position: 3, next: undefined},
name: undefined
},
]);
@ -58,12 +58,13 @@ runInEachFileSystem(() => {
});
describe('extractOriginalSegments()', () => {
it('should return an empty Map for source files with no source map',
() => { expect(extractOriginalSegments(parseMappings(null, []))).toEqual(new Map()); });
it('should return an empty Map for source files with no source map', () => {
expect(extractOriginalSegments(parseMappings(null, [], []))).toEqual(new Map());
});
it('should be empty Map for source files with no source map mappings', () => {
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',
@ -76,11 +77,11 @@ runInEachFileSystem(() => {
version: 3
};
const originalSegments =
extractOriginalSegments(parseMappings(rawSourceMap, [originalSource]));
extractOriginalSegments(parseMappings(rawSourceMap, [originalSource], [0, 8]));
expect(originalSegments.get(originalSource)).toEqual([
{line: 0, column: 0, next: undefined},
{line: 0, column: 2, next: undefined},
{line: 0, column: 3, next: undefined},
{line: 0, column: 0, position: 0, next: undefined},
{line: 0, column: 2, position: 2, next: undefined},
{line: 0, column: 3, position: 3, next: undefined},
]);
});
@ -95,15 +96,15 @@ runInEachFileSystem(() => {
version: 3
};
const originalSegments =
extractOriginalSegments(parseMappings(rawSourceMap, [sourceA, sourceB]));
extractOriginalSegments(parseMappings(rawSourceMap, [sourceA, sourceB], [0, 8]));
expect(originalSegments.get(sourceA)).toEqual([
{line: 0, column: 0, next: undefined},
{line: 0, column: 2, next: undefined},
{line: 0, column: 0, position: 0, next: undefined},
{line: 0, column: 2, position: 2, next: undefined},
]);
expect(originalSegments.get(sourceB)).toEqual([
{line: 0, column: 2, next: undefined},
{line: 0, column: 3, next: undefined},
{line: 0, column: 5, next: undefined},
{line: 0, column: 2, position: 2, next: undefined},
{line: 0, column: 3, position: 3, next: undefined},
{line: 0, column: 5, position: 5, next: undefined},
]);
});
});
@ -111,59 +112,59 @@ runInEachFileSystem(() => {
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',
() => {
const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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);
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',
() => {
const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, position: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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);
expect(index).toEqual(3);
});
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 marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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);
expect(index).toEqual(4);
});
it('should return -1 if the segment marker is lower than all of them', () => {
const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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);
expect(index).toEqual(-1);
@ -172,11 +173,11 @@ runInEachFileSystem(() => {
describe('[exact match inclusive]', () => {
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 marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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',
() => {
const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, position: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
@ -201,11 +202,11 @@ runInEachFileSystem(() => {
describe('[exact match exclusive]', () => {
it('should find the preceding mapping index if there is a matching segment marker', () => {
const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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',
() => {
const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, position: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
@ -231,59 +232,59 @@ runInEachFileSystem(() => {
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',
() => {
const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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);
expect(index).toEqual(2);
});
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 marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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);
expect(index).toEqual(2);
});
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 marker4: SegmentMarker = {line: 0, column: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 30, position: 30, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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);
expect(index).toEqual(3);
});
it('should return -1 if the segment marker is lower than the lowerIndex hint', () => {
const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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);
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',
() => {
const marker5: SegmentMarker = {line: 0, column: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, next: marker2};
const marker5: SegmentMarker = {line: 0, column: 50, position: 50, next: undefined};
const marker4: SegmentMarker = {line: 0, column: 40, position: 40, next: marker5};
const marker3: SegmentMarker = {line: 0, column: 30, position: 30, next: marker4};
const marker2: SegmentMarker = {line: 0, column: 20, position: 20, next: marker3};
const marker1: SegmentMarker = {line: 0, column: 10, position: 10, next: marker2};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
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);
expect(index).toEqual(-1);
@ -319,7 +320,7 @@ runInEachFileSystem(() => {
sources: ['a.js', 'b.js'],
version: 3
};
const mappings = parseMappings(rawSourceMap, [sourceA, sourceB]);
const mappings = parseMappings(rawSourceMap, [sourceA, sourceB], [0, 8]);
ensureOriginalSegmentLinks(mappings);
expect(mappings[0].originalSegment.next).toBe(mappings[2].originalSegment);
expect(mappings[1].originalSegment.next).toBe(mappings[3].originalSegment);
@ -356,7 +357,7 @@ runInEachFileSystem(() => {
const sourceFile = new SourceFile(
_('/foo/src/index.js'), 'abc123defg', rawSourceMap, false, [originalSource]);
expect(removeOriginalSegmentLinks(sourceFile.flattenedMappings))
.toEqual(parseMappings(rawSourceMap, [originalSource]));
.toEqual(parseMappings(rawSourceMap, [originalSource], [0, 11]));
});
it('should merge mappings from flattened original source files', () => {
@ -383,39 +384,39 @@ runInEachFileSystem(() => {
expect(removeOriginalSegmentLinks(aSource.flattenedMappings)).toEqual([
{
generatedSegment: {line: 0, column: 0, next: undefined},
generatedSegment: {line: 0, column: 0, position: 0, next: undefined},
originalSource: dSource,
originalSegment: {line: 0, column: 0, next: undefined},
originalSegment: {line: 0, column: 0, position: 0, next: undefined},
name: undefined
},
{
generatedSegment: {line: 0, column: 1, next: undefined},
generatedSegment: {line: 0, column: 1, position: 1, next: undefined},
originalSource: cSource,
originalSegment: {line: 0, column: 0, next: undefined},
originalSegment: {line: 0, column: 0, position: 0, next: undefined},
name: undefined
},
{
generatedSegment: {line: 0, column: 2, next: undefined},
generatedSegment: {line: 0, column: 2, position: 2, next: undefined},
originalSource: cSource,
originalSegment: {line: 0, column: 2, next: undefined},
originalSegment: {line: 0, column: 2, position: 2, next: undefined},
name: undefined
},
{
generatedSegment: {line: 0, column: 3, next: undefined},
generatedSegment: {line: 0, column: 3, position: 3, next: undefined},
originalSource: dSource,
originalSegment: {line: 0, column: 1, next: undefined},
originalSegment: {line: 0, column: 1, position: 1, next: undefined},
name: undefined
},
{
generatedSegment: {line: 0, column: 4, next: undefined},
generatedSegment: {line: 0, column: 4, position: 4, next: undefined},
originalSource: cSource,
originalSegment: {line: 0, column: 1, next: undefined},
originalSegment: {line: 0, column: 1, position: 1, next: undefined},
name: undefined
},
{
generatedSegment: {line: 0, column: 5, next: undefined},
generatedSegment: {line: 0, column: 5, position: 5, next: undefined},
originalSource: dSource,
originalSegment: {line: 0, column: 2, next: undefined},
originalSegment: {line: 0, column: 2, position: 2, next: undefined},
name: undefined
},
]);
@ -441,7 +442,7 @@ runInEachFileSystem(() => {
// 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.)
expect(removeOriginalSegmentLinks(aSource.flattenedMappings))
.toEqual(parseMappings(aSourceMap, [bSource]));
.toEqual(parseMappings(aSourceMap, [bSource], [0, 7]));
});
/**