Skip to content

Commit d29b92e

Browse files
committed
deduplicate references lists in tsbuildinfo
1 parent e234f0c commit d29b92e

File tree

33 files changed

+564
-405
lines changed

33 files changed

+564
-405
lines changed

src/compiler/builder.ts

+102-20
Original file line numberDiff line numberDiff line change
@@ -695,12 +695,87 @@ namespace ts {
695695
export interface ProgramBuildInfo {
696696
fileInfos: MapLike<BuilderState.FileInfo>;
697697
options: CompilerOptions;
698-
referencedMap?: MapLike<string[]>;
699-
exportedModulesMap?: MapLike<string[]>;
698+
referencedMap?: MapLike<string[] | number>;
699+
referencedMapLists?: string[][];
700+
exportedModulesMap?: MapLike<string[] | number>;
701+
exportedModulesMapLists?: string[][];
700702
semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[];
701703
affectedFilesPendingEmit?: ProgramBuilderInfoFilePendingEmit[];
702704
}
703705

706+
/**
707+
* Runs deduplication on a map of references lists to reduce storage cost
708+
*/
709+
function deduplicateReferencedMap(inputMap: ReadonlyESMap<Path, BuilderState.ReferencedSet>, relativeToBuildInfo: (path: Path) => string): { map: MapLike<string[] | number>, lists?: string[][] } {
710+
const map: MapLike<string[] | number> = {};
711+
const lists: string[][] = [];
712+
const sharedMap = new Map<BuilderState.ReferencedSet, number>();
713+
const notSharedMap = new Map<BuilderState.ReferencedSet, string>();
714+
const seenSetsBySize = new Map<number, BuilderState.ReferencedSet[]>();
715+
for (const key of arrayFrom(inputMap.keys()).sort(compareStringsCaseSensitive)) {
716+
const set = inputMap.get(key)!;
717+
let sharedSet;
718+
let notSharedSet;
719+
// already shared
720+
if (sharedMap.has(set)) {
721+
sharedSet = set;
722+
}
723+
// already seen, but not shared yet
724+
else if (notSharedMap.has(set)) {
725+
notSharedSet = set;
726+
}
727+
// compare with all seen sets to find duplicates without same identity
728+
else {
729+
const potentialDuplicates = seenSetsBySize.get(set.size);
730+
if (potentialDuplicates !== undefined) {
731+
outer: for (const otherSet of potentialDuplicates) {
732+
const it = set.keys();
733+
let itResult;
734+
while (!(itResult = it.next()).done) {
735+
if (!otherSet.has(itResult.value)) continue outer;
736+
}
737+
if (sharedMap.has(otherSet)) {
738+
sharedSet = otherSet;
739+
}
740+
else {
741+
notSharedSet = otherSet;
742+
}
743+
break;
744+
}
745+
}
746+
}
747+
748+
if (sharedSet !== undefined) {
749+
// use the existing id
750+
map[relativeToBuildInfo(key)] = sharedMap.get(sharedSet)!;
751+
}
752+
else if(notSharedSet !== undefined) {
753+
// create a new shared list and assign both sets to it
754+
// also update the existing key with the new shared index
755+
const idx = lists.length;
756+
const existingKey = notSharedMap.get(notSharedSet)!;
757+
sharedMap.set(set, idx);
758+
sharedMap.set(notSharedSet, idx);
759+
lists.push(map[existingKey] as string[]);
760+
notSharedMap.delete(notSharedSet);
761+
map[existingKey] = idx;
762+
map[relativeToBuildInfo(key)] = idx;
763+
}
764+
else {
765+
const relativeKey = relativeToBuildInfo(key);
766+
notSharedMap.set(set, relativeKey);
767+
map[relativeKey] = arrayFrom(set.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive);
768+
if (seenSetsBySize.has(set.size)) {
769+
seenSetsBySize.get(set.size)!.push(set);
770+
}
771+
else {
772+
seenSetsBySize.set(set.size, [set]);
773+
}
774+
}
775+
}
776+
return { map, lists: lists.length > 0 ? lists : undefined };
777+
}
778+
704779
/**
705780
* Gets the program information to be emitted in buildInfo so that we can use it to create new program
706781
*/
@@ -719,23 +794,15 @@ namespace ts {
719794
options: convertToReusableCompilerOptions(state.compilerOptions, relativeToBuildInfoEnsuringAbsolutePath)
720795
};
721796
if (state.referencedMap) {
722-
const referencedMap: MapLike<string[]> = {};
723-
for (const key of arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive)) {
724-
referencedMap[relativeToBuildInfo(key)] = arrayFrom(state.referencedMap.get(key)!.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive);
725-
}
726-
result.referencedMap = referencedMap;
797+
const { map, lists } = deduplicateReferencedMap(state.referencedMap, relativeToBuildInfo);
798+
result.referencedMap = map;
799+
result.referencedMapLists = lists;
727800
}
728801

