diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 763c62deccf..d7eb648bca2 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -13,7 +13,8 @@ namespace ts { interface DependencyGraph { buildQueue: ResolvedConfigFileName[]; - referencingProjectsMap: ConfigFileMap>; + /** value in config File map is true if project is referenced using prepend */ + referencingProjectsMap: ConfigFileMap>; } export interface BuildOptions { @@ -907,17 +908,16 @@ namespace ts { } const buildResult = buildSingleProject(resolved); - // If declaration output changed then only queue in build for downstream projects - if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { - const dependencyGraph = getGlobalDependencyGraph(); - const referencingProjects = dependencyGraph.referencingProjectsMap.getValue(resolved); - if (!referencingProjects) return; - // Always use build order to queue projects - for (const project of dependencyGraph.buildQueue) { - // Can skip circular references - if (referencingProjects.hasKey(project)) { - addProjToQueue(project); - } + const dependencyGraph = getGlobalDependencyGraph(); + const referencingProjects = dependencyGraph.referencingProjectsMap.getValue(resolved); + if (!referencingProjects) return; + // Always use build order to queue projects + for (const project of dependencyGraph.buildQueue) { + const prepend = referencingProjects.getValue(project); + // If the project is referenced with prepend, always build downstream projectm, + // otherwise queue it only if declaration output changed + if (prepend || (prepend !== undefined && !(buildResult & BuildResultFlags.DeclarationOutputUnchanged))) { + addProjToQueue(project); } } } @@ -927,7 +927,7 @@ namespace ts { const permanentMarks = createFileMap(toPath); const circularityReportStack: string[] = []; const buildOrder: ResolvedConfigFileName[] = []; - const referencingProjectsMap = createFileMap>(toPath); + const referencingProjectsMap = createFileMap>(toPath); for (const root of roots) { visit(root); } @@ -958,7 +958,7 @@ namespace ts { visit(resolvedRefPath, inCircularContext || ref.circular); // Get projects referencing resolvedRefPath and add projPath to it const referencingProjects = getOrCreateValueFromConfigFileMap(referencingProjectsMap, resolvedRefPath, () => createFileMap(toPath)); - referencingProjects.setValue(projPath, true); + referencingProjects.setValue(projPath, !!ref.prepend); } } diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 12c82becef3..697d14f72ba 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -276,6 +276,81 @@ export class someClass2 { }`); verifyWatches(host); }); + it("when referenced using prepend, builds referencing project even for non local change", () => { + const coreTsConfig: File = { + path: core[0].path, + content: JSON.stringify({ + compilerOptions: { composite: true, declaration: true, outFile: "index.js" } + }) + }; + const coreIndex: File = { + path: core[1].path, + content: `function foo() { return 10; }` + }; + const logicTsConfig: File = { + path: logic[0].path, + content: JSON.stringify({ + compilerOptions: { composite: true, declaration: true, outFile: "index.js" }, + references: [{ path: "../core", prepend: true }] + }) + }; + const logicIndex: File = { + path: logic[1].path, + content: `function bar() { return foo() + 1 };` + }; + + const projectFiles = [coreTsConfig, coreIndex, logicTsConfig, logicIndex]; + const host = createWatchedSystem([libFile, ...projectFiles], { currentDirectory: projectsLocation }); + createSolutionBuilderWithWatch(host, [`${project}/${SubProject.logic}`]); + verifyWatches(); + checkOutputErrorsInitial(host, emptyArray); + const outputFileStamps = getOutputFileStamps(); + for (const stamp of outputFileStamps) { + assert.isDefined(stamp[1], `${stamp[0]} expected to be present`); + } + + // Make non local change + verifyChangeInCore(`${coreIndex.content} +function myFunc() { return 10; }`); + + // Make local change to function bar + verifyChangeInCore(`${coreIndex.content} +function myFunc() { return 100; }`); + + function verifyChangeInCore(content: string) { + const outputFileStamps = getOutputFileStamps(); + host.writeFile(coreIndex.path, content); + + host.checkTimeoutQueueLengthAndRun(1); // Builds core + const changedCore = getOutputFileStamps(); + verifyChangedFiles(changedCore, outputFileStamps, [ + ...getOutputFileNames(SubProject.core, "index") + ]); + host.checkTimeoutQueueLengthAndRun(1); // Builds logic + const changedLogic = getOutputFileStamps(); + verifyChangedFiles(changedLogic, changedCore, [ + ...getOutputFileNames(SubProject.logic, "index") + ]); + host.checkTimeoutQueueLength(0); + checkOutputErrorsIncremental(host, emptyArray); + verifyWatches(); + } + + function getOutputFileStamps(): OutputFileStamp[] { + const result = [ + ...getOutputStamps(host, SubProject.core, "index"), + ...getOutputStamps(host, SubProject.logic, "index"), + ]; + return result; + } + + function verifyWatches() { + checkWatchedFiles(host, projectFiles.map(f => f.path)); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, [projectPath(SubProject.core), projectPath(SubProject.logic)], /*recursive*/ true); + } + }); + // TODO: write tests reporting errors but that will have more involved work since file }); }