mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-10 18:04:18 -05:00
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:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",`, "");
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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`
|
||||
]))
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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" : ""}`
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user