Skip to content

Commit 2db8a13

Browse files
committed
Remove project status, watches etc when project is no longer part of build order
1 parent 4efcfb7 commit 2db8a13

File tree

6 files changed

+186
-7
lines changed

6 files changed

+186
-7
lines changed

src/compiler/tsbuild.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,51 @@ namespace ts {
564564
}
565565

566566
function getBuildOrder(state: SolutionBuilderState) {
567-
return state.buildOrder ||
568-
(state.buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f))));
567+
return state.buildOrder || createStateBuildOrder(state);
568+
}
569+
570+
function createStateBuildOrder(state: SolutionBuilderState) {
571+
const buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f)));
572+
if (arrayIsEqualTo(state.buildOrder, buildOrder)) return state.buildOrder!;
573+
574+
// Clear all to ResolvedConfigFilePaths cache to start fresh
575+
state.resolvedConfigFilePaths.clear();
576+
const currentProjects = arrayToSet(
577+
buildOrder,
578+
resolved => toResolvedConfigFilePath(state, resolved)
579+
) as ConfigFileMap<true>;
580+
581+
const noopOnDelete = { onDeleteValue: noop };
582+
// Config file cache
583+
mutateMapSkippingNewValues(state.configFileCache, currentProjects, noopOnDelete);
584+
mutateMapSkippingNewValues(state.projectStatus, currentProjects, noopOnDelete);
585+
mutateMapSkippingNewValues(state.buildInfoChecked, currentProjects, noopOnDelete);
586+
mutateMapSkippingNewValues(state.builderPrograms, currentProjects, noopOnDelete);
587+
mutateMapSkippingNewValues(state.diagnostics, currentProjects, noopOnDelete);
588+
mutateMapSkippingNewValues(state.projectPendingBuild, currentProjects, noopOnDelete);
589+
mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete);
590+
591+
// Remove watches for the program no longer in the solution
592+
if (state.watch) {
593+
mutateMapSkippingNewValues(
594+
state.allWatchedConfigFiles,
595+
currentProjects,
596+
{ onDeleteValue: closeFileWatcher }
597+
);
598+
599+
mutateMapSkippingNewValues(
600+
state.allWatchedWildcardDirectories,
601+
currentProjects,
602+
{ onDeleteValue: existingMap => existingMap.forEach(closeFileWatcherOf) }
603+
);
604+
605+
mutateMapSkippingNewValues(
606+
state.allWatchedInputFiles,
607+
currentProjects,
608+
{ onDeleteValue: existingMap => existingMap.forEach(closeFileWatcher) }
609+
);
610+
}
611+
return state.buildOrder = buildOrder;
569612
}
570613

