Detangle unittests to reference helpers directly (#51450)

* Remove unnecessary redirection now that we are not in namespaces

* Move the tsc helpers in one place

* Move virtual file system with watch in unittests folder instead of harness since harness doesnt use it anyways

* Replace protocol and commandNames

* Revert incorrect pick
This commit is contained in:
Sheetal Nandi
2022-11-09 10:02:40 -08:00
committed by GitHub
parent 19091abda1
commit 6e0a62e8dd
149 changed files with 8114 additions and 8040 deletions

View File

@@ -1,3 +0,0 @@
/* Generated file to emulate the ts.TestFSWithWatch namespace. */
export * from "../virtualFileSystemWithWatch";

View File

@@ -8,6 +8,4 @@ export * from "../../typingsInstallerCore/_namespaces/ts";
export * from "../../deprecatedCompat/_namespaces/ts";
export * from "../harnessGlobals";
import * as server from "./ts.server";
export { server };
import * as TestFSWithWatch from "./ts.TestFSWithWatch";
export { TestFSWithWatch };
export { server };

View File

@@ -42,6 +42,8 @@ export interface DiffOptions {
baseIsNotShadowRoot?: boolean;
}
export const timeIncrements = 1000;
/**
* Represents a virtual POSIX-like file system.
*/
@@ -65,7 +67,7 @@ export class FileSystem {
private _dirStack: string[] | undefined;
constructor(ignoreCase: boolean, options: FileSystemOptions = {}) {
const { time = ts.TestFSWithWatch.timeIncrements, files, meta } = options;
const { time = timeIncrements, files, meta } = options;
this.ignoreCase = ignoreCase;
this.stringComparer = this.ignoreCase ? vpath.compareCaseInsensitive : vpath.compareCaseSensitive;
this._time = time;
@@ -178,7 +180,7 @@ export class FileSystem {
this._time = value;
}
else if (!this.isReadonly) {
this._time += ts.TestFSWithWatch.timeIncrements;
this._time += timeIncrements;
}
return this._time;
}

View File

@@ -1,3 +0,0 @@
/* Generated file to emulate the ts.projectSystem namespace. */
export * from "../unittests/tsserver/helpers";

View File

@@ -10,13 +10,5 @@ export * from "../../typingsInstallerCore/_namespaces/ts";
export * from "../../deprecatedCompat/_namespaces/ts";
export * from "../../harness/_namespaces/ts";
export * from "../../loggedIO/_namespaces/ts";
import * as tscWatch from "./ts.tscWatch";
export { tscWatch };
import * as server from "./ts.server";
export { server };
import * as projectSystem from "./ts.projectSystem";
export { projectSystem };
export * from "../unittests/helpers";
export * from "../unittests/services/extract/helpers";
export * from "../unittests/tsbuild/helpers";
export * from "../unittests/tsc/helpers";
export { server };

View File

@@ -1,3 +0,0 @@
/* Generated file to emulate the ts.tscWatch namespace. */
export * from "../unittests/tscWatch/helpers";

View File

@@ -1,14 +1,15 @@
import * as ts from "../_namespaces/ts";
import { NamedSourceText, newProgram, ProgramWithSourceTexts, SourceText, updateProgram, updateProgramText } from "./helpers";
describe("unittests:: builder", () => {
it("emits dependent files", () => {
const files: ts.NamedSourceText[] = [
{ name: "/a.ts", text: ts.SourceText.New("", 'import { b } from "./b";', "") },
{ name: "/b.ts", text: ts.SourceText.New("", ' import { c } from "./c";', "export const b = c;") },
{ name: "/c.ts", text: ts.SourceText.New("", "", "export const c = 0;") },
const files: NamedSourceText[] = [
{ name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") },
{ name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") },
{ name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") },
];
let program = ts.newProgram(files, ["/a.ts"], {});
let program = newProgram(files, ["/a.ts"], {});
const assertChanges = makeAssertChanges(() => program);
assertChanges(["/c.js", "/b.js", "/a.js"]);
@@ -24,12 +25,12 @@ describe("unittests:: builder", () => {
});
it("if emitting all files, emits the changed file first", () => {
const files: ts.NamedSourceText[] = [
{ name: "/a.ts", text: ts.SourceText.New("", "", "namespace A { export const x = 0; }") },
{ name: "/b.ts", text: ts.SourceText.New("", "", "namespace B { export const x = 0; }") },
const files: NamedSourceText[] = [
{ name: "/a.ts", text: SourceText.New("", "", "namespace A { export const x = 0; }") },
{ name: "/b.ts", text: SourceText.New("", "", "namespace B { export const x = 0; }") },
];
let program = ts.newProgram(files, ["/a.ts", "/b.ts"], {});
let program = newProgram(files, ["/a.ts", "/b.ts"], {});
const assertChanges = makeAssertChanges(() => program);
assertChanges(["/a.js", "/b.js"]);
@@ -42,15 +43,15 @@ describe("unittests:: builder", () => {
});
it("keeps the file in affected files if cancellation token throws during the operation", () => {
const files: ts.NamedSourceText[] = [
{ name: "/a.ts", text: ts.SourceText.New("", 'import { b } from "./b";', "") },
{ name: "/b.ts", text: ts.SourceText.New("", ' import { c } from "./c";', "export const b = c;") },
{ name: "/c.ts", text: ts.SourceText.New("", "", "export const c = 0;") },
{ name: "/d.ts", text: ts.SourceText.New("", "", "export const dd = 0;") },
{ name: "/e.ts", text: ts.SourceText.New("", "", "export const ee = 0;") },
const files: NamedSourceText[] = [
{ name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") },
{ name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") },
{ name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") },
{ name: "/d.ts", text: SourceText.New("", "", "export const dd = 0;") },
{ name: "/e.ts", text: SourceText.New("", "", "export const ee = 0;") },
];
let program = ts.newProgram(files, ["/d.ts", "/e.ts", "/a.ts"], {});
let program = newProgram(files, ["/d.ts", "/e.ts", "/a.ts"], {});
const assertChanges = makeAssertChangesWithCancellationToken(() => program);
// No cancellation
assertChanges(["/d.js", "/e.js", "/c.js", "/b.js", "/a.js"]);
@@ -123,8 +124,8 @@ function makeAssertChangesWithCancellationToken(getProgram: () => ts.Program): (
};
}
function updateProgramFile(program: ts.ProgramWithSourceTexts, fileName: string, fileContent: string): ts.ProgramWithSourceTexts {
return ts.updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => {
ts.updateProgramText(files, fileName, fileContent);
function updateProgramFile(program: ProgramWithSourceTexts, fileName: string, fileContent: string): ProgramWithSourceTexts {
return updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => {
updateProgramText(files, fileName, fileContent);
});
}

View File

@@ -1,6 +1,7 @@
import * as ts from "../../_namespaces/ts";
import * as fakes from "../../_namespaces/fakes";
import * as vfs from "../../_namespaces/vfs";
import { libFile } from "../virtualFileSystemWithWatch";
interface TestProjectSpecification {
configFileName?: string;
@@ -77,7 +78,7 @@ function testProjectReferences(spec: TestSpecification, entryPointConfigFileName
}
}
const vfsys = new vfs.FileSystem(false, { files: { "/lib.d.ts": ts.TestFSWithWatch.libFile.content } });
const vfsys = new vfs.FileSystem(false, { files: { "/lib.d.ts": libFile.content } });
files.forEach((v, k) => {
vfsys.mkdirpSync(ts.getDirectoryPath(k));
vfsys.writeFileSync(k, v);

View File

@@ -204,7 +204,7 @@ function mapEqualToCache<T>(left: ts.ESMap<string, T>, right: ts.ModeAwareCache<
}
export function checkResolvedModulesCache(program: ts.Program, fileName: string, expectedContent: ts.ESMap<string, ts.ResolvedModule | undefined> | undefined): void {
checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, ts.checkResolvedModule);
checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule);
}
export function checkResolvedTypeDirectivesCache(program: ts.Program, fileName: string, expectedContent: ts.ESMap<string, ts.ResolvedTypeReferenceDirective> | undefined): void {

View File

@@ -8,6 +8,42 @@ interface File {
symlinks?: string[];
}
function getDiagnosticMessageChain(message: ts.DiagnosticMessage, args?: (string | number)[], next?: ts.DiagnosticMessageChain[]): ts.DiagnosticMessageChain {
let text = ts.getLocaleSpecificMessage(message);
if (args?.length) {
text = ts.formatStringFromArgs(text, args);
}
return {
messageText: text,
category: message.category,
code: message.code,
next
};
}
function isDiagnosticMessageChain(message: ts.DiagnosticMessage | ts.DiagnosticMessageChain): message is ts.DiagnosticMessageChain {
return !!(message as ts.DiagnosticMessageChain).messageText;
}
function getDiagnosticOfFileFrom(file: ts.SourceFile | undefined, start: number | undefined, length: number | undefined, message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic {
return {
file,
start,
length,
messageText: isDiagnosticMessageChain(message) ?
message :
getDiagnosticMessageChain(message, args).messageText,
category: message.category,
code: message.code,
};
}
function getDiagnosticOfFileFromProgram(program: ts.Program, filePath: string, start: number, length: number, message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic {
return getDiagnosticOfFileFrom(program.getSourceFileByPath(ts.toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase())),
start, length, message, ...args);
}
function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ts.ModuleResolutionHost {
const map = new ts.Map<string, File>();
for (const file of files) {
@@ -596,21 +632,21 @@ describe("unittests:: moduleResolution:: Files with different casing with forceC
/*useCaseSensitiveFileNames*/ false,
["c.ts", "d.ts"],
program => [{
...ts.tscWatch.getDiagnosticOfFileFromProgram(
...getDiagnosticOfFileFromProgram(
program,
"c.ts",
`/// <reference path="D.ts"/>`.indexOf(`D.ts`),
"D.ts".length,
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
["D.ts", "d.ts"],
[
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.The_file_is_in_the_program_because_Colon,
ts.emptyArray,
[
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Referenced_via_0_from_file_1, ["D.ts", "c.ts"]),
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)
getDiagnosticMessageChain(ts.Diagnostics.Referenced_via_0_from_file_1, ["D.ts", "c.ts"]),
getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)
]
)
],
@@ -633,21 +669,21 @@ describe("unittests:: moduleResolution:: Files with different casing with forceC
/*useCaseSensitiveFileNames*/ false,
["c.ts", "d.ts"],
program => [{
...ts.tscWatch.getDiagnosticOfFileFromProgram(
...getDiagnosticOfFileFromProgram(
program,
"c.ts",
`import {x} from "D"`.indexOf(`"D"`),
`"D"`.length,
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
["/a/b/D.ts", "d.ts"],
[
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.The_file_is_in_the_program_because_Colon,
ts.emptyArray,
[
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]),
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)
getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]),
getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)
]
)
],
@@ -670,21 +706,21 @@ describe("unittests:: moduleResolution:: Files with different casing with forceC
/*useCaseSensitiveFileNames*/ false,
["moduleA.ts", "moduleB.ts"],
program => [{
...ts.tscWatch.getDiagnosticOfFileFromProgram(
...getDiagnosticOfFileFromProgram(
program,
"moduleA.ts",
`import {x} from "./ModuleB"`.indexOf(`"./ModuleB"`),
`"./ModuleB"`.length,
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
["ModuleB.ts", "moduleB.ts"],
[
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.The_file_is_in_the_program_because_Colon,
ts.emptyArray,
[
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./ModuleB"`, "moduleA.ts"]),
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)
getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./ModuleB"`, "moduleA.ts"]),
getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)
]
)
],
@@ -708,21 +744,21 @@ describe("unittests:: moduleResolution:: Files with different casing with forceC
/*useCaseSensitiveFileNames*/ true,
["c.ts", "d.ts"],
program => [{
...ts.tscWatch.getDiagnosticOfFileFromProgram(
...getDiagnosticOfFileFromProgram(
program,
"c.ts",
`import {x} from "D"`.indexOf(`"D"`),
`"D"`.length,
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
["/a/b/D.ts", "d.ts"],
[
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.The_file_is_in_the_program_because_Colon,
ts.emptyArray,
[
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]),
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)
getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]),
getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)
]
)
],
@@ -747,7 +783,7 @@ describe("unittests:: moduleResolution:: Files with different casing with forceC
["moduleA.ts", "moduleB.ts", "moduleC.ts"],
program => {
const importInA = {
...ts.tscWatch.getDiagnosticOfFileFromProgram(
...getDiagnosticOfFileFromProgram(
program,
"moduleA.ts",
`import a = require("./ModuleC")`.indexOf(`"./ModuleC"`),
@@ -758,7 +794,7 @@ describe("unittests:: moduleResolution:: Files with different casing with forceC
reportsDeprecated: undefined
};
const importInB = {
...ts.tscWatch.getDiagnosticOfFileFromProgram(
...getDiagnosticOfFileFromProgram(
program,
"moduleB.ts",
`import a = require("./moduleC")`.indexOf(`"./moduleC"`),
@@ -768,20 +804,20 @@ describe("unittests:: moduleResolution:: Files with different casing with forceC
reportsUnnecessary: undefined,
reportsDeprecated: undefined
};
const importHereInA = ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "moduleA.ts"]);
const importHereInB = ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "moduleB.ts"]);
const details = [ts.tscWatch.getDiagnosticMessageChain(
const importHereInA = getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "moduleA.ts"]);
const importHereInB = getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "moduleB.ts"]);
const details = [getDiagnosticMessageChain(
ts.Diagnostics.The_file_is_in_the_program_because_Colon,
ts.emptyArray,
[importHereInA, importHereInB, ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)]
[importHereInA, importHereInB, getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)]
)];
return [
{
...ts.tscWatch.getDiagnosticOfFileFrom(
...getDiagnosticOfFileFrom(
importInA.file,
importInA.start,
importInA.length,
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
["ModuleC.ts", "moduleC.ts" ],
details,
@@ -790,11 +826,11 @@ describe("unittests:: moduleResolution:: Files with different casing with forceC
relatedInformation: [importInB]
},
{
...ts.tscWatch.getDiagnosticOfFileFrom(
...getDiagnosticOfFileFrom(
importInB.file,
importInB.start,
importInB.length,
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing,
["moduleC.ts", "ModuleC.ts"],
details,
@@ -824,21 +860,21 @@ import b = require("./moduleB");
/*useCaseSensitiveFileNames*/ false,
["moduleD.ts"],
program => [{
...ts.tscWatch.getDiagnosticOfFileFromProgram(
...getDiagnosticOfFileFromProgram(
program,
"moduleB.ts",
`import a = require("./moduleC")`.indexOf(`"./moduleC"`),
`"./moduleC"`.length,
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing,
["/a/B/c/moduleC.ts", "/a/B/c/ModuleC.ts"],
[
ts.tscWatch.getDiagnosticMessageChain(
getDiagnosticMessageChain(
ts.Diagnostics.The_file_is_in_the_program_because_Colon,
ts.emptyArray,
[
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "/a/B/c/moduleA.ts"]),
ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "/a/B/c/moduleB.ts"])
getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "/a/B/c/moduleA.ts"]),
getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "/a/B/c/moduleB.ts"])
]
)
],
@@ -846,7 +882,7 @@ import b = require("./moduleB");
),
relatedInformation: [
{
...ts.tscWatch.getDiagnosticOfFileFromProgram(
...getDiagnosticOfFileFromProgram(
program,
"moduleA.ts",
`import a = require("./ModuleC")`.indexOf(`"./ModuleC"`),

View File

@@ -1,6 +1,7 @@
import * as ts from "../_namespaces/ts";
import { checkResolvedModulesCache, checkResolvedTypeDirectivesCache, createTestCompilerHost, NamedSourceText, newLine, newProgram, ProgramWithSourceTexts, SourceText, TestCompilerHost, updateProgram, updateProgramText } from "./helpers";
import { checkResolvedModulesCache, checkResolvedTypeDirectivesCache, createResolvedModule, createTestCompilerHost, NamedSourceText, newLine, newProgram, ProgramWithSourceTexts, SourceText, TestCompilerHost, updateProgram, updateProgramText } from "./helpers";
import { createWatchedSystem, File, libFile } from "./virtualFileSystemWithWatch";
describe("unittests:: Reuse program structure:: General", () => {
const target = ts.ScriptTarget.Latest;
@@ -155,7 +156,7 @@ describe("unittests:: Reuse program structure:: General", () => {
const options: ts.CompilerOptions = { target };
const program1 = newProgram(files, ["a.ts"], options);
checkResolvedModulesCache(program1, "a.ts", new ts.Map(ts.getEntries({ b: ts.createResolvedModule("b.ts") })));
checkResolvedModulesCache(program1, "a.ts", new ts.Map(ts.getEntries({ b: createResolvedModule("b.ts") })));
checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined);
const program2 = updateProgram(program1, ["a.ts"], options, files => {
@@ -164,7 +165,7 @@ describe("unittests:: Reuse program structure:: General", () => {
assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely);
// content of resolution cache should not change
checkResolvedModulesCache(program1, "a.ts", new ts.Map(ts.getEntries({ b: ts.createResolvedModule("b.ts") })));
checkResolvedModulesCache(program1, "a.ts", new ts.Map(ts.getEntries({ b: createResolvedModule("b.ts") })));
checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined);
// imports has changed - program is not reused
@@ -181,7 +182,7 @@ describe("unittests:: Reuse program structure:: General", () => {
files[0].text = files[0].text.updateImportsAndExports(newImports);
});
assert.equal(program4.structureIsReused, ts.StructureIsReused.SafeModules);
checkResolvedModulesCache(program4, "a.ts", new ts.Map(ts.getEntries({ b: ts.createResolvedModule("b.ts"), c: undefined })));
checkResolvedModulesCache(program4, "a.ts", new ts.Map(ts.getEntries({ b: createResolvedModule("b.ts"), c: undefined })));
});
it("set the resolvedImports after re-using an ambient external module declaration", () => {
@@ -692,9 +693,6 @@ describe("unittests:: Reuse program structure:: host is optional", () => {
});
});
type File = ts.TestFSWithWatch.File;
import createTestSystem = ts.TestFSWithWatch.createWatchedSystem;
import libFile = ts.TestFSWithWatch.libFile;
describe("unittests:: Reuse program structure:: isProgramUptoDate", () => {
function getWhetherProgramIsUptoDate(
@@ -748,7 +746,7 @@ describe("unittests:: Reuse program structure:: isProgramUptoDate", () => {
}
function verifyProgram(files: File[], rootFiles: string[], options: ts.CompilerOptions, configFile: string) {
const system = createTestSystem(files);
const system = createWatchedSystem(files);
verifyProgramWithoutConfigFile(system, rootFiles, options);
verifyProgramWithConfigFile(system, configFile);
}
@@ -863,7 +861,7 @@ describe("unittests:: Reuse program structure:: isProgramUptoDate", () => {
path: "/src/tsconfig.json",
content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] })
};
verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path);
verifyProgramWithConfigFile(createWatchedSystem([app, module1, module2, module3, libFile, configFile]), configFile.path);
});
it("has the same root file names", () => {
const module1: File = {
@@ -879,7 +877,7 @@ describe("unittests:: Reuse program structure:: isProgramUptoDate", () => {
content: "class classD { method() { return 10; } }\nexport default classD;"
};
const rootFiles = [module1.path, module2.path, module3.path];
const system = createTestSystem([module1, module2, module3]);
const system = createWatchedSystem([module1, module2, module3]);
const options = {};
const program = ts.createWatchProgram(ts.createWatchCompilerHostOfFilesAndCompilerOptions({
rootFiles,
@@ -915,7 +913,7 @@ describe("unittests:: Reuse program structure:: isProgramUptoDate", () => {
};
const rootFiles = [module1.path, module2.path];
const newRootFiles = [module1.path, module2.path, module3.path];
const system = createTestSystem([module1, module2, module3]);
const system = createWatchedSystem([module1, module2, module3]);
const options = {};
const program = ts.createWatchProgram(ts.createWatchCompilerHostOfFilesAndCompilerOptions({
rootFiles,
@@ -940,7 +938,7 @@ describe("unittests:: Reuse program structure:: isProgramUptoDate", () => {
};
const rootFiles = [module1.path, module2.path];
const newRootFiles = [module2.path, module3.path];
const system = createTestSystem([module1, module2, module3]);
const system = createWatchedSystem([module1, module2, module3]);
const options = {};
const program = ts.createWatchProgram(ts.createWatchCompilerHostOfFilesAndCompilerOptions({
rootFiles,

View File

@@ -1,7 +1,10 @@
import * as ts from "../../_namespaces/ts";
import * as Harness from "../../_namespaces/Harness";
import { createServerHost, File } from "../virtualFileSystemWithWatch";
import { createProjectService } from "../tsserver/helpers";
import { extractTest, newLineCharacter, notImplementedHost } from "./extract/helpers";
const libFile: ts.TestFSWithWatch.File = {
const libFile: File = {
path: "/a/lib/lib.d.ts",
content: `/// <reference no-default-lib="true"/>
interface Boolean {}
@@ -264,7 +267,7 @@ interface String { charAt: any; }
interface Array<T> {}`
};
const moduleFile: ts.TestFSWithWatch.File = {
const moduleFile: File = {
path: "/module.ts",
content:
`export function fn(res: any): any {
@@ -310,7 +313,7 @@ function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: stri
ts.Debug.assert(!(expectSuggestionDiagnostic && expectNoSuggestionDiagnostic), "Cannot combine both 'ExpectSuggestionDiagnostic' and 'ExpectNoSuggestionDiagnostic'");
ts.Debug.assert(!(expectAction && expectNoAction), "Cannot combine both 'ExpectAction' and 'ExpectNoAction'");
const t = ts.extractTest(text);
const t = extractTest(text);
const selectionRange = t.ranges.get("selection")!;
if (!selectionRange) {
throw new Error(`Test ${caption} does not specify selection range`);
@@ -345,8 +348,8 @@ function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: stri
program,
cancellationToken: { throwIfCancellationRequested: ts.noop, isCancellationRequested: ts.returnFalse },
preferences: ts.emptyOptions,
host: ts.notImplementedHost,
formatContext: ts.formatting.getFormatContext(ts.testFormatSettings, ts.notImplementedHost)
host: notImplementedHost,
formatContext: ts.formatting.getFormatContext(ts.testFormatSettings, notImplementedHost)
};
const diagnostics = languageService.getSuggestionDiagnostics(f.path);
@@ -369,7 +372,7 @@ function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: stri
const diagProgram = makeLanguageService({ path, content: newText }, includeLib, includeModule).getProgram()!;
assert.isFalse(hasSyntacticDiagnostics(diagProgram));
outputText = data.join(ts.newLineCharacter);
outputText = data.join(newLineCharacter);
}
else {
// eslint-disable-next-line no-null/no-null
@@ -395,7 +398,7 @@ function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: stri
}
}
function makeLanguageService(file: ts.TestFSWithWatch.File, includeLib?: boolean, includeModule?: boolean) {
function makeLanguageService(file: File, includeLib?: boolean, includeModule?: boolean) {
const files = [file];
if (includeLib) {
files.push(libFile); // libFile is expensive to parse repeatedly - only test when required
@@ -403,8 +406,8 @@ function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: stri
if (includeModule) {
files.push(moduleFile);
}
const host = ts.projectSystem.createServerHost(files);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost(files);
const projectService = createProjectService(host);
projectService.openClientFile(file.path);
return ts.first(projectService.inferredProjects).getLanguageService();
}

View File

@@ -1,4 +1,5 @@
import * as ts from "../../../_namespaces/ts";
import { testExtractSymbol, testExtractSymbolFailed } from "./helpers";
describe("unittests:: services:: extract:: extractConstants", () => {
testExtractConstant("extractConstant_TopLevel",
@@ -296,9 +297,9 @@ switch (1) {
});
function testExtractConstant(caption: string, text: string) {
ts.testExtractSymbol(caption, text, "extractConstant", ts.Diagnostics.Extract_constant);
testExtractSymbol(caption, text, "extractConstant", ts.Diagnostics.Extract_constant);
}
function testExtractConstantFailed(caption: string, text: string) {
ts.testExtractSymbolFailed(caption, text, ts.Diagnostics.Extract_constant);
testExtractSymbolFailed(caption, text, ts.Diagnostics.Extract_constant);
}

View File

@@ -1,4 +1,5 @@
import * as ts from "../../../_namespaces/ts";
import { testExtractSymbol } from "./helpers";
describe("unittests:: services:: extract:: extractFunctions", () => {
testExtractFunction("extractFunction1",
@@ -565,5 +566,5 @@ function F() {
});
function testExtractFunction(caption: string, text: string, includeLib?: boolean) {
ts.testExtractSymbol(caption, text, "extractFunction", ts.Diagnostics.Extract_function, includeLib);
testExtractSymbol(caption, text, "extractFunction", ts.Diagnostics.Extract_function, includeLib);
}

View File

@@ -1,5 +1,7 @@
import * as ts from "../../../_namespaces/ts";
import * as Harness from "../../../_namespaces/Harness";
import { createServerHost, libFile } from "../../virtualFileSystemWithWatch";
import { createProjectService } from "../../tsserver/helpers";
interface Range {
pos: number;
@@ -132,8 +134,8 @@ export function testExtractSymbol(caption: string, text: string, baselineFolder:
}
function makeProgram(f: {path: string, content: string }, includeLib?: boolean) {
const host = ts.projectSystem.createServerHost(includeLib ? [f, ts.projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost(includeLib ? [f, libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required
const projectService = createProjectService(host);
projectService.openClientFile(f.path);
const program = projectService.inferredProjects[0].getLanguageService().getProgram()!;
const autoImportProvider = projectService.inferredProjects[0].getLanguageService().getAutoImportProvider();
@@ -157,8 +159,8 @@ export function testExtractSymbolFailed(caption: string, text: string, descripti
path: "/a.ts",
content: t.source
};
const host = ts.projectSystem.createServerHost([f, ts.projectSystem.libFile]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([f, libFile]);
const projectService = createProjectService(host);
projectService.openClientFile(f.path);
const program = projectService.inferredProjects[0].getLanguageService().getProgram()!;
const sourceFile = program.getSourceFile(f.path)!;

View File

@@ -1,8 +1,9 @@
import * as ts from "../../../_namespaces/ts";
import { extractTest } from "./helpers";
function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) {
return it(caption, () => {
const t = ts.extractTest(s);
const t = extractTest(s);
const file = ts.createSourceFile("a.ts", t.source, ts.ScriptTarget.Latest, /*setParentNodes*/ true);
const selectionRange = t.ranges.get("selection");
if (!selectionRange) {
@@ -17,7 +18,7 @@ function testExtractRangeFailed(caption: string, s: string, expectedErrors: stri
function testExtractRange(caption: string, s: string) {
return it(caption, () => {
const t = ts.extractTest(s);
const t = extractTest(s);
const f = ts.createSourceFile("a.ts", t.source, ts.ScriptTarget.Latest, /*setParentNodes*/ true);
const selectionRange = t.ranges.get("selection");
if (!selectionRange) {

View File

@@ -1,6 +1,7 @@
import { expect } from "chai";
import * as ts from "../../_namespaces/ts";
import { createServerHost, File, libFile } from "../virtualFileSystemWithWatch";
describe("unittests:: services:: languageService", () => {
const files: {[index: string]: string} = {
@@ -90,7 +91,7 @@ export function Component(x: Config): any;`
const files = new ts.Map<string, { version: string, text: string; }>();
files.set("/project/root.ts", { version: "1", text: `import { foo } from "./other"` });
files.set("/project/other.ts", { version: "1", text: `export function foo() { }` });
files.set("/lib/lib.d.ts", { version: "1", text: ts.projectSystem.libFile.content });
files.set("/lib/lib.d.ts", { version: "1", text: libFile.content });
const host: ts.LanguageServiceHost = {
useCaseSensitiveFileNames: ts.returnTrue,
getCompilationSettings: ts.getDefaultCompilerOptions,
@@ -143,8 +144,8 @@ export function Component(x: Config): any;`
describe("detects program upto date when new file is added to the referenced project", () => {
function setup(useSourceOfProjectReferenceRedirect: (() => boolean) | undefined) {
const config1: ts.TestFSWithWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project1/tsconfig.json`,
const config1: File = {
path: `/user/username/projects/myproject/projects/project1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
module: "none",
@@ -153,16 +154,16 @@ export function Component(x: Config): any;`
exclude: ["temp"]
})
};
const class1: ts.TestFSWithWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project1/class1.ts`,
const class1: File = {
path: `/user/username/projects/myproject/projects/project1/class1.ts`,
content: `class class1 {}`
};
const class1Dts: ts.TestFSWithWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project1/class1.d.ts`,
const class1Dts: File = {
path: `/user/username/projects/myproject/projects/project1/class1.d.ts`,
content: `declare class class1 {}`
};
const config2: ts.TestFSWithWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project2/tsconfig.json`,
const config2: File = {
path: `/user/username/projects/myproject/projects/project2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
module: "none",
@@ -173,12 +174,12 @@ export function Component(x: Config): any;`
]
})
};
const class2: ts.TestFSWithWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project2/class2.ts`,
const class2: File = {
path: `/user/username/projects/myproject/projects/project2/class2.ts`,
content: `class class2 {}`
};
const system = ts.projectSystem.createServerHost([config1, class1, class1Dts, config2, class2, ts.projectSystem.libFile]);
const result = ts.getParsedCommandLineOfConfigFile(`${ts.tscWatch.projectRoot}/projects/project2/tsconfig.json`, /*optionsToExtend*/ undefined, {
const system = createServerHost([config1, class1, class1Dts, config2, class2, libFile]);
const result = ts.getParsedCommandLineOfConfigFile(`/user/username/projects/myproject/projects/project2/tsconfig.json`, /*optionsToExtend*/ undefined, {
useCaseSensitiveFileNames: true,
fileExists: path => system.fileExists(path),
readFile: path => system.readFile(path),
@@ -203,7 +204,7 @@ export function Component(x: Config): any;`
},
readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth),
getCurrentDirectory: () => system.getCurrentDirectory(),
getDefaultLibFileName: () => ts.projectSystem.libFile.path,
getDefaultLibFileName: () => libFile.path,
getProjectReferences: () => result.projectReferences,
};
const ls = ts.createLanguageService(host);
@@ -213,21 +214,21 @@ export function Component(x: Config): any;`
const { ls, system, class1, class2 } = setup(ts.returnTrue);
assert.deepEqual(
ls.getProgram()!.getSourceFiles().map(f => f.fileName),
[ts.projectSystem.libFile.path, class1.path, class2.path]
[libFile.path, class1.path, class2.path]
);
// Add new file to referenced project
const class3 = `${ts.tscWatch.projectRoot}/projects/project1/class3.ts`;
const class3 = `/user/username/projects/myproject/projects/project1/class3.ts`;
system.writeFile(class3, `class class3 {}`);
const program = ls.getProgram()!;
assert.deepEqual(
program.getSourceFiles().map(f => f.fileName),
[ts.projectSystem.libFile.path, class1.path, class3, class2.path]
[libFile.path, class1.path, class3, class2.path]
);
// Add excluded file to referenced project
system.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` });
system.ensureFileOrFolder({ path: `/user/username/projects/myproject/projects/project1/temp/file.d.ts`, content: `declare class file {}` });
assert.strictEqual(ls.getProgram(), program);
// Add output from new class to referenced project
system.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`);
system.writeFile(`/user/username/projects/myproject/projects/project1/class3.d.ts`, `declare class class3 {}`);
assert.strictEqual(ls.getProgram(), program);
});
@@ -236,38 +237,38 @@ export function Component(x: Config): any;`
const program1 = ls.getProgram()!;
assert.deepEqual(
program1.getSourceFiles().map(f => f.fileName),
[ts.projectSystem.libFile.path, class1Dts.path, class2.path]
[libFile.path, class1Dts.path, class2.path]
);
// Add new file to referenced project
const class3 = `${ts.tscWatch.projectRoot}/projects/project1/class3.ts`;
const class3 = `/user/username/projects/myproject/projects/project1/class3.ts`;
system.writeFile(class3, `class class3 {}`);
assert.notStrictEqual(ls.getProgram(), program1);
assert.deepEqual(
ls.getProgram()!.getSourceFiles().map(f => f.fileName),
[ts.projectSystem.libFile.path, class1Dts.path, class2.path]
[libFile.path, class1Dts.path, class2.path]
);
// Add class3 output
const class3Dts = `${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`;
const class3Dts = `/user/username/projects/myproject/projects/project1/class3.d.ts`;
system.writeFile(class3Dts, `declare class class3 {}`);
const program2 = ls.getProgram()!;
assert.deepEqual(
program2.getSourceFiles().map(f => f.fileName),
[ts.projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path]
[libFile.path, class1Dts.path, class3Dts, class2.path]
);
// Add excluded file to referenced project
system.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` });
system.ensureFileOrFolder({ path: `/user/username/projects/myproject/projects/project1/temp/file.d.ts`, content: `declare class file {}` });
assert.strictEqual(ls.getProgram(), program2);
// Delete output from new class to referenced project
system.deleteFile(class3Dts);
assert.deepEqual(
ls.getProgram()!.getSourceFiles().map(f => f.fileName),
[ts.projectSystem.libFile.path, class1Dts.path, class2.path]
[libFile.path, class1Dts.path, class2.path]
);
// Write output again
system.writeFile(class3Dts, `declare class class3 {}`);
assert.deepEqual(
ls.getProgram()!.getSourceFiles().map(f => f.fileName),
[ts.projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path]
[libFile.path, class1Dts.path, class3Dts, class2.path]
);
});
});

View File

@@ -1,5 +1,8 @@
import * as ts from "../../_namespaces/ts";
import * as Harness from "../../_namespaces/Harness";
import { createServerHost, File } from "../virtualFileSystemWithWatch";
import { createProjectService } from "../tsserver/helpers";
import { newLineCharacter } from "./extract/helpers";
describe("unittests:: services:: organizeImports", () => {
describe("Sort imports", () => {
@@ -1004,15 +1007,15 @@ export * from "lib";
libFile);
});
function testOrganizeExports(testName: string, testFile: ts.TestFSWithWatch.File, ...otherFiles: ts.TestFSWithWatch.File[]) {
function testOrganizeExports(testName: string, testFile: File, ...otherFiles: File[]) {
testOrganizeImports(`${testName}.exports`, /*skipDestructiveCodeActions*/ true, testFile, ...otherFiles);
}
function testOrganizeImports(testName: string, skipDestructiveCodeActions: boolean, testFile: ts.TestFSWithWatch.File, ...otherFiles: ts.TestFSWithWatch.File[]) {
function testOrganizeImports(testName: string, skipDestructiveCodeActions: boolean, testFile: File, ...otherFiles: File[]) {
it(testName, () => runBaseline(`organizeImports/${testName}.ts`, skipDestructiveCodeActions, testFile, ...otherFiles));
}
function runBaseline(baselinePath: string, skipDestructiveCodeActions: boolean, testFile: ts.TestFSWithWatch.File, ...otherFiles: ts.TestFSWithWatch.File[]) {
function runBaseline(baselinePath: string, skipDestructiveCodeActions: boolean, testFile: File, ...otherFiles: File[]) {
const { path: testPath, content: testContent } = testFile;
const languageService = makeLanguageService(testFile, ...otherFiles);
const changes = languageService.organizeImports({ skipDestructiveCodeActions, type: "file", fileName: testPath }, ts.testFormatSettings, ts.emptyOptions);
@@ -1025,12 +1028,12 @@ export * from "lib";
testContent,
"// ==ORGANIZED==",
newText,
].join(ts.newLineCharacter));
].join(newLineCharacter));
}
function makeLanguageService(...files: ts.TestFSWithWatch.File[]) {
const host = ts.projectSystem.createServerHost(files);
const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true });
function makeLanguageService(...files: File[]) {
const host = createServerHost(files);
const projectService = createProjectService(host, { useSingleInferredProject: true });
projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? ts.JsxEmit.React : ts.JsxEmit.None });
files.forEach(f => projectService.openClientFile(f.path));
return projectService.inferredProjects[0].getLanguageService();

View File

@@ -1,5 +1,6 @@
import * as ts from "../../_namespaces/ts";
import * as Harness from "../../_namespaces/Harness";
import { notImplementedHost } from "./extract/helpers";
// Some tests have trailing whitespace
@@ -21,7 +22,7 @@ describe("unittests:: services:: textChanges", () => {
const newLineCharacter = ts.getNewLineCharacter(printerOptions);
function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean): ts.formatting.FormatContext {
return ts.formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...ts.testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : ts.testFormatSettings, ts.notImplementedHost);
return ts.formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...ts.testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : ts.testFormatSettings, notImplementedHost);
}
// validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed.

View File

@@ -1,10 +1,11 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import { addRest, addShebang, addSpread, addTestPrologue, addTripleSlashRef, appendText, enableStrict, loadProjectFromDisk, removeRest, replaceText, verifyTsc, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
let outFileFs: vfs.FileSystem;
before(() => {
outFileFs = ts.loadProjectFromDisk("tests/projects/amdModulesWithOut");
outFileFs = loadProjectFromDisk("tests/projects/amdModulesWithOut");
});
after(() => {
outFileFs = undefined!;
@@ -21,7 +22,7 @@ describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
modifyFs,
modifyAgainFs
}: VerifyOutFileScenarioInput) {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "amdModulesWithOut",
subScenario,
fs: () => outFileFs,
@@ -31,7 +32,7 @@ describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
edits: [
{
subScenario: "incremental-declaration-doesnt-change",
modifyFs: fs => ts.appendText(fs, "/src/lib/file1.ts", "console.log(x);")
modifyFs: fs => appendText(fs, "/src/lib/file1.ts", "console.log(x);")
},
...(modifyAgainFs ? [{
subScenario: "incremental-headers-change-without-dts-changes",
@@ -51,15 +52,15 @@ describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
verifyOutFileScenario({
subScenario: "multiple prologues in all projects",
modifyFs: fs => {
ts.enableStrict(fs, "/src/lib/tsconfig.json");
ts.addTestPrologue(fs, "/src/lib/file0.ts", `"myPrologue"`);
ts.addTestPrologue(fs, "/src/lib/file2.ts", `"myPrologueFile"`);
ts.addTestPrologue(fs, "/src/lib/global.ts", `"myPrologue3"`);
ts.enableStrict(fs, "/src/app/tsconfig.json");
ts.addTestPrologue(fs, "/src/app/file3.ts", `"myPrologue"`);
ts.addTestPrologue(fs, "/src/app/file4.ts", `"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 => ts.addTestPrologue(fs, "/src/lib/file1.ts", `"myPrologue5"`)
modifyAgainFs: fs => addTestPrologue(fs, "/src/lib/file1.ts", `"myPrologue5"`)
});
});
@@ -69,9 +70,9 @@ describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
verifyOutFileScenario({
subScenario: "shebang in all projects",
modifyFs: fs => {
ts.addShebang(fs, "lib", "file0");
ts.addShebang(fs, "lib", "file1");
ts.addShebang(fs, "app", "file3");
addShebang(fs, "lib", "file0");
addShebang(fs, "lib", "file1");
addShebang(fs, "app", "file3");
},
});
});
@@ -81,12 +82,12 @@ describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
verifyOutFileScenario({
subScenario: "multiple emitHelpers in all projects",
modifyFs: fs => {
ts.addSpread(fs, "lib", "file0");
ts.addRest(fs, "lib", "file1");
ts.addRest(fs, "app", "file3");
ts.addSpread(fs, "app", "file4");
addSpread(fs, "lib", "file0");
addRest(fs, "lib", "file1");
addRest(fs, "app", "file3");
addSpread(fs, "app", "file4");
},
modifyAgainFs: fs => ts.removeRest(fs, "lib", "file1")
modifyAgainFs: fs => removeRest(fs, "lib", "file1")
});
});
@@ -96,8 +97,8 @@ describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
verifyOutFileScenario({
subScenario: "triple slash refs in all projects",
modifyFs: fs => {
ts.addTripleSlashRef(fs, "lib", "file0");
ts.addTripleSlashRef(fs, "app", "file4");
addTripleSlashRef(fs, "lib", "file0");
addTripleSlashRef(fs, "app", "file4");
}
});
});
@@ -105,10 +106,10 @@ describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
describe("stripInternal", () => {
function stripInternalScenario(fs: vfs.FileSystem) {
const internal = "/*@internal*/";
ts.replaceText(fs, "/src/app/tsconfig.json", `"composite": true,`, `"composite": true,
replaceText(fs, "/src/app/tsconfig.json", `"composite": true,`, `"composite": true,
"stripInternal": true,`);
ts.replaceText(fs, "/src/lib/file0.ts", "const", `${internal} const`);
ts.appendText(fs, "/src/lib/file1.ts", `
replaceText(fs, "/src/lib/file0.ts", "const", `${internal} const`);
appendText(fs, "/src/lib/file1.ts", `
export class normalC {
${internal} constructor() { }
${internal} prop: string;
@@ -140,19 +141,19 @@ ${internal} export enum internalEnum { a, b, c }`);
verifyOutFileScenario({
subScenario: "stripInternal",
modifyFs: stripInternalScenario,
modifyAgainFs: fs => ts.replaceText(fs, "/src/lib/file1.ts", `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
ts.replaceText(fs, "/src/lib/tsconfig.json", `"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
ts.replaceText(fs, "/src/app/file3.ts", "file1", "lib/file1");
replaceText(fs, "/src/app/file3.ts", "file1", "lib/file1");
}
ts.verifyTsc({
verifyTsc({
scenario: "amdModulesWithOut",
subScenario: "when the module resolution finds original source file",
fs: () => outFileFs,

View File

@@ -1,11 +1,10 @@
import * as ts from "../../_namespaces/ts";
import { loadProjectFromFiles, verifyTsc } from "../tsc/helpers";
describe("unittests:: tsbuild - clean", () => {
ts.verifyTsc({
verifyTsc({
scenario: "clean",
subScenario: `file name and output name clashing`,
commandLineArgs: ["--b", "/src/tsconfig.json", "-clean"],
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/index.js": "",
"/src/bar.ts": "",
"/src/tsconfig.json": JSON.stringify({

View File

@@ -1,18 +1,19 @@
import * as ts from "../../_namespaces/ts";
import { appendText, compilerOptionsToConfigJson, loadProjectFromFiles, noChangeRun, noChangeWithExportsDiscrepancyRun, replaceText, TestTscEdit, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: commandLine::", () => {
describe("different options::", () => {
function withOptionChange(subScenario: string, ...options: readonly string[]): ts.TestTscEdit {
function withOptionChange(subScenario: string, ...options: readonly string[]): TestTscEdit {
return {
subScenario,
modifyFs: ts.noop,
commandLineArgs: ["--b", "/src/project", "--verbose", ...options]
};
}
function noChangeWithSubscenario(subScenario: string): ts.TestTscEdit {
return { ...ts.noChangeRun, subScenario };
function noChangeWithSubscenario(subScenario: string): TestTscEdit {
return { ...noChangeRun, subScenario };
}
function withOptionChangeAndDiscrepancyExplanation(subScenario: string, option: string): ts.TestTscEdit {
function withOptionChangeAndDiscrepancyExplanation(subScenario: string, option: string): TestTscEdit {
return {
...withOptionChange(subScenario, option),
discrepancyExplanation: () => [
@@ -21,7 +22,7 @@ describe("unittests:: tsbuild:: commandLine::", () => {
]
};
}
function withEmitDeclarationOnlyChangeAndDiscrepancyExplanation(subScenario: string): ts.TestTscEdit {
function withEmitDeclarationOnlyChangeAndDiscrepancyExplanation(subScenario: string): TestTscEdit {
const edit = withOptionChangeAndDiscrepancyExplanation(subScenario, "--emitDeclarationOnly");
const discrepancyExplanation = edit.discrepancyExplanation!;
edit.discrepancyExplanation = () => [
@@ -31,22 +32,22 @@ describe("unittests:: tsbuild:: commandLine::", () => {
];
return edit;
}
function withOptionChangeAndExportExplanation(subScenario: string, ...options: readonly string[]): ts.TestTscEdit {
function withOptionChangeAndExportExplanation(subScenario: string, ...options: readonly string[]): TestTscEdit {
return {
...withOptionChange(subScenario, ...options),
discrepancyExplanation: ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
discrepancyExplanation: noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
};
}
function nochangeWithIncrementalDeclarationFromBeforeExplaination(): ts.TestTscEdit {
function nochangeWithIncrementalDeclarationFromBeforeExplaination(): TestTscEdit {
return {
...ts.noChangeRun,
...noChangeRun,
discrepancyExplanation: () => [
`Clean build tsbuildinfo will have compilerOptions {}`,
`Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`,
],
};
}
function nochangeWithIncrementalOutDeclarationFromBeforeExplaination(): ts.TestTscEdit {
function nochangeWithIncrementalOutDeclarationFromBeforeExplaination(): TestTscEdit {
const edit = nochangeWithIncrementalDeclarationFromBeforeExplaination();
const discrepancyExplanation = edit.discrepancyExplanation!;
edit.discrepancyExplanation = () => [
@@ -56,22 +57,22 @@ describe("unittests:: tsbuild:: commandLine::", () => {
];
return edit;
}
function localChange(): ts.TestTscEdit {
function localChange(): TestTscEdit {
return {
subScenario: "local change",
modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "Local = 1", "Local = 10"),
modifyFs: fs => replaceText(fs, "/src/project/a.ts", "Local = 1", "Local = 10"),
};
}
function fs(options: ts.CompilerOptions) {
return ts.loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({ compilerOptions: ts.compilerOptionsToConfigJson(options) }),
return loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({ compilerOptions: compilerOptionsToConfigJson(options) }),
"/src/project/a.ts": `export const a = 10;const aLocal = 10;`,
"/src/project/b.ts": `export const b = 10;const bLocal = 10;`,
"/src/project/c.ts": `import { a } from "./a";export const c = a;`,
"/src/project/d.ts": `import { b } from "./b";export const d = b;`,
});
}
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: "different options",
fs: () => fs({ composite: true }),
@@ -80,11 +81,11 @@ describe("unittests:: tsbuild:: commandLine::", () => {
withOptionChange("with sourceMap", "--sourceMap"),
noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"),
withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"),
ts.noChangeRun,
noChangeRun,
withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"),
noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"),
withOptionChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything", "--emitDeclarationOnly"),
ts.noChangeRun,
noChangeRun,
localChange(),
withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"),
withOptionChange("with inlineSourceMap", "--inlineSourceMap"),
@@ -92,7 +93,7 @@ describe("unittests:: tsbuild:: commandLine::", () => {
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: "different options with outFile",
fs: () => fs({ composite: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }),
@@ -101,11 +102,11 @@ describe("unittests:: tsbuild:: commandLine::", () => {
withOptionChange("with sourceMap", "--sourceMap"),
noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"),
withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"),
ts.noChangeRun,
noChangeRun,
withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"),
noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"),
withEmitDeclarationOnlyChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything"),
ts.noChangeRun,
noChangeRun,
localChange(),
withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"),
withOptionChange("with inlineSourceMap", "--inlineSourceMap"),
@@ -113,7 +114,7 @@ describe("unittests:: tsbuild:: commandLine::", () => {
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: "different options with incremental",
fs: () => fs({ incremental: true }),
@@ -135,7 +136,7 @@ describe("unittests:: tsbuild:: commandLine::", () => {
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: "different options with incremental with outFile",
fs: () => fs({ incremental: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }),
@@ -160,16 +161,16 @@ describe("unittests:: tsbuild:: commandLine::", () => {
});
describe("emitDeclarationOnly::", () => {
function fs(options: ts.CompilerOptions) {
return ts.loadProjectFromFiles({
return loadProjectFromFiles({
"/src/project1/src/tsconfig.json": JSON.stringify({
compilerOptions: ts.compilerOptionsToConfigJson(options),
compilerOptions: compilerOptionsToConfigJson(options),
}),
"/src/project1/src/a.ts": `export const a = 10;const aLocal = 10;`,
"/src/project1/src/b.ts": `export const b = 10;const bLocal = 10;`,
"/src/project1/src/c.ts": `import { a } from "./a";export const c = a;`,
"/src/project1/src/d.ts": `import { b } from "./b";export const d = b;`,
"/src/project2/src/tsconfig.json": JSON.stringify({
compilerOptions: ts.compilerOptionsToConfigJson(options),
compilerOptions: compilerOptionsToConfigJson(options),
references: [{ path: "../../project1/src" }],
}),
"/src/project2/src/e.ts": `export const e = 10;`,
@@ -178,20 +179,20 @@ describe("unittests:: tsbuild:: commandLine::", () => {
});
}
function verifyWithIncremental(options: ts.CompilerOptions) {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: subScenario("emitDeclarationOnly on commandline"),
fs: () => fs(options),
commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
},
{
subScenario: "non local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"),
},
{
subScenario: "emit js files",
@@ -199,7 +200,7 @@ describe("unittests:: tsbuild:: commandLine::", () => {
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
},
{
...ts.noChangeRun,
...noChangeRun,
discrepancyExplanation: () => [
`Clean build tsbuildinfo for both projects will have compilerOptions with composite and emitDeclarationOnly`,
`Incremental build will detect that it doesnt need to rebuild so tsbuild info for projects is from before which has option composite only`,
@@ -207,12 +208,12 @@ describe("unittests:: tsbuild:: commandLine::", () => {
},
{
subScenario: "js emit with change without emitDeclarationOnly",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"),
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
},
{
subScenario: "local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"),
// --out without composite doesnt emit buildInfo without emitting program so it wouldnt have project2 tsbuildInfo so no mismatch
discrepancyExplanation: options.incremental && options.outFile ? undefined : () => [
`Clean build tsbuildinfo for project2 will have compilerOptions with composite and emitDeclarationOnly`,
@@ -221,26 +222,26 @@ describe("unittests:: tsbuild:: commandLine::", () => {
},
{
subScenario: "non local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"),
},
{
subScenario: "js emit with change without emitDeclarationOnly",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"),
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
},
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: subScenario("emitDeclarationOnly false on commandline"),
fs: () => fs({ ...options, emitDeclarationOnly: true }),
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
},
{
subScenario: "emit js files",
@@ -248,7 +249,7 @@ describe("unittests:: tsbuild:: commandLine::", () => {
commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"],
},
{
...ts.noChangeRun,
...noChangeRun,
discrepancyExplanation: () => [
`Clean build tsbuildinfo for both projects will have compilerOptions with composite and emitDeclarationOnly`,
`Incremental build will detect that it doesnt need to rebuild so tsbuild info for projects is from before which has option composite as true but emitDeclrationOnly as false`,
@@ -261,7 +262,7 @@ describe("unittests:: tsbuild:: commandLine::", () => {
},
{
subScenario: "js emit with change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"),
commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"],
},
],
@@ -276,66 +277,66 @@ describe("unittests:: tsbuild:: commandLine::", () => {
verifyWithIncremental({ composite: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD });
verifyWithIncremental({ incremental: true, declaration: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD });
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: "emitDeclarationOnly on commandline with declaration",
fs: () => fs({ declaration: true }),
commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
},
{
subScenario: "non local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"),
},
{
subScenario: "emit js files",
modifyFs: ts.noop,
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
},
ts.noChangeRun,
noChangeRun,
{
subScenario: "js emit with change without emitDeclarationOnly",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"),
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
},
{
subScenario: "local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"),
},
{
subScenario: "non local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"),
},
{
subScenario: "js emit with change without emitDeclarationOnly",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"),
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
},
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: "emitDeclarationOnly false on commandline with declaration",
fs: () => fs({ declaration: true, emitDeclarationOnly: true }),
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
},
{
subScenario: "emit js files",
modifyFs: ts.noop,
commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"],
},
ts.noChangeRun,
noChangeRun,
{
subScenario: "no change run with js emit",
modifyFs: ts.noop,
@@ -343,73 +344,73 @@ describe("unittests:: tsbuild:: commandLine::", () => {
},
{
subScenario: "js emit with change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"),
commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"],
},
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: "emitDeclarationOnly on commandline with declaration with outFile",
fs: () => fs({ declaration: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }),
commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
},
{
subScenario: "non local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"),
},
{
subScenario: "emit js files",
modifyFs: ts.noop,
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
},
ts.noChangeRun,
noChangeRun,
{
subScenario: "js emit with change without emitDeclarationOnly",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"),
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
},
{
subScenario: "local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"),
},
{
subScenario: "non local change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"),
},
{
subScenario: "js emit with change without emitDeclarationOnly",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"),
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
},
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "commandLine",
subScenario: "emitDeclarationOnly false on commandline with declaration with outFile",
fs: () => fs({ declaration: true, emitDeclarationOnly: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }),
commandLineArgs: ["--b", "/src/project2/src", "--verbose"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"),
},
{
subScenario: "emit js files",
modifyFs: ts.noop,
commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"],
},
ts.noChangeRun,
noChangeRun,
{
subScenario: "no change run with js emit",
modifyFs: ts.noop,
@@ -417,7 +418,7 @@ describe("unittests:: tsbuild:: commandLine::", () => {
},
{
subScenario: "js emit with change",
modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"),
modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"),
commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"],
},
],

View File

@@ -1,23 +1,23 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { appendText, loadProjectFromDisk, loadProjectFromFiles, noChangeRun, replaceText, verifyTsc, verifyTscWithEdits } from "../tsc/helpers";
import { dedent } from "../../_namespaces/Utils";
describe("unittests:: tsbuild:: configFileErrors:: when tsconfig extends the missing file", () => {
ts.verifyTsc({
verifyTsc({
scenario: "configFileErrors",
subScenario: "when tsconfig extends the missing file",
fs: () => ts.loadProjectFromDisk("tests/projects/missingExtendedConfig"),
fs: () => loadProjectFromDisk("tests/projects/missingExtendedConfig"),
commandLineArgs: ["--b", "/src/tsconfig.json"],
});
});
describe("unittests:: tsbuild:: configFileErrors:: reports syntax errors in config file", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "configFileErrors",
subScenario: "reports syntax errors in config file",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/a.ts": "export function foo() { }",
"/src/b.ts": "export function bar() { }",
"/src/tsconfig.json": Utils.dedent`
"/src/tsconfig.json": dedent`
{
"compilerOptions": {
"composite": true,
@@ -31,7 +31,7 @@ describe("unittests:: tsbuild:: configFileErrors:: reports syntax errors in conf
commandLineArgs: ["--b", "/src/tsconfig.json"],
edits: [
{
modifyFs: fs => ts.replaceText(fs, "/src/tsconfig.json", ",", `,
modifyFs: fs => replaceText(fs, "/src/tsconfig.json", ",", `,
"declaration": true,`),
subScenario: "reports syntax errors after change to config file",
discrepancyExplanation: () => [
@@ -40,10 +40,10 @@ describe("unittests:: tsbuild:: configFileErrors:: reports syntax errors in conf
],
},
{
modifyFs: fs => ts.appendText(fs, "/src/a.ts", "export function fooBar() { }"),
modifyFs: fs => appendText(fs, "/src/a.ts", "export function fooBar() { }"),
subScenario: "reports syntax errors after change to ts file",
},
ts.noChangeRun,
noChangeRun,
{
modifyFs: fs => fs.writeFileSync(
"/src/tsconfig.json",

View File

@@ -1,8 +1,8 @@
import * as ts from "../../_namespaces/ts";
import { loadProjectFromFiles, verifyTsc } from "../tsc/helpers";
describe("unittests:: tsbuild:: configFileExtends:: when tsconfig extends another config", () => {
function getConfigExtendsWithIncludeFs() {
return ts.loadProjectFromFiles({
return loadProjectFromFiles({
"/src/tsconfig.json": JSON.stringify({
references: [
{ path: "./shared/tsconfig.json" },
@@ -37,13 +37,13 @@ describe("unittests:: tsbuild:: configFileExtends:: when tsconfig extends anothe
"/src/webpack/index.ts": `export const b: Unrestricted = 1;`,
});
}
ts.verifyTsc({
verifyTsc({
scenario: "configFileExtends",
subScenario: "when building solution with projects extends config with include",
fs: getConfigExtendsWithIncludeFs,
commandLineArgs: ["--b", "/src/tsconfig.json", "--v", "--listFiles"],
});
ts.verifyTsc({
verifyTsc({
scenario: "configFileExtends",
subScenario: "when building project uses reference and both extend config with include",
fs: getConfigExtendsWithIncludeFs,

View File

@@ -1,18 +1,18 @@
import * as ts from "../../_namespaces/ts";
import { loadProjectFromDisk, loadProjectFromFiles, noChangeOnlyRuns, replaceText, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: when containerOnly project is referenced", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "containerOnlyReferenced",
subScenario: "verify that subsequent builds after initial build doesnt build anything",
fs: () => ts.loadProjectFromDisk("tests/projects/containerOnlyReferenced"),
fs: () => loadProjectFromDisk("tests/projects/containerOnlyReferenced"),
commandLineArgs: ["--b", "/src", "--verbose"],
edits: ts.noChangeOnlyRuns
edits: noChangeOnlyRuns
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "containerOnlyReferenced",
subScenario: "when solution is referenced indirectly",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project1/tsconfig.json": JSON.stringify({
compilerOptions: { composite: true },
references: [],
@@ -36,7 +36,7 @@ describe("unittests:: tsbuild:: when containerOnly project is referenced", () =>
commandLineArgs: ["--b", "/src/project4", "--verbose", "--explainFiles"],
edits: [{
subScenario: "modify project3 file",
modifyFs: fs => ts.replaceText(fs, "/src/project3/src/c.ts", "c = ", "cc = "),
modifyFs: fs => replaceText(fs, "/src/project3/src/c.ts", "c = ", "cc = "),
}],
});
});

View File

@@ -1,6 +1,6 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import * as Utils from "../../_namespaces/Utils";
import { loadProjectFromFiles, verifyTsc } from "../tsc/helpers";
describe("unittests:: tsbuild:: declarationEmit", () => {
function getFiles(): vfs.FileSet {
@@ -58,17 +58,17 @@ declare type MyNominal<T, Name extends string> = T & {
};`,
};
}
ts.verifyTsc({
verifyTsc({
scenario: "declarationEmit",
subScenario: "when declaration file is referenced through triple slash",
fs: () => ts.loadProjectFromFiles(getFiles()),
fs: () => loadProjectFromFiles(getFiles()),
commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"]
});
ts.verifyTsc({
verifyTsc({
scenario: "declarationEmit",
subScenario: "when declaration file is referenced through triple slash but uses no references",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
...getFiles(),
"/src/solution/tsconfig.json": JSON.stringify({
extends: "./tsconfig.base.json",
@@ -79,10 +79,10 @@ declare type MyNominal<T, Name extends string> = T & {
commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"]
});
ts.verifyTsc({
verifyTsc({
scenario: "declarationEmit",
subScenario: "when declaration file used inferred type from referenced project",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/tsconfig.json": JSON.stringify({
compilerOptions: {
composite: true,

View File

@@ -1,29 +1,29 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import { loadProjectFromDisk, prependText, replaceText, verifyTsc } from "../tsc/helpers";
describe("unittests:: tsbuild:: on demo project", () => {
let projFs: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/demo");
projFs = loadProjectFromDisk("tests/projects/demo");
});
after(() => {
projFs = undefined!; // Release the contents
});
ts.verifyTsc({
verifyTsc({
scenario: "demo",
subScenario: "in master branch with everything setup correctly and reports no error",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"]
});
ts.verifyTsc({
verifyTsc({
scenario: "demo",
subScenario: "in circular branch reports the error about it by stopping build",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"],
modifyFs: fs => ts.replaceText(
modifyFs: fs => replaceText(
fs,
"/src/core/tsconfig.json",
"}",
@@ -35,12 +35,12 @@ describe("unittests:: tsbuild:: on demo project", () => {
]`
)
});
ts.verifyTsc({
verifyTsc({
scenario: "demo",
subScenario: "in bad-ref branch reports the error about files not in rootDir at the import location",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"],
modifyFs: fs => ts.prependText(
modifyFs: fs => prependText(
fs,
"/src/core/utilities.ts",
`import * as A from '../animals';

View File

@@ -1,52 +1,52 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import { loadProjectFromDisk, replaceText, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: on project with emitDeclarationOnly set to true", () => {
let projFs: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/emitDeclarationOnly");
projFs = loadProjectFromDisk("tests/projects/emitDeclarationOnly");
});
after(() => {
projFs = undefined!;
});
function verifyEmitDeclarationOnly(disableMap?: true) {
ts.verifyTscWithEdits({
verifyTscWithEdits({
subScenario: `only dts output in circular import project with emitDeclarationOnly${disableMap ? "" : " and declarationMap"}`,
fs: () => projFs,
scenario: "emitDeclarationOnly",
commandLineArgs: ["--b", "/src", "--verbose"],
modifyFs: disableMap ?
(fs => ts.replaceText(fs, "/src/tsconfig.json", `"declarationMap": true,`, "")) :
(fs => replaceText(fs, "/src/tsconfig.json", `"declarationMap": true,`, "")) :
undefined,
edits: [{
subScenario: "incremental-declaration-changes",
modifyFs: fs => ts.replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"),
modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"),
}],
});
}
verifyEmitDeclarationOnly();
verifyEmitDeclarationOnly(/*disableMap*/ true);
ts.verifyTscWithEdits({
verifyTscWithEdits({
subScenario: `only dts output in non circular imports project with emitDeclarationOnly`,
fs: () => projFs,
scenario: "emitDeclarationOnly",
commandLineArgs: ["--b", "/src", "--verbose"],
modifyFs: fs => {
fs.rimrafSync("/src/src/index.ts");
ts.replaceText(fs, "/src/src/a.ts", `import { B } from "./b";`, `export class B { prop = "hello"; }`);
replaceText(fs, "/src/src/a.ts", `import { B } from "./b";`, `export class B { prop = "hello"; }`);
},
edits: [
{
subScenario: "incremental-declaration-doesnt-change",
modifyFs: fs => ts.replaceText(fs, "/src/src/a.ts", "export interface A {", `class C { }
modifyFs: fs => replaceText(fs, "/src/src/a.ts", "export interface A {", `class C { }
export interface A {`),
},
{
subScenario: "incremental-declaration-changes",
modifyFs: fs => ts.replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"),
modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"),
},
],
});

View File

@@ -1,23 +1,23 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import { loadProjectFromDisk, verifyTsc } from "../tsc/helpers";
describe("unittests:: tsbuild - empty files option in tsconfig", () => {
let projFs: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/empty-files");
projFs = loadProjectFromDisk("tests/projects/empty-files");
});
after(() => {
projFs = undefined!;
});
ts.verifyTsc({
verifyTsc({
scenario: "emptyFiles",
subScenario: "has empty files diagnostic when files is empty and no references are provided",
fs: () => projFs,
commandLineArgs: ["--b", "/src/no-references"],
});
ts.verifyTsc({
verifyTsc({
scenario: "emptyFiles",
subScenario: "does not have empty files diagnostic when files is empty and references are provided",
fs: () => projFs,

View File

@@ -1,11 +1,11 @@
import * as ts from "../../_namespaces/ts";
import { loadProjectFromFiles, verifyTsc } from "../tsc/helpers";
// https://github.com/microsoft/TypeScript/issues/33849
describe("unittests:: tsbuild:: exitCodeOnBogusFile:: test exit code", () => {
ts.verifyTsc({
verifyTsc({
scenario: "exitCodeOnBogusFile",
subScenario: `test exit code`,
fs: () => ts.loadProjectFromFiles({}),
fs: () => loadProjectFromFiles({}),
commandLineArgs: ["-b", "bogus.json"]
});
});

View File

@@ -1,769 +0,0 @@
import * as ts from "../../_namespaces/ts";
import * as fakes from "../../_namespaces/fakes";
import * as vfs from "../../_namespaces/vfs";
import * as Harness from "../../_namespaces/Harness";
import * as vpath from "../../_namespaces/vpath";
export function errorDiagnostic(message: fakes.ExpectedDiagnosticMessage): fakes.ExpectedErrorDiagnostic {
return { message };
}
export function getExpectedDiagnosticForProjectsInBuild(...projects: string[]): fakes.ExpectedDiagnostic {
return [ts.Diagnostics.Projects_in_this_build_Colon_0, projects.map(p => "\r\n * " + p).join("")];
}
export function changeCompilerVersion(host: fakes.SolutionBuilderHost) {
const originalReadFile = host.readFile;
host.readFile = path => {
const value = originalReadFile.call(host, path);
if (!value || !ts.isBuildInfoFile(path)) return value;
const buildInfo = ts.getBuildInfo(path, value);
if (!buildInfo) return value;
buildInfo.version = fakes.version;
return ts.getBuildInfoText(buildInfo);
};
}
export function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
if (old.indexOf(oldText) < 0) {
throw new Error(`Text "${oldText}" does not exist in file ${path}`);
}
const newContent = old.replace(oldText, newText);
fs.writeFileSync(path, newContent, "utf-8");
}
export function prependText(fs: vfs.FileSystem, path: string, additionalContent: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
fs.writeFileSync(path, `${additionalContent}${old}`, "utf-8");
}
export function appendText(fs: vfs.FileSystem, path: string, additionalContent: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
fs.writeFileSync(path, `${old}${additionalContent}`);
}
export function indexOf(fs: vfs.FileSystem, path: string, searchStr: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const content = fs.readFileSync(path, "utf-8");
return content.indexOf(searchStr);
}
export function lastIndexOf(fs: vfs.FileSystem, path: string, searchStr: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const content = fs.readFileSync(path, "utf-8");
return content.lastIndexOf(searchStr);
}
export function expectedLocationIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation {
return {
file,
start: indexOf(fs, file, searchStr),
length: searchStr.length
};
}
export function expectedLocationLastIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation {
return {
file,
start: lastIndexOf(fs, file, searchStr),
length: searchStr.length
};
}
export const libContent = `${ts.TestFSWithWatch.libFile.content}
interface ReadonlyArray<T> {}
declare const console: { log(msg: any): void; };`;
export const symbolLibContent = `
interface SymbolConstructor {
readonly species: symbol;
readonly toStringTag: symbol;
}
declare var Symbol: SymbolConstructor;
interface Symbol {
readonly [Symbol.toStringTag]: string;
}
`;
/**
* Load project from disk into /src folder
*/
export function loadProjectFromDisk(
root: string,
libContentToAppend?: string
): vfs.FileSystem {
const resolver = vfs.createResolver(Harness.IO);
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
files: {
["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver)
},
cwd: "/",
meta: { defaultLibLocation: "/lib" },
});
addLibAndMakeReadonly(fs, libContentToAppend);
return fs;
}
/**
* All the files must be in /src
*/
export function loadProjectFromFiles(
files: vfs.FileSet,
libContentToAppend?: string
): vfs.FileSystem {
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
files,
cwd: "/",
meta: { defaultLibLocation: "/lib" },
});
addLibAndMakeReadonly(fs, libContentToAppend);
return fs;
}
function addLibAndMakeReadonly(fs: vfs.FileSystem, libContentToAppend?: string) {
fs.mkdirSync("/lib");
fs.writeFileSync("/lib/lib.d.ts", libContentToAppend ? `${libContent}${libContentToAppend}` : libContent);
fs.makeReadonly();
}
export function verifyOutputsPresent(fs: vfs.FileSystem, outputs: readonly string[]) {
for (const output of outputs) {
assert(fs.existsSync(output), `Expect file ${output} to exist`);
}
}
export function verifyOutputsAbsent(fs: vfs.FileSystem, outputs: readonly string[]) {
for (const output of outputs) {
assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`);
}
}
export function generateSourceMapBaselineFiles(sys: ts.System & { writtenFiles: ts.ReadonlyCollection<ts.Path>; }) {
const mapFileNames = ts.mapDefinedIterator(sys.writtenFiles.keys(), f => f.endsWith(".map") ? f : undefined);
while (true) {
const result = mapFileNames.next();
if (result.done) break;
const mapFile = result.value;
const text = Harness.SourceMapRecorder.getSourceMapRecordWithSystem(sys, mapFile);
sys.writeFile(`${mapFile}.baseline.txt`, text);
}
}
function generateBundleFileSectionInfo(sys: ts.System, originalReadCall: ts.System["readFile"], baselineRecorder: Harness.Compiler.WriterAggregator, bundleFileInfo: ts.BundleFileInfo | undefined, outFile: string | undefined) {
if (!ts.length(bundleFileInfo && bundleFileInfo.sections) && !outFile) return; // Nothing to baseline
const content = outFile && sys.fileExists(outFile) ? originalReadCall.call(sys, outFile, "utf8")! : "";
baselineRecorder.WriteLine("======================================================================");
baselineRecorder.WriteLine(`File:: ${outFile}`);
for (const section of bundleFileInfo ? bundleFileInfo.sections : ts.emptyArray) {
baselineRecorder.WriteLine("----------------------------------------------------------------------");
writeSectionHeader(section);
if (section.kind !== ts.BundleFileSectionKind.Prepend) {
writeTextOfSection(section.pos, section.end);
}
else if (section.texts.length > 0) {
ts.Debug.assert(section.pos === ts.first(section.texts).pos);
ts.Debug.assert(section.end === ts.last(section.texts).end);
for (const text of section.texts) {
baselineRecorder.WriteLine(">>--------------------------------------------------------------------");
writeSectionHeader(text);
writeTextOfSection(text.pos, text.end);
}
}
else {
ts.Debug.assert(section.pos === section.end);
}
}
baselineRecorder.WriteLine("======================================================================");
function writeTextOfSection(pos: number, end: number) {
const textLines = content.substring(pos, end).split(/\r?\n/);
for (const line of textLines) {
baselineRecorder.WriteLine(line);
}
}
function writeSectionHeader(section: ts.BundleFileSection) {
baselineRecorder.WriteLine(`${section.kind}: (${section.pos}-${section.end})${section.data ? ":: " + section.data : ""}${section.kind === ts.BundleFileSectionKind.Prepend ? " texts:: " + section.texts.length : ""}`);
}
}
type ReadableProgramBuildInfoDiagnostic = string | [string, readonly ts.ReusableDiagnostic[]];
type ReadableBuilderFileEmit = string & { __readableBuilderFileEmit: any; };
type ReadableProgramBuilderInfoFilePendingEmit = [original: string | [string], emitKind: ReadableBuilderFileEmit];
type ReadableProgramBuildInfoEmitSignature = string | [string, ts.EmitSignature | []];
type ReadableProgramBuildInfoFileInfo<T> = Omit<ts.BuilderState.FileInfo, "impliedFormat"> & {
impliedFormat: string | undefined;
original: T | undefined;
};
type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo,
"fileIdsList" | "fileInfos" |
"referencedMap" | "exportedModulesMap" | "semanticDiagnosticsPerFile" |
"affectedFilesPendingEmit" | "changeFileSet" | "emitSignatures"
> & {
fileNamesList: readonly (readonly string[])[] | undefined;
fileInfos: ts.MapLike<ReadableProgramBuildInfoFileInfo<ts.ProgramMultiFileEmitBuildInfoFileInfo>>;
referencedMap: ts.MapLike<string[]> | undefined;
exportedModulesMap: ts.MapLike<string[]> | undefined;
semanticDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined;
affectedFilesPendingEmit: readonly ReadableProgramBuilderInfoFilePendingEmit[] | undefined;
changeFileSet: readonly string[] | undefined;
emitSignatures: readonly ReadableProgramBuildInfoEmitSignature[] | undefined;
};
type ReadableProgramBuildInfoBundlePendingEmit = [emitKind: ReadableBuilderFileEmit, original: ts.ProgramBuildInfoBundlePendingEmit];
type ReadableProgramBundleEmitBuildInfo = Omit<ts.ProgramBundleEmitBuildInfo, "fileInfos" | "pendingEmit"> & {
fileInfos: ts.MapLike<string | ReadableProgramBuildInfoFileInfo<ts.BuilderState.FileInfo>>;
pendingEmit: ReadableProgramBuildInfoBundlePendingEmit | undefined;
};
type ReadableProgramBuildInfo = ReadableProgramMultiFileEmitBuildInfo | ReadableProgramBundleEmitBuildInfo;
function isReadableProgramBundleEmitBuildInfo(info: ReadableProgramBuildInfo | undefined): info is ReadableProgramBundleEmitBuildInfo {
return !!info && !!ts.outFile(info.options || {});
}
type ReadableBuildInfo = Omit<ts.BuildInfo, "program"> & { program: ReadableProgramBuildInfo | undefined; size: number; };
function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string, buildInfo: ts.BuildInfo) {
let program: ReadableProgramBuildInfo | undefined;
let fileNamesList: string[][] | undefined;
if (buildInfo.program && ts.isProgramBundleEmitBuildInfo(buildInfo.program)) {
const fileInfos: ReadableProgramBundleEmitBuildInfo["fileInfos"] = {};
buildInfo.program?.fileInfos?.forEach((fileInfo, index) =>
fileInfos[toFileName(index + 1 as ts.ProgramBuildInfoFileId)] = ts.isString(fileInfo) ?
fileInfo :
toReadableFileInfo(fileInfo, ts.identity)
);
const pendingEmit = buildInfo.program.pendingEmit;
program = {
...buildInfo.program,
fileInfos,
pendingEmit: pendingEmit === undefined ?
undefined :
[
toReadableBuilderFileEmit(ts.toProgramEmitPending(pendingEmit, buildInfo.program.options)),
pendingEmit
],
};
}
else if (buildInfo.program) {
const fileInfos: ReadableProgramMultiFileEmitBuildInfo["fileInfos"] = {};
buildInfo.program?.fileInfos?.forEach((fileInfo, index) => fileInfos[toFileName(index + 1 as ts.ProgramBuildInfoFileId)] = toReadableFileInfo(fileInfo, ts.toBuilderStateFileInfoForMultiEmit));
fileNamesList = buildInfo.program.fileIdsList?.map(fileIdsListId => fileIdsListId.map(toFileName));
const fullEmitForOptions = buildInfo.program.affectedFilesPendingEmit ? ts.getBuilderFileEmit(buildInfo.program.options || {}) : undefined;
program = buildInfo.program && {
fileNames: buildInfo.program.fileNames,
fileNamesList,
fileInfos: buildInfo.program.fileInfos ? fileInfos : undefined!,
options: buildInfo.program.options,
referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap),
exportedModulesMap: toMapOfReferencedSet(buildInfo.program.exportedModulesMap),
semanticDiagnosticsPerFile: buildInfo.program.semanticDiagnosticsPerFile?.map(d =>
ts.isNumber(d) ?
toFileName(d) :
[toFileName(d[0]), d[1]]
),
affectedFilesPendingEmit: buildInfo.program.affectedFilesPendingEmit?.map(value => toReadableProgramBuilderInfoFilePendingEmit(value, fullEmitForOptions!)),
changeFileSet: buildInfo.program.changeFileSet?.map(toFileName),
emitSignatures: buildInfo.program.emitSignatures?.map(s =>
ts.isNumber(s) ?
toFileName(s) :
[toFileName(s[0]), s[1]]
),
latestChangedDtsFile: buildInfo.program.latestChangedDtsFile,
};
}
const version = buildInfo.version === ts.version ? fakes.version : buildInfo.version;
const result: ReadableBuildInfo = {
// Baseline fixed order for bundle
bundle: buildInfo.bundle && {
...buildInfo.bundle,
js: buildInfo.bundle.js && {
sections: buildInfo.bundle.js.sections,
hash: buildInfo.bundle.js.hash,
mapHash: buildInfo.bundle.js.mapHash,
sources: buildInfo.bundle.js.sources,
},
dts: buildInfo.bundle.dts && {
sections: buildInfo.bundle.dts.sections,
hash: buildInfo.bundle.dts.hash,
mapHash: buildInfo.bundle.dts.mapHash,
sources: buildInfo.bundle.dts.sources,
},
},
program,
version,
size: ts.getBuildInfoText({ ...buildInfo, version }).length,
};
// For now its just JSON.stringify
sys.writeFile(`${buildInfoPath}.readable.baseline.txt`, JSON.stringify(result, /*replacer*/ undefined, 2));
function toFileName(fileId: ts.ProgramBuildInfoFileId) {
return buildInfo.program!.fileNames[fileId - 1];
}
function toFileNames(fileIdsListId: ts.ProgramBuildInfoFileIdListId) {
return fileNamesList![fileIdsListId - 1];
}
function toReadableFileInfo<T>(original: T, toFileInfo: (fileInfo: T) => ts.BuilderState.FileInfo): ReadableProgramBuildInfoFileInfo<T> {
const info = toFileInfo(original);
return {
original: ts.isString(original) ? undefined : original,
...info,
impliedFormat: info.impliedFormat && ts.getNameOfCompilerOptionValue(info.impliedFormat, ts.moduleOptionDeclaration.type),
};
}
function toMapOfReferencedSet(referenceMap: ts.ProgramBuildInfoReferencedMap | undefined): ts.MapLike<string[]> | undefined {
if (!referenceMap) return undefined;
const result: ts.MapLike<string[]> = {};
for (const [fileNamesKey, fileNamesListKey] of referenceMap) {
result[toFileName(fileNamesKey)] = toFileNames(fileNamesListKey);
}
return result;
}
function toReadableProgramBuilderInfoFilePendingEmit(value: ts.ProgramBuilderInfoFilePendingEmit, fullEmitForOptions: ts.BuilderFileEmit): ReadableProgramBuilderInfoFilePendingEmit {
return [
ts.isNumber(value) ? toFileName(value) : [toFileName(value[0])],
toReadableBuilderFileEmit(ts.toBuilderFileEmit(value, fullEmitForOptions)),
];
}
function toReadableBuilderFileEmit(emit: ts.BuilderFileEmit | undefined): ReadableBuilderFileEmit {
let result = "";
if (emit) {
if (emit & ts.BuilderFileEmit.Js) addFlags("Js");
if (emit & ts.BuilderFileEmit.JsMap) addFlags("JsMap");
if (emit & ts.BuilderFileEmit.JsInlineMap) addFlags("JsInlineMap");
if (emit & ts.BuilderFileEmit.Dts) addFlags("Dts");
if (emit & ts.BuilderFileEmit.DtsMap) addFlags("DtsMap");
}
return (result || "None") as ReadableBuilderFileEmit;
function addFlags(flag: string) {
result = result ? `${result} | ${flag}` : flag;
}
}
}
export function toPathWithSystem(sys: ts.System, fileName: string): ts.Path {
return ts.toPath(fileName, sys.getCurrentDirectory(), ts.createGetCanonicalFileName(sys.useCaseSensitiveFileNames));
}
export function baselineBuildInfo(
options: ts.CompilerOptions,
sys: ts.TscCompileSystem | ts.tscWatch.WatchedSystem,
originalReadCall?: ts.System["readFile"],
) {
const buildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(options);
if (!buildInfoPath || !sys.writtenFiles!.has(toPathWithSystem(sys, buildInfoPath))) return;
if (!sys.fileExists(buildInfoPath)) return;
const buildInfo = ts.getBuildInfo(buildInfoPath, (originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!);
if (!buildInfo) return sys.writeFile(`${buildInfoPath}.baseline.txt`, "Error reading valid buildinfo file");
generateBuildInfoProgramBaseline(sys, buildInfoPath, buildInfo);
if (!ts.outFile(options)) return;
const { jsFilePath, declarationFilePath } = ts.getOutputPathsForBundle(options, /*forceDts*/ false);
const bundle = buildInfo.bundle;
if (!bundle || (!ts.length(bundle.js && bundle.js.sections) && !ts.length(bundle.dts && bundle.dts.sections))) return;
// Write the baselines:
const baselineRecorder = new Harness.Compiler.WriterAggregator();
generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.js, jsFilePath);
generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.dts, declarationFilePath);
baselineRecorder.Close();
const text = baselineRecorder.lines.join("\r\n");
sys.writeFile(`${buildInfoPath}.baseline.txt`, text);
}
interface VerifyTscEditDiscrepanciesInput {
index: number;
scenario: ts.TestTscCompile["scenario"];
subScenario: ts.TestTscCompile["subScenario"];
baselines: string[] | undefined;
commandLineArgs: ts.TestTscCompile["commandLineArgs"];
modifyFs: ts.TestTscCompile["modifyFs"];
editFs: TestTscEdit["modifyFs"];
baseFs: vfs.FileSystem;
newSys: ts.TscCompileSystem;
discrepancyExplanation: TestTscEdit["discrepancyExplanation"];
}
function verifyTscEditDiscrepancies({
index, scenario, subScenario, commandLineArgs,
discrepancyExplanation, baselines,
modifyFs, editFs, baseFs, newSys
}: VerifyTscEditDiscrepanciesInput): string[] | undefined {
const sys = ts.testTscCompile({
scenario,
subScenario,
fs: () => baseFs.makeReadonly(),
commandLineArgs,
modifyFs: fs => {
if (modifyFs) modifyFs(fs);
editFs(fs);
},
disableUseFileVersionAsSignature: true,
});
let headerAdded = false;
for (const outputFile of ts.arrayFrom(sys.writtenFiles.keys())) {
const cleanBuildText = sys.readFile(outputFile);
const incrementalBuildText = newSys.readFile(outputFile);
if (ts.isBuildInfoFile(outputFile)) {
// Check only presence and absence and not text as we will do that for readable baseline
if (!sys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in clean build:: File:: ${outputFile}`);
if (!newSys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in incremental build:: File:: ${outputFile}`);
verifyPresenceAbsence(incrementalBuildText, cleanBuildText, `Incremental and clean tsbuildinfo file presence differs:: File:: ${outputFile}`);
}
else if (!ts.fileExtensionIs(outputFile, ".tsbuildinfo.readable.baseline.txt")) {
verifyTextEqual(incrementalBuildText, cleanBuildText, `File: ${outputFile}`);
}
else if (incrementalBuildText !== cleanBuildText) {
// Verify build info without affectedFilesPendingEmit
const { buildInfo: incrementalBuildInfo, readableBuildInfo: incrementalReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText);
const { buildInfo: cleanBuildInfo, readableBuildInfo: cleanReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText);
verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, `TsBuild info text without affectedFilesPendingEmit:: ${outputFile}::`);
// Verify file info sigantures
verifyMapLike(
incrementalReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"],
cleanReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"],
(key, incrementalFileInfo, cleanFileInfo) => {
if (incrementalFileInfo.signature !== cleanFileInfo.signature && incrementalFileInfo.signature !== incrementalFileInfo.version) {
return [
`Incremental signature is neither dts signature nor file version for File:: ${key}`,
`Incremental:: ${JSON.stringify(incrementalFileInfo, /*replacer*/ undefined, 2)}`,
`Clean:: ${JSON.stringify(cleanFileInfo, /*replacer*/ undefined, 2)}`
];
}
},
`FileInfos:: File:: ${outputFile}`
);
if (!isReadableProgramBundleEmitBuildInfo(incrementalReadableBuildInfo?.program)) {
ts.Debug.assert(!isReadableProgramBundleEmitBuildInfo(cleanReadableBuildInfo?.program));
// Verify exportedModulesMap
verifyMapLike(
incrementalReadableBuildInfo?.program?.exportedModulesMap,
cleanReadableBuildInfo?.program?.exportedModulesMap,
(key, incrementalReferenceSet, cleanReferenceSet) => {
if (!ts.arrayIsEqualTo(incrementalReferenceSet, cleanReferenceSet) && !ts.arrayIsEqualTo(incrementalReferenceSet, (incrementalReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key])) {
return [
`Incremental Reference set is neither from dts nor files reference map for File:: ${key}::`,
`Incremental:: ${JSON.stringify(incrementalReferenceSet, /*replacer*/ undefined, 2)}`,
`Clean:: ${JSON.stringify(cleanReferenceSet, /*replacer*/ undefined, 2)}`,
`IncrementalReferenceMap:: ${JSON.stringify((incrementalReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key], /*replacer*/ undefined, 2)}`,
`CleanReferenceMap:: ${JSON.stringify((cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key], /*replacer*/ undefined, 2)}`,
];
}
},
`exportedModulesMap:: File:: ${outputFile}`
);
// Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option
if (incrementalReadableBuildInfo?.program?.affectedFilesPendingEmit) {
if (cleanReadableBuildInfo?.program?.affectedFilesPendingEmit === undefined) {
addBaseline(
`Incremental build contains affectedFilesPendingEmit, clean build does not have it: ${outputFile}::`,
`Incremental buildInfoText:: ${incrementalBuildText}`,
`Clean buildInfoText:: ${cleanBuildText}`
);
}
let expectedIndex = 0;
incrementalReadableBuildInfo.program.affectedFilesPendingEmit.forEach(([actualFileOrArray]) => {
const actualFile = ts.isString(actualFileOrArray) ? actualFileOrArray : actualFileOrArray[0];
expectedIndex = ts.findIndex(
(cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).affectedFilesPendingEmit,
([expectedFileOrArray]) => actualFile === (ts.isString(expectedFileOrArray) ? expectedFileOrArray : expectedFileOrArray[0]),
expectedIndex
);
if (expectedIndex === -1) {
addBaseline(
`Incremental build contains ${actualFile} file as pending emit, clean build does not have it: ${outputFile}::`,
`Incremental buildInfoText:: ${incrementalBuildText}`,
`Clean buildInfoText:: ${cleanBuildText}`
);
}
expectedIndex++;
});
}
}
}
}
if (!headerAdded && discrepancyExplanation) addBaseline("*** Supplied discrepancy explanation but didnt file any difference");
return baselines;
function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, message: string) {
if (incrementalText !== cleanText) writeNotEqual(incrementalText, cleanText, message);
}
function verifyMapLike<T>(incremental: ts.MapLike<T> | undefined, clean: ts.MapLike<T> | undefined, verifyValue: (key: string, incrementalValue: T, cleanValue: T) => string[] | undefined, message: string) {
verifyPresenceAbsence(incremental, clean, `Incremental and clean do not match:: ${message}`);
if (!incremental || !clean) return;
const incrementalMap = new ts.Map(ts.getEntries(incremental));
const cleanMap = new ts.Map(ts.getEntries(clean));
if (incrementalMap.size !== cleanMap.size) {
addBaseline(
`Incremental and clean size of maps do not match:: ${message}`,
`Incremental: ${JSON.stringify(incremental, /*replacer*/ undefined, 2)}`,
`Clean: ${JSON.stringify(clean, /*replacer*/ undefined, 2)}`,
);
return;
}
cleanMap.forEach((cleanValue, key) => {
const incrementalValue = incrementalMap.get(key);
if (!incrementalValue) {
addBaseline(
`Incremental does not contain ${key} which is present in clean:: ${message}`,
`Incremental: ${JSON.stringify(incremental, /*replacer*/ undefined, 2)}`,
`Clean: ${JSON.stringify(clean, /*replacer*/ undefined, 2)}`,
);
}
else {
const result = verifyValue(key, incrementalMap.get(key)!, cleanValue);
if (result) addBaseline(...result);
}
});
}
function verifyPresenceAbsence<T>(actual: T | undefined, expected: T | undefined, message: string) {
if (expected === undefined) {
if (actual === undefined) return;
}
else {
if (actual !== undefined) return;
}
writeNotEqual(actual, expected, message);
}
function writeNotEqual<T>(actual: T | undefined, expected: T | undefined, message: string) {
addBaseline(
message,
"CleanBuild:",
ts.isString(expected) ? expected : JSON.stringify(expected),
"IncrementalBuild:",
ts.isString(actual) ? actual : JSON.stringify(actual),
);
}
function addBaseline(...text: string[]) {
if (!baselines || !headerAdded) {
(baselines ||= []).push(`${index}:: ${subScenario}`, ...(discrepancyExplanation?.()|| ["*** Needs explanation"]));
headerAdded = true;
}
baselines.push(...text);
}
}
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): {
buildInfo: string | undefined;
readableBuildInfo?: ReadableBuildInfo;
} {
if (!text) return { buildInfo: text };
const readableBuildInfo = JSON.parse(text) as ReadableBuildInfo;
let sanitizedFileInfos: ts.MapLike<string | Omit<ReadableProgramBuildInfoFileInfo<ts.ProgramMultiFileEmitBuildInfoFileInfo> | ReadableProgramBuildInfoFileInfo<ts.BuilderState.FileInfo>, "signature" | "original"> & { signature: undefined; original: undefined; }> | undefined;
if (readableBuildInfo.program?.fileInfos) {
sanitizedFileInfos = {};
for (const id in readableBuildInfo.program.fileInfos) {
if (ts.hasProperty(readableBuildInfo.program.fileInfos, id)) {
const info = readableBuildInfo.program.fileInfos[id];
sanitizedFileInfos[id] = ts.isString(info) ? info : { ...info, signature: undefined, original: undefined };
}
}
}
return {
buildInfo: JSON.stringify({
...readableBuildInfo,
program: readableBuildInfo.program && {
...readableBuildInfo.program,
fileNames: undefined,
fileNamesList: undefined,
fileInfos: sanitizedFileInfos,
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
options: { ...readableBuildInfo.program.options, noEmit: undefined },
exportedModulesMap: undefined,
affectedFilesPendingEmit: undefined,
latestChangedDtsFile: readableBuildInfo.program.latestChangedDtsFile ? "FakeFileName" : undefined,
},
size: undefined, // Size doesnt need to be equal
}, /*replacer*/ undefined, 2),
readableBuildInfo,
};
}
export enum CleanBuildDescrepancy {
CleanFileTextDifferent,
CleanFilePresent,
}
export interface TestTscEdit {
modifyFs: (fs: vfs.FileSystem) => void;
subScenario: string;
commandLineArgs?: readonly string[];
/** An array of lines to be printed in order when a discrepancy is detected */
discrepancyExplanation?: () => readonly string[];
}
export interface VerifyTscWithEditsInput extends ts.TestTscCompile {
edits: TestTscEdit[];
}
/**
* Verify non watch tsc invokcation after each edit
*/
export function verifyTscWithEdits({
subScenario, fs, scenario, commandLineArgs,
baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms,
edits
}: VerifyTscWithEditsInput) {
describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario} serializedEdits`, () => {
let sys: ts.TscCompileSystem;
let baseFs: vfs.FileSystem;
let editsSys: ts.TscCompileSystem[];
before(() => {
ts.Debug.assert(!!edits.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`);
baseFs = fs().makeReadonly();
sys = ts.testTscCompile({
scenario,
subScenario,
fs: () => baseFs,
commandLineArgs,
modifyFs,
baselineSourceMap,
baselineReadFileCalls,
baselinePrograms
});
edits.forEach((
{ modifyFs, subScenario: editScenario, commandLineArgs: editCommandLineArgs },
index
) => {
(editsSys || (editsSys = [])).push(ts.testTscCompile({
scenario,
subScenario: editScenario || subScenario,
diffWithInitial: true,
fs: () => index === 0 ? sys.vfs : editsSys[index - 1].vfs,
commandLineArgs: editCommandLineArgs || commandLineArgs,
modifyFs,
baselineSourceMap,
baselineReadFileCalls,
baselinePrograms
}));
});
});
after(() => {
baseFs = undefined!;
sys = undefined!;
editsSys = undefined!;
});
ts.verifyTscBaseline(() => ({
baseLine: () => {
const { file, text } = sys.baseLine();
const texts: string[] = [text];
editsSys.forEach((sys, index) => {
const incrementalScenario = edits[index];
texts.push("");
texts.push(`Change:: ${incrementalScenario.subScenario}`);
texts.push(sys.baseLine().text);
});
return { file, text: texts.join("\r\n") };
}
}));
it("tsc invocation after edit and clean build correctness", () => {
let baselines: string[] | undefined;
for (let index = 0; index < edits.length; index++) {
baselines = verifyTscEditDiscrepancies({
index,
scenario,
subScenario: edits[index].subScenario,
baselines,
baseFs,
newSys: editsSys[index],
commandLineArgs: edits[index].commandLineArgs || commandLineArgs,
discrepancyExplanation: edits[index].discrepancyExplanation,
editFs: fs => {
for (let i = 0; i <= index; i++) {
edits[i].modifyFs(fs);
}
},
modifyFs
});
}
Harness.Baseline.runBaseline(
`${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}-discrepancies.js`,
baselines ? baselines.join("\r\n") : null // eslint-disable-line no-null/no-null
);
});
});
}
export function enableStrict(fs: vfs.FileSystem, path: string) {
replaceText(fs, path, `"strict": false`, `"strict": true`);
}
export function addTestPrologue(fs: vfs.FileSystem, path: string, prologue: string) {
prependText(fs, path, `${prologue}
`);
}
export function addShebang(fs: vfs.FileSystem, project: string, file: string) {
prependText(fs, `src/${project}/${file}.ts`, `#!someshebang ${project} ${file}
`);
}
export function restContent(project: string, file: string) {
return `function for${project}${file}Rest() {
const { b, ...rest } = { a: 10, b: 30, yy: 30 };
}`;
}
function nonrestContent(project: string, file: string) {
return `function for${project}${file}Rest() { }`;
}
export function addRest(fs: vfs.FileSystem, project: string, file: string) {
appendText(fs, `src/${project}/${file}.ts`, restContent(project, file));
}
export function removeRest(fs: vfs.FileSystem, project: string, file: string) {
replaceText(fs, `src/${project}/${file}.ts`, restContent(project, file), nonrestContent(project, file));
}
export function addStubFoo(fs: vfs.FileSystem, project: string, file: string) {
appendText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file));
}
export function changeStubToRest(fs: vfs.FileSystem, project: string, file: string) {
replaceText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file), restContent(project, file));
}
export function addSpread(fs: vfs.FileSystem, project: string, file: string) {
const path = `src/${project}/${file}.ts`;
const content = fs.readFileSync(path, "utf8");
fs.writeFileSync(path, `${content}
function ${project}${file}Spread(...b: number[]) { }
const ${project}${file}_ar = [20, 30];
${project}${file}Spread(10, ...${project}${file}_ar);`);
replaceText(fs, `src/${project}/tsconfig.json`, `"strict": false,`, `"strict": false,
"downlevelIteration": true,`);
}
export function getTripleSlashRef(project: string) {
return `/src/${project}/tripleRef.d.ts`;
}
export function addTripleSlashRef(fs: vfs.FileSystem, project: string, file: string) {
fs.writeFileSync(getTripleSlashRef(project), `declare class ${project}${file} { }`);
prependText(fs, `src/${project}/${file}.ts`, `///<reference path="./tripleRef.d.ts"/>
const ${file}Const = new ${project}${file}();
`);
}

View File

@@ -1,16 +1,16 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import { appendText, loadProjectFromDisk, replaceText, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => {
let projFs: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/inferredTypeFromTransitiveModule");
projFs = loadProjectFromDisk("tests/projects/inferredTypeFromTransitiveModule");
});
after(() => {
projFs = undefined!;
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "inferredTypeFromTransitiveModule",
subScenario: "inferred type from transitive module",
fs: () => projFs,
@@ -27,7 +27,7 @@ describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => {
],
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
subScenario: "inferred type from transitive module with isolatedModules",
fs: () => projFs,
scenario: "inferredTypeFromTransitiveModule",
@@ -45,14 +45,14 @@ describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => {
]
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "inferredTypeFromTransitiveModule",
subScenario: "reports errors in files affected by change in signature with isolatedModules",
fs: () => projFs,
commandLineArgs: ["--b", "/src", "--verbose"],
modifyFs: fs => {
changeToIsolatedModules(fs);
ts.appendText(fs, "/src/lazyIndex.ts", `
appendText(fs, "/src/lazyIndex.ts", `
import { default as bar } from './bar';
bar("hello");`);
},
@@ -71,20 +71,20 @@ bar("hello");`);
},
{
subScenario: "Fix Error",
modifyFs: fs => ts.replaceText(fs, "/src/lazyIndex.ts", `bar("hello")`, "bar()")
modifyFs: fs => replaceText(fs, "/src/lazyIndex.ts", `bar("hello")`, "bar()")
},
]
});
});
function changeToIsolatedModules(fs: vfs.FileSystem) {
ts.replaceText(fs, "/src/tsconfig.json", `"incremental": true`, `"incremental": true, "isolatedModules": true`);
replaceText(fs, "/src/tsconfig.json", `"incremental": true`, `"incremental": true, "isolatedModules": true`);
}
function changeBarParam(fs: vfs.FileSystem) {
ts.replaceText(fs, "/src/bar.ts", "param: string", "");
replaceText(fs, "/src/bar.ts", "param: string", "");
}
function changeBarParamBack(fs: vfs.FileSystem) {
ts.replaceText(fs, "/src/bar.ts", "foobar()", "foobar(param: string)");
replaceText(fs, "/src/bar.ts", "foobar()", "foobar(param: string)");
}

View File

@@ -1,11 +1,11 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { loadProjectFromFiles, replaceText, symbolLibContent, verifyTsc, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: javascriptProjectEmit::", () => {
ts.verifyTsc({
verifyTsc({
scenario: "javascriptProjectEmit",
subScenario: `loads js-based projects and emits them correctly`,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/common/nominal.js": Utils.dedent`
/**
* @template T, Name
@@ -86,14 +86,14 @@ describe("unittests:: tsbuild:: javascriptProjectEmit::", () => {
"declaration": true
}
}`,
}, ts.symbolLibContent),
}, symbolLibContent),
commandLineArgs: ["-b", "/src"]
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "javascriptProjectEmit",
subScenario: `modifies outfile js projects and concatenates them correctly`,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/common/nominal.js": Utils.dedent`
/**
* @template T, Name
@@ -176,18 +176,18 @@ describe("unittests:: tsbuild:: javascriptProjectEmit::", () => {
"declaration": true
}
}`,
}, ts.symbolLibContent),
}, symbolLibContent),
commandLineArgs: ["-b", "/src"],
edits: [{
subScenario: "incremental-declaration-doesnt-change",
modifyFs: fs => ts.replaceText(fs, "/src/sub-project/index.js", "null", "undefined")
modifyFs: fs => replaceText(fs, "/src/sub-project/index.js", "null", "undefined")
}]
});
ts.verifyTsc({
verifyTsc({
scenario: "javascriptProjectEmit",
subScenario: `loads js-based projects with non-moved json files and emits them correctly`,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/common/obj.json": Utils.dedent`
{
"val": 42
@@ -267,7 +267,7 @@ describe("unittests:: tsbuild:: javascriptProjectEmit::", () => {
"declaration": true
}
}`,
}, ts.symbolLibContent),
}, symbolLibContent),
commandLineArgs: ["-b", "/src"]
});
});

View File

@@ -1,19 +1,19 @@
import * as ts from "../../_namespaces/ts";
import { replaceText, appendText, loadProjectFromDisk, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: lateBoundSymbol:: interface is merged and contains late bound member", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
subScenario: "interface is merged and contains late bound member",
fs: () => ts.loadProjectFromDisk("tests/projects/lateBoundSymbol"),
fs: () => loadProjectFromDisk("tests/projects/lateBoundSymbol"),
scenario: "lateBoundSymbol",
commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"],
edits: [
{
subScenario: "incremental-declaration-doesnt-change",
modifyFs: fs => ts.replaceText(fs, "/src/src/main.ts", "const x = 10;", ""),
modifyFs: fs => replaceText(fs, "/src/src/main.ts", "const x = 10;", ""),
},
{
subScenario: "incremental-declaration-doesnt-change",
modifyFs: fs => ts.appendText(fs, "/src/src/main.ts", "const x = 10;"),
modifyFs: fs => appendText(fs, "/src/src/main.ts", "const x = 10;"),
},
]
});

View File

@@ -1,32 +1,35 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { createWatchedSystem, libFile } from "../virtualFileSystemWithWatch";
import { loadProjectFromFiles, noChangeOnlyRuns, verifyTsc, verifyTscWithEdits } from "../tsc/helpers";
import { verifyTscWatch } from "../tscWatch/helpers";
describe("unittests:: tsbuild:: moduleResolution:: handles the modules and options from referenced project correctly", () => {
function sys(optionsToExtend?: ts.CompilerOptions) {
return ts.tscWatch.createWatchedSystem([
return createWatchedSystem([
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/index.ts`,
path: `/user/username/projects/myproject/packages/pkg1/index.ts`,
content: Utils.dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/tsconfig.json`,
path: `/user/username/projects/myproject/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { outDir: "build", ...optionsToExtend },
references: [{ path: "../pkg2" }]
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/const.ts`,
path: `/user/username/projects/myproject/packages/pkg2/const.ts`,
content: `export type TheNum = 42;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/index.ts`,
path: `/user/username/projects/myproject/packages/pkg2/index.ts`,
content: `export type { TheNum } from 'const';`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/tsconfig.json`,
path: `/user/username/projects/myproject/packages/pkg2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
@@ -37,7 +40,7 @@ describe("unittests:: tsbuild:: moduleResolution:: handles the modules and optio
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`,
path: `/user/username/projects/myproject/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
@@ -45,14 +48,14 @@ describe("unittests:: tsbuild:: moduleResolution:: handles the modules and optio
})
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg2`,
symLink: `${ts.tscWatch.projectRoot}/packages/pkg2`,
path: `/user/username/projects/myproject/node_modules/pkg2`,
symLink: `/user/username/projects/myproject/packages/pkg2`,
},
ts.tscWatch.libFile
], { currentDirectory: ts.tscWatch.projectRoot });
libFile
], { currentDirectory: "/user/username/projects/myproject" });
}
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `resolves specifier in output declaration file from referenced project correctly`,
sys,
@@ -60,7 +63,7 @@ describe("unittests:: tsbuild:: moduleResolution:: handles the modules and optio
changes: ts.emptyArray
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `resolves specifier in output declaration file from referenced project correctly with preserveSymlinks`,
sys: () => sys({ preserveSymlinks: true }),
@@ -68,10 +71,10 @@ describe("unittests:: tsbuild:: moduleResolution:: handles the modules and optio
changes: ts.emptyArray
});
ts.verifyTsc({
verifyTsc({
scenario: "moduleResolution",
subScenario: `type reference resolution uses correct options for different resolution options referenced project`,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/packages/pkg1_index.ts": `export const theNum: TheNum = "type1";`,
"/src/packages/pkg1.tsconfig.json": JSON.stringify({
compilerOptions: { composite: true, typeRoots: ["./typeroot1"] },
@@ -90,10 +93,10 @@ describe("unittests:: tsbuild:: moduleResolution:: handles the modules and optio
});
describe("unittests:: tsbuild:: moduleResolution:: impliedNodeFormat differs between projects for shared file", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "moduleResolution",
subScenario: "impliedNodeFormat differs between projects for shared file",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/projects/a/src/index.ts": "",
"/src/projects/a/tsconfig.json": JSON.stringify({
compilerOptions: { strict: true }
@@ -115,8 +118,8 @@ describe("unittests:: tsbuild:: moduleResolution:: impliedNodeFormat differs bet
types: "index.d.ts",
}),
}),
modifyFs: fs => fs.writeFileSync("/lib/lib.es2022.full.d.ts", ts.tscWatch.libFile.content),
modifyFs: fs => fs.writeFileSync("/lib/lib.es2022.full.d.ts", libFile.content),
commandLineArgs: ["-b", "/src/projects/a", "/src/projects/b", "--verbose", "--traceResolution", "--explainFiles"],
edits: ts.noChangeOnlyRuns
edits: noChangeOnlyRuns
});
});

View File

@@ -1,12 +1,13 @@
import * as ts from "../../_namespaces/ts";
import { libFile } from "../virtualFileSystemWithWatch";
import * as Utils from "../../_namespaces/Utils";
import { loadProjectFromFiles, symbolLibContent, verifyTsc } from "../tsc/helpers";
// https://github.com/microsoft/TypeScript/issues/31696
describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers to referenced projects resolve correctly", () => {
ts.verifyTsc({
verifyTsc({
scenario: "moduleSpecifiers",
subScenario: `synthesized module specifiers resolve correctly`,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/solution/common/nominal.ts": Utils.dedent`
export declare type Nominal<T, Name extends string> = T & {
[Symbol.species]: Name;
@@ -86,17 +87,17 @@ describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers
],
"include": []
}`
}, ts.symbolLibContent),
}, symbolLibContent),
commandLineArgs: ["-b", "/src", "--verbose"]
});
});
// https://github.com/microsoft/TypeScript/issues/44434 but with `module: node16`, some `exports` maps blocking direct access, and no `baseUrl`
describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers across referenced projects resolve correctly", () => {
ts.verifyTsc({
verifyTsc({
scenario: "moduleSpecifiers",
subScenario: `synthesized module specifiers across projects resolve correctly`,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/src-types/index.ts": Utils.dedent`
export * from './dogconfig.js';`,
"/src/src-types/dogconfig.ts": Utils.dedent`
@@ -180,7 +181,7 @@ describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers
}`,
}, ""),
modifyFs: fs => {
fs.writeFileSync("/lib/lib.es2022.full.d.ts", ts.tscWatch.libFile.content);
fs.writeFileSync("/lib/lib.es2022.full.d.ts", libFile.content);
fs.symlinkSync("/src", "/src/src-types/node_modules");
fs.symlinkSync("/src", "/src/src-dogs/node_modules");
},

View File

@@ -1,11 +1,11 @@
import * as ts from "../../_namespaces/ts";
import { loadProjectFromFiles, noChangeRun, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: noEmit", () => {
function verifyNoEmitWorker(subScenario: string, aTsContent: string, commandLineArgs: readonly string[]) {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "noEmit",
subScenario,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/a.ts": aTsContent,
"/src/tsconfig.json": JSON.stringify({
compilerOptions: { noEmit: true }
@@ -13,12 +13,12 @@ describe("unittests:: tsbuild:: noEmit", () => {
}),
commandLineArgs,
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "Fix error",
modifyFs: fs => fs.writeFileSync("/src/a.ts", `const a = "hello"`),
},
ts.noChangeRun,
noChangeRun,
],
baselinePrograms: true,
});

View File

@@ -1,22 +1,22 @@
import * as ts from "../../_namespaces/ts";
import { loadProjectFromDisk, noChangeRun, noChangeWithExportsDiscrepancyRun, verifyTscWithEdits } from "../tsc/helpers";
import * as vfs from "../../_namespaces/vfs";
describe("unittests:: tsbuild - with noEmitOnError", () => {
let projFs: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/noEmitOnError");
projFs = loadProjectFromDisk("tests/projects/noEmitOnError");
});
after(() => {
projFs = undefined!;
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "noEmitOnError",
subScenario: "syntax errors",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig.json"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "Fix error",
modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db";
@@ -24,18 +24,18 @@ const a = {
lastName: 'sdsd'
};`, "utf-8"),
},
ts.noChangeRun,
noChangeRun,
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "noEmitOnError",
subScenario: "syntax errors with incremental",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig.json", "--incremental"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "Fix error",
modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db";
@@ -43,12 +43,12 @@ const a = {
lastName: 'sdsd'
};`, "utf-8"),
},
ts.noChangeRun,
noChangeRun,
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "noEmitOnError",
subScenario: "semantic errors",
fs: () => projFs,
@@ -56,18 +56,18 @@ const a = {
const a: string = 10;`, "utf-8"),
commandLineArgs: ["--b", "/src/tsconfig.json"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "Fix error",
modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db";
const a: string = "hello";`, "utf-8"),
},
ts.noChangeRun,
noChangeRun,
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "noEmitOnError",
subScenario: "semantic errors with incremental",
fs: () => projFs,
@@ -75,13 +75,13 @@ const a: string = "hello";`, "utf-8"),
const a: string = 10;`, "utf-8"),
commandLineArgs: ["--b", "/src/tsconfig.json", "--incremental"],
edits: [
ts.noChangeWithExportsDiscrepancyRun,
noChangeWithExportsDiscrepancyRun,
{
subScenario: "Fix error",
modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db";
const a: string = "hello";`, "utf-8"),
},
ts.noChangeRun,
noChangeRun,
],
baselinePrograms: true,
});

View File

@@ -1,12 +1,13 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import * as fakes from "../../_namespaces/fakes";
import { addRest, addShebang, addSpread, addStubFoo, addTestPrologue, addTripleSlashRef, appendText, changeStubToRest, createSolutionBuilderHostForBaseline, enableStrict, loadProjectFromDisk, noChangeOnlyRuns, prependText, removeRest, replaceText, testTscCompileLike, TestTscEdit, TscCompileSystem, verifyTsc, verifyTscCompileLike, verifyTscWithEdits, VerifyTscWithEditsInput } from "../tsc/helpers";
describe("unittests:: tsbuild:: outFile::", () => {
let outFileFs: vfs.FileSystem;
let outFileWithBuildFs: vfs.FileSystem;
before(() => {
outFileFs = ts.loadProjectFromDisk("tests/projects/outfile-concat");
outFileFs = loadProjectFromDisk("tests/projects/outfile-concat");
});
after(() => {
outFileFs = undefined!;
@@ -32,17 +33,17 @@ describe("unittests:: tsbuild:: outFile::", () => {
baselineOnly,
additionalCommandLineArgs,
}: VerifyOutFileScenarioInput) {
const edits: ts.TestTscEdit[] = [];
const edits: TestTscEdit[] = [];
if (!ignoreDtsChanged) {
edits.push({
subScenario: "incremental-declaration-changes",
modifyFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", "Hello", "Hola"),
modifyFs: fs => replaceText(fs, "/src/first/first_PART1.ts", "Hello", "Hola"),
});
}
if (!ignoreDtsUnchanged) {
edits.push({
subScenario: "incremental-declaration-doesnt-change",
modifyFs: fs => ts.appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"),
modifyFs: fs => appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"),
});
}
if (modifyAgainFs) {
@@ -51,7 +52,7 @@ describe("unittests:: tsbuild:: outFile::", () => {
modifyFs: modifyAgainFs
});
}
const input: ts.VerifyTscWithEditsInput = {
const input: VerifyTscWithEditsInput = {
subScenario,
fs: () => outFileFs,
scenario: "outfile-concat",
@@ -62,8 +63,8 @@ describe("unittests:: tsbuild:: outFile::", () => {
edits,
};
return edits.length ?
ts.verifyTscWithEdits(input) :
ts.verifyTsc(input);
verifyTscWithEdits(input) :
verifyTsc(input);
}
// Verify initial + incremental edits
@@ -80,7 +81,7 @@ describe("unittests:: tsbuild:: outFile::", () => {
// Verify baseline with build info + dts unChanged
verifyOutFileScenario({
subScenario: "when final project is not composite but uses project references",
modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""),
modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -88,7 +89,7 @@ describe("unittests:: tsbuild:: outFile::", () => {
// Verify baseline with build info
verifyOutFileScenario({
subScenario: "when final project is not composite but incremental",
modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"incremental": true,`),
modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"incremental": true,`),
ignoreDtsChanged: true,
ignoreDtsUnchanged: true,
baselineOnly: true
@@ -97,7 +98,7 @@ describe("unittests:: tsbuild:: outFile::", () => {
// Verify baseline with build info
verifyOutFileScenario({
subScenario: "when final project specifies tsBuildInfoFile",
modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"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,
@@ -108,22 +109,22 @@ describe("unittests:: tsbuild:: outFile::", () => {
if (outFileWithBuildFs) return outFileWithBuildFs;
const fs = outFileFs.shadow();
const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" });
const host = ts.createSolutionBuilderHostForBaseline(sys as ts.TscCompileSystem);
const host = createSolutionBuilderHostForBaseline(sys as TscCompileSystem);
const builder = ts.createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: true });
builder.build();
fs.makeReadonly();
return outFileWithBuildFs = fs;
}
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "outFile",
subScenario: "clean projects",
fs: getOutFileFsAfterBuild,
commandLineArgs: ["--b", "/src/third", "--clean"],
edits: ts.noChangeOnlyRuns
edits: noChangeOnlyRuns
});
ts.verifyTsc({
verifyTsc({
scenario: "outFile",
subScenario: "verify buildInfo absence results in new build",
fs: getOutFileFsAfterBuild,
@@ -131,50 +132,50 @@ describe("unittests:: tsbuild:: outFile::", () => {
modifyFs: fs => fs.unlinkSync("/src/first/bin/first-output.tsbuildinfo"),
});
ts.verifyTsc({
verifyTsc({
scenario: "outFile",
subScenario: "tsbuildinfo is not generated when incremental is set to false",
fs: () => outFileFs,
commandLineArgs: ["--b", "/src/third", "--verbose"],
modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""),
modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""),
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
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
const buildHost = ts.createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion");
const buildHost = createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion");
const builder = ts.createSolutionBuilder(buildHost, ["/src/third"], { verbose: true });
sys.exit(builder.build());
}
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "outFile",
subScenario: "rebuilds completely when command line incremental flag changes between non dts changes",
fs: () => outFileFs,
// Make non composite third project
modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""),
modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""),
// Build with command line incremental
commandLineArgs: ["--b", "/src/third", "--i", "--verbose"],
edits: [
{
subScenario: "Make non incremental build with change in file that doesnt affect dts",
modifyFs: fs => ts.appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"),
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",
modifyFs: fs => ts.appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"),
modifyFs: fs => appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"),
commandLineArgs: ["--b", "/src/third", "--verbose", "--incremental"],
}
]
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "outFile",
subScenario: "when input file text does not change but its modified time changes",
fs: () => outFileFs,
@@ -190,25 +191,25 @@ describe("unittests:: tsbuild:: outFile::", () => {
]
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
verifyTscCompileLike(testTscCompileLike, {
scenario: "outFile",
subScenario: "builds till project specified",
fs: () => outFileFs,
commandLineArgs: ["--build", "/src/second/tsconfig.json"],
compile: sys => {
const buildHost = ts.createSolutionBuilderHostForBaseline(sys);
const buildHost = createSolutionBuilderHostForBaseline(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {});
sys.exit(builder.build("/src/second/tsconfig.json"));
}
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
verifyTscCompileLike(testTscCompileLike, {
scenario: "outFile",
subScenario: "cleans till project specified",
fs: getOutFileFsAfterBuild,
commandLineArgs: ["--build", "--clean", "/src/second/tsconfig.json"],
compile: sys => {
const buildHost = ts.createSolutionBuilderHostForBaseline(sys);
const buildHost = createSolutionBuilderHostForBaseline(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], { verbose: true });
sys.exit(builder.clean("/src/second/tsconfig.json"));
}
@@ -221,18 +222,18 @@ describe("unittests:: tsbuild:: outFile::", () => {
verifyOutFileScenario({
subScenario: "strict in all projects",
modifyFs: fs => {
ts.enableStrict(fs, "/src/first/tsconfig.json");
ts.enableStrict(fs, "/src/second/tsconfig.json");
ts.enableStrict(fs, "/src/third/tsconfig.json");
enableStrict(fs, "/src/first/tsconfig.json");
enableStrict(fs, "/src/second/tsconfig.json");
enableStrict(fs, "/src/third/tsconfig.json");
},
modifyAgainFs: fs => ts.addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue"`)
modifyAgainFs: fs => addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue"`)
});
// Verify ignore dtsChanged
verifyOutFileScenario({
subScenario: "strict in one dependency",
modifyFs: fs => ts.enableStrict(fs, "/src/second/tsconfig.json"),
modifyAgainFs: fs => ts.addTestPrologue(fs, "src/first/first_PART1.ts", `"myPrologue"`),
modifyFs: fs => enableStrict(fs, "/src/second/tsconfig.json"),
modifyAgainFs: fs => addTestPrologue(fs, "src/first/first_PART1.ts", `"myPrologue"`),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -241,28 +242,28 @@ describe("unittests:: tsbuild:: outFile::", () => {
verifyOutFileScenario({
subScenario: "multiple prologues in all projects",
modifyFs: fs => {
ts.enableStrict(fs, "/src/first/tsconfig.json");
ts.addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue"`);
ts.enableStrict(fs, "/src/second/tsconfig.json");
ts.addTestPrologue(fs, "/src/second/second_part1.ts", `"myPrologue"`);
ts.addTestPrologue(fs, "/src/second/second_part2.ts", `"myPrologue2";`);
ts.enableStrict(fs, "/src/third/tsconfig.json");
ts.addTestPrologue(fs, "/src/third/third_part1.ts", `"myPrologue";`);
ts.addTestPrologue(fs, "/src/third/third_part1.ts", `"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 => ts.addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`)
modifyAgainFs: fs => addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`)
});
// Verify ignore dtsChanged
verifyOutFileScenario({
subScenario: "multiple prologues in different projects",
modifyFs: fs => {
ts.enableStrict(fs, "/src/first/tsconfig.json");
ts.addTestPrologue(fs, "/src/second/second_part1.ts", `"myPrologue"`);
ts.addTestPrologue(fs, "/src/second/second_part2.ts", `"myPrologue2";`);
ts.enableStrict(fs, "/src/third/tsconfig.json");
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 => ts.addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`),
modifyAgainFs: fs => addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -275,17 +276,17 @@ describe("unittests:: tsbuild:: outFile::", () => {
verifyOutFileScenario({
subScenario: "shebang in all projects",
modifyFs: fs => {
ts.addShebang(fs, "first", "first_PART1");
ts.addShebang(fs, "first", "first_part2");
ts.addShebang(fs, "second", "second_part1");
ts.addShebang(fs, "third", "third_part1");
addShebang(fs, "first", "first_PART1");
addShebang(fs, "first", "first_part2");
addShebang(fs, "second", "second_part1");
addShebang(fs, "third", "third_part1");
},
});
// Verify ignore dtsChanged
verifyOutFileScenario({
subScenario: "shebang in only one dependency project",
modifyFs: fs => ts.addShebang(fs, "second", "second_part1"),
modifyFs: fs => addShebang(fs, "second", "second_part1"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -297,21 +298,21 @@ describe("unittests:: tsbuild:: outFile::", () => {
verifyOutFileScenario({
subScenario: "emitHelpers in all projects",
modifyFs: fs => {
ts.addRest(fs, "first", "first_PART1");
ts.addRest(fs, "second", "second_part1");
ts.addRest(fs, "third", "third_part1");
addRest(fs, "first", "first_PART1");
addRest(fs, "second", "second_part1");
addRest(fs, "third", "third_part1");
},
modifyAgainFs: fs => ts.removeRest(fs, "first", "first_PART1")
modifyAgainFs: fs => removeRest(fs, "first", "first_PART1")
});
// Verify ignore dtsChanged
verifyOutFileScenario({
subScenario: "emitHelpers in only one dependency project",
modifyFs: fs => {
ts.addStubFoo(fs, "first", "first_PART1");
ts.addRest(fs, "second", "second_part1");
addStubFoo(fs, "first", "first_PART1");
addRest(fs, "second", "second_part1");
},
modifyAgainFs: fs => ts.changeStubToRest(fs, "first", "first_PART1"),
modifyAgainFs: fs => changeStubToRest(fs, "first", "first_PART1"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -320,14 +321,14 @@ describe("unittests:: tsbuild:: outFile::", () => {
verifyOutFileScenario({
subScenario: "multiple emitHelpers in all projects",
modifyFs: fs => {
ts.addRest(fs, "first", "first_PART1");
ts.addSpread(fs, "first", "first_part3");
ts.addRest(fs, "second", "second_part1");
ts.addSpread(fs, "second", "second_part2");
ts.addRest(fs, "third", "third_part1");
ts.addSpread(fs, "third", "third_part1");
addRest(fs, "first", "first_PART1");
addSpread(fs, "first", "first_part3");
addRest(fs, "second", "second_part1");
addSpread(fs, "second", "second_part2");
addRest(fs, "third", "third_part1");
addSpread(fs, "third", "third_part1");
},
modifyAgainFs: fs => ts.removeRest(fs, "first", "first_PART1"),
modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -336,11 +337,11 @@ describe("unittests:: tsbuild:: outFile::", () => {
verifyOutFileScenario({
subScenario: "multiple emitHelpers in different projects",
modifyFs: fs => {
ts.addRest(fs, "first", "first_PART1");
ts.addSpread(fs, "second", "second_part1");
ts.addRest(fs, "third", "third_part1");
addRest(fs, "first", "first_PART1");
addSpread(fs, "second", "second_part1");
addRest(fs, "third", "third_part1");
},
modifyAgainFs: fs => ts.removeRest(fs, "first", "first_PART1"),
modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -353,16 +354,16 @@ describe("unittests:: tsbuild:: outFile::", () => {
verifyOutFileScenario({
subScenario: "triple slash refs in all projects",
modifyFs: fs => {
ts.addTripleSlashRef(fs, "first", "first_part2");
ts.addTripleSlashRef(fs, "second", "second_part1");
ts.addTripleSlashRef(fs, "third", "third_part1");
addTripleSlashRef(fs, "first", "first_part2");
addTripleSlashRef(fs, "second", "second_part1");
addTripleSlashRef(fs, "third", "third_part1");
}
});
// Verify ignore dtsChanged
verifyOutFileScenario({
subScenario: "triple slash refs in one project",
modifyFs: fs => ts.addTripleSlashRef(fs, "second", "second_part1"),
modifyFs: fs => addTripleSlashRef(fs, "second", "second_part1"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -370,7 +371,7 @@ describe("unittests:: tsbuild:: outFile::", () => {
describe("stripInternal", () => {
function disableRemoveComments(fs: vfs.FileSystem, file: string) {
ts.replaceText(fs, file, `"removeComments": true`, `"removeComments": false`);
replaceText(fs, file, `"removeComments": true`, `"removeComments": false`);
}
function diableRemoveCommentsInAll(fs: vfs.FileSystem) {
@@ -380,7 +381,7 @@ describe("unittests:: tsbuild:: outFile::", () => {
}
function stripInternalOfThird(fs: vfs.FileSystem) {
ts.replaceText(fs, "/src/third/tsconfig.json", `"declaration": true,`, `"declaration": true,
replaceText(fs, "/src/third/tsconfig.json", `"declaration": true,`, `"declaration": true,
"stripInternal": true,`);
}
@@ -390,8 +391,8 @@ describe("unittests:: tsbuild:: outFile::", () => {
diableRemoveCommentsInAll(fs);
}
stripInternalOfThird(fs);
ts.replaceText(fs, "/src/first/first_PART1.ts", "interface", `${internal} interface`);
ts.appendText(fs, "/src/second/second_part1.ts", `
replaceText(fs, "/src/first/first_PART1.ts", "interface", `${internal} interface`);
appendText(fs, "/src/second/second_part1.ts", `
class normalC {
${internal} constructor() { }
${internal} prop: string;
@@ -423,14 +424,14 @@ ${internal} enum internalEnum { a, b, c }`);
verifyOutFileScenario({
subScenario: "stripInternal",
modifyFs: stripInternalScenario,
modifyAgainFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", `/*@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 => ts.replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -439,7 +440,7 @@ ${internal} enum internalEnum { a, b, c }`);
verifyOutFileScenario({
subScenario: "stripInternal jsdoc style comment",
modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true),
modifyAgainFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -454,9 +455,9 @@ ${internal} enum internalEnum { a, b, c }`);
describe("with three levels of project dependency", () => {
function makeOneTwoThreeDependOrder(fs: vfs.FileSystem) {
ts.replaceText(fs, "/src/second/tsconfig.json", "[", `[
replaceText(fs, "/src/second/tsconfig.json", "[", `[
{ "path": "../first", "prepend": true }`);
ts.replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../first", "prepend": true },`, "");
replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../first", "prepend": true },`, "");
}
function stripInternalWithDependentOrder(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) {
@@ -468,14 +469,14 @@ ${internal} enum internalEnum { a, b, c }`);
verifyOutFileScenario({
subScenario: "stripInternal when one-two-three are prepended in order",
modifyFs: stripInternalWithDependentOrder,
modifyAgainFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", `/*@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 => ts.replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -484,7 +485,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 => ts.replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"),
modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"),
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -503,7 +504,7 @@ ${internal} enum internalEnum { a, b, c }`);
subScenario: "stripInternal baseline when internal is inside another internal",
modifyFs: fs => {
stripInternalOfThird(fs);
ts.prependText(fs, "/src/first/first_PART1.ts", `namespace ts {
prependText(fs, "/src/first/first_PART1.ts", `namespace ts {
/* @internal */
/**
* Subset of properties from SourceFile that are used in multiple utility functions
@@ -542,7 +543,7 @@ ${internal} enum internalEnum { a, b, c }`);
subScenario: "stripInternal when few members of enum are internal",
modifyFs: fs => {
stripInternalOfThird(fs);
ts.prependText(fs, "/src/first/first_PART1.ts", `enum TokenFlags {
prependText(fs, "/src/first/first_PART1.ts", `enum TokenFlags {
None = 0,
/* @internal */
PrecedingLineBreak = 1 << 0,
@@ -624,9 +625,9 @@ ${internal} enum internalEnum { a, b, c }`);
subScenario: "declarationMap and sourceMap disabled",
modifyFs: fs => {
makeThirdEmptySourceFile(fs);
ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, "");
ts.replaceText(fs, "/src/third/tsconfig.json", `"sourceMap": true,`, "");
ts.replaceText(fs, "/src/third/tsconfig.json", `"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,
@@ -635,25 +636,25 @@ ${internal} enum internalEnum { a, b, c }`);
});
});
ts.verifyTsc({
verifyTsc({
scenario: "outFile",
subScenario: "non module projects without prepend",
fs: () => outFileFs,
commandLineArgs: ["--b", "/src/third", "--verbose"],
modifyFs: fs => {
// No prepend
ts.replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../first", "prepend": true }`, `{ "path": "../first" }`);
ts.replaceText(fs, "/src/third/tsconfig.json", `{ "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
ts.replaceText(fs, "/src/first/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`);
ts.replaceText(fs, "/src/second/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`);
ts.replaceText(fs, "/src/third/tsconfig.json", `"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
ts.replaceText(fs, "/src/first/tsconfig.json", `"outFile": "./bin/first-output.js",`, "");
ts.replaceText(fs, "/src/second/tsconfig.json", `"outFile": "../2/second-output.js",`, "");
ts.replaceText(fs, "/src/third/tsconfig.json", `"outFile": "./thirdjs/output/third-output.js",`, "");
replaceText(fs, "/src/first/tsconfig.json", `"outFile": "./bin/first-output.js",`, "");
replaceText(fs, "/src/second/tsconfig.json", `"outFile": "../2/second-output.js",`, "");
replaceText(fs, "/src/third/tsconfig.json", `"outFile": "./thirdjs/output/third-output.js",`, "");
},
});
});

View File

@@ -1,26 +1,27 @@
import * as ts from "../../_namespaces/ts";
import * as fakes from "../../_namespaces/fakes";
import { loadProjectFromFiles, noChangeRun, TestTscEdit, TscCompileSystem, verifyTscWithEdits, VerifyTscWithEditsInput } from "../tsc/helpers";
describe("unittests:: tsbuild - output file paths", () => {
const noChangeProject: ts.TestTscEdit = {
const noChangeProject: TestTscEdit = {
modifyFs: ts.noop,
subScenario: "Normal build without change, that does not block emit on error to show files that get emitted",
commandLineArgs: ["-p", "/src/tsconfig.json"],
};
const edits: ts.TestTscEdit[] = [
ts.noChangeRun,
const edits: TestTscEdit[] = [
noChangeRun,
noChangeProject,
];
function verify(input: Pick<ts.VerifyTscWithEditsInput, "subScenario" | "fs" | "edits">, expectedOuptutNames: readonly string[]) {
ts.verifyTscWithEdits({
function verify(input: Pick<VerifyTscWithEditsInput, "subScenario" | "fs" | "edits">, expectedOuptutNames: readonly string[]) {
verifyTscWithEdits({
scenario: "outputPaths",
commandLineArgs: ["--b", "/src/tsconfig.json", "-v"],
...input
});
it("verify getOutputFileNames", () => {
const sys = new fakes.System(input.fs().makeReadonly(), { executingFilePath: "/lib/tsc" }) as ts.TscCompileSystem;
const sys = new fakes.System(input.fs().makeReadonly(), { executingFilePath: "/lib/tsc" }) as TscCompileSystem;
assert.deepEqual(
ts.getOutputFileNames(
@@ -35,7 +36,7 @@ describe("unittests:: tsbuild - output file paths", () => {
verify({
subScenario: "when rootDir is not specified",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/src/index.ts": "export const x = 10;",
"/src/tsconfig.json": JSON.stringify({
compilerOptions: {
@@ -48,7 +49,7 @@ describe("unittests:: tsbuild - output file paths", () => {
verify({
subScenario: "when rootDir is not specified and is composite",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/src/index.ts": "export const x = 10;",
"/src/tsconfig.json": JSON.stringify({
compilerOptions: {
@@ -62,7 +63,7 @@ describe("unittests:: tsbuild - output file paths", () => {
verify({
subScenario: "when rootDir is specified",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/src/index.ts": "export const x = 10;",
"/src/tsconfig.json": JSON.stringify({
compilerOptions: {
@@ -76,7 +77,7 @@ describe("unittests:: tsbuild - output file paths", () => {
verify({
subScenario: "when rootDir is specified but not all files belong to rootDir",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/src/index.ts": "export const x = 10;",
"/src/types/type.ts": "export type t = string;",
"/src/tsconfig.json": JSON.stringify({
@@ -91,7 +92,7 @@ describe("unittests:: tsbuild - output file paths", () => {
verify({
subScenario: "when rootDir is specified but not all files belong to rootDir and is composite",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/src/index.ts": "export const x = 10;",
"/src/types/type.ts": "export type t = string;",
"/src/tsconfig.json": JSON.stringify({

View File

@@ -1,11 +1,12 @@
import * as ts from "../../_namespaces/ts";
import * as fakes from "../../_namespaces/fakes";
import * as vfs from "../../_namespaces/vfs";
import { baselinePrograms, commandLineCallbacks, loadProjectFromFiles, toPathWithSystem, TscCompileSystem, verifyTscBaseline } from "../tsc/helpers";
describe("unittests:: tsbuild:: Public API with custom transformers when passed to build", () => {
let sys: ts.TscCompileSystem;
let sys: TscCompileSystem;
before(() => {
const inputFs = ts.loadProjectFromFiles({
const inputFs = loadProjectFromFiles({
"/src/tsconfig.json": JSON.stringify({
references: [
{ path: "./shared/tsconfig.json" },
@@ -36,7 +37,7 @@ export function f22() { } // trailing`,
const fs = inputFs.shadow();
// Create system
sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }) as ts.TscCompileSystem;
sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }) as TscCompileSystem;
fakes.patchHostForBuildInfoReadWrite(sys);
const commandLineArgs = ["--b", "/src/tsconfig.json"];
sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`);
@@ -44,12 +45,12 @@ export function f22() { } // trailing`,
const writtenFiles = sys.writtenFiles = new ts.Set();
const originalWriteFile = sys.writeFile;
sys.writeFile = (fileName, content, writeByteOrderMark) => {
const path = ts.toPathWithSystem(sys, fileName);
const path = toPathWithSystem(sys, fileName);
assert.isFalse(writtenFiles.has(path));
writtenFiles.add(path);
return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
};
const { cb, getPrograms } = ts.commandLineCallbacks(sys, /*originalReadCall*/ undefined);
const { cb, getPrograms } = commandLineCallbacks(sys, /*originalReadCall*/ undefined);
const buildHost = ts.createSolutionBuilderHost(
sys,
/*createProgram*/ undefined,
@@ -64,7 +65,7 @@ export function f22() { } // trailing`,
sys.exit(exitStatus);
sys.write(`exitCode:: ExitStatus.${ts.ExitStatus[sys.exitCode as ts.ExitStatus]}\n`);
const baseline: string[] = [];
ts.tscWatch.baselinePrograms(baseline, getPrograms, ts.emptyArray, /*baselineDependencies*/ false);
baselinePrograms(baseline, getPrograms, ts.emptyArray, /*baselineDependencies*/ false);
sys.write(baseline.join("\n"));
fs.makeReadonly();
sys.baseLine = () => {
@@ -120,5 +121,5 @@ ${patch ? vfs.formatPatch(patch) : ""}`
after(() => {
sys = undefined!;
});
ts.verifyTscBaseline(() => sys);
verifyTscBaseline(() => sys);
});

View File

@@ -1,32 +1,32 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import { loadProjectFromDisk, replaceText, verifyTsc } from "../tsc/helpers";
describe("unittests:: tsbuild:: with rootDir of project reference in parentDirectory", () => {
let projFs: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent");
projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent");
});
after(() => {
projFs = undefined!; // Release the contents
});
ts.verifyTsc({
verifyTsc({
scenario: "projectReferenceWithRootDirInParent",
subScenario: "builds correctly",
fs: () => projFs,
commandLineArgs: ["--b", "/src/src/main", "/src/src/other"],
});
ts.verifyTsc({
verifyTsc({
scenario: "projectReferenceWithRootDirInParent",
subScenario: "reports error for same tsbuildinfo file because no rootDir in the base",
fs: () => projFs,
commandLineArgs: ["--b", "/src/src/main", "--verbose"],
modifyFs: fs => ts.replaceText(fs, "/src/tsconfig.base.json", `"rootDir": "./src/",`, ""),
modifyFs: fs => replaceText(fs, "/src/tsconfig.base.json", `"rootDir": "./src/",`, ""),
});
ts.verifyTsc({
verifyTsc({
scenario: "projectReferenceWithRootDirInParent",
subScenario: "reports error for same tsbuildinfo file",
fs: () => projFs,
@@ -42,7 +42,7 @@ describe("unittests:: tsbuild:: with rootDir of project reference in parentDirec
},
});
ts.verifyTsc({
verifyTsc({
scenario: "projectReferenceWithRootDirInParent",
subScenario: "reports no error when tsbuildinfo differ",
fs: () => projFs,

View File

@@ -1,31 +1,31 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import { loadProjectFromDisk, noChangeOnlyRuns, replaceText, verifyTsc, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: with resolveJsonModule option on project resolveJsonModuleAndComposite", () => {
let projFs: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite");
projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite");
});
after(() => {
projFs = undefined!; // Release the contents
});
ts.verifyTsc({
verifyTsc({
scenario: "resolveJsonModule",
subScenario: "include only",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig_withInclude.json", "--v", "--explainFiles"],
});
ts.verifyTsc({
verifyTsc({
scenario: "resolveJsonModule",
subScenario: "include of json along with other include",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig_withIncludeOfJson.json", "--v", "--explainFiles"],
});
ts.verifyTsc({
verifyTsc({
scenario: "resolveJsonModule",
subScenario: "include of json along with other include and file name matches ts file",
fs: () => projFs,
@@ -39,45 +39,45 @@ export default hello.hello`);
},
});
ts.verifyTsc({
verifyTsc({
scenario: "resolveJsonModule",
subScenario: "files containing json file",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig_withFiles.json", "--v", "--explainFiles"],
});
ts.verifyTsc({
verifyTsc({
scenario: "resolveJsonModule",
subScenario: "include and files",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig_withIncludeAndFiles.json", "--v", "--explainFiles"],
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "resolveJsonModule",
subScenario: "sourcemap",
fs: () => projFs,
commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose", "--explainFiles"],
modifyFs: fs => ts.replaceText(fs, "src/tsconfig_withFiles.json", `"composite": true,`, `"composite": true, "sourceMap": true,`),
edits: ts.noChangeOnlyRuns
modifyFs: fs => replaceText(fs, "src/tsconfig_withFiles.json", `"composite": true,`, `"composite": true, "sourceMap": true,`),
edits: noChangeOnlyRuns
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "resolveJsonModule",
subScenario: "without outDir",
fs: () => projFs,
commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose"],
modifyFs: fs => ts.replaceText(fs, "src/tsconfig_withFiles.json", `"outDir": "dist",`, ""),
edits: ts.noChangeOnlyRuns
modifyFs: fs => replaceText(fs, "src/tsconfig_withFiles.json", `"outDir": "dist",`, ""),
edits: noChangeOnlyRuns
});
});
describe("unittests:: tsbuild:: with resolveJsonModule option on project importJsonFromProjectReference", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "resolveJsonModule",
subScenario: "importing json module from project reference",
fs: () => ts.loadProjectFromDisk("tests/projects/importJsonFromProjectReference"),
fs: () => loadProjectFromDisk("tests/projects/importJsonFromProjectReference"),
commandLineArgs: ["--b", "src/tsconfig.json", "--verbose", "--explainFiles"],
edits: ts.noChangeOnlyRuns
edits: noChangeOnlyRuns
});
});

View File

@@ -2,12 +2,14 @@ import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import * as fakes from "../../_namespaces/fakes";
import * as Harness from "../../_namespaces/Harness";
import { changeToHostTrackingWrittenFiles, createWatchedSystem, File, getTsBuildProjectFilePath, libFile, TestServerHost } from "../virtualFileSystemWithWatch";
import { appendText, createSolutionBuilderHostForBaseline, libContent, loadProjectFromDisk, loadProjectFromFiles, noChangeOnlyRuns, noChangeRun, prependText, replaceText, testTscCompileLike, TestTscEdit, TscCompileSystem, verifyTsc, verifyTscCompileLike, verifyTscWithEdits } from "../tsc/helpers";
describe("unittests:: tsbuild:: on 'sample1' project", () => {
let projFs: vfs.FileSystem;
let projFsWithBuild: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/sample1");
projFs = loadProjectFromDisk("tests/projects/sample1");
});
after(() => {
@@ -15,9 +17,9 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
projFsWithBuild = undefined!;
});
function getTsBuildProjectFile(project: string, file: string): ts.tscWatch.File {
function getTsBuildProjectFile(project: string, file: string): File {
return {
path: ts.TestFSWithWatch.getTsBuildProjectFilePath(project, file),
path: getTsBuildProjectFilePath(project, file),
content: projFs.readFileSync(`/src/${project}/${file}`, "utf8")!
};
}
@@ -26,7 +28,7 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
if (projFsWithBuild) return projFsWithBuild;
const fs = projFs.shadow();
const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" });
const host = ts.createSolutionBuilderHostForBaseline(sys as ts.TscCompileSystem);
const host = createSolutionBuilderHostForBaseline(sys as TscCompileSystem);
const builder = ts.createSolutionBuilder(host, ["/src/tests"], {});
builder.build();
fs.makeReadonly();
@@ -34,7 +36,7 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
}
describe("sanity check of clean build of 'sample1' project", () => {
ts.verifyTsc({
verifyTsc({
scenario: "sample1",
subScenario: "builds correctly when outDir is specified",
fs: () => projFs,
@@ -45,7 +47,7 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
})),
});
ts.verifyTsc({
verifyTsc({
scenario: "sample1",
subScenario: "builds correctly when declarationDir is specified",
fs: () => projFs,
@@ -56,17 +58,17 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
})),
});
ts.verifyTsc({
verifyTsc({
scenario: "sample1",
subScenario: "builds correctly when project is not composite or doesnt have any references",
fs: () => projFs,
commandLineArgs: ["--b", "/src/core", "--verbose"],
modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"composite": true,`, ""),
modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"composite": true,`, ""),
});
});
describe("dry builds", () => {
ts.verifyTsc({
verifyTsc({
scenario: "sample1",
subScenario: "does not write any files in a dry build",
fs: () => projFs,
@@ -75,33 +77,33 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
});
describe("clean builds", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "removes all files it built",
fs: getSampleFsAfterBuild,
commandLineArgs: ["--b", "/src/tests", "--clean"],
edits: ts.noChangeOnlyRuns
edits: noChangeOnlyRuns
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "cleans till project specified",
fs: getSampleFsAfterBuild,
commandLineArgs: ["--b", "/src/logic", "--clean"],
compile: sys => {
const buildHost = ts.createSolutionBuilderHostForBaseline(sys);
const buildHost = createSolutionBuilderHostForBaseline(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {});
sys.exit(builder.clean("/src/logic"));
}
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
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 = ts.createSolutionBuilderHostForBaseline(sys);
const buildHost = createSolutionBuilderHostForBaseline(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {});
sys.exit(builder.clean("/src/logic2"));
}
@@ -109,17 +111,17 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
});
describe("force builds", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "always builds under with force option",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tests", "--force"],
edits: ts.noChangeOnlyRuns
edits: noChangeOnlyRuns
});
});
describe("can detect when and what to rebuild", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "can detect when and what to rebuild",
fs: getSampleFsAfterBuild,
@@ -133,16 +135,16 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
// Update a file in the parent (without affecting types), should get fast downstream builds
{
subScenario: "Detects type-only changes in upstream projects",
modifyFs: fs => ts.replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"),
modifyFs: fs => replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"),
},
{
subScenario: "rebuilds when tsconfig changes",
modifyFs: fs => ts.replaceText(fs, "/src/tests/tsconfig.json", `"composite": true`, `"composite": true, "target": "es3"`),
modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"composite": true`, `"composite": true, "target": "es3"`),
},
]
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "when input file text does not change but its modified time changes",
fs: () => projFs,
@@ -158,7 +160,7 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
]
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "when declarationMap changes",
fs: () => projFs,
@@ -166,33 +168,33 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
edits: [
{
subScenario: "Disable declarationMap",
modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"declarationMap": true,`, `"declarationMap": false,`),
modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"declarationMap": true,`, `"declarationMap": false,`),
},
{
subScenario: "Enable declarationMap",
modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"declarationMap": false,`, `"declarationMap": true,`),
modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"declarationMap": false,`, `"declarationMap": true,`),
},
]
});
ts.verifyTsc({
verifyTsc({
scenario: "sample1",
subScenario: "indicates that it would skip builds during a dry build",
fs: getSampleFsAfterBuild,
commandLineArgs: ["--b", "/src/tests", "--dry"],
});
ts.verifyTsc({
verifyTsc({
scenario: "sample1",
subScenario: "rebuilds from start if force option is set",
fs: getSampleFsAfterBuild,
commandLineArgs: ["--b", "/src/tests", "--verbose", "--force"],
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "tsbuildinfo has error",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": "{}",
"/src/project/tsconfig.tsbuildinfo": "Some random string",
@@ -200,24 +202,24 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
commandLineArgs: ["--b", "src/project", "-i", "-v"],
edits: [{
subScenario: "tsbuildinfo written has error",
modifyFs: fs => ts.prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"),
modifyFs: fs => prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"),
}]
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
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
const buildHost = ts.createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion");
const buildHost = createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion");
const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true });
sys.exit(builder.build());
}
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
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: () => {
@@ -231,20 +233,20 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
commandLineArgs: ["--b", "/src/tests", "--verbose"],
compile: sys => {
// Buildinfo will have version which does not match with current ts version
const buildHost = ts.createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion");
const buildHost = createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion");
const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true });
sys.exit(builder.build());
},
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "rebuilds when extended config file changes",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tests", "--verbose"],
modifyFs: fs => {
fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: { target: "es3" } }));
ts.replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`);
replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`);
},
edits: [{
subScenario: "incremental-declaration-changes",
@@ -252,25 +254,25 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
}]
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "builds till project specified",
fs: () => projFs,
commandLineArgs: ["--build", "/src/logic/tsconfig.json"],
compile: sys => {
const buildHost = ts.createSolutionBuilderHostForBaseline(sys);
const buildHost = createSolutionBuilderHostForBaseline(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], {});
sys.exit(builder.build("/src/logic/tsconfig.json"));
}
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
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 = ts.createSolutionBuilderHostForBaseline(sys);
const buildHost = createSolutionBuilderHostForBaseline(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], {});
sys.exit(builder.build("/src/logic2/tsconfig.json"));
}
@@ -286,19 +288,19 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json");
const testsIndex = getTsBuildProjectFile("tests", "index.ts");
const baseline: string[] = [];
let oldSnap: ReturnType<ts.TestFSWithWatch.TestServerHost["snap"]> | undefined;
const system = ts.TestFSWithWatch.changeToHostTrackingWrittenFiles(
let oldSnap: ReturnType<TestServerHost["snap"]> | undefined;
const system = changeToHostTrackingWrittenFiles(
fakes.patchHostForBuildInfoReadWrite(
ts.tscWatch.createWatchedSystem([
createWatchedSystem([
coreConfig, coreIndex, coreDecl, coreAnotherModule,
logicConfig, logicIndex,
testsConfig, testsIndex,
ts.tscWatch.libFile
libFile
])
)
);
const host = ts.createSolutionBuilderHostForBaseline(system);
const host = createSolutionBuilderHostForBaseline(system);
const builder = ts.createSolutionBuilder(host, [testsConfig.path], {});
baseline.push("Input::");
baselineState();
@@ -323,13 +325,13 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
}
});
ts.verifyTscCompileLike(ts.testTscCompileLike, {
verifyTscCompileLike(testTscCompileLike, {
scenario: "sample1",
subScenario: "building using buildReferencedProject",
fs: () => projFs,
commandLineArgs: ["--build", "/src/logic2/tsconfig.json"],
compile: sys => {
const buildHost = ts.createSolutionBuilderHostForBaseline(sys);
const buildHost = createSolutionBuilderHostForBaseline(sys);
const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true });
sys.exit(builder.buildReferences("/src/tests"));
}
@@ -337,13 +339,13 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
});
describe("downstream-blocked compilations", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "does not build downstream projects if upstream projects have errors",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tests", "--verbose"],
modifyFs: fs => ts.replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`),
edits: ts.noChangeOnlyRuns
modifyFs: fs => replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`),
edits: noChangeOnlyRuns
});
});
@@ -358,19 +360,19 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json");
const testsIndex = getTsBuildProjectFile("tests", "index.ts");
const baseline: string[] = [];
let oldSnap: ReturnType<ts.TestFSWithWatch.TestServerHost["snap"]> | undefined;
const system = ts.TestFSWithWatch.changeToHostTrackingWrittenFiles(
let oldSnap: ReturnType<TestServerHost["snap"]> | undefined;
const system = changeToHostTrackingWrittenFiles(
fakes.patchHostForBuildInfoReadWrite(
ts.tscWatch.createWatchedSystem([
createWatchedSystem([
coreConfig, coreIndex, coreDecl, coreAnotherModule,
logicConfig, logicIndex,
testsConfig, testsIndex,
ts.tscWatch.libFile
libFile
])
)
);
const host = ts.createSolutionBuilderHostForBaseline(system);
const host = createSolutionBuilderHostForBaseline(system);
const builder = ts.createSolutionBuilder(host, [testsConfig.path], { dry: false, force: false, verbose: false });
builder.build();
baselineState("Build of project");
@@ -409,36 +411,36 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
});
});
const coreChanges: ts.TestTscEdit[] = [
const coreChanges: TestTscEdit[] = [
{
subScenario: "incremental-declaration-changes",
modifyFs: fs => ts.appendText(fs, "/src/core/index.ts", `
modifyFs: fs => appendText(fs, "/src/core/index.ts", `
export class someClass { }`),
},
{
subScenario: "incremental-declaration-doesnt-change",
modifyFs: fs => ts.appendText(fs, "/src/core/index.ts", `
modifyFs: fs => appendText(fs, "/src/core/index.ts", `
class someClass2 { }`),
},
ts.noChangeRun,
noChangeRun,
];
describe("lists files", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "listFiles",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tests", "--listFiles"],
edits: coreChanges
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "listEmittedFiles",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tests", "--listEmittedFiles"],
edits: coreChanges
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "sample1",
subScenario: "explainFiles",
fs: () => projFs,
@@ -448,7 +450,7 @@ class someClass2 { }`),
});
describe("emit output", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
subScenario: "sample",
fs: () => projFs,
scenario: "sample1",
@@ -459,25 +461,25 @@ class someClass2 { }`),
...coreChanges,
{
subScenario: "when logic config changes declaration dir",
modifyFs: fs => ts.replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true,
modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true,
"declarationDir": "decls",`),
},
ts.noChangeRun,
noChangeRun,
],
});
ts.verifyTsc({
verifyTsc({
scenario: "sample1",
subScenario: "when logic specifies tsBuildInfoFile",
fs: () => projFs,
modifyFs: fs => ts.replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true,
modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true,
"tsBuildInfoFile": "ownFile.tsbuildinfo",`),
commandLineArgs: ["--b", "/src/tests", "--verbose"],
baselineSourceMap: true,
baselineReadFileCalls: true
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
subScenario: "when declaration option changes",
fs: () => projFs,
scenario: "sample1",
@@ -490,11 +492,11 @@ class someClass2 { }`),
}`),
edits: [{
subScenario: "incremental-declaration-changes",
modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`),
modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`),
}],
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
subScenario: "when target option changes",
fs: () => projFs,
scenario: "sample1",
@@ -502,7 +504,7 @@ class someClass2 { }`),
modifyFs: fs => {
fs.writeFileSync("/lib/lib.esnext.full.d.ts", `/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />`);
fs.writeFileSync("/lib/lib.esnext.d.ts", ts.libContent);
fs.writeFileSync("/lib/lib.esnext.d.ts", libContent);
fs.writeFileSync("/lib/lib.d.ts", `/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />`);
fs.writeFileSync("/src/core/tsconfig.json", `{
@@ -516,11 +518,11 @@ class someClass2 { }`),
},
edits: [{
subScenario: "incremental-declaration-changes",
modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", "esnext", "es5"),
modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", "esnext", "es5"),
}],
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
subScenario: "when module option changes",
fs: () => projFs,
scenario: "sample1",
@@ -533,11 +535,11 @@ class someClass2 { }`),
}`),
edits: [{
subScenario: "incremental-declaration-changes",
modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"module": "commonjs"`, `"module": "amd"`),
modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"module": "commonjs"`, `"module": "amd"`),
}],
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
subScenario: "when esModuleInterop option changes",
fs: () => projFs,
scenario: "sample1",
@@ -558,11 +560,11 @@ class someClass2 { }`),
}`),
edits: [{
subScenario: "incremental-declaration-changes",
modifyFs: fs => ts.replaceText(fs, "/src/tests/tsconfig.json", `"esModuleInterop": false`, `"esModuleInterop": true`),
modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"esModuleInterop": false`, `"esModuleInterop": true`),
}],
});
ts.verifyTsc({
verifyTsc({
scenario: "sample1",
subScenario: "reports error if input file is missing",
fs: () => projFs,
@@ -576,7 +578,7 @@ class someClass2 { }`),
}
});
ts.verifyTsc({
verifyTsc({
scenario: "sample1",
subScenario: "reports error if input file is missing with force",
fs: () => projFs,

View File

@@ -1,10 +1,10 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import { loadProjectFromDisk, verifyTsc } from "../tsc/helpers";
describe("unittests:: tsbuild:: when project reference is referenced transitively", () => {
let projFs: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/transitiveReferences");
projFs = loadProjectFromDisk("tests/projects/transitiveReferences");
});
after(() => {
projFs = undefined!; // Release the contents
@@ -23,14 +23,14 @@ export const b = new A();`);
}));
}
ts.verifyTsc({
verifyTsc({
scenario: "transitiveReferences",
subScenario: "builds correctly",
fs: () => projFs,
commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"],
});
ts.verifyTsc({
verifyTsc({
scenario: "transitiveReferences",
subScenario: "builds correctly when the referenced project uses different module resolution",
fs: () => projFs,
@@ -38,7 +38,7 @@ export const b = new A();`);
modifyFs: fs => modifyFsBTsToNonRelativeImport(fs, "classic"),
});
ts.verifyTsc({
verifyTsc({
scenario: "transitiveReferences",
subScenario: "reports error about module not found with node resolution with external module name",
fs: () => projFs,

View File

@@ -1,21 +1,22 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { createWatchedSystem, libFile, TestServerHost } from "../virtualFileSystemWithWatch";
import { verifyTscWatch } from "../tscWatch/helpers";
import { dedent } from "../../_namespaces/Utils";
describe("unittests:: tsbuildWatch:: watchMode:: configFileErrors:: reports syntax errors in config file", () => {
function build(sys: ts.tscWatch.WatchedSystem) {
function build(sys: TestServerHost) {
sys.checkTimeoutQueueLengthAndRun(1); // build the project
sys.checkTimeoutQueueLength(0);
}
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "configFileErrors",
subScenario: "reports syntax errors in config file",
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
[
{ path: `${ts.tscWatch.projectRoot}/a.ts`, content: "export function foo() { }" },
{ path: `${ts.tscWatch.projectRoot}/b.ts`, content: "export function bar() { }" },
{ path: `/user/username/projects/myproject/a.ts`, content: "export function foo() { }" },
{ path: `/user/username/projects/myproject/b.ts`, content: "export function bar() { }" },
{
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
content: Utils.dedent`
path: `/user/username/projects/myproject/tsconfig.json`,
content: dedent`
{
"compilerOptions": {
"composite": true,
@@ -26,31 +27,31 @@ describe("unittests:: tsbuildWatch:: watchMode:: configFileErrors:: reports synt
]
}`
},
ts.tscWatch.libFile
libFile
],
{ currentDirectory: ts.tscWatch.projectRoot }
{ currentDirectory: "/user/username/projects/myproject" }
),
commandLineArgs: ["--b", "-w"],
changes: [
{
caption: "reports syntax errors after change to config file",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/tsconfig.json`, ",", `,
change: sys => sys.replaceFileText(`/user/username/projects/myproject/tsconfig.json`, ",", `,
"declaration": true,`),
timeouts: build,
},
{
caption: "reports syntax errors after change to ts file",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/a.ts`, "foo", "fooBar"),
change: sys => sys.replaceFileText(`/user/username/projects/myproject/a.ts`, "foo", "fooBar"),
timeouts: build,
},
{
caption: "reports error when there is no change to tsconfig file",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/tsconfig.json`, "", ""),
change: sys => sys.replaceFileText(`/user/username/projects/myproject/tsconfig.json`, "", ""),
timeouts: build,
},
{
caption: "builds after fixing config file errors",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({
change: sys => sys.writeFile(`/user/username/projects/myproject/tsconfig.json`, JSON.stringify({
compilerOptions: { composite: true, declaration: true },
files: ["a.ts", "b.ts"]
})),

View File

@@ -1,20 +1,22 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, getTsBuildProjectFile, libFile } from "../virtualFileSystemWithWatch";
import { libContent } from "../tsc/helpers";
import { verifyTscWatch } from "../tscWatch/helpers";
describe("unittests:: tsbuildWatch:: watchMode:: with demo project", () => {
const projectLocation = `${ts.TestFSWithWatch.tsbuildProjectsLocation}/demo`;
let coreFiles: ts.tscWatch.File[];
let animalFiles: ts.tscWatch.File[];
let zooFiles: ts.tscWatch.File[];
let solutionFile: ts.tscWatch.File;
let baseConfig: ts.tscWatch.File;
let allFiles: ts.tscWatch.File[];
const projectLocation = `/user/username/projects/demo`;
let coreFiles: File[];
let animalFiles: File[];
let zooFiles: File[];
let solutionFile: File;
let baseConfig: File;
let allFiles: File[];
before(() => {
coreFiles = subProjectFiles("core", ["tsconfig.json", "utilities.ts"]);
animalFiles = subProjectFiles("animals", ["tsconfig.json", "animal.ts", "dog.ts", "index.ts"]);
zooFiles = subProjectFiles("zoo", ["tsconfig.json", "zoo.ts"]);
solutionFile = projectFile("tsconfig.json");
baseConfig = projectFile("tsconfig-base.json");
allFiles = [...coreFiles, ...animalFiles, ...zooFiles, solutionFile, baseConfig, { path: ts.tscWatch.libFile.path, content: ts.libContent }];
allFiles = [...coreFiles, ...animalFiles, ...zooFiles, solutionFile, baseConfig, { path: libFile.path, content: libContent }];
});
after(() => {
@@ -26,12 +28,12 @@ describe("unittests:: tsbuildWatch:: watchMode:: with demo project", () => {
allFiles = undefined!;
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "demo",
subScenario: "updates with circular reference",
commandLineArgs: ["-b", "-w", "-verbose"],
sys: () => {
const sys = ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectLocation });
const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation });
sys.writeFile(coreFiles[0].path, coreFiles[0].content.replace(
"}",
`},
@@ -56,12 +58,12 @@ describe("unittests:: tsbuildWatch:: watchMode:: with demo project", () => {
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "demo",
subScenario: "updates with bad reference",
commandLineArgs: ["-b", "-w", "-verbose"],
sys: () => {
const sys = ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectLocation });
const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation });
sys.writeFile(coreFiles[1].path, `import * as A from '../animals';
${coreFiles[1].content}`);
return sys;
@@ -73,16 +75,19 @@ ${coreFiles[1].content}`);
import * as A from '../animals';
${coreFiles[1].content}`),
// build core
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
}
]
});
function subProjectFiles(subProject: string, fileNames: readonly string[]): ts.tscWatch.File[] {
function subProjectFiles(subProject: string, fileNames: readonly string[]): File[] {
return fileNames.map(file => projectFile(`${subProject}/${file}`));
}
function projectFile(fileName: string): ts.tscWatch.File {
return ts.TestFSWithWatch.getTsBuildProjectFile("demo", fileName);
function projectFile(fileName: string): File {
return getTsBuildProjectFile("demo", fileName);
}
});

View File

@@ -1,34 +1,35 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { createWatchedSystem, libFile } from "../virtualFileSystemWithWatch";
import { verifyTscWatch } from "../tscWatch/helpers";
import { dedent } from "../../_namespaces/Utils";
describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolutionCache",
subScenario: "handles the cache correctly when two projects use different module resolution settings",
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
[
{ path: `${ts.tscWatch.projectRoot}/project1/index.ts`, content: `import { foo } from "file";` },
{ path: `${ts.tscWatch.projectRoot}/project1/node_modules/file/index.d.ts`, content: "export const foo = 10;" },
{ path: `/user/username/projects/myproject/project1/index.ts`, content: `import { foo } from "file";` },
{ path: `/user/username/projects/myproject/project1/node_modules/file/index.d.ts`, content: "export const foo = 10;" },
{
path: `${ts.tscWatch.projectRoot}/project1/tsconfig.json`,
path: `/user/username/projects/myproject/project1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true, types: ["foo", "bar"] },
files: ["index.ts"]
})
},
{ path: `${ts.tscWatch.projectRoot}/project2/index.ts`, content: `import { foo } from "file";` },
{ path: `${ts.tscWatch.projectRoot}/project2/file.d.ts`, content: "export const foo = 10;" },
{ path: `/user/username/projects/myproject/project2/index.ts`, content: `import { foo } from "file";` },
{ path: `/user/username/projects/myproject/project2/file.d.ts`, content: "export const foo = 10;" },
{
path: `${ts.tscWatch.projectRoot}/project2/tsconfig.json`,
path: `/user/username/projects/myproject/project2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true, types: ["foo"], moduleResolution: "classic" },
files: ["index.ts"]
})
},
{ path: `${ts.tscWatch.projectRoot}/node_modules/@types/foo/index.d.ts`, content: "export const foo = 10;" },
{ path: `${ts.tscWatch.projectRoot}/node_modules/@types/bar/index.d.ts`, content: "export const bar = 10;" },
{ path: `/user/username/projects/myproject/node_modules/@types/foo/index.d.ts`, content: "export const foo = 10;" },
{ path: `/user/username/projects/myproject/node_modules/@types/bar/index.d.ts`, content: "export const bar = 10;" },
{
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
files: [],
references: [
@@ -37,15 +38,15 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
]
})
},
ts.tscWatch.libFile
libFile
],
{ currentDirectory: ts.tscWatch.projectRoot }
{ currentDirectory: "/user/username/projects/myproject" }
),
commandLineArgs: ["--b", "-w", "-v"],
changes: [
{
caption: "Append text",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/project1/index.ts`, "const bar = 10;"),
change: sys => sys.appendFile(`/user/username/projects/myproject/project1/index.ts`, "const bar = 10;"),
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1); // build project1 and solution
sys.checkTimeoutQueueLength(0);
@@ -54,12 +55,12 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`,
sys: () => ts.tscWatch.createWatchedSystem([
sys: () => createWatchedSystem([
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`,
path: `/user/username/projects/myproject/packages/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "1.0.0",
@@ -68,13 +69,13 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/index.ts`,
content: Utils.dedent`
path: `/user/username/projects/myproject/packages/pkg1/index.ts`,
content: dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/tsconfig.json`,
path: `/user/username/projects/myproject/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "build",
@@ -84,15 +85,15 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/const.cts`,
path: `/user/username/projects/myproject/packages/pkg2/const.cts`,
content: `export type TheNum = 42;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/index.ts`,
path: `/user/username/projects/myproject/packages/pkg2/index.ts`,
content: `export type { TheNum } from './const.cjs';`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/tsconfig.json`,
path: `/user/username/projects/myproject/packages/pkg2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
@@ -102,7 +103,7 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`,
path: `/user/username/projects/myproject/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
@@ -111,33 +112,33 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg2`,
symLink: `${ts.tscWatch.projectRoot}/packages/pkg2`,
path: `/user/username/projects/myproject/node_modules/pkg2`,
symLink: `/user/username/projects/myproject/packages/pkg2`,
},
{ ...ts.tscWatch.libFile, path: `/a/lib/lib.es2022.full.d.ts` }
], { currentDirectory: ts.tscWatch.projectRoot }),
{ ...libFile, path: `/a/lib/lib.es2022.full.d.ts` }
], { currentDirectory: "/user/username/projects/myproject" }),
commandLineArgs: ["-b", "packages/pkg1", "-w", "--verbose", "--traceResolution"],
changes: [
{
caption: "reports import errors after change to package file",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks
change: sys => sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"module"`, `"commonjs"`),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "removes those errors when a package file is changed back",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`, `"commonjs"`, `"module"`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
change: sys => sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"commonjs"`, `"module"`),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "reports import errors after change to package file",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks
change: sys => sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"module"`, `"commonjs"`),
timeouts: sys => sys.runQueuedTimeoutCallbacks()
},
{
caption: "removes those errors when a package file is changed to cjs extensions",
change: sys => {
ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`);
sys.renameFile(`${ts.tscWatch.projectRoot}/packages/pkg2/index.ts`, `${ts.tscWatch.projectRoot}/packages/pkg2/index.cts`);
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`);
sys.renameFile(`/user/username/projects/myproject/packages/pkg2/index.ts`, `/user/username/projects/myproject/packages/pkg2/index.cts`);
},
timeouts: sys => {
sys.runQueuedTimeoutCallbacks(); // building pkg2
@@ -147,12 +148,12 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `build mode watches for changes to package-json main fields`,
sys: () => ts.tscWatch.createWatchedSystem([
sys: () => createWatchedSystem([
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`,
path: `/user/username/projects/myproject/packages/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "1.0.0",
@@ -160,13 +161,13 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/index.ts`,
content: Utils.dedent`
path: `/user/username/projects/myproject/packages/pkg1/index.ts`,
content: dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/tsconfig.json`,
path: `/user/username/projects/myproject/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "build",
@@ -175,7 +176,7 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/tsconfig.json`,
path: `/user/username/projects/myproject/packages/pkg2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
@@ -185,19 +186,19 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/const.ts`,
path: `/user/username/projects/myproject/packages/pkg2/const.ts`,
content: `export type TheNum = 42;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/index.ts`,
path: `/user/username/projects/myproject/packages/pkg2/index.ts`,
content: `export type { TheNum } from './const.js';`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/other.ts`,
path: `/user/username/projects/myproject/packages/pkg2/other.ts`,
content: `export type TheStr = string;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`,
path: `/user/username/projects/myproject/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
@@ -205,22 +206,22 @@ describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg2`,
symLink: `${ts.tscWatch.projectRoot}/packages/pkg2`,
path: `/user/username/projects/myproject/node_modules/pkg2`,
symLink: `/user/username/projects/myproject/packages/pkg2`,
},
ts.tscWatch.libFile
], { currentDirectory: ts.tscWatch.projectRoot }),
libFile
], { currentDirectory: "/user/username/projects/myproject" }),
commandLineArgs: ["-b", "packages/pkg1", "--verbose", "-w", "--traceResolution"],
changes: [
{
caption: "reports import errors after change to package file",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
change: sys => sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `index.js`, `other.js`),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "removes those errors when a package file is changed back",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
change: sys => sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `other.js`, `index.js`),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
]
});

View File

@@ -1,31 +1,39 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, libFile } from "../virtualFileSystemWithWatch";
import { libContent } from "../tsc/helpers";
import { verifyTscWatch } from "../tscWatch/helpers";
describe("unittests:: tsbuildWatch:: watchMode:: with noEmit", () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "noEmit",
subScenario: "does not go in loop when watching when no files are emitted",
commandLineArgs: ["-b", "-w", "-verbose"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
[
{ path: ts.tscWatch.libFile.path, content: ts.libContent },
{ path: `${ts.tscWatch.projectRoot}/a.js`, content: "" },
{ path: `${ts.tscWatch.projectRoot}/b.ts`, content: "" },
{ path: `${ts.tscWatch.projectRoot}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { allowJs: true, noEmit: true } }) },
{ path: libFile.path, content: libContent },
{ path: `/user/username/projects/myproject/a.js`, content: "" },
{ path: `/user/username/projects/myproject/b.ts`, content: "" },
{ path: `/user/username/projects/myproject/tsconfig.json`, content: JSON.stringify({ compilerOptions: { allowJs: true, noEmit: true } }) },
],
{ currentDirectory: ts.tscWatch.projectRoot }
{ currentDirectory: "/user/username/projects/myproject" }
),
changes: [
{
caption: "No change",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/a.js`, sys.readFile(`${ts.tscWatch.projectRoot}/a.js`)!),
change: sys => sys.writeFile(`/user/username/projects/myproject/a.js`, sys.readFile(`/user/username/projects/myproject/a.js`)!),
// build project
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
},
{
caption: "change",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/a.js`, "const x = 10;"),
change: sys => sys.writeFile(`/user/username/projects/myproject/a.js`, "const x = 10;"),
// build project
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
},
],
baselineIncremental: true

View File

@@ -1,32 +1,40 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, getTsBuildProjectFile, libFile } from "../virtualFileSystemWithWatch";
import { libContent } from "../tsc/helpers";
import { TscWatchCompileChange, verifyTscWatch } from "../tscWatch/helpers";
describe("unittests:: tsbuildWatch:: watchMode:: with noEmitOnError", () => {
function change(caption: string, content: string): ts.tscWatch.TscWatchCompileChange {
function change(caption: string, content: string): TscWatchCompileChange {
return {
caption,
change: sys => sys.writeFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content),
change: sys => sys.writeFile(`/user/username/projects/noEmitOnError/src/main.ts`, content),
// build project
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
};
}
const noChange: ts.tscWatch.TscWatchCompileChange = {
const noChange: TscWatchCompileChange = {
caption: "No change",
change: sys => sys.writeFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!),
change: sys => sys.writeFile(`/user/username/projects/noEmitOnError/src/main.ts`, sys.readFile(`/user/username/projects/noEmitOnError/src/main.ts`)!),
// build project
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
};
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "noEmitOnError",
subScenario: "does not emit any files on error",
commandLineArgs: ["-b", "-w", "-verbose"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
[
...["tsconfig.json", "shared/types/db.ts", "src/main.ts", "src/other.ts"]
.map(f => ts.TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)),
{ path: ts.tscWatch.libFile.path, content: ts.libContent }
.map(f => getTsBuildProjectFile("noEmitOnError", f)),
{ path: libFile.path, content: libContent }
],
{ currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError` }
{ currentDirectory: `/user/username/projects/noEmitOnError` }
),
changes: [
noChange,

View File

@@ -1,6 +1,6 @@
import * as ts from "../../_namespaces/ts";
import projectsLocation = ts.TestFSWithWatch.tsbuildProjectsLocation;
import { createWatchedSystem, File, getTsBuildProjectFile, getTsBuildProjectFilePath, libFile, TestServerHost } from "../virtualFileSystemWithWatch";
import { commonFile1, commonFile2, createBaseline, createSolutionBuilderWithWatchHostForBaseline, noopChange, runWatchBaseline, TscWatchCompileChange, verifyTscWatch } from "../tscWatch/helpers";
describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => {
const enum SubProject {
core = "core",
@@ -8,15 +8,15 @@ describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => {
tests = "tests",
ui = "ui"
}
type ReadonlyFile = Readonly<ts.tscWatch.File>;
type ReadonlyFile = Readonly<File>;
/** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */
type SubProjectFiles = [tsconfig: ReadonlyFile, index: ReadonlyFile] | [tsconfig: ReadonlyFile, index: ReadonlyFile, anotherModule: ReadonlyFile, someDecl: ReadonlyFile];
function projectFilePath(subProject: SubProject, baseFileName: string) {
return `${ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", subProject)}/${baseFileName.toLowerCase()}`;
return `${getTsBuildProjectFilePath("sample1", subProject)}/${baseFileName.toLowerCase()}`;
}
function projectFile(subProject: SubProject, baseFileName: string): ts.tscWatch.File {
return ts.TestFSWithWatch.getTsBuildProjectFile("sample1", `${subProject}/${baseFileName}`);
function projectFile(subProject: SubProject, baseFileName: string): File {
return getTsBuildProjectFile("sample1", `${subProject}/${baseFileName}`);
}
function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles {
@@ -30,11 +30,11 @@ describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => {
return [tsconfig, index, anotherModule, someDecl];
}
function changeFile(fileName: string | (() => string), content: string | (() => string), caption: string): ts.tscWatch.TscWatchCompileChange {
function changeFile(fileName: string | (() => string), content: string | (() => string), caption: string): TscWatchCompileChange {
return {
caption,
change: sys => sys.writeFile(ts.isString(fileName) ? fileName : fileName(), ts.isString(content) ? content : content()),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Builds core
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // Builds core
};
}
@@ -46,14 +46,14 @@ describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => {
let logic: SubProjectFiles;
let tests: SubProjectFiles;
let ui: SubProjectFiles;
let allFiles: readonly ts.tscWatch.File[];
let allFiles: readonly File[];
before(() => {
core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true);
logic = subProjectFiles(SubProject.logic);
tests = subProjectFiles(SubProject.tests);
ui = subProjectFiles(SubProject.ui);
allFiles = [ts.tscWatch.libFile, ...core, ...logic, ...tests, ...ui];
allFiles = [libFile, ...core, ...logic, ...tests, ...ui];
});
after(() => {
@@ -64,20 +64,20 @@ describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => {
allFiles = undefined!;
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "creates solution in watch mode",
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
sys: () => createWatchedSystem(allFiles, { currentDirectory: "/user/username/projects" }),
changes: ts.emptyArray
});
it("verify building references watches only those projects", () => {
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }));
const host = ts.tscWatch.createSolutionBuilderWithWatchHostForBaseline(sys, cb);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(allFiles, { currentDirectory: "/user/username/projects" }));
const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb);
const solutionBuilder = ts.createSolutionBuilderWithWatch(host, [`sample1/${SubProject.tests}`], { watch: true });
solutionBuilder.buildReferences(`sample1/${SubProject.tests}`);
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "programUpdates",
subScenario: "verify building references watches only those projects",
commandLineArgs: ["--b", "--w"],
@@ -92,25 +92,28 @@ describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => {
describe("validates the changes and watched files", () => {
const newFileWithoutExtension = "newFile";
const newFile: ts.tscWatch.File = {
const newFile: File = {
path: projectFilePath(SubProject.core, `${newFileWithoutExtension}.ts`),
content: `export const newFileConst = 30;`
};
function verifyProjectChanges(subScenario: string, allFilesGetter: () => readonly ts.tscWatch.File[]) {
const buildLogicAndTests: ts.tscWatch.TscWatchCompileChange = {
function verifyProjectChanges(subScenario: string, allFilesGetter: () => readonly File[]) {
const buildLogicAndTests: TscWatchCompileChange = {
caption: "Build logic and tests",
change: ts.noop,
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
};
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: `${subScenario}/change builds changes and reports found errors message`,
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
allFilesGetter(),
{ currentDirectory: projectsLocation }
{ currentDirectory: "/user/username/projects" }
),
changes: [
changeCore(() => `${core[1].content}
@@ -130,19 +133,19 @@ export class someClass { }`;
sys.writeFile(core[1].path, `${change1}
export class someClass2 { }`);
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Builds core
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // Builds core
},
buildLogicAndTests,
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: `${subScenario}/non local change does not start build of referencing projects`,
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
allFilesGetter(),
{ currentDirectory: projectsLocation }
{ currentDirectory: "/user/username/projects" }
),
changes: [
changeCore(() => `${core[1].content}
@@ -153,13 +156,13 @@ function foo() { }`, "Make local change to core"),
function changeNewFile(newFileContent: string) {
return changeFile(newFile.path, newFileContent, "Change to new File and build core");
}
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: `${subScenario}/builds when new file is added, and its subsequent updates`,
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
allFilesGetter(),
{ currentDirectory: projectsLocation }
{ currentDirectory: "/user/username/projects" }
),
changes: [
changeNewFile(newFile.content),
@@ -183,44 +186,47 @@ export class someClass2 { }`),
"with circular project reference",
() => {
const [coreTsconfig, ...otherCoreFiles] = core;
const circularCoreConfig: ts.tscWatch.File = {
const circularCoreConfig: File = {
path: coreTsconfig.path,
content: JSON.stringify({
compilerOptions: { composite: true, declaration: true },
references: [{ path: "../tests", circular: true }]
})
};
return [ts.tscWatch.libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests];
return [libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests];
}
);
});
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "watches config files that are not present",
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
sys: () => ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, ...core, logic[1], ...tests],
{ currentDirectory: projectsLocation }
sys: () => createWatchedSystem(
[libFile, ...core, logic[1], ...tests],
{ currentDirectory: "/user/username/projects" }
),
changes: [
{
caption: "Write logic tsconfig and build logic",
change: sys => sys.writeFile(logic[0].path, logic[0].content),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Builds logic
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // Builds logic
},
{
caption: "Build Tests",
change: ts.noop,
// Build tests
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
}
]
});
describe("when referenced using prepend, builds referencing project even for non local change", () => {
let coreIndex: ts.tscWatch.File;
let coreIndex: File;
before(() => {
coreIndex = {
path: core[1].path,
@@ -230,35 +236,38 @@ export class someClass2 { }`),
after(() => {
coreIndex = undefined!;
});
const buildLogic: ts.tscWatch.TscWatchCompileChange = {
const buildLogic: TscWatchCompileChange = {
caption: "Build logic",
change: ts.noop,
// Builds logic
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
};
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "when referenced using prepend builds referencing project even for non local change",
commandLineArgs: ["-b", "-w", `sample1/${SubProject.logic}`],
sys: () => {
const coreTsConfig: ts.tscWatch.File = {
const coreTsConfig: File = {
path: core[0].path,
content: JSON.stringify({
compilerOptions: { composite: true, declaration: true, outFile: "index.js" }
})
};
const logicTsConfig: ts.tscWatch.File = {
const logicTsConfig: File = {
path: logic[0].path,
content: JSON.stringify({
compilerOptions: { composite: true, declaration: true, outFile: "index.js" },
references: [{ path: "../core", prepend: true }]
})
};
const logicIndex: ts.tscWatch.File = {
const logicIndex: File = {
path: logic[1].path,
content: `function bar() { return foo() + 1 };`
};
return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, coreTsConfig, coreIndex, logicTsConfig, logicIndex], { currentDirectory: projectsLocation });
return createWatchedSystem([libFile, coreTsConfig, coreIndex, logicTsConfig, logicIndex], { currentDirectory: "/user/username/projects" });
},
changes: [
changeCore(() => `${coreIndex.content}
@@ -272,8 +281,8 @@ 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}/sample1/Library`;
const libraryTs: ts.tscWatch.File = {
const subProjectLibrary = `${"/user/username/projects"}/sample1/Library`;
const libraryTs: File = {
path: `${subProjectLibrary}/library.ts`,
content: `
interface SomeObject
@@ -288,28 +297,28 @@ export function createSomeObject(): SomeObject
};
}`
};
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "when referenced project change introduces error in the down stream project and then fixes it",
commandLineArgs: ["-b", "-w", "App"],
sys: () => {
const libraryTsconfig: ts.tscWatch.File = {
const libraryTsconfig: File = {
path: `${subProjectLibrary}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true } })
};
const subProjectApp = `${projectsLocation}/sample1/App`;
const appTs: ts.tscWatch.File = {
const subProjectApp = `${"/user/username/projects"}/sample1/App`;
const appTs: File = {
path: `${subProjectApp}/app.ts`,
content: `import { createSomeObject } from "../Library/library";
createSomeObject().message;`
};
const appTsconfig: ts.tscWatch.File = {
const appTsconfig: File = {
path: `${subProjectApp}/tsconfig.json`,
content: JSON.stringify({ references: [{ path: "../Library" }] })
};
const files = [ts.tscWatch.libFile, libraryTs, libraryTsconfig, appTs, appTsconfig];
return ts.tscWatch.createWatchedSystem(files, { currentDirectory: `${projectsLocation}/sample1` });
const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig];
return createWatchedSystem(files, { currentDirectory: `${"/user/username/projects"}/sample1` });
},
changes: [
{
@@ -337,25 +346,31 @@ createSomeObject().message;`
describe("reports errors in all projects on incremental compile", () => {
function verifyIncrementalErrors(subScenario: string, buildOptions: readonly string[]) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: `reportErrors/${subScenario}`,
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, ...buildOptions],
sys: () => ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
sys: () => createWatchedSystem(allFiles, { currentDirectory: "/user/username/projects" }),
changes: [
{
caption: "change logic",
change: sys => sys.writeFile(logic[1].path, `${logic[1].content}
let y: string = 10;`),
// Builds logic
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
},
{
caption: "change core",
change: sys => sys.writeFile(core[1].path, `${core[1].content}
let x: string = 10;`),
// Builds core
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
}
]
});
@@ -366,65 +381,65 @@ let x: string = 10;`),
describe("when declaration emit errors are present", () => {
const solution = "solution";
const subProject = "app";
const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`;
const fileWithError: ts.tscWatch.File = {
const subProjectLocation = `${"/user/username/projects"}/${solution}/${subProject}`;
const fileWithError: File = {
path: `${subProjectLocation}/fileWithError.ts`,
content: `export var myClassWithError = class {
tags() { }
private p = 12
};`
};
const fileWithFixedError: ts.tscWatch.File = {
const fileWithFixedError: File = {
path: fileWithError.path,
content: fileWithError.content.replace("private p = 12", "")
};
const fileWithoutError: ts.tscWatch.File = {
const fileWithoutError: File = {
path: `${subProjectLocation}/fileWithoutError.ts`,
content: `export class myClass { }`
};
const tsconfig: ts.tscWatch.File = {
const tsconfig: File = {
path: `${subProjectLocation}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true } })
};
function incrementalBuild(sys: ts.tscWatch.WatchedSystem) {
function incrementalBuild(sys: TestServerHost) {
sys.checkTimeoutQueueLengthAndRun(1); // Build the app
sys.checkTimeoutQueueLength(0);
}
const fixError: ts.tscWatch.TscWatchCompileChange = {
const fixError: TscWatchCompileChange = {
caption: "Fix error in fileWithError",
// Fix error
change: sys => sys.writeFile(fileWithError.path, fileWithFixedError.content),
timeouts: incrementalBuild
};
const changeFileWithoutError: ts.tscWatch.TscWatchCompileChange = {
const changeFileWithoutError: TscWatchCompileChange = {
caption: "Change fileWithoutError",
change: sys => sys.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2")),
timeouts: incrementalBuild
};
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "reportErrors/declarationEmitErrors/when fixing error files all files are emitted",
commandLineArgs: ["-b", "-w", subProject],
sys: () => ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, fileWithError, fileWithoutError, tsconfig],
{ currentDirectory: `${projectsLocation}/${solution}` }
sys: () => createWatchedSystem(
[libFile, fileWithError, fileWithoutError, tsconfig],
{ currentDirectory: `${"/user/username/projects"}/${solution}` }
),
changes: [
fixError
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "reportErrors/declarationEmitErrors/when file with no error changes",
commandLineArgs: ["-b", "-w", subProject],
sys: () => ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, fileWithError, fileWithoutError, tsconfig],
{ currentDirectory: `${projectsLocation}/${solution}` }
sys: () => createWatchedSystem(
[libFile, fileWithError, fileWithoutError, tsconfig],
{ currentDirectory: `${"/user/username/projects"}/${solution}` }
),
changes: [
changeFileWithoutError
@@ -432,19 +447,19 @@ let x: string = 10;`),
});
describe("when reporting errors on introducing error", () => {
const introduceError: ts.tscWatch.TscWatchCompileChange = {
const introduceError: TscWatchCompileChange = {
caption: "Introduce error",
change: sys => sys.writeFile(fileWithError.path, fileWithError.content),
timeouts: incrementalBuild,
};
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "reportErrors/declarationEmitErrors/introduceError/when fixing errors only changed file is emitted",
commandLineArgs: ["-b", "-w", subProject],
sys: () => ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, fileWithFixedError, fileWithoutError, tsconfig],
{ currentDirectory: `${projectsLocation}/${solution}` }
sys: () => createWatchedSystem(
[libFile, fileWithFixedError, fileWithoutError, tsconfig],
{ currentDirectory: `${"/user/username/projects"}/${solution}` }
),
changes: [
introduceError,
@@ -452,13 +467,13 @@ let x: string = 10;`),
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "reportErrors/declarationEmitErrors/introduceError/when file with no error changes",
commandLineArgs: ["-b", "-w", subProject],
sys: () => ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, fileWithFixedError, fileWithoutError, tsconfig],
{ currentDirectory: `${projectsLocation}/${solution}` }
sys: () => createWatchedSystem(
[libFile, fileWithFixedError, fileWithoutError, tsconfig],
{ currentDirectory: `${"/user/username/projects"}/${solution}` }
),
changes: [
introduceError,
@@ -469,11 +484,11 @@ let x: string = 10;`),
});
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "incremental updates in verbose mode",
commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, "-verbose"],
sys: () => ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
sys: () => createWatchedSystem(allFiles, { currentDirectory: "/user/username/projects" }),
changes: [
{
caption: "Make non dts change",
@@ -496,104 +511,104 @@ export function someFn() { }`),
],
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "works when noUnusedParameters changes to false",
commandLineArgs: ["-b", "-w"],
sys: () => {
const index: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/index.ts`,
const index: File = {
path: `/user/username/projects/myproject/index.ts`,
content: `const fn = (a: string, b: string) => b;`
};
const configFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const configFile: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
noUnusedParameters: true
}
})
};
return ts.tscWatch.createWatchedSystem([index, configFile, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot });
return createWatchedSystem([index, configFile, libFile], { currentDirectory: "/user/username/projects/myproject" });
},
changes: [
{
caption: "Change tsconfig to set noUnusedParameters to false",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({
change: sys => sys.writeFile(`/user/username/projects/myproject/tsconfig.json`, JSON.stringify({
compilerOptions: {
noUnusedParameters: false
}
})),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "should not trigger recompilation because of program emit",
commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"],
sys: () => ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, ...core], { currentDirectory: projectsLocation }),
sys: () => createWatchedSystem([libFile, ...core], { currentDirectory: "/user/username/projects" }),
changes: [
ts.tscWatch.noopChange,
noopChange,
{
caption: "Add new file",
change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
ts.tscWatch.noopChange,
noopChange,
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "should not trigger recompilation because of program emit with outDir specified",
commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"],
sys: () => {
const [coreConfig, ...rest] = core;
const newCoreConfig: ts.tscWatch.File = { path: coreConfig.path, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "outDir" } }) };
return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, newCoreConfig, ...rest], { currentDirectory: projectsLocation });
const newCoreConfig: File = { path: coreConfig.path, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "outDir" } }) };
return createWatchedSystem([libFile, newCoreConfig, ...rest], { currentDirectory: "/user/username/projects" });
},
changes: [
ts.tscWatch.noopChange,
noopChange,
{
caption: "Add new file",
change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
ts.tscWatch.noopChange
noopChange
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "works with extended source files",
commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"],
sys: () => {
const alphaExtendedConfigFile: ts.tscWatch.File = {
const alphaExtendedConfigFile: File = {
path: "/a/b/alpha.tsconfig.json",
content: "{}"
};
const project1Config: ts.tscWatch.File = {
const project1Config: File = {
path: "/a/b/project1.tsconfig.json",
content: JSON.stringify({
extends: "./alpha.tsconfig.json",
compilerOptions: {
composite: true,
},
files: [ts.tscWatch.commonFile1.path, ts.tscWatch.commonFile2.path]
files: [commonFile1.path, commonFile2.path]
})
};
const bravoExtendedConfigFile: ts.tscWatch.File = {
const bravoExtendedConfigFile: File = {
path: "/a/b/bravo.tsconfig.json",
content: JSON.stringify({
extends: "./alpha.tsconfig.json"
})
};
const otherFile: ts.tscWatch.File = {
const otherFile: File = {
path: "/a/b/other.ts",
content: "let z = 0;",
};
const project2Config: ts.tscWatch.File = {
const project2Config: File = {
path: "/a/b/project2.tsconfig.json",
content: JSON.stringify({
extends: "./bravo.tsconfig.json",
@@ -603,9 +618,9 @@ export function someFn() { }`),
files: [otherFile.path]
})
};
return ts.tscWatch.createWatchedSystem([
ts.tscWatch.libFile,
alphaExtendedConfigFile, project1Config, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2,
return createWatchedSystem([
libFile,
alphaExtendedConfigFile, project1Config, commonFile1, commonFile2,
bravoExtendedConfigFile, project2Config, otherFile
], { currentDirectory: "/a/b" });
},
@@ -615,12 +630,15 @@ export function someFn() { }`),
change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", JSON.stringify({
compilerOptions: { strict: true }
})),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build project1
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build project1
},
{
caption: "Build project 2",
change: ts.noop,
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
timeouts: sys => { // Build project2
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
},
{
caption: "change bravo config",
@@ -628,34 +646,43 @@ export function someFn() { }`),
extends: "./alpha.tsconfig.json",
compilerOptions: { strict: false }
})),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
timeouts: sys => { // Build project2
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
},
{
caption: "project 2 extends alpha",
change: sys => sys.writeFile("/a/b/project2.tsconfig.json", JSON.stringify({
extends: "./alpha.tsconfig.json",
})),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
timeouts: sys => { // Build project2
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
},
{
caption: "update aplha config",
change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", "{}"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // build project1
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // build project1
},
{
caption: "Build project 2",
change: ts.noop,
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
timeouts: sys => { // Build project2
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
},
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "works correctly when project with extended config is removed",
commandLineArgs: ["-b", "-w", "-v"],
sys: () => {
const alphaExtendedConfigFile: ts.tscWatch.File = {
const alphaExtendedConfigFile: File = {
path: "/a/b/alpha.tsconfig.json",
content: JSON.stringify({
compilerOptions: {
@@ -663,17 +690,17 @@ export function someFn() { }`),
}
})
};
const project1Config: ts.tscWatch.File = {
const project1Config: File = {
path: "/a/b/project1.tsconfig.json",
content: JSON.stringify({
extends: "./alpha.tsconfig.json",
compilerOptions: {
composite: true,
},
files: [ts.tscWatch.commonFile1.path, ts.tscWatch.commonFile2.path]
files: [commonFile1.path, commonFile2.path]
})
};
const bravoExtendedConfigFile: ts.tscWatch.File = {
const bravoExtendedConfigFile: File = {
path: "/a/b/bravo.tsconfig.json",
content: JSON.stringify({
compilerOptions: {
@@ -681,11 +708,11 @@ export function someFn() { }`),
}
})
};
const otherFile: ts.tscWatch.File = {
const otherFile: File = {
path: "/a/b/other.ts",
content: "let z = 0;",
};
const project2Config: ts.tscWatch.File = {
const project2Config: File = {
path: "/a/b/project2.tsconfig.json",
content: JSON.stringify({
extends: "./bravo.tsconfig.json",
@@ -695,7 +722,7 @@ export function someFn() { }`),
files: [otherFile.path]
})
};
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({
references: [
@@ -709,9 +736,9 @@ export function someFn() { }`),
files: [],
})
};
return ts.tscWatch.createWatchedSystem([
ts.tscWatch.libFile, configFile,
alphaExtendedConfigFile, project1Config, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2,
return createWatchedSystem([
libFile, configFile,
alphaExtendedConfigFile, project1Config, commonFile1, commonFile2,
bravoExtendedConfigFile, project2Config, otherFile
], { currentDirectory: "/a/b" });
},
@@ -726,19 +753,22 @@ export function someFn() { }`),
],
files: [],
})),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
}
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "programUpdates",
subScenario: "tsbuildinfo has error",
sys: () => ts.tscWatch.createWatchedSystem({
sys: () => createWatchedSystem({
"/src/project/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": "{}",
"/src/project/tsconfig.tsbuildinfo": "Some random string",
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
[libFile.path]: libFile.content,
}),
commandLineArgs: ["--b", "src/project", "-i", "-w"],
changes: ts.emptyArray

View File

@@ -1,4 +1,6 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, libFile } from "../virtualFileSystemWithWatch";
import { noopChange, TscWatchCompileChange, verifyTscWatch } from "../tscWatch/helpers";
describe("unittests:: tsbuildWatch:: watchMode:: projectsBuilding", () => {
function pkgs<T>(cb: (index: number) => T, count: number, startIndex?: number): T[] {
@@ -11,14 +13,14 @@ describe("unittests:: tsbuildWatch:: watchMode:: projectsBuilding", () => {
function createPkgReference(index: number) {
return { path: `./pkg${index}` };
}
function pkgFiles(index: number): ts.tscWatch.File[] {
function pkgFiles(index: number): File[] {
return [
{
path: `${ts.tscWatch.projectRoot}/pkg${index}/index.ts`,
path: `/user/username/projects/myproject/pkg${index}/index.ts`,
content: `export const pkg${index} = ${index};`
},
{
path: `${ts.tscWatch.projectRoot}/pkg${index}/tsconfig.json`,
path: `/user/username/projects/myproject/pkg${index}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true },
references: index === 0 ?
@@ -28,158 +30,158 @@ describe("unittests:: tsbuildWatch:: watchMode:: projectsBuilding", () => {
}
];
}
function solution(maxPkgs: number): ts.tscWatch.File {
function solution(maxPkgs: number): File {
return {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
references: pkgs(createPkgReference, maxPkgs),
files: [],
})
};
}
function checkBuildPkg(startIndex: number, count: number): ts.tscWatch.TscWatchCompileChange {
function checkBuildPkg(startIndex: number, count: number): TscWatchCompileChange {
return {
caption: `build ${pkgs(index => `pkg${index}`, count, startIndex).join(",")}`,
change: ts.noop,
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
};
}
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsBuilding",
subScenario: `when there are 3 projects in a solution`,
commandLineArgs: ["-b", "-w", "-v"],
sys: () => ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, ...ts.flatMap(pkgs(pkgFiles, 3), ts.identity), solution(3)],
{ currentDirectory: ts.tscWatch.projectRoot }
sys: () => createWatchedSystem(
[libFile, ...ts.flatMap(pkgs(pkgFiles, 3), ts.identity), solution(3)],
{ currentDirectory: "/user/username/projects/myproject" }
),
changes: [
{
caption: "dts doesn't change",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `const someConst2 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // Build pkg0 and update timestamps
},
ts.tscWatch.noopChange,
noopChange,
{
caption: "dts change",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `export const someConst = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build pkg0
},
checkBuildPkg(1, 2),
ts.tscWatch.noopChange,
noopChange,
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsBuilding",
subScenario: `when there are 5 projects in a solution`,
commandLineArgs: ["-b", "-w", "-v"],
sys: () => ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, ...ts.flatMap(pkgs(pkgFiles, 5), ts.identity), solution(5)],
{ currentDirectory: ts.tscWatch.projectRoot }
sys: () => createWatchedSystem(
[libFile, ...ts.flatMap(pkgs(pkgFiles, 5), ts.identity), solution(5)],
{ currentDirectory: "/user/username/projects/myproject" }
),
changes: [
{
caption: "dts doesn't change",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `const someConst2 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // Build pkg0 and update timestamps
},
ts.tscWatch.noopChange,
noopChange,
{
caption: "dts change",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `export const someConst = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build pkg0
},
checkBuildPkg(1, 4),
ts.tscWatch.noopChange,
noopChange,
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsBuilding",
subScenario: `when there are 8 projects in a solution`,
commandLineArgs: ["-b", "-w", "-v"],
sys: () => ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, ...ts.flatMap(pkgs(pkgFiles, 8), ts.identity), solution(8)],
{ currentDirectory: ts.tscWatch.projectRoot }
sys: () => createWatchedSystem(
[libFile, ...ts.flatMap(pkgs(pkgFiles, 8), ts.identity), solution(8)],
{ currentDirectory: "/user/username/projects/myproject" }
),
changes: [
{
caption: "dts doesn't change",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `const someConst2 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // Build pkg0 and update timestamps
},
ts.tscWatch.noopChange,
noopChange,
{
caption: "dts change",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `export const someConst = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build pkg0
},
checkBuildPkg(1, 5),
checkBuildPkg(6, 2),
ts.tscWatch.noopChange,
noopChange,
{
caption: "dts change2",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst3 = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `export const someConst3 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build pkg0
},
checkBuildPkg(1, 5),
{
caption: "change while building",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst4 = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `const someConst4 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build pkg0
},
checkBuildPkg(6, 2),
ts.tscWatch.noopChange,
noopChange,
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsBuilding",
subScenario: `when there are 23 projects in a solution`,
commandLineArgs: ["-b", "-w", "-v"],
sys: () => ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, ...ts.flatMap(pkgs(pkgFiles, 23), ts.identity), solution(23)],
{ currentDirectory: ts.tscWatch.projectRoot }
sys: () => createWatchedSystem(
[libFile, ...ts.flatMap(pkgs(pkgFiles, 23), ts.identity), solution(23)],
{ currentDirectory: "/user/username/projects/myproject" }
),
changes: [
{
caption: "dts doesn't change",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `const someConst2 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // Build pkg0 and update timestamps
},
ts.tscWatch.noopChange,
noopChange,
{
caption: "dts change",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `export const someConst = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build pkg0
},
checkBuildPkg(1, 5),
checkBuildPkg(6, 5),
checkBuildPkg(11, 5),
checkBuildPkg(16, 5),
checkBuildPkg(21, 3),
ts.tscWatch.noopChange,
noopChange,
{
caption: "dts change2",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst3 = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `export const someConst3 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build pkg0
},
checkBuildPkg(1, 5),
checkBuildPkg(6, 5),
{
caption: "change while building",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst4 = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `const someConst4 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build pkg0
},
checkBuildPkg(11, 5),
{
caption: "change while building: dts changes",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst5 = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0
change: sys => sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `export const someConst5 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1) // Build pkg0
},
checkBuildPkg(1, 5),
checkBuildPkg(6, 5),
checkBuildPkg(11, 5),
checkBuildPkg(16, 5),
checkBuildPkg(21, 3),
ts.tscWatch.noopChange,
noopChange,
]
});
});

View File

@@ -1,8 +1,10 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, libFile } from "../virtualFileSystemWithWatch";
import { createBaseline, createSolutionBuilderWithWatchHostForBaseline, runWatchBaseline } from "../tscWatch/helpers";
it("unittests:: tsbuildWatch:: watchMode:: Public API with custom transformers", () => {
const solution: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const solution: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
references: [
{ path: "./shared/tsconfig.json" },
@@ -11,29 +13,29 @@ it("unittests:: tsbuildWatch:: watchMode:: Public API with custom transformers",
files: []
})
};
const sharedConfig: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/shared/tsconfig.json`,
const sharedConfig: File = {
path: `/user/username/projects/myproject/shared/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true },
})
};
const sharedIndex: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/shared/index.ts`,
const sharedIndex: File = {
path: `/user/username/projects/myproject/shared/index.ts`,
content: `export function f1() { }
export class c { }
export enum e { }
// leading
export function f2() { } // trailing`
};
const webpackConfig: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/webpack/tsconfig.json`,
const webpackConfig: File = {
path: `/user/username/projects/myproject/webpack/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true, },
references: [{ path: "../shared/tsconfig.json" }]
})
};
const webpackIndex: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/webpack/index.ts`,
const webpackIndex: File = {
path: `/user/username/projects/myproject/webpack/index.ts`,
content: `export function f2() { }
export class c2 { }
export enum e2 { }
@@ -41,12 +43,12 @@ export enum e2 { }
export function f22() { } // trailing`
};
const commandLineArgs = ["--b", "--w"];
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: ts.tscWatch.projectRoot }));
const buildHost = ts.tscWatch.createSolutionBuilderWithWatchHostForBaseline(sys, cb);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: "/user/username/projects/myproject" }));
const buildHost = createSolutionBuilderWithWatchHostForBaseline(sys, cb);
buildHost.getCustomTransformers = getCustomTransformers;
const builder = ts.createSolutionBuilderWithWatch(buildHost, [solution.path], { verbose: true });
builder.build();
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "publicApi",
subScenario: "with custom transformers",
commandLineArgs,

View File

@@ -1,26 +1,28 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, getTsBuildProjectFile, libFile } from "../virtualFileSystemWithWatch";
import { libContent } from "../tsc/helpers";
import { verifyTscWatch } from "../tscWatch/helpers";
describe("unittests:: tsbuildWatch:: watchMode:: with reexport when referenced project reexports definitions from another file", () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "reexport",
subScenario: "Reports errors correctly",
commandLineArgs: ["-b", "-w", "-verbose", "src"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
[
...[
"src/tsconfig.json",
"src/main/tsconfig.json", "src/main/index.ts",
"src/pure/tsconfig.json", "src/pure/index.ts", "src/pure/session.ts"
]
.map(f => ts.TestFSWithWatch.getTsBuildProjectFile("reexport", f)),
{ path: ts.tscWatch.libFile.path, content: ts.libContent }
.map(f => getTsBuildProjectFile("reexport", f)),
{ path: libFile.path, content: libContent }
],
{ currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/reexport` }
{ currentDirectory: `/user/username/projects/reexport` }
),
changes: [
{
caption: "Introduce error",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "// ", ""),
change: sys => sys.replaceFileText(`/user/username/projects/reexport/src/pure/session.ts`, "// ", ""),
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1); // build src/pure
sys.checkTimeoutQueueLengthAndRun(1); // build src/main and src
@@ -29,7 +31,7 @@ describe("unittests:: tsbuildWatch:: watchMode:: with reexport when referenced p
},
{
caption: "Fix error",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "bar: ", "// bar: "),
change: sys => sys.replaceFileText(`/user/username/projects/reexport/src/pure/session.ts`, "bar: ", "// bar: "),
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1); // build src/pure
sys.checkTimeoutQueueLengthAndRun(1); // build src/main and src

View File

@@ -1,23 +1,25 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, libFile, TestServerHost } from "../virtualFileSystemWithWatch";
import { createBaseline, createSolutionBuilderWithWatchHostForBaseline, runWatchBaseline } from "../tscWatch/helpers";
describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: with different watch environments", () => {
it("watchFile on same file multiple times because file is part of multiple projects", () => {
const project = `${ts.TestFSWithWatch.tsbuildProjectsLocation}/myproject`;
const project = `/user/username/projects/myproject`;
let maxPkgs = 4;
const configPath = `${project}/tsconfig.json`;
const typing: ts.tscWatch.File = {
const typing: File = {
path: `${project}/typings/xterm.d.ts`,
content: "export const typing = 10;"
};
const allPkgFiles = pkgs(pkgFiles);
const system = ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project });
const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project });
writePkgReferences(system);
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(system);
const host = ts.tscWatch.createSolutionBuilderWithWatchHostForBaseline(sys, cb);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(system);
const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb);
const solutionBuilder = ts.createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true });
solutionBuilder.build();
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchEnvironment",
subScenario: `same file in multiple projects with single watcher per file`,
commandLineArgs: ["--b", "--w"],
@@ -31,7 +33,8 @@ describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: wi
change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`),
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys);
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
}
},
{
@@ -41,14 +44,15 @@ describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: wi
maxPkgs--;
writePkgReferences(sys);
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, typing.content),
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys);
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
}
},
{
@@ -58,7 +62,7 @@ describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: wi
maxPkgs = 0;
writePkgReferences(sys);
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "modify typing file",
@@ -82,7 +86,7 @@ describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: wi
function createPkgReference(index: number) {
return { path: `./pkg${index}` };
}
function pkgFiles(index: number): ts.tscWatch.File[] {
function pkgFiles(index: number): File[] {
return [
{
path: `${project}/pkg${index}/index.ts`,
@@ -100,7 +104,7 @@ describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: wi
}
];
}
function writePkgReferences(system: ts.TestFSWithWatch.TestServerHost) {
function writePkgReferences(system: TestServerHost) {
system.writeFile(configPath, JSON.stringify({
files: [],
include: [],

View File

@@ -1,46 +1,49 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import * as Harness from "../../_namespaces/Harness";
import { createWatchedSystem, File, libFile } from "../virtualFileSystemWithWatch";
import { baselineBuildInfo, CommandLineProgram } from "../tsc/helpers";
import { applyChange, createBaseline, watchBaseline } from "../tscWatch/helpers";
describe("unittests:: tsc:: builder cancellationToken", () => {
verifyCancellation(/*useBuildInfo*/ true, "when emitting buildInfo");
verifyCancellation(/*useBuildInfo*/ false, "when using state");
function verifyCancellation(useBuildInfo: boolean, scenario: string) {
it(scenario, () => {
const aFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/a.ts`,
const aFile: File = {
path: `/user/username/projects/myproject/a.ts`,
content: Utils.dedent`
import {B} from './b';
declare var console: any;
let b = new B();
console.log(b.c.d);`
};
const bFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/b.ts`,
const bFile: File = {
path: `/user/username/projects/myproject/b.ts`,
content: Utils.dedent`
import {C} from './c';
export class B {
c = new C();
}`
};
const cFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/c.ts`,
const cFile: File = {
path: `/user/username/projects/myproject/c.ts`,
content: Utils.dedent`
export class C {
d = 1;
}`
};
const dFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/d.ts`,
const dFile: File = {
path: `/user/username/projects/myproject/d.ts`,
content: "export class D { }"
};
const config: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { incremental: true, declaration: true } })
};
const { sys, baseline, oldSnap: originalSnap } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem(
[aFile, bFile, cFile, dFile, config, ts.tscWatch.libFile],
{ currentDirectory: ts.tscWatch.projectRoot }
const { sys, baseline, oldSnap: originalSnap } = createBaseline(createWatchedSystem(
[aFile, bFile, cFile, dFile, config, libFile],
{ currentDirectory: "/user/username/projects/myproject" }
));
sys.exit = exitCode => sys.exitCode = exitCode;
const reportDiagnostic = ts.createDiagnosticReporter(sys, /*pretty*/ true);
@@ -53,8 +56,8 @@ describe("unittests:: tsc:: builder cancellationToken", () => {
reportDiagnostic
)!;
const host = ts.createIncrementalCompilerHost(parsedConfig.options, sys);
let programs: ts.CommandLineProgram[] = ts.emptyArray;
let oldPrograms: ts.CommandLineProgram[] = ts.emptyArray;
let programs: CommandLineProgram[] = ts.emptyArray;
let oldPrograms: CommandLineProgram[] = ts.emptyArray;
let builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram = undefined!;
let oldSnap = originalSnap;
let cancel = false;
@@ -73,7 +76,7 @@ describe("unittests:: tsc:: builder cancellationToken", () => {
// Cancel on first semantic operation
// Change
oldSnap = ts.tscWatch.applyChange(
oldSnap = applyChange(
sys,
baseline,
sys => sys.appendFile(cFile.path, "export function foo() {}"),
@@ -91,8 +94,8 @@ describe("unittests:: tsc:: builder cancellationToken", () => {
}
cancel = false;
builderProgram.emitBuildInfo();
ts.baselineBuildInfo(builderProgram.getCompilerOptions(), sys);
ts.tscWatch.watchBaseline({
baselineBuildInfo(builderProgram.getCompilerOptions(), sys);
watchBaseline({
baseline,
getPrograms: () => programs,
oldPrograms,
@@ -111,7 +114,7 @@ describe("unittests:: tsc:: builder cancellationToken", () => {
Harness.Baseline.runBaseline(`tsc/cancellationToken/${scenario.split(" ").join("-")}.js`, baseline.join("\r\n"));
function noChange(caption: string) {
oldSnap = ts.tscWatch.applyChange(sys, baseline, ts.noop, caption);
oldSnap = applyChange(sys, baseline, ts.noop, caption);
}
function updatePrograms() {
@@ -139,8 +142,8 @@ describe("unittests:: tsc:: builder cancellationToken", () => {
function emitAndBaseline() {
ts.emitFilesAndReportErrorsAndGetExitStatus(builderProgram, reportDiagnostic);
ts.baselineBuildInfo(builderProgram.getCompilerOptions(), sys);
ts.tscWatch.watchBaseline({
baselineBuildInfo(builderProgram.getCompilerOptions(), sys);
watchBaseline({
baseline,
getPrograms: () => programs,
oldPrograms,

View File

@@ -1,11 +1,11 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { loadProjectFromFiles, replaceText, verifyTsc, verifyTscWithEdits } from "./helpers";
describe("unittests:: tsc:: composite::", () => {
ts.verifyTsc({
verifyTsc({
scenario: "composite",
subScenario: "when setting composite false on command line",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
{
@@ -22,10 +22,10 @@ describe("unittests:: tsc:: composite::", () => {
commandLineArgs: ["--composite", "false", "--p", "src/project"],
});
ts.verifyTsc({
verifyTsc({
scenario: "composite",
subScenario: "when setting composite null on command line",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
{
@@ -42,10 +42,10 @@ describe("unittests:: tsc:: composite::", () => {
commandLineArgs: ["--composite", "null", "--p", "src/project"],
});
ts.verifyTsc({
verifyTsc({
scenario: "composite",
subScenario: "when setting composite false on command line but has tsbuild info in config",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
{
@@ -63,10 +63,10 @@ describe("unittests:: tsc:: composite::", () => {
commandLineArgs: ["--composite", "false", "--p", "src/project"],
});
ts.verifyTsc({
verifyTsc({
scenario: "composite",
subScenario: "when setting composite false and tsbuildinfo as null on command line but has tsbuild info in config",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
{
@@ -84,10 +84,10 @@ describe("unittests:: tsc:: composite::", () => {
commandLineArgs: ["--composite", "false", "--p", "src/project", "--tsBuildInfoFile", "null"],
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "composite",
subScenario: "converting to modules",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "const x = 10;",
"/src/project/tsconfig.json": JSON.stringify({
compilerOptions: {
@@ -100,7 +100,7 @@ describe("unittests:: tsc:: composite::", () => {
edits: [
{
subScenario: "convert to modules",
modifyFs: fs => ts.replaceText(fs, "/src/project/tsconfig.json", "none", "es2015"),
modifyFs: fs => replaceText(fs, "/src/project/tsconfig.json", "none", "es2015"),
}
]
});

View File

@@ -1,26 +1,28 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { createWatchedSystem, FileOrFolderOrSymLink, isSymLink, libFile } from "../virtualFileSystemWithWatch";
import { verifyTscWatch } from "../tscWatch/helpers";
describe("unittests:: tsc:: declarationEmit::", () => {
interface VerifyDeclarationEmitInput {
subScenario: string;
files: ts.TestFSWithWatch.FileOrFolderOrSymLink[];
files: FileOrFolderOrSymLink[];
rootProject: string;
changeCaseFileTestPath: (path: string) => boolean;
}
function changeCaseFile(file: ts.TestFSWithWatch.FileOrFolderOrSymLink, testPath: (path: string) => boolean, replacePath: (path: string) => string): ts.TestFSWithWatch.FileOrFolderOrSymLink {
return !ts.TestFSWithWatch.isSymLink(file) || !testPath(file.symLink) ?
function changeCaseFile(file: FileOrFolderOrSymLink, testPath: (path: string) => boolean, replacePath: (path: string) => string): FileOrFolderOrSymLink {
return !isSymLink(file) || !testPath(file.symLink) ?
testPath(file.path) ? { ...file, path: replacePath(file.path) } : file :
{ path: testPath(file.path) ? replacePath(file.path) : file.path, symLink: replacePath(file.symLink) };
}
function verifyDeclarationEmit({ subScenario, files, rootProject, changeCaseFileTestPath }: VerifyDeclarationEmitInput) {
describe(subScenario, () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "declarationEmit",
subScenario,
sys: () => ts.tscWatch.createWatchedSystem(files, { currentDirectory: ts.tscWatch.projectRoot }),
sys: () => createWatchedSystem(files, { currentDirectory: "/user/username/projects/myproject" }),
commandLineArgs: ["-p", rootProject, "--explainFiles"],
changes: ts.emptyArray
});
@@ -28,12 +30,12 @@ describe("unittests:: tsc:: declarationEmit::", () => {
const caseChangeScenario = `${subScenario} moduleCaseChange`;
describe(caseChangeScenario, () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "declarationEmit",
subScenario: caseChangeScenario,
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
files.map(f => changeCaseFile(f, changeCaseFileTestPath, str => str.replace("myproject", "myProject"))),
{ currentDirectory: ts.tscWatch.projectRoot }
{ currentDirectory: "/user/username/projects/myproject" }
),
commandLineArgs: ["-p", rootProject, "--explainFiles"],
changes: ts.emptyArray
@@ -112,16 +114,16 @@ describe("unittests:: tsc:: declarationEmit::", () => {
subScenario: "when same version is referenced through source and another symlinked package",
rootProject: "plugin-one",
files: [
{ path: `${ts.tscWatch.projectRoot}/plugin-two/index.d.ts`, content: pluginTwoDts() },
{ path: `${ts.tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() },
{ path: `${ts.tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() },
{ path: `${ts.tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() },
{ path: `${ts.tscWatch.projectRoot}/plugin-one/index.ts`, content: pluginOneIndex() },
{ path: `${ts.tscWatch.projectRoot}/plugin-one/action.ts`, content: pluginOneAction() },
{ path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() },
{ path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() },
{ path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `${ts.tscWatch.projectRoot}/plugin-two` },
ts.tscWatch.libFile
{ path: `/user/username/projects/myproject/plugin-two/index.d.ts`, content: pluginTwoDts() },
{ path: `/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() },
{ path: `/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() },
{ path: `/user/username/projects/myproject/plugin-one/tsconfig.json`, content: pluginOneConfig() },
{ path: `/user/username/projects/myproject/plugin-one/index.ts`, content: pluginOneIndex() },
{ path: `/user/username/projects/myproject/plugin-one/action.ts`, content: pluginOneAction() },
{ path: `/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() },
{ path: `/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() },
{ path: `/user/username/projects/myproject/plugin-one/node_modules/plugin-two`, symLink: `/user/username/projects/myproject/plugin-two` },
libFile
],
changeCaseFileTestPath: str => ts.stringContains(str, "/plugin-two"),
});
@@ -131,27 +133,27 @@ describe("unittests:: tsc:: declarationEmit::", () => {
rootProject: "plugin-one",
files: [
{
path: `${ts.tscWatch.projectRoot}/plugin-two/package.json`,
path: `/user/username/projects/myproject/plugin-two/package.json`,
content: JSON.stringify({
name: "plugin-two",
version: "0.1.3",
main: "dist/commonjs/index.js"
})
},
{ path: `${ts.tscWatch.projectRoot}/plugin-two/dist/commonjs/index.d.ts`, content: pluginTwoDts() },
{ path: `${ts.tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() },
{ path: `${ts.tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() },
{ path: `${ts.tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() },
{ path: `/user/username/projects/myproject/plugin-two/dist/commonjs/index.d.ts`, content: pluginTwoDts() },
{ path: `/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() },
{ path: `/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() },
{ path: `/user/username/projects/myproject/plugin-one/tsconfig.json`, content: pluginOneConfig() },
{
path: `${ts.tscWatch.projectRoot}/plugin-one/index.ts`,
path: `/user/username/projects/myproject/plugin-one/index.ts`,
content: `${pluginOneIndex()}
${pluginOneAction()}`
},
{ path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() },
{ path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() },
{ path: `/temp/yarn/data/link/plugin-two`, symLink: `${ts.tscWatch.projectRoot}/plugin-two` },
{ path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `/temp/yarn/data/link/plugin-two` },
ts.tscWatch.libFile
{ path: `/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() },
{ path: `/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() },
{ path: `/temp/yarn/data/link/plugin-two`, symLink: `/user/username/projects/myproject/plugin-two` },
{ path: `/user/username/projects/myproject/plugin-one/node_modules/plugin-two`, symLink: `/temp/yarn/data/link/plugin-two` },
libFile
],
changeCaseFileTestPath: str => ts.stringContains(str, "/plugin-two"),
});
@@ -162,12 +164,12 @@ ${pluginOneAction()}`
rootProject: "pkg3",
files: [
{
path: `${ts.tscWatch.projectRoot}/pkg1/dist/index.d.ts`,
path: `/user/username/projects/myproject/pkg1/dist/index.d.ts`,
content: Utils.dedent`
export * from './types';`
},
{
path: `${ts.tscWatch.projectRoot}/pkg1/dist/types.d.ts`,
path: `/user/username/projects/myproject/pkg1/dist/types.d.ts`,
content: Utils.dedent`
export declare type A = {
id: string;
@@ -184,7 +186,7 @@ ${pluginOneAction()}`
}`
},
{
path: `${ts.tscWatch.projectRoot}/pkg1/package.json`,
path: `/user/username/projects/myproject/pkg1/package.json`,
content: JSON.stringify({
name: "@raymondfeng/pkg1",
version: "1.0.0",
@@ -193,17 +195,17 @@ ${pluginOneAction()}`
})
},
{
path: `${ts.tscWatch.projectRoot}/pkg2/dist/index.d.ts`,
path: `/user/username/projects/myproject/pkg2/dist/index.d.ts`,
content: Utils.dedent`
export * from './types';`
},
{
path: `${ts.tscWatch.projectRoot}/pkg2/dist/types.d.ts`,
path: `/user/username/projects/myproject/pkg2/dist/types.d.ts`,
content: Utils.dedent`
export {MetadataAccessor} from '@raymondfeng/pkg1';`
},
{
path: `${ts.tscWatch.projectRoot}/pkg2/package.json`,
path: `/user/username/projects/myproject/pkg2/package.json`,
content: JSON.stringify({
name: "@raymondfeng/pkg2",
version: "1.0.0",
@@ -212,18 +214,18 @@ ${pluginOneAction()}`
})
},
{
path: `${ts.tscWatch.projectRoot}/pkg3/src/index.ts`,
path: `/user/username/projects/myproject/pkg3/src/index.ts`,
content: Utils.dedent`
export * from './keys';`
},
{
path: `${ts.tscWatch.projectRoot}/pkg3/src/keys.ts`,
path: `/user/username/projects/myproject/pkg3/src/keys.ts`,
content: Utils.dedent`
import {MetadataAccessor} from "@raymondfeng/pkg2";
export const ADMIN = MetadataAccessor.create<boolean>('1');`
},
{
path: `${ts.tscWatch.projectRoot}/pkg3/tsconfig.json`,
path: `/user/username/projects/myproject/pkg3/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "dist",
@@ -237,14 +239,14 @@ ${pluginOneAction()}`
})
},
{
path: `${ts.tscWatch.projectRoot}/pkg2/node_modules/@raymondfeng/pkg1`,
symLink: `${ts.tscWatch.projectRoot}/pkg1`
path: `/user/username/projects/myproject/pkg2/node_modules/@raymondfeng/pkg1`,
symLink: `/user/username/projects/myproject/pkg1`
},
{
path: `${ts.tscWatch.projectRoot}/pkg3/node_modules/@raymondfeng/pkg2`,
symLink: `${ts.tscWatch.projectRoot}/pkg2`
path: `/user/username/projects/myproject/pkg3/node_modules/@raymondfeng/pkg2`,
symLink: `/user/username/projects/myproject/pkg2`
},
ts.tscWatch.libFile
libFile
],
changeCaseFileTestPath: str => ts.stringContains(str, "/pkg1"),
});

View File

@@ -1,12 +1,12 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { loadProjectFromFiles, verifyTsc } from "./helpers";
describe("unittests:: tsc:: forceConsistentCasingInFileNames::", () => {
ts.verifyTsc({
verifyTsc({
scenario: "forceConsistentCasingInFileNames",
subScenario: "with relative and non relative file resolutions",
commandLineArgs: ["/src/project/src/struct.d.ts", "--forceConsistentCasingInFileNames", "--explainFiles"],
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/struct.d.ts": Utils.dedent`
import * as xs1 from "fp-ts/lib/Struct";
import * as xs2 from "fp-ts/lib/struct";

View File

@@ -1,7 +1,9 @@
import * as ts from "../../_namespaces/ts";
import * as fakes from "../../_namespaces/fakes";
import * as vfs from "../../_namespaces/vfs";
import * as vpath from "../../_namespaces/vpath";
import * as Harness from "../../_namespaces/Harness";
import { libFile, TestServerHost } from "../virtualFileSystemWithWatch";
export type TscCompileSystem = fakes.System & {
writtenFiles: ts.Set<ts.Path>;
@@ -14,11 +16,11 @@ export function compilerOptionsToConfigJson(options: ts.CompilerOptions) {
return ts.optionMapToObject(ts.serializeCompilerOptions(options));
}
export const noChangeRun: ts.TestTscEdit = {
export const noChangeRun: TestTscEdit = {
subScenario: "no-change-run",
modifyFs: ts.noop
};
export const noChangeWithExportsDiscrepancyRun: ts.TestTscEdit = {
export const noChangeWithExportsDiscrepancyRun: TestTscEdit = {
...noChangeRun,
discrepancyExplanation: () => [
"Incremental build did not emit and has .ts as signature so exports has all imported modules/referenced files",
@@ -26,7 +28,6 @@ export const noChangeWithExportsDiscrepancyRun: ts.TestTscEdit = {
]
};
export const noChangeOnlyRuns = [noChangeRun];
export const noChangeWithExportsDiscrepancyOnlyRuns = [noChangeWithExportsDiscrepancyRun];
export interface TestTscCompile extends TestTscCompileLikeBase {
baselineSourceMap?: boolean;
@@ -45,7 +46,7 @@ function isAnyProgram(program: ts.Program | ts.BuilderProgram | ts.ParsedCommand
return !!(program as ts.Program | ts.BuilderProgram).getCompilerOptions;
}
export function commandLineCallbacks(
sys: TscCompileSystem | ts.tscWatch.WatchedSystem,
sys: TscCompileSystem | TestServerHost,
originalReadCall?: ts.System["readFile"],
): CommandLineCallbacks {
let programs: CommandLineProgram[] | undefined;
@@ -53,14 +54,14 @@ export function commandLineCallbacks(
return {
cb: program => {
if (isAnyProgram(program)) {
ts.baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall);
baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall);
(programs || (programs = [])).push(ts.isBuilderProgram(program) ?
[program.getProgram(), program] :
[program]
);
}
else {
ts.baselineBuildInfo(program.options, sys, originalReadCall);
baselineBuildInfo(program.options, sys, originalReadCall);
}
},
getPrograms: () => {
@@ -136,7 +137,7 @@ function makeSystemReadyForBaseline(sys: TscCompileSystem, versionToWrite?: stri
const writtenFiles = sys.writtenFiles = new ts.Set();
const originalWriteFile = sys.writeFile;
sys.writeFile = (fileName, content, writeByteOrderMark) => {
const path = ts.toPathWithSystem(sys, fileName);
const path = toPathWithSystem(sys, fileName);
// When buildinfo is same for two projects,
// it gives error and doesnt write buildinfo but because buildInfo is written for one project,
// readable baseline will be written two times for those two projects with same contents and is ok
@@ -147,9 +148,9 @@ function makeSystemReadyForBaseline(sys: TscCompileSystem, versionToWrite?: stri
}
export function createSolutionBuilderHostForBaseline(
sys: TscCompileSystem | ts.tscWatch.WatchedSystem,
sys: TscCompileSystem | TestServerHost,
versionToWrite?: string,
originalRead?: (TscCompileSystem | ts.tscWatch.WatchedSystem)["readFile"]
originalRead?: (TscCompileSystem | TestServerHost)["readFile"]
) {
if (sys instanceof fakes.System) makeSystemReadyForBaseline(sys, versionToWrite);
const { cb } = commandLineCallbacks(sys, originalRead);
@@ -198,21 +199,97 @@ export function testTscCompile(input: TestTscCompile) {
}
function additionalBaseline(sys: TscCompileSystem) {
const { baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies } = input;
if (baselinePrograms) {
const { baselineSourceMap, baselineReadFileCalls, baselinePrograms: shouldBaselinePrograms, baselineDependencies } = input;
if (shouldBaselinePrograms) {
const baseline: string[] = [];
ts.tscWatch.baselinePrograms(baseline, getPrograms!, ts.emptyArray, baselineDependencies);
baselinePrograms(baseline, getPrograms!, ts.emptyArray, baselineDependencies);
sys.write(baseline.join("\n"));
}
if (baselineReadFileCalls) {
sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `);
}
if (baselineSourceMap) ts.generateSourceMapBaselineFiles(sys);
if (baselineSourceMap) generateSourceMapBaselineFiles(sys);
actualReadFileMap = undefined;
getPrograms = undefined;
}
}
export function baselinePrograms(baseline: string[], getPrograms: () => readonly CommandLineProgram[], oldPrograms: readonly (CommandLineProgram | undefined)[], baselineDependencies: boolean | undefined) {
const programs = getPrograms();
for (let i = 0; i < programs.length; i++) {
baselineProgram(baseline, programs[i], oldPrograms[i], baselineDependencies);
}
return programs;
}
function baselineProgram(baseline: string[], [program, builderProgram]: CommandLineProgram, oldProgram: CommandLineProgram | undefined, baselineDependencies: boolean | undefined) {
if (program !== oldProgram?.[0]) {
const options = program.getCompilerOptions();
baseline.push(`Program root files: ${JSON.stringify(program.getRootFileNames())}`);
baseline.push(`Program options: ${JSON.stringify(options)}`);
baseline.push(`Program structureReused: ${(ts as any).StructureIsReused[program.structureIsReused]}`);
baseline.push("Program files::");
for (const file of program.getSourceFiles()) {
baseline.push(file.fileName);
}
}
else {
baseline.push(`Program: Same as old program`);
}
baseline.push("");
if (!builderProgram) return;
if (builderProgram !== oldProgram?.[1]) {
const state = builderProgram.getState();
const internalState = state as unknown as ts.BuilderProgramState;
if (state.semanticDiagnosticsPerFile?.size) {
baseline.push("Semantic diagnostics in builder refreshed for::");
for (const file of program.getSourceFiles()) {
if (!internalState.semanticDiagnosticsFromOldState || !internalState.semanticDiagnosticsFromOldState.has(file.resolvedPath)) {
baseline.push(file.fileName);
}
}
}
else {
baseline.push("No cached semantic diagnostics in the builder::");
}
if (internalState) {
baseline.push("");
if (internalState.hasCalledUpdateShapeSignature?.size) {
baseline.push("Shape signatures in builder refreshed for::");
internalState.hasCalledUpdateShapeSignature.forEach((path: ts.Path) => {
const info = state.fileInfos.get(path);
if (info?.version === info?.signature || !info?.signature) {
baseline.push(path + " (used version)");
}
else if (internalState.filesChangingSignature?.has(path)) {
baseline.push(path + " (computed .d.ts during emit)");
}
else {
baseline.push(path + " (computed .d.ts)");
}
});
}
else {
baseline.push("No shapes updated in the builder::");
}
}
baseline.push("");
if (!baselineDependencies) return;
baseline.push("Dependencies for::");
for (const file of builderProgram.getSourceFiles()) {
baseline.push(`${file.fileName}:`);
for (const depenedency of builderProgram.getAllDependencies(file)) {
baseline.push(` ${depenedency}`);
}
}
}
else {
baseline.push(`BuilderProgram: Same as old builder program`);
}
baseline.push("");
}
export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) {
it(`Generates files matching the baseline`, () => {
const { file, text } = sys().baseLine();
@@ -248,3 +325,698 @@ export function verifyTscCompileLike<T extends VerifyTscCompileLike>(verifier: (
export function verifyTsc(input: TestTscCompile) {
verifyTscCompileLike(testTscCompile, input);
}
export function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
if (old.indexOf(oldText) < 0) {
throw new Error(`Text "${oldText}" does not exist in file ${path}`);
}
const newContent = old.replace(oldText, newText);
fs.writeFileSync(path, newContent, "utf-8");
}
export function prependText(fs: vfs.FileSystem, path: string, additionalContent: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
fs.writeFileSync(path, `${additionalContent}${old}`, "utf-8");
}
export function appendText(fs: vfs.FileSystem, path: string, additionalContent: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
fs.writeFileSync(path, `${old}${additionalContent}`);
}
export const libContent = `${libFile.content}
interface ReadonlyArray<T> {}
declare const console: { log(msg: any): void; };`;
export const symbolLibContent = `
interface SymbolConstructor {
readonly species: symbol;
readonly toStringTag: symbol;
}
declare var Symbol: SymbolConstructor;
interface Symbol {
readonly [Symbol.toStringTag]: string;
}
`;
/**
* Load project from disk into /src folder
*/
export function loadProjectFromDisk(
root: string,
libContentToAppend?: string
): vfs.FileSystem {
const resolver = vfs.createResolver(Harness.IO);
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
files: {
["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver)
},
cwd: "/",
meta: { defaultLibLocation: "/lib" },
});
addLibAndMakeReadonly(fs, libContentToAppend);
return fs;
}
/**
* All the files must be in /src
*/
export function loadProjectFromFiles(
files: vfs.FileSet,
libContentToAppend?: string
): vfs.FileSystem {
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
files,
cwd: "/",
meta: { defaultLibLocation: "/lib" },
});
addLibAndMakeReadonly(fs, libContentToAppend);
return fs;
}
function addLibAndMakeReadonly(fs: vfs.FileSystem, libContentToAppend?: string) {
fs.mkdirSync("/lib");
fs.writeFileSync("/lib/lib.d.ts", libContentToAppend ? `${libContent}${libContentToAppend}` : libContent);
fs.makeReadonly();
}
export function generateSourceMapBaselineFiles(sys: ts.System & { writtenFiles: ts.ReadonlyCollection<ts.Path>; }) {
const mapFileNames = ts.mapDefinedIterator(sys.writtenFiles.keys(), f => f.endsWith(".map") ? f : undefined);
while (true) {
const result = mapFileNames.next();
if (result.done) break;
const mapFile = result.value;
const text = Harness.SourceMapRecorder.getSourceMapRecordWithSystem(sys, mapFile);
sys.writeFile(`${mapFile}.baseline.txt`, text);
}
}
function generateBundleFileSectionInfo(sys: ts.System, originalReadCall: ts.System["readFile"], baselineRecorder: Harness.Compiler.WriterAggregator, bundleFileInfo: ts.BundleFileInfo | undefined, outFile: string | undefined) {
if (!ts.length(bundleFileInfo && bundleFileInfo.sections) && !outFile) return; // Nothing to baseline
const content = outFile && sys.fileExists(outFile) ? originalReadCall.call(sys, outFile, "utf8")! : "";
baselineRecorder.WriteLine("======================================================================");
baselineRecorder.WriteLine(`File:: ${outFile}`);
for (const section of bundleFileInfo ? bundleFileInfo.sections : ts.emptyArray) {
baselineRecorder.WriteLine("----------------------------------------------------------------------");
writeSectionHeader(section);
if (section.kind !== ts.BundleFileSectionKind.Prepend) {
writeTextOfSection(section.pos, section.end);
}
else if (section.texts.length > 0) {
ts.Debug.assert(section.pos === ts.first(section.texts).pos);
ts.Debug.assert(section.end === ts.last(section.texts).end);
for (const text of section.texts) {
baselineRecorder.WriteLine(">>--------------------------------------------------------------------");
writeSectionHeader(text);
writeTextOfSection(text.pos, text.end);
}
}
else {
ts.Debug.assert(section.pos === section.end);
}
}
baselineRecorder.WriteLine("======================================================================");
function writeTextOfSection(pos: number, end: number) {
const textLines = content.substring(pos, end).split(/\r?\n/);
for (const line of textLines) {
baselineRecorder.WriteLine(line);
}
}
function writeSectionHeader(section: ts.BundleFileSection) {
baselineRecorder.WriteLine(`${section.kind}: (${section.pos}-${section.end})${section.data ? ":: " + section.data : ""}${section.kind === ts.BundleFileSectionKind.Prepend ? " texts:: " + section.texts.length : ""}`);
}
}
type ReadableProgramBuildInfoDiagnostic = string | [string, readonly ts.ReusableDiagnostic[]];
type ReadableBuilderFileEmit = string & { __readableBuilderFileEmit: any; };
type ReadableProgramBuilderInfoFilePendingEmit = [original: string | [string], emitKind: ReadableBuilderFileEmit];
type ReadableProgramBuildInfoEmitSignature = string | [string, ts.EmitSignature | []];
type ReadableProgramBuildInfoFileInfo<T> = Omit<ts.BuilderState.FileInfo, "impliedFormat"> & {
impliedFormat: string | undefined;
original: T | undefined;
};
type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo,
"fileIdsList" | "fileInfos" |
"referencedMap" | "exportedModulesMap" | "semanticDiagnosticsPerFile" |
"affectedFilesPendingEmit" | "changeFileSet" | "emitSignatures"
> & {
fileNamesList: readonly (readonly string[])[] | undefined;
fileInfos: ts.MapLike<ReadableProgramBuildInfoFileInfo<ts.ProgramMultiFileEmitBuildInfoFileInfo>>;
referencedMap: ts.MapLike<string[]> | undefined;
exportedModulesMap: ts.MapLike<string[]> | undefined;
semanticDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined;
affectedFilesPendingEmit: readonly ReadableProgramBuilderInfoFilePendingEmit[] | undefined;
changeFileSet: readonly string[] | undefined;
emitSignatures: readonly ReadableProgramBuildInfoEmitSignature[] | undefined;
};
type ReadableProgramBuildInfoBundlePendingEmit = [emitKind: ReadableBuilderFileEmit, original: ts.ProgramBuildInfoBundlePendingEmit];
type ReadableProgramBundleEmitBuildInfo = Omit<ts.ProgramBundleEmitBuildInfo, "fileInfos" | "pendingEmit"> & {
fileInfos: ts.MapLike<string | ReadableProgramBuildInfoFileInfo<ts.BuilderState.FileInfo>>;
pendingEmit: ReadableProgramBuildInfoBundlePendingEmit | undefined;
};
type ReadableProgramBuildInfo = ReadableProgramMultiFileEmitBuildInfo | ReadableProgramBundleEmitBuildInfo;
function isReadableProgramBundleEmitBuildInfo(info: ReadableProgramBuildInfo | undefined): info is ReadableProgramBundleEmitBuildInfo {
return !!info && !!ts.outFile(info.options || {});
}
type ReadableBuildInfo = Omit<ts.BuildInfo, "program"> & { program: ReadableProgramBuildInfo | undefined; size: number; };
function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string, buildInfo: ts.BuildInfo) {
let program: ReadableProgramBuildInfo | undefined;
let fileNamesList: string[][] | undefined;
if (buildInfo.program && ts.isProgramBundleEmitBuildInfo(buildInfo.program)) {
const fileInfos: ReadableProgramBundleEmitBuildInfo["fileInfos"] = {};
buildInfo.program?.fileInfos?.forEach((fileInfo, index) =>
fileInfos[toFileName(index + 1 as ts.ProgramBuildInfoFileId)] = ts.isString(fileInfo) ?
fileInfo :
toReadableFileInfo(fileInfo, ts.identity)
);
const pendingEmit = buildInfo.program.pendingEmit;
program = {
...buildInfo.program,
fileInfos,
pendingEmit: pendingEmit === undefined ?
undefined :
[
toReadableBuilderFileEmit(ts.toProgramEmitPending(pendingEmit, buildInfo.program.options)),
pendingEmit
],
};
}
else if (buildInfo.program) {
const fileInfos: ReadableProgramMultiFileEmitBuildInfo["fileInfos"] = {};
buildInfo.program?.fileInfos?.forEach((fileInfo, index) => fileInfos[toFileName(index + 1 as ts.ProgramBuildInfoFileId)] = toReadableFileInfo(fileInfo, ts.toBuilderStateFileInfoForMultiEmit));
fileNamesList = buildInfo.program.fileIdsList?.map(fileIdsListId => fileIdsListId.map(toFileName));
const fullEmitForOptions = buildInfo.program.affectedFilesPendingEmit ? ts.getBuilderFileEmit(buildInfo.program.options || {}) : undefined;
program = buildInfo.program && {
fileNames: buildInfo.program.fileNames,
fileNamesList,
fileInfos: buildInfo.program.fileInfos ? fileInfos : undefined!,
options: buildInfo.program.options,
referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap),
exportedModulesMap: toMapOfReferencedSet(buildInfo.program.exportedModulesMap),
semanticDiagnosticsPerFile: buildInfo.program.semanticDiagnosticsPerFile?.map(d =>
ts.isNumber(d) ?
toFileName(d) :
[toFileName(d[0]), d[1]]
),
affectedFilesPendingEmit: buildInfo.program.affectedFilesPendingEmit?.map(value => toReadableProgramBuilderInfoFilePendingEmit(value, fullEmitForOptions!)),
changeFileSet: buildInfo.program.changeFileSet?.map(toFileName),
emitSignatures: buildInfo.program.emitSignatures?.map(s =>
ts.isNumber(s) ?
toFileName(s) :
[toFileName(s[0]), s[1]]
),
latestChangedDtsFile: buildInfo.program.latestChangedDtsFile,
};
}
const version = buildInfo.version === ts.version ? fakes.version : buildInfo.version;
const result: ReadableBuildInfo = {
// Baseline fixed order for bundle
bundle: buildInfo.bundle && {
...buildInfo.bundle,
js: buildInfo.bundle.js && {
sections: buildInfo.bundle.js.sections,
hash: buildInfo.bundle.js.hash,
mapHash: buildInfo.bundle.js.mapHash,
sources: buildInfo.bundle.js.sources,
},
dts: buildInfo.bundle.dts && {
sections: buildInfo.bundle.dts.sections,
hash: buildInfo.bundle.dts.hash,
mapHash: buildInfo.bundle.dts.mapHash,
sources: buildInfo.bundle.dts.sources,
},
},
program,
version,
size: ts.getBuildInfoText({ ...buildInfo, version }).length,
};
// For now its just JSON.stringify
sys.writeFile(`${buildInfoPath}.readable.baseline.txt`, JSON.stringify(result, /*replacer*/ undefined, 2));
function toFileName(fileId: ts.ProgramBuildInfoFileId) {
return buildInfo.program!.fileNames[fileId - 1];
}
function toFileNames(fileIdsListId: ts.ProgramBuildInfoFileIdListId) {
return fileNamesList![fileIdsListId - 1];
}
function toReadableFileInfo<T>(original: T, toFileInfo: (fileInfo: T) => ts.BuilderState.FileInfo): ReadableProgramBuildInfoFileInfo<T> {
const info = toFileInfo(original);
return {
original: ts.isString(original) ? undefined : original,
...info,
impliedFormat: info.impliedFormat && ts.getNameOfCompilerOptionValue(info.impliedFormat, ts.moduleOptionDeclaration.type),
};
}
function toMapOfReferencedSet(referenceMap: ts.ProgramBuildInfoReferencedMap | undefined): ts.MapLike<string[]> | undefined {
if (!referenceMap) return undefined;
const result: ts.MapLike<string[]> = {};
for (const [fileNamesKey, fileNamesListKey] of referenceMap) {
result[toFileName(fileNamesKey)] = toFileNames(fileNamesListKey);
}
return result;
}
function toReadableProgramBuilderInfoFilePendingEmit(value: ts.ProgramBuilderInfoFilePendingEmit, fullEmitForOptions: ts.BuilderFileEmit): ReadableProgramBuilderInfoFilePendingEmit {
return [
ts.isNumber(value) ? toFileName(value) : [toFileName(value[0])],
toReadableBuilderFileEmit(ts.toBuilderFileEmit(value, fullEmitForOptions)),
];
}
function toReadableBuilderFileEmit(emit: ts.BuilderFileEmit | undefined): ReadableBuilderFileEmit {
let result = "";
if (emit) {
if (emit & ts.BuilderFileEmit.Js) addFlags("Js");
if (emit & ts.BuilderFileEmit.JsMap) addFlags("JsMap");
if (emit & ts.BuilderFileEmit.JsInlineMap) addFlags("JsInlineMap");
if (emit & ts.BuilderFileEmit.Dts) addFlags("Dts");
if (emit & ts.BuilderFileEmit.DtsMap) addFlags("DtsMap");
}
return (result || "None") as ReadableBuilderFileEmit;
function addFlags(flag: string) {
result = result ? `${result} | ${flag}` : flag;
}
}
}
export function toPathWithSystem(sys: ts.System, fileName: string): ts.Path {
return ts.toPath(fileName, sys.getCurrentDirectory(), ts.createGetCanonicalFileName(sys.useCaseSensitiveFileNames));
}
export function baselineBuildInfo(
options: ts.CompilerOptions,
sys: TscCompileSystem | TestServerHost,
originalReadCall?: ts.System["readFile"],
) {
const buildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(options);
if (!buildInfoPath || !sys.writtenFiles!.has(toPathWithSystem(sys, buildInfoPath))) return;
if (!sys.fileExists(buildInfoPath)) return;
const buildInfo = ts.getBuildInfo(buildInfoPath, (originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!);
if (!buildInfo) return sys.writeFile(`${buildInfoPath}.baseline.txt`, "Error reading valid buildinfo file");
generateBuildInfoProgramBaseline(sys, buildInfoPath, buildInfo);
if (!ts.outFile(options)) return;
const { jsFilePath, declarationFilePath } = ts.getOutputPathsForBundle(options, /*forceDts*/ false);
const bundle = buildInfo.bundle;
if (!bundle || (!ts.length(bundle.js && bundle.js.sections) && !ts.length(bundle.dts && bundle.dts.sections))) return;
// Write the baselines:
const baselineRecorder = new Harness.Compiler.WriterAggregator();
generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.js, jsFilePath);
generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.dts, declarationFilePath);
baselineRecorder.Close();
const text = baselineRecorder.lines.join("\r\n");
sys.writeFile(`${buildInfoPath}.baseline.txt`, text);
}
interface VerifyTscEditDiscrepanciesInput {
index: number;
scenario: TestTscCompile["scenario"];
subScenario: TestTscCompile["subScenario"];
baselines: string[] | undefined;
commandLineArgs: TestTscCompile["commandLineArgs"];
modifyFs: TestTscCompile["modifyFs"];
editFs: TestTscEdit["modifyFs"];
baseFs: vfs.FileSystem;
newSys: TscCompileSystem;
discrepancyExplanation: TestTscEdit["discrepancyExplanation"];
}
function verifyTscEditDiscrepancies({
index, scenario, subScenario, commandLineArgs,
discrepancyExplanation, baselines,
modifyFs, editFs, baseFs, newSys
}: VerifyTscEditDiscrepanciesInput): string[] | undefined {
const sys = testTscCompile({
scenario,
subScenario,
fs: () => baseFs.makeReadonly(),
commandLineArgs,
modifyFs: fs => {
if (modifyFs) modifyFs(fs);
editFs(fs);
},
disableUseFileVersionAsSignature: true,
});
let headerAdded = false;
for (const outputFile of ts.arrayFrom(sys.writtenFiles.keys())) {
const cleanBuildText = sys.readFile(outputFile);
const incrementalBuildText = newSys.readFile(outputFile);
if (ts.isBuildInfoFile(outputFile)) {
// Check only presence and absence and not text as we will do that for readable baseline
if (!sys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in clean build:: File:: ${outputFile}`);
if (!newSys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in incremental build:: File:: ${outputFile}`);
verifyPresenceAbsence(incrementalBuildText, cleanBuildText, `Incremental and clean tsbuildinfo file presence differs:: File:: ${outputFile}`);
}
else if (!ts.fileExtensionIs(outputFile, ".tsbuildinfo.readable.baseline.txt")) {
verifyTextEqual(incrementalBuildText, cleanBuildText, `File: ${outputFile}`);
}
else if (incrementalBuildText !== cleanBuildText) {
// Verify build info without affectedFilesPendingEmit
const { buildInfo: incrementalBuildInfo, readableBuildInfo: incrementalReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText);
const { buildInfo: cleanBuildInfo, readableBuildInfo: cleanReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText);
verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, `TsBuild info text without affectedFilesPendingEmit:: ${outputFile}::`);
// Verify file info sigantures
verifyMapLike(
incrementalReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"],
cleanReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"],
(key, incrementalFileInfo, cleanFileInfo) => {
if (incrementalFileInfo.signature !== cleanFileInfo.signature && incrementalFileInfo.signature !== incrementalFileInfo.version) {
return [
`Incremental signature is neither dts signature nor file version for File:: ${key}`,
`Incremental:: ${JSON.stringify(incrementalFileInfo, /*replacer*/ undefined, 2)}`,
`Clean:: ${JSON.stringify(cleanFileInfo, /*replacer*/ undefined, 2)}`
];
}
},
`FileInfos:: File:: ${outputFile}`
);
if (!isReadableProgramBundleEmitBuildInfo(incrementalReadableBuildInfo?.program)) {
ts.Debug.assert(!isReadableProgramBundleEmitBuildInfo(cleanReadableBuildInfo?.program));
// Verify exportedModulesMap
verifyMapLike(
incrementalReadableBuildInfo?.program?.exportedModulesMap,
cleanReadableBuildInfo?.program?.exportedModulesMap,
(key, incrementalReferenceSet, cleanReferenceSet) => {
if (!ts.arrayIsEqualTo(incrementalReferenceSet, cleanReferenceSet) && !ts.arrayIsEqualTo(incrementalReferenceSet, (incrementalReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key])) {
return [
`Incremental Reference set is neither from dts nor files reference map for File:: ${key}::`,
`Incremental:: ${JSON.stringify(incrementalReferenceSet, /*replacer*/ undefined, 2)}`,
`Clean:: ${JSON.stringify(cleanReferenceSet, /*replacer*/ undefined, 2)}`,
`IncrementalReferenceMap:: ${JSON.stringify((incrementalReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key], /*replacer*/ undefined, 2)}`,
`CleanReferenceMap:: ${JSON.stringify((cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key], /*replacer*/ undefined, 2)}`,
];
}
},
`exportedModulesMap:: File:: ${outputFile}`
);
// Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option
if (incrementalReadableBuildInfo?.program?.affectedFilesPendingEmit) {
if (cleanReadableBuildInfo?.program?.affectedFilesPendingEmit === undefined) {
addBaseline(
`Incremental build contains affectedFilesPendingEmit, clean build does not have it: ${outputFile}::`,
`Incremental buildInfoText:: ${incrementalBuildText}`,
`Clean buildInfoText:: ${cleanBuildText}`
);
}
let expectedIndex = 0;
incrementalReadableBuildInfo.program.affectedFilesPendingEmit.forEach(([actualFileOrArray]) => {
const actualFile = ts.isString(actualFileOrArray) ? actualFileOrArray : actualFileOrArray[0];
expectedIndex = ts.findIndex(
(cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).affectedFilesPendingEmit,
([expectedFileOrArray]) => actualFile === (ts.isString(expectedFileOrArray) ? expectedFileOrArray : expectedFileOrArray[0]),
expectedIndex
);
if (expectedIndex === -1) {
addBaseline(
`Incremental build contains ${actualFile} file as pending emit, clean build does not have it: ${outputFile}::`,
`Incremental buildInfoText:: ${incrementalBuildText}`,
`Clean buildInfoText:: ${cleanBuildText}`
);
}
expectedIndex++;
});
}
}
}
}
if (!headerAdded && discrepancyExplanation) addBaseline("*** Supplied discrepancy explanation but didnt file any difference");
return baselines;
function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, message: string) {
if (incrementalText !== cleanText) writeNotEqual(incrementalText, cleanText, message);
}
function verifyMapLike<T>(incremental: ts.MapLike<T> | undefined, clean: ts.MapLike<T> | undefined, verifyValue: (key: string, incrementalValue: T, cleanValue: T) => string[] | undefined, message: string) {
verifyPresenceAbsence(incremental, clean, `Incremental and clean do not match:: ${message}`);
if (!incremental || !clean) return;
const incrementalMap = new ts.Map(ts.getEntries(incremental));
const cleanMap = new ts.Map(ts.getEntries(clean));
if (incrementalMap.size !== cleanMap.size) {
addBaseline(
`Incremental and clean size of maps do not match:: ${message}`,
`Incremental: ${JSON.stringify(incremental, /*replacer*/ undefined, 2)}`,
`Clean: ${JSON.stringify(clean, /*replacer*/ undefined, 2)}`,
);
return;
}
cleanMap.forEach((cleanValue, key) => {
const incrementalValue = incrementalMap.get(key);
if (!incrementalValue) {
addBaseline(
`Incremental does not contain ${key} which is present in clean:: ${message}`,
`Incremental: ${JSON.stringify(incremental, /*replacer*/ undefined, 2)}`,
`Clean: ${JSON.stringify(clean, /*replacer*/ undefined, 2)}`,
);
}
else {
const result = verifyValue(key, incrementalMap.get(key)!, cleanValue);
if (result) addBaseline(...result);
}
});
}
function verifyPresenceAbsence<T>(actual: T | undefined, expected: T | undefined, message: string) {
if (expected === undefined) {
if (actual === undefined) return;
}
else {
if (actual !== undefined) return;
}
writeNotEqual(actual, expected, message);
}
function writeNotEqual<T>(actual: T | undefined, expected: T | undefined, message: string) {
addBaseline(
message,
"CleanBuild:",
ts.isString(expected) ? expected : JSON.stringify(expected),
"IncrementalBuild:",
ts.isString(actual) ? actual : JSON.stringify(actual),
);
}
function addBaseline(...text: string[]) {
if (!baselines || !headerAdded) {
(baselines ||= []).push(`${index}:: ${subScenario}`, ...(discrepancyExplanation?.()|| ["*** Needs explanation"]));
headerAdded = true;
}
baselines.push(...text);
}
}
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): {
buildInfo: string | undefined;
readableBuildInfo?: ReadableBuildInfo;
} {
if (!text) return { buildInfo: text };
const readableBuildInfo = JSON.parse(text) as ReadableBuildInfo;
let sanitizedFileInfos: ts.MapLike<string | Omit<ReadableProgramBuildInfoFileInfo<ts.ProgramMultiFileEmitBuildInfoFileInfo> | ReadableProgramBuildInfoFileInfo<ts.BuilderState.FileInfo>, "signature" | "original"> & { signature: undefined; original: undefined; }> | undefined;
if (readableBuildInfo.program?.fileInfos) {
sanitizedFileInfos = {};
for (const id in readableBuildInfo.program.fileInfos) {
if (ts.hasProperty(readableBuildInfo.program.fileInfos, id)) {
const info = readableBuildInfo.program.fileInfos[id];
sanitizedFileInfos[id] = ts.isString(info) ? info : { ...info, signature: undefined, original: undefined };
}
}
}
return {
buildInfo: JSON.stringify({
...readableBuildInfo,
program: readableBuildInfo.program && {
...readableBuildInfo.program,
fileNames: undefined,
fileNamesList: undefined,
fileInfos: sanitizedFileInfos,
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
options: { ...readableBuildInfo.program.options, noEmit: undefined },
exportedModulesMap: undefined,
affectedFilesPendingEmit: undefined,
latestChangedDtsFile: readableBuildInfo.program.latestChangedDtsFile ? "FakeFileName" : undefined,
},
size: undefined, // Size doesnt need to be equal
}, /*replacer*/ undefined, 2),
readableBuildInfo,
};
}
export interface TestTscEdit {
modifyFs: (fs: vfs.FileSystem) => void;
subScenario: string;
commandLineArgs?: readonly string[];
/** An array of lines to be printed in order when a discrepancy is detected */
discrepancyExplanation?: () => readonly string[];
}
export interface VerifyTscWithEditsInput extends TestTscCompile {
edits: TestTscEdit[];
}
/**
* Verify non watch tsc invokcation after each edit
*/
export function verifyTscWithEdits({
subScenario, fs, scenario, commandLineArgs,
baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms,
edits
}: VerifyTscWithEditsInput) {
describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario} serializedEdits`, () => {
let sys: TscCompileSystem;
let baseFs: vfs.FileSystem;
let editsSys: TscCompileSystem[];
before(() => {
ts.Debug.assert(!!edits.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`);
baseFs = fs().makeReadonly();
sys = testTscCompile({
scenario,
subScenario,
fs: () => baseFs,
commandLineArgs,
modifyFs,
baselineSourceMap,
baselineReadFileCalls,
baselinePrograms
});
edits.forEach((
{ modifyFs, subScenario: editScenario, commandLineArgs: editCommandLineArgs },
index
) => {
(editsSys || (editsSys = [])).push(testTscCompile({
scenario,
subScenario: editScenario || subScenario,
diffWithInitial: true,
fs: () => index === 0 ? sys.vfs : editsSys[index - 1].vfs,
commandLineArgs: editCommandLineArgs || commandLineArgs,
modifyFs,
baselineSourceMap,
baselineReadFileCalls,
baselinePrograms
}));
});
});
after(() => {
baseFs = undefined!;
sys = undefined!;
editsSys = undefined!;
});
verifyTscBaseline(() => ({
baseLine: () => {
const { file, text } = sys.baseLine();
const texts: string[] = [text];
editsSys.forEach((sys, index) => {
const incrementalScenario = edits[index];
texts.push("");
texts.push(`Change:: ${incrementalScenario.subScenario}`);
texts.push(sys.baseLine().text);
});
return { file, text: texts.join("\r\n") };
}
}));
it("tsc invocation after edit and clean build correctness", () => {
let baselines: string[] | undefined;
for (let index = 0; index < edits.length; index++) {
baselines = verifyTscEditDiscrepancies({
index,
scenario,
subScenario: edits[index].subScenario,
baselines,
baseFs,
newSys: editsSys[index],
commandLineArgs: edits[index].commandLineArgs || commandLineArgs,
discrepancyExplanation: edits[index].discrepancyExplanation,
editFs: fs => {
for (let i = 0; i <= index; i++) {
edits[i].modifyFs(fs);
}
},
modifyFs
});
}
Harness.Baseline.runBaseline(
`${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}-discrepancies.js`,
baselines ? baselines.join("\r\n") : null // eslint-disable-line no-null/no-null
);
});
});
}
export function enableStrict(fs: vfs.FileSystem, path: string) {
replaceText(fs, path, `"strict": false`, `"strict": true`);
}
export function addTestPrologue(fs: vfs.FileSystem, path: string, prologue: string) {
prependText(fs, path, `${prologue}
`);
}
export function addShebang(fs: vfs.FileSystem, project: string, file: string) {
prependText(fs, `src/${project}/${file}.ts`, `#!someshebang ${project} ${file}
`);
}
export function restContent(project: string, file: string) {
return `function for${project}${file}Rest() {
const { b, ...rest } = { a: 10, b: 30, yy: 30 };
}`;
}
function nonrestContent(project: string, file: string) {
return `function for${project}${file}Rest() { }`;
}
export function addRest(fs: vfs.FileSystem, project: string, file: string) {
appendText(fs, `src/${project}/${file}.ts`, restContent(project, file));
}
export function removeRest(fs: vfs.FileSystem, project: string, file: string) {
replaceText(fs, `src/${project}/${file}.ts`, restContent(project, file), nonrestContent(project, file));
}
export function addStubFoo(fs: vfs.FileSystem, project: string, file: string) {
appendText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file));
}
export function changeStubToRest(fs: vfs.FileSystem, project: string, file: string) {
replaceText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file), restContent(project, file));
}
export function addSpread(fs: vfs.FileSystem, project: string, file: string) {
const path = `src/${project}/${file}.ts`;
const content = fs.readFileSync(path, "utf8");
fs.writeFileSync(path, `${content}
function ${project}${file}Spread(...b: number[]) { }
const ${project}${file}_ar = [20, 30];
${project}${file}Spread(10, ...${project}${file}_ar);`);
replaceText(fs, `src/${project}/tsconfig.json`, `"strict": false,`, `"strict": false,
"downlevelIteration": true,`);
}
export function getTripleSlashRef(project: string) {
return `/src/${project}/tripleRef.d.ts`;
}
export function addTripleSlashRef(fs: vfs.FileSystem, project: string, file: string) {
fs.writeFileSync(getTripleSlashRef(project), `declare class ${project}${file} { }`);
prependText(fs, `src/${project}/${file}.ts`, `///<reference path="./tripleRef.d.ts"/>
const ${file}Const = new ${project}${file}();
`);
}

View File

@@ -1,12 +1,13 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import * as vfs from "../../_namespaces/vfs";
import { appendText, compilerOptionsToConfigJson, libContent, loadProjectFromDisk, loadProjectFromFiles, noChangeOnlyRuns, noChangeRun, noChangeWithExportsDiscrepancyRun, prependText, replaceText, TestTscEdit, verifyTsc, verifyTscWithEdits } from "./helpers";
describe("unittests:: tsc:: incremental::", () => {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "when passing filename for buildinfo on commandline",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
{
@@ -20,13 +21,13 @@ describe("unittests:: tsc:: incremental::", () => {
}`,
}),
commandLineArgs: ["--incremental", "--p", "src/project", "--tsBuildInfoFile", "src/project/.tsbuildinfo", "--explainFiles"],
edits: ts.noChangeOnlyRuns
edits: noChangeOnlyRuns
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "when passing rootDir from commandline",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
{
@@ -37,31 +38,31 @@ describe("unittests:: tsc:: incremental::", () => {
}`,
}),
commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"],
edits: ts.noChangeOnlyRuns
edits: noChangeOnlyRuns
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "with only dts files",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.d.ts": "export const x = 10;",
"/src/project/src/another.d.ts": "export const y = 10;",
"/src/project/tsconfig.json": "{}",
}),
commandLineArgs: ["--incremental", "--p", "src/project"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "incremental-declaration-doesnt-change",
modifyFs: fs => ts.appendText(fs, "/src/project/src/main.d.ts", "export const xy = 100;")
modifyFs: fs => appendText(fs, "/src/project/src/main.d.ts", "export const xy = 100;")
}
]
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "when passing rootDir is in the tsconfig",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
{
@@ -73,13 +74,13 @@ describe("unittests:: tsc:: incremental::", () => {
}`,
}),
commandLineArgs: ["--p", "src/project"],
edits: ts.noChangeOnlyRuns
edits: noChangeOnlyRuns
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "tsbuildinfo has error",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": "{}",
"/src/project/tsconfig.tsbuildinfo": "Some random string",
@@ -87,33 +88,33 @@ describe("unittests:: tsc:: incremental::", () => {
commandLineArgs: ["--p", "src/project", "-i"],
edits: [{
subScenario: "tsbuildinfo written has error",
modifyFs: fs => ts.prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"),
modifyFs: fs => prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"),
}]
});
describe("with noEmitOnError", () => {
let projFs: vfs.FileSystem;
before(() => {
projFs = ts.loadProjectFromDisk("tests/projects/noEmitOnError");
projFs = loadProjectFromDisk("tests/projects/noEmitOnError");
});
after(() => {
projFs = undefined!;
});
function verifyNoEmitOnError(subScenario: string, fixModifyFs: ts.TestTscEdit["modifyFs"], modifyFs?: ts.TestTscEdit["modifyFs"]) {
ts.verifyTscWithEdits({
function verifyNoEmitOnError(subScenario: string, fixModifyFs: TestTscEdit["modifyFs"], modifyFs?: TestTscEdit["modifyFs"]) {
verifyTscWithEdits({
scenario: "incremental",
subScenario,
fs: () => projFs,
commandLineArgs: ["--incremental", "-p", "src"],
modifyFs,
edits: [
ts.noChangeWithExportsDiscrepancyRun,
noChangeWithExportsDiscrepancyRun,
{
subScenario: "incremental-declaration-doesnt-change",
modifyFs: fixModifyFs
},
ts.noChangeRun,
noChangeRun,
],
baselinePrograms: true
});
@@ -142,25 +143,25 @@ const a: string = 10;`, "utf-8"),
function verifyNoEmitChanges(compilerOptions: ts.CompilerOptions) {
const discrepancyExplanation = () => [
...ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation!(),
...noChangeWithExportsDiscrepancyRun.discrepancyExplanation!(),
"Clean build will not have latestChangedDtsFile as there was no emit and emitSignatures as undefined for files",
"Incremental will store the past latestChangedDtsFile and emitSignatures",
];
const discrepancyIfNoDtsEmit = ts.getEmitDeclarations(compilerOptions) ?
undefined :
ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation;
const noChangeRunWithNoEmit: ts.TestTscEdit = {
...ts.noChangeRun,
noChangeWithExportsDiscrepancyRun.discrepancyExplanation;
const noChangeRunWithNoEmit: TestTscEdit = {
...noChangeRun,
subScenario: "No Change run with noEmit",
commandLineArgs: ["--p", "src/project", "--noEmit"],
discrepancyExplanation: compilerOptions.composite ?
discrepancyExplanation :
!compilerOptions.declaration ?
ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation :
noChangeWithExportsDiscrepancyRun.discrepancyExplanation :
undefined,
};
const noChangeRunWithEmit: ts.TestTscEdit = {
...ts.noChangeRun,
const noChangeRunWithEmit: TestTscEdit = {
...noChangeRun,
subScenario: "No Change run with emit",
commandLineArgs: ["--p", "src/project"],
discrepancyExplanation: discrepancyIfNoDtsEmit,
@@ -172,7 +173,7 @@ const a: string = 10;`, "utf-8"),
}
}
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: `noEmit changes${optionsString}`,
commandLineArgs: ["--p", "src/project"],
@@ -183,16 +184,16 @@ const a: string = 10;`, "utf-8"),
{
subScenario: "Introduce error but still noEmit",
commandLineArgs: ["--p", "src/project", "--noEmit"],
modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"),
modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"),
discrepancyExplanation: compilerOptions.composite ?
discrepancyExplanation :
compilerOptions.declaration ?
ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation :
noChangeWithExportsDiscrepancyRun.discrepancyExplanation :
undefined,
},
{
subScenario: "Fix error and emit",
modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"),
modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"),
discrepancyExplanation: discrepancyIfNoDtsEmit,
},
noChangeRunWithEmit,
@@ -201,7 +202,7 @@ const a: string = 10;`, "utf-8"),
noChangeRunWithEmit,
{
subScenario: "Introduce error and emit",
modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"),
modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"),
discrepancyExplanation: discrepancyIfNoDtsEmit,
},
noChangeRunWithEmit,
@@ -211,10 +212,10 @@ const a: string = 10;`, "utf-8"),
{
subScenario: "Fix error and no emit",
commandLineArgs: ["--p", "src/project", "--noEmit"],
modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"),
modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"),
discrepancyExplanation: compilerOptions.composite ?
discrepancyExplanation :
ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
},
noChangeRunWithEmit,
noChangeRunWithNoEmit,
@@ -223,7 +224,7 @@ const a: string = 10;`, "utf-8"),
],
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: `noEmit changes with initial noEmit${optionsString}`,
commandLineArgs: ["--p", "src/project", "--noEmit"],
@@ -233,21 +234,21 @@ const a: string = 10;`, "utf-8"),
{
subScenario: "Introduce error with emit",
commandLineArgs: ["--p", "src/project"],
modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"),
modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"),
},
{
subScenario: "Fix error and no emit",
modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"),
modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"),
discrepancyExplanation: compilerOptions.composite ?
discrepancyExplanation :
ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
},
noChangeRunWithEmit,
],
});
function fs() {
return ts.loadProjectFromFiles({
return loadProjectFromFiles({
"/src/project/src/class.ts": Utils.dedent`
export class classC {
prop = 1;
@@ -275,10 +276,10 @@ const a: string = 10;`, "utf-8"),
}
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: `when global file is added, the signatures are updated`,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": Utils.dedent`
/// <reference path="./filePresent.ts"/>
/// <reference path="./fileNotFound.ts"/>
@@ -297,22 +298,22 @@ const a: string = 10;`, "utf-8"),
}),
commandLineArgs: ["--p", "src/project"],
edits: [
ts.noChangeRun,
noChangeRun,
{
subScenario: "Modify main file",
modifyFs: fs => ts.appendText(fs, `/src/project/src/main.ts`, `something();`),
modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`),
},
{
subScenario: "Modify main file again",
modifyFs: fs => ts.appendText(fs, `/src/project/src/main.ts`, `something();`),
modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`),
},
{
subScenario: "Add new file and update main file",
modifyFs: fs => {
fs.writeFileSync(`/src/project/src/newFile.ts`, "function foo() { return 20; }");
ts.prependText(fs, `/src/project/src/main.ts`, `/// <reference path="./newFile.ts"/>
prependText(fs, `/src/project/src/main.ts`, `/// <reference path="./newFile.ts"/>
`);
ts.appendText(fs, `/src/project/src/main.ts`, `foo();`);
appendText(fs, `/src/project/src/main.ts`, `foo();`);
},
},
{
@@ -321,7 +322,7 @@ const a: string = 10;`, "utf-8"),
},
{
subScenario: "Modify main file",
modifyFs: fs => ts.appendText(fs, `/src/project/src/main.ts`, `something();`),
modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`),
},
],
baselinePrograms: true,
@@ -343,10 +344,10 @@ declare global {
}`;
}
ts.verifyTsc({
verifyTsc({
scenario: "react-jsx-emit-mode",
subScenario: "with no backing types found doesn't crash",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result
"/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition
"/src/project/src/index.tsx": `export const App = () => <div propA={true}></div>;`,
@@ -355,10 +356,10 @@ declare global {
commandLineArgs: ["--p", "src/project"]
});
ts.verifyTsc({
verifyTsc({
scenario: "react-jsx-emit-mode",
subScenario: "with no backing types found doesn't crash under --strict",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result
"/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition
"/src/project/src/index.tsx": `export const App = () => <div propA={true}></div>;`,
@@ -368,11 +369,11 @@ declare global {
});
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "when new file is added to the referenced project",
commandLineArgs: ["-i", "-p", `src/projects/project2`],
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/projects/project1/tsconfig.json": JSON.stringify({
compilerOptions: {
module: "none",
@@ -428,11 +429,11 @@ declare global {
]
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "when project has strict true",
commandLineArgs: ["-noEmit", "-p", `src/project`],
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({
compilerOptions: {
incremental: true,
@@ -441,15 +442,15 @@ declare global {
}),
"/src/project/class1.ts": `export class class1 {}`,
}),
edits: ts.noChangeOnlyRuns,
edits: noChangeOnlyRuns,
baselinePrograms: true
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "serializing error chains",
commandLineArgs: ["-p", `src/project`],
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({
compilerOptions: {
incremental: true,
@@ -473,13 +474,13 @@ declare global {
<div />
</Component>)`
}, `\ninterface ReadonlyArray<T> { readonly length: number }`),
edits: ts.noChangeOnlyRuns,
edits: noChangeOnlyRuns,
});
ts.verifyTsc({
verifyTsc({
scenario: "incremental",
subScenario: "ts file with no-default-lib that augments the global scope",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": Utils.dedent`
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
@@ -503,15 +504,15 @@ declare global {
}),
commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"],
modifyFs: (fs) => {
fs.writeFileSync("/lib/lib.esnext.d.ts", ts.libContent);
fs.writeFileSync("/lib/lib.esnext.d.ts", libContent);
}
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "change to type that gets used as global through export in another file",
commandLineArgs: ["-p", `src/project`],
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { composite: true }, }),
"/src/project/class1.ts": `const a: MagicNumber = 1;
console.log(a);`,
@@ -524,11 +525,11 @@ console.log(a);`,
}],
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "change to type that gets used as global through export in another file through indirect import",
commandLineArgs: ["-p", `src/project`],
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { composite: true }, }),
"/src/project/class1.ts": `const a: MagicNumber = 1;
console.log(a);`,
@@ -543,11 +544,11 @@ console.log(a);`,
});
function verifyModifierChange(declaration: boolean) {
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: `change to modifier of class expression field${declaration ? " with declaration emit enabled" : ""}`,
commandLineArgs: ["-p", "src/project", "--incremental"],
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { declaration } }),
"/src/project/main.ts": Utils.dedent`
import MessageablePerson from './MessageablePerson.js';
@@ -564,18 +565,18 @@ console.log(a);`,
type MessageablePerson = InstanceType<ReturnType<typeof wrapper>>;
export default MessageablePerson;`,
}),
modifyFs: fs => ts.appendText(fs, "/lib/lib.d.ts", Utils.dedent`
modifyFs: fs => appendText(fs, "/lib/lib.d.ts", Utils.dedent`
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;`
),
edits: [
{
subScenario: "modify public to protected",
modifyFs: fs => ts.replaceText(fs, "/src/project/MessageablePerson.ts", "public", "protected"),
modifyFs: fs => replaceText(fs, "/src/project/MessageablePerson.ts", "public", "protected"),
},
{
subScenario: "modify protected to public",
modifyFs: fs => ts.replaceText(fs, "/src/project/MessageablePerson.ts", "protected", "public"),
modifyFs: fs => replaceText(fs, "/src/project/MessageablePerson.ts", "protected", "public"),
},
],
});
@@ -583,10 +584,10 @@ console.log(a);`,
verifyModifierChange(/*declaration*/ false);
verifyModifierChange(/*declaration*/ true);
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: `when declarationMap changes`,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({
compilerOptions: {
noEmitOnError: true,
@@ -601,7 +602,7 @@ console.log(a);`,
edits: [
{
subScenario: "error and enable declarationMap",
modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "x", "x: 20"),
modifyFs: fs => replaceText(fs, "/src/project/a.ts", "x", "x: 20"),
commandLineArgs: ["--p", "/src/project", "--declarationMap"],
discrepancyExplanation: () => [
`Clean build does not emit any file so will have emitSignatures with all files since they are not emitted`,
@@ -611,16 +612,16 @@ console.log(a);`,
},
{
subScenario: "fix error declarationMap",
modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "x: 20", "x"),
modifyFs: fs => replaceText(fs, "/src/project/a.ts", "x: 20", "x"),
commandLineArgs: ["--p", "/src/project", "--declarationMap"],
},
]
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: `when declarationMap changes with outFile`,
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({
compilerOptions: {
noEmitOnError: true,
@@ -636,29 +637,29 @@ console.log(a);`,
edits: [
{
subScenario: "error and enable declarationMap",
modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "x", "x: 20"),
modifyFs: fs => replaceText(fs, "/src/project/a.ts", "x", "x: 20"),
commandLineArgs: ["--p", "/src/project", "--declarationMap"],
},
{
subScenario: "fix error declarationMap",
modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "x: 20", "x"),
modifyFs: fs => replaceText(fs, "/src/project/a.ts", "x: 20", "x"),
commandLineArgs: ["--p", "/src/project", "--declarationMap"],
},
]
});
describe("different options::", () => {
function withOptionChange(subScenario: string, ...options: readonly string[]): ts.TestTscEdit {
function withOptionChange(subScenario: string, ...options: readonly string[]): TestTscEdit {
return {
subScenario,
modifyFs: ts.noop,
commandLineArgs: ["--p", "/src/project", ...options],
};
}
function noChangeWithSubscenario(subScenario: string): ts.TestTscEdit {
return { ...ts.noChangeRun, subScenario };
function noChangeWithSubscenario(subScenario: string): TestTscEdit {
return { ...noChangeRun, subScenario };
}
function withOptionChangeAndDiscrepancyExplanation(subScenario: string, option: string): ts.TestTscEdit {
function withOptionChangeAndDiscrepancyExplanation(subScenario: string, option: string): TestTscEdit {
return {
...withOptionChange(subScenario, option),
discrepancyExplanation: () => [
@@ -667,7 +668,7 @@ console.log(a);`,
]
};
}
function withEmitDeclarationOnlyChangeAndDiscrepancyExplanation(subScenario: string): ts.TestTscEdit {
function withEmitDeclarationOnlyChangeAndDiscrepancyExplanation(subScenario: string): TestTscEdit {
const edit = withOptionChangeAndDiscrepancyExplanation(subScenario, "--emitDeclarationOnly");
const discrepancyExplanation = edit.discrepancyExplanation!;
edit.discrepancyExplanation = () => [
@@ -677,22 +678,22 @@ console.log(a);`,
];
return edit;
}
function withOptionChangeAndExportExplanation(subScenario: string, ...options: readonly string[]): ts.TestTscEdit {
function withOptionChangeAndExportExplanation(subScenario: string, ...options: readonly string[]): TestTscEdit {
return {
...withOptionChange(subScenario, ...options),
discrepancyExplanation: ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
discrepancyExplanation: noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
};
}
function nochangeWithIncrementalDeclarationFromBeforeExplaination(): ts.TestTscEdit {
function nochangeWithIncrementalDeclarationFromBeforeExplaination(): TestTscEdit {
return {
...ts.noChangeRun,
...noChangeRun,
discrepancyExplanation: () => [
`Clean build tsbuildinfo will have compilerOptions {}`,
`Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`,
],
};
}
function nochangeWithIncrementalOutDeclarationFromBeforeExplaination(): ts.TestTscEdit {
function nochangeWithIncrementalOutDeclarationFromBeforeExplaination(): TestTscEdit {
const edit = nochangeWithIncrementalDeclarationFromBeforeExplaination();
const discrepancyExplanation = edit.discrepancyExplanation!;
edit.discrepancyExplanation = () => [
@@ -702,22 +703,22 @@ console.log(a);`,
];
return edit;
}
function localChange(): ts.TestTscEdit {
function localChange(): TestTscEdit {
return {
subScenario: "local change",
modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "Local = 1", "Local = 10"),
modifyFs: fs => replaceText(fs, "/src/project/a.ts", "Local = 1", "Local = 10"),
};
}
function fs(options: ts.CompilerOptions) {
return ts.loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({ compilerOptions: ts.compilerOptionsToConfigJson(options) }),
return loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({ compilerOptions: compilerOptionsToConfigJson(options) }),
"/src/project/a.ts": `export const a = 10;const aLocal = 10;`,
"/src/project/b.ts": `export const b = 10;const bLocal = 10;`,
"/src/project/c.ts": `import { a } from "./a";export const c = a;`,
"/src/project/d.ts": `import { b } from "./b";export const d = b;`,
});
}
function enableDeclarationMap(): ts.TestTscEdit {
function enableDeclarationMap(): TestTscEdit {
return {
subScenario: "declarationMap enabling",
modifyFs: fs => {
@@ -727,7 +728,7 @@ console.log(a);`,
},
};
}
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "different options",
fs: () => fs({ composite: true }),
@@ -736,11 +737,11 @@ console.log(a);`,
withOptionChange("with sourceMap", "--sourceMap"),
noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"),
withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"),
ts.noChangeRun,
noChangeRun,
withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"),
noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"),
withOptionChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything", "--emitDeclarationOnly"),
ts.noChangeRun,
noChangeRun,
localChange(),
withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"),
withOptionChange("with inlineSourceMap", "--inlineSourceMap"),
@@ -750,7 +751,7 @@ console.log(a);`,
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "different options with outFile",
fs: () => fs({ composite: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }),
@@ -759,11 +760,11 @@ console.log(a);`,
withOptionChange("with sourceMap", "--sourceMap"),
noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"),
withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"),
ts.noChangeRun,
noChangeRun,
withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"),
noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"),
withEmitDeclarationOnlyChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything"),
ts.noChangeRun,
noChangeRun,
localChange(),
withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"),
withOptionChange("with inlineSourceMap", "--inlineSourceMap"),
@@ -773,7 +774,7 @@ console.log(a);`,
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "different options with incremental",
fs: () => fs({ incremental: true }),
@@ -795,7 +796,7 @@ console.log(a);`,
],
baselinePrograms: true,
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "incremental",
subScenario: "different options with incremental with outFile",
fs: () => fs({ incremental: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }),

View File

@@ -1,43 +1,43 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { loadProjectFromFiles, noChangeRun, verifyTsc, verifyTscWithEdits } from "./helpers";
describe("unittests:: tsc:: listFilesOnly::", () => {
ts.verifyTsc({
verifyTsc({
scenario: "listFilesOnly",
subScenario: "combined with watch",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/test.ts": Utils.dedent`
export const x = 1;`,
}),
commandLineArgs: ["/src/test.ts", "--watch", "--listFilesOnly"]
});
ts.verifyTsc({
verifyTsc({
scenario: "listFilesOnly",
subScenario: "loose file",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/test.ts": Utils.dedent`
export const x = 1;`,
}),
commandLineArgs: ["/src/test.ts", "--listFilesOnly"]
});
ts.verifyTscWithEdits({
verifyTscWithEdits({
scenario: "listFilesOnly",
subScenario: "combined with incremental",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/test.ts": `export const x = 1;`,
"/src/tsconfig.json": "{}"
}),
commandLineArgs: ["-p", "/src", "--incremental", "--listFilesOnly"],
edits: [
{
...ts.noChangeRun,
...noChangeRun,
commandLineArgs: ["-p", "/src", "--incremental"],
},
ts.noChangeRun,
noChangeRun,
{
...ts.noChangeRun,
...noChangeRun,
commandLineArgs: ["-p", "/src", "--incremental"],
}
]

View File

@@ -1,10 +1,10 @@
import * as ts from "../../_namespaces/ts";
import { loadProjectFromFiles, verifyTsc } from "./helpers";
describe("unittests:: tsc:: projectReferences::", () => {
ts.verifyTsc({
verifyTsc({
scenario: "projectReferences",
subScenario: "when project contains invalid project reference",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": JSON.stringify({
compilerOptions: {
@@ -19,10 +19,10 @@ describe("unittests:: tsc:: projectReferences::", () => {
commandLineArgs: ["--p", "src/project"],
});
ts.verifyTsc({
verifyTsc({
scenario: "projectReferences",
subScenario: "when project references composite project with noEmit",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/utils/index.ts": "export const x = 10;",
"/src/utils/tsconfig.json": JSON.stringify({
compilerOptions: {

View File

@@ -1,10 +1,10 @@
import * as ts from "../../_namespaces/ts";
import { loadProjectFromFiles, verifyTsc } from "./helpers";
describe("unittests:: tsc:: redirect::", () => {
ts.verifyTsc({
verifyTsc({
scenario: "redirect",
subScenario: "when redirecting ts file",
fs: () => ts.loadProjectFromFiles({
fs: () => loadProjectFromFiles({
"/src/project/tsconfig.json": JSON.stringify({
compilerOptions: {
outDir: "out"

View File

@@ -1,25 +1,25 @@
import * as ts from "../../_namespaces/ts";
import { loadProjectFromFiles, verifyTsc } from "./helpers";
describe("unittests:: tsc:: runWithoutArgs::", () => {
ts.verifyTsc({
verifyTsc({
scenario: "runWithoutArgs",
subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped",
fs: () => ts.loadProjectFromFiles({}),
fs: () => loadProjectFromFiles({}),
commandLineArgs: [],
environmentVariables: { TS_TEST_TERMINAL_WIDTH: "120" }
});
ts.verifyTsc({
verifyTsc({
scenario: "runWithoutArgs",
subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped when host can't provide terminal width",
fs: () => ts.loadProjectFromFiles({}),
fs: () => loadProjectFromFiles({}),
commandLineArgs: [],
});
ts.verifyTsc({
verifyTsc({
scenario: "runWithoutArgs",
subScenario: "does not add color when NO_COLOR is set",
fs: () => ts.loadProjectFromFiles({}),
fs: () => loadProjectFromFiles({}),
commandLineArgs: [],
environmentVariables: { NO_COLOR: "true" }
});

View File

@@ -1,24 +1,26 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, libFile } from "../virtualFileSystemWithWatch";
import { createBaseline, createWatchCompilerHostOfConfigFileForBaseline, runWatchBaseline, TscWatchCompileChange, verifyTscWatch } from "./helpers";
describe("unittests:: tsc-watch:: console clearing", () => {
const scenario = "consoleClearing";
const file: ts.tscWatch.File = {
const file: File = {
path: "/f.ts",
content: ""
};
const makeChangeToFile: ts.tscWatch.TscWatchCompileChange[] = [{
const makeChangeToFile: TscWatchCompileChange[] = [{
caption: "Comment added to file f",
change: sys => sys.modifyFile(file.path, "//"),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}];
function checkConsoleClearingUsingCommandLineOptions(subScenario: string, commandLineOptions?: string[]) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario,
commandLineArgs: ["--w", file.path, ...commandLineOptions || ts.emptyArray],
sys: () => ts.tscWatch.createWatchedSystem([file, ts.tscWatch.libFile]),
sys: () => createWatchedSystem([file, libFile]),
changes: makeChangeToFile,
});
}
@@ -32,20 +34,20 @@ describe("unittests:: tsc-watch:: console clearing", () => {
const compilerOptions: ts.CompilerOptions = {
preserveWatchOutput: true
};
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/tsconfig.json",
content: JSON.stringify({ compilerOptions })
};
const files = [file, configFile, ts.tscWatch.libFile];
const files = [file, configFile, libFile];
it("using createWatchOfConfigFile ", () => {
const baseline = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem(files));
const watch = ts.createWatchProgram(ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
const baseline = createBaseline(createWatchedSystem(files));
const watch = ts.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
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario,
subScenario: "when preserveWatchOutput is true in config file/createWatchOfConfigFile",
commandLineArgs: ["--w", "-p", configFile.path],
@@ -55,11 +57,11 @@ describe("unittests:: tsc-watch:: console clearing", () => {
watchOrSolution: watch
});
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "when preserveWatchOutput is true in config file/when createWatchProgram is invoked with configFileParseResult on WatchCompilerHostOfConfigFile",
commandLineArgs: ["--w", "-p", configFile.path],
sys: () => ts.tscWatch.createWatchedSystem(files),
sys: () => createWatchedSystem(files),
changes: makeChangeToFile,
});
});

View File

@@ -1,28 +1,30 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, libFile, TestServerHost } from "../virtualFileSystemWithWatch";
import { TscWatchCompileChange, verifyTscWatch } from "./helpers";
const scenario = "emit";
describe("unittests:: tsc-watch:: emit with outFile or out setting", () => {
function verifyOutAndOutFileSetting(subScenario: string, out?: string, outFile?: string) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `emit with outFile or out setting/${subScenario}`,
commandLineArgs: ["--w", "-p", "/a/tsconfig.json"],
sys: () => ts.tscWatch.createWatchedSystem({
sys: () => createWatchedSystem({
"/a/a.ts": "let x = 1",
"/a/b.ts": "let y = 1",
"/a/tsconfig.json": JSON.stringify({ compilerOptions: { out, outFile } }),
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
[libFile.path]: libFile.content,
}),
changes: [
{
caption: "Make change in the file",
change: sys => sys.writeFile("/a/a.ts", "let x = 11"),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks
timeouts: sys => sys.runQueuedTimeoutCallbacks()
},
{
caption: "Make change in the file again",
change: sys => sys.writeFile("/a/a.ts", "let xy = 11"),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks
timeouts: sys => sys.runQueuedTimeoutCallbacks()
}
]
});
@@ -32,28 +34,28 @@ describe("unittests:: tsc-watch:: emit with outFile or out setting", () => {
verifyOutAndOutFileSetting("config has outFile", /*out*/ undefined, "/a/out.js");
function verifyFilesEmittedOnce(subScenario: string, useOutFile: boolean) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `emit with outFile or out setting/${subScenario}`,
commandLineArgs: ["--w", "-p", "/a/b/project/tsconfig.json"],
sys: () => {
const file1: ts.tscWatch.File = {
const file1: File = {
path: "/a/b/output/AnotherDependency/file1.d.ts",
content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }"
};
const file2: ts.tscWatch.File = {
const file2: File = {
path: "/a/b/dependencies/file2.d.ts",
content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }"
};
const file3: ts.tscWatch.File = {
const file3: File = {
path: "/a/b/project/src/main.ts",
content: "namespace Main { export function fooBar() {} }"
};
const file4: ts.tscWatch.File = {
const file4: File = {
path: "/a/b/project/src/main2.ts",
content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }"
};
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/a/b/project/tsconfig.json",
content: JSON.stringify({
compilerOptions: useOutFile ?
@@ -62,7 +64,7 @@ describe("unittests:: tsc-watch:: emit with outFile or out setting", () => {
files: [file1.path, file2.path, file3.path, file4.path]
})
};
return ts.tscWatch.createWatchedSystem([file1, file2, file3, file4, ts.tscWatch.libFile, configFile]);
return createWatchedSystem([file1, file2, file3, file4, libFile, configFile]);
},
changes: ts.emptyArray
});
@@ -83,10 +85,10 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
/** custom config file options */
configObj?: any;
/** Additional files and folders to add */
getAdditionalFileOrFolder?: () => ts.tscWatch.File[];
getAdditionalFileOrFolder?: () => File[];
/** initial list of files to emit if not the default list */
firstReloadFileList?: string[];
changes: ts.tscWatch.TscWatchCompileChange[]
changes: TscWatchCompileChange[]
}
function verifyTscWatchEmit({
subScenario,
@@ -95,42 +97,42 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
firstReloadFileList,
changes
}: VerifyTscWatchEmit) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `emit for configured projects/${subScenario}`,
commandLineArgs: ["--w", "-p", configFilePath],
sys: () => {
const moduleFile1: ts.tscWatch.File = {
const moduleFile1: File = {
path: moduleFile1Path,
content: "export function Foo() { };",
};
const file1Consumer1: ts.tscWatch.File = {
const file1Consumer1: File = {
path: file1Consumer1Path,
content: `import {Foo} from "./moduleFile1"; export var y = 10;`,
};
const file1Consumer2: ts.tscWatch.File = {
const file1Consumer2: File = {
path: file1Consumer2Path,
content: `import {Foo} from "./moduleFile1"; let z = 10;`,
};
const moduleFile2: ts.tscWatch.File = {
const moduleFile2: File = {
path: moduleFile2Path,
content: `export var Foo4 = 10;`,
};
const globalFile3: ts.tscWatch.File = {
const globalFile3: File = {
path: globalFilePath,
content: `interface GlobalFoo { age: number }`
};
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: configFilePath,
content: JSON.stringify(configObj || {})
};
const additionalFiles = getAdditionalFileOrFolder?.() || ts.emptyArray;
const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.tscWatch.libFile, ...additionalFiles];
return ts.tscWatch.createWatchedSystem(firstReloadFileList ?
const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles];
return createWatchedSystem(firstReloadFileList ?
ts.map(firstReloadFileList, fileName => ts.find(files, file => file.path === fileName)!) :
files
);
@@ -139,13 +141,13 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
});
}
function modifyModuleFile1Shape(sys: ts.tscWatch.WatchedSystem) {
function modifyModuleFile1Shape(sys: TestServerHost) {
sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { };`);
}
const changeModuleFile1Shape: ts.tscWatch.TscWatchCompileChange = {
const changeModuleFile1Shape: TscWatchCompileChange = {
caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { };`",
change: modifyModuleFile1Shape,
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
};
verifyTscWatchEmit({
@@ -155,7 +157,7 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
{
caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`",
change: sys => sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { console.log('hi'); };`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -166,18 +168,18 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
{
caption: "Change file1Consumer1 content to `export let y = Foo();`",
change: sys => sys.writeFile(file1Consumer1Path, `export let y = Foo();`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
changeModuleFile1Shape,
{
caption: "Add the import statements back to file1Consumer1",
change: sys => sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`",
change: sys => sys.writeFile(moduleFile1Path, `export let y = Foo();`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Multiple file edits in one go",
@@ -187,7 +189,7 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`);
modifyModuleFile1Shape(sys);
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -201,7 +203,7 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
modifyModuleFile1Shape(sys);
sys.deleteFile(file1Consumer2Path);
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -215,7 +217,7 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
sys.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`);
modifyModuleFile1Shape(sys);
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -228,7 +230,7 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
{
caption: "change file1 internal, and verify only file1 is affected",
change: sys => sys.appendFile(moduleFile1Path, "var T1: number;"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -239,7 +241,7 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
{
caption: "change shape of global file",
change: sys => sys.appendFile(globalFilePath, "var T2: string;"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -270,7 +272,7 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
{
caption: "change file1Consumer1",
change: sys => sys.appendFile(file1Consumer1Path, "export var T: number;"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
changeModuleFile1Shape,
{
@@ -279,7 +281,7 @@ describe("unittests:: tsc-watch:: emit for configured projects", () => {
sys.appendFile(file1Consumer1Path, "export var T2: number;");
sys.writeFile(moduleFile1Path, `export var T2: number;export function Foo() { };`);
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -298,12 +300,12 @@ export var t1 = 10;`
export var t2 = 10;`
}
],
firstReloadFileList: [ts.tscWatch.libFile.path, "/a/b/file1.ts", "/a/b/file2.ts", configFilePath],
firstReloadFileList: [libFile.path, "/a/b/file1.ts", "/a/b/file2.ts", configFilePath],
changes: [
{
caption: "change file1",
change: sys => sys.appendFile("/a/b/file1.ts", "export var t3 = 10;"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -315,12 +317,12 @@ export var t2 = 10;`
content: `/// <reference path="./moduleFile1.ts" />
export var x = Foo();`
}],
firstReloadFileList: [ts.tscWatch.libFile.path, "/a/b/referenceFile1.ts", moduleFile1Path, configFilePath],
firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", moduleFile1Path, configFilePath],
changes: [
{
caption: "delete moduleFile1",
change: sys => sys.deleteFile(moduleFile1Path),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -332,17 +334,17 @@ export var x = Foo();`
content: `/// <reference path="./moduleFile2.ts" />
export var x = Foo();`
}],
firstReloadFileList: [ts.tscWatch.libFile.path, "/a/b/referenceFile1.ts", configFilePath],
firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", configFilePath],
changes: [
{
caption: "edit refereceFile1",
change: sys => sys.appendFile("/a/b/referenceFile1.ts", "export var yy = Foo();"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "create moduleFile2",
change: sys => sys.writeFile(moduleFile2Path, "export var Foo4 = 10;"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
@@ -350,17 +352,17 @@ export var x = Foo();`
describe("unittests:: tsc-watch:: emit file content", () => {
function verifyNewLine(subScenario: string, newLine: string) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `emit file content/${subScenario}`,
commandLineArgs: ["--w", "/a/app.ts"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
[
{
path: "/a/app.ts",
content: ["var x = 1;", "var y = 2;"].join(newLine)
},
ts.tscWatch.libFile
libFile
],
{ newLine }
),
@@ -368,7 +370,7 @@ describe("unittests:: tsc-watch:: emit file content", () => {
{
caption: "Append a line",
change: sys => sys.appendFile("/a/app.ts", newLine + "var z = 3;"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
],
});
@@ -376,7 +378,7 @@ describe("unittests:: tsc-watch:: emit file content", () => {
verifyNewLine("handles new lines lineFeed", "\n");
verifyNewLine("handles new lines carriageReturn lineFeed", "\r\n");
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "emit file content/should emit specified file",
commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"],
@@ -400,62 +402,62 @@ describe("unittests:: tsc-watch:: emit file content", () => {
path: "/a/b/tsconfig.json",
content: "{}"
};
return ts.tscWatch.createWatchedSystem([file1, file2, file3, configFile, ts.tscWatch.libFile]);
return createWatchedSystem([file1, file2, file3, configFile, libFile]);
},
changes: [
{
caption: "Append content to f1",
change: sys => sys.appendFile("/a/b/f1.ts", "export function foo2() { return 2; }"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Again Append content to f1",
change: sys => sys.appendFile("/a/b/f1.ts", "export function fooN() { return 2; }"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
],
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "emit file content/elides const enums correctly in incremental compilation",
commandLineArgs: ["-w", "/user/someone/projects/myproject/file3.ts"],
sys: () => {
const currentDirectory = "/user/someone/projects/myproject";
const file1: ts.tscWatch.File = {
const file1: File = {
path: `${currentDirectory}/file1.ts`,
content: "export const enum E1 { V = 1 }"
};
const file2: ts.tscWatch.File = {
const file2: File = {
path: `${currentDirectory}/file2.ts`,
content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }`
};
const file3: ts.tscWatch.File = {
const file3: File = {
path: `${currentDirectory}/file3.ts`,
content: `import { E2 } from "./file2"; const v: E2 = E2.V;`
};
return ts.tscWatch.createWatchedSystem([file1, file2, file3, ts.tscWatch.libFile]);
return createWatchedSystem([file1, file2, file3, libFile]);
},
changes: [
{
caption: "Append content to file3",
change: sys => sys.appendFile("/user/someone/projects/myproject/file3.ts", "function foo2() { return 2; }"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
],
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "emit file content/file is deleted and created as part of change",
commandLineArgs: ["-w"],
sys: () => {
const projectLocation = "/home/username/project";
const file: ts.tscWatch.File = {
const file: File = {
path: `${projectLocation}/app/file.ts`,
content: "var a = 10;"
};
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: `${projectLocation}/tsconfig.json`,
content: JSON.stringify({
include: [
@@ -463,26 +465,26 @@ describe("unittests:: tsc-watch:: emit file content", () => {
]
})
};
const files = [file, configFile, ts.tscWatch.libFile];
return ts.tscWatch.createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true });
const files = [file, configFile, libFile];
return createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true });
},
changes: [
{
caption: "file is deleted and then created to modify content",
change: sys => sys.appendFile("/home/username/project/app/file.ts", "\nvar b = 10;", { invokeFileDeleteCreateAsPartInsteadOfChange: true }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}
]
});
});
describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "when module emit is specified as node/when instead of filechanged recursive directory watcher is invoked",
commandLineArgs: ["--w", "--p", "/a/rootFolder/project/tsconfig.json"],
sys: () => {
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/a/rootFolder/project/tsconfig.json",
content: JSON.stringify({
compilerOptions: {
@@ -495,15 +497,15 @@ describe("unittests:: tsc-watch:: emit with when module emit is specified as nod
],
})
};
const file1: ts.tscWatch.File = {
const file1: File = {
path: "/a/rootFolder/project/Scripts/TypeScript.ts",
content: "var z = 10;"
};
const file2: ts.tscWatch.File = {
const file2: File = {
path: "/a/rootFolder/project/Scripts/Javascript.js",
content: "var zz = 10;"
};
return ts.tscWatch.createWatchedSystem([configFile, file1, file2, ts.tscWatch.libFile]);
return createWatchedSystem([configFile, file1, file2, libFile]);
},
changes: [
{
@@ -513,7 +515,7 @@ describe("unittests:: tsc-watch:: emit with when module emit is specified as nod
"var zz30 = 100;",
{ invokeDirectoryWatcherInsteadOfFileChanged: true },
),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
],
});

View File

@@ -1,15 +1,17 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, getTsBuildProjectFile, libFile } from "../virtualFileSystemWithWatch";
import { libContent } from "../tsc/helpers";
import { TscWatchCompileChange, verifyTscWatch } from "./helpers";
describe("unittests:: tsc-watch:: Emit times and Error updates in builder after program changes", () => {
const config: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: `{}`
};
interface VerifyEmitAndErrorUpdates {
subScenario: string
files: () => ts.tscWatch.File[];
files: () => File[];
currentDirectory?: string;
changes: ts.tscWatch.TscWatchCompileChange[];
changes: TscWatchCompileChange[];
}
function verifyEmitAndErrorUpdates({
subScenario,
@@ -17,73 +19,73 @@ describe("unittests:: tsc-watch:: Emit times and Error updates in builder after
currentDirectory,
changes,
}: VerifyEmitAndErrorUpdates) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "emitAndErrorUpdates",
subScenario: `default/${subScenario}`,
commandLineArgs: ["--w"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
files(),
{ currentDirectory: currentDirectory || ts.tscWatch.projectRoot }
{ currentDirectory: currentDirectory || "/user/username/projects/myproject" }
),
changes,
baselineIncremental: true
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "emitAndErrorUpdates",
subScenario: `defaultAndD/${subScenario}`,
commandLineArgs: ["--w", "--d"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
files(),
{ currentDirectory: currentDirectory || ts.tscWatch.projectRoot }
{ currentDirectory: currentDirectory || "/user/username/projects/myproject" }
),
changes,
baselineIncremental: true
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "emitAndErrorUpdates",
subScenario: `isolatedModules/${subScenario}`,
commandLineArgs: ["--w", "--isolatedModules"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
files(),
{ currentDirectory: currentDirectory || ts.tscWatch.projectRoot }
{ currentDirectory: currentDirectory || "/user/username/projects/myproject" }
),
changes,
baselineIncremental: true
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "emitAndErrorUpdates",
subScenario: `isolatedModulesAndD/${subScenario}`,
commandLineArgs: ["--w", "--isolatedModules", "--d"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
files(),
{ currentDirectory: currentDirectory || ts.tscWatch.projectRoot }
{ currentDirectory: currentDirectory || "/user/username/projects/myproject" }
),
changes,
baselineIncremental: true
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "emitAndErrorUpdates",
subScenario: `assumeChangesOnlyAffectDirectDependencies/${subScenario}`,
commandLineArgs: ["--w", "--assumeChangesOnlyAffectDirectDependencies"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
files(),
{ currentDirectory: currentDirectory || ts.tscWatch.projectRoot }
{ currentDirectory: currentDirectory || "/user/username/projects/myproject" }
),
changes,
baselineIncremental: true
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "emitAndErrorUpdates",
subScenario: `assumeChangesOnlyAffectDirectDependenciesAndD/${subScenario}`,
commandLineArgs: ["--w", "--assumeChangesOnlyAffectDirectDependencies", "--d"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
files(),
{ currentDirectory: currentDirectory || ts.tscWatch.projectRoot }
{ currentDirectory: currentDirectory || "/user/username/projects/myproject" }
),
changes,
baselineIncremental: true
@@ -91,48 +93,48 @@ describe("unittests:: tsc-watch:: Emit times and Error updates in builder after
}
describe("deep import changes", () => {
const aFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/a.ts`,
const aFile: File = {
path: `/user/username/projects/myproject/a.ts`,
content: `import {B} from './b';
declare var console: any;
let b = new B();
console.log(b.c.d);`
};
function verifyDeepImportChange(subScenario: string, bFile: ts.tscWatch.File, cFile: ts.tscWatch.File) {
function verifyDeepImportChange(subScenario: string, bFile: File, cFile: File) {
verifyEmitAndErrorUpdates({
subScenario: `deepImportChanges/${subScenario}`,
files: () => [aFile, bFile, cFile, config, ts.tscWatch.libFile],
files: () => [aFile, bFile, cFile, config, libFile],
changes: [
{
caption: "Rename property d to d2 of class C to initialize signatures",
change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "Rename property d2 to d of class C to revert back to original text",
change: sys => sys.writeFile(cFile.path, cFile.content.replace("d2", "d")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "Rename property d to d2 of class C",
change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
],
});
}
describe("updates errors when deep import file changes", () => {
const bFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/b.ts`,
const bFile: File = {
path: `/user/username/projects/myproject/b.ts`,
content: `import {C} from './c';
export class B
{
c = new C();
}`
};
const cFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/c.ts`,
const cFile: File = {
path: `/user/username/projects/myproject/c.ts`,
content: `export class C
{
d = 1;
@@ -145,16 +147,16 @@ export class B
);
});
describe("updates errors when deep import through declaration file changes", () => {
const bFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/b.d.ts`,
const bFile: File = {
path: `/user/username/projects/myproject/b.d.ts`,
content: `import {C} from './c';
export class B
{
c: C;
}`
};
const cFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/c.d.ts`,
const cFile: File = {
path: `/user/username/projects/myproject/c.d.ts`,
content: `export class C
{
d: number;
@@ -169,8 +171,8 @@ export class B
});
describe("updates errors in file not exporting a deep multilevel import that changes", () => {
const aFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/a.ts`,
const aFile: File = {
path: `/user/username/projects/myproject/a.ts`,
content: `export interface Point {
name: string;
c: Coords;
@@ -180,14 +182,14 @@ export interface Coords {
y: number;
}`
};
const bFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/b.ts`,
const bFile: File = {
path: `/user/username/projects/myproject/b.ts`,
content: `import { Point } from "./a";
export interface PointWrapper extends Point {
}`
};
const cFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/c.ts`,
const cFile: File = {
path: `/user/username/projects/myproject/c.ts`,
content: `import { PointWrapper } from "./b";
export function getPoint(): PointWrapper {
return {
@@ -199,47 +201,47 @@ export function getPoint(): PointWrapper {
}
};`
};
const dFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/d.ts`,
const dFile: File = {
path: `/user/username/projects/myproject/d.ts`,
content: `import { getPoint } from "./c";
getPoint().c.x;`
};
const eFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/e.ts`,
const eFile: File = {
path: `/user/username/projects/myproject/e.ts`,
content: `import "./d";`
};
verifyEmitAndErrorUpdates({
subScenario: "file not exporting a deep multilevel import that changes",
files: () => [aFile, bFile, cFile, dFile, eFile, config, ts.tscWatch.libFile],
files: () => [aFile, bFile, cFile, dFile, eFile, config, libFile],
changes: [
{
caption: "Rename property x2 to x of interface Coords to initialize signatures",
change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "Rename property x to x2 of interface Coords to revert back to original text",
change: sys => sys.writeFile(aFile.path, aFile.content.replace("x: number", "x2: number")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "Rename property x2 to x of interface Coords",
change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
]
});
});
describe("updates errors when file transitively exported file changes", () => {
const config: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
files: ["app.ts"],
compilerOptions: { baseUrl: "." }
})
};
const app: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/app.ts`,
const app: File = {
path: `/user/username/projects/myproject/app.ts`,
content: `import { Data } from "lib2/public";
export class App {
public constructor() {
@@ -247,12 +249,12 @@ export class App {
}
}`
};
const lib2Public: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/lib2/public.ts`,
const lib2Public: File = {
path: `/user/username/projects/myproject/lib2/public.ts`,
content: `export * from "./data";`
};
const lib2Data: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/lib2/data.ts`,
const lib2Data: File = {
path: `/user/username/projects/myproject/lib2/data.ts`,
content: `import { ITest } from "lib1/public";
export class Data {
public test() {
@@ -263,40 +265,40 @@ export class Data {
}
}`
};
const lib1Public: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/lib1/public.ts`,
const lib1Public: File = {
path: `/user/username/projects/myproject/lib1/public.ts`,
content: `export * from "./tools/public";`
};
const lib1ToolsPublic: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/lib1/tools/public.ts`,
const lib1ToolsPublic: File = {
path: `/user/username/projects/myproject/lib1/tools/public.ts`,
content: `export * from "./tools.interface";`
};
const lib1ToolsInterface: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/lib1/tools/tools.interface.ts`,
const lib1ToolsInterface: File = {
path: `/user/username/projects/myproject/lib1/tools/tools.interface.ts`,
content: `export interface ITest {
title: string;
}`
};
function verifyTransitiveExports(subScenario: string, files: readonly ts.tscWatch.File[]) {
function verifyTransitiveExports(subScenario: string, files: readonly File[]) {
verifyEmitAndErrorUpdates({
subScenario: `transitive exports/${subScenario}`,
files: () => [lib1ToolsInterface, lib1ToolsPublic, app, lib2Public, lib1Public, ...files, config, ts.tscWatch.libFile],
files: () => [lib1ToolsInterface, lib1ToolsPublic, app, lib2Public, lib1Public, ...files, config, libFile],
changes: [
{
caption: "Rename property title to title2 of interface ITest to initialize signatures",
change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "Rename property title2 to title of interface ITest to revert back to original text",
change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title2", "title")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "Rename property title to title2 of interface ITest",
change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
]
});
@@ -308,8 +310,8 @@ export class Data {
);
});
describe("when there are circular import and exports", () => {
const lib2Data: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/lib2/data.ts`,
const lib2Data: File = {
path: `/user/username/projects/myproject/lib2/data.ts`,
content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2";
export class Data {
public dat?: Data2; public test() {
@@ -320,8 +322,8 @@ export class Data {
}
}`
};
const lib2Data2: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/lib2/data2.ts`,
const lib2Data2: File = {
path: `/user/username/projects/myproject/lib2/data2.ts`,
content: `import { Data } from "./data";
export class Data2 {
public dat?: Data;
@@ -335,25 +337,25 @@ export class Data2 {
});
describe("with noEmitOnError", () => {
function change(caption: string, content: string): ts.tscWatch.TscWatchCompileChange {
function change(caption: string, content: string): TscWatchCompileChange {
return {
caption,
change: sys => sys.writeFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content),
change: sys => sys.writeFile(`/user/username/projects/noEmitOnError/src/main.ts`, content),
// build project
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
};
}
const noChange: ts.tscWatch.TscWatchCompileChange = {
const noChange: TscWatchCompileChange = {
caption: "No change",
change: sys => sys.writeFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!),
change: sys => sys.writeFile(`/user/username/projects/noEmitOnError/src/main.ts`, sys.readFile(`/user/username/projects/noEmitOnError/src/main.ts`)!),
// build project
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
};
verifyEmitAndErrorUpdates({
subScenario: "with noEmitOnError",
currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError`,
currentDirectory: `/user/username/projects/noEmitOnError`,
files: () => ["shared/types/db.ts", "src/main.ts", "src/other.ts", "tsconfig.json"]
.map(f => ts.TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)).concat({ path: ts.tscWatch.libFile.path, content: ts.libContent }),
.map(f => getTsBuildProjectFile("noEmitOnError", f)).concat({ path: libFile.path, content: libContent }),
changes: [
noChange,
change("Fix Syntax error", `import { A } from "../shared/types/db";

View File

@@ -1,28 +1,30 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { createWatchedSystem, File, libFile, SymLink } from "../virtualFileSystemWithWatch";
import { TscWatchCompileChange, verifyTscWatch } from "./helpers";
describe("unittests:: tsc-watch:: forceConsistentCasingInFileNames", () => {
const loggerFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/logger.ts`,
const loggerFile: File = {
path: `/user/username/projects/myproject/logger.ts`,
content: `export class logger { }`
};
const anotherFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/another.ts`,
const anotherFile: File = {
path: `/user/username/projects/myproject/another.ts`,
content: `import { logger } from "./logger"; new logger();`
};
const tsconfig: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { forceConsistentCasingInFileNames: true }
})
};
function verifyConsistentFileNames({ subScenario, changes }: { subScenario: string; changes: ts.tscWatch.TscWatchCompileChange[]; }) {
ts.tscWatch.verifyTscWatch({
function verifyConsistentFileNames({ subScenario, changes }: { subScenario: string; changes: TscWatchCompileChange[]; }) {
verifyTscWatch({
scenario: "forceConsistentCasingInFileNames",
subScenario,
commandLineArgs: ["--w", "--p", tsconfig.path],
sys: () => ts.tscWatch.createWatchedSystem([loggerFile, anotherFile, tsconfig, ts.tscWatch.libFile]),
sys: () => createWatchedSystem([loggerFile, anotherFile, tsconfig, libFile]),
changes
});
}
@@ -33,7 +35,7 @@ describe("unittests:: tsc-watch:: forceConsistentCasingInFileNames", () => {
{
caption: "Change module name from logger to Logger",
change: sys => sys.writeFile(anotherFile.path, anotherFile.content.replace("./logger", "./Logger")),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
]
});
@@ -43,53 +45,53 @@ describe("unittests:: tsc-watch:: forceConsistentCasingInFileNames", () => {
changes: [
{
caption: "Change name of file from logger to Logger",
change: sys => sys.renameFile(loggerFile.path, `${ts.tscWatch.projectRoot}/Logger.ts`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
change: sys => sys.renameFile(loggerFile.path, `/user/username/projects/myproject/Logger.ts`),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "forceConsistentCasingInFileNames",
subScenario: "when relative information file location changes",
commandLineArgs: ["--w", "--p", ".", "--explainFiles"],
sys: () => {
const moduleA: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/moduleA.ts`,
const moduleA: File = {
path: `/user/username/projects/myproject/moduleA.ts`,
content: `import a = require("./ModuleC")`
};
const moduleB: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/moduleB.ts`,
const moduleB: File = {
path: `/user/username/projects/myproject/moduleB.ts`,
content: `import a = require("./moduleC")`
};
const moduleC: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/moduleC.ts`,
const moduleC: File = {
path: `/user/username/projects/myproject/moduleC.ts`,
content: `export const x = 10;`
};
const tsconfig: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })
};
return ts.tscWatch.createWatchedSystem([moduleA, moduleB, moduleC, ts.tscWatch.libFile, tsconfig], { currentDirectory: ts.tscWatch.projectRoot });
return createWatchedSystem([moduleA, moduleB, moduleC, libFile, tsconfig], { currentDirectory: "/user/username/projects/myproject" });
},
changes: [
{
caption: "Prepend a line to moduleA",
change: sys => sys.prependFile(`${ts.tscWatch.projectRoot}/moduleA.ts`, `// some comment
change: sys => sys.prependFile(`/user/username/projects/myproject/moduleA.ts`, `// some comment
`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
],
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "forceConsistentCasingInFileNames",
subScenario: "jsxImportSource option changed",
commandLineArgs: ["--w", "--p", ".", "--explainFiles"],
sys: () => ts.tscWatch.createWatchedSystem([
ts.tscWatch.libFile,
sys: () => createWatchedSystem([
libFile,
{
path: `${ts.tscWatch.projectRoot}/node_modules/react/Jsx-runtime/index.d.ts`,
path: `/user/username/projects/myproject/node_modules/react/Jsx-runtime/index.d.ts`,
content: `export namespace JSX {
interface Element {}
interface IntrinsicElements {
@@ -104,38 +106,38 @@ export const Fragment: unique symbol;
`,
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/react/package.json`,
path: `/user/username/projects/myproject/node_modules/react/package.json`,
content: JSON.stringify({ name: "react", version: "0.0.1" })
},
{
path: `${ts.tscWatch.projectRoot}/index.tsx`,
path: `/user/username/projects/myproject/index.tsx`,
content: `export const App = () => <div propA={true}></div>;`
},
{
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { jsx: "react-jsx", jsxImportSource: "react", forceConsistentCasingInFileNames: true },
files: ["node_modules/react/Jsx-Runtime/index.d.ts", "index.tsx"]
})
}
], { currentDirectory: ts.tscWatch.projectRoot }),
], { currentDirectory: "/user/username/projects/myproject" }),
changes: ts.emptyArray,
});
function verifyWindowsStyleRoot(subScenario: string, windowsStyleRoot: string, projectRootRelative: string) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "forceConsistentCasingInFileNames",
subScenario,
commandLineArgs: ["--w", "--p", `${windowsStyleRoot}/${projectRootRelative}`, "--explainFiles"],
sys: () => {
const moduleA: ts.tscWatch.File = {
const moduleA: File = {
path: `${windowsStyleRoot}/${projectRootRelative}/a.ts`,
content: `
export const a = 1;
export const b = 2;
`
};
const moduleB: ts.tscWatch.File = {
const moduleB: File = {
path: `${windowsStyleRoot}/${projectRootRelative}/b.ts`,
content: `
import { a } from "${windowsStyleRoot.toLocaleUpperCase()}/${projectRootRelative}/a"
@@ -144,18 +146,18 @@ import { b } from "${windowsStyleRoot.toLocaleLowerCase()}/${projectRootRelative
a;b;
`
};
const tsconfig: ts.tscWatch.File = {
const tsconfig: File = {
path: `${windowsStyleRoot}/${projectRootRelative}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })
};
return ts.tscWatch.createWatchedSystem([moduleA, moduleB, ts.tscWatch.libFile, tsconfig], { windowsStyleRoot, useCaseSensitiveFileNames: false });
return createWatchedSystem([moduleA, moduleB, libFile, tsconfig], { windowsStyleRoot, useCaseSensitiveFileNames: false });
},
changes: [
{
caption: "Prepend a line to moduleA",
change: sys => sys.prependFile(`${windowsStyleRoot}/${projectRootRelative}/a.ts`, `// some comment
`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
],
});
@@ -165,12 +167,12 @@ a;b;
verifyWindowsStyleRoot("when Windows-style drive root is uppercase", "C:/", "project");
function verifyFileSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "forceConsistentCasingInFileNames",
subScenario,
commandLineArgs: ["--w", "--p", ".", "--explainFiles"],
sys: () => {
const moduleA: ts.tscWatch.File = {
const moduleA: File = {
path: diskPath,
content: `
@@ -178,12 +180,12 @@ export const a = 1;
export const b = 2;
`
};
const symlinkA: ts.tscWatch.SymLink = {
path: `${ts.tscWatch.projectRoot}/link.ts`,
const symlinkA: SymLink = {
path: `/user/username/projects/myproject/link.ts`,
symLink: targetPath,
};
const moduleB: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/b.ts`,
const moduleB: File = {
path: `/user/username/projects/myproject/b.ts`,
content: `
import { a } from "${importedPath}";
import { b } from "./link";
@@ -191,36 +193,36 @@ import { b } from "./link";
a;b;
`
};
const tsconfig: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })
};
return ts.tscWatch.createWatchedSystem([moduleA, symlinkA, moduleB, ts.tscWatch.libFile, tsconfig], { currentDirectory: ts.tscWatch.projectRoot });
return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: "/user/username/projects/myproject" });
},
changes: [
{
caption: "Prepend a line to moduleA",
change: sys => sys.prependFile(diskPath, `// some comment
`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
],
});
}
verifyFileSymlink("when both file symlink target and import match disk", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/XY.ts`, `./XY`);
verifyFileSymlink("when file symlink target matches disk but import does not", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/Xy.ts`, `./XY`);
verifyFileSymlink("when import matches disk but file symlink target does not", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/XY.ts`, `./Xy`);
verifyFileSymlink("when import and file symlink target agree but do not match disk", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/Xy.ts`, `./Xy`);
verifyFileSymlink("when import, file symlink target, and disk are all different", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/Xy.ts`, `./yX`);
verifyFileSymlink("when both file symlink target and import match disk", `/user/username/projects/myproject/XY.ts`, `/user/username/projects/myproject/XY.ts`, `./XY`);
verifyFileSymlink("when file symlink target matches disk but import does not", `/user/username/projects/myproject/XY.ts`, `/user/username/projects/myproject/Xy.ts`, `./XY`);
verifyFileSymlink("when import matches disk but file symlink target does not", `/user/username/projects/myproject/XY.ts`, `/user/username/projects/myproject/XY.ts`, `./Xy`);
verifyFileSymlink("when import and file symlink target agree but do not match disk", `/user/username/projects/myproject/XY.ts`, `/user/username/projects/myproject/Xy.ts`, `./Xy`);
verifyFileSymlink("when import, file symlink target, and disk are all different", `/user/username/projects/myproject/XY.ts`, `/user/username/projects/myproject/Xy.ts`, `./yX`);
function verifyDirSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "forceConsistentCasingInFileNames",
subScenario,
commandLineArgs: ["--w", "--p", ".", "--explainFiles"],
sys: () => {
const moduleA: ts.tscWatch.File = {
const moduleA: File = {
path: `${diskPath}/a.ts`,
content: `
@@ -228,12 +230,12 @@ export const a = 1;
export const b = 2;
`
};
const symlinkA: ts.tscWatch.SymLink = {
path: `${ts.tscWatch.projectRoot}/link`,
const symlinkA: SymLink = {
path: `/user/username/projects/myproject/link`,
symLink: targetPath,
};
const moduleB: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/b.ts`,
const moduleB: File = {
path: `/user/username/projects/myproject/b.ts`,
content: `
import { a } from "${importedPath}/a";
import { b } from "./link/a";
@@ -241,35 +243,35 @@ import { b } from "./link/a";
a;b;
`
};
const tsconfig: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
// Use outFile because otherwise the real and linked files will have the same output path
content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true, outFile: "out.js", module: "system" } })
};
return ts.tscWatch.createWatchedSystem([moduleA, symlinkA, moduleB, ts.tscWatch.libFile, tsconfig], { currentDirectory: ts.tscWatch.projectRoot });
return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: "/user/username/projects/myproject" });
},
changes: [
{
caption: "Prepend a line to moduleA",
change: sys => sys.prependFile(`${diskPath}/a.ts`, `// some comment
`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
],
});
}
verifyDirSymlink("when both directory symlink target and import match disk", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/XY`, `./XY`);
verifyDirSymlink("when directory symlink target matches disk but import does not", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/Xy`, `./XY`);
verifyDirSymlink("when import matches disk but directory symlink target does not", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/XY`, `./Xy`);
verifyDirSymlink("when import and directory symlink target agree but do not match disk", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/Xy`, `./Xy`);
verifyDirSymlink("when import, directory symlink target, and disk are all different", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/Xy`, `./yX`);
verifyDirSymlink("when both directory symlink target and import match disk", `/user/username/projects/myproject/XY`, `/user/username/projects/myproject/XY`, `./XY`);
verifyDirSymlink("when directory symlink target matches disk but import does not", `/user/username/projects/myproject/XY`, `/user/username/projects/myproject/Xy`, `./XY`);
verifyDirSymlink("when import matches disk but directory symlink target does not", `/user/username/projects/myproject/XY`, `/user/username/projects/myproject/XY`, `./Xy`);
verifyDirSymlink("when import and directory symlink target agree but do not match disk", `/user/username/projects/myproject/XY`, `/user/username/projects/myproject/Xy`, `./Xy`);
verifyDirSymlink("when import, directory symlink target, and disk are all different", `/user/username/projects/myproject/XY`, `/user/username/projects/myproject/Xy`, `./yX`);
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "forceConsistentCasingInFileNames",
subScenario: "with nodeNext resolution",
commandLineArgs: ["--w", "--explainFiles"],
sys: () => ts.tscWatch.createWatchedSystem({
sys: () => createWatchedSystem({
"/Users/name/projects/web/src/bin.ts": `import { foo } from "yargs";`,
"/Users/name/projects/web/node_modules/@types/yargs/index.d.ts": "export function foo(): void;",
"/Users/name/projects/web/node_modules/@types/yargs/index.d.mts": "export function foo(): void;",
@@ -292,16 +294,16 @@ a;b;
traceResolution: true,
}
}),
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
[libFile.path]: libFile.content,
}, { currentDirectory: "/Users/name/projects/web" }),
changes: ts.emptyArray,
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "forceConsistentCasingInFileNames",
subScenario: "self name package reference",
commandLineArgs: ["-w", "--explainFiles"],
sys: () => ts.tscWatch.createWatchedSystem({
sys: () => createWatchedSystem({
"/Users/name/projects/web/package.json": JSON.stringify({
name: "@this/package",
type: "module",
@@ -324,17 +326,17 @@ a;b;
traceResolution: true,
}
}),
"/a/lib/lib.esnext.full.d.ts": ts.tscWatch.libFile.content,
"/a/lib/lib.esnext.full.d.ts": libFile.content,
}, { currentDirectory: "/Users/name/projects/web" }),
changes: ts.emptyArray,
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "forceConsistentCasingInFileNames",
subScenario: "package json is looked up for file",
commandLineArgs: ["-w", "--explainFiles"],
sys: () => ts.tscWatch.createWatchedSystem({
sys: () => createWatchedSystem({
"/Users/name/projects/lib-boilerplate/package.json": JSON.stringify({
name: "lib-boilerplate",
version: "0.0.2",
@@ -355,7 +357,7 @@ a;b;
traceResolution: true,
}
}),
"/a/lib/lib.es2021.full.d.ts": ts.tscWatch.libFile.content,
"/a/lib/lib.es2021.full.d.ts": libFile.content,
}, { currentDirectory: "/Users/name/projects/lib-boilerplate" }),
changes: ts.emptyArray,
});

View File

@@ -1,17 +1,8 @@
import * as ts from "../../_namespaces/ts";
import * as fakes from "../../_namespaces/fakes";
import * as Harness from "../../_namespaces/Harness";
export const projects = `/user/username/projects`;
export const projectRoot = `${projects}/myproject`;
export import WatchedSystem = ts.TestFSWithWatch.TestServerHost;
export type File = ts.TestFSWithWatch.File;
export type SymLink = ts.TestFSWithWatch.SymLink;
export import libFile = ts.TestFSWithWatch.libFile;
export import createWatchedSystem = ts.TestFSWithWatch.createWatchedSystem;
export import checkArray = ts.TestFSWithWatch.checkArray;
export import checkOutputContains = ts.TestFSWithWatch.checkOutputContains;
export import checkOutputDoesNotContain = ts.TestFSWithWatch.checkOutputDoesNotContain;
import { patchHostForBuildInfoReadWrite } from "../../_namespaces/fakes";
import { Baseline } from "../../_namespaces/Harness";
import { changeToHostTrackingWrittenFiles, createWatchedSystem, File, FileOrFolderOrSymLink, FileOrFolderOrSymLinkMap, TestServerHost, TestServerHostCreationParameters, TestServerHostTrackingWrittenFiles } from "../virtualFileSystemWithWatch";
import { baselinePrograms, commandLineCallbacks, CommandLineCallbacks, CommandLineProgram, createSolutionBuilderHostForBaseline, generateSourceMapBaselineFiles } from "../tsc/helpers";
export const commonFile1: File = {
path: "/a/b/commonFile1.ts",
@@ -22,89 +13,13 @@ export const commonFile2: File = {
content: "let y = 1"
};
export function checkProgramActualFiles(program: ts.Program, expectedFiles: readonly string[]) {
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
}
export function getDiagnosticMessageChain(message: ts.DiagnosticMessage, args?: (string | number)[], next?: ts.DiagnosticMessageChain[]): ts.DiagnosticMessageChain {
let text = ts.getLocaleSpecificMessage(message);
if (args?.length) {
text = ts.formatStringFromArgs(text, args);
}
return {
messageText: text,
category: message.category,
code: message.code,
next
};
}
function isDiagnosticMessageChain(message: ts.DiagnosticMessage | ts.DiagnosticMessageChain): message is ts.DiagnosticMessageChain {
return !!(message as ts.DiagnosticMessageChain).messageText;
}
export function getDiagnosticOfFileFrom(file: ts.SourceFile | undefined, start: number | undefined, length: number | undefined, message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic {
return {
file,
start,
length,
messageText: isDiagnosticMessageChain(message) ?
message :
getDiagnosticMessageChain(message, args).messageText,
category: message.category,
code: message.code,
};
}
export function getDiagnosticWithoutFile(message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic {
return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args);
}
export function getDiagnosticOfFile(file: ts.SourceFile, start: number, length: number, message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic {
return getDiagnosticOfFileFrom(file, start, length, message, ...args);
}
export function getDiagnosticOfFileFromProgram(program: ts.Program, filePath: string, start: number, length: number, message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic {
return getDiagnosticOfFileFrom(program.getSourceFileByPath(ts.toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase())),
start, length, message, ...args);
}
export function getUnknownCompilerOption(program: ts.Program, configFile: File, option: string) {
const quotedOption = `"${option}"`;
return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, ts.Diagnostics.Unknown_compiler_option_0, option);
}
export function getUnknownDidYouMeanCompilerOption(program: ts.Program, configFile: File, option: string, didYouMean: string) {
const quotedOption = `"${option}"`;
return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, ts.Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, option, didYouMean);
}
export function getDiagnosticModuleNotFoundOfFile(program: ts.Program, file: File, moduleName: string) {
const quotedModuleName = `"${moduleName}"`;
return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, ts.Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option, moduleName);
}
export function runQueuedTimeoutCallbacks(sys: WatchedSystem) {
sys.runQueuedTimeoutCallbacks();
}
export function checkSingleTimeoutQueueLengthAndRun(sys: WatchedSystem) {
sys.checkTimeoutQueueLengthAndRun(1);
}
export function checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys: WatchedSystem) {
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
}
export type WatchOrSolution<T extends ts.BuilderProgram> = void | ts.SolutionBuilder<T> | ts.WatchOfConfigFile<T> | ts.WatchOfFilesAndCompilerOptions<T>;
export interface TscWatchCompileChange<T extends ts.BuilderProgram = ts.EmitAndSemanticDiagnosticsBuilderProgram> {
caption: string;
change: (sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void;
change: (sys: TestServerHostTrackingWrittenFiles) => void;
timeouts: (
sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles,
programs: readonly ts.CommandLineProgram[],
sys: TestServerHostTrackingWrittenFiles,
programs: readonly CommandLineProgram[],
watchOrSolution: WatchOrSolution<T>
) => void;
}
@@ -119,7 +34,7 @@ export interface TscWatchCompileBase<T extends ts.BuilderProgram = ts.EmitAndSem
changes: readonly TscWatchCompileChange<T>[];
}
export interface TscWatchCompile extends TscWatchCompileBase {
sys: () => WatchedSystem;
sys: () => TestServerHost;
}
export const noopChange: TscWatchCompileChange = {
@@ -128,7 +43,7 @@ export const noopChange: TscWatchCompileChange = {
timeouts: sys => sys.checkTimeoutQueueLength(0),
};
export type SystemSnap = ReturnType<WatchedSystem["snap"]>;
export type SystemSnap = ReturnType<TestServerHost["snap"]>;
function tscWatchCompile(input: TscWatchCompile) {
it("tsc-watch:: Generates files matching the baseline", () => {
const { sys, baseline, oldSnap } = createBaseline(input.sys());
@@ -139,7 +54,7 @@ function tscWatchCompile(input: TscWatchCompile) {
} = input;
if (!isWatch(commandLineArgs)) sys.exit = exitCode => sys.exitCode = exitCode;
const { cb, getPrograms } = ts.commandLineCallbacks(sys);
const { cb, getPrograms } = commandLineCallbacks(sys);
const watchOrSolution = ts.executeCommandLine(
sys,
cb,
@@ -163,26 +78,26 @@ function tscWatchCompile(input: TscWatchCompile) {
export interface BaselineBase {
baseline: string[];
sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles;
sys: TestServerHostTrackingWrittenFiles;
oldSnap: SystemSnap;
}
export interface Baseline extends BaselineBase, ts.CommandLineCallbacks {
export interface Baseline extends BaselineBase, CommandLineCallbacks {
}
export function createBaseline(system: WatchedSystem, modifySystem?: (sys: WatchedSystem, originalRead: WatchedSystem["readFile"]) => void): Baseline {
export function createBaseline(system: TestServerHost, modifySystem?: (sys: TestServerHost, originalRead: TestServerHost["readFile"]) => void): Baseline {
const originalRead = system.readFile;
const initialSys = fakes.patchHostForBuildInfoReadWrite(system);
const initialSys = patchHostForBuildInfoReadWrite(system);
modifySystem?.(initialSys, originalRead);
const sys = ts.TestFSWithWatch.changeToHostTrackingWrittenFiles(initialSys);
const sys = changeToHostTrackingWrittenFiles(initialSys);
const baseline: string[] = [];
baseline.push("Input::");
sys.diff(baseline);
const { cb, getPrograms } = ts.commandLineCallbacks(sys);
const { cb, getPrograms } = commandLineCallbacks(sys);
return { sys, baseline, oldSnap: sys.snap(), cb, getPrograms };
}
export function createSolutionBuilderWithWatchHostForBaseline(sys: WatchedSystem, cb: ts.ExecuteCommandLineCallbacks) {
export function createSolutionBuilderWithWatchHostForBaseline(sys: TestServerHost, cb: ts.ExecuteCommandLineCallbacks) {
const host = ts.createSolutionBuilderWithWatchHost(sys,
/*createProgram*/ undefined,
ts.createDiagnosticReporter(sys, /*pretty*/ true),
@@ -195,7 +110,7 @@ export function createSolutionBuilderWithWatchHostForBaseline(sys: WatchedSystem
}
interface CreateWatchCompilerHostOfConfigFileForBaseline<T extends ts.BuilderProgram> extends ts.CreateWatchCompilerHostOfConfigFileInput<T> {
system: WatchedSystem,
system: TestServerHost,
cb: ts.ExecuteCommandLineCallbacks;
}
@@ -212,7 +127,7 @@ export function createWatchCompilerHostOfConfigFileForBaseline<T extends ts.Buil
}
interface CreateWatchCompilerHostOfFilesAndCompilerOptionsForBaseline<T extends ts.BuilderProgram> extends ts.CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T> {
system: WatchedSystem,
system: TestServerHost,
cb: ts.ExecuteCommandLineCallbacks;
}
export function createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanticDiagnosticsBuilderProgram>(
@@ -246,8 +161,8 @@ export function applyChange(sys: BaselineBase["sys"], baseline: BaselineBase["ba
}
export interface RunWatchBaseline<T extends ts.BuilderProgram> extends BaselineBase, TscWatchCompileBase<T> {
sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles;
getPrograms: () => readonly ts.CommandLineProgram[];
sys: TestServerHostTrackingWrittenFiles;
getPrograms: () => readonly CommandLineProgram[];
watchOrSolution: WatchOrSolution<T>;
}
export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanticDiagnosticsBuilderProgram>({
@@ -280,7 +195,7 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
baselineDependencies,
});
}
Harness.Baseline.runBaseline(`${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}${isWatch(commandLineArgs) ? "Watch" : ""}/${scenario}/${subScenario.split(" ").join("-")}.js`, baseline.join("\r\n"));
Baseline.runBaseline(`${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}${isWatch(commandLineArgs) ? "Watch" : ""}/${scenario}/${subScenario.split(" ").join("-")}.js`, baseline.join("\r\n"));
}
function isWatch(commandLineArgs: readonly string[]) {
@@ -292,11 +207,11 @@ function isWatch(commandLineArgs: readonly string[]) {
}
export interface WatchBaseline extends BaselineBase, TscWatchCheckOptions {
oldPrograms: readonly (ts.CommandLineProgram | undefined)[];
getPrograms: () => readonly ts.CommandLineProgram[];
oldPrograms: readonly (CommandLineProgram | undefined)[];
getPrograms: () => readonly CommandLineProgram[];
}
export function watchBaseline({ baseline, getPrograms, oldPrograms, sys, oldSnap, baselineSourceMap, baselineDependencies }: WatchBaseline) {
if (baselineSourceMap) ts.generateSourceMapBaselineFiles(sys);
if (baselineSourceMap) generateSourceMapBaselineFiles(sys);
sys.serializeOutput(baseline);
const programs = baselinePrograms(baseline, getPrograms, oldPrograms, baselineDependencies);
sys.serializeWatches(baseline);
@@ -308,83 +223,6 @@ export function watchBaseline({ baseline, getPrograms, oldPrograms, sys, oldSnap
sys.writtenFiles.clear();
return programs;
}
export function baselinePrograms(baseline: string[], getPrograms: () => readonly ts.CommandLineProgram[], oldPrograms: readonly (ts.CommandLineProgram | undefined)[], baselineDependencies: boolean | undefined) {
const programs = getPrograms();
for (let i = 0; i < programs.length; i++) {
baselineProgram(baseline, programs[i], oldPrograms[i], baselineDependencies);
}
return programs;
}
function baselineProgram(baseline: string[], [program, builderProgram]: ts.CommandLineProgram, oldProgram: ts.CommandLineProgram | undefined, baselineDependencies: boolean | undefined) {
if (program !== oldProgram?.[0]) {
const options = program.getCompilerOptions();
baseline.push(`Program root files: ${JSON.stringify(program.getRootFileNames())}`);
baseline.push(`Program options: ${JSON.stringify(options)}`);
baseline.push(`Program structureReused: ${(ts as any).StructureIsReused[program.structureIsReused]}`);
baseline.push("Program files::");
for (const file of program.getSourceFiles()) {
baseline.push(file.fileName);
}
}
else {
baseline.push(`Program: Same as old program`);
}
baseline.push("");
if (!builderProgram) return;
if (builderProgram !== oldProgram?.[1]) {
const state = builderProgram.getState();
const internalState = state as unknown as ts.BuilderProgramState;
if (state.semanticDiagnosticsPerFile?.size) {
baseline.push("Semantic diagnostics in builder refreshed for::");
for (const file of program.getSourceFiles()) {
if (!internalState.semanticDiagnosticsFromOldState || !internalState.semanticDiagnosticsFromOldState.has(file.resolvedPath)) {
baseline.push(file.fileName);
}
}
}
else {
baseline.push("No cached semantic diagnostics in the builder::");
}
if (internalState) {
baseline.push("");
if (internalState.hasCalledUpdateShapeSignature?.size) {
baseline.push("Shape signatures in builder refreshed for::");
internalState.hasCalledUpdateShapeSignature.forEach((path: ts.Path) => {
const info = state.fileInfos.get(path);
if (info?.version === info?.signature || !info?.signature) {
baseline.push(path + " (used version)");
}
else if (internalState.filesChangingSignature?.has(path)) {
baseline.push(path + " (computed .d.ts during emit)");
}
else {
baseline.push(path + " (computed .d.ts)");
}
});
}
else {
baseline.push("No shapes updated in the builder::");
}
}
baseline.push("");
if (!baselineDependencies) return;
baseline.push("Dependencies for::");
for (const file of builderProgram.getSourceFiles()) {
baseline.push(`${file.fileName}:`);
for (const depenedency of builderProgram.getAllDependencies(file)) {
baseline.push(` ${depenedency}`);
}
}
}
else {
baseline.push(`BuilderProgram: Same as old builder program`);
}
baseline.push("");
}
export interface VerifyTscWatch extends TscWatchCompile {
baselineIncremental?: boolean;
}
@@ -405,28 +243,23 @@ export function verifyTscWatch(input: VerifyTscWatch) {
});
}
export function replaceFileText(sys: WatchedSystem, file: string, searchValue: string | RegExp, replaceValue: string) {
const content = ts.Debug.checkDefined(sys.readFile(file));
sys.writeFile(file, content.replace(searchValue, replaceValue));
}
export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], originalRead?: WatchedSystem["readFile"]) {
const host = ts.createSolutionBuilderHostForBaseline(system, /*versionToWrite*/ undefined, originalRead);
export function createSolutionBuilder(system: TestServerHost, rootNames: readonly string[], originalRead?: TestServerHost["readFile"]) {
const host = createSolutionBuilderHostForBaseline(system, /*versionToWrite*/ undefined, originalRead);
return ts.createSolutionBuilder(host, rootNames, {});
}
export function ensureErrorFreeBuild(host: WatchedSystem, rootNames: readonly string[]) {
export function ensureErrorFreeBuild(host: TestServerHost, rootNames: readonly string[]) {
// ts build should succeed
solutionBuildWithBaseline(host, rootNames);
assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
}
export function solutionBuildWithBaseline(sys: WatchedSystem, solutionRoots: readonly string[], originalRead?: WatchedSystem["readFile"]) {
export function solutionBuildWithBaseline(sys: TestServerHost, solutionRoots: readonly string[], originalRead?: TestServerHost["readFile"]) {
const originalReadFile = sys.readFile;
const originalWrite = sys.write;
const originalWriteFile = sys.writeFile;
const solutionBuilder = createSolutionBuilder(ts.TestFSWithWatch.changeToHostTrackingWrittenFiles(
fakes.patchHostForBuildInfoReadWrite(sys)
const solutionBuilder = createSolutionBuilder(changeToHostTrackingWrittenFiles(
patchHostForBuildInfoReadWrite(sys)
), solutionRoots, originalRead);
solutionBuilder.build();
sys.readFile = originalReadFile;
@@ -435,6 +268,6 @@ export function solutionBuildWithBaseline(sys: WatchedSystem, solutionRoots: rea
return sys;
}
export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: ts.TestFSWithWatch.FileOrFolderOrSymLinkMap | readonly ts.TestFSWithWatch.FileOrFolderOrSymLink[], params?: ts.TestFSWithWatch.TestServerHostCreationParameters) {
export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters) {
return solutionBuildWithBaseline(createWatchedSystem(files, params), solutionRoots);
}

View File

@@ -1,19 +1,22 @@
import * as ts from "../../_namespaces/ts";
import * as Harness from "../../_namespaces/Harness";
import { createWatchedSystem, File, libFile, TestServerHost } from "../virtualFileSystemWithWatch";
import { CommandLineProgram, libContent } from "../tsc/helpers";
import { applyChange, createBaseline, SystemSnap, verifyTscWatch, watchBaseline } from "./helpers";
describe("unittests:: tsc-watch:: emit file --incremental", () => {
const project = "/users/username/projects/project";
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: `${project}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { incremental: true } })
};
interface VerifyIncrementalWatchEmitInput {
subScenario: string;
files: () => readonly ts.tscWatch.File[];
files: () => readonly File[];
optionsToExtend?: readonly string[];
modifyFs?: (host: ts.tscWatch.WatchedSystem) => void;
modifyFs?: (host: TestServerHost) => void;
}
function verifyIncrementalWatchEmit(input: VerifyIncrementalWatchEmitInput) {
describe(input.subScenario, () => {
@@ -30,27 +33,27 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
{ subScenario, files, optionsToExtend, modifyFs }: VerifyIncrementalWatchEmitInput,
incremental: boolean
) {
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.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 || ts.emptyArray)];
baseline.push(`${sys.getExecutingFilePath()} ${argsToPass.join(" ")}`);
let oldPrograms: readonly ts.CommandLineProgram[] = ts.emptyArray;
let oldPrograms: readonly CommandLineProgram[] = ts.emptyArray;
build(oldSnap);
if (modifyFs) {
const oldSnap = ts.tscWatch.applyChange(sys, baseline, modifyFs);
const oldSnap = applyChange(sys, baseline, modifyFs);
build(oldSnap);
}
Harness.Baseline.runBaseline(`${ts.isBuild(argsToPass) ? "tsbuild/watchMode" : "tscWatch"}/incremental/${subScenario.split(" ").join("-")}-${incremental ? "incremental" : "watch"}.js`, baseline.join("\r\n"));
function build(oldSnap: ts.tscWatch.SystemSnap) {
function build(oldSnap: SystemSnap) {
const closer = ts.executeCommandLine(
sys,
cb,
argsToPass,
);
oldPrograms = ts.tscWatch.watchBaseline({
oldPrograms = watchBaseline({
baseline,
getPrograms,
oldPrograms,
@@ -62,11 +65,11 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
}
describe("non module compilation", () => {
const file1: ts.tscWatch.File = {
const file1: File = {
path: `${project}/file1.ts`,
content: "const x = 10;"
};
const file2: ts.tscWatch.File = {
const file2: File = {
path: `${project}/file2.ts`,
content: "const y = 20;"
};
@@ -74,7 +77,7 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
function verify(subScenario: string, optionsToExtend?: readonly string[]) {
const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10");
verifyIncrementalWatchEmit({
files: () => [ts.tscWatch.libFile, file1, file2, configFile],
files: () => [libFile, file1, file2, configFile],
optionsToExtend,
subScenario: `own file emit without errors/${subScenario}`,
modifyFs: host => host.writeFile(file2.path, modifiedFile2Content),
@@ -85,7 +88,7 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
});
verifyIncrementalWatchEmit({
files: () => [ts.tscWatch.libFile, file1, configFile, {
files: () => [libFile, file1, configFile, {
path: file2.path,
content: `const y: string = 20;`
}],
@@ -94,7 +97,7 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
});
verifyIncrementalWatchEmit({
files: () => [ts.tscWatch.libFile, file1, file2, {
files: () => [libFile, file1, file2, {
path: configFile.path,
content: JSON.stringify({ compilerOptions: { incremental: true, outFile: "out.js" } })
}],
@@ -103,39 +106,39 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
});
describe("module compilation", () => {
const file1: ts.tscWatch.File = {
const file1: File = {
path: `${project}/file1.ts`,
content: "export const x = 10;"
};
const file2: ts.tscWatch.File = {
const file2: File = {
path: `${project}/file2.ts`,
content: "export const y = 20;"
};
const config: ts.tscWatch.File = {
const config: File = {
path: configFile.path,
content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd" } })
};
verifyIncrementalWatchEmit({
files: () => [ts.tscWatch.libFile, file1, file2, config],
files: () => [libFile, file1, file2, config],
subScenario: "module compilation/own file emit without errors",
modifyFs: host => host.writeFile(file2.path, file2.content.replace("y", "z").replace("20", "10")),
});
describe("own file emit with errors", () => {
const fileModified: ts.tscWatch.File = {
const fileModified: File = {
path: file2.path,
content: `export const y: string = 20;`
};
verifyIncrementalWatchEmit({
files: () => [ts.tscWatch.libFile, file1, fileModified, config],
files: () => [libFile, file1, fileModified, config],
subScenario: "module compilation/own file emit with errors",
modifyFs: host => host.writeFile(file1.path, file1.content.replace("x = 10", "z = 10")),
});
it("verify that state is read correctly", () => {
const system = ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, file1, fileModified, config], { currentDirectory: project });
const system = createWatchedSystem([libFile, file1, fileModified, config], { currentDirectory: project });
const reportDiagnostic = ts.createDiagnosticReporter(system);
const parsedConfig = ts.parseConfigFileWithSystem("tsconfig.json", {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, reportDiagnostic)!;
ts.performIncrementalCompilation({
@@ -160,9 +163,9 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
assert.equal(state.changedFilesSet!.size, 0, "changes");
assert.equal(state.fileInfos.size, 3, "FileInfo size");
assert.deepEqual(state.fileInfos.get(ts.tscWatch.libFile.path as ts.Path), {
version: system.createHash(ts.tscWatch.libFile.content),
signature: system.createHash(ts.tscWatch.libFile.content),
assert.deepEqual(state.fileInfos.get(libFile.path as ts.Path), {
version: system.createHash(libFile.content),
signature: system.createHash(libFile.content),
affectsGlobalScope: true,
impliedFormat: undefined,
});
@@ -189,7 +192,7 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
assert.equal(ts.arrayFrom(state.exportedModulesMap!.keys()).length, 0);
assert.equal(state.semanticDiagnosticsPerFile!.size, 3);
assert.deepEqual(state.semanticDiagnosticsPerFile!.get(ts.tscWatch.libFile.path as ts.Path), ts.emptyArray);
assert.deepEqual(state.semanticDiagnosticsPerFile!.get(libFile.path as ts.Path), ts.emptyArray);
assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file1.path as ts.Path), ts.emptyArray);
assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file2.path as ts.Path), [{
file: state.program!.getSourceFileByPath(file2.path as ts.Path)!,
@@ -208,7 +211,7 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
});
verifyIncrementalWatchEmit({
files: () => [ts.tscWatch.libFile, file1, file2, {
files: () => [libFile, file1, file2, {
path: configFile.path,
content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd", outFile: "out.js" } })
}],
@@ -218,7 +221,7 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
verifyIncrementalWatchEmit({
files: () => {
const config: ts.tscWatch.File = {
const config: File = {
path: configFile.path,
content: JSON.stringify({
compilerOptions: {
@@ -230,7 +233,7 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => {
}
})
};
const aTs: ts.tscWatch.File = {
const aTs: File = {
path: `${project}/a.ts`,
content: `import { B } from "./b";
export interface A {
@@ -238,7 +241,7 @@ export interface A {
}
`
};
const bTs: ts.tscWatch.File = {
const bTs: File = {
path: `${project}/b.ts`,
content: `import { C } from "./c";
export interface B {
@@ -246,7 +249,7 @@ export interface B {
}
`
};
const cTs: ts.tscWatch.File = {
const cTs: File = {
path: `${project}/c.ts`,
content: `import { A } from "./a";
export interface C {
@@ -254,14 +257,14 @@ export interface C {
}
`
};
const indexTs: ts.tscWatch.File = {
const indexTs: File = {
path: `${project}/index.ts`,
content: `export { A } from "./a";
export { B } from "./b";
export { C } from "./c";
`
};
return [ts.tscWatch.libFile, aTs, bTs, cTs, indexTs, config];
return [libFile, aTs, bTs, cTs, indexTs, config];
},
subScenario: "incremental with circular references",
modifyFs: host => host.writeFile(`${project}/a.ts`, `import { B } from "./b";
@@ -275,7 +278,7 @@ export interface A {
verifyIncrementalWatchEmit({
subScenario: "when file with ambient global declaration file is deleted",
files: () => [
{ path: ts.tscWatch.libFile.path, content: ts.libContent },
{ path: libFile.path, content: libContent },
{ path: `${project}/globals.d.ts`, content: `declare namespace Config { const value: string;} ` },
{ path: `${project}/index.ts`, content: `console.log(Config.value);` },
{ path: configFile.path, content: JSON.stringify({ compilerOptions: { incremental: true, } }) }
@@ -301,7 +304,7 @@ export const Fragment: unique symbol;
verifyIncrementalWatchEmit({
subScenario: "jsxImportSource option changed",
files: () => [
{ path: ts.tscWatch.libFile.path, content: ts.libContent },
{ path: libFile.path, content: libContent },
{ path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent },
{ path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) },
{ path: `${project}/node_modules/preact/jsx-runtime/index.d.ts`, content: jsxLibraryContent.replace("propA", "propB") },
@@ -316,7 +319,7 @@ export const Fragment: unique symbol;
verifyIncrementalWatchEmit({
subScenario: "jsxImportSource backing types added",
files: () => [
{ path: ts.tscWatch.libFile.path, content: ts.libContent },
{ path: libFile.path, content: libContent },
{ path: `${project}/index.tsx`, content: `export const App = () => <div propA={true}></div>;` },
{ path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) }
],
@@ -332,7 +335,7 @@ export const Fragment: unique symbol;
verifyIncrementalWatchEmit({
subScenario: "jsxImportSource backing types removed",
files: () => [
{ path: ts.tscWatch.libFile.path, content: ts.libContent },
{ path: libFile.path, content: libContent },
{ path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent },
{ path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) },
{ path: `${project}/index.tsx`, content: `export const App = () => <div propA={true}></div>;` },
@@ -347,7 +350,7 @@ export const Fragment: unique symbol;
verifyIncrementalWatchEmit({
subScenario: "importHelpers backing types removed",
files: () => [
{ path: ts.tscWatch.libFile.path, content: ts.libContent },
{ path: libFile.path, content: libContent },
{ path: `${project}/node_modules/tslib/index.d.ts`, content: "export function __assign(...args: any[]): any;" },
{ path: `${project}/node_modules/tslib/package.json`, content: JSON.stringify({ name: "tslib", version: "0.0.1" }) },
{ path: `${project}/index.tsx`, content: `export const x = {...{}};` },
@@ -364,7 +367,7 @@ export const Fragment: unique symbol;
verifyIncrementalWatchEmit({
subScenario: "editing module augmentation",
files: () => [
{ path: ts.tscWatch.libFile.path, content: ts.libContent },
{ path: libFile.path, content: libContent },
{ path: `${project}/node_modules/classnames/index.d.ts`, content: `export interface Result {} export default function classNames(): Result;` },
{ path: `${project}/src/types/classnames.d.ts`, content: `export {}; declare module "classnames" { interface Result { foo } }` },
{ path: `${project}/src/index.ts`, content: `import classNames from "classnames"; classNames().foo;` },
@@ -377,14 +380,14 @@ export const Fragment: unique symbol;
});
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "incremental",
subScenario: "tsbuildinfo has error",
sys: () => ts.tscWatch.createWatchedSystem({
sys: () => createWatchedSystem({
"/src/project/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": "{}",
"/src/project/tsconfig.tsbuildinfo": "Some random string",
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
[libFile.path]: libFile.content,
}),
commandLineArgs: ["--p", "src/project", "-i", "-w"],
changes: ts.emptyArray

View File

@@ -1,13 +1,15 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { createWatchedSystem, File, libFile } from "../virtualFileSystemWithWatch";
import { verifyTscWatch } from "./helpers";
describe("unittests:: tsc-watch:: moduleResolution", () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `watches for changes to package-json main fields`,
sys: () => ts.tscWatch.createWatchedSystem([
sys: () => createWatchedSystem([
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`,
path: `/user/username/projects/myproject/packages/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "1.0.0",
@@ -15,13 +17,13 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/index.ts`,
path: `/user/username/projects/myproject/packages/pkg1/index.ts`,
content: Utils.dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg1/tsconfig.json`,
path: `/user/username/projects/myproject/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "build",
@@ -29,19 +31,19 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/build/const.d.ts`,
path: `/user/username/projects/myproject/packages/pkg2/build/const.d.ts`,
content: `export type TheNum = 42;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/build/index.d.ts`,
path: `/user/username/projects/myproject/packages/pkg2/build/index.d.ts`,
content: `export type { TheNum } from './const.js';`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/build/other.d.ts`,
path: `/user/username/projects/myproject/packages/pkg2/build/other.d.ts`,
content: `export type TheStr = string;`
},
{
path: `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`,
path: `/user/username/projects/myproject/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
@@ -49,16 +51,16 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg2`,
symLink: `${ts.tscWatch.projectRoot}/packages/pkg2`,
path: `/user/username/projects/myproject/node_modules/pkg2`,
symLink: `/user/username/projects/myproject/packages/pkg2`,
},
ts.tscWatch.libFile
], { currentDirectory: ts.tscWatch.projectRoot }),
libFile
], { currentDirectory: "/user/username/projects/myproject" }),
commandLineArgs: ["--project", "./packages/pkg1/tsconfig.json", "-w", "--traceResolution"],
changes: [
{
caption: "reports import errors after change to package file",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`),
change: sys => sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `index.js`, `other.js`),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks(); // invalidates failed lookups
sys.runQueuedTimeoutCallbacks(); // actual update
@@ -66,7 +68,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
},
{
caption: "removes those errors when a package file is changed back",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`),
change: sys => sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `other.js`, `index.js`),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks(); // invalidates failed lookups
sys.runQueuedTimeoutCallbacks(); // actual update
@@ -75,12 +77,12 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: "diagnostics from cache",
sys: () => ts.tscWatch.createWatchedSystem([
sys: () => createWatchedSystem([
{
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
moduleResolution: "nodenext",
@@ -91,7 +93,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/package.json`,
path: `/user/username/projects/myproject/package.json`,
content: JSON.stringify({
name: "@this/package",
type: "module",
@@ -104,23 +106,23 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/index.ts`,
path: `/user/username/projects/myproject/index.ts`,
content: Utils.dedent`
import * as me from "@this/package";
me.thing()
export function thing(): void {}
`
},
ts.tscWatch.libFile
], { currentDirectory: ts.tscWatch.projectRoot }),
libFile
], { currentDirectory: "/user/username/projects/myproject" }),
commandLineArgs: ["-w", "--traceResolution"],
changes: ts.emptyArray
});
describe("package json file is edited", () => {
function getSys(packageFileContents: string) {
const configFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/src/tsconfig.json`,
const configFile: File = {
path: `/user/username/projects/myproject/src/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
target: "es2016",
@@ -129,30 +131,30 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
}
})
};
const packageFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/package.json`,
const packageFile: File = {
path: `/user/username/projects/myproject/package.json`,
content: packageFileContents
};
const fileA: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/src/fileA.ts`,
const fileA: File = {
path: `/user/username/projects/myproject/src/fileA.ts`,
content: Utils.dedent`
import { foo } from "./fileB.mjs";
foo();
`
};
const fileB: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/project/src/fileB.mts`,
const fileB: File = {
path: `/user/username/projects/myproject/project/src/fileB.mts`,
content: Utils.dedent`
export function foo() {
}
`
};
return ts.tscWatch.createWatchedSystem(
[configFile, fileA, fileB, packageFile, { ...ts.tscWatch.libFile, path: "/a/lib/lib.es2016.full.d.ts" }],
{ currentDirectory: ts.tscWatch.projectRoot }
return createWatchedSystem(
[configFile, fileA, fileB, packageFile, { ...libFile, path: "/a/lib/lib.es2016.full.d.ts" }],
{ currentDirectory: "/user/username/projects/myproject" }
);
}
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: "package json file is edited",
commandLineArgs: ["--w", "--p", "src", "--extendedDiagnostics", "-traceResolution", "--explainFiles"],
@@ -160,7 +162,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
changes: [
{
caption: "Modify package json file to add type module",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({
change: sys => sys.writeFile(`/user/username/projects/myproject/package.json`, JSON.stringify({
name: "app", version: "1.0.0", type: "module",
})),
timeouts: host => {
@@ -170,7 +172,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
},
{
caption: "Modify package.json file to remove type module",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })),
change: sys => sys.writeFile(`/user/username/projects/myproject/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })),
timeouts: host => {
host.runQueuedTimeoutCallbacks(); // Failed lookup updates
host.runQueuedTimeoutCallbacks(); // Actual update
@@ -178,7 +180,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
},
{
caption: "Delete package.json",
change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/package.json`),
change: sys => sys.deleteFile(`/user/username/projects/myproject/package.json`),
timeouts: host => {
host.runQueuedTimeoutCallbacks(); // Failed lookup updates
host.runQueuedTimeoutCallbacks(); // Actual update
@@ -186,7 +188,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
},
{
caption: "Modify package json file to add type module",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({
change: sys => sys.writeFile(`/user/username/projects/myproject/package.json`, JSON.stringify({
name: "app", version: "1.0.0", type: "module",
})),
timeouts: host => {
@@ -196,7 +198,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
},
{
caption: "Delete package.json",
change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/package.json`),
change: sys => sys.deleteFile(`/user/username/projects/myproject/package.json`),
timeouts: host => {
host.runQueuedTimeoutCallbacks(); // Failed lookup updates
host.runQueuedTimeoutCallbacks(); // Actual update
@@ -205,7 +207,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
],
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: "package json file is edited when package json with type module exists",
commandLineArgs: ["--w", "--p", "src", "--extendedDiagnostics", "-traceResolution", "--explainFiles"],
@@ -215,7 +217,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
changes: [
{
caption: "Modify package.json file to remove type module",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })),
change: sys => sys.writeFile(`/user/username/projects/myproject/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })),
timeouts: host => {
host.runQueuedTimeoutCallbacks(); // Failed lookup updates
host.runQueuedTimeoutCallbacks(); // Actual update
@@ -223,7 +225,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
},
{
caption: "Modify package json file to add type module",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({
change: sys => sys.writeFile(`/user/username/projects/myproject/package.json`, JSON.stringify({
name: "app", version: "1.0.0", type: "module",
})),
timeouts: host => {
@@ -233,7 +235,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
},
{
caption: "Delete package.json",
change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/package.json`),
change: sys => sys.deleteFile(`/user/username/projects/myproject/package.json`),
timeouts: host => {
host.runQueuedTimeoutCallbacks(); // Failed lookup updates
host.runQueuedTimeoutCallbacks(); // Actual update
@@ -241,7 +243,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
},
{
caption: "Modify package json file to without type module",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })),
change: sys => sys.writeFile(`/user/username/projects/myproject/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })),
timeouts: host => {
host.runQueuedTimeoutCallbacks(); // Failed lookup updates
host.runQueuedTimeoutCallbacks(); // Actual update
@@ -249,7 +251,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
},
{
caption: "Delete package.json",
change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/package.json`),
change: sys => sys.deleteFile(`/user/username/projects/myproject/package.json`),
timeouts: host => {
host.runQueuedTimeoutCallbacks(); // Failed lookup updates
host.runQueuedTimeoutCallbacks(); // Actual update
@@ -259,18 +261,18 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
});
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: "module resolutions from file are partially used",
sys: () => ts.tscWatch.createWatchedSystem([
sys: () => createWatchedSystem([
{
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { moduleResolution: "node16" },
})
},
{
path: `${ts.tscWatch.projectRoot}/index.ts`,
path: `/user/username/projects/myproject/index.ts`,
content: Utils.dedent`
import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" };
import type { RequireInterface } from "pkg1" assert { "resolution-mode": "require" };
@@ -278,13 +280,13 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
`
},
{
path: `${ts.tscWatch.projectRoot}/a.ts`,
path: `/user/username/projects/myproject/a.ts`,
content: Utils.dedent`
export const x = 10;
`
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg/package.json`,
path: `/user/username/projects/myproject/node_modules/pkg/package.json`,
content: JSON.stringify({
name: "pkg",
version: "0.0.1",
@@ -295,15 +297,15 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg/import.d.ts`,
path: `/user/username/projects/myproject/node_modules/pkg/import.d.ts`,
content: `export interface ImportInterface {}`
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg/require.d.ts`,
path: `/user/username/projects/myproject/node_modules/pkg/require.d.ts`,
content: `export interface RequireInterface {}`
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg1/package.json`,
path: `/user/username/projects/myproject/node_modules/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "0.0.1",
@@ -314,33 +316,33 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg1/import.d.ts`,
path: `/user/username/projects/myproject/node_modules/pkg1/import.d.ts`,
content: `export interface ImportInterface {}`
},
ts.tscWatch.libFile
], { currentDirectory: ts.tscWatch.projectRoot }),
libFile
], { currentDirectory: "/user/username/projects/myproject" }),
commandLineArgs: ["-w", "--traceResolution"],
changes: [
{
caption: "modify aFile by adding import",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/a.ts`, `import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" }`),
change: sys => sys.appendFile(`/user/username/projects/myproject/a.ts`, `import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" }`),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "moduleResolution",
subScenario: "type reference resolutions reuse",
sys: () => ts.tscWatch.createWatchedSystem([
sys: () => createWatchedSystem([
{
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { moduleResolution: "node16" },
})
},
{
path: `${ts.tscWatch.projectRoot}/index.ts`,
path: `/user/username/projects/myproject/index.ts`,
content: Utils.dedent`
/// <reference types="pkg" resolution-mode="import"/>
/// <reference types="pkg1" resolution-mode="require"/>
@@ -348,13 +350,13 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
`
},
{
path: `${ts.tscWatch.projectRoot}/a.ts`,
path: `/user/username/projects/myproject/a.ts`,
content: Utils.dedent`
export const x = 10;
`
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg/package.json`,
path: `/user/username/projects/myproject/node_modules/pkg/package.json`,
content: JSON.stringify({
name: "pkg",
version: "0.0.1",
@@ -365,7 +367,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg/import.d.ts`,
path: `/user/username/projects/myproject/node_modules/pkg/import.d.ts`,
content: Utils.dedent`
export {};
declare global {
@@ -374,7 +376,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
`
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg/require.d.ts`,
path: `/user/username/projects/myproject/node_modules/pkg/require.d.ts`,
content: Utils.dedent`
export {};
declare global {
@@ -383,7 +385,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
`
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg1/package.json`,
path: `/user/username/projects/myproject/node_modules/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "0.0.1",
@@ -394,7 +396,7 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
})
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/pkg1/import.d.ts`,
path: `/user/username/projects/myproject/node_modules/pkg1/import.d.ts`,
content: Utils.dedent`
export {};
declare global {
@@ -403,16 +405,16 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
`
},
{
path: `${ts.tscWatch.projectRoot}/node_modules/@types/pkg2/index.d.ts`,
path: `/user/username/projects/myproject/node_modules/@types/pkg2/index.d.ts`,
content: `export const x = 10;`
},
ts.tscWatch.libFile
], { currentDirectory: ts.tscWatch.projectRoot }),
libFile
], { currentDirectory: "/user/username/projects/myproject" }),
commandLineArgs: ["-w", "--traceResolution"],
changes: [
{
caption: "modify aFile by adding import",
change: sys => sys.prependFile(`${ts.tscWatch.projectRoot}/a.ts`, `/// <reference types="pkg" resolution-mode="import"/>\n`),
change: sys => sys.prependFile(`/user/username/projects/myproject/a.ts`, `/// <reference types="pkg" resolution-mode="import"/>\n`),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
]

View File

@@ -1,13 +1,14 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import { createWatchedSystem, File, libFile } from "../virtualFileSystemWithWatch";
import { verifyTscWatch } from "./helpers";
describe("unittests:: tsc-watch:: nodeNextWatch:: emit when module emit is specified as nodenext", () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "nodenext watch emit",
subScenario: "esm-mode file is edited",
commandLineArgs: ["--w", "--p", "/project/tsconfig.json"],
sys: () => {
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/project/tsconfig.json",
content: JSON.stringify({
compilerOptions: {
@@ -19,7 +20,7 @@ describe("unittests:: tsc-watch:: nodeNextWatch:: emit when module emit is speci
}
})
};
const packageFile: ts.tscWatch.File = {
const packageFile: File = {
path: "/project/package.json",
content: JSON.stringify({
name: "some-proj",
@@ -29,18 +30,18 @@ describe("unittests:: tsc-watch:: nodeNextWatch:: emit when module emit is speci
main: "index.js",
})
};
const file1: ts.tscWatch.File = {
const file1: File = {
path: "/project/src/index.ts",
content: Utils.dedent`
import * as Thing from "thing";
Thing.fn();`
};
const declFile: ts.tscWatch.File = {
const declFile: File = {
path: "/project/src/deps.d.ts",
content: `declare module "thing";`
};
return ts.tscWatch.createWatchedSystem([configFile, file1, declFile, packageFile, { ...ts.tscWatch.libFile, path: "/a/lib/lib.es2020.full.d.ts" }]);
return createWatchedSystem([configFile, file1, declFile, packageFile, { ...libFile, path: "/a/lib/lib.es2020.full.d.ts" }]);
},
changes: [
{
@@ -52,7 +53,7 @@ describe("unittests:: tsc-watch:: nodeNextWatch:: emit when module emit is speci
Thing.fn();`,
{},
),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
],
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,33 @@
import * as ts from "../../_namespaces/ts";
import { getTsBuildProjectFile, getTsBuildProjectFilePath, libFile, TestServerHost } from "../virtualFileSystemWithWatch";
import { createSolutionBuilder, createSystemWithSolutionBuild, verifyTscWatch } from "./helpers";
describe("unittests:: tsc-watch:: projects with references: invoking when references are already built", () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "on sample project",
sys: () => ts.tscWatch.createSystemWithSolutionBuild(
sys: () => createSystemWithSolutionBuild(
["tests"],
[
ts.tscWatch.libFile,
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/tsconfig.json"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/index.ts"),
libFile,
getTsBuildProjectFile("sample1", "core/tsconfig.json"),
getTsBuildProjectFile("sample1", "core/index.ts"),
getTsBuildProjectFile("sample1", "core/anotherModule.ts"),
getTsBuildProjectFile("sample1", "core/some_decl.d.ts"),
getTsBuildProjectFile("sample1", "logic/tsconfig.json"),
getTsBuildProjectFile("sample1", "logic/index.ts"),
getTsBuildProjectFile("sample1", "tests/tsconfig.json"),
getTsBuildProjectFile("sample1", "tests/index.ts"),
],
{ currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/sample1` }
{ currentDirectory: `/user/username/projects/sample1` }
),
commandLineArgs: ["-w", "-p", "tests"],
changes: [
{
caption: "local edit in logic ts, and build logic",
change: sys => {
sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `function foo() { }`);
const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["logic"]);
sys.appendFile(getTsBuildProjectFilePath("sample1", "logic/index.ts"), `function foo() { }`);
const solutionBuilder = createSolutionBuilder(sys, ["logic"]);
solutionBuilder.build();
},
// not ideal, but currently because of d.ts but no new file is written
@@ -35,160 +37,160 @@ describe("unittests:: tsc-watch:: projects with references: invoking when refere
{
caption: "non local edit in logic ts, and build logic",
change: sys => {
sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `export function gfoo() { }`);
const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["logic"]);
sys.appendFile(getTsBuildProjectFilePath("sample1", "logic/index.ts"), `export function gfoo() { }`);
const solutionBuilder = createSolutionBuilder(sys, ["logic"]);
solutionBuilder.build();
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "change in project reference config file builds correctly",
change: sys => {
sys.writeFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/tsconfig.json"), JSON.stringify({
sys.writeFile(getTsBuildProjectFilePath("sample1", "logic/tsconfig.json"), JSON.stringify({
compilerOptions: { composite: true, declaration: true, declarationDir: "decls" },
references: [{ path: "../core" }]
}));
const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["logic"]);
const solutionBuilder = createSolutionBuilder(sys, ["logic"]);
solutionBuilder.build();
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
],
baselineDependencies: true
});
function changeCompilerOpitonsPaths(sys: ts.tscWatch.WatchedSystem, config: string, newPaths: object) {
function changeCompilerOpitonsPaths(sys: TestServerHost, config: string, newPaths: object) {
const configJson = JSON.parse(sys.readFile(config)!);
configJson.compilerOptions.paths = newPaths;
sys.writeFile(config, JSON.stringify(configJson));
}
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "on transitive references",
sys: () => ts.tscWatch.createSystemWithSolutionBuild(
sys: () => createSystemWithSolutionBuild(
["tsconfig.c.json"],
[
ts.tscWatch.libFile,
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"),
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json"),
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"),
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "b.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
libFile,
getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"),
getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json"),
getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"),
getTsBuildProjectFile("transitiveReferences", "a.ts"),
getTsBuildProjectFile("transitiveReferences", "b.ts"),
getTsBuildProjectFile("transitiveReferences", "c.ts"),
getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
],
{ currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` }
{ currentDirectory: `/user/username/projects/transitiveReferences` }
),
commandLineArgs: ["-w", "-p", "tsconfig.c.json"],
changes: [
{
caption: "non local edit b ts, and build b",
change: sys => {
sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), `export function gfoo() { }`);
const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["tsconfig.b.json"]);
sys.appendFile(getTsBuildProjectFilePath("transitiveReferences", "b.ts"), `export function gfoo() { }`);
const solutionBuilder = createSolutionBuilder(sys, ["tsconfig.b.json"]);
solutionBuilder.build();
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "edit on config file",
change: sys => {
sys.ensureFileOrFolder({
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"),
content: sys.readFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))!
path: getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"),
content: sys.readFile(getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))!
});
changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./nrefs/*"] });
changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./nrefs/*"] });
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "Revert config file edit",
change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./refs/*"] }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./refs/*"] }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "edit in referenced config file",
change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./nrefs/*"] }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./nrefs/*"] }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "Revert referenced config file edit",
change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./refs/*"] }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./refs/*"] }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "deleting referenced config file",
change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json")),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => sys.deleteFile(getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "Revert deleting referenced config file",
change: sys => sys.ensureFileOrFolder(ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json")),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => sys.ensureFileOrFolder(getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "deleting transitively referenced config file",
change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.a.json")),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => sys.deleteFile(getTsBuildProjectFilePath("transitiveReferences", "tsconfig.a.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "Revert deleting transitively referenced config file",
change: sys => sys.ensureFileOrFolder(ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json")),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => sys.ensureFileOrFolder(getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
],
baselineDependencies: true,
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "when referenced project uses different module resolution",
sys: () => ts.tscWatch.createSystemWithSolutionBuild(
sys: () => createSystemWithSolutionBuild(
["tsconfig.c.json"],
[
ts.tscWatch.libFile,
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"),
libFile,
getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"),
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"),
path: getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"),
content: JSON.stringify({
compilerOptions: { composite: true, moduleResolution: "classic" },
files: ["b.ts"],
references: [{ path: "tsconfig.a.json" }]
})
},
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"),
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"),
getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"),
getTsBuildProjectFile("transitiveReferences", "a.ts"),
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"),
path: getTsBuildProjectFilePath("transitiveReferences", "b.ts"),
content: `import {A} from "a";export const b = new A();`
},
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
getTsBuildProjectFile("transitiveReferences", "c.ts"),
getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
],
{ currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` }
{ currentDirectory: `/user/username/projects/transitiveReferences` }
),
commandLineArgs: ["-w", "-p", "tsconfig.c.json"],
changes: ts.emptyArray,
baselineDependencies: true,
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "on transitive references in different folders",
sys: () => ts.tscWatch.createSystemWithSolutionBuild(
sys: () => createSystemWithSolutionBuild(
["c"],
[
ts.tscWatch.libFile,
libFile,
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
path: getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
content: JSON.stringify({
compilerOptions: { composite: true },
files: ["index.ts"]
}),
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
path: getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
content: JSON.stringify({
compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } },
files: ["index.ts"],
@@ -196,7 +198,7 @@ describe("unittests:: tsc-watch:: projects with references: invoking when refere
}),
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"),
path: getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"),
content: JSON.stringify({
compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } },
files: ["index.ts"],
@@ -204,71 +206,71 @@ describe("unittests:: tsc-watch:: projects with references: invoking when refere
}),
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"),
path: getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"),
content: `export class A {}`,
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"),
path: getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"),
content: `import {A} from '@ref/a';
export const b = new A();`,
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"),
path: getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"),
content: `import {b} from '../b';
import {X} from "@ref/a";
b;
X;`,
},
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
],
{ currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` }
{ currentDirectory: `/user/username/projects/transitiveReferences` }
),
commandLineArgs: ["-w", "-p", "c"],
changes: [
{
caption: "non local edit b ts, and build b",
change: sys => {
sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`);
const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["b"]);
sys.appendFile(getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`);
const solutionBuilder = createSolutionBuilder(sys, ["b"]);
solutionBuilder.build();
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "edit on config file",
change: sys => {
sys.ensureFileOrFolder({
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"),
content: sys.readFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))!
path: getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"),
content: sys.readFile(getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))!
});
changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] });
changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] });
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "Revert config file edit",
change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "edit in referenced config file",
change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "Revert referenced config file edit",
change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "deleting referenced config file",
change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")),
change: sys => sys.deleteFile(getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2)
},
{
caption: "Revert deleting referenced config file",
change: sys => sys.writeFile(
ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
JSON.stringify({
compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } },
files: ["index.ts"],
@@ -279,13 +281,13 @@ X;`,
},
{
caption: "deleting transitively referenced config file",
change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")),
change: sys => sys.deleteFile(getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2)
},
{
caption: "Revert deleting transitively referenced config file",
change: sys => sys.writeFile(
ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
JSON.stringify({
compilerOptions: { composite: true },
files: ["index.ts"]
@@ -297,97 +299,97 @@ X;`,
baselineDependencies: true,
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "on transitive references in different folders with no files clause",
sys: () => ts.tscWatch.createSystemWithSolutionBuild(
sys: () => createSystemWithSolutionBuild(
["c"],
[
ts.tscWatch.libFile,
libFile,
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
path: getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
content: JSON.stringify({ compilerOptions: { composite: true } }),
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
path: getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
content: JSON.stringify({
compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } },
references: [{ path: `../a` }]
}),
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"),
path: getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"),
content: JSON.stringify({
compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } },
references: [{ path: `../b` }]
}),
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"),
path: getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"),
content: `export class A {}`,
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"),
path: getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"),
content: `import {A} from '@ref/a';
export const b = new A();`,
},
{
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"),
path: getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"),
content: `import {b} from '../b';
import {X} from "@ref/a";
b;
X;`,
},
ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
],
{ currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` }
{ currentDirectory: `/user/username/projects/transitiveReferences` }
),
commandLineArgs: ["-w", "-p", "c"],
changes: [
{
caption: "non local edit b ts, and build b",
change: sys => {
sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`);
const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["b"]);
sys.appendFile(getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`);
const solutionBuilder = createSolutionBuilder(sys, ["b"]);
solutionBuilder.build();
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "edit on config file",
change: sys => {
sys.ensureFileOrFolder({
path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"),
content: sys.readFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))!
path: getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"),
content: sys.readFile(getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))!
});
changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] });
changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] });
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "Revert config file edit",
change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "edit in referenced config file",
change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "Revert referenced config file edit",
change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
change: sys => changeCompilerOpitonsPaths(sys, getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1)
},
{
caption: "deleting referenced config file",
change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")),
change: sys => sys.deleteFile(getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2)
},
{
caption: "Revert deleting referenced config file",
change: sys => sys.writeFile(
ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
JSON.stringify({
compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } },
references: [{ path: `../a` }]
@@ -397,13 +399,13 @@ X;`,
},
{
caption: "deleting transitively referenced config file",
change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")),
change: sys => sys.deleteFile(getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2)
},
{
caption: "Revert deleting transitively referenced config file",
change: sys => sys.writeFile(
ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
JSON.stringify({ compilerOptions: { composite: true } }),
),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2)
@@ -412,29 +414,29 @@ X;`,
baselineDependencies: true,
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "when declarationMap changes for dependency",
sys: () => ts.tscWatch.createSystemWithSolutionBuild(
sys: () => createSystemWithSolutionBuild(
["core"],
[
ts.tscWatch.libFile,
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json"),
ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts"),
libFile,
getTsBuildProjectFile("sample1", "core/tsconfig.json"),
getTsBuildProjectFile("sample1", "core/index.ts"),
getTsBuildProjectFile("sample1", "core/anotherModule.ts"),
getTsBuildProjectFile("sample1", "core/some_decl.d.ts"),
getTsBuildProjectFile("sample1", "logic/tsconfig.json"),
getTsBuildProjectFile("sample1", "logic/index.ts"),
],
{ currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/sample1` }
{ currentDirectory: `/user/username/projects/sample1` }
),
commandLineArgs: ["-w", "-p", "logic"],
changes: [
{
caption: "change declration map in core",
change: sys => {
ts.tscWatch.replaceFileText(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", "core/tsconfig.json"), `"declarationMap": true,`, `"declarationMap": false,`);
const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["core"]);
sys.replaceFileText(getTsBuildProjectFilePath("sample1", "core/tsconfig.json"), `"declarationMap": true,`, `"declarationMap": false,`);
const solutionBuilder = createSolutionBuilder(sys, ["core"]);
solutionBuilder.build();
},
timeouts: sys => sys.runQueuedTimeoutCallbacks(0),

View File

@@ -1,4 +1,6 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, libFile, SymLink } from "../virtualFileSystemWithWatch";
import { createBaseline, createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline, runWatchBaseline, verifyTscWatch } from "./helpers";
describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => {
const scenario = "resolutionCache";
@@ -12,8 +14,8 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution
content: `foo()`
};
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([root, imported, ts.tscWatch.libFile]));
const host = ts.tscWatch.createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile]));
const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
rootFiles: [root.path],
system: sys,
options: { module: ts.ModuleKind.AMD },
@@ -23,7 +25,7 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution
const originalFileExists = host.fileExists;
const watch = ts.createWatchProgram(host);
let fileExistsIsCalled = false;
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "resolutionCache",
subScenario: "caching works",
commandLineArgs: ["--w", root.path],
@@ -40,7 +42,7 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution
sys.writeFile(root.path, `import {x} from "f1"
var x: string = 1;`);
},
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks
timeouts: sys => sys.runQueuedTimeoutCallbacks()
},
{
caption: "Resolves f2",
@@ -95,8 +97,8 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution
content: `export const y = 1;`
};
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([root, ts.tscWatch.libFile]));
const host = ts.tscWatch.createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, libFile]));
const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
rootFiles: [root.path],
system: sys,
options: { module: ts.ModuleKind.AMD },
@@ -118,7 +120,7 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution
const watch = ts.createWatchProgram(host);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "resolutionCache",
subScenario: "loads missing files from disk",
commandLineArgs: ["--w", root.path],
@@ -153,8 +155,8 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution
content: `export const y = 1;export const x = 10;`
};
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([root, imported, ts.tscWatch.libFile]));
const host = ts.tscWatch.createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile]));
const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({
rootFiles: [root.path],
system: sys,
options: { module: ts.ModuleKind.AMD },
@@ -174,7 +176,7 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution
};
const watch = ts.createWatchProgram(host);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "resolutionCache",
subScenario: "should compile correctly when resolved module goes missing and then comes back",
commandLineArgs: ["--w", root.path],
@@ -211,14 +213,14 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution
});
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "works when module resolution changes to ambient module",
commandLineArgs: ["-w", "/a/b/foo.ts"],
sys: () => ts.tscWatch.createWatchedSystem([{
sys: () => createWatchedSystem([{
path: "/a/b/foo.ts",
content: `import * as fs from "fs";`
}, ts.tscWatch.libFile], { currentDirectory: "/a/b" }),
}, libFile], { currentDirectory: "/a/b" }),
changes: [
{
caption: "npm install node types",
@@ -241,12 +243,12 @@ declare module "fs" {
}`
});
},
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "works when included file with ambient module changes",
commandLineArgs: ["--w", "/a/b/foo.ts", "/a/b/bar.d.ts"],
@@ -269,7 +271,7 @@ declare module "url" {
}
`
};
return ts.tscWatch.createWatchedSystem([root, file, ts.tscWatch.libFile], { currentDirectory: "/a/b" });
return createWatchedSystem([root, file, libFile], { currentDirectory: "/a/b" });
},
changes: [
{
@@ -281,30 +283,30 @@ declare module "fs" {
}
}
`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "works when reusing program with files from external library",
commandLineArgs: ["--w", "-p", "/a/b/projects/myProject/src"],
sys: () => {
const configDir = "/a/b/projects/myProject/src/";
const file1: ts.tscWatch.File = {
const file1: File = {
path: configDir + "file1.ts",
content: 'import module1 = require("module1");\nmodule1("hello");'
};
const file2: ts.tscWatch.File = {
const file2: File = {
path: configDir + "file2.ts",
content: 'import module11 = require("module1");\nmodule11("hello");'
};
const module1: ts.tscWatch.File = {
const module1: File = {
path: "/a/b/projects/myProject/node_modules/module1/index.js",
content: "module.exports = options => { return options.toString(); }"
};
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: configDir + "tsconfig.json",
content: JSON.stringify({
compilerOptions: {
@@ -316,67 +318,67 @@ declare module "fs" {
}
})
};
return ts.tscWatch.createWatchedSystem([file1, file2, module1, ts.tscWatch.libFile, configFile], { currentDirectory: "/a/b/projects/myProject/" });
return createWatchedSystem([file1, file2, module1, libFile, configFile], { currentDirectory: "/a/b/projects/myProject/" });
},
changes: [
{
caption: "Add new line to file1",
change: sys => sys.appendFile("/a/b/projects/myProject/src/file1.ts", "\n;"),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "works when renaming node_modules folder that already contains @types folder",
commandLineArgs: ["--w", `${ts.tscWatch.projectRoot}/a.ts`],
commandLineArgs: ["--w", `/user/username/projects/myproject/a.ts`],
sys: () => {
const file: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/a.ts`,
const file: File = {
path: `/user/username/projects/myproject/a.ts`,
content: `import * as q from "qqq";`
};
const module: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules2/@types/qqq/index.d.ts`,
const module: File = {
path: `/user/username/projects/myproject/node_modules2/@types/qqq/index.d.ts`,
content: "export {}"
};
return ts.tscWatch.createWatchedSystem([file, ts.tscWatch.libFile, module], { currentDirectory: ts.tscWatch.projectRoot });
return createWatchedSystem([file, libFile, module], { currentDirectory: "/user/username/projects/myproject" });
},
changes: [
{
caption: "npm install",
change: sys => sys.renameFolder(`${ts.tscWatch.projectRoot}/node_modules2`, `${ts.tscWatch.projectRoot}/node_modules`),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
change: sys => sys.renameFolder(`/user/username/projects/myproject/node_modules2`, `/user/username/projects/myproject/node_modules`),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}
]
});
describe("ignores files/folder changes in node_modules that start with '.'", () => {
function verifyIgnore(subScenario: string, commandLineArgs: readonly string[]) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `ignores changes in node_modules that start with dot/${subScenario}`,
commandLineArgs,
sys: () => {
const file1: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/test.ts`,
const file1: File = {
path: `/user/username/projects/myproject/test.ts`,
content: `import { x } from "somemodule";`
};
const file2: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/somemodule/index.d.ts`,
const file2: File = {
path: `/user/username/projects/myproject/node_modules/somemodule/index.d.ts`,
content: `export const x = 10;`
};
const config: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: "{}"
};
return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, file1, file2, config]);
return createWatchedSystem([libFile, file1, file2, config]);
},
changes: [
{
caption: "npm install file and folder that start with '.'",
change: sys => sys.ensureFileOrFolder({
path: `${ts.tscWatch.projectRoot}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`,
path: `/user/username/projects/myproject/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`,
content: JSON.stringify({ something: 10 })
}),
timeouts: sys => sys.checkTimeoutQueueLength(0),
@@ -384,21 +386,21 @@ declare module "fs" {
]
});
}
verifyIgnore("watch without configFile", ["--w", `${ts.tscWatch.projectRoot}/test.ts`]);
verifyIgnore("watch with configFile", ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`]);
verifyIgnore("watch without configFile", ["--w", `/user/username/projects/myproject/test.ts`]);
verifyIgnore("watch with configFile", ["--w", "-p", `/user/username/projects/myproject/tsconfig.json`]);
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "when types in compiler option are global and installed at later point",
commandLineArgs: ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`],
commandLineArgs: ["--w", "-p", `/user/username/projects/myproject/tsconfig.json`],
sys: () => {
const app: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/lib/app.ts`,
const app: File = {
path: `/user/username/projects/myproject/lib/app.ts`,
content: `myapp.component("hello");`
};
const tsconfig: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
module: "none",
@@ -406,21 +408,21 @@ declare module "fs" {
}
})
};
return ts.tscWatch.createWatchedSystem([app, tsconfig, ts.tscWatch.libFile]);
return createWatchedSystem([app, tsconfig, libFile]);
},
changes: [
{
caption: "npm install ts-types",
change: sys => {
sys.ensureFileOrFolder({
path: `${ts.tscWatch.projectRoot}/node_modules/@myapp/ts-types/package.json`,
path: `/user/username/projects/myproject/node_modules/@myapp/ts-types/package.json`,
content: JSON.stringify({
version: "1.65.1",
types: "types/somefile.define.d.ts"
})
});
sys.ensureFileOrFolder({
path: `${ts.tscWatch.projectRoot}/node_modules/@myapp/ts-types/types/somefile.define.d.ts`,
path: `/user/username/projects/myproject/node_modules/@myapp/ts-types/types/somefile.define.d.ts`,
content: `
declare namespace myapp {
function component(str: string): number;
@@ -445,63 +447,63 @@ declare namespace myapp {
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "with modules linked to sibling folder",
commandLineArgs: ["-w"],
sys: () => {
const mainPackageRoot = `${ts.tscWatch.projectRoot}/main`;
const linkedPackageRoot = `${ts.tscWatch.projectRoot}/linked-package`;
const mainFile: ts.tscWatch.File = {
const mainPackageRoot = `/user/username/projects/myproject/main`;
const linkedPackageRoot = `/user/username/projects/myproject/linked-package`;
const mainFile: File = {
path: `${mainPackageRoot}/index.ts`,
content: "import { Foo } from '@scoped/linked-package'"
};
const config: ts.tscWatch.File = {
const config: File = {
path: `${mainPackageRoot}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." },
files: ["index.ts"]
})
};
const linkedPackageInMain: ts.tscWatch.SymLink = {
const linkedPackageInMain: SymLink = {
path: `${mainPackageRoot}/node_modules/@scoped/linked-package`,
symLink: `${linkedPackageRoot}`
};
const linkedPackageJson: ts.tscWatch.File = {
const linkedPackageJson: File = {
path: `${linkedPackageRoot}/package.json`,
content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" })
};
const linkedPackageIndex: ts.tscWatch.File = {
const linkedPackageIndex: File = {
path: `${linkedPackageRoot}/dist/index.d.ts`,
content: "export * from './other';"
};
const linkedPackageOther: ts.tscWatch.File = {
const linkedPackageOther: File = {
path: `${linkedPackageRoot}/dist/other.d.ts`,
content: 'export declare const Foo = "BAR";'
};
const files = [ts.tscWatch.libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther];
return ts.tscWatch.createWatchedSystem(files, { currentDirectory: mainPackageRoot });
const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther];
return createWatchedSystem(files, { currentDirectory: mainPackageRoot });
},
changes: ts.emptyArray
});
describe("works when installing something in node_modules or @types when there is no notification from fs for index file", () => {
function getNodeAtTypes() {
const nodeAtTypesIndex: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/@types/node/index.d.ts`,
const nodeAtTypesIndex: File = {
path: `/user/username/projects/myproject/node_modules/@types/node/index.d.ts`,
content: `/// <reference path="base.d.ts" />`
};
const nodeAtTypesBase: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/@types/node/base.d.ts`,
const nodeAtTypesBase: File = {
path: `/user/username/projects/myproject/node_modules/@types/node/base.d.ts`,
content: `// Base definitions for all NodeJS modules that are not specific to any version of TypeScript:
/// <reference path="ts3.6/base.d.ts" />`
};
const nodeAtTypes36Base: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/@types/node/ts3.6/base.d.ts`,
const nodeAtTypes36Base: File = {
path: `/user/username/projects/myproject/node_modules/@types/node/ts3.6/base.d.ts`,
content: `/// <reference path="../globals.d.ts" />`
};
const nodeAtTypesGlobals: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/@types/node/globals.d.ts`,
const nodeAtTypesGlobals: File = {
path: `/user/username/projects/myproject/node_modules/@types/node/globals.d.ts`,
content: `declare var process: NodeJS.Process;
declare namespace NodeJS {
interface Process {
@@ -511,40 +513,40 @@ declare namespace NodeJS {
};
return { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals };
}
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "works when installing something in node_modules or @types when there is no notification from fs for index file",
commandLineArgs: ["--w", `--extendedDiagnostics`],
sys: () => {
const file: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/worker.ts`,
const file: File = {
path: `/user/username/projects/myproject/worker.ts`,
content: `process.on("uncaughtException");`
};
const tsconfig: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: "{}"
};
const { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals } = getNodeAtTypes();
return ts.tscWatch.createWatchedSystem([file, ts.tscWatch.libFile, tsconfig, nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals], { currentDirectory: ts.tscWatch.projectRoot });
return createWatchedSystem([file, libFile, tsconfig, nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals], { currentDirectory: "/user/username/projects/myproject" });
},
changes: [
{
caption: "npm ci step one: remove all node_modules files",
change: sys => sys.deleteFolder(`${ts.tscWatch.projectRoot}/node_modules/@types`, /*recursive*/ true),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
change: sys => sys.deleteFolder(`/user/username/projects/myproject/node_modules/@types`, /*recursive*/ true),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: `npm ci step two: create atTypes but something else in the @types folder`,
change: sys => sys.ensureFileOrFolder({
path: `${ts.tscWatch.projectRoot}/node_modules/@types/mocha/index.d.ts`,
path: `/user/username/projects/myproject/node_modules/@types/mocha/index.d.ts`,
content: `export const foo = 10;`
}),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks
timeouts: sys => sys.runQueuedTimeoutCallbacks()
},
{
caption: `npm ci step three: create atTypes node folder`,
change: sys => sys.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/node_modules/@types/node` }),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks
change: sys => sys.ensureFileOrFolder({ path: `/user/username/projects/myproject/node_modules/@types/node` }),
timeouts: sys => sys.runQueuedTimeoutCallbacks()
},
{
caption: `npm ci step four: create atTypes write all the files but dont invoke watcher for index.d.ts`,

View File

@@ -1,29 +1,31 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, FileOrFolderOrSymLink, getTsBuildProjectFile, libFile, SymLink } from "../virtualFileSystemWithWatch";
import { libContent } from "../tsc/helpers";
import { createBaseline, createWatchCompilerHostOfConfigFileForBaseline, runWatchBaseline, solutionBuildWithBaseline } from "./helpers";
import getFileFromProject = ts.TestFSWithWatch.getTsBuildProjectFile;
describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedirect", () => {
interface VerifyWatchInput {
files: readonly ts.TestFSWithWatch.FileOrFolderOrSymLink[];
files: readonly FileOrFolderOrSymLink[];
config: string;
subScenario: string;
}
function verifyWatch({ files, config, subScenario }: VerifyWatchInput, alreadyBuilt: boolean) {
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(
ts.tscWatch.createWatchedSystem(files),
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(
createWatchedSystem(files),
alreadyBuilt ? (sys, originalRead) => {
ts.tscWatch.solutionBuildWithBaseline(sys, [config], originalRead);
solutionBuildWithBaseline(sys, [config], originalRead);
sys.clearOutput();
} : undefined
);
const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config,
system: sys,
cb,
});
host.useSourceOfProjectReferenceRedirect = ts.returnTrue;
const watch = ts.createWatchProgram(host);
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "sourceOfProjectReferenceRedirect",
subScenario: `${subScenario}${alreadyBuilt ? " when solution is already built" : ""}`,
commandLineArgs: ["--w", "--p", config],
@@ -48,15 +50,15 @@ describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedire
describe("with simple project", () => {
verifyScenario(() => {
const baseConfig = getFileFromProject("demo", "tsconfig-base.json");
const coreTs = getFileFromProject("demo", "core/utilities.ts");
const coreConfig = getFileFromProject("demo", "core/tsconfig.json");
const animalTs = getFileFromProject("demo", "animals/animal.ts");
const dogTs = getFileFromProject("demo", "animals/dog.ts");
const indexTs = getFileFromProject("demo", "animals/index.ts");
const animalsConfig = getFileFromProject("demo", "animals/tsconfig.json");
const baseConfig = getTsBuildProjectFile("demo", "tsconfig-base.json");
const coreTs = getTsBuildProjectFile("demo", "core/utilities.ts");
const coreConfig = getTsBuildProjectFile("demo", "core/tsconfig.json");
const animalTs = getTsBuildProjectFile("demo", "animals/animal.ts");
const dogTs = getTsBuildProjectFile("demo", "animals/dog.ts");
const indexTs = getTsBuildProjectFile("demo", "animals/index.ts");
const animalsConfig = getTsBuildProjectFile("demo", "animals/tsconfig.json");
return {
files: [{ path: ts.tscWatch.libFile.path, content: ts.libContent }, baseConfig, coreTs, coreConfig, animalTs, dogTs, indexTs, animalsConfig],
files: [{ path: libFile.path, content: libContent }, baseConfig, coreTs, coreConfig, animalTs, dogTs, indexTs, animalsConfig],
config: animalsConfig.path,
subScenario: "with simple project"
};
@@ -65,11 +67,11 @@ describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedire
describe("when references are monorepo like with symlinks", () => {
interface Packages {
bPackageJson: ts.tscWatch.File;
aTest: ts.tscWatch.File;
bFoo: ts.tscWatch.File;
bBar: ts.tscWatch.File;
bSymlink: ts.tscWatch.SymLink;
bPackageJson: File;
aTest: File;
bFoo: File;
bBar: File;
bSymlink: SymLink;
subScenario: string;
}
function verifySymlinkScenario(packages: () => Packages) {
@@ -87,16 +89,16 @@ describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedire
const aConfig = config("A", extraOptions, ["../B"]);
const bConfig = config("B", extraOptions);
return {
files: [ts.tscWatch.libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink],
files: [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink],
config: aConfig.path,
subScenario: `${subScenario}${extraOptions.preserveSymlinks ? " with preserveSymlinks" : ""}`
};
});
}
function config(packageName: string, extraOptions: ts.CompilerOptions, references?: string[]): ts.tscWatch.File {
function config(packageName: string, extraOptions: ts.CompilerOptions, references?: string[]): File {
return {
path: `${ts.tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`,
path: `/user/username/projects/myproject/packages/${packageName}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "lib",
@@ -110,9 +112,9 @@ describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedire
};
}
function file(packageName: string, fileName: string, content: string): ts.tscWatch.File {
function file(packageName: string, fileName: string, content: string): File {
return {
path: `${ts.tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`,
path: `/user/username/projects/myproject/packages/${packageName}/src/${fileName}`,
content
};
}
@@ -121,7 +123,7 @@ describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedire
describe("when packageJson has types field", () => {
verifySymlinkScenario(() => ({
bPackageJson: {
path: `${ts.tscWatch.projectRoot}/packages/B/package.json`,
path: `/user/username/projects/myproject/packages/B/package.json`,
content: JSON.stringify({
main: "lib/index.js",
types: "lib/index.d.ts"
@@ -135,8 +137,8 @@ bar();
bFoo: file("B", "index.ts", `export function foo() { }`),
bBar: file("B", "bar.ts", `export function bar() { }`),
bSymlink: {
path: `${ts.tscWatch.projectRoot}/node_modules/${scope}b`,
symLink: `${ts.tscWatch.projectRoot}/packages/B`
path: `/user/username/projects/myproject/node_modules/${scope}b`,
symLink: `/user/username/projects/myproject/packages/B`
},
subScenario: `when packageJson has types field${scope ? " with scoped package" : ""}`
}));
@@ -145,7 +147,7 @@ bar();
describe("when referencing file from subFolder", () => {
verifySymlinkScenario(() => ({
bPackageJson: {
path: `${ts.tscWatch.projectRoot}/packages/B/package.json`,
path: `/user/username/projects/myproject/packages/B/package.json`,
content: "{}"
},
aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo';
@@ -156,8 +158,8 @@ bar();
bFoo: file("B", "foo.ts", `export function foo() { }`),
bBar: file("B", "bar/foo.ts", `export function bar() { }`),
bSymlink: {
path: `${ts.tscWatch.projectRoot}/node_modules/${scope}b`,
symLink: `${ts.tscWatch.projectRoot}/packages/B`
path: `/user/username/projects/myproject/node_modules/${scope}b`,
symLink: `/user/username/projects/myproject/packages/B`
},
subScenario: `when referencing file from subFolder${scope ? " with scoped package" : ""}`
}));

View File

@@ -1,5 +1,8 @@
import * as ts from "../../_namespaces/ts";
import * as Harness from "../../_namespaces/Harness";
import { createWatchedSystem, File, libFile, TestServerHostTrackingWrittenFiles } from "../virtualFileSystemWithWatch";
import { applyChange, createBaseline, createWatchCompilerHostOfConfigFileForBaseline, runWatchBaseline, watchBaseline } from "./helpers";
import { commandLineCallbacks } from "../tsc/helpers";
describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => {
it("verify that module resolution with json extension works when returned without extension", () => {
@@ -7,23 +10,23 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu
compilerOptions: { module: "commonjs", resolveJsonModule: true },
files: ["index.ts"]
};
const mainFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/index.ts`,
const mainFile: File = {
path: `/user/username/projects/myproject/index.ts`,
content: "import settings from './settings.json';"
};
const config: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify(configFileJson)
};
const settingsJson: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/settings.json`,
const settingsJson: File = {
path: `/user/username/projects/myproject/settings.json`,
content: JSON.stringify({ content: "Print this" })
};
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, mainFile, config, settingsJson],
{ currentDirectory: ts.tscWatch.projectRoot }),
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(
[libFile, mainFile, config, settingsJson],
{ currentDirectory: "/user/username/projects/myproject" }),
);
const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
system: sys,
cb,
@@ -39,7 +42,7 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu
};
});
const watch = ts.createWatchProgram(host);
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchApi",
subScenario: "verify that module resolution with json extension works when returned without extension",
commandLineArgs: ["--w", "--p", config.path],
@@ -55,26 +58,26 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu
describe("hasInvalidatedResolutions", () => {
function verifyWatch(subScenario: string, implementHasInvalidatedResolution: boolean) {
it(subScenario, () => {
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem({
[`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem({
[`/user/username/projects/myproject/tsconfig.json`]: JSON.stringify({
compilerOptions: { traceResolution: true, extendedDiagnostics: true },
files: ["main.ts"]
}),
[`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./other";`,
[`${ts.tscWatch.projectRoot}/other.d.ts`]: "export function foo(): void;",
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
}, { currentDirectory: ts.tscWatch.projectRoot }));
const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
configFileName: `${ts.tscWatch.projectRoot}/tsconfig.json`,
[`/user/username/projects/myproject/main.ts`]: `import { foo } from "./other";`,
[`/user/username/projects/myproject/other.d.ts`]: "export function foo(): void;",
[libFile.path]: libFile.content,
}, { currentDirectory: "/user/username/projects/myproject" }));
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: `/user/username/projects/myproject/tsconfig.json`,
system: sys,
cb,
});
host.resolveModuleNames = (moduleNames, containingFile, _reusedNames, _redirectedReference, options) =>
moduleNames.map(m => ts.resolveModuleName(m, containingFile, options, host).resolvedModule);
// Invalidate resolutions only when ts file is created
if (implementHasInvalidatedResolution) host.hasInvalidatedResolutions = () => sys.fileExists(`${ts.tscWatch.projectRoot}/other.ts`);
if (implementHasInvalidatedResolution) host.hasInvalidatedResolutions = () => sys.fileExists(`/user/username/projects/myproject/other.ts`);
const watch = ts.createWatchProgram(host);
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchApi",
subScenario,
commandLineArgs: ["--w"],
@@ -85,19 +88,19 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu
changes: [
{
caption: "write other with same contents",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/other.d.ts`, ""),
change: sys => sys.appendFile(`/user/username/projects/myproject/other.d.ts`, ""),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "change other file",
change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/other.d.ts`, "export function bar(): void;"),
change: sys => sys.appendFile(`/user/username/projects/myproject/other.d.ts`, "export function bar(): void;"),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "write other with same contents but write ts file",
change: sys => {
sys.appendFile(`${ts.tscWatch.projectRoot}/other.d.ts`, "");
sys.writeFile(`${ts.tscWatch.projectRoot}/other.ts`, "export function foo() {}");
sys.appendFile(`/user/username/projects/myproject/other.d.ts`, "");
sys.writeFile(`/user/username/projects/myproject/other.ts`, "export function foo() {}");
},
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
@@ -113,22 +116,22 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu
describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to watch status reporter", () => {
it("verify that the error count is correctly passed down to the watch status reporter", () => {
const config: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { module: "commonjs" },
files: ["index.ts"]
})
};
const mainFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/index.ts`,
const mainFile: File = {
path: `/user/username/projects/myproject/index.ts`,
content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}"
};
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem(
[ts.tscWatch.libFile, mainFile, config],
{ currentDirectory: ts.tscWatch.projectRoot }),
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(
[libFile, mainFile, config],
{ currentDirectory: "/user/username/projects/myproject" }),
);
const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
system: sys,
cb,
@@ -141,7 +144,7 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to wat
};
const watch = ts.createWatchProgram(host);
assert.equal(watchedErrorCount, 2, "The error count was expected to be 2 for the file change");
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchApi",
subScenario: "verify that the error count is correctly passed down to the watch status reporter",
commandLineArgs: ["--w", "--p", config.path],
@@ -157,16 +160,16 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to wat
describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement setTimeout or clearTimeout", () => {
it("verifies that getProgram gets updated program if new file is added to the program", () => {
const config: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: "{}"
};
const mainFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/main.ts`,
const mainFile: File = {
path: `/user/username/projects/myproject/main.ts`,
content: "const x = 10;"
};
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([config, mainFile, ts.tscWatch.libFile]));
const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([config, mainFile, libFile]));
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
system: sys,
cb,
@@ -174,7 +177,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement s
host.setTimeout = undefined;
host.clearTimeout = undefined;
const watch = ts.createWatchProgram(host);
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchApi",
subScenario: "without timesouts on host program gets updated",
commandLineArgs: ["--w", "--p", config.path],
@@ -184,7 +187,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement s
getPrograms,
changes: [{
caption: "Write a file",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/bar.ts`, "const y =10;"),
change: sys => sys.writeFile(`/user/username/projects/myproject/bar.ts`, "const y =10;"),
timeouts: sys => {
sys.checkTimeoutQueueLength(0);
watch.getProgram();
@@ -197,22 +200,22 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement s
describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExtensions to process", () => {
it("verifies that extraFileExtensions are supported to get the program with other extensions", () => {
const config: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: "{}"
};
const mainFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/main.ts`,
const mainFile: File = {
path: `/user/username/projects/myproject/main.ts`,
content: "const x = 10;"
};
const otherFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/other.vue`,
const otherFile: File = {
path: `/user/username/projects/myproject/other.vue`,
content: ""
};
const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(
ts.tscWatch.createWatchedSystem([config, mainFile, otherFile, ts.tscWatch.libFile])
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(
createWatchedSystem([config, mainFile, otherFile, libFile])
);
const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
optionsToExtend: { allowNonTsExtensions: true },
extraFileExtensions: [{ extension: ".vue", isMixedContent: true, scriptKind: ts.ScriptKind.Deferred }],
@@ -220,7 +223,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExt
cb,
});
const watch = ts.createWatchProgram(host);
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchApi",
subScenario: "extraFileExtensions are supported",
commandLineArgs: ["--w", "--p", config.path],
@@ -230,8 +233,8 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExt
getPrograms,
changes: [{
caption: "Write a file",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/other2.vue`, otherFile.content),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
change: sys => sys.writeFile(`/user/username/projects/myproject/other2.vue`, otherFile.content),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
}],
watchOrSolution: watch
});
@@ -240,20 +243,20 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExt
describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => {
function createSystem(configText: string, mainText: string) {
const config: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: configText
};
const mainFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/main.ts`,
const mainFile: File = {
path: `/user/username/projects/myproject/main.ts`,
content: mainText
};
const otherFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/other.ts`,
const otherFile: File = {
path: `/user/username/projects/myproject/other.ts`,
content: "export const y = 10;"
};
return {
...ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([config, mainFile, otherFile, ts.tscWatch.libFile])),
...createBaseline(createWatchedSystem([config, mainFile, otherFile, libFile])),
config,
mainFile,
otherFile,
@@ -262,15 +265,15 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
function createWatch<T extends ts.BuilderProgram>(
baseline: string[],
config: ts.tscWatch.File,
sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles,
config: File,
sys: TestServerHostTrackingWrittenFiles,
createProgram: ts.CreateProgram<T>,
optionsToExtend?: ts.CompilerOptions,
) {
const { cb, getPrograms } = ts.commandLineCallbacks(sys);
const { cb, getPrograms } = commandLineCallbacks(sys);
baseline.push(`tsc --w${optionsToExtend?.noEmit ? " --noEmit" : ""}`);
const oldSnap = sys.snap();
const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline<T>({
const host = createWatchCompilerHostOfConfigFileForBaseline<T>({
configFileName: config.path,
optionsToExtend,
createProgram,
@@ -278,7 +281,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
cb,
});
const watch = ts.createWatchProgram(host);
ts.tscWatch.watchBaseline({
watchBaseline({
baseline,
getPrograms,
oldPrograms: ts.emptyArray,
@@ -290,7 +293,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
function verifyOutputs(baseline: string[], sys: ts.System, emitSys: ts.System) {
baseline.push("Checking if output is same as EmitAndSemanticDiagnosticsBuilderProgram::");
for (const output of [`${ts.tscWatch.projectRoot}/main.js`, `${ts.tscWatch.projectRoot}/main.d.ts`, `${ts.tscWatch.projectRoot}/other.js`, `${ts.tscWatch.projectRoot}/other.d.ts`, `${ts.tscWatch.projectRoot}/tsconfig.tsbuildinfo`]) {
for (const output of [`/user/username/projects/myproject/main.js`, `/user/username/projects/myproject/main.d.ts`, `/user/username/projects/myproject/other.js`, `/user/username/projects/myproject/other.d.ts`, `/user/username/projects/myproject/tsconfig.tsbuildinfo`]) {
baseline.push(`Output file text for ${output} is same:: ${sys.readFile(output) === emitSys.readFile(output)}`);
}
baseline.push("");
@@ -305,22 +308,22 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
function applyChangeForBuilderTest(
baseline: string[],
emitBaseline: string[],
sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles,
emitSys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles,
change: (sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void,
sys: TestServerHostTrackingWrittenFiles,
emitSys: TestServerHostTrackingWrittenFiles,
change: (sys: TestServerHostTrackingWrittenFiles) => void,
caption: string
) {
// Change file
ts.tscWatch.applyChange(sys, baseline, change, caption);
ts.tscWatch.applyChange(emitSys, emitBaseline, change, caption);
applyChange(sys, baseline, change, caption);
applyChange(emitSys, emitBaseline, change, caption);
}
function verifyBuilder<T extends ts.BuilderProgram>(
baseline: string[],
emitBaseline: string[],
config: ts.tscWatch.File,
sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles,
emitSys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles,
config: File,
sys: TestServerHostTrackingWrittenFiles,
emitSys: TestServerHostTrackingWrittenFiles,
createProgram: ts.CreateProgram<T>,
optionsToExtend?: ts.CompilerOptions) {
createWatch(baseline, config, sys, createProgram, optionsToExtend);
@@ -330,7 +333,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => {
const { sys, baseline, oldSnap, cb, getPrograms, config, mainFile } = createSystem("{}", "export const x = 10;");
const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
const host = createWatchCompilerHostOfConfigFileForBaseline({
configFileName: config.path,
optionsToExtend: { noEmit: true },
createProgram: ts.createSemanticDiagnosticsBuilderProgram,
@@ -338,7 +341,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
cb,
});
const watch = ts.createWatchProgram(host);
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchApi",
subScenario: "verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram",
commandLineArgs: ["--w", "--p", config.path],
@@ -349,7 +352,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
changes: [{
caption: "Modify a file",
change: sys => sys.appendFile(mainFile.path, "\n// SomeComment"),
timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
}],
watchOrSolution: watch
});
@@ -441,9 +444,9 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
createWatch(baseline, config, sys, ts.createSemanticDiagnosticsBuilderProgram);
// Fix error and emit
ts.tscWatch.applyChange(sys, baseline, sys => sys.writeFile(mainFile.path, "export const x = 10;"), "Fix error");
applyChange(sys, baseline, sys => sys.writeFile(mainFile.path, "export const x = 10;"), "Fix error");
const { cb, getPrograms } = ts.commandLineCallbacks(sys);
const { cb, getPrograms } = commandLineCallbacks(sys);
const oldSnap = sys.snap();
const reportDiagnostic = ts.createDiagnosticReporter(sys, /*pretty*/ true);
const reportWatchStatus = ts.createWatchStatusReporter(sys, /*pretty*/ true);
@@ -468,7 +471,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
cb(program);
};
ts.createWatchProgram(host);
ts.tscWatch.watchBaseline({
watchBaseline({
baseline,
getPrograms,
oldPrograms: ts.emptyArray,
@@ -481,8 +484,8 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD
describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implemented", () => {
function setup(useSourceOfProjectReferenceRedirect?: () => boolean) {
const config1: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project1/tsconfig.json`,
const config1: File = {
path: `/user/username/projects/myproject/projects/project1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
module: "none",
@@ -491,16 +494,16 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem
exclude: ["temp"]
})
};
const class1: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project1/class1.ts`,
const class1: File = {
path: `/user/username/projects/myproject/projects/project1/class1.ts`,
content: `class class1 {}`
};
const class1Dts: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project1/class1.d.ts`,
const class1Dts: File = {
path: `/user/username/projects/myproject/projects/project1/class1.d.ts`,
content: `declare class class1 {}`
};
const config2: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project2/tsconfig.json`,
const config2: File = {
path: `/user/username/projects/myproject/projects/project2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
module: "none",
@@ -511,13 +514,13 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem
]
})
};
const class2: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/projects/project2/class2.ts`,
const class2: File = {
path: `/user/username/projects/myproject/projects/project2/class2.ts`,
content: `class class2 {}`
};
const system = ts.tscWatch.createWatchedSystem([config1, class1, class1Dts, config2, class2, ts.tscWatch.libFile]);
const baseline = ts.tscWatch.createBaseline(system);
const compilerHost = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
const system = createWatchedSystem([config1, class1, class1Dts, config2, class2, libFile]);
const baseline = createBaseline(system);
const compilerHost = createWatchCompilerHostOfConfigFileForBaseline({
cb: baseline.cb,
system,
configFileName: config2.path,
@@ -543,7 +546,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem
it("when new file is added to the referenced project with host implementing getParsedCommandLine", () => {
const { watch, baseline, config2, calledGetParsedCommandLine } = setup(ts.returnTrue);
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchApi",
subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine",
commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"],
@@ -553,18 +556,18 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem
caption: "Add class3 to project1",
change: sys => {
calledGetParsedCommandLine.clear();
sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.ts`, `class class3 {}`);
sys.writeFile(`/user/username/projects/myproject/projects/project1/class3.ts`, `class class3 {}`);
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Add excluded file to project1",
change: sys => sys.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }),
change: sys => sys.ensureFileOrFolder({ path: `/user/username/projects/myproject/projects/project1/temp/file.d.ts`, content: `declare class file {}` }),
timeouts: sys => sys.checkTimeoutQueueLength(0),
},
{
caption: "Add output of class3",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`),
change: sys => sys.writeFile(`/user/username/projects/myproject/projects/project1/class3.d.ts`, `declare class class3 {}`),
timeouts: sys => sys.checkTimeoutQueueLength(0),
},
],
@@ -574,7 +577,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem
it("when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect", () => {
const { watch, baseline, config2, calledGetParsedCommandLine } = setup();
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchApi",
subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect",
commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"],
@@ -584,29 +587,29 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem
caption: "Add class3 to project1",
change: sys => {
calledGetParsedCommandLine.clear();
sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.ts`, `class class3 {}`);
sys.writeFile(`/user/username/projects/myproject/projects/project1/class3.ts`, `class class3 {}`);
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Add class3 output to project1",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
change: sys => sys.writeFile(`/user/username/projects/myproject/projects/project1/class3.d.ts`, `declare class class3 {}`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Add excluded file to project1",
change: sys => sys.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }),
change: sys => sys.ensureFileOrFolder({ path: `/user/username/projects/myproject/projects/project1/temp/file.d.ts`, content: `declare class file {}` }),
timeouts: sys => sys.checkTimeoutQueueLength(0),
},
{
caption: "Delete output of class3",
change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
change: sys => sys.deleteFile(`/user/username/projects/myproject/projects/project1/class3.d.ts`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Add output of class3",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
change: sys => sys.writeFile(`/user/username/projects/myproject/projects/project1/class3.d.ts`, `declare class class3 {}`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
],
watchOrSolution: watch
@@ -617,27 +620,27 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem
describe("unittests:: tsc-watch:: watchAPI:: when builder emit occurs with emitOnlyDtsFiles", () => {
function verify(subScenario: string, outFile?: string) {
it(subScenario, () => {
const system = ts.tscWatch.createWatchedSystem({
[`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({
const system = createWatchedSystem({
[`/user/username/projects/myproject/tsconfig.json`]: JSON.stringify({
compilerOptions: { composite: true, noEmitOnError: true, module: "amd", outFile },
files: ["a.ts", "b.ts"],
}),
[`${ts.tscWatch.projectRoot}/a.ts`]: "export const x = 10;",
[`${ts.tscWatch.projectRoot}/b.ts`]: "export const y: 10 = 20;",
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
}, { currentDirectory: ts.tscWatch.projectRoot });
const baseline = ts.tscWatch.createBaseline(system);
const compilerHost = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({
[`/user/username/projects/myproject/a.ts`]: "export const x = 10;",
[`/user/username/projects/myproject/b.ts`]: "export const y: 10 = 20;",
[libFile.path]: libFile.content,
}, { currentDirectory: "/user/username/projects/myproject" });
const baseline = createBaseline(system);
const compilerHost = createWatchCompilerHostOfConfigFileForBaseline({
cb: baseline.cb,
system,
configFileName: `${ts.tscWatch.projectRoot}/tsconfig.json`,
configFileName: `/user/username/projects/myproject/tsconfig.json`,
optionsToExtend: { extendedDiagnostics: true }
});
const originalEmitProgram = compilerHost.afterProgramCreate;
compilerHost.afterProgramCreate = myAfterProgramCreate;
let callFullEmit = true;
const watch = ts.createWatchProgram(compilerHost);
ts.tscWatch.runWatchBaseline({
runWatchBaseline({
scenario: "watchApi",
subScenario,
commandLineArgs: ["--w", "--extendedDiagnostics"],
@@ -646,10 +649,10 @@ describe("unittests:: tsc-watch:: watchAPI:: when builder emit occurs with emitO
{
caption: "Fix error but run emit with emitOnlyDts",
change: sys => {
sys.writeFile(`${ts.tscWatch.projectRoot}/b.ts`, `export const y = 10;`);
sys.writeFile(`/user/username/projects/myproject/b.ts`, `export const y = 10;`);
callFullEmit = false;
},
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Emit with emitOnlyDts shouldnt emit anything",

View File

@@ -1,21 +1,22 @@
import * as ts from "../../_namespaces/ts";
import { createWatchedSystem, File, libFile, SymLink, TestServerHost, Tsc_WatchDirectory, Tsc_WatchFile } from "../virtualFileSystemWithWatch";
import { commonFile1, commonFile2, noopChange, verifyTscWatch } from "./helpers";
import Tsc_WatchDirectory = ts.TestFSWithWatch.Tsc_WatchDirectory;
describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => {
const scenario = "watchEnvironment";
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchFile/using dynamic priority polling",
commandLineArgs: ["--w", `/a/username/project/typescript.ts`],
sys: () => {
const projectFolder = "/a/username/project";
const file1: ts.tscWatch.File = {
const file1: File = {
path: `${projectFolder}/typescript.ts`,
content: "var z = 10;"
};
const environmentVariables = new ts.Map<string, string>();
environmentVariables.set("TSC_WATCHFILE", ts.TestFSWithWatch.Tsc_WatchFile.DynamicPolling);
return ts.tscWatch.createWatchedSystem([file1, ts.tscWatch.libFile], { environmentVariables });
environmentVariables.set("TSC_WATCHFILE", Tsc_WatchFile.DynamicPolling);
return createWatchedSystem([file1, libFile], { environmentVariables });
},
changes: [
{
@@ -37,7 +38,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
// Make a change to file
change: sys => sys.writeFile("/a/username/project/typescript.ts", "var zz30 = 100;"),
// During this timeout the file would be detected as unchanged
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Callbacks: medium priority + high priority queue and scheduled program update",
@@ -67,12 +68,12 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchFile/using fixed chunk size polling",
commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"],
sys: () => {
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({
watchOptions: {
@@ -80,8 +81,8 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
}
})
};
const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile];
return ts.tscWatch.createWatchedSystem(files);
const files = [libFile, commonFile1, commonFile2, configFile];
return createWatchedSystem(files);
},
changes: [
{
@@ -99,8 +100,8 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
{
caption: "Make change to file but should detect as changed and schedule program update",
// Make a change to file
change: sys => sys.writeFile(ts.tscWatch.commonFile1.path, "var zz30 = 100;"),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun,
change: sys => sys.writeFile(commonFile1.path, "var zz30 = 100;"),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Callbacks: queue and scheduled program update",
@@ -125,7 +126,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
function verifyRenamingFileInSubFolder(subScenario: string, tscWatchDirectory: Tsc_WatchDirectory) {
const projectFolder = "/a/username/project";
const projectSrcFolder = `${projectFolder}/src`;
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: `${projectFolder}/tsconfig.json`,
content: JSON.stringify({
watchOptions: {
@@ -133,19 +134,19 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
}
})
};
const file: ts.tscWatch.File = {
const file: File = {
path: `${projectSrcFolder}/file1.ts`,
content: ""
};
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `watchDirectories/${subScenario}`,
commandLineArgs: ["--w", "-p", configFile.path],
sys: () => {
const files = [file, configFile, ts.tscWatch.libFile];
const files = [file, configFile, libFile];
const environmentVariables = new ts.Map<string, string>();
environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory);
return ts.tscWatch.createWatchedSystem(files, { environmentVariables });
return createWatchedSystem(files, { environmentVariables });
},
changes: [
{
@@ -172,71 +173,71 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
verifyRenamingFileInSubFolder("uses non recursive dynamic polling when renaming file in subfolder", Tsc_WatchDirectory.DynamicPolling);
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchDirectories/when there are symlinks to folders in recursive folders",
commandLineArgs: ["--w"],
sys: () => {
const cwd = "/home/user/projects/myproject";
const file1: ts.tscWatch.File = {
const file1: File = {
path: `${cwd}/src/file.ts`,
content: `import * as a from "a"`
};
const tsconfig: ts.tscWatch.File = {
const tsconfig: File = {
path: `${cwd}/tsconfig.json`,
content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}`
};
const realA: ts.tscWatch.File = {
const realA: File = {
path: `${cwd}/node_modules/reala/index.d.ts`,
content: `export {}`
};
const realB: ts.tscWatch.File = {
const realB: File = {
path: `${cwd}/node_modules/realb/index.d.ts`,
content: `export {}`
};
const symLinkA: ts.tscWatch.SymLink = {
const symLinkA: SymLink = {
path: `${cwd}/node_modules/a`,
symLink: `${cwd}/node_modules/reala`
};
const symLinkB: ts.tscWatch.SymLink = {
const symLinkB: SymLink = {
path: `${cwd}/node_modules/b`,
symLink: `${cwd}/node_modules/realb`
};
const symLinkBInA: ts.tscWatch.SymLink = {
const symLinkBInA: SymLink = {
path: `${cwd}/node_modules/reala/node_modules/b`,
symLink: `${cwd}/node_modules/b`
};
const symLinkAInB: ts.tscWatch.SymLink = {
const symLinkAInB: SymLink = {
path: `${cwd}/node_modules/realb/node_modules/a`,
symLink: `${cwd}/node_modules/a`
};
const files = [ts.tscWatch.libFile, file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB];
const files = [libFile, file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB];
const environmentVariables = new ts.Map<string, string>();
environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
return ts.tscWatch.createWatchedSystem(files, { environmentVariables, currentDirectory: cwd });
return createWatchedSystem(files, { environmentVariables, currentDirectory: cwd });
},
changes: ts.emptyArray
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchDirectories/with non synchronous watch directory",
commandLineArgs: ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`],
commandLineArgs: ["--w", "-p", `/user/username/projects/myproject/tsconfig.json`],
sys: () => {
const configFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const configFile: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: "{}"
};
const file1: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/src/file1.ts`,
const file1: File = {
path: `/user/username/projects/myproject/src/file1.ts`,
content: `import { x } from "file2";`
};
const file2: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/file2/index.d.ts`,
const file2: File = {
path: `/user/username/projects/myproject/node_modules/file2/index.d.ts`,
content: `export const x = 10;`
};
const files = [ts.tscWatch.libFile, file1, file2, configFile];
return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true });
const files = [libFile, file1, file2, configFile];
return createWatchedSystem(files, { runWithoutRecursiveWatches: true });
},
changes: [
{
@@ -250,7 +251,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
{
caption: "Remove directory node_modules",
// Remove directory node_modules
change: sys => sys.deleteFolder(`${ts.tscWatch.projectRoot}/node_modules`, /*recursive*/ true),
change: sys => sys.deleteFolder(`/user/username/projects/myproject/node_modules`, /*recursive*/ true),
timeouts: sys => {
sys.checkTimeoutQueueLength(3); // 1. Failed lookup invalidation 2. For updating program and 3. for updating child watches
sys.runQueuedTimeoutCallbacks(sys.getNextTimeoutId() - 2); // Update program
@@ -269,17 +270,17 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
{
caption: "Start npm install",
// npm install
change: sys => sys.createDirectory(`${ts.tscWatch.projectRoot}/node_modules`),
change: sys => sys.createDirectory(`/user/username/projects/myproject/node_modules`),
timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure
},
{
caption: "npm install folder creation of file2",
change: sys => sys.createDirectory(`${ts.tscWatch.projectRoot}/node_modules/file2`),
change: sys => sys.createDirectory(`/user/username/projects/myproject/node_modules/file2`),
timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure
},
{
caption: "npm install index file in file2",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/node_modules/file2/index.d.ts`, `export const x = 10;`),
change: sys => sys.writeFile(`/user/username/projects/myproject/node_modules/file2/index.d.ts`, `export const x = 10;`),
timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure
},
{
@@ -309,32 +310,32 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
],
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchDirectories/with non synchronous watch directory with outDir and declaration enabled",
commandLineArgs: ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`],
commandLineArgs: ["--w", "-p", `/user/username/projects/myproject/tsconfig.json`],
sys: () => {
const configFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const configFile: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { outDir: "dist", declaration: true } })
};
const file1: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/src/file1.ts`,
const file1: File = {
path: `/user/username/projects/myproject/src/file1.ts`,
content: `import { x } from "file2";`
};
const file2: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/file2/index.d.ts`,
const file2: File = {
path: `/user/username/projects/myproject/node_modules/file2/index.d.ts`,
content: `export const x = 10;`
};
const files = [ts.tscWatch.libFile, file1, file2, configFile];
return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true });
const files = [libFile, file1, file2, configFile];
return createWatchedSystem(files, { runWithoutRecursiveWatches: true });
},
changes: [
ts.tscWatch.noopChange,
noopChange,
{
caption: "Add new file, should schedule and run timeout to update directory watcher",
change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/src/file3.ts`, `export const y = 10;`),
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Update the child watch
change: sys => sys.writeFile(`/user/username/projects/myproject/src/file3.ts`, `export const y = 10;`),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // Update the child watch
},
{
caption: "Actual program update to include new file",
@@ -344,37 +345,37 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
{
caption: "After program emit with new file, should schedule and run timeout to update directory watcher",
change: ts.noop,
timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Update the child watch
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), // Update the child watch
},
ts.tscWatch.noopChange,
noopChange,
],
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchDirectories/with non synchronous watch directory renaming a file",
commandLineArgs: ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`],
commandLineArgs: ["--w", "-p", `/user/username/projects/myproject/tsconfig.json`],
sys: () => {
const configFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const configFile: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { outDir: "dist" } })
};
const file1: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/src/file1.ts`,
const file1: File = {
path: `/user/username/projects/myproject/src/file1.ts`,
content: `import { x } from "./file2";`
};
const file2: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/src/file2.ts`,
const file2: File = {
path: `/user/username/projects/myproject/src/file2.ts`,
content: `export const x = 10;`
};
const files = [ts.tscWatch.libFile, file1, file2, configFile];
return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true });
const files = [libFile, file1, file2, configFile];
return createWatchedSystem(files, { runWithoutRecursiveWatches: true });
},
changes: [
ts.tscWatch.noopChange,
noopChange,
{
caption: "rename the file",
change: sys => sys.renameFile(`${ts.tscWatch.projectRoot}/src/file2.ts`, `${ts.tscWatch.projectRoot}/src/renamed.ts`),
change: sys => sys.renameFile(`/user/username/projects/myproject/src/file2.ts`, `/user/username/projects/myproject/src/renamed.ts`),
timeouts: sys => {
sys.checkTimeoutQueueLength(2); // 1. For updating program and 2. for updating child watches
sys.runQueuedTimeoutCallbacks(1); // Update program
@@ -395,12 +396,12 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
});
describe("handles watch compiler options", () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchOptions/with watchFile option",
commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"],
sys: () => {
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({
watchOptions: {
@@ -408,18 +409,18 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
}
})
};
const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile];
return ts.tscWatch.createWatchedSystem(files);
const files = [libFile, commonFile1, commonFile2, configFile];
return createWatchedSystem(files);
},
changes: ts.emptyArray
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchOptions/with watchDirectory option",
commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"],
sys: () => {
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({
watchOptions: {
@@ -427,18 +428,18 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
}
})
};
const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile];
return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true });
const files = [libFile, commonFile1, commonFile2, configFile];
return createWatchedSystem(files, { runWithoutRecursiveWatches: true });
},
changes: ts.emptyArray
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchOptions/with fallbackPolling option",
commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"],
sys: () => {
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({
watchOptions: {
@@ -446,59 +447,59 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
}
})
};
const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile];
return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
const files = [libFile, commonFile1, commonFile2, configFile];
return createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
},
changes: ts.emptyArray
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: "watchOptions/with watchFile as watch options to extend",
commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json", "--watchFile", "UseFsEvents"],
sys: () => {
const configFile: ts.tscWatch.File = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: "{}"
};
const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile];
return ts.tscWatch.createWatchedSystem(files);
const files = [libFile, commonFile1, commonFile2, configFile];
return createWatchedSystem(files);
},
changes: ts.emptyArray
});
describe("exclude options", () => {
function sys(watchOptions: ts.WatchOptions, runWithoutRecursiveWatches?: boolean): ts.tscWatch.WatchedSystem {
const configFile: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
function sys(watchOptions: ts.WatchOptions, runWithoutRecursiveWatches?: boolean): TestServerHost {
const configFile: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({ exclude: ["node_modules"], watchOptions })
};
const main: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/src/main.ts`,
const main: File = {
path: `/user/username/projects/myproject/src/main.ts`,
content: `import { foo } from "bar"; foo();`
};
const bar: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/bar/index.d.ts`,
const bar: File = {
path: `/user/username/projects/myproject/node_modules/bar/index.d.ts`,
content: `export { foo } from "./foo";`
};
const foo: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/bar/foo.d.ts`,
const foo: File = {
path: `/user/username/projects/myproject/node_modules/bar/foo.d.ts`,
content: `export function foo(): string;`
};
const fooBar: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/bar/fooBar.d.ts`,
const fooBar: File = {
path: `/user/username/projects/myproject/node_modules/bar/fooBar.d.ts`,
content: `export function fooBar(): string;`
};
const temp: ts.tscWatch.File = {
path: `${ts.tscWatch.projectRoot}/node_modules/bar/temp/index.d.ts`,
const temp: File = {
path: `/user/username/projects/myproject/node_modules/bar/temp/index.d.ts`,
content: "export function temp(): string;"
};
const files = [ts.tscWatch.libFile, main, bar, foo, fooBar, temp, configFile];
return ts.tscWatch.createWatchedSystem(files, { currentDirectory: ts.tscWatch.projectRoot, runWithoutRecursiveWatches });
const files = [libFile, main, bar, foo, fooBar, temp, configFile];
return createWatchedSystem(files, { currentDirectory: "/user/username/projects/myproject", runWithoutRecursiveWatches });
}
function verifyWorker(...additionalFlags: string[]) {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `watchOptions/with excludeFiles option${additionalFlags.join("")}`,
commandLineArgs: ["-w", ...additionalFlags],
@@ -506,13 +507,13 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
changes: [
{
caption: "Change foo",
change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/node_modules/bar/foo.d.ts`, "foo", "fooBar"),
change: sys => sys.replaceFileText(`/user/username/projects/myproject/node_modules/bar/foo.d.ts`, "foo", "fooBar"),
timeouts: sys => sys.checkTimeoutQueueLength(0),
}
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `watchOptions/with excludeDirectories option${additionalFlags.join("")}`,
commandLineArgs: ["-w", ...additionalFlags],
@@ -520,12 +521,12 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
changes: [
{
caption: "delete fooBar",
change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/node_modules/bar/fooBar.d.ts`),
change: sys => sys.deleteFile(`/user/username/projects/myproject/node_modules/bar/fooBar.d.ts`),
timeouts: sys => sys.checkTimeoutQueueLength(0), }
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `watchOptions/with excludeDirectories option with recursive directory watching${additionalFlags.join("")}`,
commandLineArgs: ["-w", ...additionalFlags],
@@ -541,7 +542,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
},
{
caption: "add new folder to temp",
change: sys => sys.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }),
change: sys => sys.ensureFileOrFolder({ path: `/user/username/projects/myproject/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }),
timeouts: sys => sys.checkTimeoutQueueLength(0),
}
]
@@ -553,28 +554,28 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
});
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `fsWatch/when using file watching thats when rename occurs when file is still on the disk`,
commandLineArgs: ["-w", "--extendedDiagnostics"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
{
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
[`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`,
[`${ts.tscWatch.projectRoot}/foo.ts`]: `export declare function foo(): string;`,
[`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({
[libFile.path]: libFile.content,
[`/user/username/projects/myproject/main.ts`]: `import { foo } from "./foo"; foo();`,
[`/user/username/projects/myproject/foo.ts`]: `export declare function foo(): string;`,
[`/user/username/projects/myproject/tsconfig.json`]: JSON.stringify({
watchOptions: { watchFile: "useFsEvents" },
files: ["foo.ts", "main.ts"]
}),
},
{ currentDirectory: ts.tscWatch.projectRoot, }
{ currentDirectory: "/user/username/projects/myproject", }
),
changes: [
{
caption: "Introduce error such that when callback happens file is already appeared",
// vm's wq generates this kind of event
// Skip delete event so inode changes but when the create's rename occurs file is on disk
change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.ts`, `export declare function foo2(): string;`, {
change: sys => sys.modifyFile(`/user/username/projects/myproject/foo.ts`, `export declare function foo2(): string;`, {
invokeFileDeleteCreateAsPartInsteadOfChange: true,
ignoreDelete: true,
}),
@@ -582,89 +583,89 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
},
{
caption: "Replace file with rename event that fixes error",
change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }),
change: sys => sys.modifyFile(`/user/username/projects/myproject/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
]
});
describe("with fsWatch on inodes", () => {
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `fsWatch/when using file watching thats on inode`,
commandLineArgs: ["-w", "--extendedDiagnostics"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
{
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
[`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`,
[`${ts.tscWatch.projectRoot}/foo.d.ts`]: `export function foo(): string;`,
[`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }),
[libFile.path]: libFile.content,
[`/user/username/projects/myproject/main.ts`]: `import { foo } from "./foo"; foo();`,
[`/user/username/projects/myproject/foo.d.ts`]: `export function foo(): string;`,
[`/user/username/projects/myproject/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }),
},
{
currentDirectory: ts.tscWatch.projectRoot,
currentDirectory: "/user/username/projects/myproject",
inodeWatching: true
}
),
changes: [
{
caption: "Replace file with rename event that introduces error",
change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }),
change: sys => sys.modifyFile(`/user/username/projects/myproject/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2),
},
{
caption: "Replace file with rename event that fixes error",
change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }),
change: sys => sys.modifyFile(`/user/username/projects/myproject/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2),
},
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `fsWatch/when using file watching thats on inode when rename event ends with tilde`,
commandLineArgs: ["-w", "--extendedDiagnostics"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
{
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
[`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`,
[`${ts.tscWatch.projectRoot}/foo.d.ts`]: `export function foo(): string;`,
[`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }),
[libFile.path]: libFile.content,
[`/user/username/projects/myproject/main.ts`]: `import { foo } from "./foo"; foo();`,
[`/user/username/projects/myproject/foo.d.ts`]: `export function foo(): string;`,
[`/user/username/projects/myproject/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }),
},
{
currentDirectory: ts.tscWatch.projectRoot,
currentDirectory: "/user/username/projects/myproject",
inodeWatching: true
}
),
changes: [
{
caption: "Replace file with rename event that introduces error",
change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }),
change: sys => sys.modifyFile(`/user/username/projects/myproject/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2),
},
{
caption: "Replace file with rename event that fixes error",
change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }),
change: sys => sys.modifyFile(`/user/username/projects/myproject/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2),
},
]
});
ts.tscWatch.verifyTscWatch({
verifyTscWatch({
scenario,
subScenario: `fsWatch/when using file watching thats on inode when rename occurs when file is still on the disk`,
commandLineArgs: ["-w", "--extendedDiagnostics"],
sys: () => ts.tscWatch.createWatchedSystem(
sys: () => createWatchedSystem(
{
[ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
[`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`,
[`${ts.tscWatch.projectRoot}/foo.ts`]: `export declare function foo(): string;`,
[`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({
[libFile.path]: libFile.content,
[`/user/username/projects/myproject/main.ts`]: `import { foo } from "./foo"; foo();`,
[`/user/username/projects/myproject/foo.ts`]: `export declare function foo(): string;`,
[`/user/username/projects/myproject/tsconfig.json`]: JSON.stringify({
watchOptions: { watchFile: "useFsEvents" },
files: ["foo.ts", "main.ts"]
}),
},
{
currentDirectory: ts.tscWatch.projectRoot,
currentDirectory: "/user/username/projects/myproject",
inodeWatching: true,
}
),
@@ -673,7 +674,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
caption: "Introduce error such that when callback happens file is already appeared",
// vm's wq generates this kind of event
// Skip delete event so inode changes but when the create's rename occurs file is on disk
change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.ts`, `export declare function foo2(): string;`, {
change: sys => sys.modifyFile(`/user/username/projects/myproject/foo.ts`, `export declare function foo2(): string;`, {
invokeFileDeleteCreateAsPartInsteadOfChange: true,
ignoreDelete: true,
skipInodeCheckOnCreate: true
@@ -682,7 +683,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
},
{
caption: "Replace file with rename event that fixes error",
change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }),
change: sys => sys.modifyFile(`/user/username/projects/myproject/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
]

View File

@@ -1,20 +1,23 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File, libFile } from "../virtualFileSystemWithWatch";
import { commonFile1, commonFile2 } from "../tscWatch/helpers";
import { TestSession, createSession } from "./helpers";
describe("unittests:: tsserver:: applyChangesToOpenFiles", () => {
const configFile: ts.projectSystem.File = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: "{}"
};
const file3: ts.projectSystem.File = {
const file3: File = {
path: "/a/b/file3.ts",
content: "let xyz = 1;"
};
const app: ts.projectSystem.File = {
const app: File = {
path: "/a/b/app.ts",
content: "let z = 1;"
};
function fileContentWithComment(file: ts.projectSystem.File) {
function fileContentWithComment(file: File) {
return `// some copy right notice
${file.content}`;
}
@@ -31,22 +34,22 @@ ${file.content}`;
}
interface Verify {
applyChangesToOpen: (session: ts.projectSystem.TestSession) => void;
openFile1Again: (session: ts.projectSystem.TestSession) => void;
applyChangesToOpen: (session: TestSession) => void;
openFile1Again: (session: TestSession) => void;
}
function verify({ applyChangesToOpen, openFile1Again }: Verify) {
const host = ts.projectSystem.createServerHost([app, file3, ts.projectSystem.commonFile1, ts.projectSystem.commonFile2, ts.projectSystem.libFile, configFile]);
const session = ts.projectSystem.createSession(host);
session.executeCommandSeq<ts.projectSystem.protocol.OpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.Open,
const host = createServerHost([app, file3, commonFile1, commonFile2, libFile, configFile]);
const session = createSession(host);
session.executeCommandSeq<ts.server.protocol.OpenRequest>({
command: ts.server.protocol.CommandTypes.Open,
arguments: { file: app.path }
});
const service = session.getProjectService();
const project = service.configuredProjects.get(configFile.path)!;
assert.isDefined(project);
verifyProjectVersion(project, 1);
session.executeCommandSeq<ts.projectSystem.protocol.OpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.Open,
session.executeCommandSeq<ts.server.protocol.OpenRequest>({
command: ts.server.protocol.CommandTypes.Open,
arguments: {
file: file3.path,
fileContent: fileContentWithComment(file3)
@@ -55,8 +58,8 @@ ${file.content}`;
verifyProjectVersion(project, 2);
// Verify Texts
verifyText(service, ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile1.content);
verifyText(service, ts.projectSystem.commonFile2.path, ts.projectSystem.commonFile2.content);
verifyText(service, commonFile1.path, commonFile1.content);
verifyText(service, commonFile2.path, commonFile2.content);
verifyText(service, app.path, app.content);
verifyText(service, file3.path, fileContentWithComment(file3));
@@ -66,36 +69,36 @@ ${file.content}`;
// Verify again
verifyProjectVersion(project, 3);
// Open file contents
verifyText(service, ts.projectSystem.commonFile1.path, fileContentWithComment(ts.projectSystem.commonFile1));
verifyText(service, ts.projectSystem.commonFile2.path, fileContentWithComment(ts.projectSystem.commonFile2));
verifyText(service, commonFile1.path, fileContentWithComment(commonFile1));
verifyText(service, commonFile2.path, fileContentWithComment(commonFile2));
verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;");
verifyText(service, file3.path, file3.content);
// Open file1 again
openFile1Again(session);
assert.isTrue(service.getScriptInfo(ts.projectSystem.commonFile1.path)!.isScriptOpen());
assert.isTrue(service.getScriptInfo(commonFile1.path)!.isScriptOpen());
// Verify that file1 contents are changed
verifyProjectVersion(project, 4);
verifyText(service, ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile1.content);
verifyText(service, ts.projectSystem.commonFile2.path, fileContentWithComment(ts.projectSystem.commonFile2));
verifyText(service, commonFile1.path, commonFile1.content);
verifyText(service, commonFile2.path, fileContentWithComment(commonFile2));
verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;");
verifyText(service, file3.path, file3.content);
}
it("with applyChangedToOpenFiles request", () => {
verify({
applyChangesToOpen: session => session.executeCommandSeq<ts.projectSystem.protocol.ApplyChangedToOpenFilesRequest>({
command: ts.projectSystem.protocol.CommandTypes.ApplyChangedToOpenFiles,
applyChangesToOpen: session => session.executeCommandSeq<ts.server.protocol.ApplyChangedToOpenFilesRequest>({
command: ts.server.protocol.CommandTypes.ApplyChangedToOpenFiles,
arguments: {
openFiles: [
{
fileName: ts.projectSystem.commonFile1.path,
content: fileContentWithComment(ts.projectSystem.commonFile1)
fileName: commonFile1.path,
content: fileContentWithComment(commonFile1)
},
{
fileName: ts.projectSystem.commonFile2.path,
content: fileContentWithComment(ts.projectSystem.commonFile2)
fileName: commonFile2.path,
content: fileContentWithComment(commonFile2)
}
],
changedFiles: [
@@ -118,12 +121,12 @@ ${file.content}`;
]
}
}),
openFile1Again: session => session.executeCommandSeq<ts.projectSystem.protocol.ApplyChangedToOpenFilesRequest>({
command: ts.projectSystem.protocol.CommandTypes.ApplyChangedToOpenFiles,
openFile1Again: session => session.executeCommandSeq<ts.server.protocol.ApplyChangedToOpenFilesRequest>({
command: ts.server.protocol.CommandTypes.ApplyChangedToOpenFiles,
arguments: {
openFiles: [{
fileName: ts.projectSystem.commonFile1.path,
content: ts.projectSystem.commonFile1.content
fileName: commonFile1.path,
content: commonFile1.content
}]
}
}),
@@ -132,17 +135,17 @@ ${file.content}`;
it("with updateOpen request", () => {
verify({
applyChangesToOpen: session => session.executeCommandSeq<ts.projectSystem.protocol.UpdateOpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.UpdateOpen,
applyChangesToOpen: session => session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
openFiles: [
{
file: ts.projectSystem.commonFile1.path,
fileContent: fileContentWithComment(ts.projectSystem.commonFile1)
file: commonFile1.path,
fileContent: fileContentWithComment(commonFile1)
},
{
file: ts.projectSystem.commonFile2.path,
fileContent: fileContentWithComment(ts.projectSystem.commonFile2)
file: commonFile2.path,
fileContent: fileContentWithComment(commonFile2)
}
],
changedFiles: [
@@ -167,12 +170,12 @@ ${file.content}`;
]
}
}),
openFile1Again: session => session.executeCommandSeq<ts.projectSystem.protocol.UpdateOpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.UpdateOpen,
openFile1Again: session => session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
openFiles: [{
file: ts.projectSystem.commonFile1.path,
fileContent: ts.projectSystem.commonFile1.content
file: commonFile1.path,
fileContent: commonFile1.content
}]
}
}),

View File

@@ -1,30 +1,32 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File } from "../virtualFileSystemWithWatch";
import { openFilesForSession, checkNumberOfInferredProjects, checkNumberOfConfiguredProjects, createSession } from "./helpers";
const angularFormsDts: ts.projectSystem.File = {
const angularFormsDts: File = {
path: "/node_modules/@angular/forms/forms.d.ts",
content: "export declare class PatternValidator {}",
};
const angularFormsPackageJson: ts.projectSystem.File = {
const angularFormsPackageJson: File = {
path: "/node_modules/@angular/forms/package.json",
content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`,
};
const angularCoreDts: ts.projectSystem.File = {
const angularCoreDts: File = {
path: "/node_modules/@angular/core/core.d.ts",
content: "",
};
const angularCorePackageJson: ts.projectSystem.File = {
const angularCorePackageJson: File = {
path: "/node_modules/@angular/core/package.json",
content: `{ "name": "@angular/core", "typings": "./core.d.ts" }`,
};
const tsconfig: ts.projectSystem.File = {
const tsconfig: File = {
path: "/tsconfig.json",
content: `{ "compilerOptions": { "module": "commonjs" } }`,
};
const packageJson: ts.projectSystem.File = {
const packageJson: File = {
path: "/package.json",
content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }`
};
const indexTs: ts.projectSystem.File = {
const indexTs: File = {
path: "/index.ts",
content: ""
};
@@ -38,7 +40,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
{ path: packageJson.path, content: `{ "dependencies": {} }` },
indexTs
]);
ts.projectSystem.openFilesForSession([indexTs], session);
openFilesForSession([indexTs], session);
assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider());
});
@@ -50,7 +52,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
packageJson,
{ path: indexTs.path, content: "import '@angular/forms';" }
]);
ts.projectSystem.openFilesForSession([indexTs], session);
openFilesForSession([indexTs], session);
assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider());
});
@@ -64,9 +66,9 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
{ path: "/node_modules/@angular/core/core.d.ts", content: `export namespace angular {};` },
]);
ts.projectSystem.openFilesForSession([angularFormsDts], session);
ts.projectSystem.checkNumberOfInferredProjects(projectService, 1);
ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 0);
openFilesForSession([angularFormsDts], session);
checkNumberOfInferredProjects(projectService, 1);
checkNumberOfConfiguredProjects(projectService, 0);
assert.isUndefined(projectService
.getDefaultProjectForFile(angularFormsDts.path as ts.server.NormalizedPath, /*ensureProject*/ true)!
.getLanguageService()
@@ -75,9 +77,9 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
it("Auto-importable file is in inferred project until imported", () => {
const { projectService, session, updateFile } = setup([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs]);
ts.projectSystem.checkNumberOfInferredProjects(projectService, 0);
ts.projectSystem.openFilesForSession([angularFormsDts], session);
ts.projectSystem.checkNumberOfInferredProjects(projectService, 1);
checkNumberOfInferredProjects(projectService, 0);
openFilesForSession([angularFormsDts], session);
checkNumberOfInferredProjects(projectService, 1);
assert.equal(
projectService.getDefaultProjectForFile(angularFormsDts.path as ts.server.NormalizedPath, /*ensureProject*/ true)?.projectKind,
ts.server.ProjectKind.Inferred);
@@ -99,7 +101,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
indexTs
]);
ts.projectSystem.openFilesForSession([indexTs], session);
openFilesForSession([indexTs], session);
assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider());
host.writeFile(packageJson.path, packageJson.content);
@@ -115,7 +117,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
indexTs
]);
ts.projectSystem.openFilesForSession([indexTs], session);
openFilesForSession([indexTs], session);
const autoImportProvider = projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider();
assert.ok(autoImportProvider);
@@ -134,7 +136,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
indexTs
]);
ts.projectSystem.openFilesForSession([indexTs], session);
openFilesForSession([indexTs], session);
const hostProject = projectService.configuredProjects.get(tsconfig.path)!;
hostProject.getPackageJsonAutoImportProvider();
const autoImportProviderProject = hostProject.autoImportProviderHost;
@@ -154,7 +156,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
]);
// Create configured project only, ensure !projectService.pendingEnsureProjectForOpenFiles
ts.projectSystem.openFilesForSession([indexTs], session);
openFilesForSession([indexTs], session);
const hostProject = projectService.configuredProjects.get(tsconfig.path)!;
projectService.delayEnsureProjectForOpenFiles();
host.runQueuedTimeoutCallbacks();
@@ -177,7 +179,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
indexTs
]);
ts.projectSystem.openFilesForSession([indexTs], session);
openFilesForSession([indexTs], session);
const project = projectService.configuredProjects.get(tsconfig.path)!;
const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true });
assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator"));
@@ -204,7 +206,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
indexTs
]);
ts.projectSystem.openFilesForSession([indexTs, angularFormsDts], session);
openFilesForSession([indexTs, angularFormsDts], session);
const project = projectService.configuredProjects.get(tsconfig.path)!;
const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true });
assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator"));
@@ -224,7 +226,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
indexTs
]);
ts.projectSystem.openFilesForSession([indexTs], session);
openFilesForSession([indexTs], session);
assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider());
host.writeFile(packageJson.path, packageJson.content);
@@ -232,7 +234,7 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
});
it("Does not create an auto import provider if there are too many dependencies", () => {
const createPackage = (i: number): ts.projectSystem.File[] => ([
const createPackage = (i: number): File[] => ([
{ path: `/node_modules/package${i}/package.json`, content: `{ "name": "package${i}" }` },
{ path: `/node_modules/package${i}/index.d.ts`, content: `` }
]);
@@ -243,10 +245,10 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
}
const dependencies = packages.reduce((hash, p) => ({ ...hash, [JSON.parse(p[0].content).name]: "*" }), {});
const packageJson: ts.projectSystem.File = { path: "/package.json", content: JSON.stringify(dependencies) };
const { projectService, session } = setup([ ...ts.flatten(packages), indexTs, tsconfig, packageJson ]);
const packageJson: File = { path: "/package.json", content: JSON.stringify(dependencies) };
const { projectService, session } = setup([...ts.flatten(packages), indexTs, tsconfig, packageJson]);
ts.projectSystem.openFilesForSession([indexTs], session);
openFilesForSession([indexTs], session);
const project = projectService.configuredProjects.get(tsconfig.path)!;
assert.isUndefined(project.getPackageJsonAutoImportProvider());
});
@@ -276,10 +278,10 @@ describe("unittests:: tsserver:: autoImportProvider - monorepo", () => {
const { projectService, session, findAllReferences } = setup(files);
ts.projectSystem.openFilesForSession([files.find(f => f.path === "/packages/b/index.ts")!], session);
ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 2); // Solution (no files), B
openFilesForSession([files.find(f => f.path === "/packages/b/index.ts")!], session);
checkNumberOfConfiguredProjects(projectService, 2); // Solution (no files), B
findAllReferences("/packages/b/index.ts", 1, "export class B".length - 1);
ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 3); // Solution (no files), A, B
checkNumberOfConfiguredProjects(projectService, 3); // Solution (no files), A, B
// Project for A is created - ensure it doesn't have an autoImportProvider
assert.isUndefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getLanguageService().getAutoImportProvider());
@@ -299,7 +301,7 @@ describe("unittests:: tsserver:: autoImportProvider - monorepo", () => {
];
const { projectService, session } = setup(files);
ts.projectSystem.openFilesForSession([files[2]], session);
openFilesForSession([files[2]], session);
assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider());
assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider());
});
@@ -314,9 +316,9 @@ describe("unittests:: tsserver:: autoImportProvider - monorepo", () => {
});
});
function setup(files: ts.projectSystem.File[]) {
const host = ts.projectSystem.createServerHost(files);
const session = ts.projectSystem.createSession(host);
function setup(files: File[]) {
const host = createServerHost(files);
const session = createSession(host);
const projectService = session.getProjectService();
return {
host,
@@ -328,8 +330,8 @@ function setup(files: ts.projectSystem.File[]) {
function updateFile(path: string, newText: string) {
ts.Debug.assertIsDefined(files.find(f => f.path === path));
session.executeCommandSeq<ts.projectSystem.protocol.ApplyChangedToOpenFilesRequest>({
command: ts.projectSystem.protocol.CommandTypes.ApplyChangedToOpenFiles,
session.executeCommandSeq<ts.server.protocol.ApplyChangedToOpenFilesRequest>({
command: ts.server.protocol.CommandTypes.ApplyChangedToOpenFiles,
arguments: {
openFiles: [{
fileName: path,
@@ -341,8 +343,8 @@ function setup(files: ts.projectSystem.File[]) {
function findAllReferences(file: string, line: number, offset: number) {
ts.Debug.assertIsDefined(files.find(f => f.path === file));
session.executeCommandSeq<ts.projectSystem.protocol.ReferencesRequest>({
command: ts.projectSystem.protocol.CommandTypes.References,
session.executeCommandSeq<ts.server.protocol.ReferencesRequest>({
command: ts.server.protocol.CommandTypes.References,
arguments: {
file,
line,

View File

@@ -1,26 +1,28 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File } from "../virtualFileSystemWithWatch";
import { createSession, openFilesForSession, checkNumberOfInferredProjects } from "./helpers";
const aTs: ts.projectSystem.File = {
const aTs: File = {
path: "/a.ts",
content: `import { B } from "./b";`
};
const bDts: ts.projectSystem.File = {
const bDts: File = {
path: "/b.d.ts",
content: `export declare class B {}`
};
const bJs: ts.projectSystem.File = {
const bJs: File = {
path: "/b.js",
content: `export class B {}`
};
describe("unittests:: tsserver:: auxiliaryProject", () => {
it("AuxiliaryProject does not remove scrips from InferredProject", () => {
const host = ts.projectSystem.createServerHost([aTs, bDts, bJs]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([aTs, bDts, bJs]);
const session = createSession(host);
const projectService = session.getProjectService();
ts.projectSystem.openFilesForSession([aTs], session);
openFilesForSession([aTs], session);
// Open file is in inferred project
ts.projectSystem.checkNumberOfInferredProjects(projectService, 1);
checkNumberOfInferredProjects(projectService, 1);
const inferredProject = projectService.inferredProjects[0];
// getNoDtsResolutionProject will create an AuxiliaryProject with a.ts and b.js
@@ -39,7 +41,7 @@ describe("unittests:: tsserver:: auxiliaryProject", () => {
// When b.js is opened in the editor, it should be put into an InferredProject
// even though it's still contained by the AuxiliaryProject.
ts.projectSystem.openFilesForSession([bJs], session);
openFilesForSession([bJs], session);
assert(!bJsScriptInfo.isOrphan());
assert(bJsScriptInfo.isContainedByBackgroundProject());
assert.equal(bJsScriptInfo.getDefaultProject().projectKind, ts.server.ProjectKind.Inferred);

View File

@@ -1,4 +1,6 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File, libFile, SymLink, TestServerHost } from "../virtualFileSystemWithWatch";
import { Logger, createProjectService, createLoggerWithInMemoryLogs, baselineTsserverLogs, createSession, openFilesForSession, makeSessionRequest, checkProjectActualFiles, checkNumberOfProjects } from "./helpers";
describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => {
enum CalledMapsWithSingleArg {
@@ -12,7 +14,7 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
}
type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs;
type CalledWithFiveArgs = [readonly string[], readonly string[], readonly string[], number];
function createLoggerTrackingHostCalls(host: ts.projectSystem.TestServerHost) {
function createLoggerTrackingHostCalls(host: TestServerHost) {
const calledMaps: Record<CalledMapsWithSingleArg, ts.MultiMap<string, true>> & Record<CalledMapsWithFiveArgs, ts.MultiMap<string, CalledWithFiveArgs>> = {
fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists),
directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists),
@@ -43,13 +45,13 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
return calledMap;
}
function logCacheEntry(logger: ts.projectSystem.Logger, callback: CalledMaps) {
function logCacheEntry(logger: Logger, callback: CalledMaps) {
const result = ts.arrayFrom<[string, (true | CalledWithFiveArgs)[]], { key: string, count: number }>(calledMaps[callback].entries(), ([key, arr]) => ({ key, count: arr.length }));
logger.info(`${callback}:: ${JSON.stringify(result)}`);
calledMaps[callback].clear();
}
function logCacheAndClear(logger: ts.projectSystem.Logger) {
function logCacheAndClear(logger: Logger) {
logCacheEntry(logger, CalledMapsWithSingleArg.fileExists);
logCacheEntry(logger, CalledMapsWithSingleArg.directoryExists);
logCacheEntry(logger, CalledMapsWithSingleArg.getDirectories);
@@ -58,7 +60,7 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
}
}
function logSemanticDiagnostics(projectService: ts.server.ProjectService, project: ts.server.Project, file: ts.projectSystem.File) {
function logSemanticDiagnostics(projectService: ts.server.ProjectService, project: ts.server.Project, file: File) {
const diags = project.getLanguageService().getSemanticDiagnostics(file.path);
projectService.logger.info(`getSemanticDiagnostics:: ${file.path}:: ${diags.length}`);
diags.forEach(d => projectService.logger.info(ts.formatDiagnostic(d, project)));
@@ -66,18 +68,18 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
it("works using legacy resolution logic", () => {
let rootContent = `import {x} from "f1"`;
const root: ts.projectSystem.File = {
const root: File = {
path: "/c/d/f0.ts",
content: rootContent
};
const imported: ts.projectSystem.File = {
const imported: File = {
path: "/c/f1.ts",
content: `foo()`
};
const host = ts.projectSystem.createServerHost([root, imported]);
const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
const host = createServerHost([root, imported]);
const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true });
projectService.openClientFile(root.path);
const project = projectService.inferredProjects[0];
@@ -115,7 +117,7 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true, target: ts.ScriptTarget.ES5 });
logSemanticDiagnostics(projectService, project, imported);
logCacheAndClear(projectService.logger);
ts.projectSystem.baselineTsserverLogs("cachingFileSystemInformation", "works using legacy resolution logic", projectService);
baselineTsserverLogs("cachingFileSystemInformation", "works using legacy resolution logic", projectService);
function editContent(newContent: string) {
rootScriptInfo.editContent(0, rootContent.length, newContent);
@@ -124,18 +126,18 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
});
it("loads missing files from disk", () => {
const root: ts.projectSystem.File = {
const root: File = {
path: "/c/foo.ts",
content: `import {y} from "bar"`
};
const imported: ts.projectSystem.File = {
const imported: File = {
path: "/c/bar.d.ts",
content: `export var y = 1`
};
const host = ts.projectSystem.createServerHost([root]);
const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
const host = createServerHost([root]);
const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true });
const logCacheAndClear = createLoggerTrackingHostCalls(host);
projectService.openClientFile(root.path);
@@ -150,29 +152,29 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
host.runQueuedTimeoutCallbacks();
logSemanticDiagnostics(projectService, project, root);
logCacheAndClear(projectService.logger);
ts.projectSystem.baselineTsserverLogs("cachingFileSystemInformation", "loads missing files from disk", projectService);
baselineTsserverLogs("cachingFileSystemInformation", "loads missing files from disk", projectService);
});
it("when calling goto definition of module", () => {
const clientFile: ts.projectSystem.File = {
const clientFile: File = {
path: "/a/b/controllers/vessels/client.ts",
content: `
import { Vessel } from '~/models/vessel';
const v = new Vessel();
`
};
const anotherModuleFile: ts.projectSystem.File = {
const anotherModuleFile: File = {
path: "/a/b/utils/db.ts",
content: "export class Bookshelf { }"
};
const moduleFile: ts.projectSystem.File = {
const moduleFile: File = {
path: "/a/b/models/vessel.ts",
content: `
import { Bookshelf } from '~/utils/db';
export class Vessel extends Bookshelf {}
`
};
const tsconfigFile: ts.projectSystem.File = {
const tsconfigFile: File = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({
compilerOptions: {
@@ -195,13 +197,13 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
})
};
const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile];
const host = ts.projectSystem.createServerHost(projectFiles);
const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
ts.projectSystem.openFilesForSession([clientFile], session);
const host = createServerHost(projectFiles);
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
openFilesForSession([clientFile], session);
const logCacheAndClear = createLoggerTrackingHostCalls(host);
// Get definitions shouldnt make host requests
const getDefinitionRequest = ts.projectSystem.makeSessionRequest<ts.projectSystem.protocol.FileLocationRequestArgs>(ts.projectSystem.protocol.CommandTypes.Definition, {
const getDefinitionRequest = makeSessionRequest<ts.server.protocol.FileLocationRequestArgs>(ts.server.protocol.CommandTypes.Definition, {
file: clientFile.path,
position: clientFile.content.indexOf("/vessel") + 1,
line: undefined!, // TODO: GH#18217
@@ -211,35 +213,35 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
logCacheAndClear(session.logger);
// Open the file should call only file exists on module directory and use cached value for parental directory
ts.projectSystem.openFilesForSession([moduleFile], session);
openFilesForSession([moduleFile], session);
logCacheAndClear(session.logger);
ts.projectSystem.baselineTsserverLogs("cachingFileSystemInformation", "when calling goto definition of module", session);
baselineTsserverLogs("cachingFileSystemInformation", "when calling goto definition of module", session);
});
describe("WatchDirectories for config file with", () => {
function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) {
it(`watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, () => {
const frontendDir = "/Users/someuser/work/applications/frontend";
const file1: ts.projectSystem.File = {
const file1: File = {
path: `${frontendDir}/src/app/utils/Analytic.ts`,
content: "export class SomeClass { };"
};
const file2: ts.projectSystem.File = {
const file2: File = {
path: `${frontendDir}/src/app/redux/configureStore.ts`,
content: "export class configureStore { }"
};
const file3: ts.projectSystem.File = {
const file3: File = {
path: `${frontendDir}/src/app/utils/Cookie.ts`,
content: "export class Cookie { }"
};
const es2016LibFile: ts.projectSystem.File = {
const es2016LibFile: File = {
path: "/a/lib/lib.es2016.full.d.ts",
content: ts.projectSystem.libFile.content
content: libFile.content
};
const typeRoots = ["types", "node_modules/@types"];
const types = ["node", "jest"];
const tsconfigFile: ts.projectSystem.File = {
const tsconfigFile: File = {
path: `${frontendDir}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
@@ -273,8 +275,8 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
})
};
const projectFiles = [file1, file2, es2016LibFile, tsconfigFile];
const host = ts.projectSystem.createServerHost(projectFiles, { useCaseSensitiveFileNames });
const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
const host = createServerHost(projectFiles, { useCaseSensitiveFileNames });
const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
projectService.openClientFile(file1.path);
const logCacheAndClear = createLoggerTrackingHostCalls(host);
@@ -286,7 +288,7 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
projectService.openClientFile(file3.path);
logCacheAndClear(projectService.logger);
ts.projectSystem.baselineTsserverLogs("cachingFileSystemInformation", `watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, projectService);
baselineTsserverLogs("cachingFileSystemInformation", `watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, projectService);
});
}
verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false);
@@ -296,15 +298,15 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
describe("Subfolder invalidations correctly include parent folder failed lookup locations", () => {
function runFailedLookupTest(resolution: "Node" | "Classic") {
const projectLocation = "/proj";
const file1: ts.projectSystem.File = {
const file1: File = {
path: `${projectLocation}/foo/boo/app.ts`,
content: `import * as debug from "debug"`
};
const file2: ts.projectSystem.File = {
const file2: File = {
path: `${projectLocation}/foo/boo/moo/app.ts`,
content: `import * as debug from "debug"`
};
const tsconfig: ts.projectSystem.File = {
const tsconfig: File = {
path: `${projectLocation}/tsconfig.json`,
content: JSON.stringify({
files: ["foo/boo/app.ts", "foo/boo/moo/app.ts"],
@@ -312,17 +314,17 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
})
};
const files = [file1, file2, tsconfig, ts.projectSystem.libFile];
const host = ts.projectSystem.createServerHost(files);
const service = ts.projectSystem.createProjectService(host);
const files = [file1, file2, tsconfig, libFile];
const host = createServerHost(files);
const service = createProjectService(host);
service.openClientFile(file1.path);
const project = service.configuredProjects.get(tsconfig.path)!;
ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path));
checkProjectActualFiles(project, files.map(f => f.path));
assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]);
assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]);
const debugTypesFile: ts.projectSystem.File = {
const debugTypesFile: File = {
path: `${projectLocation}/node_modules/debug/index.d.ts`,
content: "export {}"
};
@@ -330,7 +332,7 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
host.writeFile(debugTypesFile.path, debugTypesFile.content);
host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions
host.runQueuedTimeoutCallbacks(); // Actual update
ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path));
checkProjectActualFiles(project, files.map(f => f.path));
assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), []);
assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), []);
}
@@ -346,19 +348,19 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
describe("Verify npm install in directory with tsconfig file works when", () => {
function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) {
const root = "/user/username/rootfolder/otherfolder";
const getRootedFileOrFolder = (fileOrFolder: ts.projectSystem.File) => {
const getRootedFileOrFolder = (fileOrFolder: File) => {
fileOrFolder.path = root + fileOrFolder.path;
return fileOrFolder;
};
const app: ts.projectSystem.File = getRootedFileOrFolder({
const app: File = getRootedFileOrFolder({
path: "/a/b/app.ts",
content: "import _ from 'lodash';"
});
const tsconfigJson: ts.projectSystem.File = getRootedFileOrFolder({
const tsconfigJson: File = getRootedFileOrFolder({
path: "/a/b/tsconfig.json",
content: '{ "compilerOptions": { } }'
});
const packageJson: ts.projectSystem.File = getRootedFileOrFolder({
const packageJson: File = getRootedFileOrFolder({
path: "/a/b/package.json",
content: `
{
@@ -383,15 +385,15 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
}
`
});
const host = ts.projectSystem.createServerHost([app, ts.projectSystem.libFile, tsconfigJson, packageJson]);
const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
const host = createServerHost([app, libFile, tsconfigJson, packageJson]);
const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } });
projectService.openClientFile(app.path);
let npmInstallComplete = false;
// Simulate npm install
const filesAndFoldersToAdd: ts.projectSystem.File[] = [
const filesAndFoldersToAdd: File[] = [
{ path: "/a/b/node_modules" },
{ path: "/a/b/node_modules/.staging/@types" },
{ path: "/a/b/node_modules/.staging/lodash-b0733faa" },
@@ -469,7 +471,7 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
npmInstallComplete = true;
verifyAfterPartialOrCompleteNpmInstall(2);
ts.projectSystem.baselineTsserverLogs(
baselineTsserverLogs(
"cachingFileSystemInformation",
`npm install works when ${timeoutDuringPartialInstallation ? "timeout occurs inbetween installation" : "timeout occurs after installation"}`,
projectService
@@ -503,25 +505,25 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
it("when node_modules dont receive event for the @types file addition", () => {
const projectLocation = "/user/username/folder/myproject";
const app: ts.projectSystem.File = {
const app: File = {
path: `${projectLocation}/app.ts`,
content: `import * as debug from "debug"`
};
const tsconfig: ts.projectSystem.File = {
const tsconfig: File = {
path: `${projectLocation}/tsconfig.json`,
content: ""
};
const files = [app, tsconfig, ts.projectSystem.libFile];
const host = ts.projectSystem.createServerHost(files);
const service = ts.projectSystem.createProjectService(host);
const files = [app, tsconfig, libFile];
const host = createServerHost(files);
const service = createProjectService(host);
service.openClientFile(app.path);
const project = service.configuredProjects.get(tsconfig.path)!;
ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path));
checkProjectActualFiles(project, files.map(f => f.path));
assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]);
const debugTypesFile: ts.projectSystem.File = {
const debugTypesFile: File = {
path: `${projectLocation}/node_modules/@types/debug/index.d.ts`,
content: "export {}"
};
@@ -535,25 +537,25 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
};
host.writeFile(debugTypesFile.path, debugTypesFile.content);
host.runQueuedTimeoutCallbacks();
ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path));
checkProjectActualFiles(project, files.map(f => f.path));
assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), []);
});
it("when creating new file in symlinked folder", () => {
const module1: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/client/folder1/module1.ts`,
const module1: File = {
path: `/user/username/projects/myproject/client/folder1/module1.ts`,
content: `export class Module1Class { }`
};
const module2: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/folder2/module2.ts`,
const module2: File = {
path: `/user/username/projects/myproject/folder2/module2.ts`,
content: `import * as M from "folder1/module1";`
};
const symlink: ts.projectSystem.SymLink = {
path: `${ts.tscWatch.projectRoot}/client/linktofolder2`,
symLink: `${ts.tscWatch.projectRoot}/folder2`,
const symlink: SymLink = {
path: `/user/username/projects/myproject/client/linktofolder2`,
symLink: `/user/username/projects/myproject/folder2`,
};
const config: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
baseUrl: "client",
@@ -562,15 +564,15 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS
include: ["client/**/*", "folder2"]
})
};
const host = ts.projectSystem.createServerHost([module1, module2, symlink, config, ts.projectSystem.libFile]);
const service = ts.projectSystem.createProjectService(host);
const host = createServerHost([module1, module2, symlink, config, libFile]);
const service = createProjectService(host);
service.openClientFile(`${symlink.path}/module2.ts`);
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 });
checkNumberOfProjects(service, { configuredProjects: 1 });
const project = ts.Debug.checkDefined(service.configuredProjects.get(config.path));
ts.projectSystem.checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, ts.projectSystem.libFile.path]);
checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, libFile.path]);
host.writeFile(`${symlink.path}/module3.ts`, `import * as M from "folder1/module1";`);
host.runQueuedTimeoutCallbacks();
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, ts.projectSystem.libFile.path, `${symlink.path}/module3.ts`]);
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, libFile.path, `${symlink.path}/module3.ts`]);
});
});

View File

@@ -1,4 +1,6 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost } from "../virtualFileSystemWithWatch";
import { createSession, TestServerCancellationToken } from "./helpers";
describe("unittests:: tsserver:: cancellationToken", () => {
// Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test
@@ -17,7 +19,7 @@ describe("unittests:: tsserver:: cancellationToken", () => {
path: "/a/b/app.ts",
content: "let xyz = 1;"
};
const host = ts.projectSystem.createServerHost([f1]);
const host = createServerHost([f1]);
let expectedRequestId: number;
const cancellationToken: ts.server.ServerCancellationToken = {
isCancellationRequested: () => false,
@@ -30,7 +32,7 @@ describe("unittests:: tsserver:: cancellationToken", () => {
resetRequest: ts.noop
};
const session = ts.projectSystem.createSession(host, { cancellationToken });
const session = createSession(host, { cancellationToken });
expectedRequestId = session.getNextSeq();
session.executeCommandSeq({
@@ -68,9 +70,9 @@ describe("unittests:: tsserver:: cancellationToken", () => {
})
};
const cancellationToken = new ts.projectSystem.TestServerCancellationToken();
const host = ts.projectSystem.createServerHost([f1, config]);
const session = ts.projectSystem.createSession(host, {
const cancellationToken = new TestServerCancellationToken();
const host = createServerHost([f1, config]);
const session = createSession(host, {
canUseEvents: true,
eventHandler: ts.noop,
cancellationToken
@@ -79,12 +81,12 @@ describe("unittests:: tsserver:: cancellationToken", () => {
session.executeCommandSeq({
command: "open",
arguments: { file: f1.path }
} as ts.projectSystem.protocol.OpenRequest);
} as ts.server.protocol.OpenRequest);
// send geterr for missing file
session.executeCommandSeq({
command: "geterr",
arguments: { files: ["/a/missing"] }
} as ts.projectSystem.protocol.GeterrRequest);
} as ts.server.protocol.GeterrRequest);
// Queued files
assert.equal(host.getOutput().length, 0, "expected 0 message");
host.checkTimeoutQueueLengthAndRun(1);
@@ -98,7 +100,7 @@ describe("unittests:: tsserver:: cancellationToken", () => {
session.executeCommandSeq({
command: "geterr",
arguments: { files: [f1.path] }
} as ts.projectSystem.protocol.GeterrRequest);
} as ts.server.protocol.GeterrRequest);
assert.equal(host.getOutput().length, 0, "expect 0 messages");
@@ -106,7 +108,7 @@ describe("unittests:: tsserver:: cancellationToken", () => {
session.executeCommandSeq({
command: "projectInfo",
arguments: { file: f1.path }
} as ts.projectSystem.protocol.ProjectInfoRequest);
} as ts.server.protocol.ProjectInfoRequest);
session.clearMessages();
// cancel previously issued Geterr
@@ -123,13 +125,13 @@ describe("unittests:: tsserver:: cancellationToken", () => {
session.executeCommandSeq({
command: "geterr",
arguments: { files: [f1.path] }
} as ts.projectSystem.protocol.GeterrRequest);
} as ts.server.protocol.GeterrRequest);
assert.equal(host.getOutput().length, 0, "expect 0 messages");
// run first step
host.runQueuedTimeoutCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 message");
const e1 = getMessage(0) as ts.projectSystem.protocol.Event;
const e1 = getMessage(0) as ts.server.protocol.Event;
assert.equal(e1.event, "syntaxDiag");
session.clearMessages();
@@ -145,26 +147,26 @@ describe("unittests:: tsserver:: cancellationToken", () => {
session.executeCommandSeq({
command: "geterr",
arguments: { files: [f1.path] }
} as ts.projectSystem.protocol.GeterrRequest);
} as ts.server.protocol.GeterrRequest);
assert.equal(host.getOutput().length, 0, "expect 0 messages");
// run first step
host.runQueuedTimeoutCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 message");
const e1 = getMessage(0) as ts.projectSystem.protocol.Event;
const e1 = getMessage(0) as ts.server.protocol.Event;
assert.equal(e1.event, "syntaxDiag");
session.clearMessages();
// the semanticDiag message
host.runQueuedImmediateCallbacks();
assert.equal(host.getOutput().length, 1);
const e2 = getMessage(0) as ts.projectSystem.protocol.Event;
const e2 = getMessage(0) as ts.server.protocol.Event;
assert.equal(e2.event, "semanticDiag");
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
assert.equal(host.getOutput().length, 2);
const e3 = getMessage(0) as ts.projectSystem.protocol.Event;
const e3 = getMessage(0) as ts.server.protocol.Event;
assert.equal(e3.event, "suggestionDiag");
verifyRequestCompleted(getErrId, 1);
@@ -175,25 +177,25 @@ describe("unittests:: tsserver:: cancellationToken", () => {
session.executeCommandSeq({
command: "geterr",
arguments: { files: [f1.path] }
} as ts.projectSystem.protocol.GeterrRequest);
} as ts.server.protocol.GeterrRequest);
assert.equal(host.getOutput().length, 0, "expect 0 messages");
// run first step
host.runQueuedTimeoutCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 message");
const e1 = getMessage(0) as ts.projectSystem.protocol.Event;
const e1 = getMessage(0) as ts.server.protocol.Event;
assert.equal(e1.event, "syntaxDiag");
session.clearMessages();
session.executeCommandSeq({
command: "geterr",
arguments: { files: [f1.path] }
} as ts.projectSystem.protocol.GeterrRequest);
} as ts.server.protocol.GeterrRequest);
// make sure that getErr1 is completed
verifyRequestCompleted(getErr1, 0);
}
function verifyRequestCompleted(expectedSeq: number, n: number) {
const event = getMessage(n) as ts.projectSystem.protocol.RequestCompletedEvent;
const event = getMessage(n) as ts.server.protocol.RequestCompletedEvent;
assert.equal(event.event, "requestCompleted");
assert.equal(event.body.request_seq, expectedSeq, "expectedSeq");
session.clearMessages();
@@ -215,9 +217,9 @@ describe("unittests:: tsserver:: cancellationToken", () => {
compilerOptions: {}
})
};
const cancellationToken = new ts.projectSystem.TestServerCancellationToken(/*cancelAfterRequest*/ 3);
const host = ts.projectSystem.createServerHost([f1, config]);
const session = ts.projectSystem.createSession(host, {
const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3);
const host = createServerHost([f1, config]);
const session = createSession(host, {
canUseEvents: true,
eventHandler: ts.noop,
cancellationToken,
@@ -227,31 +229,31 @@ describe("unittests:: tsserver:: cancellationToken", () => {
session.executeCommandSeq({
command: "open",
arguments: { file: f1.path }
} as ts.projectSystem.protocol.OpenRequest);
} as ts.server.protocol.OpenRequest);
// send navbar request (normal priority)
session.executeCommandSeq({
command: "navbar",
arguments: { file: f1.path }
} as ts.projectSystem.protocol.NavBarRequest);
} as ts.server.protocol.NavBarRequest);
// ensure the nav bar request can be canceled
verifyExecuteCommandSeqIsCancellable({
command: "navbar",
arguments: { file: f1.path }
} as ts.projectSystem.protocol.NavBarRequest);
} as ts.server.protocol.NavBarRequest);
// send outlining spans request (normal priority)
session.executeCommandSeq({
command: "outliningSpans",
arguments: { file: f1.path }
} as ts.projectSystem.protocol.OutliningSpansRequestFull);
} as ts.server.protocol.OutliningSpansRequestFull);
// ensure the outlining spans request can be canceled
verifyExecuteCommandSeqIsCancellable({
command: "outliningSpans",
arguments: { file: f1.path }
} as ts.projectSystem.protocol.OutliningSpansRequestFull);
} as ts.server.protocol.OutliningSpansRequestFull);
}
function verifyExecuteCommandSeqIsCancellable<T extends ts.server.protocol.Request>(request: Partial<T>) {

View File

@@ -1,12 +1,13 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File, libFile } from "../virtualFileSystemWithWatch";
import { TestTypingsInstaller, makeSessionRequest, createSession, openFilesForSession, checkNumberOfProjects, checkProjectRootFiles, createLoggerWithInMemoryLogs, baselineTsserverLogs, toExternalFiles, protocolTextSpanFromSubstring, TestSession } from "./helpers";
import CommandNames = ts.server.CommandNames;
function createTestTypingsInstaller(host: ts.server.ServerHost) {
return new ts.projectSystem.TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host);
return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host);
}
describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
function sendAffectedFileRequestAndCheckResult(session: ts.server.Session, request: ts.server.protocol.Request, expectedFileList: { projectFileName: string, files: ts.projectSystem.File[] }[]) {
function sendAffectedFileRequestAndCheckResult(session: ts.server.Session, request: ts.server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) {
const response = session.executeCommand(request).response as ts.server.protocol.CompileOnSaveAffectedFileListSingleProject[];
const actualResult = response.sort((list1, list2) => ts.compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName));
expectedFileList = expectedFileList.sort((list1, list2) => ts.compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName));
@@ -27,12 +28,12 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
}
describe("for configured projects", () => {
let moduleFile1: ts.projectSystem.File;
let file1Consumer1: ts.projectSystem.File;
let file1Consumer2: ts.projectSystem.File;
let moduleFile2: ts.projectSystem.File;
let globalFile3: ts.projectSystem.File;
let configFile: ts.projectSystem.File;
let moduleFile1: File;
let file1Consumer1: File;
let file1Consumer2: File;
let moduleFile2: File;
let globalFile3: File;
let configFile: File;
let changeModuleFile1ShapeRequest1: ts.server.protocol.Request;
let changeModuleFile1InternalRequest1: ts.server.protocol.Request;
// A compile on save affected file request using file1
@@ -72,7 +73,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
};
// Change the content of file1 to `export var T: number;export function Foo() { };`
changeModuleFile1ShapeRequest1 = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
changeModuleFile1ShapeRequest1 = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 1,
@@ -82,7 +83,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
});
// Change the content of file1 to `export var T: number;export function Foo() { };`
changeModuleFile1InternalRequest1 = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
changeModuleFile1InternalRequest1 = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 1,
@@ -91,15 +92,15 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
insertString: `var T1: number;`
});
moduleFile1FileListRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path, projectFileName: configFile.path });
moduleFile1FileListRequest = makeSessionRequest<ts.server.protocol.FileRequestArgs>(ts.server.CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path, projectFileName: configFile.path });
});
it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => {
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session);
openFilesForSession([moduleFile1, file1Consumer1], session);
// Send an initial compileOnSave request
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
@@ -107,7 +108,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
// Change the content of file1 to `export var T: number;export function Foo() { console.log('hi'); };`
const changeFile1InternalRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
const changeFile1InternalRequest = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 46,
@@ -120,17 +121,17 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
});
it("should be up-to-date with the reference map changes", () => {
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session);
openFilesForSession([moduleFile1, file1Consumer1], session);
// Send an initial compileOnSave request
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
// Change file2 content to `let y = Foo();`
const removeFile1Consumer1ImportRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
const removeFile1Consumer1ImportRequest = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: file1Consumer1.path,
line: 1,
offset: 1,
@@ -143,7 +144,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]);
// Add the import statements back to file2
const addFile2ImportRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
const addFile2ImportRequest = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: file1Consumer1.path,
line: 1,
offset: 1,
@@ -154,7 +155,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
session.executeCommand(addFile2ImportRequest);
// Change the content of file1 to `export var T2: string;export var T: number;export function Foo() { };`
const changeModuleFile1ShapeRequest2 = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
const changeModuleFile1ShapeRequest2 = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 1,
@@ -167,11 +168,11 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
});
it("should be up-to-date with changes made in non-open files", () => {
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1], session);
openFilesForSession([moduleFile1], session);
// Send an initial compileOnSave request
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
@@ -183,11 +184,11 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
});
it("should be up-to-date with deleted files", () => {
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1], session);
openFilesForSession([moduleFile1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
session.executeCommand(changeModuleFile1ShapeRequest1);
@@ -197,14 +198,14 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
});
it("should be up-to-date with newly created files", () => {
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1], session);
openFilesForSession([moduleFile1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
const file1Consumer3: ts.projectSystem.File = {
const file1Consumer3: File = {
path: "/a/b/file1Consumer3.ts",
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
};
@@ -233,11 +234,11 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
}`
};
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session);
openFilesForSession([moduleFile1, file1Consumer1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
// change file1 shape now, and verify both files are affected
@@ -250,12 +251,12 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
});
it("should return all files if a global file changed shape", () => {
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([globalFile3], session);
const changeGlobalFile3ShapeRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
openFilesForSession([globalFile3], session);
const changeGlobalFile3ShapeRequest = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: globalFile3.path,
line: 1,
offset: 1,
@@ -266,7 +267,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
// check after file1 shape changes
session.executeCommand(changeGlobalFile3ShapeRequest);
const globalFile3FileListRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: globalFile3.path });
const globalFile3FileListRequest = makeSessionRequest<ts.server.protocol.FileRequestArgs>(ts.server.CommandNames.CompileOnSaveAffectedFileList, { file: globalFile3.path });
sendAffectedFileRequestAndCheckResult(session, globalFile3FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2] }]);
});
@@ -276,10 +277,10 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
content: `{}`
};
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1], session);
const session = createSession(host, { typingsInstaller });
openFilesForSession([moduleFile1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []);
});
@@ -294,10 +295,10 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
}`
};
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1], session);
const session = createSession(host, { typingsInstaller });
openFilesForSession([moduleFile1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []);
});
@@ -309,18 +310,18 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
}`
};
const configFile2: ts.projectSystem.File = {
const configFile2: File = {
path: "/a/tsconfig.json",
content: `{
"compileOnSave": true
}`
};
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session);
openFilesForSession([moduleFile1, file1Consumer1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
});
@@ -335,12 +336,12 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
}`
};
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1], session);
const session = createSession(host, { typingsInstaller });
openFilesForSession([moduleFile1], session);
const file1ChangeShapeRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
const file1ChangeShapeRequest = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 27,
@@ -364,12 +365,12 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
}`
};
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1], session);
const session = createSession(host, { typingsInstaller });
openFilesForSession([moduleFile1], session);
const file1ChangeShapeRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
const file1ChangeShapeRequest = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 27,
@@ -382,18 +383,18 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
});
it("should return cascaded affected file list", () => {
const file1Consumer1Consumer1: ts.projectSystem.File = {
const file1Consumer1Consumer1: File = {
path: "/a/b/file1Consumer1Consumer1.ts",
content: `import {y} from "./file1Consumer1";`
};
const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, ts.projectSystem.libFile]);
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session);
openFilesForSession([moduleFile1, file1Consumer1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]);
const changeFile1Consumer1ShapeRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(CommandNames.Change, {
const changeFile1Consumer1ShapeRequest = makeSessionRequest<ts.server.protocol.ChangeRequestArgs>(ts.server.CommandNames.Change, {
file: file1Consumer1.path,
line: 2,
offset: 1,
@@ -407,39 +408,39 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
});
it("should work fine for files with circular references", () => {
const file1: ts.projectSystem.File = {
const file1: File = {
path: "/a/b/file1.ts",
content: `
/// <reference path="./file2.ts" />
export var t1 = 10;`
};
const file2: ts.projectSystem.File = {
const file2: File = {
path: "/a/b/file2.ts",
content: `
/// <reference path="./file1.ts" />
export var t2 = 10;`
};
const host = ts.projectSystem.createServerHost([file1, file2, configFile]);
const host = createServerHost([file1, file2, configFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([file1, file2], session);
const file1AffectedListRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
openFilesForSession([file1, file2], session);
const file1AffectedListRequest = makeSessionRequest<ts.server.protocol.FileRequestArgs>(ts.server.CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]);
});
it("should return results for all projects if not specifying projectFileName", () => {
const file1: ts.projectSystem.File = { path: "/a/b/file1.ts", content: "export var t = 10;" };
const file2: ts.projectSystem.File = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` };
const file3: ts.projectSystem.File = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` };
const configFile1: ts.projectSystem.File = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` };
const configFile2: ts.projectSystem.File = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` };
const file1: File = { path: "/a/b/file1.ts", content: "export var t = 10;" };
const file2: File = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` };
const file3: File = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` };
const configFile1: File = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` };
const configFile2: File = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` };
const host = ts.projectSystem.createServerHost([file1, file2, file3, configFile1, configFile2]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([file1, file2, file3, configFile1, configFile2]);
const session = createSession(host);
ts.projectSystem.openFilesForSession([file1, file2, file3], session);
const file1AffectedListRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
openFilesForSession([file1, file2, file3], session);
const file1AffectedListRequest = makeSessionRequest<ts.server.protocol.FileRequestArgs>(ts.server.CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [
{ projectFileName: configFile1.path, files: [file1, file2] },
@@ -448,38 +449,38 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
});
it("should detect removed code file", () => {
const referenceFile1: ts.projectSystem.File = {
const referenceFile1: File = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile1.ts" />
export var x = Foo();`
};
const host = ts.projectSystem.createServerHost([moduleFile1, referenceFile1, configFile]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([moduleFile1, referenceFile1, configFile]);
const session = createSession(host);
ts.projectSystem.openFilesForSession([referenceFile1], session);
openFilesForSession([referenceFile1], session);
host.deleteFile(moduleFile1.path);
const request = ts.projectSystem.makeSessionRequest<ts.server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
const request = makeSessionRequest<ts.server.protocol.FileRequestArgs>(ts.server.CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
sendAffectedFileRequestAndCheckResult(session, request, [
{ projectFileName: configFile.path, files: [referenceFile1] }
]);
const requestForMissingFile = ts.projectSystem.makeSessionRequest<ts.server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path });
const requestForMissingFile = makeSessionRequest<ts.server.protocol.FileRequestArgs>(ts.server.CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path });
sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []);
});
it("should detect non-existing code file", () => {
const referenceFile1: ts.projectSystem.File = {
const referenceFile1: File = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile2.ts" />
export var x = Foo();`
};
const host = ts.projectSystem.createServerHost([referenceFile1, configFile]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([referenceFile1, configFile]);
const session = createSession(host);
ts.projectSystem.openFilesForSession([referenceFile1], session);
const request = ts.projectSystem.makeSessionRequest<ts.server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
openFilesForSession([referenceFile1], session);
const request = makeSessionRequest<ts.server.protocol.FileRequestArgs>(ts.server.CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
sendAffectedFileRequestAndCheckResult(session, request, [
{ projectFileName: configFile.path, files: [referenceFile1] }
]);
@@ -503,37 +504,37 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
compileOnSave: true
})
};
const host = ts.projectSystem.createServerHost([dtsFile, f2, config]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([dtsFile, f2, config]);
const session = createSession(host);
session.executeCommand({
seq: 1,
type: "request",
command: "open",
arguments: { file: dtsFile.path }
} as ts.projectSystem.protocol.OpenRequest);
} as ts.server.protocol.OpenRequest);
const projectService = session.getProjectService();
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = projectService.configuredProjects.get(config.path)!;
ts.projectSystem.checkProjectRootFiles(project, [dtsFile.path, f2.path]);
checkProjectRootFiles(project, [dtsFile.path, f2.path]);
session.executeCommand({
seq: 2,
type: "request",
command: "open",
arguments: { file: f2.path }
} as ts.projectSystem.protocol.OpenRequest);
ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
} as ts.server.protocol.OpenRequest);
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
const { response } = session.executeCommand({
seq: 3,
type: "request",
command: "compileOnSaveAffectedFileList",
arguments: { file: dtsFile.path }
} as ts.projectSystem.protocol.CompileOnSaveAffectedFileListRequest);
} as ts.server.protocol.CompileOnSaveAffectedFileListRequest);
if (expectDTSEmit) {
assert.equal((response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project");
assert.equal((response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[])[0].fileNames.length, 2, "expected to affect 2 files");
assert.equal((response as ts.server.protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project");
assert.equal((response as ts.server.protocol.CompileOnSaveAffectedFileListSingleProject[])[0].fileNames.length, 2, "expected to affect 2 files");
}
else {
assert.equal((response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 0, "expected no output");
assert.equal((response as ts.server.protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 0, "expected no output");
}
@@ -542,8 +543,8 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
type: "request",
command: "compileOnSaveAffectedFileList",
arguments: { file: f2.path }
} as ts.projectSystem.protocol.CompileOnSaveAffectedFileListRequest);
assert.equal((response2 as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project");
} as ts.server.protocol.CompileOnSaveAffectedFileListRequest);
assert.equal((response2 as ts.server.protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project");
}
it("should return empty array if change is made in a global declaration file", () => {
@@ -610,14 +611,14 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
compileOnSave: true
})
};
const host = ts.projectSystem.createServerHost([f1, f2, config]);
const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
ts.projectSystem.openFilesForSession([f1], session);
session.executeCommandSeq<ts.projectSystem.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList,
const host = createServerHost([f1, f2, config]);
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
openFilesForSession([f1], session);
session.executeCommandSeq<ts.server.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.server.protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: { file: f1.path }
});
ts.projectSystem.baselineTsserverLogs("compileOnSave", subScenario, session);
baselineTsserverLogs("compileOnSave", subScenario, session);
});
}
test("compileOnSaveAffectedFileList projectUsesOutFile should not be returned if not set", {});
@@ -638,8 +639,8 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
path: path + ts.Extension.Ts,
content: lines.join(newLine)
};
const host = ts.projectSystem.createServerHost([f], { newLine });
const session = ts.projectSystem.createSession(host);
const host = createServerHost([f], { newLine });
const session = createSession(host);
const openRequest: ts.server.protocol.OpenRequest = {
seq: 1,
type: "request",
@@ -672,12 +673,12 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
path: "/a/b/tsconfig.json",
content: `{}`
};
const host = ts.projectSystem.createServerHost([file1, file2, configFile, ts.projectSystem.libFile], { newLine: "\r\n" });
const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" });
const typingsInstaller = createTestTypingsInstaller(host);
const session = ts.projectSystem.createSession(host, { typingsInstaller });
const session = createSession(host, { typingsInstaller });
ts.projectSystem.openFilesForSession([file1, file2], session);
const compileFileRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.CompileOnSaveEmitFileRequestArgs>(CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path });
openFilesForSession([file1, file2], session);
const compileFileRequest = makeSessionRequest<ts.server.protocol.CompileOnSaveEmitFileRequestArgs>(ts.server.CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path });
session.executeCommand(compileFileRequest);
const expectedEmittedFileName = "/a/b/f1.js";
@@ -700,12 +701,12 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
content: "console.log('file3');"
};
const externalProjectName = "/a/b/externalproject";
const host = ts.projectSystem.createServerHost([file1, file2, file3, ts.projectSystem.libFile]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([file1, file2, file3, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
projectService.openExternalProject({
rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]),
rootFiles: toExternalFiles([file1.path, file2.path]),
options: {
allowJs: true,
outFile: "dist.js",
@@ -714,7 +715,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
projectFileName: externalProjectName
});
const emitRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.CompileOnSaveEmitFileRequestArgs>(CommandNames.CompileOnSaveEmitFile, { file: file1.path });
const emitRequest = makeSessionRequest<ts.server.protocol.CompileOnSaveEmitFileRequestArgs>(ts.server.CommandNames.CompileOnSaveEmitFile, { file: file1.path });
session.executeCommand(emitRequest);
const expectedOutFileName = "/a/b/dist.js";
@@ -732,13 +733,13 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
content: "consonle.log('file1');"
};
const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj";
const host = ts.projectSystem.createServerHost([file1, ts.projectSystem.libFile]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([file1, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
const outFileName = "bar.js";
projectService.openExternalProject({
rootFiles: ts.projectSystem.toExternalFiles([file1.path]),
rootFiles: toExternalFiles([file1.path]),
options: {
outFile: outFileName,
sourceMap: true,
@@ -747,7 +748,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
projectFileName: externalProjectName
});
const emitRequest = ts.projectSystem.makeSessionRequest<ts.server.protocol.CompileOnSaveEmitFileRequestArgs>(CommandNames.CompileOnSaveEmitFile, { file: file1.path });
const emitRequest = makeSessionRequest<ts.server.protocol.CompileOnSaveEmitFileRequestArgs>(ts.server.CommandNames.CompileOnSaveEmitFile, { file: file1.path });
session.executeCommand(emitRequest);
// Verify js file
@@ -780,8 +781,8 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
});
function verify(richResponse: boolean | undefined) {
const config: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compileOnSave: true,
compilerOptions: {
@@ -792,27 +793,27 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
exclude: ["node_modules"]
})
};
const file1: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/file1.ts`,
const file1: File = {
path: `/user/username/projects/myproject/file1.ts`,
content: "const x = 1;"
};
const file2: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/file2.ts`,
const file2: File = {
path: `/user/username/projects/myproject/file2.ts`,
content: "const y = 2;"
};
const host = ts.projectSystem.createServerHost([file1, file2, config, ts.projectSystem.libFile]);
const session = ts.projectSystem.createSession(host);
ts.projectSystem.openFilesForSession([file1], session);
const host = createServerHost([file1, file2, config, libFile]);
const session = createSession(host);
openFilesForSession([file1], session);
const affectedFileResponse = session.executeCommandSeq<ts.projectSystem.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList,
const affectedFileResponse = session.executeCommandSeq<ts.server.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.server.protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: { file: file1.path }
}).response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[];
}).response as ts.server.protocol.CompileOnSaveAffectedFileListSingleProject[];
assert.deepEqual(affectedFileResponse, [
{ fileNames: [file1.path, file2.path], projectFileName: config.path, projectUsesOutFile: false }
]);
const file1SaveResponse = session.executeCommandSeq<ts.projectSystem.protocol.CompileOnSaveEmitFileRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile,
const file1SaveResponse = session.executeCommandSeq<ts.server.protocol.CompileOnSaveEmitFileRequest>({
command: ts.server.protocol.CommandTypes.CompileOnSaveEmitFile,
arguments: { file: file1.path, richResponse }
}).response;
if (richResponse) {
@@ -821,9 +822,9 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
else {
assert.isTrue(file1SaveResponse);
}
assert.strictEqual(host.readFile(`${ts.tscWatch.projectRoot}/test/file1.d.ts`), "declare const x = 1;\n");
const file2SaveResponse = session.executeCommandSeq<ts.projectSystem.protocol.CompileOnSaveEmitFileRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile,
assert.strictEqual(host.readFile(`/user/username/projects/myproject/test/file1.d.ts`), "declare const x = 1;\n");
const file2SaveResponse = session.executeCommandSeq<ts.server.protocol.CompileOnSaveEmitFileRequest>({
command: ts.server.protocol.CommandTypes.CompileOnSaveEmitFile,
arguments: { file: file2.path, richResponse }
}).response;
if (richResponse) {
@@ -833,7 +834,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
start: undefined,
end: undefined,
fileName: undefined,
text: ts.formatStringFromArgs(ts.Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.message, [`${ts.tscWatch.projectRoot}/test/file1.d.ts`]),
text: ts.formatStringFromArgs(ts.Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.message, [`/user/username/projects/myproject/test/file1.d.ts`]),
code: ts.Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.code,
category: ts.diagnosticCategoryName(ts.Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file),
reportsUnnecessary: undefined,
@@ -846,7 +847,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
else {
assert.isFalse(file2SaveResponse);
}
assert.isFalse(host.fileExists(`${ts.tscWatch.projectRoot}/test/file2.d.ts`));
assert.isFalse(host.fileExists(`/user/username/projects/myproject/test/file2.d.ts`));
}
});
@@ -867,9 +868,9 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
verifyGlobalSave(/*declaration*/ false, /*hasModule*/ false);
});
});
function verifyGlobalSave(declaration: boolean,hasModule: boolean) {
const config: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
function verifyGlobalSave(declaration: boolean, hasModule: boolean) {
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compileOnSave: true,
compilerOptions: {
@@ -878,37 +879,37 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
},
})
};
const file1: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/file1.ts`,
const file1: File = {
path: `/user/username/projects/myproject/file1.ts`,
content: `const x = 1;
function foo() {
return "hello";
}`
};
const file2: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/file2.ts`,
const file2: File = {
path: `/user/username/projects/myproject/file2.ts`,
content: `const y = 2;
function bar() {
return "world";
}`
};
const file3: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/file3.ts`,
const file3: File = {
path: `/user/username/projects/myproject/file3.ts`,
content: "const xy = 3;"
};
const module: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/module.ts`,
const module: File = {
path: `/user/username/projects/myproject/module.ts`,
content: "export const xyz = 4;"
};
const files = [file1, file2, file3, ...(hasModule ? [module] : ts.emptyArray)];
const host = ts.projectSystem.createServerHost([...files, config, ts.projectSystem.libFile]);
const session = ts.projectSystem.createSession(host);
ts.projectSystem.openFilesForSession([file1, file2], session);
const host = createServerHost([...files, config, libFile]);
const session = createSession(host);
openFilesForSession([file1, file2], session);
const affectedFileResponse = session.executeCommandSeq<ts.projectSystem.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList,
const affectedFileResponse = session.executeCommandSeq<ts.server.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.server.protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: { file: file1.path }
}).response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[];
}).response as ts.server.protocol.CompileOnSaveAffectedFileListSingleProject[];
assert.deepEqual(affectedFileResponse, [
{ fileNames: files.map(f => f.path), projectFileName: config.path, projectUsesOutFile: false }
]);
@@ -925,9 +926,9 @@ function bar() {
// Change file2 get affected file list = will return only file2 if --declaration otherwise all files
verifyLocalEdit(file2, "world", "hello", /*returnsAllFilesAsAffected*/ !declaration);
function verifyFileSave(file: ts.projectSystem.File) {
const response = session.executeCommandSeq<ts.projectSystem.protocol.CompileOnSaveEmitFileRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile,
function verifyFileSave(file: File) {
const response = session.executeCommandSeq<ts.server.protocol.CompileOnSaveEmitFileRequest>({
command: ts.server.protocol.CommandTypes.CompileOnSaveEmitFile,
arguments: { file: file.path }
}).response;
assert.isTrue(response);
@@ -948,24 +949,24 @@ function bar() {
}
}
function verifyLocalEdit(file: ts.projectSystem.File, oldText: string, newText: string, returnsAllFilesAsAffected?: boolean) {
function verifyLocalEdit(file: File, oldText: string, newText: string, returnsAllFilesAsAffected?: boolean) {
// Change file1 get affected file list
session.executeCommandSeq<ts.projectSystem.protocol.UpdateOpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.UpdateOpen,
session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
changedFiles: [{
fileName: file.path,
textChanges: [{
newText,
...ts.projectSystem.protocolTextSpanFromSubstring(file.content, oldText)
...protocolTextSpanFromSubstring(file.content, oldText)
}]
}]
}
});
const affectedFileResponse = session.executeCommandSeq<ts.projectSystem.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList,
const affectedFileResponse = session.executeCommandSeq<ts.server.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.server.protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: { file: file.path }
}).response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[];
}).response as ts.server.protocol.CompileOnSaveAffectedFileListSingleProject[];
assert.deepEqual(affectedFileResponse, [
{ fileNames: [file.path, ...(returnsAllFilesAsAffected ? files.filter(f => f !== file).map(f => f.path) : ts.emptyArray)], projectFileName: config.path, projectUsesOutFile: false }
]);
@@ -977,9 +978,9 @@ function bar() {
});
describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => {
function insertString(session: ts.projectSystem.TestSession, file: ts.projectSystem.File) {
session.executeCommandSeq<ts.projectSystem.protocol.ChangeRequest>({
command: ts.projectSystem.protocol.CommandTypes.Change,
function insertString(session: TestSession, file: File) {
session.executeCommandSeq<ts.server.protocol.ChangeRequest>({
command: ts.server.protocol.CommandTypes.Change,
arguments: {
file: file.path,
line: 1,
@@ -991,61 +992,61 @@ describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRe
});
}
function logDirtyOfProjects(session: ts.projectSystem.TestSession) {
session.logger.logs.push(`Project1 is dirty: ${session.getProjectService().configuredProjects.get(`${ts.tscWatch.projectRoot}/app1/tsconfig.json`)!.dirty}`);
session.logger.logs.push(`Project2 is dirty: ${session.getProjectService().configuredProjects.get(`${ts.tscWatch.projectRoot}/app2/tsconfig.json`)!.dirty}`);
function logDirtyOfProjects(session: TestSession) {
session.logger.logs.push(`Project1 is dirty: ${session.getProjectService().configuredProjects.get(`/user/username/projects/myproject/app1/tsconfig.json`)!.dirty}`);
session.logger.logs.push(`Project2 is dirty: ${session.getProjectService().configuredProjects.get(`/user/username/projects/myproject/app2/tsconfig.json`)!.dirty}`);
}
function verify(subScenario: string, commandArgs: ts.projectSystem.protocol.FileRequestArgs) {
function verify(subScenario: string, commandArgs: ts.server.protocol.FileRequestArgs) {
it(subScenario, () => {
const core: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/core/core.ts`,
const core: File = {
path: `/user/username/projects/myproject/core/core.ts`,
content: "let z = 10;"
};
const app1: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/app1/app.ts`,
const app1: File = {
path: `/user/username/projects/myproject/app1/app.ts`,
content: "let x = 10;"
};
const app2: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/app2/app.ts`,
const app2: File = {
path: `/user/username/projects/myproject/app2/app.ts`,
content: "let y = 10;"
};
const app1Config: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/app1/tsconfig.json`,
const app1Config: File = {
path: `/user/username/projects/myproject/app1/tsconfig.json`,
content: JSON.stringify({
files: ["app.ts", "../core/core.ts"],
compilerOptions: { outFile: "build/output.js" },
compileOnSave: true
})
};
const app2Config: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/app2/tsconfig.json`,
const app2Config: File = {
path: `/user/username/projects/myproject/app2/tsconfig.json`,
content: JSON.stringify({
files: ["app.ts", "../core/core.ts"],
compilerOptions: { outFile: "build/output.js" },
compileOnSave: true
})
};
const files = [ts.projectSystem.libFile, core, app1, app2, app1Config, app2Config];
const host = ts.projectSystem.createServerHost(files);
const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
ts.projectSystem.openFilesForSession([app1, app2, core], session);
const files = [libFile, core, app1, app2, app1Config, app2Config];
const host = createServerHost(files);
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
openFilesForSession([app1, app2, core], session);
insertString(session, app1);
insertString(session, app2);
logDirtyOfProjects(session);
session.executeCommandSeq<ts.projectSystem.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList,
session.executeCommandSeq<ts.server.protocol.CompileOnSaveAffectedFileListRequest>({
command: ts.server.protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: commandArgs
});
logDirtyOfProjects(session);
ts.projectSystem.baselineTsserverLogs("compileOnSave", subScenario, session);
baselineTsserverLogs("compileOnSave", subScenario, session);
});
}
verify("CompileOnSaveAffectedFileListRequest when projectFile is specified", {
file: `${ts.tscWatch.projectRoot}/core/core.ts`,
projectFileName: `${ts.tscWatch.projectRoot}/app1/tsconfig.json`
file: `/user/username/projects/myproject/core/core.ts`,
projectFileName: `/user/username/projects/myproject/app1/tsconfig.json`
});
verify("CompileOnSaveAffectedFileListRequest when projectFile is not specified", {
file: `${ts.tscWatch.projectRoot}/core/core.ts`,
file: `/user/username/projects/myproject/core/core.ts`,
});
});

View File

@@ -1,35 +1,37 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File, libFile } from "../virtualFileSystemWithWatch";
import { createSession, openFilesForSession, executeSessionRequest, TestTypingsInstaller, checkNumberOfProjects, checkProjectActualFiles } from "./helpers";
describe("unittests:: tsserver:: completions", () => {
it("works", () => {
const aTs: ts.projectSystem.File = {
const aTs: File = {
path: "/a.ts",
content: "export const foo = 0;",
};
const bTs: ts.projectSystem.File = {
const bTs: File = {
path: "/b.ts",
content: "foo",
};
const tsconfig: ts.projectSystem.File = {
const tsconfig: File = {
path: "/tsconfig.json",
content: "{}",
};
const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([aTs, bTs, tsconfig]));
ts.projectSystem.openFilesForSession([aTs, bTs], session);
const session = createSession(createServerHost([aTs, bTs, tsconfig]));
openFilesForSession([aTs, bTs], session);
const requestLocation: ts.projectSystem.protocol.FileLocationRequestArgs = {
const requestLocation: ts.server.protocol.FileLocationRequestArgs = {
file: bTs.path,
line: 1,
offset: 3,
};
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.CompletionsRequest, ts.projectSystem.protocol.CompletionInfoResponse>(session, ts.projectSystem.protocol.CommandTypes.CompletionInfo, {
const response = executeSessionRequest<ts.server.protocol.CompletionsRequest, ts.server.protocol.CompletionInfoResponse>(session, ts.server.protocol.CommandTypes.CompletionInfo, {
...requestLocation,
includeExternalModuleExports: true,
prefix: "foo",
});
const entry: ts.projectSystem.protocol.CompletionEntry = {
const entry: ts.server.protocol.CompletionEntry = {
hasAction: true,
insertText: undefined,
isRecommended: undefined,
@@ -52,7 +54,7 @@ describe("unittests:: tsserver:: completions", () => {
const exportMapKey = (response?.entries[0].data as any)?.exportMapKey;
assert.isString(exportMapKey);
delete (response?.entries[0].data as any).exportMapKey;
assert.deepEqual<ts.projectSystem.protocol.CompletionInfo | undefined>(response, {
assert.deepEqual<ts.server.protocol.CompletionInfo | undefined>(response, {
flags: ts.CompletionInfoFlags.MayIncludeAutoImports,
isGlobalCompletion: true,
isIncomplete: undefined,
@@ -62,13 +64,13 @@ describe("unittests:: tsserver:: completions", () => {
entries: [entry],
});
const detailsRequestArgs: ts.projectSystem.protocol.CompletionDetailsRequestArgs = {
const detailsRequestArgs: ts.server.protocol.CompletionDetailsRequestArgs = {
...requestLocation,
entryNames: [{ name: "foo", source: "/a", data: { exportName: "foo", fileName: "/a.ts", exportMapKey } }],
};
const detailsResponse = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.CompletionDetailsRequest, ts.projectSystem.protocol.CompletionDetailsResponse>(session, ts.projectSystem.protocol.CommandTypes.CompletionDetails, detailsRequestArgs);
const detailsCommon: ts.projectSystem.protocol.CompletionEntryDetails & ts.CompletionEntryDetails = {
const detailsResponse = executeSessionRequest<ts.server.protocol.CompletionDetailsRequest, ts.server.protocol.CompletionDetailsResponse>(session, ts.server.protocol.CommandTypes.CompletionDetails, detailsRequestArgs);
const detailsCommon: ts.server.protocol.CompletionEntryDetails & ts.CompletionEntryDetails = {
displayParts: [
ts.keywordPart(ts.SyntaxKind.ConstKeyword),
ts.spacePart(),
@@ -84,7 +86,7 @@ describe("unittests:: tsserver:: completions", () => {
source: [{ text: "./a", kind: "text" }],
sourceDisplay: [{ text: "./a", kind: "text" }],
};
assert.deepEqual<readonly ts.projectSystem.protocol.CompletionEntryDetails[] | undefined>(detailsResponse, [
assert.deepEqual<readonly ts.server.protocol.CompletionEntryDetails[] | undefined>(detailsResponse, [
{
codeActions: [
{
@@ -109,14 +111,14 @@ describe("unittests:: tsserver:: completions", () => {
},
]);
interface CompletionDetailsFullRequest extends ts.projectSystem.protocol.FileLocationRequest {
readonly command: ts.projectSystem.protocol.CommandTypes.CompletionDetailsFull;
readonly arguments: ts.projectSystem.protocol.CompletionDetailsRequestArgs;
interface CompletionDetailsFullRequest extends ts.server.protocol.FileLocationRequest {
readonly command: ts.server.protocol.CommandTypes.CompletionDetailsFull;
readonly arguments: ts.server.protocol.CompletionDetailsRequestArgs;
}
interface CompletionDetailsFullResponse extends ts.projectSystem.protocol.Response {
interface CompletionDetailsFullResponse extends ts.server.protocol.Response {
readonly body?: readonly ts.CompletionEntryDetails[];
}
const detailsFullResponse = ts.projectSystem.executeSessionRequest<CompletionDetailsFullRequest, CompletionDetailsFullResponse>(session, ts.projectSystem.protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs);
const detailsFullResponse = executeSessionRequest<CompletionDetailsFullRequest, CompletionDetailsFullResponse>(session, ts.server.protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs);
assert.deepEqual<readonly ts.CompletionEntryDetails[] | undefined>(detailsFullResponse, [
{
codeActions: [
@@ -139,7 +141,7 @@ describe("unittests:: tsserver:: completions", () => {
it("works when files are included from two different drives of windows", () => {
const projectRoot = "e:/myproject";
const appPackage: ts.projectSystem.File = {
const appPackage: File = {
path: `${projectRoot}/package.json`,
content: JSON.stringify({
name: "test",
@@ -150,7 +152,7 @@ describe("unittests:: tsserver:: completions", () => {
}
})
};
const appFile: ts.projectSystem.File = {
const appFile: File = {
path: `${projectRoot}/src/app.js`,
content: `import React from 'react';
import {
@@ -160,37 +162,37 @@ import {
};
const localNodeModules = `${projectRoot}/node_modules`;
const localAtTypes = `${localNodeModules}/@types`;
const localReactPackage: ts.projectSystem.File = {
const localReactPackage: File = {
path: `${localAtTypes}/react/package.json`,
content: JSON.stringify({
name: "@types/react",
version: "16.9.14",
})
};
const localReact: ts.projectSystem.File = {
const localReact: File = {
path: `${localAtTypes}/react/index.d.ts`,
content: `import * as PropTypes from 'prop-types';
`
};
const localReactRouterDomPackage: ts.projectSystem.File = {
const localReactRouterDomPackage: File = {
path: `${localNodeModules}/react-router-dom/package.json`,
content: JSON.stringify({
name: "react-router-dom",
version: "5.1.2",
})
};
const localReactRouterDom: ts.projectSystem.File = {
const localReactRouterDom: File = {
path: `${localNodeModules}/react-router-dom/index.js`,
content: `export function foo() {}`
};
const localPropTypesPackage: ts.projectSystem.File = {
const localPropTypesPackage: File = {
path: `${localAtTypes}/prop-types/package.json`,
content: JSON.stringify({
name: "@types/prop-types",
version: "15.7.3",
})
};
const localPropTypes: ts.projectSystem.File = {
const localPropTypes: File = {
path: `${localAtTypes}/prop-types/index.d.ts`,
content: `export type ReactComponentLike =
| string
@@ -201,14 +203,14 @@ import {
const globalCacheLocation = `c:/typescript`;
const globalAtTypes = `${globalCacheLocation}/node_modules/@types`;
const globalReactRouterDomPackage: ts.projectSystem.File = {
const globalReactRouterDomPackage: File = {
path: `${globalAtTypes}/react-router-dom/package.json`,
content: JSON.stringify({
name: "@types/react-router-dom",
version: "5.1.2",
})
};
const globalReactRouterDom: ts.projectSystem.File = {
const globalReactRouterDom: File = {
path: `${globalAtTypes}/react-router-dom/index.d.ts`,
content: `import * as React from 'react';
export interface BrowserRouterProps {
@@ -218,11 +220,11 @@ export interface BrowserRouterProps {
keyLength?: number;
}`
};
const globalReactPackage: ts.projectSystem.File = {
const globalReactPackage: File = {
path: `${globalAtTypes}/react/package.json`,
content: localReactPackage.content
};
const globalReact: ts.projectSystem.File = {
const globalReact: File = {
path: `${globalAtTypes}/react/index.d.ts`,
content: localReact.content
};
@@ -236,7 +238,7 @@ export interface BrowserRouterProps {
];
const files = [
...filesInProject,
appPackage, ts.projectSystem.libFile,
appPackage, libFile,
localReactPackage,
localReactRouterDomPackage, localReactRouterDom,
localPropTypesPackage,
@@ -244,17 +246,17 @@ export interface BrowserRouterProps {
globalReactPackage,
];
const host = ts.projectSystem.createServerHost(files, { windowsStyleRoot: "c:/" });
const session = ts.projectSystem.createSession(host, {
typingsInstaller: new ts.projectSystem.TestTypingsInstaller(globalCacheLocation, /*throttleLimit*/ 5, host),
const host = createServerHost(files, { windowsStyleRoot: "c:/" });
const session = createSession(host, {
typingsInstaller: new TestTypingsInstaller(globalCacheLocation, /*throttleLimit*/ 5, host),
});
const service = session.getProjectService();
ts.projectSystem.openFilesForSession([appFile], session);
ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 });
const windowsStyleLibFilePath = "c:/" + ts.projectSystem.libFile.path.substring(1);
ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], filesInProject.map(f => f.path).concat(windowsStyleLibFilePath));
session.executeCommandSeq<ts.projectSystem.protocol.CompletionsRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompletionInfo,
openFilesForSession([appFile], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
const windowsStyleLibFilePath = "c:/" + libFile.path.substring(1);
checkProjectActualFiles(service.inferredProjects[0], filesInProject.map(f => f.path).concat(windowsStyleLibFilePath));
session.executeCommandSeq<ts.server.protocol.CompletionsRequest>({
command: ts.server.protocol.CommandTypes.CompletionInfo,
arguments: {
file: appFile.path,
line: 5,

View File

@@ -1,20 +1,22 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File } from "../virtualFileSystemWithWatch";
import { openFilesForSession, createSession } from "./helpers";
function createExportingModuleFile(path: string, exportPrefix: string, exportCount: number): ts.projectSystem.File {
function createExportingModuleFile(path: string, exportPrefix: string, exportCount: number): File {
return {
path,
content: ts.fill(exportCount, i => `export const ${exportPrefix}_${i} = ${i};`).join("\n"),
};
}
function createExportingModuleFiles(pathPrefix: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): ts.projectSystem.File[] {
function createExportingModuleFiles(pathPrefix: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): File[] {
return ts.fill(fileCount, fileIndex => createExportingModuleFile(
`${pathPrefix}_${fileIndex}.ts`,
getExportPrefix(fileIndex),
exportCount));
}
function createNodeModulesPackage(packageName: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): ts.projectSystem.File[] {
function createNodeModulesPackage(packageName: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): File[] {
const exportingFiles = createExportingModuleFiles(`/node_modules/${packageName}/file`, fileCount, exportCount, getExportPrefix);
return [
{
@@ -31,17 +33,17 @@ function createNodeModulesPackage(packageName: string, fileCount: number, export
];
}
const indexFile: ts.projectSystem.File = {
const indexFile: File = {
path: "/index.ts",
content: ""
};
const tsconfigFile: ts.projectSystem.File = {
const tsconfigFile: File = {
path: "/tsconfig.json",
content: `{ "compilerOptions": { "module": "commonjs" } }`
};
const packageJsonFile: ts.projectSystem.File = {
const packageJsonFile: File = {
path: "/package.json",
content: `{ "dependencies": { "dep-a": "*" } }`,
};
@@ -51,27 +53,27 @@ describe("unittests:: tsserver:: completionsIncomplete", () => {
const excessFileCount = ts.Completions.moduleSpecifierResolutionLimit + 50;
const exportingFiles = createExportingModuleFiles(`/lib/a`, ts.Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`);
const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]);
ts.projectSystem.openFilesForSession([indexFile], session);
openFilesForSession([indexFile], session);
typeToTriggerCompletions(indexFile.path, "a", completions => {
assert(completions.isIncomplete);
assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), ts.Completions.moduleSpecifierResolutionLimit);
assert.lengthOf(completions.entries.filter(entry => entry.source && !(entry.data as any)?.moduleSpecifier), excessFileCount);
})
.continueTyping("a", completions => {
assert(completions.isIncomplete);
assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), ts.Completions.moduleSpecifierResolutionLimit * 2);
})
.continueTyping("_", completions => {
assert(!completions.isIncomplete);
assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), exportingFiles.length);
});
.continueTyping("a", completions => {
assert(completions.isIncomplete);
assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), ts.Completions.moduleSpecifierResolutionLimit * 2);
})
.continueTyping("_", completions => {
assert(!completions.isIncomplete);
assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), exportingFiles.length);
});
});
it("resolves more when available from module specifier cache (1)", () => {
const exportingFiles = createExportingModuleFiles(`/lib/a`, 50, 50, i => `aa_${i}_`);
const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]);
ts.projectSystem.openFilesForSession([indexFile], session);
openFilesForSession([indexFile], session);
typeToTriggerCompletions(indexFile.path, "a", completions => {
assert(!completions.isIncomplete);
@@ -82,7 +84,7 @@ describe("unittests:: tsserver:: completionsIncomplete", () => {
const excessFileCount = 50;
const exportingFiles = createExportingModuleFiles(`/lib/a`, ts.Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`);
const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]);
ts.projectSystem.openFilesForSession([indexFile], session);
openFilesForSession([indexFile], session);
typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete))
.backspace()
@@ -90,14 +92,14 @@ describe("unittests:: tsserver:: completionsIncomplete", () => {
});
it("ambient module specifier resolutions do not count against the resolution limit", () => {
const ambientFiles = ts.fill(100, (i): ts.projectSystem.File => ({
const ambientFiles = ts.fill(100, (i): File => ({
path: `/lib/ambient_${i}.ts`,
content: `declare module "ambient_${i}" { export const aa_${i} = ${i}; }`,
}));
const exportingFiles = createExportingModuleFiles(`/lib/a`, ts.Completions.moduleSpecifierResolutionLimit, 5, i => `aa_${i}_`);
const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...ambientFiles, ...exportingFiles]);
ts.projectSystem.openFilesForSession([indexFile], session);
openFilesForSession([indexFile], session);
typeToTriggerCompletions(indexFile.path, "a", completions => {
assert(!completions.isIncomplete);
@@ -109,7 +111,7 @@ describe("unittests:: tsserver:: completionsIncomplete", () => {
const exportingFiles = createExportingModuleFiles(`/lib/a`, ts.Completions.moduleSpecifierResolutionLimit, 1, i => `aa_${i}_`);
const nodeModulesPackage = createNodeModulesPackage("dep-a", 50, 1, i => `depA_${i}_`);
const { typeToTriggerCompletions, assertCompletionDetailsOk, session } = setup([tsconfigFile, packageJsonFile, indexFile, ...exportingFiles, ...nodeModulesPackage]);
ts.projectSystem.openFilesForSession([indexFile], session);
openFilesForSession([indexFile], session);
typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete))
.continueTyping("_", completions => {
@@ -122,7 +124,7 @@ describe("unittests:: tsserver:: completionsIncomplete", () => {
});
it("works for transient symbols between requests", () => {
const constantsDts: ts.projectSystem.File = {
const constantsDts: File = {
path: "/lib/foo/constants.d.ts",
content: `
type Signals = "SIGINT" | "SIGABRT";
@@ -131,26 +133,26 @@ describe("unittests:: tsserver:: completionsIncomplete", () => {
};
const exportingFiles = createExportingModuleFiles("/lib/a", ts.Completions.moduleSpecifierResolutionLimit, 1, i => `S${i}`);
const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles, constantsDts]);
ts.projectSystem.openFilesForSession([indexFile], session);
openFilesForSession([indexFile], session);
typeToTriggerCompletions(indexFile.path, "s", completions => {
const sigint = completions.entries.find(e => e.name === "SIGINT");
assert(sigint);
assert(!(sigint.data as any).moduleSpecifier);
})
.continueTyping("i", completions => {
const sigint = completions.entries.find(e => e.name === "SIGINT");
assert((sigint!.data as any).moduleSpecifier);
});
.continueTyping("i", completions => {
const sigint = completions.entries.find(e => e.name === "SIGINT");
assert((sigint!.data as any).moduleSpecifier);
});
});
});
function setup(files: ts.projectSystem.File[]) {
const host = ts.projectSystem.createServerHost(files);
const session = ts.projectSystem.createSession(host);
function setup(files: File[]) {
const host = createServerHost(files);
const session = createSession(host);
const projectService = session.getProjectService();
session.executeCommandSeq<ts.projectSystem.protocol.ConfigureRequest>({
command: ts.projectSystem.protocol.CommandTypes.Configure,
session.executeCommandSeq<ts.server.protocol.ConfigureRequest>({
command: ts.server.protocol.CommandTypes.Configure,
arguments: {
preferences: {
allowIncompleteCompletions: true,
@@ -164,16 +166,16 @@ function setup(files: ts.projectSystem.File[]) {
return { host, session, projectService, typeToTriggerCompletions, assertCompletionDetailsOk };
function typeToTriggerCompletions(fileName: string, typedCharacters: string, cb?: (completions: ts.projectSystem.protocol.CompletionInfo) => void) {
function typeToTriggerCompletions(fileName: string, typedCharacters: string, cb?: (completions: ts.server.protocol.CompletionInfo) => void) {
const project = projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!;
return type(typedCharacters, cb, /*isIncompleteContinuation*/ false);
function type(typedCharacters: string, cb: ((completions: ts.projectSystem.protocol.CompletionInfo) => void) | undefined, isIncompleteContinuation: boolean) {
function type(typedCharacters: string, cb: ((completions: ts.server.protocol.CompletionInfo) => void) | undefined, isIncompleteContinuation: boolean) {
const file = ts.Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName));
const { line, character } = ts.getLineAndCharacterOfPosition(file, file.text.length);
const oneBasedEditPosition = { line: line + 1, offset: character + 1 };
session.executeCommandSeq<ts.projectSystem.protocol.UpdateOpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.UpdateOpen,
session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
changedFiles: [{
fileName,
@@ -186,22 +188,22 @@ function setup(files: ts.projectSystem.File[]) {
},
});
const response = session.executeCommandSeq<ts.projectSystem.protocol.CompletionsRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompletionInfo,
const response = session.executeCommandSeq<ts.server.protocol.CompletionsRequest>({
command: ts.server.protocol.CommandTypes.CompletionInfo,
arguments: {
file: fileName,
line: oneBasedEditPosition.line,
offset: oneBasedEditPosition.offset,
triggerKind: isIncompleteContinuation
? ts.projectSystem.protocol.CompletionTriggerKind.TriggerForIncompleteCompletions
? ts.server.protocol.CompletionTriggerKind.TriggerForIncompleteCompletions
: undefined,
}
}).response as ts.projectSystem.protocol.CompletionInfo;
}).response as ts.server.protocol.CompletionInfo;
cb?.(ts.Debug.checkDefined(response));
return {
backspace,
continueTyping: (typedCharacters: string, cb: (completions: ts.projectSystem.protocol.CompletionInfo) => void) => {
continueTyping: (typedCharacters: string, cb: (completions: ts.server.protocol.CompletionInfo) => void) => {
return type(typedCharacters, cb, !!response.isIncomplete);
},
};
@@ -213,8 +215,8 @@ function setup(files: ts.projectSystem.File[]) {
const endLineCharacter = ts.getLineAndCharacterOfPosition(file, file.text.length);
const oneBasedStartPosition = { line: startLineCharacter.line + 1, offset: startLineCharacter.character + 1 };
const oneBasedEndPosition = { line: endLineCharacter.line + 1, offset: endLineCharacter.character + 1 };
session.executeCommandSeq<ts.projectSystem.protocol.UpdateOpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.UpdateOpen,
session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
changedFiles: [{
fileName,
@@ -229,19 +231,19 @@ function setup(files: ts.projectSystem.File[]) {
return {
backspace,
type: (typedCharacters: string, cb: (completions: ts.projectSystem.protocol.CompletionInfo) => void) => {
type: (typedCharacters: string, cb: (completions: ts.server.protocol.CompletionInfo) => void) => {
return type(typedCharacters, cb, /*isIncompleteContinuation*/ false);
},
};
}
}
function assertCompletionDetailsOk(fileName: string, entry: ts.projectSystem.protocol.CompletionEntry) {
function assertCompletionDetailsOk(fileName: string, entry: ts.server.protocol.CompletionEntry) {
const project = projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!;
const file = ts.Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName));
const { line, character } = ts.getLineAndCharacterOfPosition(file, file.text.length - 1);
const details = session.executeCommandSeq<ts.projectSystem.protocol.CompletionDetailsRequest>({
command: ts.projectSystem.protocol.CommandTypes.CompletionDetails,
const details = session.executeCommandSeq<ts.server.protocol.CompletionDetailsRequest>({
command: ts.server.protocol.CommandTypes.CompletionDetails,
arguments: {
file: fileName,
line: line + 1,
@@ -252,7 +254,7 @@ function setup(files: ts.projectSystem.File[]) {
data: entry.data,
}]
}
}).response as ts.projectSystem.protocol.CompletionEntryDetails[];
}).response as ts.server.protocol.CompletionEntryDetails[];
assert(details[0]);
assert(details[0].codeActions);

View File

@@ -1,4 +1,5 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File, libFile } from "../virtualFileSystemWithWatch";
import { createProjectService, checkNumberOfConfiguredProjects, checkNumberOfInferredProjects, createLoggerWithInMemoryLogs, checkNumberOfProjects, baselineTsserverLogs } from "./helpers";
describe("unittests:: tsserver:: searching for config file", () => {
it("should stop at projectRootPath if given", () => {
@@ -10,17 +11,17 @@ describe("unittests:: tsserver:: searching for config file", () => {
path: "/tsconfig.json",
content: "{}"
};
const host = ts.projectSystem.createServerHost([f1, configFile]);
const service = ts.projectSystem.createProjectService(host);
const host = createServerHost([f1, configFile]);
const service = createProjectService(host);
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a");
ts.projectSystem.checkNumberOfConfiguredProjects(service, 0);
ts.projectSystem.checkNumberOfInferredProjects(service, 1);
checkNumberOfConfiguredProjects(service, 0);
checkNumberOfInferredProjects(service, 1);
service.closeClientFile(f1.path);
service.openClientFile(f1.path);
ts.projectSystem.checkNumberOfConfiguredProjects(service, 1);
ts.projectSystem.checkNumberOfInferredProjects(service, 0);
checkNumberOfConfiguredProjects(service, 1);
checkNumberOfInferredProjects(service, 0);
});
it("should use projectRootPath when searching for inferred project again", () => {
@@ -38,15 +39,15 @@ describe("unittests:: tsserver:: searching for config file", () => {
path: "/a/b/projects/tsconfig.json",
content: "{}"
};
const host = ts.projectSystem.createServerHost([f1, ts.projectSystem.libFile, configFile, configFile2]);
const service = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
const host = createServerHost([f1, libFile, configFile, configFile2]);
const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir);
// Delete config file - should create inferred project and not configured project
host.deleteFile(configFile.path);
host.runQueuedTimeoutCallbacks();
ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 });
ts.projectSystem.baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again", service);
checkNumberOfProjects(service, { inferredProjects: 1 });
baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again", service);
});
it("should use projectRootPath when searching for inferred project again 2", () => {
@@ -64,39 +65,39 @@ describe("unittests:: tsserver:: searching for config file", () => {
path: "/a/b/projects/tsconfig.json",
content: "{}"
};
const host = ts.projectSystem.createServerHost([f1, ts.projectSystem.libFile, configFile, configFile2]);
const service = ts.projectSystem.createProjectService(host, {
const host = createServerHost([f1, libFile, configFile, configFile2]);
const service = createProjectService(host, {
useSingleInferredProject: true,
useInferredProjectPerProjectRoot: true,
logger: ts.projectSystem.createLoggerWithInMemoryLogs(host),
logger: createLoggerWithInMemoryLogs(host),
});
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir);
// Delete config file - should create inferred project with project root path set
host.deleteFile(configFile.path);
host.runQueuedTimeoutCallbacks();
ts.projectSystem.baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again 2", service);
baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again 2", service);
});
describe("when the opened file is not from project root", () => {
const projectRoot = "/a/b/projects/project";
const file: ts.projectSystem.File = {
const file: File = {
path: `${projectRoot}/src/index.ts`,
content: "let y = 10"
};
const tsconfig: ts.projectSystem.File = {
const tsconfig: File = {
path: `${projectRoot}/tsconfig.json`,
content: "{}"
};
function openClientFile(files: ts.projectSystem.File[]) {
const host = ts.projectSystem.createServerHost(files);
const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
function openClientFile(files: File[]) {
const host = createServerHost(files);
const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj");
return { host, projectService };
}
it("tsconfig for the file exists", () => {
const { host, projectService } = openClientFile([file, ts.projectSystem.libFile, tsconfig]);
const { host, projectService } = openClientFile([file, libFile, tsconfig]);
host.deleteFile(tsconfig.path);
host.runQueuedTimeoutCallbacks();
@@ -104,11 +105,11 @@ describe("unittests:: tsserver:: searching for config file", () => {
host.writeFile(tsconfig.path, tsconfig.content);
host.runQueuedTimeoutCallbacks();
ts.projectSystem.baselineTsserverLogs("configFileSearch", "tsconfig for the file exists", projectService);
baselineTsserverLogs("configFileSearch", "tsconfig for the file exists", projectService);
});
it("tsconfig for the file does not exist", () => {
const { host, projectService } = openClientFile([file, ts.projectSystem.libFile]);
const { host, projectService } = openClientFile([file, libFile]);
host.writeFile(tsconfig.path, tsconfig.content);
host.runQueuedTimeoutCallbacks();
@@ -116,7 +117,7 @@ describe("unittests:: tsserver:: searching for config file", () => {
host.deleteFile(tsconfig.path);
host.runQueuedTimeoutCallbacks();
ts.projectSystem.baselineTsserverLogs("configFileSearch", "tsconfig for the file does not exist", projectService);
baselineTsserverLogs("configFileSearch", "tsconfig for the file does not exist", projectService);
});
});
@@ -124,10 +125,10 @@ describe("unittests:: tsserver:: searching for config file", () => {
function verifyConfigFileWatch(scenario: string, projectRootPath: string | undefined) {
it(scenario, () => {
const path = `/root/teams/VSCode68/Shared Documents/General/jt-ts-test-workspace/x.js`;
const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, { path, content: "const x = 10" }], { useCaseSensitiveFileNames: true });
const service = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
const host = createServerHost([libFile, { path, content: "const x = 10" }], { useCaseSensitiveFileNames: true });
const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
service.openClientFile(path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRootPath);
ts.projectSystem.baselineTsserverLogs("configFileSearch", scenario, service);
baselineTsserverLogs("configFileSearch", scenario, service);
});
}
verifyConfigFileWatch("when projectRootPath is not present", /*projectRootPath*/ undefined);

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,21 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File } from "../virtualFileSystemWithWatch";
import { DocumentSpanFromSubstring, textSpanFromSubstring, TestSession, openFilesForSession, closeFilesForSession, createSession, checkNumberOfProjects, checkProjectActualFiles, executeSessionRequest, protocolFileLocationFromSubstring, protocolFileSpanWithContextFromSubstring, protocolTextSpanFromSubstring, protocolFileSpanFromSubstring, makeReferenceItem, protocolLocationFromSubstring, protocolRenameSpanFromSubstring } from "./helpers";
function documentSpanFromSubstring({ file, text, contextText, options, contextOptions }: ts.projectSystem.DocumentSpanFromSubstring): ts.DocumentSpan {
function documentSpanFromSubstring({ file, text, contextText, options, contextOptions }: DocumentSpanFromSubstring): ts.DocumentSpan {
const contextSpan = contextText !== undefined ? documentSpanFromSubstring({ file, text: contextText, options: contextOptions }) : undefined;
return {
fileName: file.path,
textSpan: ts.projectSystem.textSpanFromSubstring(file.content, text, options),
textSpan: textSpanFromSubstring(file.content, text, options),
...contextSpan && { contextSpan: contextSpan.textSpan }
};
}
function renameLocation(input: ts.projectSystem.DocumentSpanFromSubstring): ts.RenameLocation {
function renameLocation(input: DocumentSpanFromSubstring): ts.RenameLocation {
return documentSpanFromSubstring(input);
}
interface MakeReferenceEntry extends ts.projectSystem.DocumentSpanFromSubstring {
interface MakeReferenceEntry extends DocumentSpanFromSubstring {
isDefinition?: boolean;
isWriteAccess?: boolean;
}
@@ -30,19 +32,19 @@ function makeReferencedSymbolEntry({ isDefinition, isWriteAccess, ...rest }: Mak
return result;
}
function checkDeclarationFiles(file: ts.projectSystem.File, session: ts.projectSystem.TestSession, expectedFiles: readonly ts.projectSystem.File[]): void {
ts.projectSystem.openFilesForSession([file], session);
function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: readonly File[]): void {
openFilesForSession([file], session);
const project = ts.Debug.checkDefined(session.getProjectService().getDefaultProjectForFile(file.path as ts.server.NormalizedPath, /*ensureProject*/ false));
const program = project.getCurrentProgram()!;
const output = ts.getFileEmitOutput(program, ts.Debug.checkDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true);
ts.projectSystem.closeFilesForSession([file], session);
closeFilesForSession([file], session);
ts.Debug.assert(!output.emitSkipped);
assert.deepEqual(output.outputFiles, expectedFiles.map((e): ts.OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false })));
}
describe("unittests:: tsserver:: with declaration file maps:: project references", () => {
const aTs: ts.projectSystem.File = {
const aTs: File = {
path: "/a/a.ts",
content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};",
};
@@ -53,7 +55,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
composite: true,
};
const configContent = JSON.stringify({ compilerOptions });
const aTsconfig: ts.projectSystem.File = { path: "/a/tsconfig.json", content: configContent };
const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent };
const aDtsMapContent: ts.RawSourceMap = {
version: 3,
@@ -63,21 +65,21 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
names: [],
mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC"
};
const aDtsMap: ts.projectSystem.File = {
const aDtsMap: File = {
path: "/a/bin/a.d.ts.map",
content: JSON.stringify(aDtsMapContent),
};
const aDts: ts.projectSystem.File = {
const aDts: File = {
path: "/a/bin/a.d.ts",
// ${""} is needed to mangle the sourceMappingURL part or it breaks the build
content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`,
};
const bTs: ts.projectSystem.File = {
const bTs: File = {
path: "/b/b.ts",
content: "export function fnB() {}",
};
const bTsconfig: ts.projectSystem.File = { path: "/b/tsconfig.json", content: configContent };
const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent };
const bDtsMapContent: ts.RawSourceMap = {
version: 3,
@@ -87,32 +89,32 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
names: [],
mappings: "AAAA,wBAAgB,GAAG,SAAK",
};
const bDtsMap: ts.projectSystem.File = {
const bDtsMap: File = {
path: "/b/bin/b.d.ts.map",
content: JSON.stringify(bDtsMapContent),
};
const bDts: ts.projectSystem.File = {
const bDts: File = {
// ${""} is need to mangle the sourceMappingURL part so it doesn't break the build
path: "/b/bin/b.d.ts",
content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`,
};
const dummyFile: ts.projectSystem.File = {
const dummyFile: File = {
path: "/dummy/dummy.ts",
content: "let a = 10;"
};
const userTs: ts.projectSystem.File = {
const userTs: File = {
path: "/user/user.ts",
content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }',
};
const userTsForConfigProject: ts.projectSystem.File = {
const userTsForConfigProject: File = {
path: "/user/user.ts",
content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }',
};
const userTsconfig: ts.projectSystem.File = {
const userTsconfig: File = {
path: "/user/tsconfig.json",
content: JSON.stringify({
file: ["user.ts"],
@@ -121,8 +123,8 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
};
function makeSampleProjects(addUserTsConfig?: boolean, keepAllFiles?: boolean) {
const host = ts.projectSystem.createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]);
const session = createSession(host);
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
checkDeclarationFiles(bTs, session, [bDtsMap, bDts]);
@@ -132,70 +134,70 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
host.deleteFile(bTs.path);
}
ts.projectSystem.openFilesForSession([userTs], session);
openFilesForSession([userTs], session);
const service = session.getProjectService();
// If config file then userConfig project and bConfig project since it is referenced
ts.projectSystem.checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 2 } : { inferredProjects: 1 });
checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 2 } : { inferredProjects: 1 });
return session;
}
function verifyInferredProjectUnchanged(session: ts.projectSystem.TestSession) {
ts.projectSystem.checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]);
function verifyInferredProjectUnchanged(session: TestSession) {
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]);
}
function verifyDummyProject(session: ts.projectSystem.TestSession) {
ts.projectSystem.checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]);
function verifyDummyProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]);
}
function verifyOnlyOrphanInferredProject(session: ts.projectSystem.TestSession) {
ts.projectSystem.openFilesForSession([dummyFile], session);
ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
function verifyOnlyOrphanInferredProject(session: TestSession) {
openFilesForSession([dummyFile], session);
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
verifyDummyProject(session);
}
function verifySingleInferredProject(session: ts.projectSystem.TestSession) {
ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
function verifySingleInferredProject(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
verifyInferredProjectUnchanged(session);
// Close user file should close all the projects after opening dummy file
ts.projectSystem.closeFilesForSession([userTs], session);
closeFilesForSession([userTs], session);
verifyOnlyOrphanInferredProject(session);
}
function verifyATsConfigProject(session: ts.projectSystem.TestSession) {
ts.projectSystem.checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]);
function verifyATsConfigProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]);
}
function verifyATsConfigOriginalProject(session: ts.projectSystem.TestSession) {
ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
function verifyATsConfigOriginalProject(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyInferredProjectUnchanged(session);
verifyATsConfigProject(session);
// Close user file should close all the projects
ts.projectSystem.closeFilesForSession([userTs], session);
closeFilesForSession([userTs], session);
verifyOnlyOrphanInferredProject(session);
}
function verifyATsConfigWhenOpened(session: ts.projectSystem.TestSession) {
ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
function verifyATsConfigWhenOpened(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyInferredProjectUnchanged(session);
verifyATsConfigProject(session);
ts.projectSystem.closeFilesForSession([userTs], session);
ts.projectSystem.openFilesForSession([dummyFile], session);
ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
closeFilesForSession([userTs], session);
openFilesForSession([dummyFile], session);
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyDummyProject(session);
verifyATsConfigProject(session); // ATsConfig should still be alive
}
function verifyUserTsConfigProject(session: ts.projectSystem.TestSession) {
ts.projectSystem.checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]);
function verifyUserTsConfigProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]);
}
it("goToDefinition", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.DefinitionRequest, ts.projectSystem.protocol.DefinitionResponse>(session, ts.projectSystem.protocol.CommandTypes.Definition, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()"));
const response = executeSessionRequest<ts.server.protocol.DefinitionRequest, ts.server.protocol.DefinitionResponse>(session, ts.server.protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, [
ts.projectSystem.protocolFileSpanWithContextFromSubstring({
protocolFileSpanWithContextFromSubstring({
file: aTs,
text: "fnA",
contextText: "export function fnA() {}"
@@ -206,11 +208,11 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("getDefinitionAndBoundSpan", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.DefinitionAndBoundSpanRequest, ts.projectSystem.protocol.DefinitionAndBoundSpanResponse>(session, ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()"));
const response = executeSessionRequest<ts.server.protocol.DefinitionAndBoundSpanRequest, ts.server.protocol.DefinitionAndBoundSpanResponse>(session, ts.server.protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, {
textSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnA"),
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
definitions: [
ts.projectSystem.protocolFileSpanWithContextFromSubstring({
protocolFileSpanWithContextFromSubstring({
file: aTs,
text: "fnA",
contextText: "export function fnA() {}"
@@ -222,38 +224,38 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("getDefinitionAndBoundSpan with file navigation", () => {
const session = makeSampleProjects(/*addUserTsConfig*/ true);
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.DefinitionAndBoundSpanRequest, ts.projectSystem.protocol.DefinitionAndBoundSpanResponse>(session, ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()"));
const response = executeSessionRequest<ts.server.protocol.DefinitionAndBoundSpanRequest, ts.server.protocol.DefinitionAndBoundSpanResponse>(session, ts.server.protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, {
textSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnA"),
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
definitions: [
ts.projectSystem.protocolFileSpanWithContextFromSubstring({
protocolFileSpanWithContextFromSubstring({
file: aTs,
text: "fnA",
contextText: "export function fnA() {}"
})
],
});
ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
verifyUserTsConfigProject(session);
// Navigate to the definition
ts.projectSystem.closeFilesForSession([userTs], session);
ts.projectSystem.openFilesForSession([aTs], session);
closeFilesForSession([userTs], session);
openFilesForSession([aTs], session);
// UserTs configured project should be alive
ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 });
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 });
verifyUserTsConfigProject(session);
verifyATsConfigProject(session);
ts.projectSystem.closeFilesForSession([aTs], session);
closeFilesForSession([aTs], session);
verifyOnlyOrphanInferredProject(session);
});
it("goToType", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.TypeDefinitionRequest, ts.projectSystem.protocol.TypeDefinitionResponse>(session, ts.projectSystem.protocol.CommandTypes.TypeDefinition, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "instanceA"));
const response = executeSessionRequest<ts.server.protocol.TypeDefinitionRequest, ts.server.protocol.TypeDefinitionResponse>(session, ts.server.protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA"));
assert.deepEqual(response, [
ts.projectSystem.protocolFileSpanWithContextFromSubstring({
protocolFileSpanWithContextFromSubstring({
file: aTs,
text: "IfaceA",
contextText: "export interface IfaceA {}"
@@ -264,9 +266,9 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("goToImplementation", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.ImplementationRequest, ts.projectSystem.protocol.ImplementationResponse>(session, ts.projectSystem.protocol.CommandTypes.Implementation, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()"));
const response = executeSessionRequest<ts.server.protocol.ImplementationRequest, ts.server.protocol.ImplementationResponse>(session, ts.server.protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, [
ts.projectSystem.protocolFileSpanWithContextFromSubstring({
protocolFileSpanWithContextFromSubstring({
file: aTs,
text: "fnA",
contextText: "export function fnA() {}"
@@ -276,10 +278,10 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("goToDefinition -- target does not exist", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.DefinitionRequest, ts.projectSystem.protocol.DefinitionResponse>(session, ts.projectSystem.CommandNames.Definition, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnB()"));
const response = executeSessionRequest<ts.server.protocol.DefinitionRequest, ts.server.protocol.DefinitionResponse>(session, ts.server.CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()"));
// bTs does not exist, so stick with bDts
assert.deepEqual(response, [
ts.projectSystem.protocolFileSpanWithContextFromSubstring({
protocolFileSpanWithContextFromSubstring({
file: bDts,
text: "fnB",
contextText: "export declare function fnB(): void;"
@@ -290,12 +292,12 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("navigateTo", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.NavtoRequest, ts.projectSystem.protocol.NavtoResponse>(session, ts.projectSystem.CommandNames.Navto, { file: userTs.path, searchValue: "fn" });
assert.deepEqual<readonly ts.projectSystem.protocol.NavtoItem[] | undefined>(response, [
const response = executeSessionRequest<ts.server.protocol.NavtoRequest, ts.server.protocol.NavtoResponse>(session, ts.server.CommandNames.Navto, { file: userTs.path, searchValue: "fn" });
assert.deepEqual<readonly ts.server.protocol.NavtoItem[] | undefined>(response, [
// Keep the .d.ts file since the .ts file no longer exists
// (otherwise it would be treated as not in the project)
{
...ts.projectSystem.protocolFileSpanFromSubstring({
...protocolFileSpanFromSubstring({
file: bDts,
text: "export declare function fnB(): void;"
}),
@@ -306,7 +308,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
kindModifiers: "export,declare",
},
{
...ts.projectSystem.protocolFileSpanFromSubstring({
...protocolFileSpanFromSubstring({
file: userTs,
text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"
}),
@@ -323,10 +325,10 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("navigateToAll -- when neither file nor project is specified", () => {
const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true);
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.NavtoRequest, ts.projectSystem.protocol.NavtoResponse>(session, ts.projectSystem.CommandNames.Navto, { file: undefined, searchValue: "fn" });
assert.deepEqual<readonly ts.projectSystem.protocol.NavtoItem[] | undefined>(response, [
const response = executeSessionRequest<ts.server.protocol.NavtoRequest, ts.server.protocol.NavtoResponse>(session, ts.server.CommandNames.Navto, { file: undefined, searchValue: "fn" });
assert.deepEqual<readonly ts.server.protocol.NavtoItem[] | undefined>(response, [
{
...ts.projectSystem.protocolFileSpanFromSubstring({
...protocolFileSpanFromSubstring({
file: bTs,
text: "export function fnB() {}"
}),
@@ -337,7 +339,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
kindModifiers: "export",
},
{
...ts.projectSystem.protocolFileSpanFromSubstring({
...protocolFileSpanFromSubstring({
file: aTs,
text: "export function fnA() {}"
}),
@@ -348,7 +350,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
kindModifiers: "export",
},
{
...ts.projectSystem.protocolFileSpanFromSubstring({
...protocolFileSpanFromSubstring({
file: userTs,
text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"
}),
@@ -363,10 +365,10 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("navigateToAll -- when file is not specified but project is", () => {
const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true);
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.NavtoRequest, ts.projectSystem.protocol.NavtoResponse>(session, ts.projectSystem.CommandNames.Navto, { projectFileName: bTsconfig.path, file: undefined, searchValue: "fn" });
assert.deepEqual<readonly ts.projectSystem.protocol.NavtoItem[] | undefined>(response, [
const response = executeSessionRequest<ts.server.protocol.NavtoRequest, ts.server.protocol.NavtoResponse>(session, ts.server.CommandNames.Navto, { projectFileName: bTsconfig.path, file: undefined, searchValue: "fn" });
assert.deepEqual<readonly ts.server.protocol.NavtoItem[] | undefined>(response, [
{
...ts.projectSystem.protocolFileSpanFromSubstring({
...protocolFileSpanFromSubstring({
file: bTs,
text: "export function fnB() {}"
}),
@@ -379,7 +381,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
]);
});
const referenceATs = (aTs: ts.projectSystem.File, isDefinition: true | undefined): ts.projectSystem.protocol.ReferencesResponseItem => ts.projectSystem.makeReferenceItem({
const referenceATs = (aTs: File, isDefinition: true | undefined): ts.server.protocol.ReferencesResponseItem => makeReferenceItem({
file: aTs,
isDefinition,
isWriteAccess: true,
@@ -387,8 +389,8 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
contextText: "export function fnA() {}",
lineText: "export function fnA() {}"
});
const referencesUserTs = (userTs: ts.projectSystem.File, isDefinition: false | undefined): readonly ts.projectSystem.protocol.ReferencesResponseItem[] => [
ts.projectSystem.makeReferenceItem({
const referencesUserTs = (userTs: File, isDefinition: false | undefined): readonly ts.server.protocol.ReferencesResponseItem[] => [
makeReferenceItem({
file: userTs,
isDefinition,
text: "fnA",
@@ -399,11 +401,11 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("findAllReferences", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.ReferencesRequest, ts.projectSystem.protocol.ReferencesResponse>(session, ts.projectSystem.protocol.CommandTypes.References, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<ts.projectSystem.protocol.ReferencesResponseBody | undefined>(response, {
const response = executeSessionRequest<ts.server.protocol.ReferencesRequest, ts.server.protocol.ReferencesResponse>(session, ts.server.protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<ts.server.protocol.ReferencesResponseBody | undefined>(response, {
refs: [...referencesUserTs(userTs, /*isDefinition*/ undefined), referenceATs(aTs, /*isDefinition*/ undefined)],
symbolName: "fnA",
symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(userTs.content, "fnA()").offset,
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset,
symbolDisplayString: "function fnA(): void",
});
@@ -412,24 +414,24 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("findAllReferences -- starting at definition", () => {
const session = makeSampleProjects();
ts.projectSystem.openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.ReferencesRequest, ts.projectSystem.protocol.ReferencesResponse>(session, ts.projectSystem.protocol.CommandTypes.References, ts.projectSystem.protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<ts.projectSystem.protocol.ReferencesResponseBody | undefined>(response, {
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = executeSessionRequest<ts.server.protocol.ReferencesRequest, ts.server.protocol.ReferencesResponse>(session, ts.server.protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<ts.server.protocol.ReferencesResponseBody | undefined>(response, {
refs: [referenceATs(aTs, /*isDefinition*/ true), ...referencesUserTs(userTs, /*isDefinition*/ false)],
symbolName: "fnA",
symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(aTs.content, "fnA").offset,
symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset,
symbolDisplayString: "function fnA(): void",
});
verifyATsConfigWhenOpened(session);
});
interface ReferencesFullRequest extends ts.projectSystem.protocol.FileLocationRequest { readonly command: ts.projectSystem.protocol.CommandTypes.ReferencesFull; }
interface ReferencesFullResponse extends ts.projectSystem.protocol.Response { readonly body: readonly ts.ReferencedSymbol[]; }
interface ReferencesFullRequest extends ts.server.protocol.FileLocationRequest { readonly command: ts.server.protocol.CommandTypes.ReferencesFull; }
interface ReferencesFullResponse extends ts.server.protocol.Response { readonly body: readonly ts.ReferencedSymbol[]; }
it("findAllReferencesFull", () => {
const session = makeSampleProjects();
const responseFull = ts.projectSystem.executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, ts.projectSystem.protocol.CommandTypes.ReferencesFull, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()"));
const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, ts.server.protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<readonly ts.ReferencedSymbol[]>(responseFull, [
{
@@ -464,25 +466,25 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
});
it("findAllReferencesFull definition is in mapped file", () => {
const aTs: ts.projectSystem.File = { path: "/a/a.ts", content: `function f() {}` };
const aTsconfig: ts.projectSystem.File = {
const aTs: File = { path: "/a/a.ts", content: `function f() {}` };
const aTsconfig: File = {
path: "/a/tsconfig.json",
content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }),
};
const bTs: ts.projectSystem.File = { path: "/b/b.ts", content: `f();` };
const bTsconfig: ts.projectSystem.File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) };
const aDts: ts.projectSystem.File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` };
const aDtsMap: ts.projectSystem.File = {
const bTs: File = { path: "/b/b.ts", content: `f();` };
const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) };
const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` };
const aDtsMap: File = {
path: "/bin/a.d.ts.map",
content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }),
};
const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap]));
const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap]));
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
ts.projectSystem.openFilesForSession([bTs], session);
ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); // configured project of b is alive since a references b
openFilesForSession([bTs], session);
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); // configured project of b is alive since a references b
const responseFull = ts.projectSystem.executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, ts.projectSystem.protocol.CommandTypes.ReferencesFull, ts.projectSystem.protocolFileLocationFromSubstring(bTs, "f()"));
const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, ts.server.protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()"));
assert.deepEqual<readonly ts.ReferencedSymbol[]>(responseFull, [
{
@@ -530,43 +532,43 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("findAllReferences -- target does not exist", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.ReferencesRequest, ts.projectSystem.protocol.ReferencesResponse>(session, ts.projectSystem.protocol.CommandTypes.References, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<ts.projectSystem.protocol.ReferencesResponseBody | undefined>(response, {
const response = executeSessionRequest<ts.server.protocol.ReferencesRequest, ts.server.protocol.ReferencesResponse>(session, ts.server.protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<ts.server.protocol.ReferencesResponseBody | undefined>(response, {
refs: [
ts.projectSystem.makeReferenceItem({
makeReferenceItem({
file: bDts,
isWriteAccess: true,
text: "fnB",
contextText: "export declare function fnB(): void;",
lineText: "export declare function fnB(): void;"
}),
ts.projectSystem.makeReferenceItem({
makeReferenceItem({
file: userTs,
text: "fnB",
lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"
}),
],
symbolName: "fnB",
symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(userTs.content, "fnB()").offset,
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset,
symbolDisplayString: "function fnB(): void",
});
verifySingleInferredProject(session);
});
const renameATs = (aTs: ts.projectSystem.File): ts.projectSystem.protocol.SpanGroup => ({
const renameATs = (aTs: File): ts.server.protocol.SpanGroup => ({
file: aTs.path,
locs: [
ts.projectSystem.protocolRenameSpanFromSubstring({
protocolRenameSpanFromSubstring({
fileText: aTs.content,
text: "fnA",
contextText: "export function fnA() {}"
})
],
});
const renameUserTs = (userTs: ts.projectSystem.File): ts.projectSystem.protocol.SpanGroup => ({
const renameUserTs = (userTs: File): ts.server.protocol.SpanGroup => ({
file: userTs.path,
locs: [
ts.projectSystem.protocolRenameSpanFromSubstring({
protocolRenameSpanFromSubstring({
fileText: userTs.content,
text: "fnA"
})
@@ -575,8 +577,8 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("renameLocations", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.RenameRequest, ts.projectSystem.protocol.RenameResponse>(session, ts.projectSystem.protocol.CommandTypes.Rename, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<ts.projectSystem.protocol.RenameResponseBody | undefined>(response, {
const response = executeSessionRequest<ts.server.protocol.RenameRequest, ts.server.protocol.RenameResponse>(session, ts.server.protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<ts.server.protocol.RenameResponseBody | undefined>(response, {
info: {
canRename: true,
displayName: "fnA",
@@ -584,7 +586,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path.
kind: ts.ScriptElementKind.functionElement,
kindModifiers: [ts.ScriptElementKindModifier.exportedModifier, ts.ScriptElementKindModifier.ambientModifier].join(","),
triggerSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnA"),
triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
},
locs: [renameUserTs(userTs), renameATs(aTs)],
});
@@ -593,9 +595,9 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("renameLocations -- starting at definition", () => {
const session = makeSampleProjects();
ts.projectSystem.openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.RenameRequest, ts.projectSystem.protocol.RenameResponse>(session, ts.projectSystem.protocol.CommandTypes.Rename, ts.projectSystem.protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<ts.projectSystem.protocol.RenameResponseBody | undefined>(response, {
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = executeSessionRequest<ts.server.protocol.RenameRequest, ts.server.protocol.RenameResponse>(session, ts.server.protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<ts.server.protocol.RenameResponseBody | undefined>(response, {
info: {
canRename: true,
displayName: "fnA",
@@ -603,7 +605,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
fullDisplayName: '"/a/a".fnA',
kind: ts.ScriptElementKind.functionElement,
kindModifiers: ts.ScriptElementKindModifier.exportedModifier,
triggerSpan: ts.projectSystem.protocolTextSpanFromSubstring(aTs.content, "fnA"),
triggerSpan: protocolTextSpanFromSubstring(aTs.content, "fnA"),
},
locs: [renameATs(aTs), renameUserTs(userTs)],
});
@@ -612,7 +614,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("renameLocationsFull", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.RenameFullRequest, ts.projectSystem.protocol.RenameFullResponse>(session, ts.projectSystem.protocol.CommandTypes.RenameLocationsFull, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()"));
const response = executeSessionRequest<ts.server.protocol.RenameFullRequest, ts.server.protocol.RenameFullResponse>(session, ts.server.protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<readonly ts.RenameLocation[]>(response, [
renameLocation({ file: userTs, text: "fnA" }),
renameLocation({ file: aTs, text: "fnA", contextText: "export function fnA() {}" }),
@@ -622,8 +624,8 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("renameLocations -- target does not exist", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.RenameRequest, ts.projectSystem.protocol.RenameResponse>(session, ts.projectSystem.protocol.CommandTypes.Rename, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<ts.projectSystem.protocol.RenameResponseBody | undefined>(response, {
const response = executeSessionRequest<ts.server.protocol.RenameRequest, ts.server.protocol.RenameResponse>(session, ts.server.protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<ts.server.protocol.RenameResponseBody | undefined>(response, {
info: {
canRename: true,
displayName: "fnB",
@@ -631,13 +633,13 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
fullDisplayName: '"/b/bin/b".fnB',
kind: ts.ScriptElementKind.functionElement,
kindModifiers: [ts.ScriptElementKindModifier.exportedModifier, ts.ScriptElementKindModifier.ambientModifier].join(","),
triggerSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnB"),
triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"),
},
locs: [
{
file: bDts.path,
locs: [
ts.projectSystem.protocolRenameSpanFromSubstring({
protocolRenameSpanFromSubstring({
fileText: bDts.content,
text: "fnB",
contextText: "export declare function fnB(): void;"
@@ -647,7 +649,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
{
file: userTs.path,
locs: [
ts.projectSystem.protocolRenameSpanFromSubstring({
protocolRenameSpanFromSubstring({
fileText: userTs.content,
text: "fnB"
})
@@ -660,15 +662,15 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
it("getEditsForFileRename", () => {
const session = makeSampleProjects();
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.GetEditsForFileRenameRequest, ts.projectSystem.protocol.GetEditsForFileRenameResponse>(session, ts.projectSystem.protocol.CommandTypes.GetEditsForFileRename, {
const response = executeSessionRequest<ts.server.protocol.GetEditsForFileRenameRequest, ts.server.protocol.GetEditsForFileRenameResponse>(session, ts.server.protocol.CommandTypes.GetEditsForFileRename, {
oldFilePath: aTs.path,
newFilePath: "/a/aNew.ts",
});
assert.deepEqual<readonly ts.projectSystem.protocol.FileCodeEdits[]>(response, [
assert.deepEqual<readonly ts.server.protocol.FileCodeEdits[]>(response, [
{
fileName: userTs.path,
textChanges: [
{ ...ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" },
{ ...protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" },
],
},
]);
@@ -676,8 +678,8 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
});
it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => {
const aTs: ts.projectSystem.File = { path: "/a/src/a.ts", content: "" };
const aTsconfig: ts.projectSystem.File = {
const aTs: File = { path: "/a/src/a.ts", content: "" };
const aTsconfig: File = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: {
@@ -688,8 +690,8 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
}
}),
};
const bTs: ts.projectSystem.File = { path: "/b/src/b.ts", content: "" };
const bTsconfig: ts.projectSystem.File = {
const bTs: File = { path: "/b/src/b.ts", content: "" };
const bTsconfig: File = {
path: "/b/tsconfig.json",
content: JSON.stringify({
compilerOptions: {
@@ -701,14 +703,14 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
}),
};
const host = ts.projectSystem.createServerHost([aTs, aTsconfig, bTs, bTsconfig]);
const session = ts.projectSystem.createSession(host);
ts.projectSystem.openFilesForSession([aTs, bTs], session);
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.GetEditsForFileRenameRequest, ts.projectSystem.protocol.GetEditsForFileRenameResponse>(session, ts.projectSystem.CommandNames.GetEditsForFileRename, {
const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]);
const session = createSession(host);
openFilesForSession([aTs, bTs], session);
const response = executeSessionRequest<ts.server.protocol.GetEditsForFileRenameRequest, ts.server.protocol.GetEditsForFileRenameResponse>(session, ts.server.CommandNames.GetEditsForFileRename, {
oldFilePath: aTs.path,
newFilePath: "/a/src/a1.ts",
});
assert.deepEqual<readonly ts.projectSystem.protocol.FileCodeEdits[]>(response, []); // Should not change anything
assert.deepEqual<readonly ts.server.protocol.FileCodeEdits[]>(response, []); // Should not change anything
});
it("does not jump to source if inlined sources", () => {
@@ -716,29 +718,29 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
...aDtsMapContent,
sourcesContent: [aTs.content]
};
const aDtsMapInlinedSources: ts.projectSystem.File = {
const aDtsMapInlinedSources: File = {
path: aDtsMap.path,
content: JSON.stringify(aDtsInlinedSources)
};
const host = ts.projectSystem.createServerHost([aTs, aDtsMapInlinedSources, aDts, bTs, bDtsMap, bDts, userTs, dummyFile]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([aTs, aDtsMapInlinedSources, aDts, bTs, bDtsMap, bDts, userTs, dummyFile]);
const session = createSession(host);
ts.projectSystem.openFilesForSession([userTs], session);
openFilesForSession([userTs], session);
const service = session.getProjectService();
// If config file then userConfig project and bConfig project since it is referenced
ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 });
checkNumberOfProjects(service, { inferredProjects: 1 });
// Inlined so does not jump to aTs
assert.deepEqual(
ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.DefinitionAndBoundSpanRequest, ts.projectSystem.protocol.DefinitionAndBoundSpanResponse>(
executeSessionRequest<ts.server.protocol.DefinitionAndBoundSpanRequest, ts.server.protocol.DefinitionAndBoundSpanResponse>(
session,
ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan,
ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()")
ts.server.protocol.CommandTypes.DefinitionAndBoundSpan,
protocolFileLocationFromSubstring(userTs, "fnA()")
),
{
textSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnA"),
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
definitions: [
ts.projectSystem.protocolFileSpanWithContextFromSubstring({
protocolFileSpanWithContextFromSubstring({
file: aDts,
text: "fnA",
contextText: "export declare function fnA(): void;"
@@ -749,15 +751,15 @@ describe("unittests:: tsserver:: with declaration file maps:: project references
// Not inlined, jumps to bTs
assert.deepEqual(
ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.DefinitionAndBoundSpanRequest, ts.projectSystem.protocol.DefinitionAndBoundSpanResponse>(
executeSessionRequest<ts.server.protocol.DefinitionAndBoundSpanRequest, ts.server.protocol.DefinitionAndBoundSpanResponse>(
session,
ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan,
ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnB()")
ts.server.protocol.CommandTypes.DefinitionAndBoundSpan,
protocolFileLocationFromSubstring(userTs, "fnB()")
),
{
textSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnB"),
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"),
definitions: [
ts.projectSystem.protocolFileSpanWithContextFromSubstring({
protocolFileSpanWithContextFromSubstring({
file: bTs,
text: "fnB",
contextText: "export function fnB() {}"

View File

@@ -1,29 +1,31 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File, libFile } from "../virtualFileSystemWithWatch";
import { TestProjectService, checkProjectActualFiles, createProjectService } from "./helpers";
describe("unittests:: tsserver:: document registry in project service", () => {
const importModuleContent = `import {a} from "./module1"`;
const file: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/index.ts`,
const file: File = {
path: `/user/username/projects/myproject/index.ts`,
content: importModuleContent
};
const moduleFile: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/module1.d.ts`,
const moduleFile: File = {
path: `/user/username/projects/myproject/module1.d.ts`,
content: "export const a: number;"
};
const configFile: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const configFile: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({ files: ["index.ts"] })
};
function getProject(service: ts.projectSystem.TestProjectService) {
function getProject(service: TestProjectService) {
return service.configuredProjects.get(configFile.path)!;
}
function checkProject(service: ts.projectSystem.TestProjectService, moduleIsOrphan: boolean) {
function checkProject(service: TestProjectService, moduleIsOrphan: boolean) {
// Update the project
const project = getProject(service);
project.getLanguageService();
ts.projectSystem.checkProjectActualFiles(project, [file.path, ts.projectSystem.libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]);
checkProjectActualFiles(project, [file.path, libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]);
const moduleInfo = service.getScriptInfo(moduleFile.path)!;
assert.isDefined(moduleInfo);
assert.equal(moduleInfo.isOrphan(), moduleIsOrphan);
@@ -32,20 +34,20 @@ describe("unittests:: tsserver:: document registry in project service", () => {
}
function createServiceAndHost() {
const host = ts.projectSystem.createServerHost([file, moduleFile, ts.projectSystem.libFile, configFile]);
const service = ts.projectSystem.createProjectService(host);
const host = createServerHost([file, moduleFile, libFile, configFile]);
const service = createProjectService(host);
service.openClientFile(file.path);
checkProject(service, /*moduleIsOrphan*/ false);
return { host, service };
}
function changeFileToNotImportModule(service: ts.projectSystem.TestProjectService) {
function changeFileToNotImportModule(service: TestProjectService) {
const info = service.getScriptInfo(file.path)!;
service.applyChangesToFile(info, ts.singleIterator({ span: { start: 0, length: importModuleContent.length }, newText: "" }));
checkProject(service, /*moduleIsOrphan*/ true);
}
function changeFileToImportModule(service: ts.projectSystem.TestProjectService) {
function changeFileToImportModule(service: TestProjectService) {
const info = service.getScriptInfo(file.path)!;
service.applyChangesToFile(info, ts.singleIterator({ span: { start: 0, length: 0 }, newText: importModuleContent }));
checkProject(service, /*moduleIsOrphan*/ false);

View File

@@ -1,30 +1,32 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File } from "../virtualFileSystemWithWatch";
import { createSession, openFilesForSession, executeSessionRequest } from "./helpers";
describe("unittests:: tsserver:: duplicate packages", () => {
// Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier.
it("works with import fixes", () => {
const packageContent = "export const foo: number;";
const packageJsonContent = JSON.stringify({ name: "foo", version: "1.2.3" });
const aFooIndex: ts.projectSystem.File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent };
const aFooPackage: ts.projectSystem.File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent };
const bFooIndex: ts.projectSystem.File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent };
const bFooPackage: ts.projectSystem.File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent };
const aFooIndex: File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent };
const aFooPackage: File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent };
const bFooIndex: File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent };
const bFooPackage: File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent };
const userContent = 'import("foo");\nfoo';
const aUser: ts.projectSystem.File = { path: "/a/user.ts", content: userContent };
const bUser: ts.projectSystem.File = { path: "/b/user.ts", content: userContent };
const tsconfig: ts.projectSystem.File = {
const aUser: File = { path: "/a/user.ts", content: userContent };
const bUser: File = { path: "/b/user.ts", content: userContent };
const tsconfig: File = {
path: "/tsconfig.json",
content: "{}",
};
const host = ts.projectSystem.createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]);
const session = createSession(host);
ts.projectSystem.openFilesForSession([aUser, bUser], session);
openFilesForSession([aUser, bUser], session);
for (const user of [aUser, bUser]) {
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.CodeFixRequest, ts.projectSystem.protocol.CodeFixResponse>(session, ts.projectSystem.protocol.CommandTypes.GetCodeFixes, {
const response = executeSessionRequest<ts.server.protocol.CodeFixRequest, ts.server.protocol.CodeFixResponse>(session, ts.server.protocol.CommandTypes.GetCodeFixes, {
file: user.path,
startLine: 2,
startOffset: 1,
@@ -32,7 +34,7 @@ describe("unittests:: tsserver:: duplicate packages", () => {
endOffset: 4,
errorCodes: [ts.Diagnostics.Cannot_find_name_0.code],
});
assert.deepEqual<readonly ts.projectSystem.protocol.CodeFixAction[] | undefined>(response, [
assert.deepEqual<readonly ts.server.protocol.CodeFixAction[] | undefined>(response, [
{
description: `Add import from "foo"`,
fixName: "import",

View File

@@ -1,41 +1,42 @@
import * as ts from "../../_namespaces/ts";
import { verifyDynamic } from "./helpers";
import { createServerHost, File, libFile } from "../virtualFileSystemWithWatch";
import { checkNumberOfProjects, checkProjectActualFiles, checkProjectRootFiles, createProjectService, createSession, executeSessionRequest, executeSessionRequestNoResponse, openFilesForSession, verifyDynamic } from "./helpers";
function verifyPathRecognizedAsDynamic(path: string) {
const file: ts.projectSystem.File = {
const file: File = {
path,
content: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />
/// <reference path="../../../../../../typings/@epic/Shell.d.ts" />
var x = 10;`
};
const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([libFile]);
const projectService = createProjectService(host);
projectService.openClientFile(file.path, file.content);
verifyDynamic(projectService, projectService.toPath(file.path));
projectService.checkNumberOfProjects({ inferredProjects: 1 });
const project = projectService.inferredProjects[0];
ts.projectSystem.checkProjectRootFiles(project, [file.path]);
ts.projectSystem.checkProjectActualFiles(project, [file.path, ts.projectSystem.libFile.path]);
checkProjectRootFiles(project, [file.path]);
checkProjectActualFiles(project, [file.path, libFile.path]);
}
describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => {
const untitledFile = "untitled:^Untitled-1";
it("Can convert positions to locations", () => {
const aTs: ts.projectSystem.File = { path: "/proj/a.ts", content: "" };
const tsconfig: ts.projectSystem.File = { path: "/proj/tsconfig.json", content: "{}" };
const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([aTs, tsconfig]), { useInferredProjectPerProjectRoot: true });
const aTs: File = { path: "/proj/a.ts", content: "" };
const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" };
const session = createSession(createServerHost([aTs, tsconfig]), { useInferredProjectPerProjectRoot: true });
ts.projectSystem.openFilesForSession([aTs], session);
openFilesForSession([aTs], session);
ts.projectSystem.executeSessionRequestNoResponse<ts.projectSystem.protocol.OpenRequest>(session, ts.projectSystem.protocol.CommandTypes.Open, {
executeSessionRequestNoResponse<ts.server.protocol.OpenRequest>(session, ts.server.protocol.CommandTypes.Open, {
file: untitledFile,
fileContent: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />\nlet foo = 1;\nfooo/**/`,
scriptKindName: "TS",
projectRootPath: "/proj",
});
verifyDynamic(session.getProjectService(), `/proj/untitled:^untitled-1`);
const response = ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.CodeFixRequest, ts.projectSystem.protocol.CodeFixResponse>(session, ts.projectSystem.protocol.CommandTypes.GetCodeFixes, {
const response = executeSessionRequest<ts.server.protocol.CodeFixRequest, ts.server.protocol.CodeFixResponse>(session, ts.server.protocol.CommandTypes.GetCodeFixes, {
file: untitledFile,
startLine: 3,
startOffset: 1,
@@ -43,7 +44,7 @@ describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => {
endOffset: 5,
errorCodes: [ts.Diagnostics.Cannot_find_name_0_Did_you_mean_1.code],
});
assert.deepEqual<readonly ts.projectSystem.protocol.CodeFixAction[] | undefined>(response, [
assert.deepEqual<readonly ts.server.protocol.CodeFixAction[] | undefined>(response, [
{
description: "Change spelling to 'foo'",
fixName: "spelling",
@@ -63,68 +64,68 @@ describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => {
});
it("opening untitled files", () => {
const config: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: "{}"
};
const host = ts.projectSystem.createServerHost([config, ts.projectSystem.libFile], { useCaseSensitiveFileNames: true, currentDirectory: ts.tscWatch.projectRoot });
const service = ts.projectSystem.createProjectService(host);
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, ts.tscWatch.projectRoot);
ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]);
verifyDynamic(service, `${ts.tscWatch.projectRoot}/${untitledFile}`);
const host = createServerHost([config, libFile], { useCaseSensitiveFileNames: true, currentDirectory: "/user/username/projects/myproject" });
const service = createProjectService(host);
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, "/user/username/projects/myproject");
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
verifyDynamic(service, `/user/username/projects/myproject/${untitledFile}`);
const untitled: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/Untitled-1.ts`,
const untitled: File = {
path: `/user/username/projects/myproject/Untitled-1.ts`,
content: "const x = 10;"
};
host.writeFile(untitled.path, untitled.content);
host.checkTimeoutQueueLength(0);
service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, ts.tscWatch.projectRoot);
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, ts.projectSystem.libFile.path, config.path]);
ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]);
service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, "/user/username/projects/myproject");
checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
service.closeClientFile(untitledFile);
ts.projectSystem.checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, ts.projectSystem.libFile.path, config.path]);
ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]);
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, ts.tscWatch.projectRoot);
verifyDynamic(service, `${ts.tscWatch.projectRoot}/${untitledFile}`);
ts.projectSystem.checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, ts.projectSystem.libFile.path, config.path]);
ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]);
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, "/user/username/projects/myproject");
verifyDynamic(service, `/user/username/projects/myproject/${untitledFile}`);
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
});
it("opening and closing untitled files when projectRootPath is different from currentDirectory", () => {
const config: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const config: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: "{}"
};
const file: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/file.ts`,
const file: File = {
path: `/user/username/projects/myproject/file.ts`,
content: "const y = 10"
};
const host = ts.projectSystem.createServerHost([config, file, ts.projectSystem.libFile], { useCaseSensitiveFileNames: true });
const service = ts.projectSystem.createProjectService(host, { useInferredProjectPerProjectRoot: true });
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, ts.tscWatch.projectRoot);
ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]);
verifyDynamic(service, `${ts.tscWatch.projectRoot}/${untitledFile}`);
const host = createServerHost([config, file, libFile], { useCaseSensitiveFileNames: true });
const service = createProjectService(host, { useInferredProjectPerProjectRoot: true });
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, "/user/username/projects/myproject");
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
verifyDynamic(service, `/user/username/projects/myproject/${untitledFile}`);
// Close untitled file
service.closeClientFile(untitledFile);
// Open file from configured project which should collect inferredProject
service.openClientFile(file.path);
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 });
checkNumberOfProjects(service, { configuredProjects: 1 });
});
it("when changing scriptKind of the untitled files", () => {
const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile], { useCaseSensitiveFileNames: true });
const service = ts.projectSystem.createProjectService(host, { useInferredProjectPerProjectRoot: true });
service.openClientFile(untitledFile, "const x = 10;", ts.ScriptKind.TS, ts.tscWatch.projectRoot);
ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]);
const host = createServerHost([libFile], { useCaseSensitiveFileNames: true });
const service = createProjectService(host, { useInferredProjectPerProjectRoot: true });
service.openClientFile(untitledFile, "const x = 10;", ts.ScriptKind.TS, "/user/username/projects/myproject");
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
const program = service.inferredProjects[0].getCurrentProgram()!;
const sourceFile = program.getSourceFile(untitledFile)!;
@@ -132,9 +133,9 @@ describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => {
service.closeClientFile(untitledFile);
// Open untitled file with different mode
service.openClientFile(untitledFile, "const x = 10;", ts.ScriptKind.TSX, ts.tscWatch.projectRoot);
ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]);
service.openClientFile(untitledFile, "const x = 10;", ts.ScriptKind.TSX, "/user/username/projects/myproject");
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
const newProgram = service.inferredProjects[0].getCurrentProgram()!;
const newSourceFile = newProgram.getSourceFile(untitledFile)!;
assert.notStrictEqual(newProgram, program);
@@ -144,12 +145,12 @@ describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => {
describe("unittests:: tsserver:: dynamicFiles:: ", () => {
it("dynamic file without external project", () => {
const file: ts.projectSystem.File = {
const file: File = {
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
content: "var x = 10;"
};
const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile], { useCaseSensitiveFileNames: true });
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([libFile], { useCaseSensitiveFileNames: true });
const projectService = createProjectService(host);
projectService.setCompilerOptionsForInferredProjects({
module: ts.ModuleKind.CommonJS,
allowJs: true,
@@ -160,8 +161,8 @@ describe("unittests:: tsserver:: dynamicFiles:: ", () => {
projectService.checkNumberOfProjects({ inferredProjects: 1 });
const project = projectService.inferredProjects[0];
ts.projectSystem.checkProjectRootFiles(project, [file.path]);
ts.projectSystem.checkProjectActualFiles(project, [file.path, ts.projectSystem.libFile.path]);
checkProjectRootFiles(project, [file.path]);
checkProjectActualFiles(project, [file.path, libFile.path]);
verifyDynamic(projectService, `/${file.path}`);
assert.strictEqual(projectService.ensureDefaultProjectForFile(ts.server.toNormalizedPath(file.path)), project);
@@ -188,30 +189,30 @@ describe("unittests:: tsserver:: dynamicFiles:: ", () => {
});
describe("dynamic file with projectRootPath", () => {
const file: ts.projectSystem.File = {
const file: File = {
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
content: "var x = 10;"
};
const configFile: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const configFile: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: "{}"
};
const configProjectFile: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/a.ts`,
const configProjectFile: File = {
path: `/user/username/projects/myproject/a.ts`,
content: "let y = 10;"
};
it("with useInferredProjectPerProjectRoot", () => {
const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
const session = ts.projectSystem.createSession(host, { useInferredProjectPerProjectRoot: true });
ts.projectSystem.openFilesForSession([{ file: file.path, projectRootPath: ts.tscWatch.projectRoot }], session);
const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
const session = createSession(host, { useInferredProjectPerProjectRoot: true });
openFilesForSession([{ file: file.path, projectRootPath: "/user/username/projects/myproject" }], session);
const projectService = session.getProjectService();
ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file.path, ts.projectSystem.libFile.path]);
verifyDynamic(projectService, `${ts.tscWatch.projectRoot}/${file.path}`);
checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);
verifyDynamic(projectService, `/user/username/projects/myproject/${file.path}`);
session.executeCommandSeq<ts.projectSystem.protocol.OutliningSpansRequest>({
command: ts.projectSystem.protocol.CommandTypes.GetOutliningSpans,
session.executeCommandSeq<ts.server.protocol.OutliningSpansRequest>({
command: ts.server.protocol.CommandTypes.GetOutliningSpans,
arguments: {
file: file.path
}
@@ -220,16 +221,16 @@ describe("unittests:: tsserver:: dynamicFiles:: ", () => {
// Without project root
const file2Path = file.path.replace("#1", "#2");
projectService.openClientFile(file2Path, file.content);
ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 });
ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file.path, ts.projectSystem.libFile.path]);
ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, ts.projectSystem.libFile.path]);
checkNumberOfProjects(projectService, { inferredProjects: 2 });
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);
checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, libFile.path]);
});
it("fails when useInferredProjectPerProjectRoot is false", () => {
const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
const projectService = createProjectService(host);
try {
projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, ts.tscWatch.projectRoot);
projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, "/user/username/projects/myproject");
}
catch (e) {
assert.strictEqual(
@@ -240,7 +241,7 @@ describe("unittests:: tsserver:: dynamicFiles:: ", () => {
const file2Path = file.path.replace("#1", "#2");
projectService.openClientFile(file2Path, file.content);
projectService.checkNumberOfProjects({ inferredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, ts.projectSystem.libFile.path]);
checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, libFile.path]);
});
});

View File

@@ -1,4 +1,6 @@
import * as ts from "../../../_namespaces/ts";
import { createServerHost, File, libFile } from "../../virtualFileSystemWithWatch";
import { createSessionWithEventTracking, checkProjectActualFiles, openFilesForSession, checkNumberOfProjects } from "../helpers";
describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large file", () => {
@@ -6,20 +8,20 @@ describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large fi
return `src/large.${useLargeTsFile ? "ts" : "js"}`;
}
function createSessionWithEventHandler(files: ts.projectSystem.File[], useLargeTsFile: boolean) {
const largeFile: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/${getLargeFile(useLargeTsFile)}`,
function createSessionWithEventHandler(files: File[], useLargeTsFile: boolean) {
const largeFile: File = {
path: `/user/username/projects/myproject/${getLargeFile(useLargeTsFile)}`,
content: "export var x = 10;",
fileSize: ts.server.maxFileSize + 1
};
files.push(largeFile);
const host = ts.projectSystem.createServerHost(files);
const { session, events: largeFileReferencedEvents } = ts.projectSystem.createSessionWithEventTracking<ts.server.LargeFileReferencedEvent>(host, ts.server.LargeFileReferencedEvent);
const host = createServerHost(files);
const { session, events: largeFileReferencedEvents } = createSessionWithEventTracking<ts.server.LargeFileReferencedEvent>(host, ts.server.LargeFileReferencedEvent);
return { session, verifyLargeFile };
function verifyLargeFile(project: ts.server.Project) {
ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path));
checkProjectActualFiles(project, files.map(f => f.path));
// large file for non ts file should be empty and for ts file should have content
const service = session.getProjectService();
@@ -35,32 +37,32 @@ describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large fi
function verifyLargeFile(useLargeTsFile: boolean) {
it("when large file is included by tsconfig", () => {
const file: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/src/file.ts`,
const file: File = {
path: `/user/username/projects/myproject/src/file.ts`,
content: "export var y = 10;"
};
const tsconfig: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({ files: ["src/file.ts", getLargeFile(useLargeTsFile)], compilerOptions: { target: 1, allowJs: true } })
};
const files = [file, ts.projectSystem.libFile, tsconfig];
const files = [file, libFile, tsconfig];
const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile);
const service = session.getProjectService();
ts.projectSystem.openFilesForSession([file], session);
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 });
openFilesForSession([file], session);
checkNumberOfProjects(service, { configuredProjects: 1 });
verifyLargeFile(service.configuredProjects.get(tsconfig.path)!);
});
it("when large file is included by module resolution", () => {
const file: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/src/file.ts`,
const file: File = {
path: `/user/username/projects/myproject/src/file.ts`,
content: `export var y = 10;import {x} from "./large"`
};
const files = [file, ts.projectSystem.libFile];
const files = [file, libFile];
const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile);
const service = session.getProjectService();
ts.projectSystem.openFilesForSession([file], session);
ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 });
openFilesForSession([file], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
verifyLargeFile(service.inferredProjects[0]);
});
}

View File

@@ -1,4 +1,6 @@
import * as ts from "../../../_namespaces/ts";
import { createServerHost, File, libFile } from "../../virtualFileSystemWithWatch";
import { createSessionWithEventTracking, checkNumberOfProjects, configuredProjectAt, createProjectService, createLoggerWithInMemoryLogs, baselineTsserverLogs } from "../helpers";
describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", () => {
it("language service disabled events are triggered", () => {
@@ -18,21 +20,21 @@ describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", ()
path: config.path,
content: JSON.stringify({ exclude: ["largefile.js"] })
};
const host = ts.projectSystem.createServerHost([f1, f2, config]);
const host = createServerHost([f1, f2, config]);
const originalGetFileSize = host.getFileSize;
host.getFileSize = (filePath: string) =>
filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
const { session, events } = ts.projectSystem.createSessionWithEventTracking<ts.server.ProjectLanguageServiceStateEvent>(host, ts.server.ProjectLanguageServiceStateEvent);
const { session, events } = createSessionWithEventTracking<ts.server.ProjectLanguageServiceStateEvent>(host, ts.server.ProjectLanguageServiceStateEvent);
session.executeCommand({
seq: 0,
type: "request",
command: "open",
arguments: { file: f1.path }
} as ts.projectSystem.protocol.OpenRequest);
} as ts.server.protocol.OpenRequest);
const projectService = session.getProjectService();
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = ts.projectSystem.configuredProjectAt(projectService, 0);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = configuredProjectAt(projectService, 0);
assert.isFalse(project.languageServiceEnabled, "Language service enabled");
assert.equal(events.length, 1, "should receive event");
assert.equal(events[0].data.project, project, "project name");
@@ -41,7 +43,7 @@ describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", ()
host.writeFile(configWithExclude.path, configWithExclude.content);
host.checkTimeoutQueueLengthAndRun(2);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.isTrue(project.languageServiceEnabled, "Language service enabled");
assert.equal(events.length, 2, "should receive event");
assert.equal(events[1].data.project, project, "project");
@@ -50,16 +52,16 @@ describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", ()
});
it("Large file size is determined correctly", () => {
const f1: ts.projectSystem.File = {
const f1: File = {
path: "/a/app.js",
content: "let x = 1;"
};
const f2: ts.projectSystem.File = {
const f2: File = {
path: "/a/largefile.js",
content: "",
fileSize: ts.server.maxProgramSizeForNonTsFiles + 1
};
const f3: ts.projectSystem.File = {
const f3: File = {
path: "/a/extremlylarge.d.ts",
content: "",
fileSize: ts.server.maxProgramSizeForNonTsFiles + 100
@@ -68,12 +70,12 @@ describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", ()
path: "/a/jsconfig.json",
content: "{}"
};
const host = ts.projectSystem.createServerHost([f1, f2, f3, ts.projectSystem.libFile, config]);
const service = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
const host = createServerHost([f1, f2, f3, libFile, config]);
const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
service.openClientFile(f1.path);
const project = service.configuredProjects.get(config.path)!;
service.logger.info(`languageServiceEnabled: ${project.languageServiceEnabled}`);
service.logger.info(`lastFileExceededProgramSize: ${project.lastFileExceededProgramSize}`);
ts.projectSystem.baselineTsserverLogs("projectLanguageServiceStateEvent", "large file size is determined correctly", service);
baselineTsserverLogs("projectLanguageServiceStateEvent", "large file size is determined correctly", service);
});
});

View File

@@ -1,26 +1,28 @@
import * as ts from "../../../_namespaces/ts";
import { createServerHost, File, libFile, TestServerHost } from "../../virtualFileSystemWithWatch";
import { TestSession, openFilesForSession, checkNumberOfProjects, protocolLocationFromSubstring, toExternalFiles, createSessionWithEventTracking, createSessionWithDefaultEventHandler } from "../helpers";
describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => {
const aTs: ts.projectSystem.File = {
path: `${ts.tscWatch.projects}/a/a.ts`,
const aTs: File = {
path: `/user/username/projects/a/a.ts`,
content: "export class A { }"
};
const configA: ts.projectSystem.File = {
path: `${ts.tscWatch.projects}/a/tsconfig.json`,
const configA: File = {
path: `/user/username/projects/a/tsconfig.json`,
content: "{}"
};
const bTsPath = `${ts.tscWatch.projects}/b/b.ts`;
const configBPath = `${ts.tscWatch.projects}/b/tsconfig.json`;
const files = [ts.projectSystem.libFile, aTs, configA];
const bTsPath = `/user/username/projects/b/b.ts`;
const configBPath = `/user/username/projects/b/tsconfig.json`;
const files = [libFile, aTs, configA];
function verifyProjectLoadingStartAndFinish(createSession: (host: ts.projectSystem.TestServerHost) => {
session: ts.projectSystem.TestSession;
function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => {
session: TestSession;
getNumberOfEvents: () => number;
clearEvents: () => void;
verifyProjectLoadEvents: (expected: [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]) => void;
}) {
function createSessionToVerifyEvent(files: readonly ts.projectSystem.File[]) {
const host = ts.projectSystem.createServerHost(files);
function createSessionToVerifyEvent(files: readonly File[]) {
const host = createServerHost(files);
const originalReadFile = host.readFile;
const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host);
host.readFile = file => {
@@ -40,9 +42,9 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
clearEvents();
}
function verifyEventWithOpenTs(file: ts.projectSystem.File, configPath: string, configuredProjects: number) {
ts.projectSystem.openFilesForSession([file], session);
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects });
function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) {
openFilesForSession([file], session);
checkNumberOfProjects(service, { configuredProjects });
const project = service.configuredProjects.get(configPath)!;
assert.isDefined(project);
verifyEvent(project, `Creating possible configured project for ${file.path} to open`);
@@ -50,11 +52,11 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
}
it("when project is created by open file", () => {
const bTs: ts.projectSystem.File = {
const bTs: File = {
path: bTsPath,
content: "export class B {}"
};
const configB: ts.projectSystem.File = {
const configB: File = {
path: configBPath,
content: "{}"
};
@@ -74,11 +76,11 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
});
it("when change is detected in an extended config file", () => {
const bTs: ts.projectSystem.File = {
const bTs: File = {
path: bTsPath,
content: "export class B {}"
};
const configB: ts.projectSystem.File = {
const configB: File = {
path: configBPath,
content: JSON.stringify({
extends: "../a/tsconfig.json",
@@ -103,22 +105,22 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
});
function verify(disableSourceOfProjectReferenceRedirect?: true) {
const aDTs: ts.projectSystem.File = {
path: `${ts.tscWatch.projects}/a/a.d.ts`,
const aDTs: File = {
path: `/user/username/projects/a/a.d.ts`,
content: `export declare class A {
}
//# sourceMappingURL=a.d.ts.map
`
};
const aDTsMap: ts.projectSystem.File = {
path: `${ts.tscWatch.projects}/a/a.d.ts.map`,
const aDTsMap: File = {
path: `/user/username/projects/a/a.d.ts.map`,
content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}`
};
const bTs: ts.projectSystem.File = {
const bTs: File = {
path: bTsPath,
content: `import {A} from "../a/a"; new A();`
};
const configB: ts.projectSystem.File = {
const configB: File = {
path: configBPath,
content: JSON.stringify({
...(disableSourceOfProjectReferenceRedirect && {
@@ -133,15 +135,15 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB));
verifyEventWithOpenTs(bTs, configB.path, 1);
session.executeCommandSeq<ts.projectSystem.protocol.ReferencesRequest>({
command: ts.projectSystem.protocol.CommandTypes.References,
session.executeCommandSeq<ts.server.protocol.ReferencesRequest>({
command: ts.server.protocol.CommandTypes.References,
arguments: {
file: bTs.path,
...ts.projectSystem.protocolLocationFromSubstring(bTs.content, "A()")
...protocolLocationFromSubstring(bTs.content, "A()")
}
});
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 2 });
checkNumberOfProjects(service, { configuredProjects: 2 });
const project = service.configuredProjects.get(configA.path)!;
assert.isDefined(project);
verifyEvent(
@@ -154,17 +156,17 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
});
describe("with external projects and config files ", () => {
const projectFileName = `${ts.tscWatch.projects}/a/project.csproj`;
const projectFileName = `/user/username/projects/a/project.csproj`;
function createSession(lazyConfiguredProjectsFromExternalProject: boolean) {
const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
service.openExternalProject({
projectFileName,
rootFiles: ts.projectSystem.toExternalFiles([aTs.path, configA.path]),
rootFiles: toExternalFiles([aTs.path, configA.path]),
options: {}
} as ts.projectSystem.protocol.ExternalProject);
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 });
} as ts.server.protocol.ExternalProject);
checkNumberOfProjects(service, { configuredProjects: 1 });
return { session, service, verifyEvent, getNumberOfEvents };
function verifyEvent() {
@@ -183,7 +185,7 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true);
assert.equal(getNumberOfEvents(), 0);
ts.projectSystem.openFilesForSession([aTs], session);
openFilesForSession([aTs], session);
verifyEvent();
});
@@ -199,7 +201,7 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
describe("when using event handler", () => {
verifyProjectLoadingStartAndFinish(host => {
const { session, events } = ts.projectSystem.createSessionWithEventTracking<ts.server.ProjectLoadingStartEvent | ts.server.ProjectLoadingFinishEvent>(host, [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]);
const { session, events } = createSessionWithEventTracking<ts.server.ProjectLoadingStartEvent | ts.server.ProjectLoadingFinishEvent>(host, [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]);
return {
session,
getNumberOfEvents: () => events.length,
@@ -211,7 +213,7 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
describe("when using default event handler", () => {
verifyProjectLoadingStartAndFinish(host => {
const { session, getEvents, clearEvents } = ts.projectSystem.createSessionWithDefaultEventHandler<ts.projectSystem.protocol.ProjectLoadingStartEvent | ts.projectSystem.protocol.ProjectLoadingFinishEvent>(host, [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]);
const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler<ts.server.protocol.ProjectLoadingStartEvent | ts.server.protocol.ProjectLoadingFinishEvent>(host, [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]);
return {
session,
getNumberOfEvents: () => getEvents().length,

View File

@@ -1,4 +1,6 @@
import * as ts from "../../../_namespaces/ts";
import { createServerHost, File, libFile, TestServerHost } from "../../virtualFileSystemWithWatch";
import { TestSession, Logger, createLoggerWithInMemoryLogs, baselineTsserverLogs, createSessionWithEventTracking, createSessionWithDefaultEventHandler, createHasErrorMessageLogger } from "../helpers";
describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
function verifyFiles(caption: string, actual: readonly string[], expected: readonly string[]) {
@@ -11,44 +13,44 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
});
}
function createVerifyInitialOpen(session: ts.projectSystem.TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: ts.server.ProjectsUpdatedInBackgroundEvent[]) => void) {
return (file: ts.projectSystem.File) => {
function createVerifyInitialOpen(session: TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: ts.server.ProjectsUpdatedInBackgroundEvent[]) => void) {
return (file: File) => {
session.executeCommandSeq({
command: ts.server.CommandNames.Open,
arguments: {
file: file.path
}
} as ts.projectSystem.protocol.OpenRequest);
} as ts.server.protocol.OpenRequest);
verifyProjectsUpdatedInBackgroundEventHandler([]);
};
}
interface ProjectsUpdatedInBackgroundEventVerifier {
session: ts.projectSystem.TestSession;
session: TestSession;
verifyProjectsUpdatedInBackgroundEventHandler(events: ts.server.ProjectsUpdatedInBackgroundEvent[]): void;
verifyInitialOpen(file: ts.projectSystem.File): void;
verifyInitialOpen(file: File): void;
}
function verifyProjectsUpdatedInBackgroundEvent(scenario: string, createSession: (host: ts.projectSystem.TestServerHost, logger?: ts.projectSystem.Logger) => ProjectsUpdatedInBackgroundEventVerifier) {
function verifyProjectsUpdatedInBackgroundEvent(scenario: string, createSession: (host: TestServerHost, logger?: Logger) => ProjectsUpdatedInBackgroundEventVerifier) {
it("when adding new file", () => {
const commonFile1: ts.projectSystem.File = {
const commonFile1: File = {
path: "/a/b/file1.ts",
content: "export var x = 10;"
};
const commonFile2: ts.projectSystem.File = {
const commonFile2: File = {
path: "/a/b/file2.ts",
content: "export var y = 10;"
};
const commonFile3: ts.projectSystem.File = {
const commonFile3: File = {
path: "/a/b/file3.ts",
content: "export var z = 10;"
};
const configFile: ts.projectSystem.File = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: `{}`
};
const openFiles = [commonFile1.path];
const host = ts.projectSystem.createServerHost([commonFile1, ts.projectSystem.libFile, configFile]);
const host = createServerHost([commonFile1, libFile, configFile]);
const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host);
verifyInitialOpen(commonFile1);
@@ -73,25 +75,25 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
describe("with --out or --outFile setting", () => {
function verifyEventWithOutSettings(compilerOptions: ts.CompilerOptions = {}) {
const config: ts.projectSystem.File = {
const config: File = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions
})
};
const f1: ts.projectSystem.File = {
const f1: File = {
path: "/a/a.ts",
content: "export let x = 1"
};
const f2: ts.projectSystem.File = {
const f2: File = {
path: "/a/b.ts",
content: "export let y = 1"
};
const openFiles = [f1.path];
const files = [f1, config, ts.projectSystem.libFile];
const host = ts.projectSystem.createServerHost(files);
const files = [f1, config, libFile];
const host = createServerHost(files);
const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host);
verifyInitialOpen(f1);
@@ -138,32 +140,32 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
/** custom config file options */
configObj?: any;
/** Additional files and folders to add */
getAdditionalFileOrFolder?(): ts.projectSystem.File[];
getAdditionalFileOrFolder?(): File[];
/** initial list of files to reload in fs and first file in this list being the file to open */
firstReloadFileList?: string[];
}
function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) {
const moduleFile1: ts.projectSystem.File = {
const moduleFile1: File = {
path: moduleFile1Path,
content: "export function Foo() { };",
};
const file1Consumer1: ts.projectSystem.File = {
const file1Consumer1: File = {
path: file1Consumer1Path,
content: `import {Foo} from "./moduleFile1"; export var y = 10;`,
};
const file1Consumer2: ts.projectSystem.File = {
const file1Consumer2: File = {
path: "/a/b/file1Consumer2.ts",
content: `import {Foo} from "./moduleFile1"; let z = 10;`,
};
const moduleFile2: ts.projectSystem.File = {
const moduleFile2: File = {
path: "/a/b/moduleFile2.ts",
content: `export var Foo4 = 10;`,
};
const globalFile3: ts.projectSystem.File = {
const globalFile3: File = {
path: "/a/b/globalFile3.ts",
content: `interface GlobalFoo { age: number }`
};
@@ -174,10 +176,10 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
content: JSON.stringify(configObj || { compilerOptions: {} })
};
const files: ts.projectSystem.File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, ts.projectSystem.libFile, configFile];
const files: File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, libFile, configFile];
const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files;
const host = ts.projectSystem.createServerHost([filesToReload[0], configFile]);
const host = createServerHost([filesToReload[0], configFile]);
// Initial project creation
const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host);
@@ -220,8 +222,8 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
}]);
}
function updateContentOfOpenFile(file: ts.projectSystem.File, newContent: string) {
session.executeCommandSeq<ts.projectSystem.protocol.ChangeRequest>({
function updateContentOfOpenFile(file: File, newContent: string) {
session.executeCommandSeq<ts.server.protocol.ChangeRequest>({
command: ts.server.CommandNames.Change,
arguments: {
file: file.path,
@@ -335,7 +337,7 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
});
it("should return cascaded affected file list", () => {
const file1Consumer1Consumer1: ts.projectSystem.File = {
const file1Consumer1Consumer1: File = {
path: "/a/b/file1Consumer1Consumer1.ts",
content: `import {y} from "./file1Consumer1";`
};
@@ -357,13 +359,13 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
});
it("should work fine for files with circular references", () => {
const file1: ts.projectSystem.File = {
const file1: File = {
path: "/a/b/file1.ts",
content: `
/// <reference path="./file2.ts" />
export var t1 = 10;`
};
const file2: ts.projectSystem.File = {
const file2: File = {
path: "/a/b/file2.ts",
content: `
/// <reference path="./file1.ts" />
@@ -371,7 +373,7 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
};
const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
getAdditionalFileOrFolder: () => [file1, file2],
firstReloadFileList: [file1.path, ts.projectSystem.libFile.path, file2.path, configFilePath]
firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath]
});
host.writeFile(file2.path, file2.content + "export var t3 = 10;");
@@ -379,7 +381,7 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
});
it("should detect removed code file", () => {
const referenceFile1: ts.projectSystem.File = {
const referenceFile1: File = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile1.ts" />
@@ -387,7 +389,7 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
};
const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
getAdditionalFileOrFolder: () => [referenceFile1],
firstReloadFileList: [referenceFile1.path, ts.projectSystem.libFile.path, moduleFile1Path, configFilePath]
firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath]
});
host.deleteFile(moduleFile1Path);
@@ -395,7 +397,7 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
});
it("should detect non-existing code file", () => {
const referenceFile1: ts.projectSystem.File = {
const referenceFile1: File = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile2.ts" />
@@ -403,7 +405,7 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
};
const { host, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
getAdditionalFileOrFolder: () => [referenceFile1],
firstReloadFileList: [referenceFile1.path, ts.projectSystem.libFile.path, configFilePath]
firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath]
});
updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();");
@@ -419,26 +421,26 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
function verifyWithMaxCacheLimit(subScenario: string, useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) {
it(subScenario, () => {
const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/";
const file1: ts.projectSystem.File = {
const file1: File = {
path: rootFolder + "a/b/project/file1.ts",
content: 'import a from "file2"'
};
const file2: ts.projectSystem.File = {
const file2: File = {
path: rootFolder + "a/b/node_modules/file2.d.ts",
content: "export class a { }"
};
const file3: ts.projectSystem.File = {
const file3: File = {
path: rootFolder + "a/b/project/file3.ts",
content: "export class c { }"
};
const configFile: ts.projectSystem.File = {
const configFile: File = {
path: rootFolder + "a/b/project/tsconfig.json",
content: JSON.stringify({ compilerOptions: { typeRoots: [] } })
};
const openFiles = [file1.path];
const host = ts.projectSystem.createServerHost([file1, file3, ts.projectSystem.libFile, configFile]);
const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host, ts.projectSystem.createLoggerWithInMemoryLogs(host));
const host = createServerHost([file1, file3, libFile, configFile]);
const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host, createLoggerWithInMemoryLogs(host));
verifyInitialOpen(file1);
file3.content += "export class d {}";
@@ -463,7 +465,7 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
openFiles
}
}] : []);
ts.projectSystem.baselineTsserverLogs("projectUpdatedInBackground", `${scenario} and ${subScenario}`, session);
baselineTsserverLogs("projectUpdatedInBackground", `${scenario} and ${subScenario}`, session);
});
}
verifyWithMaxCacheLimit("project is not at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true);
@@ -474,8 +476,8 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
describe("when event handler is set in the session", () => {
verifyProjectsUpdatedInBackgroundEvent("when event handler is set in the session", createSessionWithProjectChangedEventHandler);
function createSessionWithProjectChangedEventHandler(host: ts.projectSystem.TestServerHost, logger: ts.projectSystem.Logger | undefined): ProjectsUpdatedInBackgroundEventVerifier {
const { session, events: projectChangedEvents } = ts.projectSystem.createSessionWithEventTracking<ts.server.ProjectsUpdatedInBackgroundEvent>(
function createSessionWithProjectChangedEventHandler(host: TestServerHost, logger: Logger | undefined): ProjectsUpdatedInBackgroundEventVerifier {
const { session, events: projectChangedEvents } = createSessionWithEventTracking<ts.server.ProjectsUpdatedInBackgroundEvent>(
host,
ts.server.ProjectsUpdatedInBackgroundEvent,
logger && { logger }
@@ -518,11 +520,11 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
});
function createSessionThatUsesEvents(host: ts.projectSystem.TestServerHost, logger: ts.projectSystem.Logger | undefined, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier {
const { session, getEvents, clearEvents } = ts.projectSystem.createSessionWithDefaultEventHandler<ts.projectSystem.protocol.ProjectsUpdatedInBackgroundEvent>(
function createSessionThatUsesEvents(host: TestServerHost, logger: Logger | undefined, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier {
const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler<ts.server.protocol.ProjectsUpdatedInBackgroundEvent>(
host,
ts.server.ProjectsUpdatedInBackgroundEvent,
{ noGetErrOnBackgroundUpdate, logger: logger || ts.projectSystem.createHasErrorMessageLogger() }
{ noGetErrOnBackgroundUpdate, logger: logger || createHasErrorMessageLogger() }
);
return {
@@ -532,7 +534,7 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
};
function verifyProjectsUpdatedInBackgroundEventHandler(expected: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) {
const expectedEvents: ts.projectSystem.protocol.ProjectsUpdatedInBackgroundEventBody[] = ts.map(expected, e => {
const expectedEvents: ts.server.protocol.ProjectsUpdatedInBackgroundEventBody[] = ts.map(expected, e => {
return {
openFiles: e.data.openFiles
};

View File

@@ -1,34 +1,36 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File } from "../virtualFileSystemWithWatch";
import { createSession, openFilesForSession, configuredProjectAt, executeSessionRequest } from "./helpers";
const packageJson: ts.projectSystem.File = {
const packageJson: File = {
path: "/package.json",
content: `{ "dependencies": { "mobx": "*" } }`
};
const aTs: ts.projectSystem.File = {
const aTs: File = {
path: "/a.ts",
content: "export const foo = 0;",
};
const bTs: ts.projectSystem.File = {
const bTs: File = {
path: "/b.ts",
content: "foo",
};
const tsconfig: ts.projectSystem.File = {
const tsconfig: File = {
path: "/tsconfig.json",
content: "{}",
};
const ambientDeclaration: ts.projectSystem.File = {
const ambientDeclaration: File = {
path: "/ambient.d.ts",
content: "declare module 'ambient' {}"
};
const mobxPackageJson: ts.projectSystem.File = {
const mobxPackageJson: File = {
path: "/node_modules/mobx/package.json",
content: `{ "name": "mobx", "version": "1.0.0" }`
};
const mobxDts: ts.projectSystem.File = {
const mobxDts: File = {
path: "/node_modules/mobx/index.d.ts",
content: "export declare function observable(): unknown;"
};
const exportEqualsMappedType: ts.projectSystem.File = {
const exportEqualsMappedType: File = {
path: "/lib/foo/constants.d.ts",
content: `
type Signals = "SIGINT" | "SIGABRT";
@@ -96,8 +98,8 @@ describe("unittests:: tsserver:: exportMapCache", () => {
const symbolIdBefore = ts.getSymbolId(sigintPropBefore![0].symbol);
// Update program without clearing cache
session.executeCommandSeq<ts.projectSystem.protocol.UpdateOpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.UpdateOpen,
session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
changedFiles: [{
fileName: bTs.path,
@@ -123,22 +125,22 @@ describe("unittests:: tsserver:: exportMapCache", () => {
});
function setup() {
const host = ts.projectSystem.createServerHost([aTs, bTs, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts, exportEqualsMappedType]);
const session = ts.projectSystem.createSession(host);
ts.projectSystem.openFilesForSession([aTs, bTs], session);
const host = createServerHost([aTs, bTs, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts, exportEqualsMappedType]);
const session = createSession(host);
openFilesForSession([aTs, bTs], session);
const projectService = session.getProjectService();
const project = ts.projectSystem.configuredProjectAt(projectService, 0);
const project = configuredProjectAt(projectService, 0);
triggerCompletions();
const checker = project.getLanguageService().getProgram()!.getTypeChecker();
return { host, project, projectService, session, exportMapCache: project.getCachedExportInfoMap(), checker, triggerCompletions };
function triggerCompletions() {
const requestLocation: ts.projectSystem.protocol.FileLocationRequestArgs = {
const requestLocation: ts.server.protocol.FileLocationRequestArgs = {
file: bTs.path,
line: 1,
offset: 3,
};
ts.projectSystem.executeSessionRequest<ts.projectSystem.protocol.CompletionsRequest, ts.projectSystem.protocol.CompletionInfoResponse>(session, ts.projectSystem.protocol.CommandTypes.CompletionInfo, {
executeSessionRequest<ts.server.protocol.CompletionsRequest, ts.server.protocol.CompletionInfoResponse>(session, ts.server.protocol.CommandTypes.CompletionInfo, {
...requestLocation,
includeExternalModuleExports: true,
prefix: "foo",

View File

@@ -1,5 +1,7 @@
import * as ts from "../../_namespaces/ts";
import * as Harness from "../../_namespaces/Harness";
import { createServerHost, File, libFile } from "../virtualFileSystemWithWatch";
import { createProjectService, toExternalFiles, checkProjectActualFiles, toExternalFile, createSession, checkNumberOfProjects, checkNumberOfExternalProjects, checkNumberOfInferredProjects, verifyDynamic, checkProjectRootFiles, configuredProjectAt } from "./helpers";
describe("unittests:: tsserver:: ExternalProjects", () => {
describe("can handle tsconfig file name with difference casing", () => {
@@ -15,32 +17,32 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
})
};
const host = ts.projectSystem.createServerHost([f1, config], { useCaseSensitiveFileNames: false });
const service = ts.projectSystem.createProjectService(host);
const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false });
const service = createProjectService(host);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
const upperCaseConfigFilePath = ts.combinePaths(ts.getDirectoryPath(config.path).toUpperCase(), ts.getBaseFileName(config.path));
service.openExternalProject({
projectFileName: "/a/b/project.csproj",
rootFiles: ts.projectSystem.toExternalFiles([f1.path, upperCaseConfigFilePath]),
rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]),
options: {}
} as ts.projectSystem.protocol.ExternalProject);
} as ts.server.protocol.ExternalProject);
service.checkNumberOfProjects({ configuredProjects: 1 });
const project = service.configuredProjects.get(config.path)!;
if (lazyConfiguredProjectsFromExternalProject) {
assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
ts.projectSystem.checkProjectActualFiles(project, ts.emptyArray);
checkProjectActualFiles(project, ts.emptyArray);
}
else {
assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded
ts.projectSystem.checkProjectActualFiles(project, [upperCaseConfigFilePath]);
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
}
service.openClientFile(f1.path);
service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated
ts.projectSystem.checkProjectActualFiles(project, [upperCaseConfigFilePath]);
ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
@@ -57,9 +59,9 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
path: "/a/file1.ts",
content: "let x = [1, 2];"
};
const p1 = { projectFileName: "/a/proj1.csproj", rootFiles: [ts.projectSystem.toExternalFile(f1.path)], options: {} };
const p1 = { projectFileName: "/a/proj1.csproj", rootFiles: [toExternalFile(f1.path)], options: {} };
const host = ts.projectSystem.createServerHost([f1]);
const host = createServerHost([f1]);
host.require = (_initialPath, moduleName) => {
assert.equal(moduleName, "myplugin");
return {
@@ -85,17 +87,17 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
error: undefined
};
};
const session = ts.projectSystem.createSession(host, { globalPlugins: ["myplugin"] });
const session = createSession(host, { globalPlugins: ["myplugin"] });
session.executeCommand({
seq: 1,
type: "request",
command: "openExternalProjects",
arguments: { projects: [p1] }
} as ts.projectSystem.protocol.OpenExternalProjectsRequest);
} as ts.server.protocol.OpenExternalProjectsRequest);
const projectService = session.getProjectService();
ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 });
checkNumberOfProjects(projectService, { externalProjects: 1 });
assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName);
const handlerResponse = session.executeCommand({
@@ -106,10 +108,10 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
file: f1.path,
projectFileName: p1.projectFileName
}
} as ts.projectSystem.protocol.SemanticDiagnosticsSyncRequest);
} as ts.server.protocol.SemanticDiagnosticsSyncRequest);
assert.isDefined(handlerResponse.response);
const response = handlerResponse.response as ts.projectSystem.protocol.Diagnostic[];
const response = handlerResponse.response as ts.server.protocol.Diagnostic[];
assert.equal(response.length, 1);
assert.equal(response[0].text, "Plugin diagnostic");
});
@@ -127,23 +129,23 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
path: "/c/app.ts",
content: "let x = 1"
};
const makeProject = (f: ts.projectSystem.File) => ({ projectFileName: f.path + ".csproj", rootFiles: [ts.projectSystem.toExternalFile(f.path)], options: {} });
const makeProject = (f: File) => ({ projectFileName: f.path + ".csproj", rootFiles: [toExternalFile(f.path)], options: {} });
const p1 = makeProject(f1);
const p2 = makeProject(f2);
const p3 = makeProject(f3);
const host = ts.projectSystem.createServerHost([f1, f2, f3]);
const session = ts.projectSystem.createSession(host);
const host = createServerHost([f1, f2, f3]);
const session = createSession(host);
session.executeCommand({
seq: 1,
type: "request",
command: "openExternalProjects",
arguments: { projects: [p1, p2] }
} as ts.projectSystem.protocol.OpenExternalProjectsRequest);
} as ts.server.protocol.OpenExternalProjectsRequest);
const projectService = session.getProjectService();
ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 2 });
checkNumberOfProjects(projectService, { externalProjects: 2 });
assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName);
assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName);
@@ -152,8 +154,8 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
type: "request",
command: "openExternalProjects",
arguments: { projects: [p1, p3] }
} as ts.projectSystem.protocol.OpenExternalProjectsRequest);
ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 2 });
} as ts.server.protocol.OpenExternalProjectsRequest);
checkNumberOfProjects(projectService, { externalProjects: 2 });
assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName);
assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName);
@@ -162,15 +164,15 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
type: "request",
command: "openExternalProjects",
arguments: { projects: [] }
} as ts.projectSystem.protocol.OpenExternalProjectsRequest);
ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 0 });
} as ts.server.protocol.OpenExternalProjectsRequest);
checkNumberOfProjects(projectService, { externalProjects: 0 });
session.executeCommand({
seq: 3,
type: "request",
command: "openExternalProjects",
arguments: { projects: [p2] }
} as ts.projectSystem.protocol.OpenExternalProjectsRequest);
} as ts.server.protocol.OpenExternalProjectsRequest);
assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName);
});
@@ -184,69 +186,69 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
content: "let y =1;"
};
const externalProjectName = "externalproject";
const host = ts.projectSystem.createServerHost([file1, file2]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([file1, file2]);
const projectService = createProjectService(host);
projectService.openExternalProject({
rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]),
rootFiles: toExternalFiles([file1.path, file2.path]),
options: {},
projectFileName: externalProjectName
});
ts.projectSystem.checkNumberOfExternalProjects(projectService, 1);
ts.projectSystem.checkNumberOfInferredProjects(projectService, 0);
checkNumberOfExternalProjects(projectService, 1);
checkNumberOfInferredProjects(projectService, 0);
// open client file - should not lead to creation of inferred project
projectService.openClientFile(file1.path, file1.content);
ts.projectSystem.checkNumberOfExternalProjects(projectService, 1);
ts.projectSystem.checkNumberOfInferredProjects(projectService, 0);
checkNumberOfExternalProjects(projectService, 1);
checkNumberOfInferredProjects(projectService, 0);
// close client file - external project should still exists
projectService.closeClientFile(file1.path);
ts.projectSystem.checkNumberOfExternalProjects(projectService, 1);
ts.projectSystem.checkNumberOfInferredProjects(projectService, 0);
checkNumberOfExternalProjects(projectService, 1);
checkNumberOfInferredProjects(projectService, 0);
projectService.closeExternalProject(externalProjectName);
ts.projectSystem.checkNumberOfExternalProjects(projectService, 0);
ts.projectSystem.checkNumberOfInferredProjects(projectService, 0);
checkNumberOfExternalProjects(projectService, 0);
checkNumberOfInferredProjects(projectService, 0);
});
it("external project for dynamic file", () => {
const externalProjectName = "^ScriptDocument1 file1.ts";
const externalFiles = ts.projectSystem.toExternalFiles(["^ScriptDocument1 file1.ts"]);
const host = ts.projectSystem.createServerHost([]);
const projectService = ts.projectSystem.createProjectService(host);
const externalFiles = toExternalFiles(["^ScriptDocument1 file1.ts"]);
const host = createServerHost([]);
const projectService = createProjectService(host);
projectService.openExternalProject({
rootFiles: externalFiles,
options: {},
projectFileName: externalProjectName
});
ts.projectSystem.checkNumberOfExternalProjects(projectService, 1);
ts.projectSystem.checkNumberOfInferredProjects(projectService, 0);
ts.projectSystem.verifyDynamic(projectService, "/^scriptdocument1 file1.ts");
checkNumberOfExternalProjects(projectService, 1);
checkNumberOfInferredProjects(projectService, 0);
verifyDynamic(projectService, "/^scriptdocument1 file1.ts");
externalFiles[0].content = "let x =1;";
projectService.applyChangesInOpenFiles(ts.arrayIterator(externalFiles));
});
it("when file name starts with ^", () => {
const file: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/file.ts`,
const file: File = {
path: `/user/username/projects/myproject/file.ts`,
content: "const x = 10;"
};
const app: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/^app.ts`,
const app: File = {
path: `/user/username/projects/myproject/^app.ts`,
content: "const y = 10;"
};
const host = ts.projectSystem.createServerHost([file, app, ts.projectSystem.libFile]);
const service = ts.projectSystem.createProjectService(host);
const host = createServerHost([file, app, libFile]);
const service = createProjectService(host);
service.openExternalProjects([{
projectFileName: `${ts.tscWatch.projectRoot}/myproject.njsproj`,
projectFileName: `/user/username/projects/myproject/myproject.njsproj`,
rootFiles: [
ts.projectSystem.toExternalFile(file.path),
ts.projectSystem.toExternalFile(app.path)
toExternalFile(file.path),
toExternalFile(app.path)
],
options: { },
options: {},
}]);
});
@@ -282,15 +284,15 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
content: "let z =1;"
};
const externalProjectName = "externalproject";
const host = ts.projectSystem.createServerHost([file1, file2, file3, config1, config2]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([file1, file2, file3, config1, config2]);
const projectService = createProjectService(host);
projectService.openExternalProject({
rootFiles: ts.projectSystem.toExternalFiles([config1.path, config2.path, file3.path]),
rootFiles: toExternalFiles([config1.path, config2.path, file3.path]),
options: {},
projectFileName: externalProjectName
});
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 2 });
checkNumberOfProjects(projectService, { configuredProjects: 2 });
const proj1 = projectService.configuredProjects.get(config1.path);
const proj2 = projectService.configuredProjects.get(config2.path);
assert.isDefined(proj1);
@@ -298,35 +300,35 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
// open client file - should not lead to creation of inferred project
projectService.openClientFile(file1.path, file1.content);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 2 });
checkNumberOfProjects(projectService, { configuredProjects: 2 });
assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2);
projectService.openClientFile(file3.path, file3.content);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 });
assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2);
projectService.closeExternalProject(externalProjectName);
// open file 'file1' from configured project keeps project alive
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 });
assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
assert.isUndefined(projectService.configuredProjects.get(config2.path));
projectService.closeClientFile(file3.path);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 });
assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
assert.isUndefined(projectService.configuredProjects.get(config2.path));
assert.isTrue(projectService.inferredProjects[0].isOrphan());
projectService.closeClientFile(file1.path);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 });
assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
assert.isUndefined(projectService.configuredProjects.get(config2.path));
assert.isTrue(projectService.inferredProjects[0].isOrphan());
projectService.openClientFile(file2.path, file2.content);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.isUndefined(projectService.configuredProjects.get(config1.path));
assert.isDefined(projectService.configuredProjects.get(config2.path));
});
@@ -341,26 +343,26 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
content: JSON.stringify({ compilerOptions: {} })
};
const externalProjectName = "externalproject";
const host = ts.projectSystem.createServerHost([file1, configFile]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([file1, configFile]);
const projectService = createProjectService(host);
projectService.openClientFile(file1.path);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
projectService.openExternalProject({
rootFiles: ts.projectSystem.toExternalFiles([configFile.path]),
rootFiles: toExternalFiles([configFile.path]),
options: {},
projectFileName: externalProjectName
});
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
projectService.closeClientFile(file1.path);
// configured project is alive since it is opened as part of external project
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
projectService.closeExternalProject(externalProjectName);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 0 });
checkNumberOfProjects(projectService, { configuredProjects: 0 });
});
it("external project with included config file opened after configured project and then closed", () => {
@@ -377,33 +379,33 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
content: JSON.stringify({ compilerOptions: {} })
};
const externalProjectName = "externalproject";
const host = ts.projectSystem.createServerHost([file1, file2, ts.projectSystem.libFile, configFile]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([file1, file2, libFile, configFile]);
const projectService = createProjectService(host);
projectService.openClientFile(file1.path);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = projectService.configuredProjects.get(configFile.path);
projectService.openExternalProject({
rootFiles: ts.projectSystem.toExternalFiles([configFile.path]),
rootFiles: toExternalFiles([configFile.path]),
options: {},
projectFileName: externalProjectName
});
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
projectService.closeExternalProject(externalProjectName);
// configured project is alive since file is still open
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
projectService.closeClientFile(file1.path);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
projectService.openClientFile(file2.path);
ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkNumberOfProjects(projectService, { inferredProjects: 1 });
assert.isUndefined(projectService.configuredProjects.get(configFile.path));
});
@@ -416,16 +418,16 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
path: "/a/b/f2.ts",
content: "let y = 1"
};
const host = ts.projectSystem.createServerHost([file1, file2]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([file1, file2]);
const projectService = createProjectService(host);
projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: ts.projectSystem.toExternalFiles([file1.path]) });
ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 });
ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [file1.path]);
projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) });
checkNumberOfProjects(projectService, { externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [file1.path]);
projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]) });
ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 });
ts.projectSystem.checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]);
projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) });
checkNumberOfProjects(projectService, { externalProjects: 1 });
checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]);
});
it("can update external project when set of root files was not changed", () => {
@@ -442,18 +444,18 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
content: "export let y = 1"
};
const host = ts.projectSystem.createServerHost([file1, file2, file3]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([file1, file2, file3]);
const projectService = createProjectService(host);
projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ts.ModuleResolutionKind.NodeJs }, rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]) });
ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 });
ts.projectSystem.checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]);
ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]);
projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ts.ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) });
checkNumberOfProjects(projectService, { externalProjects: 1 });
checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]);
checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]);
projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ts.ModuleResolutionKind.Classic }, rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]) });
ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 });
ts.projectSystem.checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]);
ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]);
projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ts.ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) });
checkNumberOfProjects(projectService, { externalProjects: 1 });
checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]);
checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]);
});
it("language service disabled state is updated in external projects", () => {
@@ -465,17 +467,17 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
path: "/a/largefile.js",
content: ""
};
const host = ts.projectSystem.createServerHost([f1, f2]);
const host = createServerHost([f1, f2]);
const originalGetFileSize = host.getFileSize;
host.getFileSize = (filePath: string) =>
filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
const service = ts.projectSystem.createProjectService(host);
const service = createProjectService(host);
const projectFileName = "/a/proj.csproj";
service.openExternalProject({
projectFileName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, f2.path]),
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
service.checkNumberOfProjects({ externalProjects: 1 });
@@ -483,7 +485,7 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
service.openExternalProject({
projectFileName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path]),
rootFiles: toExternalFiles([f1.path]),
options: {}
});
service.checkNumberOfProjects({ externalProjects: 1 });
@@ -491,7 +493,7 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
service.openExternalProject({
projectFileName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, f2.path]),
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
service.checkNumberOfProjects({ externalProjects: 1 });
@@ -509,13 +511,13 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
content: "{}"
};
const projectFileName = "/user/someuser/project/WebApplication6.csproj";
const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, site, configFile]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([libFile, site, configFile]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
const externalProject: ts.projectSystem.protocol.ExternalProject = {
const externalProject: ts.server.protocol.ExternalProject = {
projectFileName,
rootFiles: [ts.projectSystem.toExternalFile(site.path), ts.projectSystem.toExternalFile(configFile.path)],
rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)],
options: { allowJs: false },
typeAcquisition: { include: [] }
};
@@ -523,23 +525,23 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
projectService.openExternalProjects([externalProject]);
let knownProjects = projectService.synchronizeProjectList([]);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 });
checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 });
const configProject = ts.projectSystem.configuredProjectAt(projectService, 0);
ts.projectSystem.checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ?
const configProject = configuredProjectAt(projectService, 0);
checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ?
ts.emptyArray : // Since no files opened from this project, its not loaded
[configFile.path]);
host.deleteFile(configFile.path);
knownProjects = projectService.synchronizeProjectList(ts.map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 });
checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 });
externalProject.rootFiles.length = 1;
projectService.openExternalProjects([externalProject]);
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 });
ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [site.path, ts.projectSystem.libFile.path]);
checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 });
checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false);
@@ -563,45 +565,45 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
path: "/a/b/tsconfig.json",
content: ""
};
const host = ts.projectSystem.createServerHost([f1, f2]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([f1, f2]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
// open external project
const projectName = "/a/b/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, f2.path]),
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
projectService.openClientFile(f1.path);
projectService.checkNumberOfProjects({ externalProjects: 1 });
ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
// rename lib.ts to tsconfig.json
host.renameFile(f2.path, tsconfig.path);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, tsconfig.path]),
rootFiles: toExternalFiles([f1.path, tsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
if (lazyConfiguredProjectsFromExternalProject) {
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), ts.emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 0), ts.emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]);
// rename tsconfig.json back to lib.ts
host.renameFile(tsconfig.path, f2.path);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, f2.path]),
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false);
@@ -633,71 +635,71 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
path: "/a/b/d/tsconfig.json",
content: "{}"
};
const host = ts.projectSystem.createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
// open external project
const projectName = "/a/b/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path]),
rootFiles: toExternalFiles([f1.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
if (lazyConfiguredProjectsFromExternalProject) {
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), ts.emptyArray); // Configured project created but not loaded till actually needed
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 1), ts.emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 0), ts.emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), ts.emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// remove one config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, dTsconfig.path]),
rootFiles: toExternalFiles([f1.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]);
// remove second config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path]),
rootFiles: toExternalFiles([f1.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// open two config files
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
if (lazyConfiguredProjectsFromExternalProject) {
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), ts.emptyArray); // Configured project created but not loaded till actually needed
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 1), ts.emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 0), ts.emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), ts.emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// close all projects - no projects should be opened
projectService.closeExternalProject(projectName);
@@ -756,18 +758,18 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
}
})
};
const host = ts.projectSystem.createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" });
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" });
const projectService = createProjectService(host);
projectService.openClientFile(app.path);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]);
host.writeFile(config2.path, config2.content);
host.checkTimeoutQueueLengthAndRun(2);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]);
});
it("should handle non-existing directories in config file", () => {
@@ -785,8 +787,8 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
]
})
};
const host = ts.projectSystem.createServerHost([f, config]);
const projectService = ts.projectSystem.createProjectService(host);
const host = createServerHost([f, config]);
const projectService = createProjectService(host);
projectService.openClientFile(f.path);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const project = projectService.configuredProjects.get(config.path)!;
@@ -815,46 +817,46 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
content: JSON.stringify({})
};
const projectFileName = "/a/b/project.csproj";
const host = ts.projectSystem.createServerHost([f1, config]);
const service = ts.projectSystem.createProjectService(host);
const host = createServerHost([f1, config]);
const service = createProjectService(host);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } });
service.openExternalProject({
projectFileName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, config.path]),
rootFiles: toExternalFiles([f1.path, config.path]),
options: {}
} as ts.projectSystem.protocol.ExternalProject);
} as ts.server.protocol.ExternalProject);
service.checkNumberOfProjects({ configuredProjects: 1 });
const project = service.configuredProjects.get(config.path)!;
assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
ts.projectSystem.checkProjectActualFiles(project, ts.emptyArray);
checkProjectActualFiles(project, ts.emptyArray);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } });
assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded
ts.projectSystem.checkProjectActualFiles(project, [config.path, f1.path]);
checkProjectActualFiles(project, [config.path, f1.path]);
service.closeExternalProject(projectFileName);
service.checkNumberOfProjects({});
service.openExternalProject({
projectFileName,
rootFiles: ts.projectSystem.toExternalFiles([f1.path, config.path]),
rootFiles: toExternalFiles([f1.path, config.path]),
options: {}
} as ts.projectSystem.protocol.ExternalProject);
} as ts.server.protocol.ExternalProject);
service.checkNumberOfProjects({ configuredProjects: 1 });
const project2 = service.configuredProjects.get(config.path)!;
assert.equal(project2.pendingReload, ts.ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded
ts.projectSystem.checkProjectActualFiles(project2, [config.path, f1.path]);
checkProjectActualFiles(project2, [config.path, f1.path]);
});
it("handles creation of external project with jsconfig before jsconfig creation watcher is invoked", () => {
const projectFileName = `${ts.tscWatch.projectRoot}/WebApplication36.csproj`;
const tsconfig: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const projectFileName = `/user/username/projects/myproject/WebApplication36.csproj`;
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: "{}"
};
const files = [ts.projectSystem.libFile, tsconfig];
const host = ts.projectSystem.createServerHost(files);
const service = ts.projectSystem.createProjectService(host);
const files = [libFile, tsconfig];
const host = createServerHost(files);
const service = createProjectService(host);
// Create external project
service.openExternalProjects([{
@@ -862,12 +864,12 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
rootFiles: [{ fileName: tsconfig.path }],
options: { allowJs: false }
}]);
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 });
checkNumberOfProjects(service, { configuredProjects: 1 });
const configProject = service.configuredProjects.get(tsconfig.path.toLowerCase())!;
ts.projectSystem.checkProjectActualFiles(configProject, [tsconfig.path]);
checkProjectActualFiles(configProject, [tsconfig.path]);
// write js file, open external project and open it for edit
const jsFilePath = `${ts.tscWatch.projectRoot}/javascript.js`;
const jsFilePath = `/user/username/projects/myproject/javascript.js`;
host.writeFile(jsFilePath, "");
service.openExternalProjects([{
projectFileName,
@@ -875,14 +877,14 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
options: { allowJs: false }
}]);
service.applyChangesInOpenFiles(ts.singleIterator({ fileName: jsFilePath, scriptKind: ts.ScriptKind.JS, content: "" }));
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(configProject, [tsconfig.path]);
checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
checkProjectActualFiles(configProject, [tsconfig.path]);
const inferredProject = service.inferredProjects[0];
ts.projectSystem.checkProjectActualFiles(inferredProject, [ts.projectSystem.libFile.path, jsFilePath]);
checkProjectActualFiles(inferredProject, [libFile.path, jsFilePath]);
// write jsconfig file
const jsConfig: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/jsconfig.json`,
const jsConfig: File = {
path: `/user/username/projects/myproject/jsconfig.json`,
content: "{}"
};
// Dont invoke file creation watchers as the repro suggests
@@ -894,11 +896,11 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
rootFiles: [{ fileName: jsConfig.path }, { fileName: tsconfig.path }, { fileName: jsFilePath }],
options: { allowJs: false }
}]);
ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 2, inferredProjects: 1 });
ts.projectSystem.checkProjectActualFiles(configProject, [tsconfig.path]);
checkNumberOfProjects(service, { configuredProjects: 2, inferredProjects: 1 });
checkProjectActualFiles(configProject, [tsconfig.path]);
assert.isTrue(inferredProject.isOrphan());
const jsConfigProject = service.configuredProjects.get(jsConfig.path.toLowerCase())!;
ts.projectSystem.checkProjectActualFiles(jsConfigProject, [jsConfig.path, jsFilePath, ts.projectSystem.libFile.path]);
checkProjectActualFiles(jsConfigProject, [jsConfig.path, jsFilePath, libFile.path]);
});
it("does not crash if external file does not exist", () => {
@@ -908,11 +910,11 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
};
const p1 = {
projectFileName: "/a/proj1.csproj",
rootFiles: [ts.projectSystem.toExternalFile(f1.path)],
rootFiles: [toExternalFile(f1.path)],
options: {},
};
const host = ts.projectSystem.createServerHost([f1]);
const host = createServerHost([f1]);
host.require = (_initialPath, moduleName) => {
assert.equal(moduleName, "myplugin");
return {
@@ -927,7 +929,7 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
error: undefined,
};
};
const session = ts.projectSystem.createSession(host, {
const session = createSession(host, {
globalPlugins: ["myplugin"],
});
const projectService = session.getProjectService();
@@ -937,6 +939,6 @@ describe("unittests:: tsserver:: ExternalProjects", () => {
// info for it. If tsserver does not handle this case, the following
// method call will crash.
projectService.openExternalProject(p1);
ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 });
checkNumberOfProjects(projectService, { externalProjects: 1 });
});
});

View File

@@ -1,21 +1,23 @@
import * as ts from "../../_namespaces/ts";
import { createServerHost, File, libFile } from "../virtualFileSystemWithWatch";
import { createSession, openFilesForSession, checkNumberOfProjects, configuredProjectAt, createLoggerWithInMemoryLogs, verifyGetErrRequest, closeFilesForSession, protocolTextSpanFromSubstring, baselineTsserverLogs } from "./helpers";
describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => {
it("works when extends is specified with a case insensitive file system", () => {
const rootPath = "/Users/username/dev/project";
const file1: ts.projectSystem.File = {
const file1: File = {
path: `${rootPath}/index.ts`,
content: 'import {x} from "file2";',
};
const file2: ts.projectSystem.File = {
const file2: File = {
path: `${rootPath}/file2.js`,
content: "",
};
const file2Dts: ts.projectSystem.File = {
const file2Dts: File = {
path: `${rootPath}/types/file2/index.d.ts`,
content: "export declare const x: string;",
};
const tsconfigAll: ts.projectSystem.File = {
const tsconfigAll: File = {
path: `${rootPath}/tsconfig.all.json`,
content: JSON.stringify({
compilerOptions: {
@@ -26,59 +28,59 @@ describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => {
},
}),
};
const tsconfig: ts.projectSystem.File = {
const tsconfig: File = {
path: `${rootPath}/tsconfig.json`,
content: JSON.stringify({ extends: "./tsconfig.all.json" }),
};
const host = ts.projectSystem.createServerHost([file1, file2, file2Dts, ts.projectSystem.libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false });
const session = ts.projectSystem.createSession(host);
const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false });
const session = createSession(host);
ts.projectSystem.openFilesForSession([file1], session);
openFilesForSession([file1], session);
const projectService = session.getProjectService();
ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const diagnostics = ts.projectSystem.configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics();
const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics();
assert.deepEqual(diagnostics, []);
});
it("works when renaming file with different casing", () => {
const loggerFile: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/Logger.ts`,
const loggerFile: File = {
path: `/user/username/projects/myproject/Logger.ts`,
content: `export class logger { }`
};
const anotherFile: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/another.ts`,
const anotherFile: File = {
path: `/user/username/projects/myproject/another.ts`,
content: `import { logger } from "./Logger"; new logger();`
};
const tsconfig: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { forceConsistentCasingInFileNames: true }
})
};
const host = ts.projectSystem.createServerHost([loggerFile, anotherFile, tsconfig, ts.projectSystem.libFile, tsconfig]);
const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
ts.projectSystem.openFilesForSession([{ file: loggerFile, projectRootPath: ts.tscWatch.projectRoot }], session);
ts.projectSystem.verifyGetErrRequest({ session, host, files: [loggerFile] });
const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]);
const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) });
openFilesForSession([{ file: loggerFile, projectRootPath: "/user/username/projects/myproject" }], session);
verifyGetErrRequest({ session, host, files: [loggerFile] });
const newLoggerPath = loggerFile.path.toLowerCase();
host.renameFile(loggerFile.path, newLoggerPath);
ts.projectSystem.closeFilesForSession([loggerFile], session);
ts.projectSystem.openFilesForSession([{ file: newLoggerPath, content: loggerFile.content, projectRootPath: ts.tscWatch.projectRoot }], session);
closeFilesForSession([loggerFile], session);
openFilesForSession([{ file: newLoggerPath, content: loggerFile.content, projectRootPath: "/user/username/projects/myproject" }], session);
// Apply edits for rename
ts.projectSystem.openFilesForSession([{ file: anotherFile, projectRootPath: ts.tscWatch.projectRoot }], session);
session.executeCommandSeq<ts.projectSystem.protocol.UpdateOpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.UpdateOpen,
openFilesForSession([{ file: anotherFile, projectRootPath: "/user/username/projects/myproject" }], session);
session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
changedFiles: [{
fileName: anotherFile.path,
textChanges: [{
newText: "./logger",
...ts.projectSystem.protocolTextSpanFromSubstring(
...protocolTextSpanFromSubstring(
anotherFile.content,
"./Logger"
)
@@ -88,39 +90,39 @@ describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => {
});
// Check errors in both files
ts.projectSystem.verifyGetErrRequest({ session, host, files: [newLoggerPath, anotherFile] });
ts.projectSystem.baselineTsserverLogs("forceConsistentCasingInFileNames", "works when renaming file with different casing", session);
verifyGetErrRequest({ session, host, files: [newLoggerPath, anotherFile] });
baselineTsserverLogs("forceConsistentCasingInFileNames", "works when renaming file with different casing", session);
});
it("when changing module name with different casing", () => {
const loggerFile: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/Logger.ts`,
const loggerFile: File = {
path: `/user/username/projects/myproject/Logger.ts`,
content: `export class logger { }`
};
const anotherFile: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/another.ts`,
const anotherFile: File = {
path: `/user/username/projects/myproject/another.ts`,
content: `import { logger } from "./Logger"; new logger();`
};
const tsconfig: ts.projectSystem.File = {
path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
const tsconfig: File = {
path: `/user/username/projects/myproject/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { forceConsistentCasingInFileNames: true }
})
};
const host = ts.projectSystem.createServerHost([loggerFile, anotherFile, tsconfig, ts.projectSystem.libFile, tsconfig]);
const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) });
ts.projectSystem.openFilesForSession([{ file: anotherFile, projectRootPath: ts.tscWatch.projectRoot }], session);
ts.projectSystem.verifyGetErrRequest({ session, host, files: [anotherFile] });
const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]);
const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) });
openFilesForSession([{ file: anotherFile, projectRootPath: "/user/username/projects/myproject" }], session);
verifyGetErrRequest({ session, host, files: [anotherFile] });
session.executeCommandSeq<ts.projectSystem.protocol.UpdateOpenRequest>({
command: ts.projectSystem.protocol.CommandTypes.UpdateOpen,
session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
changedFiles: [{
fileName: anotherFile.path,
textChanges: [{
newText: "./logger",
...ts.projectSystem.protocolTextSpanFromSubstring(
...protocolTextSpanFromSubstring(
anotherFile.content,
"./Logger"
)
@@ -130,7 +132,7 @@ describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => {
});
// Check errors in both files
ts.projectSystem.verifyGetErrRequest({ host, session, files: [anotherFile] });
ts.projectSystem.baselineTsserverLogs("forceConsistentCasingInFileNames", "when changing module name with different casing", session);
verifyGetErrRequest({ host, session, files: [anotherFile] });
baselineTsserverLogs("forceConsistentCasingInFileNames", "when changing module name with different casing", session);
});
});

Some files were not shown because too many files have changed in this diff Show More