Some refactoring for baselining for easy update (#48516)

* Some refactoring for baselining for easy update

* Set modified time in baseline

* Some renames per feedback
This commit is contained in:
Sheetal Nandi
2022-04-06 12:46:34 -07:00
committed by GitHub
parent 16b6f0f533
commit 3fd8a6e443
120 changed files with 12957 additions and 1215 deletions

View File

@@ -649,7 +649,7 @@ namespace ts {
return false;
}
export type ExecuteCommandLineCallbacks = (program: Program | EmitAndSemanticDiagnosticsBuilderProgram | ParsedCommandLine) => void;
export type ExecuteCommandLineCallbacks = (program: Program | BuilderProgram | ParsedCommandLine) => void;
export function executeCommandLine(
system: System,
cb: ExecuteCommandLineCallbacks,

View File

@@ -511,18 +511,19 @@ ${indentText}${text}`;
buildInfo.version = ts.version;
return ts.getBuildInfoText(buildInfo);
};
return patchHostForBuildInfoWrite(sys, version);
}
export function patchHostForBuildInfoWrite<T extends ts.System>(sys: T, version: string) {
const originalWrite = sys.write;
sys.write = msg => originalWrite.call(sys, msg.replace(ts.version, version));
if (sys.writeFile) {
const originalWriteFile = sys.writeFile;
sys.writeFile = (fileName: string, content: string, writeByteOrderMark: boolean) => {
if (!ts.isBuildInfoFile(fileName)) return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
const buildInfo = ts.getBuildInfo(content);
buildInfo.version = version;
originalWriteFile.call(sys, fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
};
}
const originalWriteFile = sys.writeFile;
sys.writeFile = (fileName: string, content: string, writeByteOrderMark: boolean) => {
if (!ts.isBuildInfoFile(fileName)) return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
const buildInfo = ts.getBuildInfo(content);
buildInfo.version = version;
originalWriteFile.call(sys, fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
};
return sys;
}

View File

@@ -809,7 +809,11 @@ namespace vfs {
const baseBuffer = base._getBuffer(baseNode);
// no difference if both buffers are the same reference
if (changedBuffer === baseBuffer) return false;
if (changedBuffer === baseBuffer) {
if (!options.includeChangedFileWithSameContent || changedNode.mtimeMs === baseNode.mtimeMs) return false;
container[basename] = new SameFileWithModifiedTime(changedBuffer);
return true;
}
// no difference if both buffers are identical
if (Buffer.compare(changedBuffer, baseBuffer) === 0) {
@@ -1391,6 +1395,12 @@ namespace vfs {
}
}
export class SameFileWithModifiedTime extends File {
constructor(data: Buffer | string, metaAndEncoding?: { encoding?: string, meta?: Record<string, any> }) {
super(data, metaAndEncoding);
}
}
/** Extended options for a hard link in a `FileSet` */
export class Link {
public readonly path: string;
@@ -1579,6 +1589,9 @@ namespace vfs {
else if (entry instanceof Directory) {
text += formatPatchWorker(file, entry.files);
}
else if (entry instanceof SameFileWithModifiedTime) {
text += `//// [${file}] file changed its modified time\r\n`;
}
else if (entry instanceof SameFileContentFile) {
text += `//// [${file}] file written with same contents\r\n`;
}

View File

@@ -1,30 +1,6 @@
namespace ts {
describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
let outFileFs: vfs.FileSystem;
const enum Project { lib, app }
function relName(path: string) {
return path.slice(1);
}
type Sources = [string, readonly string[]];
const enum Source { config, ts }
const sources: [Sources, Sources] = [
[
"/src/lib/tsconfig.json",
[
"/src/lib/file0.ts",
"/src/lib/file1.ts",
"/src/lib/file2.ts",
"/src/lib/global.ts",
]
],
[
"/src/app/tsconfig.json",
[
"/src/app/file3.ts",
"/src/app/file4.ts"
]
]
];
before(() => {
outFileFs = loadProjectFromDisk("tests/projects/amdModulesWithOut");
});
@@ -53,7 +29,7 @@ namespace ts {
incrementalScenarios: [
{
buildKind: BuildKind.IncrementalDtsUnchanged,
modifyFs: fs => appendText(fs, relName(sources[Project.lib][Source.ts][1]), "console.log(x);")
modifyFs: fs => appendText(fs, "/src/lib/file1.ts", "console.log(x);")
},
...(modifyAgainFs ? [{
buildKind: BuildKind.IncrementalHeadersChange,
@@ -73,15 +49,15 @@ namespace ts {
verifyOutFileScenario({
subScenario: "multiple prologues in all projects",
modifyFs: fs => {
enableStrict(fs, sources[Project.lib][Source.config]);
addTestPrologue(fs, sources[Project.lib][Source.ts][0], `"myPrologue"`);
addTestPrologue(fs, sources[Project.lib][Source.ts][2], `"myPrologueFile"`);
addTestPrologue(fs, sources[Project.lib][Source.ts][3], `"myPrologue3"`);
enableStrict(fs, sources[Project.app][Source.config]);
addTestPrologue(fs, sources[Project.app][Source.ts][0], `"myPrologue"`);
addTestPrologue(fs, sources[Project.app][Source.ts][1], `"myPrologue2";`);
enableStrict(fs, "/src/lib/tsconfig.json");
addTestPrologue(fs, "/src/lib/file0.ts", `"myPrologue"`);
addTestPrologue(fs, "/src/lib/file2.ts", `"myPrologueFile"`);
addTestPrologue(fs, "/src/lib/global.ts", `"myPrologue3"`);
enableStrict(fs, "/src/app/tsconfig.json");
addTestPrologue(fs, "/src/app/file3.ts", `"myPrologue"`);
addTestPrologue(fs, "/src/app/file4.ts", `"myPrologue2";`);
},
modifyAgainFs: fs => addTestPrologue(fs, relName(sources[Project.lib][Source.ts][1]), `"myPrologue5"`)
modifyAgainFs: fs => addTestPrologue(fs, "/src/lib/file1.ts", `"myPrologue5"`)
});
});
@@ -127,10 +103,10 @@ namespace ts {
describe("stripInternal", () => {
function stripInternalScenario(fs: vfs.FileSystem) {
const internal = "/*@internal*/";
replaceText(fs, sources[Project.app][Source.config], `"composite": true,`, `"composite": true,
replaceText(fs, "/src/app/tsconfig.json", `"composite": true,`, `"composite": true,
"stripInternal": true,`);
replaceText(fs, sources[Project.lib][Source.ts][0], "const", `${internal} const`);
appendText(fs, sources[Project.lib][Source.ts][1], `
replaceText(fs, "/src/lib/file0.ts", "const", `${internal} const`);
appendText(fs, "/src/lib/file1.ts", `
export class normalC {
${internal} constructor() { }
${internal} prop: string;
@@ -162,16 +138,16 @@ ${internal} export enum internalEnum { a, b, c }`);
verifyOutFileScenario({
subScenario: "stripInternal",
modifyFs: stripInternalScenario,
modifyAgainFs: fs => replaceText(fs, sources[Project.lib][Source.ts][1], `export const`, `/*@internal*/ export const`),
modifyAgainFs: fs => replaceText(fs, "/src/lib/file1.ts", `export const`, `/*@internal*/ export const`),
});
});
describe("when the module resolution finds original source file", () => {
function modifyFs(fs: vfs.FileSystem) {
// Make lib to output to parent dir
replaceText(fs, sources[Project.lib][Source.config], `"outFile": "module.js"`, `"outFile": "../module.js", "rootDir": "../"`);
replaceText(fs, "/src/lib/tsconfig.json", `"outFile": "module.js"`, `"outFile": "../module.js", "rootDir": "../"`);
// Change reference to file1 module to resolve to lib/file1
replaceText(fs, sources[Project.app][Source.ts][0], "file1", "lib/file1");
replaceText(fs, "/src/app/file3.ts", "file1", "lib/file1");
}
verifyTsc({

View File

@@ -332,23 +332,23 @@ interface Symbol {
}
interface VerifyIncrementalCorrectness {
scenario: TscCompile["scenario"];
commandLineArgs: TscCompile["commandLineArgs"];
modifyFs: TscCompile["modifyFs"];
scenario: TestTscCompile["scenario"];
commandLineArgs: TestTscCompile["commandLineArgs"];
modifyFs: TestTscCompile["modifyFs"];
incrementalModifyFs: TscIncremental["modifyFs"];
tick: () => void;
baseFs: vfs.FileSystem;
newSys: TscCompileSystem;
cleanBuildDiscrepancies: TscIncremental["cleanBuildDiscrepancies"];
}
function verifyIncrementalCorrectness(input: () => VerifyIncrementalCorrectness, index: number, subScenario: TscCompile["subScenario"]) {
function verifyIncrementalCorrectness(input: () => VerifyIncrementalCorrectness, index: number, subScenario: TestTscCompile["subScenario"]) {
it(`Verify emit output file text is same when built clean for incremental scenario at:: ${index} ${subScenario}`, () => {
const {
scenario, commandLineArgs, cleanBuildDiscrepancies,
modifyFs, incrementalModifyFs,
tick, baseFs, newSys
} = input();
const sys = tscCompile({
const sys = testTscCompile({
scenario,
subScenario,
fs: () => baseFs.makeReadonly(),
@@ -521,7 +521,7 @@ interface Symbol {
}
}
export interface VerifyTsBuildInputWorker extends TscCompile {
export interface VerifyTsBuildInputWorker extends TestTscCompile {
incrementalScenarios: TscIncremental[];
}
function verifyTscIncrementalEditsWorker({
@@ -535,7 +535,7 @@ interface Symbol {
let baseFs: vfs.FileSystem;
before(() => {
({ fs: baseFs, tick } = getFsWithTime(fs()));
sys = tscCompile({
sys = testTscCompile({
scenario,
subScenario,
fs: () => baseFs.makeReadonly(),
@@ -571,7 +571,7 @@ interface Symbol {
before(() => {
Debug.assert(buildKind !== BuildKind.Initial, "Incremental edit cannot be initial compilation");
tick();
newSys = tscCompile({
newSys = testTscCompile({
scenario,
subScenario: incrementalSubScenario || subScenario,
buildKind,
@@ -629,7 +629,7 @@ interface Symbol {
let incrementalSys: TscCompileSystem[];
before(() => {
({ fs: baseFs, tick } = getFsWithTime(fs()));
sys = tscCompile({
sys = testTscCompile({
scenario,
subScenario,
fs: () => baseFs.makeReadonly(),
@@ -648,7 +648,7 @@ interface Symbol {
) => {
Debug.assert(buildKind !== BuildKind.Initial, "Incremental edit cannot be initial compilation");
tick();
(incrementalSys || (incrementalSys = [])).push(tscCompile({
(incrementalSys || (incrementalSys = [])).push(testTscCompile({
scenario,
subScenario: incrementalSubScenario || subScenario,
buildKind,

View File

@@ -1,78 +1,11 @@
namespace ts {
describe("unittests:: tsbuild:: outFile::", () => {
let outFileFs: vfs.FileSystem;
const enum Ext { js, jsmap, dts, dtsmap, buildinfo }
const enum Project { first, second, third }
type OutputFile = [string, string, string, string, string];
function relName(path: string) {
return path.slice(1);
}
const outputFiles: [OutputFile, OutputFile, OutputFile] = [
[
"/src/first/bin/first-output.js",
"/src/first/bin/first-output.js.map",
"/src/first/bin/first-output.d.ts",
"/src/first/bin/first-output.d.ts.map",
"/src/first/bin/first-output.tsbuildinfo"
],
[
"/src/2/second-output.js",
"/src/2/second-output.js.map",
"/src/2/second-output.d.ts",
"/src/2/second-output.d.ts.map",
"/src/2/second-output.tsbuildinfo"
],
[
"/src/third/thirdjs/output/third-output.js",
"/src/third/thirdjs/output/third-output.js.map",
"/src/third/thirdjs/output/third-output.d.ts",
"/src/third/thirdjs/output/third-output.d.ts.map",
"/src/third/thirdjs/output/third-output.tsbuildinfo"
]
];
const relOutputFiles = outputFiles.map(v => v.map(relName)) as [OutputFile, OutputFile, OutputFile];
type Sources = [string, readonly string[]];
const enum Source { config, ts }
const enum Part { one, two, three }
const sources: [Sources, Sources, Sources] = [
[
"/src/first/tsconfig.json",
[
"/src/first/first_PART1.ts",
"/src/first/first_part2.ts",
"/src/first/first_part3.ts"
]
],
[
"/src/second/tsconfig.json",
[
"/src/second/second_part1.ts",
"/src/second/second_part2.ts"
]
],
[
"/src/third/tsconfig.json",
[
"/src/third/third_part1.ts"
]
]
];
const relSources = sources.map(([config, sources]) => [relName(config), sources.map(relName)]) as any as [Sources, Sources, Sources];
let initialExpectedDiagnostics: readonly fakes.ExpectedDiagnostic[] = [
getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[Project.first][Source.config], relOutputFiles[Project.first][Ext.js]],
[Diagnostics.Building_project_0, sources[Project.first][Source.config]],
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[Project.second][Source.config], relOutputFiles[Project.second][Ext.js]],
[Diagnostics.Building_project_0, sources[Project.second][Source.config]],
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[Project.third][Source.config], relOutputFiles[Project.third][Ext.js]],
[Diagnostics.Building_project_0, sources[Project.third][Source.config]]
];
before(() => {
outFileFs = loadProjectFromDisk("tests/projects/outfile-concat");
});
after(() => {
outFileFs = undefined!;
initialExpectedDiagnostics = undefined!;
});
function createSolutionBuilder(host: fakes.SolutionBuilderHost, baseOptions?: BuildOptions) {
@@ -102,13 +35,13 @@ namespace ts {
if (!ignoreDtsChanged) {
incrementalScenarios.push({
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: fs => replaceText(fs, relSources[Project.first][Source.ts][Part.one], "Hello", "Hola"),
modifyFs: fs => replaceText(fs, "/src/first/first_PART1.ts", "Hello", "Hola"),
});
}
if (!ignoreDtsUnchanged) {
incrementalScenarios.push({
buildKind: BuildKind.IncrementalDtsUnchanged,
modifyFs: fs => appendText(fs, relSources[Project.first][Source.ts][Part.one], "console.log(s);"),
modifyFs: fs => appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"),
});
}
if (modifyAgainFs) {
@@ -146,7 +79,7 @@ namespace ts {
// Verify baseline with build info + dts unChanged
verifyOutFileScenario({
subScenario: "when final project is not composite but uses project references",
modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""),
modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -154,7 +87,7 @@ namespace ts {
// Verify baseline with build info
verifyOutFileScenario({
subScenario: "when final project is not composite but incremental",
modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, `"incremental": true,`),
modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"incremental": true,`),
ignoreDtsChanged: true,
ignoreDtsUnchanged: true,
baselineOnly: true
@@ -163,7 +96,7 @@ namespace ts {
// Verify baseline with build info
verifyOutFileScenario({
subScenario: "when final project specifies tsBuildInfoFile",
modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, `"composite": true,
modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"composite": true,
"tsBuildInfoFile": "./thirdjs/output/third.tsbuildinfo",`),
ignoreDtsChanged: true,
ignoreDtsUnchanged: true,
@@ -192,7 +125,7 @@ namespace ts {
subScenario: "verify buildInfo absence results in new build",
fs: getOutFileFsAfterBuild,
commandLineArgs: ["--b", "/src/third", "--verbose"],
modifyFs: fs => fs.unlinkSync(outputFiles[Project.first][Ext.buildinfo]),
modifyFs: fs => fs.unlinkSync("/src/first/bin/first-output.tsbuildinfo"),
});
verifyTsc({
@@ -200,100 +133,69 @@ namespace ts {
subScenario: "tsbuildinfo is not generated when incremental is set to false",
fs: () => outFileFs,
commandLineArgs: ["--b", "/src/third", "--verbose"],
modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""),
modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""),
});
it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => {
const { fs, tick } = getFsWithTime(outFileFs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host);
builder.build();
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
host.clearDiagnostics();
tick();
builder = createSolutionBuilder(host);
changeCompilerVersion(host);
tick();
builder.build();
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]),
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[Project.first][Source.config], fakes.version, version],
[Diagnostics.Building_project_0, sources[Project.first][Source.config]],
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[Project.second][Source.config], fakes.version, version],
[Diagnostics.Building_project_0, sources[Project.second][Source.config]],
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[Project.third][Source.config], fakes.version, version],
[Diagnostics.Building_project_0, sources[Project.third][Source.config]],
);
verifyTscCompileLike(testTscCompileLike, {
scenario: "outFile",
subScenario: "rebuilds completely when version in tsbuildinfo doesnt match ts version",
fs: getOutFileFsAfterBuild,
commandLineArgs: ["--b", "/src/third", "--verbose"],
compile: sys => {
// Buildinfo will have version which does not match with current ts version
fakes.patchHostForBuildInfoWrite(sys, "FakeTSCurrentVersion");
const buildHost = createSolutionBuilderHost(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/third"], { verbose: true });
sys.exit(builder.build());
}
});
it("rebuilds completely when command line incremental flag changes between non dts changes", () => {
const { fs, tick } = getFsWithTime(outFileFs);
verifyTscSerializedIncrementalEdits({
scenario: "outFile",
subScenario: "rebuilds completely when command line incremental flag changes between non dts changes",
fs: () => outFileFs,
// Make non composite third project
replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, "");
modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""),
// Build with command line incremental
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, { incremental: true });
builder.build();
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
host.clearDiagnostics();
tick();
// Make non incremental build with change in file that doesnt affect dts
appendText(fs, relSources[Project.first][Source.ts][Part.one], "console.log(s);");
builder = createSolutionBuilder(host, { verbose: true });
builder.build();
host.assertDiagnosticMessages(getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]),
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[Project.first][Source.config], relOutputFiles[Project.first][Ext.js], relSources[Project.first][Source.ts][Part.one]],
[Diagnostics.Building_project_0, sources[Project.first][Source.config]],
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[Project.second][Source.config], relSources[Project.second][Source.ts][Part.one], relOutputFiles[Project.second][Ext.js]],
[Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, relSources[Project.third][Source.config], "src/first"],
[Diagnostics.Building_project_0, sources[Project.third][Source.config]]
);
host.clearDiagnostics();
tick();
// Make incremental build with change in file that doesnt affect dts
appendText(fs, relSources[Project.first][Source.ts][Part.one], "console.log(s);");
builder = createSolutionBuilder(host, { verbose: true, incremental: true });
builder.build();
// Builds completely because tsbuildinfo is old.
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]),
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[Project.first][Source.config], relOutputFiles[Project.first][Ext.js], relSources[Project.first][Source.ts][Part.one]],
[Diagnostics.Building_project_0, sources[Project.first][Source.config]],
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[Project.second][Source.config], relSources[Project.second][Source.ts][Part.one], relOutputFiles[Project.second][Ext.js]],
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[Project.third][Source.config], relOutputFiles[Project.third][Ext.buildinfo], "src/first"],
[Diagnostics.Building_project_0, sources[Project.third][Source.config]]
);
host.clearDiagnostics();
commandLineArgs: ["--b", "/src/third", "--i", "--verbose"],
incrementalScenarios: [
{
subScenario: "Make non incremental build with change in file that doesnt affect dts",
buildKind: BuildKind.IncrementalDtsUnchanged,
modifyFs: fs => appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"),
commandLineArgs: ["--b", "/src/third", "--verbose"],
},
{
subScenario: "Make incremental build with change in file that doesnt affect dts",
buildKind: BuildKind.IncrementalDtsUnchanged,
modifyFs: fs => appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"),
commandLineArgs: ["--b", "/src/third", "--verbose", "--incremental"],
}
]
});
it("builds till project specified", () => {
const fs = outFileFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, { verbose: false });
const result = builder.build(sources[Project.second][Source.config]);
host.assertDiagnosticMessages(/*empty*/);
// First and Third is not built
verifyOutputsAbsent(fs, [...outputFiles[Project.first], ...outputFiles[Project.third]]);
// second is built
verifyOutputsPresent(fs, outputFiles[Project.second]);
assert.equal(result, ExitStatus.Success);
verifyTscCompileLike(testTscCompileLike, {
scenario: "outFile",
subScenario: "builds till project specified",
fs: () => outFileFs,
commandLineArgs: ["--build", "/src/second/tsconfig.json"],
compile: sys => {
const buildHost = createSolutionBuilderHost(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {});
sys.exit(builder.build("/src/second/tsconfig.json"));
}
});
it("cleans till project specified", () => {
const fs = outFileFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, { verbose: false });
builder.build();
const result = builder.clean(sources[Project.second][Source.config]);
host.assertDiagnosticMessages(/*empty*/);
// First and Third output for present
verifyOutputsPresent(fs, [...outputFiles[Project.first], ...outputFiles[Project.third]]);
// second is cleaned
verifyOutputsAbsent(fs, outputFiles[Project.second]);
assert.equal(result, ExitStatus.Success);
verifyTscCompileLike(testTscCompileLike, {
scenario: "outFile",
subScenario: "cleans till project specified",
fs: getOutFileFsAfterBuild,
commandLineArgs: ["--build", "--clean", "/src/second/tsconfig.json"],
compile: sys => {
const buildHost = createSolutionBuilderHost(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], { verbose: true });
sys.exit(builder.clean("/src/second/tsconfig.json"));
}
});
describe("Prepend output with .tsbuildinfo", () => {
@@ -303,17 +205,17 @@ namespace ts {
verifyOutFileScenario({
subScenario: "strict in all projects",
modifyFs: fs => {
enableStrict(fs, sources[Project.first][Source.config]);
enableStrict(fs, sources[Project.second][Source.config]);
enableStrict(fs, sources[Project.third][Source.config]);
enableStrict(fs, "/src/first/tsconfig.json");
enableStrict(fs, "/src/second/tsconfig.json");
enableStrict(fs, "/src/third/tsconfig.json");
},
modifyAgainFs: fs => addTestPrologue(fs, relSources[Project.first][Source.ts][Part.one], `"myPrologue"`)
modifyAgainFs: fs => addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue"`)
});
// Verify ignore dtsChanged
verifyOutFileScenario({
subScenario: "strict in one dependency",
modifyFs: fs => enableStrict(fs, sources[Project.second][Source.config]),
modifyFs: fs => enableStrict(fs, "/src/second/tsconfig.json"),
modifyAgainFs: fs => addTestPrologue(fs, "src/first/first_PART1.ts", `"myPrologue"`),
ignoreDtsChanged: true,
baselineOnly: true
@@ -323,28 +225,28 @@ namespace ts {
verifyOutFileScenario({
subScenario: "multiple prologues in all projects",
modifyFs: fs => {
enableStrict(fs, sources[Project.first][Source.config]);
addTestPrologue(fs, sources[Project.first][Source.ts][Part.one], `"myPrologue"`);
enableStrict(fs, sources[Project.second][Source.config]);
addTestPrologue(fs, sources[Project.second][Source.ts][Part.one], `"myPrologue"`);
addTestPrologue(fs, sources[Project.second][Source.ts][Part.two], `"myPrologue2";`);
enableStrict(fs, sources[Project.third][Source.config]);
addTestPrologue(fs, sources[Project.third][Source.ts][Part.one], `"myPrologue";`);
addTestPrologue(fs, sources[Project.third][Source.ts][Part.one], `"myPrologue3";`);
enableStrict(fs, "/src/first/tsconfig.json");
addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue"`);
enableStrict(fs, "/src/second/tsconfig.json");
addTestPrologue(fs, "/src/second/second_part1.ts", `"myPrologue"`);
addTestPrologue(fs, "/src/second/second_part2.ts", `"myPrologue2";`);
enableStrict(fs, "/src/third/tsconfig.json");
addTestPrologue(fs, "/src/third/third_part1.ts", `"myPrologue";`);
addTestPrologue(fs, "/src/third/third_part1.ts", `"myPrologue3";`);
},
modifyAgainFs: fs => addTestPrologue(fs, relSources[Project.first][Source.ts][Part.one], `"myPrologue5"`)
modifyAgainFs: fs => addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`)
});
// Verify ignore dtsChanged
verifyOutFileScenario({
subScenario: "multiple prologues in different projects",
modifyFs: fs => {
enableStrict(fs, sources[Project.first][Source.config]);
addTestPrologue(fs, sources[Project.second][Source.ts][Part.one], `"myPrologue"`);
addTestPrologue(fs, sources[Project.second][Source.ts][Part.two], `"myPrologue2";`);
enableStrict(fs, sources[Project.third][Source.config]);
enableStrict(fs, "/src/first/tsconfig.json");
addTestPrologue(fs, "/src/second/second_part1.ts", `"myPrologue"`);
addTestPrologue(fs, "/src/second/second_part2.ts", `"myPrologue2";`);
enableStrict(fs, "/src/third/tsconfig.json");
},
modifyAgainFs: fs => addTestPrologue(fs, sources[Project.first][Source.ts][Part.one], `"myPrologue5"`),
modifyAgainFs: fs => addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -456,13 +358,13 @@ namespace ts {
}
function diableRemoveCommentsInAll(fs: vfs.FileSystem) {
disableRemoveComments(fs, sources[Project.first][Source.config]);
disableRemoveComments(fs, sources[Project.second][Source.config]);
disableRemoveComments(fs, sources[Project.third][Source.config]);
disableRemoveComments(fs, "/src/first/tsconfig.json");
disableRemoveComments(fs, "/src/second/tsconfig.json");
disableRemoveComments(fs, "/src/third/tsconfig.json");
}
function stripInternalOfThird(fs: vfs.FileSystem) {
replaceText(fs, sources[Project.third][Source.config], `"declaration": true,`, `"declaration": true,
replaceText(fs, "/src/third/tsconfig.json", `"declaration": true,`, `"declaration": true,
"stripInternal": true,`);
}
@@ -472,8 +374,8 @@ namespace ts {
diableRemoveCommentsInAll(fs);
}
stripInternalOfThird(fs);
replaceText(fs, sources[Project.first][Source.ts][Part.one], "interface", `${internal} interface`);
appendText(fs, sources[Project.second][Source.ts][Part.one], `
replaceText(fs, "/src/first/first_PART1.ts", "interface", `${internal} interface`);
appendText(fs, "/src/second/second_part1.ts", `
class normalC {
${internal} constructor() { }
${internal} prop: string;
@@ -505,14 +407,14 @@ ${internal} enum internalEnum { a, b, c }`);
verifyOutFileScenario({
subScenario: "stripInternal",
modifyFs: stripInternalScenario,
modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"),
});
// Verify ignore dtsChanged
verifyOutFileScenario({
subScenario: "stripInternal with comments emit enabled",
modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true),
modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -521,7 +423,7 @@ ${internal} enum internalEnum { a, b, c }`);
verifyOutFileScenario({
subScenario: "stripInternal jsdoc style comment",
modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true),
modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/**@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -536,9 +438,9 @@ ${internal} enum internalEnum { a, b, c }`);
describe("with three levels of project dependency", () => {
function makeOneTwoThreeDependOrder(fs: vfs.FileSystem) {
replaceText(fs, sources[Project.second][Source.config], "[", `[
replaceText(fs, "/src/second/tsconfig.json", "[", `[
{ "path": "../first", "prepend": true }`);
replaceText(fs, sources[Project.third][Source.config], `{ "path": "../first", "prepend": true },`, "");
replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../first", "prepend": true },`, "");
}
function stripInternalWithDependentOrder(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) {
@@ -550,14 +452,14 @@ ${internal} enum internalEnum { a, b, c }`);
verifyOutFileScenario({
subScenario: "stripInternal when one-two-three are prepended in order",
modifyFs: stripInternalWithDependentOrder,
modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"),
});
// Verify ignore dtsChanged
verifyOutFileScenario({
subScenario: "stripInternal with comments emit enabled when one-two-three are prepended in order",
modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true),
modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -566,7 +468,7 @@ ${internal} enum internalEnum { a, b, c }`);
verifyOutFileScenario({
subScenario: "stripInternal jsdoc style comment when one-two-three are prepended in order",
modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true),
modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/**@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -585,7 +487,7 @@ ${internal} enum internalEnum { a, b, c }`);
subScenario: "stripInternal baseline when internal is inside another internal",
modifyFs: fs => {
stripInternalOfThird(fs);
prependText(fs, sources[Project.first][Source.ts][Part.one], `namespace ts {
prependText(fs, "/src/first/first_PART1.ts", `namespace ts {
/* @internal */
/**
* Subset of properties from SourceFile that are used in multiple utility functions
@@ -624,7 +526,7 @@ ${internal} enum internalEnum { a, b, c }`);
subScenario: "stripInternal when few members of enum are internal",
modifyFs: fs => {
stripInternalOfThird(fs);
prependText(fs, sources[Project.first][Source.ts][Part.one], `enum TokenFlags {
prependText(fs, "/src/first/first_PART1.ts", `enum TokenFlags {
None = 0,
/* @internal */
PrecedingLineBreak = 1 << 0,
@@ -659,9 +561,9 @@ ${internal} enum internalEnum { a, b, c }`);
ignoreDtsChanged: true,
ignoreDtsUnchanged: true,
modifyFs: fs => {
fs.writeFileSync(sources[Project.first][Source.ts][Part.one], "/* @internal */ const A = 1;");
fs.writeFileSync(sources[Project.third][Source.ts][Part.one], "const B = 2;");
fs.writeFileSync(sources[Project.first][Source.config], JSON.stringify({
fs.writeFileSync("/src/first/first_PART1.ts", "/* @internal */ const A = 1;");
fs.writeFileSync("/src/third/third_part1.ts", "const B = 2;");
fs.writeFileSync("/src/first/tsconfig.json", JSON.stringify({
compilerOptions: {
composite: true,
declaration: true,
@@ -670,9 +572,9 @@ ${internal} enum internalEnum { a, b, c }`);
sourceMap: true,
outFile: "./bin/first-output.js"
},
files: [sources[Project.first][Source.ts][Part.one]]
files: ["/src/first/first_PART1.ts"]
}));
fs.writeFileSync(sources[Project.third][Source.config], JSON.stringify({
fs.writeFileSync("/src/third/tsconfig.json", JSON.stringify({
compilerOptions: {
composite: true,
declaration: true,
@@ -682,7 +584,7 @@ ${internal} enum internalEnum { a, b, c }`);
outFile: "./thirdjs/output/third-output.js",
},
references: [{ path: "../first", prepend: true }],
files: [sources[Project.third][Source.ts][Part.one]]
files: ["/src/third/third_part1.ts"]
}));
}
});
@@ -690,7 +592,7 @@ ${internal} enum internalEnum { a, b, c }`);
describe("empty source files", () => {
function makeThirdEmptySourceFile(fs: vfs.FileSystem) {
fs.writeFileSync(sources[Project.third][Source.ts][Part.one], "", "utf8");
fs.writeFileSync("/src/third/third_part1.ts", "", "utf8");
}
// Verify ignore dtsChanged
@@ -706,9 +608,9 @@ ${internal} enum internalEnum { a, b, c }`);
subScenario: "declarationMap and sourceMap disabled",
modifyFs: fs => {
makeThirdEmptySourceFile(fs);
replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, "");
replaceText(fs, sources[Project.third][Source.config], `"sourceMap": true,`, "");
replaceText(fs, sources[Project.third][Source.config], `"declarationMap": true,`, "");
replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, "");
replaceText(fs, "/src/third/tsconfig.json", `"sourceMap": true,`, "");
replaceText(fs, "/src/third/tsconfig.json", `"declarationMap": true,`, "");
},
ignoreDtsChanged: true,
ignoreDtsUnchanged: true,
@@ -724,18 +626,18 @@ ${internal} enum internalEnum { a, b, c }`);
commandLineArgs: ["--b", "/src/third", "--verbose"],
modifyFs: fs => {
// No prepend
replaceText(fs, sources[Project.third][Source.config], `{ "path": "../first", "prepend": true }`, `{ "path": "../first" }`);
replaceText(fs, sources[Project.third][Source.config], `{ "path": "../second", "prepend": true }`, `{ "path": "../second" }`);
replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../first", "prepend": true }`, `{ "path": "../first" }`);
replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../second", "prepend": true }`, `{ "path": "../second" }`);
// Non Modules
replaceText(fs, sources[Project.first][Source.config], `"composite": true,`, `"composite": true, "module": "none",`);
replaceText(fs, sources[Project.second][Source.config], `"composite": true,`, `"composite": true, "module": "none",`);
replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, `"composite": true, "module": "none",`);
replaceText(fs, "/src/first/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`);
replaceText(fs, "/src/second/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`);
replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`);
// Own file emit
replaceText(fs, sources[Project.first][Source.config], `"outFile": "./bin/first-output.js",`, "");
replaceText(fs, sources[Project.second][Source.config], `"outFile": "../2/second-output.js",`, "");
replaceText(fs, sources[Project.third][Source.config], `"outFile": "./thirdjs/output/third-output.js",`, "");
replaceText(fs, "/src/first/tsconfig.json", `"outFile": "./bin/first-output.js",`, "");
replaceText(fs, "/src/second/tsconfig.json", `"outFile": "../2/second-output.js",`, "");
replaceText(fs, "/src/third/tsconfig.json", `"outFile": "./thirdjs/output/third-output.js",`, "");
},
});
});

View File

@@ -1,11 +1,6 @@
namespace ts {
describe("unittests:: tsbuild:: on 'sample1' project", () => {
let projFs: vfs.FileSystem;
const testsOutputs = ["/src/tests/index.js", "/src/tests/index.d.ts", "/src/tests/tsconfig.tsbuildinfo"];
const logicOutputs = ["/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts", "/src/logic/tsconfig.tsbuildinfo"];
const coreOutputs = ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map", "/src/core/tsconfig.tsbuildinfo"];
const allExpectedOutputs = [...testsOutputs, ...logicOutputs, ...coreOutputs];
before(() => {
projFs = loadProjectFromDisk("tests/projects/sample1");
});
@@ -14,6 +9,13 @@ namespace ts {
projFs = undefined!; // Release the contents
});
function getTsBuildProjectFile(project: string, file: string): tscWatch.File {
return {
path: TestFSWithWatch.getTsBuildProjectFilePath(project, file),
content: projFs.readFileSync(`/src/${project}/${file}`, "utf8")!
};
}
function getSampleFsAfterBuild() {
const fs = projFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs);
@@ -73,27 +75,28 @@ namespace ts {
incrementalScenarios: noChangeOnlyRuns
});
it("cleans till project specified", () => {
const fs = projFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
builder.build();
const result = builder.clean("/src/logic");
host.assertDiagnosticMessages(/*empty*/);
verifyOutputsPresent(fs, testsOutputs);
verifyOutputsAbsent(fs, [...logicOutputs, ...coreOutputs]);
assert.equal(result, ExitStatus.Success);
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "cleans till project specified",
fs: getSampleFsAfterBuild,
commandLineArgs: ["--b", "/src/logic", "--clean"],
compile: sys => {
const buildHost = createSolutionBuilderHost(sys);
const builder = createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {});
sys.exit(builder.clean("/src/logic"));
}
});
it("cleaning project in not build order doesnt throw error", () => {
const fs = projFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
builder.build();
const result = builder.clean("/src/logic2");
host.assertDiagnosticMessages(/*empty*/);
verifyOutputsPresent(fs, allExpectedOutputs);
assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped);
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "cleaning project in not build order doesnt throw error",
fs: getSampleFsAfterBuild,
commandLineArgs: ["--b", "/src/logic2", "--clean"],
compile: sys => {
const buildHost = createSolutionBuilderHost(sys);
const builder = createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {});
sys.exit(builder.clean("/src/logic2"));
}
});
});
@@ -108,17 +111,6 @@ namespace ts {
});
describe("can detect when and what to rebuild", () => {
function initializeWithBuild(opts?: BuildOptions) {
const { fs, tick } = getFsWithTime(projFs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
builder.build();
host.clearDiagnostics();
tick();
builder = createSolutionBuilder(host, ["/src/tests"], { ...(opts || {}), verbose: true });
return { fs, host, builder };
}
verifyTscIncrementalEdits({
scenario: "sample1",
subScenario: "can detect when and what to rebuild",
@@ -157,48 +149,39 @@ namespace ts {
]
});
it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => {
const { host, builder } = initializeWithBuild();
changeCompilerVersion(host);
builder.build();
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/core/tsconfig.json", fakes.version, version],
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/logic/tsconfig.json", fakes.version, version],
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/tests/tsconfig.json", fakes.version, version],
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"],
);
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "rebuilds completely when version in tsbuildinfo doesnt match ts version",
fs: getSampleFsAfterBuild,
commandLineArgs: ["--b", "/src/tests", "--verbose"],
compile: sys => {
// Buildinfo will have version which does not match with current ts version
fakes.patchHostForBuildInfoWrite(sys, "FakeTSCurrentVersion");
const buildHost = createSolutionBuilderHost(sys);
const builder = createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true });
sys.exit(builder.build());
}
});
it("does not rebuild if there is no program and bundle in the ts build info event if version doesnt match ts version", () => {
const { fs, tick } = getFsWithTime(projFs);
const host = fakes.SolutionBuilderHost.create(fs, /*options*/ undefined, /*setParentNodes*/ undefined, createAbstractBuilder);
let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"],
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tests/tsconfig.json", "src/tests/index.js"],
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"]
);
verifyOutputsPresent(fs, allExpectedOutputs);
host.clearDiagnostics();
tick();
builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
changeCompilerVersion(host);
builder.build();
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"],
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"],
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/tests/tsconfig.json", "src/tests/index.ts", "src/tests/index.js"]
);
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "does not rebuild if there is no program and bundle in the ts build info event if version doesnt match ts version",
fs: () => {
const fs = projFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs, /*options*/ undefined, /*setParentNodes*/ undefined, createAbstractBuilder);
const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
builder.build();
fs.makeReadonly();
return fs;
},
commandLineArgs: ["--b", "/src/tests", "--verbose"],
compile: sys => {
// Buildinfo will have version which does not match with current ts version
fakes.patchHostForBuildInfoWrite(sys, "FakeTSCurrentVersion");
const buildHost = createSolutionBuilderHost(sys);
const builder = createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true });
sys.exit(builder.build());
},
});
verifyTscSerializedIncrementalEdits({
@@ -216,80 +199,87 @@ namespace ts {
}]
});
it("builds till project specified", () => {
const fs = projFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
const result = builder.build("/src/logic");
host.assertDiagnosticMessages(/*empty*/);
verifyOutputsAbsent(fs, testsOutputs);
verifyOutputsPresent(fs, [...logicOutputs, ...coreOutputs]);
assert.equal(result, ExitStatus.Success);
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "builds till project specified",
fs: () => projFs,
commandLineArgs: ["--build", "/src/logic/tsconfig.json"],
compile: sys => {
const buildHost = createSolutionBuilderHost(sys);
const builder = createSolutionBuilder(buildHost, ["/src/tests"], {});
sys.exit(builder.build("/src/logic/tsconfig.json"));
}
});
it("building project in not build order doesnt throw error", () => {
const fs = projFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
const result = builder.build("/src/logic2");
host.assertDiagnosticMessages(/*empty*/);
verifyOutputsAbsent(fs, allExpectedOutputs);
assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped);
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "building project in not build order doesnt throw error",
fs: () => projFs,
commandLineArgs: ["--build", "/src/logic2/tsconfig.json"],
compile: sys => {
const buildHost = createSolutionBuilderHost(sys);
const builder = createSolutionBuilder(buildHost, ["/src/tests"], {});
sys.exit(builder.build("/src/logic2/tsconfig.json"));
}
});
it("building using getNextInvalidatedProject", () => {
interface SolutionBuilderResult<T> {
project: ResolvedConfigFileName;
result: T;
}
const coreConfig = getTsBuildProjectFile("core", "tsconfig.json");
const coreIndex = getTsBuildProjectFile("core", "index.ts");
const coreDecl = getTsBuildProjectFile("core", "some_decl.d.ts");
const coreAnotherModule = getTsBuildProjectFile("core", "anotherModule.ts");
const logicConfig = getTsBuildProjectFile("logic", "tsconfig.json");
const logicIndex = getTsBuildProjectFile("logic", "index.ts");
const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json");
const testsIndex = getTsBuildProjectFile("tests", "index.ts");
const baseline: string[] = [];
let oldSnap: ReturnType<TestFSWithWatch.TestServerHost["snap"]> | undefined;
const system = TestFSWithWatch.changeToHostTrackingWrittenFiles(
fakes.patchHostForBuildInfoReadWrite(
tscWatch.createWatchedSystem([
coreConfig, coreIndex, coreDecl, coreAnotherModule,
logicConfig, logicIndex,
testsConfig, testsIndex,
tscWatch.libFile
])
)
);
const fs = projFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
verifyBuildNextResult({
project: "/src/core/tsconfig.json" as ResolvedConfigFileName,
result: ExitStatus.Success
}, coreOutputs, [...logicOutputs, ...testsOutputs]);
const host = createSolutionBuilderHost(system);
const builder = createSolutionBuilder(host, [testsConfig.path], {});
baseline.push("Input::");
baselineState();
verifyBuildNextResult(); // core
verifyBuildNextResult(); // logic
verifyBuildNextResult();// tests
verifyBuildNextResult(); // All Done
Harness.Baseline.runBaseline(`tsbuild/sample1/building-using-getNextInvalidatedProject.js`, baseline.join("\r\n"));
verifyBuildNextResult({
project: "/src/logic/tsconfig.json" as ResolvedConfigFileName,
result: ExitStatus.Success
}, [...coreOutputs, ...logicOutputs], testsOutputs);
verifyBuildNextResult({
project: "/src/tests/tsconfig.json" as ResolvedConfigFileName,
result: ExitStatus.Success
}, allExpectedOutputs, emptyArray);
verifyBuildNextResult(/*expected*/ undefined, allExpectedOutputs, emptyArray);
function verifyBuildNextResult(
expected: SolutionBuilderResult<ExitStatus> | undefined,
presentOutputs: readonly string[],
absentOutputs: readonly string[]
) {
function verifyBuildNextResult() {
const project = builder.getNextInvalidatedProject();
const result = project && project.done();
assert.deepEqual(project && { project: project.project, result }, expected);
verifyOutputsPresent(fs, presentOutputs);
verifyOutputsAbsent(fs, absentOutputs);
baseline.push(`Project Result:: ${JSON.stringify({ project: project?.project, result })}`);
baselineState();
}
function baselineState() {
system.serializeOutput(baseline);
system.diff(baseline, oldSnap);
system.writtenFiles.clear();
oldSnap = system.snap();
}
});
it("building using buildReferencedProject", () => {
const fs = projFs.shadow();
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
builder.buildReferences("/src/tests");
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"],
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
);
verifyOutputsPresent(fs, [...coreOutputs, ...logicOutputs]);
verifyOutputsAbsent(fs, testsOutputs);
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "building using buildReferencedProject",
fs: () => projFs,
commandLineArgs: ["--build", "/src/logic2/tsconfig.json"],
compile: sys => {
const buildHost = createSolutionBuilderHost(sys);
const builder = createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true });
sys.exit(builder.buildReferences("/src/tests"));
}
});
});
@@ -305,55 +295,62 @@ namespace ts {
describe("project invalidation", () => {
it("invalidates projects correctly", () => {
const { fs, time, tick } = getFsWithTime(projFs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
const coreConfig = getTsBuildProjectFile("core", "tsconfig.json");
const coreIndex = getTsBuildProjectFile("core", "index.ts");
const coreDecl = getTsBuildProjectFile("core", "some_decl.d.ts");
const coreAnotherModule = getTsBuildProjectFile("core", "anotherModule.ts");
const logicConfig = getTsBuildProjectFile("logic", "tsconfig.json");
const logicIndex = getTsBuildProjectFile("logic", "index.ts");
const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json");
const testsIndex = getTsBuildProjectFile("tests", "index.ts");
const baseline: string[] = [];
let oldSnap: ReturnType<TestFSWithWatch.TestServerHost["snap"]> | undefined;
const system = TestFSWithWatch.changeToHostTrackingWrittenFiles(
fakes.patchHostForBuildInfoReadWrite(
tscWatch.createWatchedSystem([
coreConfig, coreIndex, coreDecl, coreAnotherModule,
logicConfig, logicIndex,
testsConfig, testsIndex,
tscWatch.libFile
])
)
);
const host = createSolutionBuilderHost(system);
const builder = createSolutionBuilder(host, [testsConfig.path], { dry: false, force: false, verbose: false });
builder.build();
host.assertDiagnosticMessages(/*empty*/);
baselineState("Build of project");
// Update a timestamp in the middle project
tick();
appendText(fs, "/src/logic/index.ts", "function foo() {}");
const originalWriteFile = fs.writeFileSync;
const writtenFiles = new Map<string, true>();
fs.writeFileSync = (path, data, encoding) => {
writtenFiles.set(path, true);
originalWriteFile.call(fs, path, data, encoding);
};
system.appendFile(logicIndex.path, "function foo() {}");
// Because we haven't reset the build context, the builder should assume there's nothing to do right now
const status = builder.getUpToDateStatusOfProject("/src/logic");
assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date");
verifyInvalidation(/*expectedToWriteTests*/ false);
const status = builder.getUpToDateStatusOfProject(logicConfig.path);
baseline.push(`Project should still be upto date: ${UpToDateStatusType[status.type]}`);
verifyInvalidation("non Dts change to logic");
// Rebuild this project
fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")}
export class cNew {}`);
verifyInvalidation(/*expectedToWriteTests*/ true);
system.appendFile(logicIndex.path, `export class cNew {}`);
verifyInvalidation("Dts change to Logic");
Harness.Baseline.runBaseline(`tsbuild/sample1/invalidates-projects-correctly.js`, baseline.join("\r\n"));
function verifyInvalidation(expectedToWriteTests: boolean) {
function verifyInvalidation(heading: string) {
// Rebuild this project
tick();
builder.invalidateProject("/src/logic/tsconfig.json" as ResolvedConfigFilePath);
builder.invalidateProject(logicConfig.path as ResolvedConfigFilePath);
builder.buildNextInvalidatedProject();
// 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();
baselineState(`${heading}:: After rebuilding logicConfig`);
// Build downstream projects should update 'tests', but not 'core'
tick();
builder.buildNextInvalidatedProject();
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");
baselineState(`${heading}:: After building next project`);
}
function baselineState(heading: string) {
baseline.push(heading);
system.serializeOutput(baseline);
system.diff(baseline, oldSnap);
system.writtenFiles.clear();
oldSnap = system.snap();
}
});
});

View File

@@ -1,21 +1,6 @@
namespace ts.tscWatch {
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => {
type TsBuildWatchSystem = TestFSWithWatch.TestServerHostTrackingWrittenFiles;
function createTsBuildWatchSystem(fileOrFolderList: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) {
return TestFSWithWatch.changeToHostTrackingWrittenFiles(
createWatchedSystem(fileOrFolderList, params)
);
}
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;
}
const scenario = "programUpdates";
const project = "sample1";
const enum SubProject {
core = "core",
logic = "logic",
@@ -24,17 +9,13 @@ namespace ts.tscWatch {
}
type ReadonlyFile = Readonly<File>;
/** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */
type SubProjectFiles = [ReadonlyFile, ReadonlyFile] | [ReadonlyFile, ReadonlyFile, ReadonlyFile, ReadonlyFile];
function projectPath(subProject: SubProject) {
return TestFSWithWatch.getTsBuildProjectFilePath(project, subProject);
}
type SubProjectFiles = [tsconfig: ReadonlyFile, index: ReadonlyFile] | [tsconfig: ReadonlyFile, index: ReadonlyFile, anotherModule: ReadonlyFile, someDecl: ReadonlyFile];
function projectFilePath(subProject: SubProject, baseFileName: string) {
return `${projectPath(subProject)}/${baseFileName.toLowerCase()}`;
return `${TestFSWithWatch.getTsBuildProjectFilePath("sample1", subProject)}/${baseFileName.toLowerCase()}`;
}
function projectFile(subProject: SubProject, baseFileName: string): File {
return TestFSWithWatch.getTsBuildProjectFile(project, `${subProject}/${baseFileName}`);
return TestFSWithWatch.getTsBuildProjectFile("sample1", `${subProject}/${baseFileName}`);
}
function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles {
@@ -48,29 +29,6 @@ namespace ts.tscWatch {
return [tsconfig, index, anotherModule, someDecl];
}
function getOutputFileNames(subProject: SubProject, baseFileNameWithoutExtension: string) {
const file = projectFilePath(subProject, baseFileNameWithoutExtension);
return [`${file}.js`, `${file}.d.ts`];
}
function getOutputStamps(host: TsBuildWatchSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] {
return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => transformOutputToOutputFileStamp(f, host));
}
function getOutputFileStamps(host: TsBuildWatchSystem, additionalFiles?: readonly [SubProject, string][]): OutputFileStamp[] {
const result = [
...getOutputStamps(host, SubProject.core, "anotherModule"),
...getOutputStamps(host, SubProject.core, "index"),
...getOutputStamps(host, SubProject.logic, "index"),
...getOutputStamps(host, SubProject.tests, "index"),
];
if (additionalFiles) {
additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension)));
}
host.writtenFiles.clear();
return result;
}
function changeFile(fileName: string | (() => string), content: string | (() => string), caption: string): TscWatchCompileChange {
return {
caption,
@@ -88,8 +46,6 @@ namespace ts.tscWatch {
let tests: SubProjectFiles;
let ui: SubProjectFiles;
let allFiles: readonly File[];
let testProjectExpectedWatchedFiles: string[];
let testProjectExpectedWatchedDirectoriesRecursive: string[];
before(() => {
core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true);
@@ -97,8 +53,6 @@ namespace ts.tscWatch {
tests = subProjectFiles(SubProject.tests);
ui = subProjectFiles(SubProject.ui);
allFiles = [libFile, ...core, ...logic, ...tests, ...ui];
testProjectExpectedWatchedFiles = [core[0], core[1], core[2]!, ...logic, ...tests].map(f => f.path.toLowerCase());
testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)];
});
after(() => {
@@ -107,38 +61,32 @@ namespace ts.tscWatch {
tests = undefined!;
ui = undefined!;
allFiles = undefined!;
testProjectExpectedWatchedFiles = undefined!;
testProjectExpectedWatchedDirectoriesRecursive = undefined!;
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "creates solution in watch mode",
commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
changes: emptyArray
});
it("verify building references watches only those projects", () => {
const system = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation });
const host = createSolutionBuilderWithWatchHost(system);
const solutionBuilder = createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], { watch: true });
solutionBuilder.buildReferences(`${project}/${SubProject.tests}`);
checkWatchedFiles(system, testProjectExpectedWatchedFiles.slice(0, testProjectExpectedWatchedFiles.length - tests.length));
checkWatchedDirectories(system, emptyArray, /*recursive*/ false);
checkWatchedDirectories(system, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true);
checkOutputErrorsInitial(system, emptyArray);
const testOutput = getOutputStamps(system, SubProject.tests, "index");
const outputFileStamps = getOutputFileStamps(system);
for (const stamp of outputFileStamps.slice(0, outputFileStamps.length - testOutput.length)) {
assert.isDefined(stamp[1], `${stamp[0]} expected to be present`);
}
for (const stamp of testOutput) {
assert.isUndefined(stamp[1], `${stamp[0]} expected to be missing`);
}
return system;
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(allFiles, { currentDirectory: projectsLocation }));
const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb);
const solutionBuilder = createSolutionBuilderWithWatch(host, [`sample1/${SubProject.tests}`], { watch: true });
solutionBuilder.buildReferences(`sample1/${SubProject.tests}`);
runWatchBaseline({
scenario: "programUpdates",
subScenario: "verify building references watches only those projects",
commandLineArgs: ["--b", "--w"],
sys,
baseline,
oldSnap,
getPrograms,
changes: emptyArray,
watchOrSolution: solutionBuilder
});
});
const buildTests: TscWatchCompileChange = {
@@ -163,9 +111,9 @@ namespace ts.tscWatch {
};
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: `${subScenario}/change builds changes and reports found errors message`,
commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => createWatchedSystem(
allFilesGetter(),
{ currentDirectory: projectsLocation }
@@ -198,9 +146,9 @@ export class someClass2 { }`);
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: `${subScenario}/non local change does not start build of referencing projects`,
commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => createWatchedSystem(
allFilesGetter(),
{ currentDirectory: projectsLocation }
@@ -217,9 +165,9 @@ function foo() { }`, "Make local change to core"),
return changeFile(newFile.path, newFileContent, "Change to new File and build core");
}
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: `${subScenario}/builds when new file is added, and its subsequent updates`,
commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => createWatchedSystem(
allFilesGetter(),
{ currentDirectory: projectsLocation }
@@ -262,9 +210,9 @@ export class someClass2 { }`),
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "watches config files that are not present",
commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => createWatchedSystem(
[libFile, ...core, logic[1], ...tests],
{ currentDirectory: projectsLocation }
@@ -297,9 +245,9 @@ export class someClass2 { }`),
timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
};
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "when referenced using prepend builds referencing project even for non local change",
commandLineArgs: ["-b", "-w", `${project}/${SubProject.logic}`],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.logic}`],
sys: () => {
const coreTsConfig: File = {
path: core[0].path,
@@ -332,7 +280,7 @@ function myFunc() { return 100; }`, "Make local change and build core"),
});
describe("when referenced project change introduces error in the down stream project and then fixes it", () => {
const subProjectLibrary = `${projectsLocation}/${project}/Library`;
const subProjectLibrary = `${projectsLocation}/sample1/Library`;
const libraryTs: File = {
path: `${subProjectLibrary}/library.ts`,
content: `
@@ -349,7 +297,7 @@ export function createSomeObject(): SomeObject
}`
};
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "when referenced project change introduces error in the down stream project and then fixes it",
commandLineArgs: ["-b", "-w", "App"],
sys: () => {
@@ -357,7 +305,7 @@ export function createSomeObject(): SomeObject
path: `${subProjectLibrary}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true } })
};
const subProjectApp = `${projectsLocation}/${project}/App`;
const subProjectApp = `${projectsLocation}/sample1/App`;
const appTs: File = {
path: `${subProjectApp}/app.ts`,
content: `import { createSomeObject } from "../Library/library";
@@ -369,7 +317,7 @@ createSomeObject().message;`
};
const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig];
return createWatchedSystem(files, { currentDirectory: `${projectsLocation}/${project}` });
return createWatchedSystem(files, { currentDirectory: `${projectsLocation}/sample1` });
},
changes: [
{
@@ -398,9 +346,9 @@ createSomeObject().message;`
describe("reports errors in all projects on incremental compile", () => {
function verifyIncrementalErrors(subScenario: string, buildOptions: readonly string[]) {
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: `reportErrors/${subScenario}`,
commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`, ...buildOptions],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, ...buildOptions],
sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
changes: [
{
@@ -466,7 +414,7 @@ let x: string = 10;`),
};
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "reportErrors/declarationEmitErrors/when fixing error files all files are emitted",
commandLineArgs: ["-b", "-w", subProject],
sys: () => createWatchedSystem(
@@ -479,7 +427,7 @@ let x: string = 10;`),
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "reportErrors/declarationEmitErrors/when file with no error changes",
commandLineArgs: ["-b", "-w", subProject],
sys: () => createWatchedSystem(
@@ -499,7 +447,7 @@ let x: string = 10;`),
};
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "reportErrors/declarationEmitErrors/introduceError/when fixing errors only changed file is emitted",
commandLineArgs: ["-b", "-w", subProject],
sys: () => createWatchedSystem(
@@ -513,7 +461,7 @@ let x: string = 10;`),
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "reportErrors/declarationEmitErrors/introduceError/when file with no error changes",
commandLineArgs: ["-b", "-w", subProject],
sys: () => createWatchedSystem(
@@ -530,9 +478,9 @@ let x: string = 10;`),
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "incremental updates in verbose mode",
commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`, "-verbose"],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, "-verbose"],
sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
changes: [
{
@@ -557,7 +505,7 @@ export function someFn() { }`),
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "works when noUnusedParameters changes to false",
commandLineArgs: ["-b", "-w"],
sys: () => {
@@ -589,15 +537,15 @@ export function someFn() { }`),
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "should not trigger recompilation because of program emit",
commandLineArgs: ["-b", "-w", `${project}/${SubProject.core}`, "-verbose"],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"],
sys: () => createWatchedSystem([libFile, ...core], { currentDirectory: projectsLocation }),
changes: [
noopChange,
{
caption: "Add new file",
change: sys => sys.writeFile(`${project}/${SubProject.core}/file3.ts`, `export const y = 10;`),
change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
noopChange,
@@ -605,9 +553,9 @@ export function someFn() { }`),
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "should not trigger recompilation because of program emit with outDir specified",
commandLineArgs: ["-b", "-w", `${project}/${SubProject.core}`, "-verbose"],
commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"],
sys: () => {
const [coreConfig, ...rest] = core;
const newCoreConfig: File = { path: coreConfig.path, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "outDir" } }) };
@@ -617,7 +565,7 @@ export function someFn() { }`),
noopChange,
{
caption: "Add new file",
change: sys => sys.writeFile(`${project}/${SubProject.core}/file3.ts`, `export const y = 10;`),
change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
noopChange
@@ -625,7 +573,7 @@ export function someFn() { }`),
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "works with extended source files",
commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"],
sys: () => {
@@ -711,7 +659,7 @@ export function someFn() { }`),
});
verifyTscWatch({
scenario,
scenario: "programUpdates",
subScenario: "works correctly when project with extended config is removed",
commandLineArgs: ["-b", "-w", "-v"],
sys: () => {

View File

@@ -40,17 +40,8 @@ export enum e2 { }
export function f22() { } // trailing`
};
const commandLineArgs = ["--b", "--w"];
const { sys, baseline, oldSnap } = createBaseline(createWatchedSystem([libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: projectRoot }));
const { cb, getPrograms } = commandLineCallbacks(sys);
const buildHost = createSolutionBuilderWithWatchHost(
sys,
/*createProgram*/ undefined,
createDiagnosticReporter(sys, /*pretty*/ true),
createBuilderStatusReporter(sys, /*pretty*/ true),
createWatchStatusReporter(sys, /*pretty*/ true)
);
buildHost.afterProgramEmitAndDiagnostics = cb;
buildHost.afterEmitBundle = cb;
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: projectRoot }));
const buildHost = createSolutionBuilderWithWatchHostForBaseline(sys, cb);
buildHost.getCustomTransformers = getCustomTransformers;
const builder = createSolutionBuilderWithWatch(buildHost, [solution.path], { verbose: true });
builder.build();

View File

@@ -23,52 +23,56 @@ namespace ts.tscWatch {
const allPkgFiles = pkgs(pkgFiles);
const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project, environmentVariables });
writePkgReferences();
const host = createSolutionBuilderWithWatchHost(system);
writePkgReferences(system);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(system);
const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb);
const solutionBuilder = createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true });
solutionBuilder.build();
checkOutputErrorsInitial(system, emptyArray, /*disableConsoleClears*/ undefined, [
`Projects in this build: \r\n${
concatenate(
pkgs(index => ` * pkg${index}/tsconfig.json`),
[" * tsconfig.json"]
).join("\r\n")}\n\n`,
...flatArray(pkgs(index => [
`Project 'pkg${index}/tsconfig.json' is out of date because output file 'pkg${index}/index.js' does not exist\n\n`,
`Building project '${project}/pkg${index}/tsconfig.json'...\n\n`
]))
]);
const watchFilesDetailed = arrayToMap(flatArray(allPkgFiles), f => f.path, () => 1);
watchFilesDetailed.set(configPath, 1);
watchFilesDetailed.set(typing.path, singleWatchPerFile ? 1 : maxPkgs);
checkWatchedFilesDetailed(system, watchFilesDetailed);
system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`);
verifyInvoke();
// Make change
maxPkgs--;
writePkgReferences();
system.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(system, emptyArray);
const lastFiles = last(allPkgFiles);
lastFiles.forEach(f => watchFilesDetailed.delete(f.path));
watchFilesDetailed.set(typing.path, singleWatchPerFile ? 1 : maxPkgs);
checkWatchedFilesDetailed(system, watchFilesDetailed);
system.writeFile(typing.path, typing.content);
verifyInvoke();
// Make change to remove all the watches
maxPkgs = 0;
writePkgReferences();
system.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(system, [
`tsconfig.json(1,10): error TS18002: The 'files' list in config file '${configPath}' is empty.\n`
]);
checkWatchedFilesDetailed(system, [configPath], 1);
system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`);
system.checkTimeoutQueueLength(0);
runWatchBaseline({
scenario: "watchEnvironment",
subScenario: `same file in multiple projects${singleWatchPerFile ? " with single watcher per file" : ""}`,
commandLineArgs: ["--b", "--w"],
sys,
baseline,
oldSnap,
getPrograms,
changes: [
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`),
timeouts: sys => pkgs(() => sys.checkTimeoutQueueLengthAndRun(1))
},
{
// Make change
caption: "change pkg references",
change: sys => {
maxPkgs--;
writePkgReferences(sys);
},
timeouts: checkSingleTimeoutQueueLengthAndRun,
},
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, typing.content),
timeouts: sys => pkgs(() => sys.checkTimeoutQueueLengthAndRun(1))
},
{
// Make change to remove all watches
caption: "change pkg references to remove all watches",
change: sys => {
maxPkgs = 0;
writePkgReferences(sys);
},
timeouts: checkSingleTimeoutQueueLengthAndRun,
},
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`),
timeouts: sys => pkgs(() => sys.checkTimeoutQueueLengthAndRun(1))
},
],
watchOrSolution: solutionBuilder
});
function flatArray<T>(arr: T[][]): readonly T[] {
return flatMap(arr, identity);
@@ -101,23 +105,13 @@ namespace ts.tscWatch {
}
];
}
function writePkgReferences() {
function writePkgReferences(system: TestFSWithWatch.TestServerHost) {
system.writeFile(configPath, JSON.stringify({
files: [],
include: [],
references: pkgs(createPkgReference)
}));
}
function verifyInvoke() {
pkgs(() => system.checkTimeoutQueueLengthAndRun(1));
checkOutputErrorsIncremental(system, emptyArray, /*disableConsoleClears*/ undefined, /*logsBeforeWatchDiagnostics*/ undefined, [
...flatArray(pkgs(index => [
`Project 'pkg${index}/tsconfig.json' is out of date because oldest output 'pkg${index}/index.js' is older than newest input 'typings/xterm.d.ts'\n\n`,
`Building project '${project}/pkg${index}/tsconfig.json'...\n\n`,
`Updating unchanged output timestamps of project '${project}/pkg${index}/tsconfig.json'...\n\n`
]))
]);
}
});
}
});

View File

@@ -19,30 +19,21 @@ namespace ts {
};
export const noChangeOnlyRuns = [noChangeRun];
export interface TscCompile {
scenario: string;
subScenario: string;
buildKind?: BuildKind; // Should be defined for tsc --b
fs: () => vfs.FileSystem;
commandLineArgs: readonly string[];
modifyFs?: (fs: vfs.FileSystem) => void;
export interface TestTscCompile extends TestTscCompileLikeBase {
baselineSourceMap?: boolean;
baselineReadFileCalls?: boolean;
baselinePrograms?: boolean;
baselineDependencies?: boolean;
disableUseFileVersionAsSignature?: boolean;
environmentVariables?: Record<string, string>;
}
export type CommandLineProgram = [Program, EmitAndSemanticDiagnosticsBuilderProgram?];
export type CommandLineProgram = [Program, BuilderProgram?];
export interface CommandLineCallbacks {
cb: ExecuteCommandLineCallbacks;
getPrograms: () => readonly CommandLineProgram[];
}
function isAnyProgram(program: Program | EmitAndSemanticDiagnosticsBuilderProgram | ParsedCommandLine): program is Program | EmitAndSemanticDiagnosticsBuilderProgram {
return !!(program as Program | EmitAndSemanticDiagnosticsBuilderProgram).getCompilerOptions;
function isAnyProgram(program: Program | BuilderProgram | ParsedCommandLine): program is Program | BuilderProgram {
return !!(program as Program | BuilderProgram).getCompilerOptions;
}
export function commandLineCallbacks(
sys: System & { writtenFiles: ReadonlyCollection<Path>; },
@@ -71,15 +62,29 @@ namespace ts {
}
};
}
export interface TestTscCompileLikeBase extends VerifyTscCompileLike {
buildKind?: BuildKind; // Should be defined for tsc --b
export function tscCompile(input: TscCompile) {
modifyFs?: (fs: vfs.FileSystem) => void;
disableUseFileVersionAsSignature?: boolean;
environmentVariables?: Record<string, string>;
}
export interface TestTscCompileLike extends TestTscCompileLikeBase {
compile: (sys: TscCompileSystem) => void;
additionalBaseline?: (sys: TscCompileSystem) => void;
}
/**
* Initialize FS, run compile function and save baseline
*/
export function testTscCompileLike(input: TestTscCompileLike) {
const initialFs = input.fs();
const inputFs = initialFs.shadow();
const {
scenario, subScenario, buildKind,
commandLineArgs, modifyFs,
baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies,
environmentVariables
environmentVariables,
compile: worker, additionalBaseline,
} = input;
if (modifyFs) modifyFs(inputFs);
inputFs.makeReadonly();
@@ -88,46 +93,12 @@ namespace ts {
// Create system
const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc", env: environmentVariables }) as TscCompileSystem;
if (input.disableUseFileVersionAsSignature) sys.disableUseFileVersionAsSignature = true;
fakes.patchHostForBuildInfoReadWrite(sys);
const writtenFiles = sys.writtenFiles = new Set();
const originalWriteFile = sys.writeFile;
sys.writeFile = (fileName, content, writeByteOrderMark) => {
const path = toPathWithSystem(sys, fileName);
assert.isFalse(writtenFiles.has(path));
writtenFiles.add(path);
return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
};
const actualReadFileMap: MapLike<number> = {};
const originalReadFile = sys.readFile;
sys.readFile = path => {
// Dont record libs
if (path.startsWith("/src/")) {
actualReadFileMap[path] = (getProperty(actualReadFileMap, path) || 0) + 1;
}
return originalReadFile.call(sys, path);
};
sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`);
sys.exit = exitCode => sys.exitCode = exitCode;
const { cb, getPrograms } = commandLineCallbacks(sys, originalReadFile, originalWriteFile);
executeCommandLine(
sys,
cb,
commandLineArgs,
);
worker(sys);
sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`);
if (baselinePrograms) {
const baseline: string[] = [];
tscWatch.baselinePrograms(baseline, getPrograms, emptyArray, baselineDependencies);
sys.write(baseline.join("\n"));
}
if (baselineReadFileCalls) {
sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `);
}
if (baselineSourceMap) generateSourceMapBaselineFiles(sys);
additionalBaseline?.(sys);
fs.makeReadonly();
sys.baseLine = () => {
const baseFsPatch = !buildKind || buildKind === BuildKind.Initial ?
inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }) :
@@ -147,30 +118,97 @@ ${patch ? vfs.formatPatch(patch) : ""}`
return sys;
}
/**
* Initialize Fs, execute command line and save baseline
*/
export function testTscCompile(input: TestTscCompile) {
let actualReadFileMap: MapLike<number> | undefined;
let getPrograms: CommandLineCallbacks["getPrograms"] | undefined;
return testTscCompileLike({
...input,
compile: commandLineCompile,
additionalBaseline
});
function commandLineCompile(sys: TscCompileSystem) {
fakes.patchHostForBuildInfoReadWrite(sys);
const writtenFiles = sys.writtenFiles = new Set();
const originalWriteFile = sys.writeFile;
sys.writeFile = (fileName, content, writeByteOrderMark) => {
const path = toPathWithSystem(sys, fileName);
assert.isFalse(writtenFiles.has(path));
writtenFiles.add(path);
return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
};
actualReadFileMap = {};
const originalReadFile = sys.readFile;
sys.readFile = path => {
// Dont record libs
if (path.startsWith("/src/")) {
actualReadFileMap![path] = (getProperty(actualReadFileMap!, path) || 0) + 1;
}
return originalReadFile.call(sys, path);
};
const result = commandLineCallbacks(sys, originalReadFile, originalWriteFile);
executeCommandLine(
sys,
result.cb,
input.commandLineArgs,
);
sys.readFile = originalReadFile;
getPrograms = result.getPrograms;
}
function additionalBaseline(sys: TscCompileSystem) {
const { baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies } = input;
if (baselinePrograms) {
const baseline: string[] = [];
tscWatch.baselinePrograms(baseline, getPrograms!, emptyArray, baselineDependencies);
sys.write(baseline.join("\n"));
}
if (baselineReadFileCalls) {
sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `);
}
if (baselineSourceMap) generateSourceMapBaselineFiles(sys);
actualReadFileMap = undefined;
getPrograms = undefined;
}
}
export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) {
it(`Generates files matching the baseline`, () => {
const { file, text } = sys().baseLine();
Harness.Baseline.runBaseline(file, text);
});
}
export interface VerifyTscCompileLike {
scenario: string;
subScenario: string;
commandLineArgs: readonly string[];
fs: () => vfs.FileSystem;
}
export function verifyTsc(input: TscCompile) {
/**
* Verify by baselining after initializing FS and custom compile
*/
export function verifyTscCompileLike<T extends VerifyTscCompileLike>(verifier: (input: T) => { baseLine: TscCompileSystem["baseLine"]; }, input: T) {
describe(`tsc ${input.commandLineArgs.join(" ")} ${input.scenario}:: ${input.subScenario}`, () => {
describe(input.scenario, () => {
describe(input.subScenario, () => {
let sys: TscCompileSystem;
before(() => {
sys = tscCompile({
...input,
fs: () => getFsWithTime(input.fs()).fs.makeReadonly()
});
});
after(() => {
sys = undefined!;
});
verifyTscBaseline(() => sys);
verifyTscBaseline(() => verifier({
...input,
fs: () => getFsWithTime(input.fs()).fs.makeReadonly()
}));
});
});
});
}
/**
* Verify by baselining after initializing FS and command line compile
*/
export function verifyTsc(input: TestTscCompile) {
verifyTscCompileLike(testTscCompile, input);
}
}

View File

@@ -38,7 +38,11 @@ namespace ts.tscWatch {
const files = [file, configFile, libFile];
it("using createWatchOfConfigFile ", () => {
const baseline = createBaseline(createWatchedSystem(files));
const watch = createWatchOfConfigFile(configFile.path, baseline.sys);
const watch = createWatchProgram(createWatchCompilerHostOfConfigFileForBaseline({
system: baseline.sys,
cb: baseline.cb,
configFileName: configFile.path,
}));
// Initially console is cleared if --preserveOutput is not provided since the config file is yet to be parsed
runWatchBaseline({
scenario,

View File

@@ -27,200 +27,6 @@ namespace ts.tscWatch {
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
}
export function checkProgramRootFiles(program: Program, expectedFiles: readonly string[]) {
checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
}
export type Watch = WatchOfConfigFile<EmitAndSemanticDiagnosticsBuilderProgram> | WatchOfFilesAndCompilerOptions<EmitAndSemanticDiagnosticsBuilderProgram>;
export function createWatchOfConfigFile(configFileName: string, system: WatchedSystem, optionsToExtend?: CompilerOptions, watchOptionsToExtend?: WatchOptions) {
const compilerHost = createWatchCompilerHostOfConfigFile({ configFileName, optionsToExtend, watchOptionsToExtend, system });
return createWatchProgram(compilerHost);
}
export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], system: WatchedSystem, options: CompilerOptions = {}, watchOptions?: WatchOptions) {
const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions({ rootFiles, options, watchOptions, system });
return createWatchProgram(compilerHost);
}
const elapsedRegex = /^Elapsed:: \d+(?:\.\d+)?ms/;
const buildVerboseLogRegEx = /^.+ \- /;
export enum HostOutputKind {
Log,
Diagnostic,
WatchDiagnostic
}
export interface HostOutputLog {
kind: HostOutputKind.Log;
expected: string;
caption?: string;
}
export interface HostOutputDiagnostic {
kind: HostOutputKind.Diagnostic;
diagnostic: Diagnostic | string;
}
export interface HostOutputWatchDiagnostic {
kind: HostOutputKind.WatchDiagnostic;
diagnostic: Diagnostic | string;
}
export type HostOutput = HostOutputLog | HostOutputDiagnostic | HostOutputWatchDiagnostic;
export function checkOutputErrors(
host: WatchedSystem,
expected: readonly HostOutput[],
disableConsoleClears?: boolean | undefined
) {
let screenClears = 0;
const outputs = host.getOutput();
assert.equal(outputs.length, expected.length, JSON.stringify(outputs));
let index = 0;
forEach(expected, expected => {
switch (expected.kind) {
case HostOutputKind.Log:
return assertLog(expected);
case HostOutputKind.Diagnostic:
return assertDiagnostic(expected);
case HostOutputKind.WatchDiagnostic:
return assertWatchDiagnostic(expected);
default:
return Debug.assertNever(expected);
}
});
assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears");
host.clearOutput();
function isDiagnostic(diagnostic: Diagnostic | string): diagnostic is Diagnostic {
return !!(diagnostic as Diagnostic).messageText;
}
function assertDiagnostic({ diagnostic }: HostOutputDiagnostic) {
const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic;
assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected));
index++;
}
function getCleanLogString(log: string) {
return log.replace(elapsedRegex, "").replace(buildVerboseLogRegEx, "");
}
function assertLog({ caption, expected }: HostOutputLog) {
const actual = outputs[index];
assert.equal(getCleanLogString(actual), getCleanLogString(expected), getOutputAtFailedMessage(caption || "Log", expected));
index++;
}
function assertWatchDiagnostic({ diagnostic }: HostOutputWatchDiagnostic) {
if (isString(diagnostic)) {
assert.equal(outputs[index], diagnostic, getOutputAtFailedMessage("Diagnostic", diagnostic));
}
else {
const expected = getWatchDiagnosticWithoutDate(diagnostic);
if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) {
assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`);
screenClears++;
}
assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected));
}
index++;
}
function getOutputAtFailedMessage(caption: string, expectedOutput: string) {
return `Expected ${caption}: ${JSON.stringify(expectedOutput)} at ${index} in ${JSON.stringify(outputs)}`;
}
function getWatchDiagnosticWithoutDate(diagnostic: Diagnostic) {
const newLines = contains(screenStartingMessageCodes, diagnostic.code)
? `${host.newLine}${host.newLine}`
: host.newLine;
return ` - ${flattenDiagnosticMessageText(diagnostic.messageText, host.newLine)}${newLines}`;
}
}
export function hostOutputLog(expected: string, caption?: string): HostOutputLog {
return { kind: HostOutputKind.Log, expected, caption };
}
export function hostOutputDiagnostic(diagnostic: Diagnostic | string): HostOutputDiagnostic {
return { kind: HostOutputKind.Diagnostic, diagnostic };
}
export function hostOutputWatchDiagnostic(diagnostic: Diagnostic | string): HostOutputWatchDiagnostic {
return { kind: HostOutputKind.WatchDiagnostic, diagnostic };
}
export function startingCompilationInWatchMode() {
return hostOutputWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode));
}
export function foundErrorsWatching(errors: readonly any[]) {
return hostOutputWatchDiagnostic(errors.length === 1 ?
createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes) :
createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length)
);
}
export function fileChangeDetected() {
return hostOutputWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
}
export function checkOutputErrorsInitial(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], disableConsoleClears?: boolean, logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
[
startingCompilationInWatchMode(),
...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")),
...map(errors, hostOutputDiagnostic),
foundErrorsWatching(errors)
],
disableConsoleClears
);
}
export function checkOutputErrorsIncremental(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
[
...map(logsBeforeWatchDiagnostic || emptyArray, expected => hostOutputLog(expected, "logsBeforeWatchDiagnostic")),
fileChangeDetected(),
...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")),
...map(errors, hostOutputDiagnostic),
foundErrorsWatching(errors)
],
disableConsoleClears
);
}
export function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
[
...map(logsBeforeWatchDiagnostic || emptyArray, expected => hostOutputLog(expected, "logsBeforeWatchDiagnostic")),
fileChangeDetected(),
...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")),
...map(errors, hostOutputDiagnostic),
],
disableConsoleClears
);
assert.equal(host.exitCode, expectedExitCode);
}
export function checkNormalBuildErrors(
host: WatchedSystem,
errors: readonly Diagnostic[] | readonly string[],
files: readonly ReportFileInError[],
reportErrorSummary?: boolean
) {
checkOutputErrors(
host,
[
...map(errors, hostOutputDiagnostic),
...reportErrorSummary ?
[hostOutputWatchDiagnostic(getErrorSummaryText(errors.length, files, host.newLine, host))] :
emptyArray
]
);
}
export function getDiagnosticMessageChain(message: DiagnosticMessage, args?: (string | number)[], next?: DiagnosticMessageChain[]): DiagnosticMessageChain {
let text = getLocaleSpecificMessage(message);
if (args?.length) {
@@ -293,24 +99,25 @@ namespace ts.tscWatch {
sys.checkTimeoutQueueLength(0);
}
export interface TscWatchCompileChange {
export type WatchOrSolution<T extends BuilderProgram> = void | SolutionBuilder<T> | WatchOfConfigFile<T> | WatchOfFilesAndCompilerOptions<T>;
export interface TscWatchCompileChange<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram> {
caption: string;
change: (sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void;
timeouts: (
sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles,
programs: readonly CommandLineProgram[],
watchOrSolution: ReturnType<typeof executeCommandLine>
watchOrSolution: WatchOrSolution<T>
) => void;
}
export interface TscWatchCheckOptions {
baselineSourceMap?: boolean;
baselineDependencies?: boolean;
}
export interface TscWatchCompileBase extends TscWatchCheckOptions {
export interface TscWatchCompileBase<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram> extends TscWatchCheckOptions {
scenario: string;
subScenario: string;
commandLineArgs: readonly string[];
changes: readonly TscWatchCompileChange[];
changes: readonly TscWatchCompileChange<T>[];
}
export interface TscWatchCompile extends TscWatchCompileBase {
sys: () => WatchedSystem;
@@ -355,23 +162,81 @@ namespace ts.tscWatch {
});
}
export interface Baseline {
export interface BaselineBase {
baseline: string[];
sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles;
oldSnap: SystemSnap;
}
export function createBaseline(system: WatchedSystem): Baseline {
const sys = TestFSWithWatch.changeToHostTrackingWrittenFiles(
fakes.patchHostForBuildInfoReadWrite(system)
);
export interface Baseline extends BaselineBase, CommandLineCallbacks {
}
export function createBaseline(system: WatchedSystem, modifySystem?: (sys: WatchedSystem) => void): Baseline {
const initialSys = fakes.patchHostForBuildInfoReadWrite(system);
modifySystem?.(initialSys);
const sys = TestFSWithWatch.changeToHostTrackingWrittenFiles(initialSys);
const baseline: string[] = [];
baseline.push("Input::");
sys.diff(baseline);
return { sys, baseline, oldSnap: sys.snap() };
const { cb, getPrograms } = commandLineCallbacks(sys);
return { sys, baseline, oldSnap: sys.snap(), cb, getPrograms };
}
export function applyChange(sys: Baseline["sys"], baseline: Baseline["baseline"], change: TscWatchCompileChange["change"], caption?: TscWatchCompileChange["caption"]) {
export function createSolutionBuilderWithWatchHostForBaseline(sys: WatchedSystem, cb: ExecuteCommandLineCallbacks) {
const host = createSolutionBuilderWithWatchHost(sys,
/*createProgram*/ undefined,
createDiagnosticReporter(sys, /*pretty*/ true),
createBuilderStatusReporter(sys, /*pretty*/ true),
createWatchStatusReporter(sys, /*pretty*/ true)
);
host.afterProgramEmitAndDiagnostics = cb;
host.afterEmitBundle = cb;
return host;
}
interface CreateWatchCompilerHostOfConfigFileForBaseline<T extends BuilderProgram> extends CreateWatchCompilerHostOfConfigFileInput<T> {
system: WatchedSystem,
cb: ExecuteCommandLineCallbacks;
}
export function createWatchCompilerHostOfConfigFileForBaseline<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(
input: CreateWatchCompilerHostOfConfigFileForBaseline<T>
) {
const host = createWatchCompilerHostOfConfigFile({
...input,
reportDiagnostic: createDiagnosticReporter(input.system, /*pretty*/ true),
reportWatchStatus: createWatchStatusReporter(input.system, /*pretty*/ true),
});
updateWatchHostForBaseline(host, input.cb);
return host;
}
interface CreateWatchCompilerHostOfFilesAndCompilerOptionsForBaseline<T extends BuilderProgram> extends CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T> {
system: WatchedSystem,
cb: ExecuteCommandLineCallbacks;
}
export function createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(
input: CreateWatchCompilerHostOfFilesAndCompilerOptionsForBaseline<T>
) {
const host = createWatchCompilerHostOfFilesAndCompilerOptions({
...input,
reportDiagnostic: createDiagnosticReporter(input.system, /*pretty*/ true),
reportWatchStatus: createWatchStatusReporter(input.system, /*pretty*/ true),
});
updateWatchHostForBaseline(host, input.cb);
return host;
}
function updateWatchHostForBaseline<T extends BuilderProgram>(host: WatchCompilerHost<T>, cb: ExecuteCommandLineCallbacks) {
const emitFilesAndReportErrors = host.afterProgramCreate!;
host.afterProgramCreate = builderProgram => {
emitFilesAndReportErrors.call(host, builderProgram);
cb(builderProgram);
};
return host;
}
export function applyChange(sys: BaselineBase["sys"], baseline: BaselineBase["baseline"], change: TscWatchCompileChange["change"], caption?: TscWatchCompileChange["caption"]) {
const oldSnap = sys.snap();
baseline.push(`Change::${caption ? " " + caption : ""}`, "");
change(sys);
@@ -380,17 +245,17 @@ namespace ts.tscWatch {
return sys.snap();
}
export interface RunWatchBaseline extends Baseline, TscWatchCompileBase {
export interface RunWatchBaseline<T extends BuilderProgram> extends BaselineBase, TscWatchCompileBase<T> {
sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles;
getPrograms: () => readonly CommandLineProgram[];
watchOrSolution: ReturnType<typeof executeCommandLine>;
watchOrSolution: WatchOrSolution<T>;
}
export function runWatchBaseline({
export function runWatchBaseline<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({
scenario, subScenario, commandLineArgs,
getPrograms, sys, baseline, oldSnap,
baselineSourceMap, baselineDependencies,
changes, watchOrSolution
}: RunWatchBaseline) {
}: RunWatchBaseline<T>) {
baseline.push(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}`);
let programs = watchBaseline({
baseline,
@@ -428,7 +293,7 @@ namespace ts.tscWatch {
});
}
export interface WatchBaseline extends Baseline, TscWatchCheckOptions {
export interface WatchBaseline extends BaselineBase, TscWatchCheckOptions {
oldPrograms: readonly (CommandLineProgram | undefined)[];
getPrograms: () => readonly CommandLineProgram[];
}

View File

@@ -28,12 +28,11 @@ namespace ts.tscWatch {
{ subScenario, files, optionsToExtend, modifyFs }: VerifyIncrementalWatchEmitInput,
incremental: boolean
) {
const { sys, baseline, oldSnap } = createBaseline(createWatchedSystem(files(), { currentDirectory: project }));
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(files(), { currentDirectory: project }));
if (incremental) sys.exit = exitCode => sys.exitCode = exitCode;
const argsToPass = [incremental ? "-i" : "-w", ...(optionsToExtend || emptyArray)];
baseline.push(`${sys.getExecutingFilePath()} ${argsToPass.join(" ")}`);
let oldPrograms: readonly CommandLineProgram[] = emptyArray;
const { cb, getPrograms } = commandLineCallbacks(sys);
build(oldSnap);
if (modifyFs) {

View File

@@ -511,7 +511,7 @@ export class A {
}]
});
it("correctly migrate files between projects", () => {
it("two watch programs are not affected by each other", () => {
const file1 = {
path: "/a/b/f1.ts",
content: `
@@ -526,16 +526,46 @@ export class A {
path: "/a/d/f3.ts",
content: "export let y = 1;"
};
const host = createWatchedSystem([file1, file2, file3]);
const watch = createWatchOfFilesAndCompilerOptions([file2.path, file3.path], host);
checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [file2.path, file3.path]);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, file1, file2, file3]));
const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
rootFiles: [file2.path, file3.path],
system: sys,
options: { allowNonTsExtensions: true },
cb,
watchOptions: undefined
});
createWatchProgram(host);
baseline.push(`${sys.getExecutingFilePath()} --w ${file2.path} ${file3.path}`);
watchBaseline({
baseline,
getPrograms,
oldPrograms: emptyArray,
sys,
oldSnap,
});
const watch2 = createWatchOfFilesAndCompilerOptions([file1.path], host);
checkProgramActualFiles(watch2.getCurrentProgram().getProgram(), [file1.path, file2.path, file3.path]);
const {cb: cb2, getPrograms: getPrograms2 } = commandLineCallbacks(sys);
const oldSnap2 = sys.snap();
baseline.push("createing separate watcher");
createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
rootFiles:[file1.path],
system: sys,
options: { allowNonTsExtensions: true },
cb: cb2,
watchOptions: undefined
}));
watchBaseline({
baseline,
getPrograms: getPrograms2,
oldPrograms: emptyArray,
sys,
oldSnap: oldSnap2,
});
// Previous program shouldnt be updated
checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [file2.path, file3.path]);
host.checkTimeoutQueueLength(0);
sys.checkTimeoutQueueLength(0);
baseline.push(`First program is not updated:: ${getPrograms() === emptyArray}`);
baseline.push(`Second program is not updated:: ${getPrograms2() === emptyArray}`);
Harness.Baseline.runBaseline(`tscWatch/${scenario}/two-watch-programs-are-not-affected-by-each-other.js`, baseline.join("\r\n"));
});
verifyTscWatch({
@@ -1025,9 +1055,25 @@ declare const eval: any`
path: "/a/compile",
content: "let x = 1"
};
const host = createWatchedSystem([f, libFile]);
const watch = createWatchOfFilesAndCompilerOptions([f.path], host, { allowNonTsExtensions: true });
checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [f.path, libFile.path]);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([f, libFile]));
const watch = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
rootFiles: [f.path],
system: sys,
options: { allowNonTsExtensions: true },
cb,
watchOptions: undefined
}));
runWatchBaseline({
scenario,
subScenario: "should support files without extensions",
commandLineArgs: ["--w", f.path],
sys,
baseline,
oldSnap,
getPrograms,
changes: emptyArray,
watchOrSolution: watch
});
});
verifyTscWatch({

View File

@@ -1,7 +1,7 @@
namespace ts.tscWatch {
describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => {
const scenario = "resolutionCache";
it("works", () => {
it("caching works", () => {
const root = {
path: "/a/d/f0.ts",
content: `import {x} from "f1"`
@@ -11,80 +11,76 @@ namespace ts.tscWatch {
content: `foo()`
};
const files = [root, imported, libFile];
const host = createWatchedSystem(files);
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
const f1IsNotModule = getDiagnosticOfFileFromProgram(watch.getCurrentProgram().getProgram(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path);
const cannotFindFoo = getDiagnosticOfFileFromProgram(watch.getCurrentProgram().getProgram(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo");
// ensure that imported file was found
checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile]));
const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
rootFiles: [root.path],
system: sys,
options: { module: ModuleKind.AMD },
cb,
watchOptions: undefined
});
const originalFileExists = host.fileExists;
{
const newContent = `import {x} from "f1"
var x: string = 1;`;
root.content = newContent;
host.writeFile(root.path, root.content);
// patch fileExists to make sure that disk is not touched
host.fileExists = notImplemented;
// trigger synchronization to make sure that import will be fetched from the cache
host.runQueuedTimeoutCallbacks();
// ensure file has correct number of errors after edit
checkOutputErrorsIncremental(host, [
f1IsNotModule,
getDiagnosticOfFileFromProgram(watch.getCurrentProgram().getProgram(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, "number", "string"),
cannotFindFoo
]);
}
{
let fileExistsIsCalled = false;
host.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsIsCalled = true;
assert.isTrue(fileName.indexOf("/f2.") !== -1);
return originalFileExists.call(host, fileName);
};
root.content = `import {x} from "f2"`;
host.writeFile(root.path, root.content);
// trigger synchronization to make sure that system will try to find 'f2' module on disk
host.runQueuedTimeoutCallbacks();
// ensure file has correct number of errors after edit
checkOutputErrorsIncremental(host, [
getDiagnosticModuleNotFoundOfFile(watch.getCurrentProgram().getProgram(), root, "f2")
]);
assert.isTrue(fileExistsIsCalled);
}
{
let fileExistsCalled = false;
host.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsCalled = true;
assert.isTrue(fileName.indexOf("/f1.") !== -1);
return originalFileExists.call(host, fileName);
};
const newContent = `import {x} from "f1"`;
root.content = newContent;
host.writeFile(root.path, root.content);
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]);
assert.isTrue(fileExistsCalled);
}
const watch = createWatchProgram(host);
let fileExistsIsCalled = false;
runWatchBaseline({
scenario: "resolutionCache",
subScenario: "caching works",
commandLineArgs: ["--w", root.path],
sys,
baseline,
oldSnap,
getPrograms,
changes: [
{
caption: "Adding text doesnt re-resole the imports",
change: sys => {
// patch fileExists to make sure that disk is not touched
host.fileExists = notImplemented;
sys.writeFile(root.path, `import {x} from "f1"
var x: string = 1;`);
},
timeouts: runQueuedTimeoutCallbacks
},
{
caption: "Resolves f2",
change: sys => {
host.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsIsCalled = true;
assert.isTrue(fileName.indexOf("/f2.") !== -1);
return originalFileExists.call(host, fileName);
};
sys.writeFile(root.path, `import {x} from "f2"`);
},
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
assert.isTrue(fileExistsIsCalled);
},
},
{
caption: "Resolve f1",
change: sys => {
fileExistsIsCalled = false;
host.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsIsCalled = true;
assert.isTrue(fileName.indexOf("/f1.") !== -1);
return originalFileExists.call(host, fileName);
};
sys.writeFile(root.path, `import {x} from "f1"`);
},
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
assert.isTrue(fileExistsIsCalled);
}
},
],
watchOrSolution: watch
});
});
it("loads missing files from disk", () => {
@@ -98,10 +94,15 @@ namespace ts.tscWatch {
content: `export const y = 1;`
};
const files = [root, libFile];
const host = createWatchedSystem(files);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, libFile]));
const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
rootFiles: [root.path],
system: sys,
options: { module: ModuleKind.AMD },
cb,
watchOptions: undefined
});
const originalFileExists = host.fileExists;
let fileExistsCalledForBar = false;
host.fileExists = fileName => {
if (fileName === "lib.d.ts") {
@@ -114,21 +115,30 @@ namespace ts.tscWatch {
return originalFileExists.call(host, fileName);
};
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
const watch = createWatchProgram(host);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
checkOutputErrorsInitial(host, [
getDiagnosticModuleNotFoundOfFile(watch.getCurrentProgram().getProgram(), root, "bar")
]);
fileExistsCalledForBar = false;
root.content = `import {y} from "bar"`;
host.writeFile(root.path, root.content);
host.writeFile(imported.path, imported.content);
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, emptyArray);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
runWatchBaseline({
scenario: "resolutionCache",
subScenario: "loads missing files from disk",
commandLineArgs: ["--w", root.path],
sys,
baseline,
oldSnap,
getPrograms,
changes: [{
caption: "write imported file",
change: sys => {
fileExistsCalledForBar = false;
sys.writeFile(root.path,`import {y} from "bar"`);
sys.writeFile(imported.path, imported.content);
},
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
}
}],
watchOrSolution: watch
});
});
it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => {
@@ -142,7 +152,14 @@ namespace ts.tscWatch {
content: `export const y = 1;export const x = 10;`
};
const host = createWatchedSystem([root, libFile, imported]);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile]));
const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
rootFiles: [root.path],
system: sys,
options: { module: ModuleKind.AMD },
cb,
watchOptions: undefined
});
const originalFileExists = host.fileExists;
let fileExistsCalledForBar = false;
host.fileExists = fileName => {
@@ -154,26 +171,43 @@ namespace ts.tscWatch {
}
return originalFileExists.call(host, fileName);
};
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
const watch = createWatchProgram(host);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
checkOutputErrorsInitial(host, emptyArray);
fileExistsCalledForBar = false;
host.deleteFile(imported.path);
host.runQueuedTimeoutCallbacks();
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
checkOutputErrorsIncremental(host, [
getDiagnosticModuleNotFoundOfFile(watch.getCurrentProgram().getProgram(), root, "bar")
]);
fileExistsCalledForBar = false;
host.writeFile(imported.path, imported.content);
host.checkTimeoutQueueLengthAndRun(1); // Scheduled invalidation of resolutions
host.checkTimeoutQueueLengthAndRun(1); // Actual update
checkOutputErrorsIncremental(host, emptyArray);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
runWatchBaseline({
scenario: "resolutionCache",
subScenario: "should compile correctly when resolved module goes missing and then comes back",
commandLineArgs: ["--w", root.path],
sys,
baseline,
oldSnap,
getPrograms,
changes: [
{
caption: "Delete imported file",
change: sys => {
fileExistsCalledForBar = false;
sys.deleteFile(imported.path);
},
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
},
},
{
caption: "Create imported file",
change: sys => {
fileExistsCalledForBar = false;
sys.writeFile(imported.path, imported.content);
},
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1); // Scheduled invalidation of resolutions
sys.checkTimeoutQueueLengthAndRun(1); // Actual update
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
},
},
],
watchOrSolution: watch
});
});
verifyTscWatch({
@@ -402,7 +436,7 @@ declare namespace myapp {
change: noop,
timeouts: (sys, [[oldProgram, oldBuilderProgram]], watchorSolution) => {
sys.checkTimeoutQueueLength(0);
const newProgram = (watchorSolution as Watch).getProgram();
const newProgram = (watchorSolution as WatchOfConfigFile<EmitAndSemanticDiagnosticsBuilderProgram>).getProgram();
assert.strictEqual(newProgram, oldBuilderProgram, "No change so builder program should be same");
assert.strictEqual(newProgram.getProgram(), oldProgram, "No change so program should be same");
}

View File

@@ -4,26 +4,37 @@ namespace ts.tscWatch {
interface VerifyWatchInput {
files: readonly TestFSWithWatch.FileOrFolderOrSymLink[];
config: string;
expectedProgramFiles: readonly string[];
subScenario: string;
}
function verifyWatch(
{ files, config, expectedProgramFiles }: VerifyWatchInput,
alreadyBuilt: boolean
) {
const sys = createWatchedSystem(files);
if (alreadyBuilt) {
const solutionBuilder = createSolutionBuilder(sys, [config], {});
solutionBuilder.build();
solutionBuilder.close();
sys.clearOutput();
}
const host = createWatchCompilerHostOfConfigFile({
function verifyWatch({ files, config, subScenario }: VerifyWatchInput, alreadyBuilt: boolean) {
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(
createWatchedSystem(files),
alreadyBuilt ? sys => {
const solutionBuilder = createSolutionBuilder(sys, [config], {});
solutionBuilder.build();
solutionBuilder.close();
sys.clearOutput();
} : undefined
);
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config,
system: sys
system: sys,
cb,
});
host.useSourceOfProjectReferenceRedirect = returnTrue;
const watch = createWatchProgram(host);
checkProgramActualFiles(watch.getCurrentProgram().getProgram(), expectedProgramFiles);
runWatchBaseline({
scenario: "sourceOfProjectReferenceRedirect",
subScenario: `${subScenario}${alreadyBuilt ? " when solution is already built" : ""}`,
commandLineArgs: ["--w", "--p", config],
sys,
baseline,
oldSnap,
getPrograms,
changes: emptyArray,
watchOrSolution: watch
});
}
function verifyScenario(input: () => VerifyWatchInput) {
@@ -48,7 +59,7 @@ namespace ts.tscWatch {
return {
files: [{ path: libFile.path, content: libContent }, baseConfig, coreTs, coreConfig, animalTs, dogTs, indexTs, animalsConfig],
config: animalsConfig.path,
expectedProgramFiles: [libFile.path, indexTs.path, dogTs.path, animalTs.path, coreTs.path]
subScenario: "with simple project"
};
});
});
@@ -60,6 +71,7 @@ namespace ts.tscWatch {
bFoo: File;
bBar: File;
bSymlink: SymLink;
subScenario: string;
}
function verifySymlinkScenario(packages: () => Packages) {
describe("when preserveSymlinks is turned off", () => {
@@ -72,13 +84,13 @@ namespace ts.tscWatch {
function verifySymlinkScenarioWorker(packages: () => Packages, extraOptions: CompilerOptions) {
verifyScenario(() => {
const { bPackageJson, aTest, bFoo, bBar, bSymlink } = packages();
const { bPackageJson, aTest, bFoo, bBar, bSymlink, subScenario } = packages();
const aConfig = config("A", extraOptions, ["../B"]);
const bConfig = config("B", extraOptions);
return {
files: [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink],
config: aConfig.path,
expectedProgramFiles: [libFile.path, aTest.path, bFoo.path, bBar.path]
subScenario: `${subScenario}${extraOptions.preserveSymlinks ? " with preserveSymlinks" : ""}`
};
});
}
@@ -126,7 +138,8 @@ bar();
bSymlink: {
path: `${projectRoot}/node_modules/${scope}b`,
symLink: `${projectRoot}/packages/B`
}
},
subScenario: `when packageJson has types field${scope ? " with scoped package" : ""}`
}));
});
@@ -146,7 +159,8 @@ bar();
bSymlink: {
path: `${projectRoot}/node_modules/${scope}b`,
symLink: `${projectRoot}/packages/B`
}
},
subScenario: `when referencing file from subFolder${scope ? " with scoped package" : ""}`
}));
});
}

View File

@@ -18,15 +18,18 @@ namespace ts.tscWatch {
};
it("verify that module resolution with json extension works when returned without extension", () => {
const files = [libFile, mainFile, config, settingsJson];
const host = createWatchedSystem(files, { currentDirectory: projectRoot });
const compilerHost = createWatchCompilerHostOfConfigFile({
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(
[libFile, mainFile, config, settingsJson],
{ currentDirectory: projectRoot }),
);
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
system: host
system: sys,
cb,
});
const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path);
compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => {
const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost);
const parsedCommandResult = parseJsonConfigFileContent(configFileJson, sys, config.path);
host.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => {
const result = resolveModuleName(m, containingFile, parsedCommandResult.options, host);
const resolvedModule = result.resolvedModule!;
return {
resolvedFileName: resolvedModule.resolvedFileName,
@@ -34,40 +37,62 @@ namespace ts.tscWatch {
originalFileName: resolvedModule.originalPath,
};
});
const watch = createWatchProgram(compilerHost);
const program = watch.getCurrentProgram().getProgram();
checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]);
const watch = createWatchProgram(host);
runWatchBaseline({
scenario: "watchApi",
subScenario: "verify that module resolution with json extension works when returned without extension",
commandLineArgs: ["--w", "--p", config.path],
sys,
baseline,
oldSnap,
getPrograms,
changes: emptyArray,
watchOrSolution: watch
});
});
});
describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to watch status reporter", () => {
const configFileJson: any = {
compilerOptions: { module: "commonjs" },
files: ["index.ts"]
};
const config: File = {
path: `${projectRoot}/tsconfig.json`,
content: JSON.stringify(configFileJson)
};
const mainFile: File = {
path: `${projectRoot}/index.ts`,
content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}"
};
it("verify that the error count is correctly passed down to the watch status reporter", () => {
const files = [libFile, mainFile, config];
const host = createWatchedSystem(files, { currentDirectory: projectRoot });
const config: File = {
path: `${projectRoot}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { module: "commonjs" },
files: ["index.ts"]
})
};
const mainFile: File = {
path: `${projectRoot}/index.ts`,
content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}"
};
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(
[libFile, mainFile, config],
{ currentDirectory: projectRoot }),
);
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
system: sys,
cb,
});
const existing = host.onWatchStatusChange!;
let watchedErrorCount;
const reportWatchStatus: WatchStatusReporter = (_, __, ___, errorCount) => {
host.onWatchStatusChange = (diagnostic, newLine, options, errorCount) => {
existing.call(host, diagnostic, newLine, options, errorCount);
watchedErrorCount = errorCount;
};
const compilerHost = createWatchCompilerHostOfConfigFile({
configFileName: config.path,
system: host,
reportWatchStatus
});
createWatchProgram(compilerHost);
const watch = createWatchProgram(host);
assert.equal(watchedErrorCount, 2, "The error count was expected to be 2 for the file change");
runWatchBaseline({
scenario: "watchApi",
subScenario: "verify that the error count is correctly passed down to the watch status reporter",
commandLineArgs: ["--w", "--p", config.path],
sys,
baseline,
oldSnap,
getPrograms,
changes: emptyArray,
watchOrSolution: watch
});
});
});
@@ -81,16 +106,33 @@ namespace ts.tscWatch {
path: `${projectRoot}/main.ts`,
content: "const x = 10;"
};
const sys = createWatchedSystem([config, mainFile, libFile]);
const watchCompilerHost = createWatchCompilerHost(config.path, {}, sys);
watchCompilerHost.setTimeout = undefined;
watchCompilerHost.clearTimeout = undefined;
const watch = createWatchProgram(watchCompilerHost);
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, libFile.path]);
// Write new file
const barPath = `${projectRoot}/bar.ts`;
sys.writeFile(barPath, "const y =10;");
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, barPath, libFile.path]);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([config, mainFile, libFile]));
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
system: sys,
cb,
});
host.setTimeout = undefined;
host.clearTimeout = undefined;
const watch = createWatchProgram(host);
runWatchBaseline({
scenario: "watchApi",
subScenario: "without timesouts on host program gets updated",
commandLineArgs: ["--w", "--p", config.path],
sys,
baseline,
oldSnap,
getPrograms,
changes: [{
caption: "Write a file",
change: sys => sys.writeFile(`${projectRoot}/bar.ts`, "const y =10;"),
timeouts: sys => {
sys.checkTimeoutQueueLength(0);
watch.getProgram();
}
}],
watchOrSolution: watch
});
});
});
@@ -108,139 +150,230 @@ namespace ts.tscWatch {
path: `${projectRoot}/other.vue`,
content: ""
};
const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
const watchCompilerHost = createWatchCompilerHost(
config.path,
{ allowNonTsExtensions: true },
sys,
/*createProgram*/ undefined,
/*reportDiagnostics*/ undefined,
/*reportWatchStatus*/ undefined,
/*watchOptionsToExtend*/ undefined,
[{ extension: ".vue", isMixedContent: true, scriptKind: ScriptKind.Deferred }]
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(
createWatchedSystem([config, mainFile, otherFile, libFile])
);
const watch = createWatchProgram(watchCompilerHost);
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]);
const other2 = `${projectRoot}/other2.vue`;
sys.writeFile(other2, otherFile.content);
checkSingleTimeoutQueueLengthAndRun(sys);
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path, other2]);
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
optionsToExtend: { allowNonTsExtensions: true },
extraFileExtensions: [{ extension: ".vue", isMixedContent: true, scriptKind: ScriptKind.Deferred }],
system: sys,
cb,
});
const watch = createWatchProgram(host);
runWatchBaseline({
scenario: "watchApi",
subScenario: "extraFileExtensions are supported",
commandLineArgs: ["--w", "--p", config.path],
sys,
baseline,
oldSnap,
getPrograms,
changes: [{
caption: "Write a file",
change: sys => sys.writeFile(`${projectRoot}/other2.vue`, otherFile.content),
timeouts: checkSingleTimeoutQueueLengthAndRun,
}],
watchOrSolution: watch
});
});
});
describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => {
function getWatch<T extends BuilderProgram>(config: File, optionsToExtend: CompilerOptions | undefined, sys: System, createProgram: CreateProgram<T>) {
const watchCompilerHost = createWatchCompilerHost(config.path, optionsToExtend, sys, createProgram);
return createWatchProgram(watchCompilerHost);
}
function setup<T extends BuilderProgram>(createProgram: CreateProgram<T>, configText: string) {
function createSystem(configText: string, mainText: string) {
const config: File = {
path: `${projectRoot}/tsconfig.json`,
content: configText
};
const mainFile: File = {
path: `${projectRoot}/main.ts`,
content: "export const x = 10;"
content: mainText
};
const otherFile: File = {
path: `${projectRoot}/other.ts`,
content: "export const y = 10;"
};
const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
const watch = getWatch(config, { noEmit: true }, sys, createProgram);
return { sys, watch, mainFile, otherFile, config };
return {
...createBaseline(createWatchedSystem([config, mainFile, otherFile, libFile])),
config,
mainFile,
otherFile,
};
}
function verifyOutputs(sys: System, emitSys: System) {
for (const output of [`${projectRoot}/main.js`, `${projectRoot}/main.d.ts`, `${projectRoot}/other.js`, `${projectRoot}/other.d.ts`, `${projectRoot}/tsconfig.tsbuildinfo`]) {
assert.strictEqual(sys.readFile(output), emitSys.readFile(output), `Output file text for ${output}`);
}
}
function verifyBuilder<T extends BuilderProgram, U extends BuilderProgram>(config: File, sys: System, emitSys: System, createProgram: CreateProgram<T>, createEmitProgram: CreateProgram<U>, optionsToExtend?: CompilerOptions) {
const watch = getWatch(config, /*optionsToExtend*/ optionsToExtend, sys, createProgram);
const emitWatch = getWatch(config, /*optionsToExtend*/ optionsToExtend, emitSys, createEmitProgram);
verifyOutputs(sys, emitSys);
function createWatch<T extends BuilderProgram>(
baseline: string[],
config: File,
optionsToExtend: CompilerOptions | undefined,
sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles,
createProgram: CreateProgram<T>
) {
const { cb, getPrograms } = commandLineCallbacks(sys);
baseline.push(`tsc --w${optionsToExtend?.noEmit ? " --noEmit" : ""}`);
const oldSnap = sys.snap();
const host = createWatchCompilerHostOfConfigFileForBaseline<T>({
configFileName: config.path,
optionsToExtend,
createProgram,
system: sys,
cb,
});
const watch = createWatchProgram(host);
watchBaseline({
baseline,
getPrograms,
oldPrograms: emptyArray,
sys,
oldSnap,
});
watch.close();
emitWatch.close();
}
function verifyOutputs(baseline: string[], sys: System, emitSys: System) {
baseline.push("Checking if output is same as EmitAndSemanticDiagnosticsBuilderProgram::");
for (const output of [`${projectRoot}/main.js`, `${projectRoot}/main.d.ts`, `${projectRoot}/other.js`, `${projectRoot}/other.d.ts`, `${projectRoot}/tsconfig.tsbuildinfo`]) {
baseline.push(`Output file text for ${output} is same:: ${sys.readFile(output) === emitSys.readFile(output)}`);
}
baseline.push("");
}
function createSystemForBuilderTest(configText: string, mainText: string) {
const result = createSystem(configText, mainText);
const { sys: emitSys, baseline: emitBaseline } = createSystem(configText, mainText);
return { ...result, emitSys, emitBaseline };
}
function applyChangeForBuilderTest(
baseline: string[],
emitBaseline: string[],
sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles,
emitSys: TestFSWithWatch.TestServerHostTrackingWrittenFiles,
change: (sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void,
caption: string
) {
// Change file
applyChange(sys, baseline, change, caption);
applyChange(emitSys, emitBaseline, change, caption);
}
function verifyBuilder<T extends BuilderProgram>(
baseline: string[],
emitBaseline: string[],
config: File,
sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles,
emitSys: TestFSWithWatch.TestServerHostTrackingWrittenFiles,
createProgram: CreateProgram<T>,
optionsToExtend?: CompilerOptions) {
createWatch(baseline, config, optionsToExtend, sys, createProgram);
createWatch(emitBaseline, config, optionsToExtend, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram);
verifyOutputs(baseline, sys, emitSys);
}
it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => {
const { sys, watch, mainFile, otherFile } = setup(createSemanticDiagnosticsBuilderProgram, "{}");
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]);
sys.appendFile(mainFile.path, "\n// SomeComment");
sys.runQueuedTimeoutCallbacks();
const program = watch.getProgram().getProgram();
assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(mainFile.path)), []);
// Should not retrieve diagnostics for other file thats not changed
assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(otherFile.path)), /*expected*/ undefined);
const { sys, baseline, oldSnap, cb, getPrograms, config, mainFile } = createSystem("{}", "export const x = 10;");
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
optionsToExtend: { noEmit: true },
createProgram: createSemanticDiagnosticsBuilderProgram,
system: sys,
cb,
});
const watch = createWatchProgram(host);
runWatchBaseline({
scenario: "watchApi",
subScenario: "verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram",
commandLineArgs: ["--w", "--p", config.path],
sys,
baseline,
oldSnap,
getPrograms,
changes: [{
caption: "Modify a file",
change: sys => sys.appendFile(mainFile.path, "\n// SomeComment"),
timeouts: runQueuedTimeoutCallbacks,
}],
watchOrSolution: watch
});
});
it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => {
const configText = JSON.stringify({ compilerOptions: { composite: true } });
const { sys, watch, config, mainFile } = setup(createSemanticDiagnosticsBuilderProgram, configText);
const { sys: emitSys, watch: emitWatch } = setup(createEmitAndSemanticDiagnosticsBuilderProgram, configText);
verifyOutputs(sys, emitSys);
describe("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => {
let baseline: string[];
let emitBaseline: string[];
before(() => {
const configText = JSON.stringify({ compilerOptions: { composite: true } });
const mainText = "export const x = 10;";
const result = createSystemForBuilderTest(configText, mainText);
baseline = result.baseline;
emitBaseline = result.emitBaseline;
const { sys, config, mainFile, emitSys } = result;
watch.close();
emitWatch.close();
// No Emit
verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true });
// Emit on both sys should result in same output
verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
// Emit on both sys should result in same output
verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram);
// Change file
sys.appendFile(mainFile.path, "\n// SomeComment");
emitSys.appendFile(mainFile.path, "\n// SomeComment");
// Change file
applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.appendFile(mainFile.path, "\n// SomeComment"), "Add comment");
// Verify noEmit results in same output
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true });
// Verify noEmit results in same output
verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, { noEmit: true });
// Emit on both sys should result in same output
verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
// Emit on both sys should result in same output
verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram);
// Change file
sys.appendFile(mainFile.path, "\n// SomeComment");
emitSys.appendFile(mainFile.path, "\n// SomeComment");
// Change file
applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.appendFile(mainFile.path, "\n// SomeComment"), "Add comment");
// Emit on both the builders should result in same files
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
// Emit on both the builders should result in same files
verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram);
});
after(() => {
baseline = undefined!;
emitBaseline = undefined!;
});
it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => {
Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmit-with-composite-with-semantic-builder.js`, baseline.join("\r\n"));
});
it("baseline in createEmitAndSemanticDiagnosticsBuilderProgram:: noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => {
Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmit-with-composite-with-emit-builder.js`, emitBaseline.join("\r\n"));
});
});
it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => {
const config: File = {
path: `${projectRoot}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true } })
};
const mainFile: File = {
path: `${projectRoot}/main.ts`,
content: "export const x: string = 10;"
};
const otherFile: File = {
path: `${projectRoot}/other.ts`,
content: "export const y = 10;"
};
const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
const emitSys = createWatchedSystem([config, mainFile, otherFile, libFile]);
describe("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => {
let baseline: string[];
let emitBaseline: string[];
before(() => {
const configText = JSON.stringify({ compilerOptions: { composite: true, noEmitOnError: true } });
const mainText = "export const x: string = 10;";
const result = createSystemForBuilderTest(configText, mainText);
baseline = result.baseline;
emitBaseline = result.emitBaseline;
const { sys, config, mainFile, emitSys } = result;
// Verify noEmit results in same output
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
// Verify noEmit results in same output
verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram);
// Change file
sys.appendFile(mainFile.path, "\n// SomeComment");
emitSys.appendFile(mainFile.path, "\n// SomeComment");
// Change file
applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.appendFile(mainFile.path, "\n// SomeComment"), "Add comment");
// Verify noEmit results in same output
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
// Verify noEmit results in same output
verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram);
// Fix error
const fixed = "export const x = 10;";
sys.appendFile(mainFile.path, fixed);
emitSys.appendFile(mainFile.path, fixed);
// Fix error
const fixed = "export const x = 10;";
applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.writeFile(mainFile.path, fixed), "Fix error");
// Emit on both the builders should result in same files
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
// Emit on both the builders should result in same files
verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram);
});
it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => {
Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmitOnError-with-composite-with-semantic-builder.js`, baseline.join("\r\n"));
});
it("baseline in createEmitAndSemanticDiagnosticsBuilderProgram:: noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => {
Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmitOnError-with-composite-with-emit-builder.js`, emitBaseline.join("\r\n"));
});
});
});
@@ -282,9 +415,10 @@ namespace ts.tscWatch {
};
const system = createWatchedSystem([config1, class1, class1Dts, config2, class2, libFile]);
const baseline = createBaseline(system);
const compilerHost = createWatchCompilerHostOfConfigFile({
configFileName: config2.path,
const compilerHost = createWatchCompilerHostOfConfigFileForBaseline({
cb: baseline.cb,
system,
configFileName: config2.path,
optionsToExtend: { extendedDiagnostics: true }
});
compilerHost.useSourceOfProjectReferenceRedirect = useSourceOfProjectReferenceRedirect;
@@ -312,7 +446,6 @@ namespace ts.tscWatch {
subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine",
commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"],
...baseline,
getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]],
changes: [
{
caption: "Add class3 to project1",
@@ -344,7 +477,6 @@ namespace ts.tscWatch {
subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect",
commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"],
...baseline,
getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]],
changes: [
{
caption: "Add class3 to project1",