571614
function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined) {

src/compiler/utilities.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4466,8 +4466,7 @@ namespace ts {
44664466
map.clear();
44674467
}
44684468

4469-
export interface MutateMapOptions<T, U> {
4470-
createNewValue(key: string, valueInNewMap: U): T;
4469+
export interface MutateMapSkippingNewValuesOptions<T, U> {
44714470
onDeleteValue(existingValue: T, key: string): void;
44724471

44734472
/**
@@ -4482,8 +4481,12 @@ namespace ts {
44824481
/**
44834482
* Mutates the map with newMap such that keys in map will be same as newMap.
44844483
*/
4485-
export function mutateMap<T, U>(map: Map<T>, newMap: ReadonlyMap<U>, options: MutateMapOptions<T, U>) {
4486-
const { createNewValue, onDeleteValue, onExistingValue } = options;
4484+
export function mutateMapSkippingNewValues<T, U>(
4485+
map: Map<T>,
4486+
newMap: ReadonlyMap<U>,
4487+
options: MutateMapSkippingNewValuesOptions<T, U>
4488+
) {
4489+
const { onDeleteValue, onExistingValue } = options;
44874490
// Needs update
44884491
map.forEach((existingValue, key) => {
44894492
const valueInNewMap = newMap.get(key);
@@ -4497,7 +4500,20 @@ namespace ts {
44974500
onExistingValue(existingValue, valueInNewMap, key);
44984501
}
44994502
});
4503+
}
4504+
4505+
export interface MutateMapOptions<T, U> extends MutateMapSkippingNewValuesOptions<T, U> {
4506+
createNewValue(key: string, valueInNewMap: U): T;
4507+
}
4508+
4509+
/**
4510+
* Mutates the map with newMap such that keys in map will be same as newMap.
4511+
*/
4512+
export function mutateMap<T, U>(map: Map<T>, newMap: ReadonlyMap<U>, options: MutateMapOptions<T, U>) {
4513+
// Needs update
4514+
mutateMapSkippingNewValues(map, newMap, options);
45004515

4516+
const { createNewValue } = options;
45014517
// Add new values that are not already present
45024518
newMap.forEach((valueInNewMap, key) => {
45034519
if (!map.has(key)) {

src/harness/virtualFileSystemWithWatch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ interface Array<T> {}`
405405
return s;
406406
}
407407

408-
private now() {
408+
now() {
409409
this.time += timeIncrements;
410410
return new Date(this.time);
411411
}

src/testRunner/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
"unittests/tsbuild/resolveJsonModule.ts",
103103
"unittests/tsbuild/sample.ts",
104104
"unittests/tsbuild/transitiveReferences.ts",
105+
"unittests/tsbuild/watchEnvironment.ts",
105106
"unittests/tsbuild/watchMode.ts",
106107
"unittests/tscWatch/consoleClearing.ts",
107108
"unittests/tscWatch/emit.ts",
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
namespace ts.tscWatch {
2+
describe("unittests:: tsbuild:: watchEnvironment:: tsbuild:: watchMode:: with different watch environments", () => {
3+
it("watchFile on same file multiple times because file is part of multiple projects", () => {
4+
const project = `${TestFSWithWatch.tsbuildProjectsLocation}/myproject`;
5+
let maxPkgs = 4;
6+
const configPath = `${project}/tsconfig.json`;
7+
const typing: File = {
8+
path: `${project}/typings/xterm.d.ts`,
9+
content: "export const typing = 10;"
10+
};
11+
12+
const allPkgFiles = pkgs(pkgFiles);
13+
const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project });
14+
writePkgReferences();
15+
const host = createSolutionBuilderWithWatchHost(system);
16+
const solutionBuilder = createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true });
17+
solutionBuilder.build();
18+
checkOutputErrorsInitial(system, emptyArray, /*disableConsoleClears*/ undefined, [
19+
`Projects in this build: \r\n${
20+
concatenate(
21+
pkgs(index => ` * pkg${index}/tsconfig.json`),
22+
[" * tsconfig.json"]
23+
).join("\r\n")}\n\n`,
24+
...flatArray(pkgs(index => [
25+
`Project 'pkg${index}/tsconfig.json' is out of date because output file 'pkg${index}/index.js' does not exist\n\n`,
26+
`Building project '${project}/pkg${index}/tsconfig.json'...\n\n`
27+
]))
28+
]);
29+
30+
const watchFilesDetailed = arrayToMap(flatArray(allPkgFiles), f => f.path, () => 1);
31+
watchFilesDetailed.set(configPath, 1);
32+
watchFilesDetailed.set(typing.path, maxPkgs);
33+
checkWatchedFilesDetailed(system, watchFilesDetailed);
34+
system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`);
35+
verifyInvoke();
36+
37+
// Make change
38+
maxPkgs--;
39+
writePkgReferences();
40+
system.checkTimeoutQueueLengthAndRun(1);
41+
checkOutputErrorsIncremental(system, emptyArray);
42+
const lastFiles = last(allPkgFiles);
43+
lastFiles.forEach(f => watchFilesDetailed.delete(f.path));
44+
watchFilesDetailed.set(typing.path, maxPkgs);
45+
checkWatchedFilesDetailed(system, watchFilesDetailed);
46+
system.writeFile(typing.path, typing.content);
47+
verifyInvoke();
48+
49+
// Make change to remove all the watches
50+
maxPkgs = 0;
51+
writePkgReferences();
52+
system.checkTimeoutQueueLengthAndRun(1);
53+
checkOutputErrorsIncremental(system, [
54+
`tsconfig.json(1,10): error TS18002: The 'files' list in config file '${configPath}' is empty.\n`
55+
]);
56+
checkWatchedFilesDetailed(system, [configPath], 1);
57+
58+
system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`);
59+
system.checkTimeoutQueueLength(0);
60+
61+
function flatArray<T>(arr: T[][]): readonly T[] {
62+
return flatMap(arr, identity);
63+
}
64+
function pkgs<T>(cb: (index: number) => T): T[] {
65+
const result: T[] = [];
66+
for (let index = 0; index < maxPkgs; index++) {
67+
result.push(cb(index));
68+
}
69+
return result;
70+
}
71+
function createPkgReference(index: number) {
72+
return { path: `./pkg${index}` };
73+
}
74+
function pkgFiles(index: number): File[] {
75+
return [
76+
{
77+
path: `${project}/pkg${index}/index.ts`,
78+
content: `export const pkg${index} = ${index};`
79+
},
80+
{
81+
path: `${project}/pkg${index}/tsconfig.json`,
82+
content: JSON.stringify({
83+
complerOptions: { composite: true },
84+
include: [
85+
"**/*.ts",
86+
"../typings/xterm.d.ts"
87+
]
88+
})
89+
}
90+
];
91+
}
92+
function writePkgReferences() {
93+
system.writeFile(configPath, JSON.stringify({
94+
files: [],
95+
include: [],
96+
references: pkgs(createPkgReference)
97+
}));
98+
}
99+
function verifyInvoke() {
100+
pkgs(() => system.checkTimeoutQueueLengthAndRun(1));
101+
checkOutputErrorsIncremental(system, emptyArray, /*disableConsoleClears*/ undefined, /*logsBeforeWatchDiagnostics*/ undefined, [
102+
...flatArray(pkgs(index => [
103+
`Project 'pkg${index}/tsconfig.json' is out of date because oldest output 'pkg${index}/index.js' is older than newest input 'typings/xterm.d.ts'\n\n`,
104+
`Building project '${project}/pkg${index}/tsconfig.json'...\n\n`,
105+
`Updating unchanged output timestamps of project '${project}/pkg${index}/tsconfig.json'...\n\n`
106+
]))
107+
]);
108+
}
109+
});
110+
});
111+
}

src/testRunner/unittests/tsbuild/watchMode.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,19 @@ namespace ts.tscWatch {
1616
return host;
1717
}
1818

19+
1920
export function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
2021
const host = createSolutionBuilderHost(system);
22+
host.now = system.now.bind(system);
2123
return ts.createSolutionBuilder(host, rootNames, defaultOptions || {});
2224
}
2325

26+
export function createSolutionBuilderWithWatchHost(system: WatchedSystem) {
27+
const host = ts.createSolutionBuilderWithWatchHost(system);
28+
host.now = system.now.bind(system);
29+
return host;
30+
}
31+
2432
function createSolutionBuilderWithWatch(system: TsBuildWatchSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
2533
const host = createSolutionBuilderWithWatchHost(system);
2634
const solutionBuilder = ts.createSolutionBuilderWithWatch(host, rootNames, defaultOptions || { watch: true });

0 commit comments

Comments
 (0)