Skip to content

Commit 9320699

Browse files
authored
Merge pull request #28436 from Microsoft/circularTransitiveExports
Use seen map to avoid circular transitive exports to cause stack overflow
2 parents aaf1d80 + 9dbe037 commit 9320699

File tree

2 files changed

+54
-20
lines changed

2 files changed

+54
-20
lines changed

src/compiler/builder.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,13 @@ namespace ts {
201201
}
202202

203203
Debug.assert(!!state.currentAffectedFilesExportedModulesMap);
204+
const seenFileAndExportsOfFile = createMap<true>();
204205
// Go through exported modules from cache first
205206
// If exported modules has path, all files referencing file exported from are affected
206207
if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) =>
207208
exportedModules &&
208209
exportedModules.has(affectedFile.path) &&
209-
removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path)
210+
removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile)
210211
)) {
211212
return;
212213
}
@@ -215,24 +216,28 @@ namespace ts {
215216
forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) =>
216217
!state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
217218
exportedModules.has(affectedFile.path) &&
218-
removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path)
219+
removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile)
219220
);
220221
}
221222

222223
/**
223224
* removes the semantic diagnostics of files referencing referencedPath and
224225
* returns true if there are no more semantic diagnostics from old state
225226
*/
226-
function removeSemanticDiagnosticsOfFilesReferencingPath(state: BuilderProgramState, referencedPath: Path) {
227+
function removeSemanticDiagnosticsOfFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Map<true>) {
227228
return forEachEntry(state.referencedMap!, (referencesInFile, filePath) =>
228-
referencesInFile.has(referencedPath) && removeSemanticDiagnosticsOfFileAndExportsOfFile(state, filePath as Path)
229+
referencesInFile.has(referencedPath) && removeSemanticDiagnosticsOfFileAndExportsOfFile(state, filePath as Path, seenFileAndExportsOfFile)
229230
);
230231
}
231232

232233
/**
233234
* Removes semantic diagnostics of file and anything that exports this file
234235
*/
235-
function removeSemanticDiagnosticsOfFileAndExportsOfFile(state: BuilderProgramState, filePath: Path): boolean {
236+
function removeSemanticDiagnosticsOfFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Map<true>): boolean {
237+
if (!addToSeen(seenFileAndExportsOfFile, filePath)) {
238+
return false;
239+
}
240+
236241
if (removeSemanticDiagnosticsOf(state, filePath)) {
237242
// If there are no more diagnostics from old cache, done
238243
return true;
@@ -244,7 +249,7 @@ namespace ts {
244249
if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) =>
245250
exportedModules &&
246251
exportedModules.has(filePath) &&
247-
removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path)
252+
removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile)
248253
)) {
249254
return true;
250255
}
@@ -253,7 +258,7 @@ namespace ts {
253258
return !!forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) =>
254259
!state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
255260
exportedModules.has(filePath) &&
256-
removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path)
261+
removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile)
257262
);
258263
}
259264

src/testRunner/unittests/tscWatchMode.ts

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,7 +1473,7 @@ foo().hello`
14731473
checkOutputErrorsIncremental(host, emptyArray);
14741474
});
14751475

1476-
it("updates errors when file transitively exported file changes", () => {
1476+
describe("updates errors when file transitively exported file changes", () => {
14771477
const projectLocation = "/user/username/projects/myproject";
14781478
const config: File = {
14791479
path: `${projectLocation}/tsconfig.json`,
@@ -1521,19 +1521,48 @@ export class Data {
15211521
title: string;
15221522
}`
15231523
};
1524-
const filesWithoutConfig = [libFile, app, lib2Public, lib2Data, lib1Public, lib1ToolsPublic, lib1ToolsInterface];
1525-
const files = [config, ...filesWithoutConfig];
1526-
const host = createWatchedSystem(files, { currentDirectory: projectLocation });
1527-
const watch = createWatchOfConfigFile(config.path, host);
1528-
checkProgramActualFiles(watch(), filesWithoutConfig.map(f => f.path));
1529-
checkOutputErrorsInitial(host, emptyArray);
15301524

1531-
host.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2"));
1532-
host.checkTimeoutQueueLengthAndRun(1);
1533-
checkProgramActualFiles(watch(), filesWithoutConfig.map(f => f.path));
1534-
checkOutputErrorsIncremental(host, [
1535-
"lib2/data.ts(5,13): error TS2322: Type '{ title: string; }' is not assignable to type 'ITest'.\n Object literal may only specify known properties, but 'title' does not exist in type 'ITest'. Did you mean to write 'title2'?\n"
1536-
]);
1525+
function verifyTransitiveExports(filesWithoutConfig: ReadonlyArray<File>) {
1526+
const files = [config, ...filesWithoutConfig];
1527+
const host = createWatchedSystem(files, { currentDirectory: projectLocation });
1528+
const watch = createWatchOfConfigFile(config.path, host);
1529+
checkProgramActualFiles(watch(), filesWithoutConfig.map(f => f.path));
1530+
checkOutputErrorsInitial(host, emptyArray);
1531+
1532+
host.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2"));
1533+
host.checkTimeoutQueueLengthAndRun(1);
1534+
checkProgramActualFiles(watch(), filesWithoutConfig.map(f => f.path));
1535+
checkOutputErrorsIncremental(host, [
1536+
"lib2/data.ts(5,13): error TS2322: Type '{ title: string; }' is not assignable to type 'ITest'.\n Object literal may only specify known properties, but 'title' does not exist in type 'ITest'. Did you mean to write 'title2'?\n"
1537+
]);
1538+
1539+
}
1540+
it("when there are no circular import and exports", () => {
1541+
verifyTransitiveExports([libFile, app, lib2Public, lib2Data, lib1Public, lib1ToolsPublic, lib1ToolsInterface]);
1542+
});
1543+
1544+
it("when there are circular import and exports", () => {
1545+
const lib2Data: File = {
1546+
path: `${projectLocation}/lib2/data.ts`,
1547+
content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2";
1548+
export class Data {
1549+
public dat?: Data2; public test() {
1550+
const result: ITest = {
1551+
title: "title"
1552+
}
1553+
return result;
1554+
}
1555+
}`
1556+
};
1557+
const lib2Data2: File = {
1558+
path: `${projectLocation}/lib2/data2.ts`,
1559+
content: `import { Data } from "./data";
1560+
export class Data2 {
1561+
public dat?: Data;
1562+
}`
1563+
};
1564+
verifyTransitiveExports([libFile, app, lib2Public, lib2Data, lib2Data2, lib1Public, lib1ToolsPublic, lib1ToolsInterface]);
1565+
});
15371566
});
15381567
});
15391568

0 commit comments

Comments
 (0)