Skip to content

Commit 3158858

Browse files
petebacondarwinatscott
authored andcommitted
fix(compiler-cli): do not duplicate repeated source-files in rendered source-maps (#40237)
When a source-map/source-file tree has nodes that refer to the same file, the flattened source-map rendering was those files multiple times, rather than consolidating them into a single source-map source. PR Close #40237
1 parent e7c3687 commit 3158858

File tree

2 files changed

+143
-26
lines changed

2 files changed

+143
-26
lines changed

packages/compiler-cli/src/ngtsc/sourcemaps/src/source_file.ts

+107-26
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,27 @@ export class SourceFile {
5050
* Render the raw source map generated from the flattened mappings.
5151
*/
5252
renderFlattenedSourceMap(): RawSourceMap {
53-
const sources: SourceFile[] = [];
54-
const names: string[] = [];
55-
53+
const sources = new IndexedMap<string, string>();
54+
const names = new IndexedSet<string>();
5655
const mappings: SourceMapMappings = [];
56+
const sourcePathDir = this.fs.dirname(this.sourcePath);
57+
// Computing the relative path can be expensive, and we are likely to have the same path for
58+
// many (if not all!) mappings.
59+
const relativeSourcePathCache =
60+
new Cache<string, string>(input => this.fs.relative(sourcePathDir, input));
5761

5862
for (const mapping of this.flattenedMappings) {
59-
const sourceIndex = findIndexOrAdd(sources, mapping.originalSource);
63+
const sourceIndex = sources.set(
64+
relativeSourcePathCache.get(mapping.originalSource.sourcePath),
65+
mapping.originalSource.contents);
6066
const mappingArray: SourceMapSegment = [
6167
mapping.generatedSegment.column,
6268
sourceIndex,
6369
mapping.originalSegment.line,
6470
mapping.originalSegment.column,
6571
];
6672
if (mapping.name !== undefined) {
67-
const nameIndex = findIndexOrAdd(names, mapping.name);
73+
const nameIndex = names.add(mapping.name);
6874
mappingArray.push(nameIndex);
6975
}
7076

@@ -77,14 +83,13 @@ export class SourceFile {
7783
mappings[line].push(mappingArray);
7884
}
7985

80-
const sourcePathDir = this.fs.dirname(this.sourcePath);
8186
const sourceMap: RawSourceMap = {
8287
version: 3,
8388
file: this.fs.relative(sourcePathDir, this.sourcePath),
84-
sources: sources.map(sf => this.fs.relative(sourcePathDir, sf.sourcePath)),
85-
names,
89+
sources: sources.keys,
90+
names: names.values,
8691
mappings: encode(mappings),
87-
sourcesContent: sources.map(sf => sf.contents),
92+
sourcesContent: sources.values,
8893
};
8994
return sourceMap;
9095
}
@@ -259,23 +264,6 @@ export interface Mapping {
259264
readonly name?: string;
260265
}
261266

262-
/**
263-
* Find the index of `item` in the `items` array.
264-
* If it is not found, then push `item` to the end of the array and return its new index.
265-
*
266-
* @param items the collection in which to look for `item`.
267-
* @param item the item to look for.
268-
* @returns the index of the `item` in the `items` array.
269-
*/
270-
function findIndexOrAdd<T>(items: T[], item: T): number {
271-
const itemIndex = items.indexOf(item);
272-
if (itemIndex > -1) {
273-
return itemIndex;
274-
} else {
275-
items.push(item);
276-
return items.length - 1;
277-
}
278-
}
279267

280268

281269
/**
@@ -448,3 +436,96 @@ export function computeStartOfLinePositions(str: string) {
448436
function computeLineLengths(str: string): number[] {
449437
return (str.split(/\n/)).map(s => s.length);
450438
}
439+
440+
/**
441+
* A collection of mappings between `keys` and `values` stored in the order in which the keys are
442+
* first seen.
443+
*
444+
* The difference between this and a standard `Map` is that when you add a key-value pair the index
445+
* of the `key` is returned.
446+
*/
447+
class IndexedMap<K, V> {
448+
private map = new Map<K, number>();
449+
450+
/**
451+
* An array of keys added to this map.
452+
*
453+
* This array is guaranteed to be in the order of the first time the key was added to the map.
454+
*/
455+
readonly keys: K[] = [];
456+
457+
/**
458+
* An array of values added to this map.
459+
*
460+
* This array is guaranteed to be in the order of the first time the associated key was added to
461+
* the map.
462+
*/
463+
readonly values: V[] = [];
464+
465+
/**
466+
* Associate the `value` with the `key` and return the index of the key in the collection.
467+
*
468+
* If the `key` already exists then the `value` is not set and the index of that `key` is
469+
* returned; otherwise the `key` and `value` are stored and the index of the new `key` is
470+
* returned.
471+
*
472+
* @param key the key to associated with the `value`.
473+
* @param value the value to associated with the `key`.
474+
* @returns the index of the `key` in the `keys` array.
475+
*/
476+
set(key: K, value: V): number {
477+
if (this.map.has(key)) {
478+
return this.map.get(key)!;
479+
}
480+
const index = this.values.push(value) - 1;
481+
this.keys.push(key);
482+
this.map.set(key, index);
483+
return index;
484+
}
485+
}
486+
487+
/**
488+
* A collection of `values` stored in the order in which they were added.
489+
*
490+
* The difference between this and a standard `Set` is that when you add a value the index of that
491+
* item is returned.
492+
*/
493+
class IndexedSet<V> {
494+
private map = new Map<V, number>();
495+
496+
/**
497+
* An array of values added to this set.
498+
* This array is guaranteed to be in the order of the first time the value was added to the set.
499+
*/
500+
readonly values: V[] = [];
501+
502+
/**
503+
* Add the `value` to the `values` array, if it doesn't already exist; returning the index of the
504+
* `value` in the `values` array.
505+
*
506+
* If the `value` already exists then the index of that `value` is returned, otherwise the new
507+
* `value` is stored and the new index returned.
508+
*
509+
* @param value the value to add to the set.
510+
* @returns the index of the `value` in the `values` array.
511+
*/
512+
add(value: V): number {
513+
if (this.map.has(value)) {
514+
return this.map.get(value)!;
515+
}
516+
const index = this.values.push(value) - 1;
517+
this.map.set(value, index);
518+
return index;
519+
}
520+
}
521+
522+
class Cache<Input, Cached> {
523+
private map = new Map<Input, Cached>();
524+
constructor(private computeFn: (input: Input) => Cached) {}
525+
get(input: Input): Cached {
526+
if (!this.map.has(input)) {
527+
this.map.set(input, this.computeFn(input));
528+
}
529+
return this.map.get(input)!;
530+
}
531+
}

packages/compiler-cli/src/ngtsc/sourcemaps/test/source_file_spec.ts

+36
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,42 @@ runInEachFileSystem(() => {
522522
expect(aTocSourceMap.sourcesContent).toEqual(['abcdef']);
523523
expect(aTocSourceMap.mappings).toEqual(aToBSourceMap.mappings);
524524
});
525+
526+
it('should consolidate source-files with the same relative path', () => {
527+
const cSource1 = new SourceFile(_('/foo/src/lib/c.js'), 'bcd123e', null, false, [], fs);
528+
const cSource2 = new SourceFile(_('/foo/src/lib/c.js'), 'bcd123e', null, false, [], fs);
529+
530+
const bToCSourceMap: RawSourceMap = {
531+
mappings: encode([[[1, 0, 0, 0], [4, 0, 0, 3], [4, 0, 0, 6], [5, 0, 0, 7]]]),
532+
names: [],
533+
sources: ['c.js'],
534+
version: 3
535+
};
536+
const bSource = new SourceFile(
537+
_('/foo/src/lib/b.js'), 'abcdef', bToCSourceMap, false, [cSource1], fs);
538+
539+
const aToBCSourceMap: RawSourceMap = {
540+
mappings:
541+
encode([[[0, 0, 0, 0], [2, 0, 0, 3], [4, 0, 0, 2], [5, 0, 0, 5], [6, 1, 0, 3]]]),
542+
names: [],
543+
sources: ['lib/b.js', 'lib/c.js'],
544+
version: 3
545+
};
546+
const aSource = new SourceFile(
547+
_('/foo/src/a.js'), 'abdecf123', aToBCSourceMap, false, [bSource, cSource2], fs);
548+
549+
const aTocSourceMap = aSource.renderFlattenedSourceMap();
550+
expect(aTocSourceMap.version).toEqual(3);
551+
expect(aTocSourceMap.file).toEqual('a.js');
552+
expect(aTocSourceMap.names).toEqual([]);
553+
expect(aTocSourceMap.sourceRoot).toBeUndefined();
554+
expect(aTocSourceMap.sources).toEqual(['lib/c.js']);
555+
expect(aTocSourceMap.sourcesContent).toEqual(['bcd123e']);
556+
expect(aTocSourceMap.mappings).toEqual(encode([[
557+
[1, 0, 0, 0], [2, 0, 0, 2], [3, 0, 0, 3], [3, 0, 0, 6], [4, 0, 0, 1], [5, 0, 0, 7],
558+
[6, 0, 0, 3]
559+
]]));
560+
});
525561
});
526562

527563
describe('getOriginalLocation()', () => {

0 commit comments

Comments
 (0)