From 7b290fdbd4e892379c3ab3ed46d7b41f15a31a2b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 20 Dec 2018 11:33:11 -0800 Subject: [PATCH] Update the timestamps of outputs that dont need to be written because of incremental build This ensures that after `tsbuild` after incremental build of `tsbuild -w` doesnt result in unnecessary rebuilds --- src/compiler/diagnosticMessages.json | 4 + src/compiler/tsbuild.ts | 97 ++++++++-- src/harness/fakes.ts | 3 + src/testRunner/unittests/tsbuild.ts | 59 +++--- src/testRunner/unittests/tsbuildWatchMode.ts | 181 +++++++++++-------- 5 files changed, 226 insertions(+), 118 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b0dfb85ce6d25..b6fec29ca8e6c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3945,6 +3945,10 @@ "category": "Error", "code": 6370 }, + "Updating unchanged output timestamps of project '{0}'...": { + "category": "Message", + "code": 6371 + }, "The expected type comes from property '{0}' which is declared here on type '{1}'": { "category": "Message", diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 78cdf676d2f06..ae5ddfcccf31f 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -119,7 +119,7 @@ namespace ts { newestDeclarationFileContentChangedTime?: Date; newestOutputFileTime?: Date; newestOutputFileName?: string; - oldestOutputFileName?: string; + oldestOutputFileName: string; } /** @@ -332,6 +332,9 @@ namespace ts { // TODO: To do better with watch mode and normal build mode api that creates program and emits files // This currently helps enable --diagnostics and --extendedDiagnostics afterProgramEmitAndDiagnostics?(program: T): void; + + // For testing + now?(): Date; } export interface SolutionBuilderHost extends SolutionBuilderHostBase { @@ -991,16 +994,40 @@ namespace ts { return; } + if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { + // Fake build + updateOutputTimestamps(proj); + return; + } + const buildResult = buildSingleProject(resolved); - const dependencyGraph = getGlobalDependencyGraph(); - const referencingProjects = dependencyGraph.referencingProjectsMap.getValue(resolved); + if (buildResult & BuildResultFlags.AnyErrors) return; + + const { referencingProjectsMap, buildQueue } = getGlobalDependencyGraph(); + const referencingProjects = referencingProjectsMap.getValue(resolved); if (!referencingProjects) return; + // Always use build order to queue projects - for (const project of dependencyGraph.buildQueue) { + for (let index = buildQueue.indexOf(resolved) + 1; index < buildQueue.length; index++) { + const project = buildQueue[index]; 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))) { + if (prepend !== undefined) { + // If the project is referenced with prepend, always build downstream project, + // If declaration output is changed changed, build the project + // otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps + const status = projectStatus.getValue(project); + if (prepend || !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { + if (status && (status.type === UpToDateStatusType.UpToDate || status.type === UpToDateStatusType.UpToDateWithUpstreamTypes)) { + projectStatus.setValue(project, { + type: UpToDateStatusType.OutOfDateWithUpstream, + outOfDateOutputFileName: status.oldestOutputFileName, + newerProjectName: resolved + }); + } + } + else if (status && status.type === UpToDateStatusType.UpToDate) { + status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; + } addProjToQueue(project); } } @@ -1110,6 +1137,7 @@ namespace ts { let declDiagnostics: Diagnostic[] | undefined; const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); const outputFiles: OutputFile[] = []; + // TODO:: handle declaration diagnostics in incremental build. emitFilesAndReportErrors(program, reportDeclarationDiagnostics, writeFileName, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark })); // Don't emit .d.ts if there are decl file errors if (declDiagnostics) { @@ -1118,6 +1146,7 @@ namespace ts { // Actual Emit const emitterDiagnostics = createDiagnosticCollection(); + const emittedOutputs = createFileMap(toPath as ToPath); outputFiles.forEach(({ name, text, writeByteOrderMark }) => { let priorChangeTime: Date | undefined; if (!anyDtsChanged && isDeclarationFile(name)) { @@ -1131,6 +1160,7 @@ namespace ts { } } + emittedOutputs.setValue(name, true); writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); if (priorChangeTime !== undefined) { newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); @@ -1143,9 +1173,13 @@ namespace ts { return buildErrors(emitDiagnostics, BuildResultFlags.EmitErrors, "Emit"); } + // Update time stamps for rest of the outputs + newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(configFile, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); + const status: UpToDateStatus = { type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime + newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, + oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile) }; diagnostics.removeKey(proj); projectStatus.setValue(proj, status); @@ -1175,23 +1209,34 @@ namespace ts { if (options.dry) { return reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj.options.configFilePath!); } + const priorNewestUpdateTime = updateOutputTimestampsWorker(proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0); + projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime } as UpToDateStatus); + } - if (options.verbose) { - reportStatus(Diagnostics.Updating_output_timestamps_of_project_0, proj.options.configFilePath!); - } - - const now = new Date(); + function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { const outputs = getAllProjectOutputs(proj); - let priorNewestUpdateTime = minimumDate; - for (const file of outputs) { - if (isDeclarationFile(file)) { - priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); + if (!skipOutputs || outputs.length !== skipOutputs.getSize()) { + if (options.verbose) { + reportStatus(verboseMessage, proj.options.configFilePath!); } + const now = host.now ? host.now() : new Date(); + for (const file of outputs) { + if (skipOutputs && skipOutputs.hasKey(file)) { + continue; + } + + if (isDeclarationFile(file)) { + priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); + } - host.setModifiedTime(file, now); + host.setModifiedTime(file, now); + if (proj.options.listEmittedFiles) { + writeFileName(`TSFILE: ${file}`); + } + } } - projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime } as UpToDateStatus); + return priorNewestUpdateTime; } function getFilesToClean(): string[] { @@ -1368,6 +1413,20 @@ namespace ts { } } + function getFirstProjectOutput(project: ParsedCommandLine): string { + if (project.options.outFile || project.options.out) { + return first(getOutFileOutputs(project)); + } + + for (const inputFile of project.fileNames) { + const outputs = getOutputFileNames(inputFile, project); + if (outputs.length) { + return first(outputs); + } + } + return Debug.fail(`project ${project.options.configFilePath} expected to have atleast one output`); + } + export function formatUpToDateStatus(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) { switch (status.type) { case UpToDateStatusType.OutOfDateWithSelf: diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts index 6119dd153b9c6..e8c8161705252 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakes.ts @@ -377,6 +377,9 @@ namespace fakes { export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost { createProgram = ts.createAbstractBuilder; + now() { + return new Date(this.sys.vfs.time()); + } diagnostics: ts.Diagnostic[] = []; diff --git a/src/testRunner/unittests/tsbuild.ts b/src/testRunner/unittests/tsbuild.ts index 4a3f259cc3452..ba94072b230ac 100644 --- a/src/testRunner/unittests/tsbuild.ts +++ b/src/testRunner/unittests/tsbuild.ts @@ -234,39 +234,46 @@ namespace ts { // Update a timestamp in the middle project tick(); touch(fs, "/src/logic/index.ts"); + const originalWriteFile = fs.writeFileSync; + const writtenFiles = createMap(); + fs.writeFileSync = (path, data, encoding) => { + writtenFiles.set(path, true); + originalWriteFile.call(fs, path, data, encoding); + }; // Because we haven't reset the build context, the builder should assume there's nothing to do right now const status = builder.getUpToDateStatusOfFile(builder.resolveProjectName("/src/logic")); assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date"); + verifyInvalidation(/*expectedToWriteTests*/ false); // Rebuild this project - tick(); - builder.invalidateProject("/src/logic"); - builder.buildInvalidatedProject(); - // The file should be updated - assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); - assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); - - // Does not build tests or core because there is no change in declaration file - tick(); - builder.buildInvalidatedProject(); - assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt"); - assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt"); - - // Rebuild this project - tick(); fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")} export class cNew {}`); - builder.invalidateProject("/src/logic"); - builder.buildInvalidatedProject(); - // The file should be updated - assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); - assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); - - // Build downstream projects should update 'tests', but not 'core' - tick(); - builder.buildInvalidatedProject(); - assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt"); - assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt"); + verifyInvalidation(/*expectedToWriteTests*/ true); + + function verifyInvalidation(expectedToWriteTests: boolean) { + // Rebuild this project + tick(); + builder.invalidateProject("/src/logic"); + builder.buildInvalidatedProject(); + // The file should be updated + assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt"); + assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); + assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt"); + assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); + writtenFiles.clear(); + + // Build downstream projects should update 'tests', but not 'core' + tick(); + builder.buildInvalidatedProject(); + if (expectedToWriteTests) { + assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt"); + } + else { + assert.equal(writtenFiles.size, 0, "Should not write any new files"); + } + assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp"); + assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt"); + } }); }); diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 72da8dbf85edb..bdc8fe1be86b9 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -2,18 +2,37 @@ namespace ts.tscWatch { import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation; import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath; import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile; + type TsBuildWatchSystem = WatchedSystem & { writtenFiles: Map; }; + + function createTsBuildWatchSystem(fileOrFolderList: ReadonlyArray, params?: TestFSWithWatch.TestServerHostCreationParameters) { + const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem; + const originalWriteFile = host.writeFile; + host.writtenFiles = createMap(); + host.writeFile = (fileName, content) => { + originalWriteFile.call(host, fileName, content); + const path = host.toFullPath(fileName); + host.writtenFiles.set(path, true); + }; + return host; + } + export function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { const host = createSolutionBuilderWithWatchHost(system); return ts.createSolutionBuilder(host, rootNames, defaultOptions || { watch: true }); } - function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { + function createSolutionBuilderWithWatch(host: TsBuildWatchSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { const solutionBuilder = createSolutionBuilder(host, rootNames, defaultOptions); solutionBuilder.buildAllProjects(); solutionBuilder.startWatching(); return solutionBuilder; } + type OutputFileStamp = [string, Date | undefined, boolean]; + function transformOutputToOutputFileStamp(f: string, host: TsBuildWatchSystem): OutputFileStamp { + return [f, host.getModifiedTime(f), host.writtenFiles.has(host.toFullPath(f))] as OutputFileStamp; + } + describe("unittests:: tsbuild-watch program updates", () => { const project = "sample1"; const enum SubProject { @@ -61,12 +80,11 @@ namespace ts.tscWatch { return [`${file}.js`, `${file}.d.ts`]; } - type OutputFileStamp = [string, Date | undefined]; - function getOutputStamps(host: WatchedSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] { - return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => [f, host.getModifiedTime(f)] as OutputFileStamp); + function getOutputStamps(host: TsBuildWatchSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] { + return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => transformOutputToOutputFileStamp(f, host)); } - function getOutputFileStamps(host: WatchedSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] { + function getOutputFileStamps(host: TsBuildWatchSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] { const result = [ ...getOutputStamps(host, SubProject.core, "anotherModule"), ...getOutputStamps(host, SubProject.core, "index"), @@ -76,18 +94,21 @@ namespace ts.tscWatch { if (additionalFiles) { additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension))); } + host.writtenFiles.clear(); return result; } - function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: string[]) { + function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: ReadonlyArray, modifiedTimeStampFiles: ReadonlyArray) { for (let i = 0; i < oldTimeStamps.length; i++) { const actual = actualStamps[i]; const old = oldTimeStamps[i]; - if (contains(changedFiles, actual[0])) { - assert.isTrue((actual[1] || 0) > (old[1] || 0), `${actual[0]} expected to written`); + const expectedIsChanged = contains(changedFiles, actual[0]); + assert.equal(actual[2], contains(changedFiles, actual[0]), `Expected ${actual[0]} to be written.`); + if (expectedIsChanged || contains(modifiedTimeStampFiles, actual[0])) { + assert.isTrue((actual[1] || 0) > (old[1] || 0), `${actual[0]} file expected to have newer modified time because it is expected to ${expectedIsChanged ? "be changed" : "have modified time stamp"}`); } else { - assert.equal(actual[1], old[1], `${actual[0]} expected to not change`); + assert.equal(actual[1], old[1], `${actual[0]} expected to not change or have timestamp modified.`); } } } @@ -101,7 +122,7 @@ namespace ts.tscWatch { const testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)]; function createSolutionInWatchMode(allFiles: ReadonlyArray, defaultOptions?: BuildOptions, disableConsoleClears?: boolean) { - const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation }); + const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation }); createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], defaultOptions); verifyWatches(host); checkOutputErrorsInitial(host, emptyArray, disableConsoleClears); @@ -112,7 +133,7 @@ namespace ts.tscWatch { return host; } - function verifyWatches(host: WatchedSystem) { + function verifyWatches(host: TsBuildWatchSystem) { checkWatchedFiles(host, testProjectExpectedWatchedFiles); checkWatchedDirectories(host, emptyArray, /*recursive*/ false); checkWatchedDirectories(host, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true); @@ -134,30 +155,50 @@ namespace ts.tscWatch { const host = createSolutionInWatchMode(allFiles); return { host, verifyChangeWithFile, verifyChangeAfterTimeout, verifyWatches }; - function verifyChangeWithFile(fileName: string, content: string) { + function verifyChangeWithFile(fileName: string, content: string, local?: boolean) { const outputFileStamps = getOutputFileStamps(host, additionalFiles); host.writeFile(fileName, content); - verifyChangeAfterTimeout(outputFileStamps); + verifyChangeAfterTimeout(outputFileStamps, local); } - function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[]) { + function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[], local?: boolean) { host.checkTimeoutQueueLengthAndRun(1); // Builds core const changedCore = getOutputFileStamps(host, additionalFiles); - verifyChangedFiles(changedCore, outputFileStamps, [ - ...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really - ...getOutputFileNames(SubProject.core, "index"), - ...(additionalFiles ? getOutputFileNames(SubProject.core, newFileWithoutExtension) : emptyArray) - ]); - host.checkTimeoutQueueLengthAndRun(1); // Builds logic + verifyChangedFiles( + changedCore, + outputFileStamps, + additionalFiles ? + getOutputFileNames(SubProject.core, newFileWithoutExtension) : + getOutputFileNames(SubProject.core, "index"), // Written files are new file or core index file thats changed + [ + ...getOutputFileNames(SubProject.core, "anotherModule"), + ...(additionalFiles ? getOutputFileNames(SubProject.core, "index") : emptyArray) + ] + ); + host.checkTimeoutQueueLengthAndRun(1); // Builds logic or updates timestamps const changedLogic = getOutputFileStamps(host, additionalFiles); - verifyChangedFiles(changedLogic, changedCore, [ - ...getOutputFileNames(SubProject.logic, "index") // Again these need not be written - ]); + verifyChangedFiles( + changedLogic, + changedCore, + additionalFiles || local ? + emptyArray : + getOutputFileNames(SubProject.logic, "index"), + additionalFiles || local ? + getOutputFileNames(SubProject.logic, "index") : + emptyArray + ); host.checkTimeoutQueueLengthAndRun(1); // Builds tests const changedTests = getOutputFileStamps(host, additionalFiles); - verifyChangedFiles(changedTests, changedLogic, [ - ...getOutputFileNames(SubProject.tests, "index") // Again these need not be written - ]); + verifyChangedFiles( + changedTests, + changedLogic, + additionalFiles || local ? + emptyArray : + getOutputFileNames(SubProject.tests, "index"), + additionalFiles || local ? + getOutputFileNames(SubProject.tests, "index") : + emptyArray + ); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, emptyArray); verifyWatches(); @@ -193,19 +234,9 @@ export class someClass2 { }`); }); it("non local change does not start build of referencing projects", () => { - const host = createSolutionInWatchMode(allFiles); - const outputFileStamps = getOutputFileStamps(host); - host.writeFile(core[1].path, `${core[1].content} -function foo() { }`); - host.checkTimeoutQueueLengthAndRun(1); // Builds core - const changedCore = getOutputFileStamps(host); - verifyChangedFiles(changedCore, outputFileStamps, [ - ...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really - ...getOutputFileNames(SubProject.core, "index"), - ]); - host.checkTimeoutQueueLength(0); - checkOutputErrorsIncremental(host, emptyArray); - verifyWatches(host); + const { verifyChangeWithFile } = createSolutionInWatchModeToVerifyChanges(); + verifyChangeWithFile(core[1].path, `${core[1].content} +function foo() { }`, /*local*/ true); }); it("builds when new file is added, and its subsequent updates", () => { @@ -242,7 +273,7 @@ export class someClass2 { }`); it("watches config files that are not present", () => { const allFiles = [libFile, ...core, logic[1], ...tests]; - const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation }); + const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation }); createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]); checkWatchedFiles(host, [core[0], core[1], core[2]!, logic[0], ...tests].map(f => f.path.toLowerCase())); // tslint:disable-line no-unnecessary-type-assertion (TODO: type assertion should be necessary) checkWatchedDirectories(host, emptyArray, /*recursive*/ false); @@ -268,14 +299,10 @@ export class someClass2 { }`); host.writeFile(logic[0].path, logic[0].content); host.checkTimeoutQueueLengthAndRun(1); // Builds logic const changedLogic = getOutputFileStamps(host); - verifyChangedFiles(changedLogic, initial, [ - ...getOutputFileNames(SubProject.logic, "index") - ]); + verifyChangedFiles(changedLogic, initial, getOutputFileNames(SubProject.logic, "index"), emptyArray); host.checkTimeoutQueueLengthAndRun(1); // Builds tests const changedTests = getOutputFileStamps(host); - verifyChangedFiles(changedTests, changedLogic, [ - ...getOutputFileNames(SubProject.tests, "index") - ]); + verifyChangedFiles(changedTests, changedLogic, getOutputFileNames(SubProject.tests, "index"), emptyArray); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, emptyArray); verifyWatches(host); @@ -305,7 +332,7 @@ export class someClass2 { }`); }; const projectFiles = [coreTsConfig, coreIndex, logicTsConfig, logicIndex]; - const host = createWatchedSystem([libFile, ...projectFiles], { currentDirectory: projectsLocation }); + const host = createTsBuildWatchSystem([libFile, ...projectFiles], { currentDirectory: projectsLocation }); createSolutionBuilderWithWatch(host, [`${project}/${SubProject.logic}`]); verifyWatches(); checkOutputErrorsInitial(host, emptyArray); @@ -318,6 +345,7 @@ export class someClass2 { }`); verifyChangeInCore(`${coreIndex.content} function myFunc() { return 10; }`); + // TODO:: local change does not build logic.js because builder doesnt find any changes in input files to generate output // Make local change to function bar verifyChangeInCore(`${coreIndex.content} function myFunc() { return 100; }`); @@ -328,14 +356,20 @@ function myFunc() { return 100; }`); host.checkTimeoutQueueLengthAndRun(1); // Builds core const changedCore = getOutputFileStamps(); - verifyChangedFiles(changedCore, outputFileStamps, [ - ...getOutputFileNames(SubProject.core, "index") - ]); + verifyChangedFiles( + changedCore, + outputFileStamps, + getOutputFileNames(SubProject.core, "index"), + emptyArray + ); host.checkTimeoutQueueLengthAndRun(1); // Builds logic const changedLogic = getOutputFileStamps(); - verifyChangedFiles(changedLogic, changedCore, [ - ...getOutputFileNames(SubProject.logic, "index") - ]); + verifyChangedFiles( + changedLogic, + changedCore, + getOutputFileNames(SubProject.logic, "index"), + emptyArray + ); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, emptyArray); verifyWatches(); @@ -346,6 +380,7 @@ function myFunc() { return 100; }`); ...getOutputStamps(host, SubProject.core, "index"), ...getOutputStamps(host, SubProject.logic, "index"), ]; + host.writtenFiles.clear(); return result; } @@ -389,7 +424,7 @@ createSomeObject().message;` }; const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig]; - const host = createWatchedSystem(files, { currentDirectory: `${projectsLocation}/${project}` }); + const host = createTsBuildWatchSystem(files, { currentDirectory: `${projectsLocation}/${project}` }); createSolutionBuilderWithWatch(host, ["App"]); checkOutputErrorsInitial(host, emptyArray); @@ -418,7 +453,7 @@ let y: string = 10;`); host.checkTimeoutQueueLengthAndRun(1); // Builds logic const changedLogic = getOutputFileStamps(host); - verifyChangedFiles(changedLogic, outputFileStamps, emptyArray); + verifyChangedFiles(changedLogic, outputFileStamps, emptyArray, emptyArray); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, [ `sample1/logic/index.ts(8,5): error TS2322: Type '10' is not assignable to type 'string'.\n` @@ -429,7 +464,7 @@ let x: string = 10;`); host.checkTimeoutQueueLengthAndRun(1); // Builds core const changedCore = getOutputFileStamps(host); - verifyChangedFiles(changedCore, changedLogic, emptyArray); + verifyChangedFiles(changedCore, changedLogic, emptyArray, emptyArray); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, [ `sample1/core/index.ts(5,5): error TS2322: Type '10' is not assignable to type 'string'.\n`, @@ -448,7 +483,7 @@ let x: string = 10;`); describe("tsc-watch and tsserver works with project references", () => { describe("invoking when references are already built", () => { - function verifyWatchesOfProject(host: WatchedSystem, expectedWatchedFiles: ReadonlyArray, expectedWatchedDirectoriesRecursive: ReadonlyArray, expectedWatchedDirectories?: ReadonlyArray) { + function verifyWatchesOfProject(host: TsBuildWatchSystem, expectedWatchedFiles: ReadonlyArray, expectedWatchedDirectoriesRecursive: ReadonlyArray, expectedWatchedDirectories?: ReadonlyArray) { checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories || emptyArray, 1, /*recursive*/ false); checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true); @@ -457,9 +492,9 @@ let x: string = 10;`); function createSolutionOfProject(allFiles: ReadonlyArray, currentDirectory: string, solutionBuilderconfig: string, - getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray) { + getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray) { // Build the composite project - const host = createWatchedSystem(allFiles, { currentDirectory }); + const host = createTsBuildWatchSystem(allFiles, { currentDirectory }); const solutionBuilder = createSolutionBuilder(host, [solutionBuilderconfig], {}); solutionBuilder.buildAllProjects(); const outputFileStamps = getOutputFileStamps(host); @@ -474,7 +509,7 @@ let x: string = 10;`); currentDirectory: string, solutionBuilderconfig: string, watchConfig: string, - getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray) { + getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray) { // Build the composite project const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps); @@ -489,7 +524,7 @@ let x: string = 10;`); currentDirectory: string, solutionBuilderconfig: string, openFileName: string, - getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray) { + getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray) { // Build the composite project const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps); @@ -527,12 +562,12 @@ let x: string = 10;`); return createSolutionAndServiceOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[1].path, getOutputFileStamps); } - function verifyWatches(host: WatchedSystem, withTsserver?: boolean) { + function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) { verifyWatchesOfProject(host, withTsserver ? expectedWatchedFiles.filter(f => f !== tests[1].path.toLowerCase()) : expectedWatchedFiles, expectedWatchedDirectoriesRecursive); } function verifyScenario( - edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void, + edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void, expectedFilesAfterEdit: ReadonlyArray ) { it("with tsc-watch", () => { @@ -635,7 +670,7 @@ export function gfoo() { } function verifyWatchState( - host: WatchedSystem, + host: TsBuildWatchSystem, watch: Watch, expectedProgramFiles: ReadonlyArray, expectedWatchedFiles: ReadonlyArray, @@ -722,20 +757,20 @@ export function gfoo() { return createSolutionAndServiceOfProject(allFiles, getProjectPath(project), configToBuild, cTs.path, getOutputFileStamps); } - function getOutputFileStamps(host: WatchedSystem) { - return expectedFiles.map(file => [file, host.getModifiedTime(file)] as OutputFileStamp); + function getOutputFileStamps(host: TsBuildWatchSystem) { + return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host)); } - function verifyProgram(host: WatchedSystem, watch: Watch) { + function verifyProgram(host: TsBuildWatchSystem, watch: Watch) { verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies, expectedWatchedDirectories); } - function verifyProject(host: WatchedSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray) { + function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray) { verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos); } function verifyServerState( - host: WatchedSystem, + host: TsBuildWatchSystem, service: projectSystem.TestProjectService, expectedProgramFiles: ReadonlyArray, expectedWatchedFiles: ReadonlyArray, @@ -755,13 +790,13 @@ export function gfoo() { } function verifyScenario( - edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void, + edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void, expectedEditErrors: ReadonlyArray, expectedProgramFiles: ReadonlyArray, expectedWatchedFiles: ReadonlyArray, expectedWatchedDirectoriesRecursive: ReadonlyArray, dependencies: ReadonlyArray<[string, ReadonlyArray]>, - revert?: (host: WatchedSystem) => void, + revert?: (host: TsBuildWatchSystem) => void, orphanInfosAfterEdit?: ReadonlyArray, orphanInfosAfterRevert?: ReadonlyArray) { it("with tsc-watch", () => { @@ -980,8 +1015,8 @@ export function gfoo() { [refs.path, [refs.path]], [cTsFile.path, [cTsFile.path, refs.path, bDts]] ]; - function getOutputFileStamps(host: WatchedSystem) { - return expectedFiles.map(file => [file, host.getModifiedTime(file)] as OutputFileStamp); + function getOutputFileStamps(host: TsBuildWatchSystem) { + return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host)); } const { host, watch } = createSolutionAndWatchModeOfProject(allFiles, getProjectPath(project), "tsconfig.c.json", "tsconfig.c.json", getOutputFileStamps); verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies);