Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit daa2a08

Browse files
petebacondarwinAndrewKushnir
authored andcommitted
perf(ngcc): use line start positions for computing offsets in source-map flattening (#36027)
By computing and caching the start of each line, rather than the length of each line, we can save a lot of duplicated computation in the `segmentDiff()` and `offsetSegment()` functions. PR Close #36027
1 parent e6167cf commit daa2a08

File tree

4 files changed

+112
-92
lines changed

4 files changed

+112
-92
lines changed

‎packages/compiler-cli/ngcc/src/sourcemaps/segment_marker.ts

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,61 +28,41 @@ export function compareSegments(a: SegmentMarker, b: SegmentMarker): number {
2828
return a.line === b.line ? a.column - b.column : a.line - b.line;
2929
}
3030

31-
// The `1` is to indicate a newline character between the lines.
32-
// Note that in the actual contents there could be more than one character that indicates a newline
33-
// - e.g. \r\n - but that is not important here since segment-markers are in line/column pairs and
34-
// so differences in length due to extra `\r` characters do not affect the algorithms.
35-
const NEWLINE_MARKER_OFFSET = 1;
36-
3731
/**
3832
* Compute the difference between two segment markers in a source file.
3933
*
40-
* @param lineLengths the lengths of each line of content of the source file where we are computing
41-
* the difference
34+
* @param startOfLinePositions the position of the start of each line of content of the source file
35+
* where we are computing the difference
4236
* @param a the start marker
4337
* @param b the end marker
4438
* @returns the number of characters between the two segments `a` and `b`
4539
*/
46-
export function segmentDiff(lineLengths: number[], a: SegmentMarker, b: SegmentMarker) {
47-
let diff = b.column - a.column;
48-
49-
// Deal with `a` being before `b`
50-
for (let lineIndex = a.line; lineIndex < b.line; lineIndex++) {
51-
diff += lineLengths[lineIndex] + NEWLINE_MARKER_OFFSET;
52-
}
53-
54-
// Deal with `a` being after `b`
55-
for (let lineIndex = a.line - 1; lineIndex >= b.line; lineIndex--) {
56-
// The `+ 1` is the newline character between the lines
57-
diff -= lineLengths[lineIndex] + NEWLINE_MARKER_OFFSET;
58-
}
59-
return diff;
40+
export function segmentDiff(startOfLinePositions: number[], a: SegmentMarker, b: SegmentMarker) {
41+
return startOfLinePositions[b.line] - startOfLinePositions[a.line] + b.column - a.column;
6042
}
6143

6244
/**
6345
* Return a new segment-marker that is offset by the given number of characters.
6446
*
65-
* @param lineLengths The length of each line in the source file whose segment-marker we are
66-
* offsetting.
67-
* @param marker The segment to offset.
68-
* @param offset The number of character to offset by.
47+
* @param startOfLinePositions the position of the start of each line of content of the source file
48+
* whose segment-marker we are offsetting.
49+
* @param marker the segment to offset.
50+
* @param offset the number of character to offset by.
6951
*/
70-
export function offsetSegment(lineLengths: number[], marker: SegmentMarker, offset: number) {
52+
export function offsetSegment(
53+
startOfLinePositions: number[], marker: SegmentMarker, offset: number) {
7154
if (offset === 0) {
7255
return marker;
7356
}
7457

7558
let line = marker.line;
76-
let column = marker.column + offset;
77-
78-
while (line < lineLengths.length - 1 && column > lineLengths[line]) {
79-
column -= lineLengths[line] + NEWLINE_MARKER_OFFSET;
59+
const newPos = startOfLinePositions[line] + marker.column + offset;
60+
while (line < startOfLinePositions.length - 1 && startOfLinePositions[line + 1] <= newPos) {
8061
line++;
8162
}
82-
while (line > 0 && column < 0) {
63+
while (line > 0 && startOfLinePositions[line] > newPos) {
8364
line--;
84-
column += lineLengths[line] + NEWLINE_MARKER_OFFSET;
8565
}
86-
66+
const column = newPos - startOfLinePositions[line];
8767
return {line, column};
8868
}

‎packages/compiler-cli/ngcc/src/sourcemaps/source_file.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class SourceFile {
2424
* pure original source files).
2525
*/
2626
readonly flattenedMappings: Mapping[];
27-
readonly lineLengths: number[];
27+
readonly startOfLinePositions: number[];
2828

2929
constructor(
3030
/** The path to this source file. */
@@ -38,7 +38,7 @@ export class SourceFile {
3838
/** Any source files referenced by the raw source map associated with this source file. */
3939
readonly sources: (SourceFile|null)[]) {
4040
this.contents = removeSourceMapComments(contents);
41-
this.lineLengths = computeLineLengths(this.contents);
41+
this.startOfLinePositions = computeStartOfLinePositions(this.contents);
4242
this.flattenedMappings = this.flattenMappings();
4343
}
4444

@@ -282,11 +282,13 @@ export function mergeMappings(generatedSource: SourceFile, ab: Mapping, bc: Mapp
282282
// segment-marker" of B->C (4*): `1 - 4 = -3`.
283283
// Since it is negative we must increment the "generated segment-marker" with `3` to give [3,2].
284284

285-
const diff = segmentDiff(ab.originalSource.lineLengths, ab.originalSegment, bc.generatedSegment);
285+
const diff =
286+
segmentDiff(ab.originalSource.startOfLinePositions, ab.originalSegment, bc.generatedSegment);
286287
if (diff > 0) {
287288
return {
288289
name,
289-
generatedSegment: offsetSegment(generatedSource.lineLengths, ab.generatedSegment, diff),
290+
generatedSegment:
291+
offsetSegment(generatedSource.startOfLinePositions, ab.generatedSegment, diff),
290292
originalSource: bc.originalSource,
291293
originalSegment: bc.originalSegment,
292294
};
@@ -295,7 +297,8 @@ export function mergeMappings(generatedSource: SourceFile, ab: Mapping, bc: Mapp
295297
name,
296298
generatedSegment: ab.generatedSegment,
297299
originalSource: bc.originalSource,
298-
originalSegment: offsetSegment(bc.originalSource.lineLengths, bc.originalSegment, -diff),
300+
originalSegment:
301+
offsetSegment(bc.originalSource.startOfLinePositions, bc.originalSegment, -diff),
299302
};
300303
}
301304
}
@@ -361,6 +364,21 @@ export function extractOriginalSegments(mappings: Mapping[]): Map<SourceFile, Se
361364
return originalSegments;
362365
}
363366

364-
export function computeLineLengths(str: string): number[] {
367+
export function computeStartOfLinePositions(str: string) {
368+
// The `1` is to indicate a newline character between the lines.
369+
// Note that in the actual contents there could be more than one character that indicates a
370+
// newline
371+
// - e.g. \r\n - but that is not important here since segment-markers are in line/column pairs and
372+
// so differences in length due to extra `\r` characters do not affect the algorithms.
373+
const NEWLINE_MARKER_OFFSET = 1;
374+
const lineLengths = computeLineLengths(str);
375+
const startPositions = [0]; // First line starts at position 0
376+
for (let i = 0; i < lineLengths.length - 1; i++) {
377+
startPositions.push(startPositions[i] + lineLengths[i] + NEWLINE_MARKER_OFFSET);
378+
}
379+
return startPositions;
380+
}
381+
382+
function computeLineLengths(str: string): number[] {
365383
return (str.split(/\r?\n/)).map(s => s.length);
366384
}

‎packages/compiler-cli/ngcc/test/sourcemaps/segment_marker_spec.ts

Lines changed: 62 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import {compareSegments, offsetSegment, segmentDiff} from '../../src/sourcemaps/segment_marker';
9-
import {computeLineLengths} from '../../src/sourcemaps/source_file';
9+
import {computeStartOfLinePositions} from '../../src/sourcemaps/source_file';
1010

1111
describe('SegmentMarker utils', () => {
1212
describe('compareSegments()', () => {
@@ -34,77 +34,99 @@ describe('SegmentMarker utils', () => {
3434

3535
describe('segmentDiff()', () => {
3636
it('should return 0 if the segments are the same', () => {
37-
const lineLengths = computeLineLengths('abcdef\nabcdefghj\nabcdefghijklm\nabcdef');
38-
expect(segmentDiff(lineLengths, {line: 0, column: 0}, {line: 0, column: 0})).toEqual(0);
39-
expect(segmentDiff(lineLengths, {line: 3, column: 0}, {line: 3, column: 0})).toEqual(0);
40-
expect(segmentDiff(lineLengths, {line: 0, column: 5}, {line: 0, column: 5})).toEqual(0);
41-
expect(segmentDiff(lineLengths, {line: 3, column: 5}, {line: 3, column: 5})).toEqual(0);
37+
const startOfLinePositions =
38+
computeStartOfLinePositions('abcdef\nabcdefghj\nabcdefghijklm\nabcdef');
39+
expect(segmentDiff(startOfLinePositions, {line: 0, column: 0}, {line: 0, column: 0}))
40+
.toEqual(0);
41+
expect(segmentDiff(startOfLinePositions, {line: 3, column: 0}, {line: 3, column: 0}))
42+
.toEqual(0);
43+
expect(segmentDiff(startOfLinePositions, {line: 0, column: 5}, {line: 0, column: 5}))
44+
.toEqual(0);
45+
expect(segmentDiff(startOfLinePositions, {line: 3, column: 5}, {line: 3, column: 5}))
46+
.toEqual(0);
4247
});
4348

4449
it('should return the column difference if the markers are on the same line', () => {
45-
const lineLengths = computeLineLengths('abcdef\nabcdefghj\nabcdefghijklm\nabcdef');
46-
expect(segmentDiff(lineLengths, {line: 0, column: 0}, {line: 0, column: 3})).toEqual(3);
47-
expect(segmentDiff(lineLengths, {line: 1, column: 1}, {line: 1, column: 5})).toEqual(4);
48-
expect(segmentDiff(lineLengths, {line: 2, column: 5}, {line: 2, column: 1})).toEqual(-4);
49-
expect(segmentDiff(lineLengths, {line: 3, column: 3}, {line: 3, column: 0})).toEqual(-3);
50+
const startOfLinePositions =
51+
computeStartOfLinePositions('abcdef\nabcdefghj\nabcdefghijklm\nabcdef');
52+
expect(segmentDiff(startOfLinePositions, {line: 0, column: 0}, {line: 0, column: 3}))
53+
.toEqual(3);
54+
expect(segmentDiff(startOfLinePositions, {line: 1, column: 1}, {line: 1, column: 5}))
55+
.toEqual(4);
56+
expect(segmentDiff(startOfLinePositions, {line: 2, column: 5}, {line: 2, column: 1}))
57+
.toEqual(-4);
58+
expect(segmentDiff(startOfLinePositions, {line: 3, column: 3}, {line: 3, column: 0}))
59+
.toEqual(-3);
5060
});
5161

52-
it('should return the number of actual characters difference (including newlineLengths) if not on the same line',
62+
it('should return the number of actual characters difference (including newline markers) if not on the same line',
5363
() => {
54-
let lineLengths: number[];
64+
let startOfLinePositions: number[];
5565

56-
lineLengths = computeLineLengths('A12345\nB123456789');
57-
expect(segmentDiff(lineLengths, {line: 0, column: 0}, {line: 1, column: 0}))
66+
startOfLinePositions = computeStartOfLinePositions('A12345\nB123456789');
67+
expect(segmentDiff(startOfLinePositions, {line: 0, column: 0}, {line: 1, column: 0}))
5868
.toEqual(6 + 1);
5969

60-
lineLengths = computeLineLengths('012A45\n01234B6789');
61-
expect(segmentDiff(lineLengths, {line: 0, column: 3}, {line: 1, column: 5}))
70+
startOfLinePositions = computeStartOfLinePositions('012A45\n01234B6789');
71+
expect(segmentDiff(startOfLinePositions, {line: 0, column: 3}, {line: 1, column: 5}))
6272
.toEqual(3 + 1 + 5);
6373

64-
lineLengths = computeLineLengths('012345\n012345A789\n01234567\nB123456');
65-
expect(segmentDiff(lineLengths, {line: 1, column: 6}, {line: 3, column: 0}))
74+
startOfLinePositions =
75+
computeStartOfLinePositions('012345\n012345A789\n01234567\nB123456');
76+
expect(segmentDiff(startOfLinePositions, {line: 1, column: 6}, {line: 3, column: 0}))
6677
.toEqual(4 + 1 + 8 + 1 + 0);
6778

68-
lineLengths = computeLineLengths('012345\nA123456789\n01234567\n012B456');
69-
expect(segmentDiff(lineLengths, {line: 1, column: 0}, {line: 3, column: 3}))
79+
startOfLinePositions =
80+
computeStartOfLinePositions('012345\nA123456789\n01234567\n012B456');
81+
expect(segmentDiff(startOfLinePositions, {line: 1, column: 0}, {line: 3, column: 3}))
7082
.toEqual(10 + 1 + 8 + 1 + 3);
7183

72-
lineLengths = computeLineLengths('012345\nB123456789\nA1234567\n0123456');
73-
expect(segmentDiff(lineLengths, {line: 2, column: 0}, {line: 1, column: 0}))
84+
startOfLinePositions =
85+
computeStartOfLinePositions('012345\nB123456789\nA1234567\n0123456');
86+
expect(segmentDiff(startOfLinePositions, {line: 2, column: 0}, {line: 1, column: 0}))
7487
.toEqual(0 - 1 - 10 + 0);
7588

76-
lineLengths = computeLineLengths('012345\n0123B56789\n01234567\n012A456');
77-
expect(segmentDiff(lineLengths, {line: 3, column: 3}, {line: 1, column: 4}))
89+
startOfLinePositions =
90+
computeStartOfLinePositions('012345\n0123B56789\n01234567\n012A456');
91+
expect(segmentDiff(startOfLinePositions, {line: 3, column: 3}, {line: 1, column: 4}))
7892
.toEqual(-3 - 1 - 8 - 1 - 10 + 4);
7993

80-
lineLengths = computeLineLengths('B12345\n0123456789\n0123A567\n0123456');
81-
expect(segmentDiff(lineLengths, {line: 2, column: 4}, {line: 0, column: 0}))
94+
startOfLinePositions =
95+
computeStartOfLinePositions('B12345\n0123456789\n0123A567\n0123456');
96+
expect(segmentDiff(startOfLinePositions, {line: 2, column: 4}, {line: 0, column: 0}))
8297
.toEqual(-4 - 1 - 10 - 1 - 6 + 0);
8398

84-
lineLengths = computeLineLengths('0123B5\n0123456789\nA1234567\n0123456');
85-
expect(segmentDiff(lineLengths, {line: 2, column: 0}, {line: 0, column: 4}))
99+
startOfLinePositions =
100+
computeStartOfLinePositions('0123B5\n0123456789\nA1234567\n0123456');
101+
expect(segmentDiff(startOfLinePositions, {line: 2, column: 0}, {line: 0, column: 4}))
86102
.toEqual(0 - 1 - 10 - 1 - 6 + 4);
87103
});
88104
});
89105

90106
describe('offsetSegment()', () => {
91107
it('should return an identical marker if offset is 0', () => {
92-
const lineLengths = computeLineLengths('012345\n0123456789\n01234567\n0123456');
108+
const startOfLinePositions =
109+
computeStartOfLinePositions('012345\n0123456789\r\n01234567\n0123456');
93110
const marker = {line: 2, column: 3};
94-
expect(offsetSegment(lineLengths, marker, 0)).toBe(marker);
111+
expect(offsetSegment(startOfLinePositions, marker, 0)).toBe(marker);
95112
});
96113

97114
it('should return a new marker offset by the given chars', () => {
98-
const lineLengths = computeLineLengths('012345\n0123456789\n012*4567\n0123456');
115+
const startOfLinePositions =
116+
computeStartOfLinePositions('012345\n0123456789\r\n012*4567\n0123456');
99117
const marker = {line: 2, column: 3};
100-
expect(offsetSegment(lineLengths, marker, 1)).toEqual({line: 2, column: 4});
101-
expect(offsetSegment(lineLengths, marker, 2)).toEqual({line: 2, column: 5});
102-
expect(offsetSegment(lineLengths, marker, 4)).toEqual({line: 2, column: 7});
103-
expect(offsetSegment(lineLengths, marker, 8)).toEqual({line: 3, column: 2});
104-
expect(offsetSegment(lineLengths, marker, -1)).toEqual({line: 2, column: 2});
105-
expect(offsetSegment(lineLengths, marker, -2)).toEqual({line: 2, column: 1});
106-
expect(offsetSegment(lineLengths, marker, -4)).toEqual({line: 1, column: 10});
107-
expect(offsetSegment(lineLengths, marker, -6)).toEqual({line: 1, column: 8});
118+
expect(offsetSegment(startOfLinePositions, marker, 1)).toEqual({line: 2, column: 4});
119+
expect(offsetSegment(startOfLinePositions, marker, 2)).toEqual({line: 2, column: 5});
120+
expect(offsetSegment(startOfLinePositions, marker, 4)).toEqual({line: 2, column: 7});
121+
expect(offsetSegment(startOfLinePositions, marker, 6)).toEqual({line: 3, column: 0});
122+
expect(offsetSegment(startOfLinePositions, marker, 8)).toEqual({line: 3, column: 2});
123+
expect(offsetSegment(startOfLinePositions, marker, 20)).toEqual({line: 3, column: 14});
124+
expect(offsetSegment(startOfLinePositions, marker, -1)).toEqual({line: 2, column: 2});
125+
expect(offsetSegment(startOfLinePositions, marker, -2)).toEqual({line: 2, column: 1});
126+
expect(offsetSegment(startOfLinePositions, marker, -3)).toEqual({line: 2, column: 0});
127+
expect(offsetSegment(startOfLinePositions, marker, -4)).toEqual({line: 1, column: 10});
128+
expect(offsetSegment(startOfLinePositions, marker, -6)).toEqual({line: 1, column: 8});
129+
expect(offsetSegment(startOfLinePositions, marker, -16)).toEqual({line: 0, column: 5});
108130
});
109131
});
110132
});

‎packages/compiler-cli/ngcc/test/sourcemaps/source_file_spec.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {absoluteFrom} from '../../../src/ngtsc/file_system';
1111
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
1212
import {RawSourceMap} from '../../src/sourcemaps/raw_source_map';
1313
import {SegmentMarker} from '../../src/sourcemaps/segment_marker';
14-
import {Mapping, SourceFile, computeLineLengths, extractOriginalSegments, findLastMappingIndexBefore, parseMappings} from '../../src/sourcemaps/source_file';
14+
import {Mapping, SourceFile, computeStartOfLinePositions, extractOriginalSegments, findLastMappingIndexBefore, parseMappings} from '../../src/sourcemaps/source_file';
1515

1616
runInEachFileSystem(() => {
1717
describe('SourceFile and utilities', () => {
@@ -482,17 +482,17 @@ runInEachFileSystem(() => {
482482
});
483483
});
484484

485-
describe('computeLineLengths()', () => {
486-
it('should compute the length of each line in the given string', () => {
487-
expect(computeLineLengths('')).toEqual([0]);
488-
expect(computeLineLengths('abc')).toEqual([3]);
489-
expect(computeLineLengths('\n')).toEqual([0, 0]);
490-
expect(computeLineLengths('\n\n')).toEqual([0, 0, 0]);
491-
expect(computeLineLengths('abc\n')).toEqual([3, 0]);
492-
expect(computeLineLengths('\nabc')).toEqual([0, 3]);
493-
expect(computeLineLengths('abc\ndefg')).toEqual([3, 4]);
494-
expect(computeLineLengths('abc\r\n')).toEqual([3, 0]);
495-
expect(computeLineLengths('abc\r\ndefg')).toEqual([3, 4]);
485+
describe('computeStartOfLinePositions()', () => {
486+
it('should compute the cumulative length of each line in the given string', () => {
487+
expect(computeStartOfLinePositions('')).toEqual([0]);
488+
expect(computeStartOfLinePositions('abc')).toEqual([0]);
489+
expect(computeStartOfLinePositions('\n')).toEqual([0, 1]);
490+
expect(computeStartOfLinePositions('\n\n')).toEqual([0, 1, 2]);
491+
expect(computeStartOfLinePositions('abc\n')).toEqual([0, 4]);
492+
expect(computeStartOfLinePositions('\nabc')).toEqual([0, 1]);
493+
expect(computeStartOfLinePositions('abc\ndefg')).toEqual([0, 4]);
494+
expect(computeStartOfLinePositions('abc\r\n')).toEqual([0, 4]);
495+
expect(computeStartOfLinePositions('abc\r\ndefg')).toEqual([0, 4]);
496496
});
497497
});
498498
});

0 commit comments

Comments
 (0)