729802
if (state.exportedModulesMap) {
730-
const exportedModulesMap: MapLike<string[]> = {};
731-
for (const key of arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive)) {
732-
const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key);
733-
// Not in temporary cache, use existing value
734-
if (newValue === undefined) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(state.exportedModulesMap.get(key)!.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive);
735-
// Value in cache and has updated value map, use that
736-
else if (newValue) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(newValue.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive);
737-
}
738-
result.exportedModulesMap = exportedModulesMap;
803+
const { map, lists } = deduplicateReferencedMap(state.exportedModulesMap, relativeToBuildInfo);
804+
result.exportedModulesMap = map;
805+
result.exportedModulesMapLists = lists;
739806
}
740807

741808
if (state.semanticDiagnosticsPerFile) {
@@ -1167,14 +1234,29 @@ namespace ts {
11671234
}
11681235
}
11691236

1170-
function getMapOfReferencedSet(mapLike: MapLike<readonly string[]> | undefined, toPath: (path: string) => Path): ReadonlyESMap<Path, BuilderState.ReferencedSet> | undefined {
1237+
function getMapOfReferencedSet(mapLike: MapLike<readonly string[] | number> | undefined, lists: (readonly string[])[] | undefined, toPath: (path: string) => Path): ReadonlyESMap<Path, BuilderState.ReferencedSet> | undefined {
11711238
if (!mapLike) return undefined;
11721239
const map = new Map<Path, BuilderState.ReferencedSet>();
1240+
const cache = new Map<number, BuilderState.ReferencedSet>();
11731241
// Copies keys/values from template. Note that for..in will not throw if
11741242
// template is undefined, and instead will just exit the loop.
11751243
for (const key in mapLike) {
11761244
if (hasProperty(mapLike, key)) {
1177-
map.set(toPath(key), new Set(mapLike[key].map(toPath)));
1245+
const value = mapLike[key];
1246+
if (typeof value === "number") {
1247+
if (cache.has(value)) {
1248+
map.set(toPath(key), cache.get(value)!);
1249+
}
1250+
else {
1251+
if(!lists) return undefined;
1252+
const list = new Set(lists[value].map(toPath));
1253+
cache.set(value, list);
1254+
map.set(toPath(key), list);
1255+
}
1256+
}
1257+
else {
1258+
map.set(toPath(key), new Set(value.map(toPath)));
1259+
}
11781260
}
11791261
}
11801262
return map;
@@ -1194,8 +1276,8 @@ namespace ts {
11941276
const state: ReusableBuilderProgramState = {
11951277
fileInfos,
11961278
compilerOptions: convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath),
1197-
referencedMap: getMapOfReferencedSet(program.referencedMap, toPath),
1198-
exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap, toPath),
1279+
referencedMap: getMapOfReferencedSet(program.referencedMap, program.referencedMapLists, toPath),
1280+
exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap, program.exportedModulesMapLists, toPath),
11991281
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toPath(isString(value) ? value : value[0]), value => isString(value) ? emptyArray : value[1]),
12001282
hasReusableDiagnostic: true,
12011283
affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toPath(value[0])),

