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
This commit is contained in:
Sheetal Nandi 2018-12-20 11:33:11 -08:00
parent f1949bbae8
commit 7b290fdbd4
5 changed files with 230 additions and 122 deletions

View file

@ -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",

View file

@ -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<T extends BuilderProgram> extends SolutionBuilderHostBase<T> {
@ -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<true>(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,25 +1209,36 @@ namespace ts {
if (options.dry) {
return reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj.options.configFilePath!);
}
if (options.verbose) {
reportStatus(Diagnostics.Updating_output_timestamps_of_project_0, proj.options.configFilePath!);
}
const now = new Date();
const outputs = getAllProjectOutputs(proj);
let priorNewestUpdateTime = minimumDate;
for (const file of outputs) {
if (isDeclarationFile(file)) {
priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime);
}
host.setModifiedTime(file, now);
}
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);
}
function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap<true>) {
const outputs = getAllProjectOutputs(proj);
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);
if (proj.options.listEmittedFiles) {
writeFileName(`TSFILE: ${file}`);
}
}
}
return priorNewestUpdateTime;
}
function getFilesToClean(): string[] {
// Get the same graph for cleaning we'd use for building
const graph = getGlobalDependencyGraph();
@ -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<T>(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) {
switch (status.type) {
case UpToDateStatusType.OutOfDateWithSelf:

View file

@ -377,6 +377,9 @@ namespace fakes {
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
createProgram = ts.createAbstractBuilder;
now() {
return new Date(this.sys.vfs.time());
}
diagnostics: ts.Diagnostic[] = [];

View file

@ -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<true>();
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");
verifyInvalidation(/*expectedToWriteTests*/ true);
// 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");
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");
}
});
});

View file

@ -2,18 +2,37 @@ namespace ts.tscWatch {
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
type TsBuildWatchSystem = WatchedSystem & { writtenFiles: Map<true>; };
function createTsBuildWatchSystem(fileOrFolderList: ReadonlyArray<TestFSWithWatch.FileOrFolderOrSymLink>, params?: TestFSWithWatch.TestServerHostCreationParameters) {
const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem;
const originalWriteFile = host.writeFile;
host.writtenFiles = createMap<true>();
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<string>, defaultOptions?: BuildOptions) {
const host = createSolutionBuilderWithWatchHost(system);
return ts.createSolutionBuilder(host, rootNames, defaultOptions || { watch: true });
}
function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
function createSolutionBuilderWithWatch(host: TsBuildWatchSystem, rootNames: ReadonlyArray<string>, 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<string>, modifiedTimeStampFiles: ReadonlyArray<string>) {
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<File>, 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<string>, expectedWatchedDirectoriesRecursive: ReadonlyArray<string>, expectedWatchedDirectories?: ReadonlyArray<string>) {
function verifyWatchesOfProject(host: TsBuildWatchSystem, expectedWatchedFiles: ReadonlyArray<string>, expectedWatchedDirectoriesRecursive: ReadonlyArray<string>, expectedWatchedDirectories?: ReadonlyArray<string>) {
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<File>,
currentDirectory: string,
solutionBuilderconfig: string,
getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
// 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<OutputFileStamp>) {
getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
// 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<OutputFileStamp>) {
getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
// 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<string>
) {
it("with tsc-watch", () => {
@ -635,7 +670,7 @@ export function gfoo() {
}
function verifyWatchState(
host: WatchedSystem,
host: TsBuildWatchSystem,
watch: Watch,
expectedProgramFiles: ReadonlyArray<string>,
expectedWatchedFiles: ReadonlyArray<string>,
@ -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<string>) {
function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray<string>) {
verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos);
}
function verifyServerState(
host: WatchedSystem,
host: TsBuildWatchSystem,
service: projectSystem.TestProjectService,
expectedProgramFiles: ReadonlyArray<string>,
expectedWatchedFiles: ReadonlyArray<string>,
@ -755,13 +790,13 @@ export function gfoo() {
}
function verifyScenario(
edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void,
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void,
expectedEditErrors: ReadonlyArray<string>,
expectedProgramFiles: ReadonlyArray<string>,
expectedWatchedFiles: ReadonlyArray<string>,
expectedWatchedDirectoriesRecursive: ReadonlyArray<string>,
dependencies: ReadonlyArray<[string, ReadonlyArray<string>]>,
revert?: (host: WatchedSystem) => void,
revert?: (host: TsBuildWatchSystem) => void,
orphanInfosAfterEdit?: ReadonlyArray<string>,
orphanInfosAfterRevert?: ReadonlyArray<string>) {
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);