diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 9eabae70d2e..45b9351759c 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -842,7 +842,7 @@ namespace ts { return deduplicateSorted(sort(array, comparer), equalityComparer || comparer); } - export function arrayIsEqualTo(array1: ReadonlyArray | undefined, array2: ReadonlyArray | undefined, equalityComparer: (a: T, b: T) => boolean = equateValues): boolean { + export function arrayIsEqualTo(array1: ReadonlyArray | undefined, array2: ReadonlyArray | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean { if (!array1 || !array2) { return array1 === array2; } @@ -852,7 +852,7 @@ namespace ts { } for (let i = 0; i < array1.length; i++) { - if (!equalityComparer(array1[i], array2[i])) { + if (!equalityComparer(array1[i], array2[i], i)) { return false; } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index c3f6a98258d..c6fe9370154 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -455,7 +455,7 @@ namespace ts { } // If project references dont match - if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences)) { + if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) { return false; } @@ -483,9 +483,27 @@ namespace ts { return true; - function sourceFileNotUptoDate(sourceFile: SourceFile): boolean { - return sourceFile.version !== getSourceVersion(sourceFile.path) || - hasInvalidatedResolution(sourceFile.path); + function sourceFileNotUptoDate(sourceFile: SourceFile) { + return !sourceFileVersionUptoDate(sourceFile) || + hasInvalidatedResolution(sourceFile.resolvedPath || sourceFile.path); + } + + function sourceFileVersionUptoDate(sourceFile: SourceFile) { + return sourceFile.version === getSourceVersion(sourceFile.resolvedPath || sourceFile.path); + } + + function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) { + if (!projectReferenceIsEqualTo(oldRef, newRef)) { + return false; + } + const oldResolvedRef = program!.getResolvedProjectReferences()![index]; + if (oldResolvedRef) { + // If sourceFile for the oldResolvedRef existed, check the version for uptodate + return sourceFileVersionUptoDate(oldResolvedRef.sourceFile); + } + // In old program, not able to resolve project reference path, + // so if config file doesnt exist, it is uptodate. + return !fileExists(resolveProjectReferencePath(oldRef)); } } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 43c7c5ba897..b85d5aa1c79 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -187,9 +187,10 @@ interface Array {}` } export function checkArray(caption: string, actual: ReadonlyArray, expected: ReadonlyArray) { + checkMapKeys(caption, arrayToMap(actual, identity), expected); assert.equal(actual.length, expected.length, `${caption}: incorrect actual number of files, expected:\r\n${expected.join("\r\n")}\r\ngot: ${actual.join("\r\n")}`); for (const f of expected) { - assert.equal(true, contains(actual, f), `${caption}: expected to find ${f} in ${actual}`); + assert.isTrue(contains(actual, f), `${caption}: expected to find ${f} in ${actual}`); } } diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 994ec6dce35..f0745a373a0 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -34,6 +34,10 @@ namespace ts.tscWatch { return `${projectPath(subProject)}/${baseFileName.toLowerCase()}`; } + function projectFileName(subProject: SubProject, baseFileName: string) { + return `${projectPath(subProject)}/${baseFileName}`; + } + function projectFile(subProject: SubProject, baseFileName: string): File { return { path: projectFilePath(subProject, baseFileName), @@ -391,10 +395,10 @@ let x: string = 10;`); }); describe("tsc-watch works with project references", () => { - const coreIndexDts = projectFilePath(SubProject.core, "index.d.ts"); - const coreAnotherModuleDts = projectFilePath(SubProject.core, "anotherModule.d.ts"); - const logicIndexDts = projectFilePath(SubProject.logic, "index.d.ts"); - const expectedWatchedFiles = [core[0], logic[0], ...tests, libFile].map(f => f.path).concat(coreIndexDts, coreAnotherModuleDts, logicIndexDts); + const coreIndexDts = projectFileName(SubProject.core, "index.d.ts"); + const coreAnotherModuleDts = projectFileName(SubProject.core, "anotherModule.d.ts"); + const logicIndexDts = projectFileName(SubProject.logic, "index.d.ts"); + const expectedWatchedFiles = [core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase())); const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests)); function createSolution() { @@ -432,7 +436,7 @@ let x: string = 10;`); } function verifyDependencies(watch: () => BuilderProgram, filePath: string, expected: ReadonlyArray) { - checkArray(`${filePath} dependencies`, watch().getAllDependencies(watch().getSourceFile(filePath)!).map(f => f.toLocaleLowerCase()), expected); + checkArray(`${filePath} dependencies`, watch().getAllDependencies(watch().getSourceFile(filePath)!), expected); } describe("invoking when references are already built", () => { @@ -447,7 +451,7 @@ let x: string = 10;`); }); it("local edit in ts file, result in watch compilation because logic.d.ts is written", () => { - const { host, solutionBuilder } = createSolutionAndWatchMode(); + const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); host.writeFile(logic[1].path, `${logic[1].content} function foo() { }`); @@ -456,10 +460,11 @@ function foo() { host.checkTimeoutQueueLengthAndRun(1); // not ideal, but currently because of d.ts but no new file is written checkOutputErrorsIncremental(host, emptyArray); + checkProgramActualFiles(watch().getProgram(), [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts]); }); it("non local edit in ts file, rebuilds in watch compilation", () => { - const { host, solutionBuilder } = createSolutionAndWatchMode(); + const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); host.writeFile(logic[1].path, `${logic[1].content} export function gfoo() { }`); @@ -468,8 +473,26 @@ export function gfoo() { host.checkTimeoutQueueLengthAndRun(1); checkOutputErrorsIncremental(host, emptyArray); + checkProgramActualFiles(watch().getProgram(), [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts]); }); + it("change in project reference config file builds correctly", () => { + const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); + host.writeFile(logic[0].path, JSON.stringify({ + compilerOptions: { composite: true, declaration: true, declarationDir: "decls" }, + references: [{ path: "../core" }] + })); + solutionBuilder.invalidateProject(logic[0].path, ConfigFileProgramReloadLevel.Full); + solutionBuilder.buildInvalidatedProject(); + + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, [ + // TODO: #26036 + // The error is reported in d.ts file because it isnt resolved from ts file path, but is resolved from .d.ts file + "sample1/logic/decls/index.d.ts(2,22): error TS2307: Cannot find module '../core/anotherModule'.\n" + ]); + checkProgramActualFiles(watch().getProgram(), [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]); + }); }); }); });