From 47025e07ce6d84c07f6570c38c2135f282869e7e Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 11 Mar 2020 16:57:03 +0000 Subject: [PATCH] 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 --- .../ngcc/src/sourcemaps/segment_marker.ts | 3 +- .../ngcc/src/sourcemaps/source_file.ts | 41 ++- .../test/sourcemaps/segment_marker_spec.ts | 162 +++++------- .../ngcc/test/sourcemaps/source_file_spec.ts | 239 ++++++++++-------- 4 files changed, 232 insertions(+), 213 deletions(-) diff --git a/packages/compiler-cli/ngcc/src/sourcemaps/segment_marker.ts b/packages/compiler-cli/ngcc/src/sourcemaps/segment_marker.ts index 44126dfc4f..229a8d220d 100644 --- a/packages/compiler-cli/ngcc/src/sourcemaps/segment_marker.ts +++ b/packages/compiler-cli/ngcc/src/sourcemaps/segment_marker.ts @@ -16,6 +16,7 @@ export interface SegmentMarker { readonly line: number; readonly column: number; + next: SegmentMarker|undefined; } /** @@ -64,5 +65,5 @@ export function offsetSegment( line--; } const column = newPos - startOfLinePositions[line]; - return {line, column}; + return {line, column, next: undefined}; } diff --git a/packages/compiler-cli/ngcc/src/sourcemaps/source_file.ts b/packages/compiler-cli/ngcc/src/sourcemaps/source_file.ts index 136531231e..7d7805b14d 100644 --- a/packages/compiler-cli/ngcc/src/sourcemaps/source_file.ts +++ b/packages/compiler-cli/ngcc/src/sourcemaps/source_file.ts @@ -90,7 +90,7 @@ export class SourceFile { */ private flattenMappings(): Mapping[] { const mappings = parseMappings(this.rawMap, this.sources); - const originalSegmentsBySource = extractOriginalSegments(mappings); + ensureOriginalSegmentLinks(mappings); const flattenedMappings: Mapping[] = []; for (let mappingIndex = 0; mappingIndex < mappings.length; 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 [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 incomingEndIndex = originalSegments.indexOf(incomingStart) + 1; - const incomingEnd = incomingEndIndex < originalSegments.length ? - originalSegments[incomingEndIndex] : - undefined; + const incomingEnd = incomingStart.next; // The `outgoingStartIndex` and `outgoingEndIndex` are the indices of the range of mappings // that leave `b` that we are interested in merging with the aToBmapping. @@ -330,12 +325,19 @@ export function parseMappings( } const generatedColumn = rawMapping[0]; const name = rawMapping.length === 5 ? rawMap.names[rawMapping[4]] : undefined; - const mapping: Mapping = { - generatedSegment: {line: generatedLine, column: generatedColumn}, - originalSource, - originalSegment: {line: rawMapping[2] !, column: rawMapping[3] !}, name + const line = rawMapping[2] !; + const column = rawMapping[3] !; + const generatedSegment: SegmentMarker = { + 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 { + for (let i = 0; i < markers.length - 1; i++) { + markers[i].next = markers[i + 1]; + } + }); +} + export function computeStartOfLinePositions(str: string) { // 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 diff --git a/packages/compiler-cli/ngcc/test/sourcemaps/segment_marker_spec.ts b/packages/compiler-cli/ngcc/test/sourcemaps/segment_marker_spec.ts index a2ffc57e0d..a7549992bb 100644 --- a/packages/compiler-cli/ngcc/test/sourcemaps/segment_marker_spec.ts +++ b/packages/compiler-cli/ngcc/test/sourcemaps/segment_marker_spec.ts @@ -11,122 +11,88 @@ import {computeStartOfLinePositions} from '../../src/sourcemaps/source_file'; describe('SegmentMarker utils', () => { describe('compareSegments()', () => { it('should return 0 if the segments are the same', () => { - expect(compareSegments({line: 0, column: 0}, {line: 0, column: 0})).toEqual(0); - expect(compareSegments({line: 123, column: 0}, {line: 123, column: 0})).toEqual(0); - expect(compareSegments({line: 0, column: 45}, {line: 0, column: 45})).toEqual(0); - expect(compareSegments({line: 123, column: 45}, {line: 123, column: 45})).toEqual(0); + expect(compareSegments( + {line: 0, column: 0, next: undefined}, {line: 0, column: 0, next: undefined})) + .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', () => { - expect(compareSegments({line: 0, column: 0}, {line: 0, column: 45})).toBeLessThan(0); - expect(compareSegments({line: 123, column: 0}, {line: 123, column: 45})).toBeLessThan(0); - expect(compareSegments({line: 13, column: 45}, {line: 123, column: 45})).toBeLessThan(0); - expect(compareSegments({line: 13, column: 45}, {line: 123, column: 9})).toBeLessThan(0); + expect(compareSegments( + {line: 0, column: 0, next: undefined}, {line: 0, column: 45, next: undefined})) + .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', () => { - expect(compareSegments({line: 0, column: 45}, {line: 0, column: 0})).toBeGreaterThan(0); - expect(compareSegments({line: 123, column: 45}, {line: 123, column: 0})).toBeGreaterThan(0); - expect(compareSegments({line: 123, column: 45}, {line: 13, column: 45})).toBeGreaterThan(0); - expect(compareSegments({line: 123, column: 9}, {line: 13, column: 45})).toBeGreaterThan(0); + expect(compareSegments( + {line: 0, column: 45, next: undefined}, {line: 0, column: 0, next: undefined})) + .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()', () => { it('should return an identical marker if offset is 0', () => { const startOfLinePositions = - computeStartOfLinePositions('012345\n0123456789\r\n01234567\n0123456'); - const marker = {line: 2, column: 3}; + computeStartOfLinePositions('012345\n0123456789\r\n012*4567\n0123456'); + const marker = {line: 2, column: 3, next: undefined}; expect(offsetSegment(startOfLinePositions, marker, 0)).toBe(marker); }); it('should return a new marker offset by the given chars', () => { const startOfLinePositions = computeStartOfLinePositions('012345\n0123456789\r\n012*4567\n0123456'); - const marker = {line: 2, column: 3}; - expect(offsetSegment(startOfLinePositions, marker, 1)).toEqual({line: 2, column: 4}); - expect(offsetSegment(startOfLinePositions, marker, 2)).toEqual({line: 2, column: 5}); - expect(offsetSegment(startOfLinePositions, marker, 4)).toEqual({line: 2, column: 7}); - expect(offsetSegment(startOfLinePositions, marker, 6)).toEqual({line: 3, column: 0}); - expect(offsetSegment(startOfLinePositions, marker, 8)).toEqual({line: 3, column: 2}); - expect(offsetSegment(startOfLinePositions, marker, 20)).toEqual({line: 3, column: 14}); - expect(offsetSegment(startOfLinePositions, marker, -1)).toEqual({line: 2, column: 2}); - expect(offsetSegment(startOfLinePositions, marker, -2)).toEqual({line: 2, column: 1}); - expect(offsetSegment(startOfLinePositions, marker, -3)).toEqual({line: 2, column: 0}); - expect(offsetSegment(startOfLinePositions, marker, -4)).toEqual({line: 1, column: 10}); - expect(offsetSegment(startOfLinePositions, marker, -6)).toEqual({line: 1, column: 8}); - expect(offsetSegment(startOfLinePositions, marker, -16)).toEqual({line: 0, column: 5}); + const marker = {line: 2, column: 3, next: undefined}; + expect(offsetSegment(startOfLinePositions, marker, 1)) + .toEqual({line: 2, column: 4, next: undefined}); + expect(offsetSegment(startOfLinePositions, marker, 2)) + .toEqual({line: 2, column: 5, next: undefined}); + expect(offsetSegment(startOfLinePositions, marker, 4)) + .toEqual({line: 2, column: 7, next: undefined}); + expect(offsetSegment(startOfLinePositions, marker, 6)) + .toEqual({line: 3, column: 0, next: undefined}); + expect(offsetSegment(startOfLinePositions, marker, 8)) + .toEqual({line: 3, column: 2, next: undefined}); + expect(offsetSegment(startOfLinePositions, marker, 20)) + .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}); }); }); }); \ No newline at end of file diff --git a/packages/compiler-cli/ngcc/test/sourcemaps/source_file_spec.ts b/packages/compiler-cli/ngcc/test/sourcemaps/source_file_spec.ts index 18acb3f30b..ef2de6ed68 100644 --- a/packages/compiler-cli/ngcc/test/sourcemaps/source_file_spec.ts +++ b/packages/compiler-cli/ngcc/test/sourcemaps/source_file_spec.ts @@ -11,7 +11,7 @@ import {absoluteFrom} from '../../../src/ngtsc/file_system'; import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; import {RawSourceMap} from '../../src/sourcemaps/raw_source_map'; 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(() => { describe('SourceFile and utilities', () => { @@ -42,15 +42,15 @@ runInEachFileSystem(() => { const mappings = parseMappings(rawSourceMap, [originalSource]); expect(mappings).toEqual([ { - generatedSegment: {line: 0, column: 0}, + generatedSegment: {line: 0, column: 0, next: undefined}, originalSource, - originalSegment: {line: 0, column: 0}, + originalSegment: {line: 0, column: 0, next: undefined}, name: undefined }, { - generatedSegment: {line: 0, column: 6}, + generatedSegment: {line: 0, column: 6, next: undefined}, originalSource, - originalSegment: {line: 0, column: 3}, + originalSegment: {line: 0, column: 3, next: undefined}, name: undefined }, ]); @@ -78,9 +78,9 @@ runInEachFileSystem(() => { const originalSegments = extractOriginalSegments(parseMappings(rawSourceMap, [originalSource])); expect(originalSegments.get(originalSource)).toEqual([ - {line: 0, column: 0}, - {line: 0, column: 2}, - {line: 0, column: 3}, + {line: 0, column: 0, next: undefined}, + {line: 0, column: 2, next: undefined}, + {line: 0, column: 3, next: undefined}, ]); }); @@ -97,13 +97,13 @@ runInEachFileSystem(() => { const originalSegments = extractOriginalSegments(parseMappings(rawSourceMap, [sourceA, sourceB])); expect(originalSegments.get(sourceA)).toEqual([ - {line: 0, column: 0}, - {line: 0, column: 2}, + {line: 0, column: 0, next: undefined}, + {line: 0, column: 2, next: undefined}, ]); expect(originalSegments.get(sourceB)).toEqual([ - {line: 0, column: 2}, - {line: 0, column: 3}, - {line: 0, column: 5}, + {line: 0, column: 2, next: undefined}, + {line: 0, column: 3, next: undefined}, + {line: 0, column: 5, next: undefined}, ]); }); }); @@ -111,59 +111,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}; - const marker4: SegmentMarker = {line: 0, column: 40}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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); 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}; - const marker4: SegmentMarker = {line: 0, column: 30}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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); 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}; - const marker4: SegmentMarker = {line: 0, column: 40}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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); 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}; - const marker4: SegmentMarker = {line: 0, column: 40}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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); expect(index).toEqual(-1); @@ -172,11 +172,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}; - const marker4: SegmentMarker = {line: 0, column: 40}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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', () => { - const marker5: SegmentMarker = {line: 0, column: 50}; - const marker4: SegmentMarker = {line: 0, column: 30}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( marker => ({ generatedSegment: marker } as Mapping)); @@ -201,11 +201,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}; - const marker4: SegmentMarker = {line: 0, column: 40}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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', () => { - const marker5: SegmentMarker = {line: 0, column: 50}; - const marker4: SegmentMarker = {line: 0, column: 30}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( marker => ({ generatedSegment: marker } as Mapping)); @@ -231,59 +231,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}; - const marker4: SegmentMarker = {line: 0, column: 40}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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); 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}; - const marker4: SegmentMarker = {line: 0, column: 40}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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); 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}; - const marker4: SegmentMarker = {line: 0, column: 30}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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); 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}; - const marker4: SegmentMarker = {line: 0, column: 40}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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); 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', () => { - const marker5: SegmentMarker = {line: 0, column: 50}; - const marker4: SegmentMarker = {line: 0, column: 40}; - const marker3: SegmentMarker = {line: 0, column: 30}; - const marker2: SegmentMarker = {line: 0, column: 20}; - const marker1: SegmentMarker = {line: 0, column: 10}; + 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 mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map( 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); 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('flattenedMappings', () => { 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 sourceFile = new SourceFile( _('/foo/src/index.js'), 'abc123defg', rawSourceMap, false, [originalSource]); - expect(sourceFile.flattenedMappings) + expect(removeOriginalSegmentLinks(sourceFile.flattenedMappings)) .toEqual(parseMappings(rawSourceMap, [originalSource])); }); @@ -359,41 +381,41 @@ runInEachFileSystem(() => { const aSource = 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, - originalSegment: {line: 0, column: 0}, + originalSegment: {line: 0, column: 0, next: undefined}, name: undefined }, { - generatedSegment: {line: 0, column: 1}, + generatedSegment: {line: 0, column: 1, next: undefined}, originalSource: cSource, - originalSegment: {line: 0, column: 0}, + originalSegment: {line: 0, column: 0, next: undefined}, name: undefined }, { - generatedSegment: {line: 0, column: 2}, + generatedSegment: {line: 0, column: 2, next: undefined}, originalSource: cSource, - originalSegment: {line: 0, column: 2}, + originalSegment: {line: 0, column: 2, next: undefined}, name: undefined }, { - generatedSegment: {line: 0, column: 3}, + generatedSegment: {line: 0, column: 3, next: undefined}, originalSource: dSource, - originalSegment: {line: 0, column: 1}, + originalSegment: {line: 0, column: 1, next: undefined}, name: undefined }, { - generatedSegment: {line: 0, column: 4}, + generatedSegment: {line: 0, column: 4, next: undefined}, originalSource: cSource, - originalSegment: {line: 0, column: 1}, + originalSegment: {line: 0, column: 1, next: undefined}, name: undefined }, { - generatedSegment: {line: 0, column: 5}, + generatedSegment: {line: 0, column: 5, next: undefined}, originalSource: dSource, - originalSegment: {line: 0, column: 2}, + originalSegment: {line: 0, column: 2, next: undefined}, name: undefined }, ]); @@ -418,8 +440,21 @@ 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(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()', () => {