diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index fbc2dc3f7d2..0fe147def8c 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -225,7 +225,35 @@ namespace ts { */ function removeSemanticDiagnosticsOfFilesReferencingPath(state: BuilderProgramState, referencedPath: Path) { return forEachEntry(state.referencedMap!, (referencesInFile, filePath) => - referencesInFile.has(referencedPath) && removeSemanticDiagnosticsOf(state, filePath as Path) + referencesInFile.has(referencedPath) && removeSemanticDiagnosticsOfFileAndExportsOfFile(state, filePath as Path) + ); + } + + /** + * Removes semantic diagnostics of file and anything that exports this file + */ + function removeSemanticDiagnosticsOfFileAndExportsOfFile(state: BuilderProgramState, filePath: Path): boolean { + if (removeSemanticDiagnosticsOf(state, filePath)) { + // If there are no more diagnostics from old cache, done + return true; + } + + Debug.assert(!!state.currentAffectedFilesExportedModulesMap); + // Go through exported modules from cache first + // If exported modules has path, all files referencing file exported from are affected + if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) => + exportedModules && + exportedModules.has(filePath) && + removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path) + )) { + return true; + } + + // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected + return !!forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) => + !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it + exportedModules.has(filePath) && + removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path) ); } diff --git a/src/testRunner/unittests/tscWatchMode.ts b/src/testRunner/unittests/tscWatchMode.ts index 3523db7544f..e60e50dacc7 100644 --- a/src/testRunner/unittests/tscWatchMode.ts +++ b/src/testRunner/unittests/tscWatchMode.ts @@ -1472,6 +1472,69 @@ foo().hello` checkProgramActualFiles(watch(), [aFile.path, libFile.path]); checkOutputErrorsIncremental(host, emptyArray); }); + + it("updates errors when file transitively exported file changes", () => { + const projectLocation = "/user/username/projects/myproject"; + const config: File = { + path: `${projectLocation}/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts"], + compilerOptions: { baseUrl: "." } + }) + }; + const app: File = { + path: `${projectLocation}/app.ts`, + content: `import { Data } from "lib2/public"; +export class App { + public constructor() { + new Data().test(); + } +}` + }; + const lib2Public: File = { + path: `${projectLocation}/lib2/public.ts`, + content: `export * from "./data";` + }; + const lib2Data: File = { + path: `${projectLocation}/lib2/data.ts`, + content: `import { ITest } from "lib1/public"; +export class Data { + public test() { + const result: ITest = { + title: "title" + } + return result; + } +}` + }; + const lib1Public: File = { + path: `${projectLocation}/lib1/public.ts`, + content: `export * from "./tools/public";` + }; + const lib1ToolsPublic: File = { + path: `${projectLocation}/lib1/tools/public.ts`, + content: `export * from "./tools.interface";` + }; + const lib1ToolsInterface: File = { + path: `${projectLocation}/lib1/tools/tools.interface.ts`, + content: `export interface ITest { + title: string; +}` + }; + const filesWithoutConfig = [libFile, app, lib2Public, lib2Data, lib1Public, lib1ToolsPublic, lib1ToolsInterface]; + const files = [config, ...filesWithoutConfig]; + const host = createWatchedSystem(files, { currentDirectory: projectLocation }); + const watch = createWatchOfConfigFile(config.path, host); + checkProgramActualFiles(watch(), filesWithoutConfig.map(f => f.path)); + checkOutputErrorsInitial(host, emptyArray); + + host.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")); + host.checkTimeoutQueueLengthAndRun(1); + checkProgramActualFiles(watch(), filesWithoutConfig.map(f => f.path)); + checkOutputErrorsIncremental(host, [ + "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" + ]); + }); }); describe("tsc-watch emit with outFile or out setting", () => {