tests/baselines/reference/tsbuild/demo/initial-build/in-master-branch-with-everything-setup-correctly-and-reports-no-error.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -398,29 +398,31 @@ exports.lastElementOf = lastElementOf;
398398
"configFilePath": "../../zoo/tsconfig.json"
399399
},
400400
"referencedMap": {
401-
"../animals/dog.d.ts": [
402-
"../animals/index.d.ts"
403-
],
401+
"../animals/dog.d.ts": 0,
404402
"../animals/index.d.ts": [
405403
"../animals/animal.d.ts",
406404
"../animals/dog.d.ts"
407405
],
408-
"../../zoo/zoo.ts": [
406+
"../../zoo/zoo.ts": 0
407+
},
408+
"referencedMapLists": [
409+
[
409410
"../animals/index.d.ts"
410411
]
411-
},
412+
],
412413
"exportedModulesMap": {
413-
"../animals/dog.d.ts": [
414-
"../animals/index.d.ts"
415-
],
414+
"../animals/dog.d.ts": 0,
416415
"../animals/index.d.ts": [
417416
"../animals/animal.d.ts",
418417
"../animals/dog.d.ts"
419418
],
420-
"../../zoo/zoo.ts": [
419+
"../../zoo/zoo.ts": 0
420+
},
421+
"exportedModulesMapLists": [
422+
[
421423
"../animals/index.d.ts"
422424
]
423-
},
425+
],
424426
"semanticDiagnosticsPerFile": [
425427
"../../../lib/lib.d.ts",
426428
"../animals/animal.d.ts",

tests/baselines/reference/tsbuild/sample1/incremental-declaration-changes/rebuilds-when-tsconfig-changes.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,14 @@ exitCode:: ExitStatus.Success
8585
]
8686
},
8787
"exportedModulesMap": {
88-
"../logic/index.d.ts": [
89-
"../core/anothermodule.d.ts"
90-
],
91-
"./index.ts": [
88+
"../logic/index.d.ts": 0,
89+
"./index.ts": 0
90+
},
91+
"exportedModulesMapLists": [
92+
[
9293
"../core/anothermodule.d.ts"
9394
]
94-
},
95+
],
9596
"semanticDiagnosticsPerFile": [
9697
"../../lib/lib.d.ts",
9798
"../core/anothermodule.d.ts",

tests/baselines/reference/tsbuild/sample1/initial-build/always-builds-under-with-force-option.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -320,13 +320,14 @@ exports.m = mod;
320320
]
321321
},
322322
"exportedModulesMap": {
323-
"../logic/index.d.ts": [
324-
"../core/anothermodule.d.ts"
325-
],
326-
"./index.ts": [
323+
"../logic/index.d.ts": 0,
324+
"./index.ts": 0
325+
},
326+
"exportedModulesMapLists": [
327+
[
327328
"../core/anothermodule.d.ts"
328329
]
329-
},
330+
],
330331
"semanticDiagnosticsPerFile": [
331332
"../../lib/lib.d.ts",
332333
"../core/anothermodule.d.ts",

tests/baselines/reference/tsbuild/sample1/initial-build/builds-correctly-when-declarationDir-is-specified.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,14 @@ exports.m = mod;
307307
]
308308
},
309309
"exportedModulesMap": {
310-
"../logic/out/decls/index.d.ts": [
311-
"../core/anothermodule.d.ts"
312-
],
313-
"./index.ts": [
310+
"../logic/out/decls/index.d.ts": 0,
311+
"./index.ts": 0
312+
},
313+
"exportedModulesMapLists": [
314+
[
314315
"../core/anothermodule.d.ts"
315316
]
316-
},
317+
],
317318
"semanticDiagnosticsPerFile": [
318319
"../../lib/lib.d.ts",
319320
"../core/anothermodule.d.ts",

tests/baselines/reference/tsbuild/sample1/initial-build/builds-correctly-when-outDir-is-specified.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,14 @@ exports.m = mod;
307307
]
308308
},
309309
"exportedModulesMap": {
310-
"../logic/outdir/index.d.ts": [
311-
"../core/anothermodule.d.ts"
312-
],
313-
"./index.ts": [
310+
"../logic/outdir/index.d.ts": 0,
311+
"./index.ts": 0
312+
},
313+
"exportedModulesMapLists": [
314+
[
314315
"../core/anothermodule.d.ts"
315316
]
316-
},
317+
],
317318
"semanticDiagnosticsPerFile": [
318319
"../../lib/lib.d.ts",
319320
"../core/anothermodule.d.ts",

tests/baselines/reference/tsbuild/sample1/initial-build/can-detect-when-and-what-to-rebuild.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,14 @@ Input::
236236
]
237237
},
238238
"exportedModulesMap": {
239-
"../logic/index.d.ts": [
240-
"../core/anothermodule.d.ts"
241-
],
242-
"./index.ts": [
239+
"../logic/index.d.ts": 0,
240+
"./index.ts": 0
241+
},
242+
"exportedModulesMapLists": [
243+
[
243244
"../core/anothermodule.d.ts"
244245
]
245-
},
246+
],
246247
"semanticDiagnosticsPerFile": [
247248
"../../lib/lib.d.ts",
248249
"../core/anothermodule.d.ts",

tests/baselines/reference/tsbuild/sample1/initial-build/explainFiles.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -372,13 +372,14 @@ exports.m = mod;
372372
]
373373
},
374374
"exportedModulesMap": {
375-
"../logic/index.d.ts": [
376-
"../core/anothermodule.d.ts"
377-
],
378-
"./index.ts": [
375+
"../logic/index.d.ts": 0,
376+
"./index.ts": 0
377+
},
378+
"exportedModulesMapLists": [
379+
[
379380
"../core/anothermodule.d.ts"
380381
]
381-
},
382+
],
382383
"semanticDiagnosticsPerFile": [
383384
"../../lib/lib.d.ts",
384385
"../core/anothermodule.d.ts",
@@ -641,13 +642,14 @@ exports.someClass = someClass;
641642
]
642643
},
643644
"exportedModulesMap": {
644-
"../logic/index.d.ts": [
645-
"../core/anothermodule.d.ts"
646-
],
647-
"./index.ts": [
645+
"../logic/index.d.ts": 0,
646+
"./index.ts": 0
647+
},
648+
"exportedModulesMapLists": [
649+
[
648650
"../core/anothermodule.d.ts"
649651
]
650-
},
652+
],
651653
"semanticDiagnosticsPerFile": [
652654
"../../lib/lib.d.ts",
653655
"../core/anothermodule.d.ts",

tests/baselines/reference/tsbuild/sample1/initial-build/listEmittedFiles.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -337,13 +337,14 @@ exports.m = mod;
337337
]
338338
},
339339
"exportedModulesMap": {
340-
"../logic/index.d.ts": [
341-
"../core/anothermodule.d.ts"
342-
],
343-
"./index.ts": [
340+
"../logic/index.d.ts": 0,
341+
"./index.ts": 0
342+
},
343+
"exportedModulesMapLists": [
344+
[
344345
"../core/anothermodule.d.ts"
345346
]
346-
},
347+
],
347348
"semanticDiagnosticsPerFile": [
348349
"../../lib/lib.d.ts",
349350
"../core/anothermodule.d.ts",
@@ -566,13 +567,14 @@ exports.someClass = someClass;
566567
]
567568
},
568569
"exportedModulesMap": {
569-
"../logic/index.d.ts": [
570-
"../core/anothermodule.d.ts"
571-
],
572-
"./index.ts": [
570+
"../logic/index.d.ts": 0,
571+
"./index.ts": 0
572+
},
573+
"exportedModulesMapLists": [
574+
[
573575
"../core/anothermodule.d.ts"
574576
]
575-
},
577+
],
576578
"semanticDiagnosticsPerFile": [
577579
"../../lib/lib.d.ts",
578580
"../core/anothermodule.d.ts",

0 commit comments

Comments
 (0)