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
This commit is contained in:

committed by
Andrew Kushnir

parent
a40be00e17
commit
e8900824dd
@ -28,61 +28,41 @@ export function compareSegments(a: SegmentMarker, b: SegmentMarker): number {
|
||||
return a.line === b.line ? a.column - b.column : a.line - b.line;
|
||||
}
|
||||
|
||||
// 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 newline
|
||||
// - e.g. \r\n - but that is not important here since segment-markers are in line/column pairs and
|
||||
// so differences in length due to extra `\r` characters do not affect the algorithms.
|
||||
const NEWLINE_MARKER_OFFSET = 1;
|
||||
|
||||
/**
|
||||
* Compute the difference between two segment markers in a source file.
|
||||
*
|
||||
* @param lineLengths the lengths of each line of content of the source file where we are computing
|
||||
* the difference
|
||||
* @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(lineLengths: number[], a: SegmentMarker, b: SegmentMarker) {
|
||||
let diff = b.column - a.column;
|
||||
|
||||
// Deal with `a` being before `b`
|
||||
for (let lineIndex = a.line; lineIndex < b.line; lineIndex++) {
|
||||
diff += lineLengths[lineIndex] + NEWLINE_MARKER_OFFSET;
|
||||
}
|
||||
|
||||
// Deal with `a` being after `b`
|
||||
for (let lineIndex = a.line - 1; lineIndex >= b.line; lineIndex--) {
|
||||
// The `+ 1` is the newline character between the lines
|
||||
diff -= lineLengths[lineIndex] + NEWLINE_MARKER_OFFSET;
|
||||
}
|
||||
return diff;
|
||||
export function segmentDiff(startOfLinePositions: number[], a: SegmentMarker, b: SegmentMarker) {
|
||||
return startOfLinePositions[b.line] - startOfLinePositions[a.line] + b.column - a.column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new segment-marker that is offset by the given number of characters.
|
||||
*
|
||||
* @param lineLengths The length of each line in the source file whose segment-marker we are
|
||||
* offsetting.
|
||||
* @param marker The segment to offset.
|
||||
* @param offset The number of character to offset by.
|
||||
* @param startOfLinePositions the position of the start of each line of content of the source file
|
||||
* whose segment-marker we are offsetting.
|
||||
* @param marker the segment to offset.
|
||||
* @param offset the number of character to offset by.
|
||||
*/
|
||||
export function offsetSegment(lineLengths: number[], marker: SegmentMarker, offset: number) {
|
||||
export function offsetSegment(
|
||||
startOfLinePositions: number[], marker: SegmentMarker, offset: number) {
|
||||
if (offset === 0) {
|
||||
return marker;
|
||||
}
|
||||
|
||||
let line = marker.line;
|
||||
let column = marker.column + offset;
|
||||
|
||||
while (line < lineLengths.length - 1 && column > lineLengths[line]) {
|
||||
column -= lineLengths[line] + NEWLINE_MARKER_OFFSET;
|
||||
const newPos = startOfLinePositions[line] + marker.column + offset;
|
||||
while (line < startOfLinePositions.length - 1 && startOfLinePositions[line + 1] <= newPos) {
|
||||
line++;
|
||||
}
|
||||
while (line > 0 && column < 0) {
|
||||
while (line > 0 && startOfLinePositions[line] > newPos) {
|
||||
line--;
|
||||
column += lineLengths[line] + NEWLINE_MARKER_OFFSET;
|
||||
}
|
||||
|
||||
const column = newPos - startOfLinePositions[line];
|
||||
return {line, column};
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export class SourceFile {
|
||||
* pure original source files).
|
||||
*/
|
||||
readonly flattenedMappings: Mapping[];
|
||||
readonly lineLengths: number[];
|
||||
readonly startOfLinePositions: number[];
|
||||
|
||||
constructor(
|
||||
/** The path to this source file. */
|
||||
@ -38,7 +38,7 @@ export class SourceFile {
|
||||
/** Any source files referenced by the raw source map associated with this source file. */
|
||||
readonly sources: (SourceFile|null)[]) {
|
||||
this.contents = removeSourceMapComments(contents);
|
||||
this.lineLengths = computeLineLengths(this.contents);
|
||||
this.startOfLinePositions = computeStartOfLinePositions(this.contents);
|
||||
this.flattenedMappings = this.flattenMappings();
|
||||
}
|
||||
|
||||
@ -282,11 +282,13 @@ export function mergeMappings(generatedSource: SourceFile, ab: Mapping, bc: Mapp
|
||||
// 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].
|
||||
|
||||
const diff = segmentDiff(ab.originalSource.lineLengths, ab.originalSegment, bc.generatedSegment);
|
||||
const diff =
|
||||
segmentDiff(ab.originalSource.startOfLinePositions, ab.originalSegment, bc.generatedSegment);
|
||||
if (diff > 0) {
|
||||
return {
|
||||
name,
|
||||
generatedSegment: offsetSegment(generatedSource.lineLengths, ab.generatedSegment, diff),
|
||||
generatedSegment:
|
||||
offsetSegment(generatedSource.startOfLinePositions, ab.generatedSegment, diff),
|
||||
originalSource: bc.originalSource,
|
||||
originalSegment: bc.originalSegment,
|
||||
};
|
||||
@ -295,7 +297,8 @@ export function mergeMappings(generatedSource: SourceFile, ab: Mapping, bc: Mapp
|
||||
name,
|
||||
generatedSegment: ab.generatedSegment,
|
||||
originalSource: bc.originalSource,
|
||||
originalSegment: offsetSegment(bc.originalSource.lineLengths, bc.originalSegment, -diff),
|
||||
originalSegment:
|
||||
offsetSegment(bc.originalSource.startOfLinePositions, bc.originalSegment, -diff),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -361,6 +364,21 @@ export function extractOriginalSegments(mappings: Mapping[]): Map<SourceFile, Se
|
||||
return originalSegments;
|
||||
}
|
||||
|
||||
export function computeLineLengths(str: string): number[] {
|
||||
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
|
||||
// newline
|
||||
// - e.g. \r\n - but that is not important here since segment-markers are in line/column pairs and
|
||||
// so differences in length due to extra `\r` characters do not affect the algorithms.
|
||||
const NEWLINE_MARKER_OFFSET = 1;
|
||||
const lineLengths = computeLineLengths(str);
|
||||
const startPositions = [0]; // First line starts at position 0
|
||||
for (let i = 0; i < lineLengths.length - 1; i++) {
|
||||
startPositions.push(startPositions[i] + lineLengths[i] + NEWLINE_MARKER_OFFSET);
|
||||
}
|
||||
return startPositions;
|
||||
}
|
||||
|
||||
function computeLineLengths(str: string): number[] {
|
||||
return (str.split(/\r?\n/)).map(s => s.length);
|
||||
}
|
||||
|
Reference in New Issue
Block a user