diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 637e77c545f..7973a4cc8dd 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -281,10 +281,19 @@ namespace ts { } // 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) => + if (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, seenFileAndExportsOfFile) + )) { + return true; + } + + // Remove diagnostics of files that import this file (without going to exports of referencing files) + return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) => + referencesInFile.has(filePath) && + !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file + removeSemanticDiagnosticsOf(state, referencingFilePath as Path) // Dont add to seen since this is not yet done with the export removal ); } diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 9dcc98eef86..2e5341732b5 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -89,6 +89,7 @@ "unittests/services/transpile.ts", "unittests/tscWatch/consoleClearing.ts", "unittests/tscWatch/emit.ts", + "unittests/tscWatch/emitAndErrorUpdates.ts", "unittests/tscWatch/programUpdates.ts", "unittests/tscWatch/resolutionCache.ts", "unittests/tscWatch/watchEnvironment.ts", diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 72da8dbf85e..c1ddb0aa4e7 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -479,7 +479,7 @@ let x: string = 10;`); const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps); // Build in watch mode - const watch = createWatchOfConfigFileReturningBuilder(watchConfig, host); + const watch = createWatchOfConfigFile(watchConfig, host); checkOutputErrorsInitial(host, emptyArray); return { host, solutionBuilder, watch }; @@ -505,10 +505,8 @@ let x: string = 10;`); projectSystem.checkProjectActualFiles(service.configuredProjects.get(configFile.toLowerCase())!, expectedFiles); } - type Watch = () => BuilderProgram; - function verifyDependencies(watch: Watch, filePath: string, expected: ReadonlyArray) { - checkArray(`${filePath} dependencies`, watch().getAllDependencies(watch().getSourceFile(filePath)!), expected); + checkArray(`${filePath} dependencies`, watch.getBuilderProgram().getAllDependencies(watch().getSourceFile(filePath)!), expected); } describe("on sample project", () => { @@ -542,7 +540,7 @@ let x: string = 10;`); host.checkTimeoutQueueLengthAndRun(1); checkOutputErrorsIncremental(host, emptyArray); - checkProgramActualFiles(watch().getProgram(), expectedFilesAfterEdit); + checkProgramActualFiles(watch(), expectedFilesAfterEdit); }); @@ -642,7 +640,7 @@ export function gfoo() { expectedWatchedDirectoriesRecursive: ReadonlyArray, dependencies: ReadonlyArray<[string, ReadonlyArray]>, expectedWatchedDirectories?: ReadonlyArray) { - checkProgramActualFiles(watch().getProgram(), expectedProgramFiles); + checkProgramActualFiles(watch(), expectedProgramFiles); verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories); for (const [file, deps] of dependencies) { verifyDependencies(watch, file, deps); diff --git a/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts b/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts new file mode 100644 index 00000000000..b76c9a985d4 --- /dev/null +++ b/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts @@ -0,0 +1,307 @@ +namespace ts.tscWatch { + describe("unittests:: tsc-watch:: Emit times and Error updates in builder after program changes", () => { + const currentDirectory = "/user/username/projects/myproject"; + const config: File = { + path: `${currentDirectory}/tsconfig.json`, + content: `{}` + }; + function getOutputFileStampAndError(host: WatchedSystem, watch: Watch, file: File) { + const builderProgram = watch.getBuilderProgram(); + const state = builderProgram.getState(); + return { + file, + fileStamp: host.getModifiedTime(file.path.replace(".ts", ".js")), + errors: builderProgram.getSemanticDiagnostics(watch().getSourceFileByPath(file.path as Path)), + errorsFromOldState: !!state.semanticDiagnosticsFromOldState && state.semanticDiagnosticsFromOldState.has(file.path) + }; + } + + function getOutputFileStampsAndErrors(host: WatchedSystem, watch: Watch, directoryFiles: ReadonlyArray) { + return directoryFiles.map(d => getOutputFileStampAndError(host, watch, d)); + } + + function findStampAndErrors(stampsAndErrors: ReadonlyArray>, file: File) { + return find(stampsAndErrors, info => info.file === file)!; + } + + function verifyOutputFileStampsAndErrors( + file: File, + emitExpected: boolean, + errorRefershExpected: boolean, + beforeChangeFileStampsAndErrors: ReadonlyArray>, + afterChangeFileStampsAndErrors: ReadonlyArray> + ) { + const beforeChange = findStampAndErrors(beforeChangeFileStampsAndErrors, file); + const afterChange = findStampAndErrors(afterChangeFileStampsAndErrors, file); + if (emitExpected) { + assert.notStrictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Expected emit for file ${file.path}`); + } + else { + assert.strictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Did not expect new emit for file ${file.path}`); + } + if (errorRefershExpected) { + if (afterChange.errors !== emptyArray || beforeChange.errors !== emptyArray) { + assert.notStrictEqual(afterChange.errors, beforeChange.errors, `Expected new errors for file ${file.path}`); + } + assert.isFalse(afterChange.errorsFromOldState, `Expected errors to be not copied from old state for file ${file.path}`); + } + else { + assert.strictEqual(afterChange.errors, beforeChange.errors, `Expected errors to not change for file ${file.path}`); + assert.isTrue(afterChange.errorsFromOldState, `Expected errors to be copied from old state for file ${file.path}`); + } + } + + interface VerifyEmitAndErrorUpdates { + change: (host: WatchedSystem) => void; + getInitialErrors: (watch: Watch) => ReadonlyArray | ReadonlyArray; + getIncrementalErrors: (watch: Watch) => ReadonlyArray | ReadonlyArray; + filesWithNewEmit: ReadonlyArray; + filesWithOnlyErrorRefresh: ReadonlyArray; + filesNotTouched: ReadonlyArray; + configFile?: File; + } + + function verifyEmitAndErrorUpdates({ filesWithNewEmit, filesWithOnlyErrorRefresh, filesNotTouched, configFile = config, change, getInitialErrors, getIncrementalErrors }: VerifyEmitAndErrorUpdates) { + const nonLibFiles = [...filesWithNewEmit, ...filesWithOnlyErrorRefresh, ...filesNotTouched]; + const files = [...nonLibFiles, configFile, libFile]; + const host = createWatchedSystem(files, { currentDirectory }); + const watch = createWatchOfConfigFile("tsconfig.json", host); + checkProgramActualFiles(watch(), [...nonLibFiles.map(f => f.path), libFile.path]); + checkOutputErrorsInitial(host, getInitialErrors(watch)); + const beforeChange = getOutputFileStampsAndErrors(host, watch, nonLibFiles); + change(host); + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, getIncrementalErrors(watch)); + const afterChange = getOutputFileStampsAndErrors(host, watch, nonLibFiles); + filesWithNewEmit.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ true, /*errorRefershExpected*/ true, beforeChange, afterChange)); + filesWithOnlyErrorRefresh.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ false, /*errorRefershExpected*/ true, beforeChange, afterChange)); + filesNotTouched.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ false, /*errorRefershExpected*/ false, beforeChange, afterChange)); + } + + describe("deep import changes", () => { + const aFile: File = { + path: `${currentDirectory}/a.ts`, + content: `import {B} from './b'; +declare var console: any; +let b = new B(); +console.log(b.c.d);` + }; + + function verifyDeepImportChange(bFile: File, cFile: File) { + const filesWithNewEmit: File[] = []; + const filesWithOnlyErrorRefresh = [aFile]; + addImportedModule(bFile); + addImportedModule(cFile); + verifyEmitAndErrorUpdates({ + filesWithNewEmit, + filesWithOnlyErrorRefresh, + filesNotTouched: emptyArray, + change: host => host.writeFile(cFile.path, cFile.content.replace("d", "d2")), + getInitialErrors: () => emptyArray, + getIncrementalErrors: watch => [ + getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.lastIndexOf("d"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "d", "C") + ] + }); + + function addImportedModule(file: File) { + if (file.path.endsWith(".d.ts")) { + filesWithOnlyErrorRefresh.push(file); + } + else { + filesWithNewEmit.push(file); + } + } + } + + it("updates errors when deep import file changes", () => { + const bFile: File = { + path: `${currentDirectory}/b.ts`, + content: `import {C} from './c'; +export class B +{ + c = new C(); +}` + }; + const cFile: File = { + path: `${currentDirectory}/c.ts`, + content: `export class C +{ + d = 1; +}` + }; + verifyDeepImportChange(bFile, cFile); + }); + + it("updates errors when deep import through declaration file changes", () => { + const bFile: File = { + path: `${currentDirectory}/b.d.ts`, + content: `import {C} from './c'; +export class B +{ + c: C; +}` + }; + const cFile: File = { + path: `${currentDirectory}/c.d.ts`, + content: `export class C +{ + d: number; +}` + }; + verifyDeepImportChange(bFile, cFile); + }); + }); + + it("updates errors in file not exporting a deep multilevel import that changes", () => { + const aFile: File = { + path: `${currentDirectory}/a.ts`, + content: `export interface Point { + name: string; + c: Coords; +} +export interface Coords { + x2: number; + y: number; +}` + }; + const bFile: File = { + path: `${currentDirectory}/b.ts`, + content: `import { Point } from "./a"; +export interface PointWrapper extends Point { +}` + }; + const cFile: File = { + path: `${currentDirectory}/c.ts`, + content: `import { PointWrapper } from "./b"; +export function getPoint(): PointWrapper { + return { + name: "test", + c: { + x: 1, + y: 2 + } + } +};` + }; + const dFile: File = { + path: `${currentDirectory}/d.ts`, + content: `import { getPoint } from "./c"; +getPoint().c.x;` + }; + const eFile: File = { + path: `${currentDirectory}/e.ts`, + content: `import "./d";` + }; + verifyEmitAndErrorUpdates({ + filesWithNewEmit: [aFile, bFile], + filesWithOnlyErrorRefresh: [cFile, dFile], + filesNotTouched: [eFile], + change: host => host.writeFile(aFile.path, aFile.content.replace("x2", "x")), + getInitialErrors: watch => [ + getDiagnosticOfFileFromProgram(watch(), cFile.path, cFile.content.indexOf("x: 1"), 4, chainDiagnosticMessages( + chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, "x", "Coords"), + Diagnostics.Type_0_is_not_assignable_to_type_1, + "{ x: number; y: number; }", + "Coords" + )), + getDiagnosticOfFileFromProgram(watch(), dFile.path, dFile.content.lastIndexOf("x"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "x", "Coords") + ], + getIncrementalErrors: () => emptyArray + }); + }); + + describe("updates errors when file transitively exported file changes", () => { + const config: File = { + path: `${currentDirectory}/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts"], + compilerOptions: { baseUrl: "." } + }) + }; + const app: File = { + path: `${currentDirectory}/app.ts`, + content: `import { Data } from "lib2/public"; +export class App { + public constructor() { + new Data().test(); + } +}` + }; + const lib2Public: File = { + path: `${currentDirectory}/lib2/public.ts`, + content: `export * from "./data";` + }; + const lib2Data: File = { + path: `${currentDirectory}/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: `${currentDirectory}/lib1/public.ts`, + content: `export * from "./tools/public";` + }; + const lib1ToolsPublic: File = { + path: `${currentDirectory}/lib1/tools/public.ts`, + content: `export * from "./tools.interface";` + }; + const lib1ToolsInterface: File = { + path: `${currentDirectory}/lib1/tools/tools.interface.ts`, + content: `export interface ITest { + title: string; +}` + }; + + function verifyTransitiveExports(lib2Data: File, lib2Data2?: File) { + const filesWithNewEmit = [lib1ToolsInterface, lib1ToolsPublic]; + const filesWithOnlyErrorRefresh = [app, lib2Public, lib1Public, lib2Data]; + if (lib2Data2) { + filesWithOnlyErrorRefresh.push(lib2Data2); + } + verifyEmitAndErrorUpdates({ + filesWithNewEmit, + filesWithOnlyErrorRefresh, + filesNotTouched: emptyArray, + configFile: config, + change: host => host.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), + getInitialErrors: () => emptyArray, + getIncrementalErrors: () => [ + "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" + ] + }); + } + it("when there are no circular import and exports", () => { + verifyTransitiveExports(lib2Data); + }); + + it("when there are circular import and exports", () => { + const lib2Data: File = { + path: `${currentDirectory}/lib2/data.ts`, + content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2"; +export class Data { + public dat?: Data2; public test() { + const result: ITest = { + title: "title" + } + return result; + } +}` + }; + const lib2Data2: File = { + path: `${currentDirectory}/lib2/data2.ts`, + content: `import { Data } from "./data"; +export class Data2 { + public dat?: Data; +}` + }; + verifyTransitiveExports(lib2Data, lib2Data2); + }); + }); + }); +} diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index af896009c0d..b593cabbec6 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -29,18 +29,18 @@ namespace ts.tscWatch { checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles); } - export function createWatchOfConfigFileReturningBuilder(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) { - const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host); - compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; - const watch = createWatchProgram(compilerHost); - return () => watch.getCurrentProgram(); + export interface Watch { + (): Program; + getBuilderProgram(): EmitAndSemanticDiagnosticsBuilderProgram; } export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) { const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host); compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; const watch = createWatchProgram(compilerHost); - return () => watch.getCurrentProgram().getProgram(); + const result = (() => watch.getCurrentProgram().getProgram()) as Watch; + result.getBuilderProgram = () => watch.getCurrentProgram(); + return result; } export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) { @@ -152,7 +152,21 @@ namespace ts.tscWatch { assert.equal(host.exitCode, expectedExitCode); } - export function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic { + function isDiagnosticMessageChain(message: DiagnosticMessage | DiagnosticMessageChain): message is DiagnosticMessageChain { + return !!(message as DiagnosticMessageChain).messageText; + } + + export function getDiagnosticOfFileFrom(file: SourceFile | undefined, start: number | undefined, length: number | undefined, message: DiagnosticMessage | DiagnosticMessageChain, ..._args: (string | number)[]): Diagnostic { + let text: DiagnosticMessageChain | string; + if (isDiagnosticMessageChain(message)) { + text = message; + } + else { + text = getLocaleSpecificMessage(message); + if (arguments.length > 4) { + text = formatStringFromArgs(text, arguments, 4); + } + } return { file, start, @@ -164,35 +178,17 @@ namespace ts.tscWatch { }; } - export function getDiagnosticWithoutFile(message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { - let text = getLocaleSpecificMessage(message); - - if (arguments.length > 1) { - text = formatStringFromArgs(text, arguments, 1); - } - - return getDiagnosticOfFileFrom(/*file*/ undefined, text, /*start*/ undefined, /*length*/ undefined, message); + export function getDiagnosticWithoutFile(message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { + return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args); } - export function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { - let text = getLocaleSpecificMessage(message); - - if (arguments.length > 4) { - text = formatStringFromArgs(text, arguments, 4); - } - - return getDiagnosticOfFileFrom(file, text, start, length, message); + export function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { + return getDiagnosticOfFileFrom(file, start, length, message, ...args); } - export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { - let text = getLocaleSpecificMessage(message); - - if (arguments.length > 5) { - text = formatStringFromArgs(text, arguments, 5); - } - + export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase()))!, - text, start, length, message); + start, length, message, ...args); } export function getUnknownCompilerOption(program: Program, configFile: File, option: string) { diff --git a/src/testRunner/unittests/tscWatch/programUpdates.ts b/src/testRunner/unittests/tscWatch/programUpdates.ts index 6109db5a341..45ff6fba7c5 100644 --- a/src/testRunner/unittests/tscWatch/programUpdates.ts +++ b/src/testRunner/unittests/tscWatch/programUpdates.ts @@ -1070,92 +1070,6 @@ export default test;`; } }); - it("updates errors when deep import file changes", () => { - const currentDirectory = "/user/username/projects/myproject"; - const aFile: File = { - path: `${currentDirectory}/a.ts`, - content: `import {B} from './b'; -declare var console: any; -let b = new B(); -console.log(b.c.d);` - }; - const bFile: File = { - path: `${currentDirectory}/b.ts`, - content: `import {C} from './c'; -export class B -{ - c = new C(); -}` - }; - const cFile: File = { - path: `${currentDirectory}/c.ts`, - content: `export class C -{ - d = 1; -}` - }; - const config: File = { - path: `${currentDirectory}/tsconfig.json`, - content: `{}` - }; - const files = [aFile, bFile, cFile, config, libFile]; - const host = createWatchedSystem(files, { currentDirectory }); - const watch = createWatchOfConfigFile("tsconfig.json", host); - checkProgramActualFiles(watch(), [aFile.path, bFile.path, cFile.path, libFile.path]); - checkOutputErrorsInitial(host, emptyArray); - const modifiedTimeOfAJs = host.getModifiedTime(`${currentDirectory}/a.js`); - host.writeFile(cFile.path, cFile.content.replace("d", "d2")); - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, [ - getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.lastIndexOf("d"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "d", "C") - ]); - // File a need not be rewritten - assert.equal(host.getModifiedTime(`${currentDirectory}/a.js`), modifiedTimeOfAJs); - }); - - it("updates errors when deep import through declaration file changes", () => { - const currentDirectory = "/user/username/projects/myproject"; - const aFile: File = { - path: `${currentDirectory}/a.ts`, - content: `import {B} from './b'; -declare var console: any; -let b = new B(); -console.log(b.c.d);` - }; - const bFile: File = { - path: `${currentDirectory}/b.d.ts`, - content: `import {C} from './c'; -export class B -{ - c: C; -}` - }; - const cFile: File = { - path: `${currentDirectory}/c.d.ts`, - content: `export class C -{ - d: number; -}` - }; - const config: File = { - path: `${currentDirectory}/tsconfig.json`, - content: `{}` - }; - const files = [aFile, bFile, cFile, config, libFile]; - const host = createWatchedSystem(files, { currentDirectory }); - const watch = createWatchOfConfigFile("tsconfig.json", host); - checkProgramActualFiles(watch(), [aFile.path, bFile.path, cFile.path, libFile.path]); - checkOutputErrorsInitial(host, emptyArray); - const modifiedTimeOfAJs = host.getModifiedTime(`${currentDirectory}/a.js`); - host.writeFile(cFile.path, cFile.content.replace("d", "d2")); - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, [ - getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.lastIndexOf("d"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "d", "C") - ]); - // File a need not be rewritten - assert.equal(host.getModifiedTime(`${currentDirectory}/a.js`), modifiedTimeOfAJs); - }); - it("updates errors when strictNullChecks changes", () => { const currentDirectory = "/user/username/projects/myproject"; const aFile: File = { @@ -1228,98 +1142,6 @@ foo().hello` checkOutputErrorsIncremental(host, emptyArray); }); - describe("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; -}` - }; - - function verifyTransitiveExports(filesWithoutConfig: ReadonlyArray) { - 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" - ]); - - } - it("when there are no circular import and exports", () => { - verifyTransitiveExports([libFile, app, lib2Public, lib2Data, lib1Public, lib1ToolsPublic, lib1ToolsInterface]); - }); - - it("when there are circular import and exports", () => { - const lib2Data: File = { - path: `${projectLocation}/lib2/data.ts`, - content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2"; -export class Data { - public dat?: Data2; public test() { - const result: ITest = { - title: "title" - } - return result; - } -}` - }; - const lib2Data2: File = { - path: `${projectLocation}/lib2/data2.ts`, - content: `import { Data } from "./data"; -export class Data2 { - public dat?: Data; -}` - }; - verifyTransitiveExports([libFile, app, lib2Public, lib2Data, lib2Data2, lib1Public, lib1ToolsPublic, lib1ToolsInterface]); - }); - }); - describe("updates errors in lib file", () => { const currentDirectory = "/user/username/projects/myproject"; const field = "fullscreen";