Refactor to simplify project references tsc-watch and tsserver tests (#42926)

This commit is contained in:
Sheetal Nandi
2021-02-23 11:27:16 -08:00
committed by GitHub
parent 2a01f923ca
commit c117c266e0
11 changed files with 4022 additions and 605 deletions

View File

@@ -147,6 +147,7 @@
"unittests/tscWatch/forceConsistentCasingInFileNames.ts",
"unittests/tscWatch/incremental.ts",
"unittests/tscWatch/programUpdates.ts",
"unittests/tscWatch/projectsWithReferences.ts",
"unittests/tscWatch/resolutionCache.ts",
"unittests/tscWatch/sourceOfProjectReferenceRedirect.ts",
"unittests/tscWatch/watchApi.ts",
@@ -192,6 +193,7 @@
"unittests/tsserver/projectReferences.ts",
"unittests/tsserver/projectReferencesSourcemap.ts",
"unittests/tsserver/projects.ts",
"unittests/tsserver/projectsWithReferences.ts",
"unittests/tsserver/refactors.ts",
"unittests/tsserver/reload.ts",
"unittests/tsserver/reloadProjects.ts",

View File

@@ -10,18 +10,6 @@ namespace ts.tscWatch {
);
}
export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], defaultOptions?: BuildOptions) {
const host = createSolutionBuilderHost(system);
return ts.createSolutionBuilder(host, rootNames, defaultOptions || {});
}
export function ensureErrorFreeBuild(host: WatchedSystem, rootNames: readonly string[]) {
// ts build should succeed
const solutionBuilder = createSolutionBuilder(host, rootNames, {});
solutionBuilder.build();
assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
}
type OutputFileStamp = [string, Date | undefined, boolean];
function transformOutputToOutputFileStamp(f: string, host: TsBuildWatchSystem): OutputFileStamp {
return [f, host.getModifiedTime(f), host.writtenFiles.has(host.toFullPath(f))] as OutputFileStamp;
@@ -39,10 +27,6 @@ namespace ts.tscWatch {
type ReadonlyFile = Readonly<File>;
/** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */
type SubProjectFiles = [ReadonlyFile, ReadonlyFile] | [ReadonlyFile, ReadonlyFile, ReadonlyFile, ReadonlyFile];
function getProjectPath(project: string) {
return `${projectsLocation}/${project}`;
}
function projectPath(subProject: SubProject) {
return getFilePathInProject(project, subProject);
}
@@ -51,10 +35,6 @@ namespace ts.tscWatch {
return `${projectPath(subProject)}/${baseFileName.toLowerCase()}`;
}
function projectFileName(subProject: SubProject, baseFileName: string) {
return `${projectPath(subProject)}/${baseFileName}`;
}
function projectFile(subProject: SubProject, baseFileName: string): File {
return getFileFromProject(project, `${subProject}/${baseFileName}`);
}
@@ -551,580 +531,6 @@ let x: string = 10;`),
});
});
describe("tsc-watch and tsserver works with project references", () => {
describe("invoking when references are already built", () => {
function verifyWatchesOfProject(host: TsBuildWatchSystem, expectedWatchedFiles: readonly string[], expectedWatchedDirectoriesRecursive: readonly string[], expectedWatchedDirectories?: readonly string[]) {
checkWatchedFilesDetailed(host, expectedWatchedFiles, 1);
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories || emptyArray, 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true);
}
function createSolutionOfProject(allFiles: readonly File[],
currentDirectory: string,
solutionBuilderconfig: string,
getOutputFileStamps: (host: TsBuildWatchSystem) => readonly OutputFileStamp[]) {
// Build the composite project
const host = createTsBuildWatchSystem(allFiles, { currentDirectory });
const solutionBuilder = createSolutionBuilder(host, [solutionBuilderconfig], {});
solutionBuilder.build();
const outputFileStamps = getOutputFileStamps(host);
for (const stamp of outputFileStamps) {
assert.isDefined(stamp[1], `${stamp[0]} expected to be present`);
}
return { host, solutionBuilder };
}
function createSolutionAndWatchModeOfProject(
allFiles: readonly File[],
currentDirectory: string,
solutionBuilderconfig: string,
watchConfig: string,
getOutputFileStamps: (host: TsBuildWatchSystem) => readonly OutputFileStamp[]) {
// Build the composite project
const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
// Build in watch mode
const watch = createWatchOfConfigFile(watchConfig, host);
checkOutputErrorsInitial(host, emptyArray);
return { host, solutionBuilder, watch };
}
function createSolutionAndServiceOfProject(allFiles: readonly File[],
currentDirectory: string,
solutionBuilderconfig: string,
openFileName: string,
getOutputFileStamps: (host: TsBuildWatchSystem) => readonly OutputFileStamp[]) {
// Build the composite project
const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
// service
const service = projectSystem.createProjectService(host);
service.openClientFile(openFileName);
return { host, solutionBuilder, service };
}
function checkProjectActualFiles(service: projectSystem.TestProjectService, configFile: string, expectedFiles: readonly string[]) {
projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 });
projectSystem.checkProjectActualFiles(service.configuredProjects.get(configFile.toLowerCase())!, expectedFiles);
}
function verifyDependencies(watch: Watch, filePath: string, expected: readonly string[]) {
checkArray(`${filePath} dependencies`, watch.getCurrentProgram().getAllDependencies(watch.getCurrentProgram().getSourceFile(filePath)!), expected);
}
describe("on sample project", () => {
const coreIndexDts = projectFileName(SubProject.core, "index.d.ts");
const coreAnotherModuleDts = projectFileName(SubProject.core, "anotherModule.d.ts");
const logicIndexDts = projectFileName(SubProject.logic, "index.d.ts");
const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests));
const expectedProjectFiles = () => [libFile, ...tests, ...logic.slice(1), ...core.slice(1, core.length - 1)].map(f => f.path);
const expectedProgramFiles = () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts];
function createSolutionAndWatchMode() {
return createSolutionAndWatchModeOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[0].path, getOutputFileStamps);
}
function createSolutionAndService() {
return createSolutionAndServiceOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[1].path, getOutputFileStamps);
}
function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) {
verifyWatchesOfProject(
host,
withTsserver ?
[...core.slice(0, core.length - 1), ...logic, tests[0], libFile].map(f => f.path.toLowerCase()) :
[core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase())),
expectedWatchedDirectoriesRecursive
);
}
function verifyScenario(
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
expectedProgramFilesAfterEdit: () => readonly string[],
expectedProjectFilesAfterEdit: () => readonly string[]
) {
it("with tsc-watch", () => {
const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
edit(host, solutionBuilder);
host.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(host, emptyArray);
checkProgramActualFiles(watch.getCurrentProgram().getProgram(), expectedProgramFilesAfterEdit());
});
it("with tsserver", () => {
const { host, solutionBuilder, service } = createSolutionAndService();
edit(host, solutionBuilder);
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(service, tests[0].path, expectedProjectFilesAfterEdit());
});
}
describe("verifies dependencies and watches", () => {
it("with tsc-watch", () => {
const { host, watch } = createSolutionAndWatchMode();
verifyWatches(host);
verifyDependencies(watch, coreIndexDts, [coreIndexDts]);
verifyDependencies(watch, coreAnotherModuleDts, [coreAnotherModuleDts]);
verifyDependencies(watch, logicIndexDts, [logicIndexDts, coreAnotherModuleDts]);
verifyDependencies(watch, tests[1].path, expectedProgramFiles().filter(f => f !== libFile.path));
});
it("with tsserver", () => {
const { host } = createSolutionAndService();
verifyWatches(host, /*withTsserver*/ true);
});
});
describe("local edit in ts file, result in watch compilation because logic.d.ts is written", () => {
verifyScenario((host, solutionBuilder) => {
host.writeFile(logic[1].path, `${logic[1].content}
function foo() {
}`);
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath);
solutionBuilder.buildNextInvalidatedProject();
// not ideal, but currently because of d.ts but no new file is written
// There will be timeout queued even though file contents are same
}, expectedProgramFiles, expectedProjectFiles);
});
describe("non local edit in ts file, rebuilds in watch compilation", () => {
verifyScenario((host, solutionBuilder) => {
host.writeFile(logic[1].path, `${logic[1].content}
export function gfoo() {
}`);
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath);
solutionBuilder.buildNextInvalidatedProject();
}, expectedProgramFiles, expectedProjectFiles);
});
describe("change in project reference config file builds correctly", () => {
verifyScenario((host, solutionBuilder) => {
host.writeFile(logic[0].path, JSON.stringify({
compilerOptions: { composite: true, declaration: true, declarationDir: "decls" },
references: [{ path: "../core" }]
}));
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath, ConfigFileProgramReloadLevel.Full);
solutionBuilder.buildNextInvalidatedProject();
}, () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")], expectedProjectFiles);
});
});
describe("on transitive references", () => {
const project = "transitiveReferences";
const aTsFile = getFileFromProject(project, "a.ts");
const bTsFile = getFileFromProject(project, "b.ts");
const cTsFile = getFileFromProject(project, "c.ts");
const aTsconfigFile = getFileFromProject(project, "tsconfig.a.json");
const bTsconfigFile = getFileFromProject(project, "tsconfig.b.json");
const cTsconfigFile = getFileFromProject(project, "tsconfig.c.json");
const refs = getFileFromProject(project, "refs/a.d.ts");
function getRootFile(multiFolder: boolean, fileFromDisk: File, multiFolderPath: string): File {
return multiFolder ? {
path: getFilePathInProject(project, multiFolderPath),
content: fileFromDisk.content
// Replace the relative imports
.replace("./", "../")
} : fileFromDisk;
}
function dtsFile(extensionLessFile: string) {
return getFilePathInProject(project, `${extensionLessFile}.d.ts`);
}
function jsFile(extensionLessFile: string) {
return getFilePathInProject(project, `${extensionLessFile}.js`);
}
function verifyWatchState(
host: TsBuildWatchSystem,
watch: Watch,
expectedProgramFiles: readonly string[],
expectedWatchedFiles: readonly string[],
expectedWatchedDirectoriesRecursive: readonly string[],
dependencies: readonly [string, readonly string[]][],
expectedWatchedDirectories?: readonly string[]) {
checkProgramActualFiles(watch.getCurrentProgram().getProgram(), expectedProgramFiles);
verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
for (const [file, deps] of dependencies) {
verifyDependencies(watch, file, deps);
}
}
function getTsConfigFile(multiFolder: boolean, fileFromDisk: File, folder: string): File {
if (!multiFolder) return fileFromDisk;
return {
path: getFilePathInProject(project, `${folder}/tsconfig.json`),
content: fileFromDisk.content
// Replace files array
.replace(`${folder}.ts`, "index.ts")
// Replace path mappings
.replace("./*", "../*")
.replace("./refs", "../refs")
// Replace references
.replace("tsconfig.a.json", "../a")
.replace("tsconfig.b.json", "../b")
};
}
// function writeFile(file: File) {
// Harness.IO.writeFile(file.path.replace(projectsLocation, "c:/temp"), file.content);
// }
function verifyTransitiveReferences(multiFolder: boolean) {
const aTs = getRootFile(multiFolder, aTsFile, "a/index.ts");
const bTs = getRootFile(multiFolder, bTsFile, "b/index.ts");
const cTs = getRootFile(multiFolder, cTsFile, "c/index.ts");
const configToBuild = multiFolder ? "c/tsconfig.json" : "tsconfig.c.json";
const aTsconfig = getTsConfigFile(multiFolder, aTsconfigFile, "a");
const bTsconfig = getTsConfigFile(multiFolder, bTsconfigFile, "b");
const cTsconfig = getTsConfigFile(multiFolder, cTsconfigFile, "c");
// if (multiFolder) {
// writeFile(aTs);
// writeFile(bTs);
// writeFile(cTs);
// writeFile(aTsconfig);
// writeFile(bTsconfig);
// writeFile(cTsconfig);
// }
const allFiles = [libFile, aTs, bTs, cTs, aTsconfig, bTsconfig, cTsconfig, refs];
const aDts = dtsFile(multiFolder ? "a/index" : "a"), bDts = dtsFile(multiFolder ? "b/index" : "b");
const expectedFiles = [jsFile(multiFolder ? "a/index" : "a"), aDts, jsFile(multiFolder ? "b/index" : "b"), bDts, jsFile(multiFolder ? "c/index" : "c")];
const expectedProgramFiles = [cTs.path, libFile.path, aDts, refs.path, bDts];
const expectedProjectFiles = [cTs.path, libFile.path, aTs.path, refs.path, bTs.path];
const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
const expectedProjectWatchedFiles = expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
const expectedWatchedDirectories = multiFolder ? [
getProjectPath(project).toLowerCase() // watches for directories created for resolution of b
] : emptyArray;
const nrefsPath = multiFolder ? ["../nrefs/*"] : ["./nrefs/*"];
const expectedWatchedDirectoriesRecursive = [
...(multiFolder ? [
getFilePathInProject(project, "a"), // Failed to package json
getFilePathInProject(project, "b"), // Failed to package json
] : []),
getFilePathInProject(project, "refs"), // Failed lookup since refs/a.ts does not exist
...projectSystem.getTypeRootsFromLocation(multiFolder ? getFilePathInProject(project, "c") : getProjectPath(project))
].map(s => s.toLowerCase());
const defaultDependencies: readonly [string, readonly string[]][] = [
[aDts, [aDts]],
[bDts, [bDts, aDts]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bDts, aDts]]
];
function createSolutionAndWatchMode() {
return createSolutionAndWatchModeOfProject(allFiles, getProjectPath(project), configToBuild, configToBuild, getOutputFileStamps);
}
function createSolutionAndService() {
return createSolutionAndServiceOfProject(allFiles, getProjectPath(project), configToBuild, cTs.path, getOutputFileStamps);
}
function getOutputFileStamps(host: TsBuildWatchSystem) {
return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host));
}
function verifyProgram(host: TsBuildWatchSystem, watch: Watch) {
verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies, expectedWatchedDirectories);
}
function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: readonly string[]) {
verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos });
}
interface VerifyServerState {
host: TsBuildWatchSystem;
service: projectSystem.TestProjectService;
expectedProjectFiles: readonly string[];
expectedProjectWatchedFiles: readonly string[];
expectedWatchedDirectoriesRecursive: readonly string[];
orphanInfos?: readonly string[];
}
function verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos }: VerifyServerState) {
checkProjectActualFiles(service, cTsconfig.path, expectedProjectFiles.concat(cTsconfig.path));
const watchedFiles = expectedProjectWatchedFiles.filter(f => f !== cTs.path.toLowerCase());
const actualOrphan = arrayFrom(mapDefinedIterator(
service.filenameToScriptInfo.values(),
v => v.containingProjects.length === 0 ? v.fileName : undefined
));
assert.equal(actualOrphan.length, orphanInfos ? orphanInfos.length : 0, `Orphans found: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
if (orphanInfos && orphanInfos.length) {
for (const orphan of orphanInfos) {
const info = service.getScriptInfoForPath(orphan as Path);
assert.isDefined(info, `${orphan} expected to be present. Actual: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
assert.equal(info!.containingProjects.length, 0);
watchedFiles.push(orphan);
}
}
verifyWatchesOfProject(host, watchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
}
interface VerifyScenario {
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void;
schedulesFailedWatchUpdate?: boolean;
expectedEditErrors: readonly string[];
expectedProgramFiles: readonly string[];
expectedProjectFiles: readonly string[];
expectedWatchedFiles: readonly string[];
expectedProjectWatchedFiles: readonly string[];
expectedWatchedDirectoriesRecursive: readonly string[];
dependencies: readonly [string, readonly string[]][];
revert?: (host: TsBuildWatchSystem) => void;
orphanInfosAfterEdit?: readonly string[];
orphanInfosAfterRevert?: readonly string[];
}
function verifyScenario({ edit, schedulesFailedWatchUpdate, expectedEditErrors, expectedProgramFiles, expectedProjectFiles, expectedWatchedFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, dependencies, revert, orphanInfosAfterEdit, orphanInfosAfterRevert }: VerifyScenario) {
it("with tsc-watch", () => {
const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
edit(host, solutionBuilder);
host.checkTimeoutQueueLengthAndRun(schedulesFailedWatchUpdate ? 2 : 1);
checkOutputErrorsIncremental(host, expectedEditErrors);
verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, dependencies, expectedWatchedDirectories);
if (revert) {
revert(host);
host.checkTimeoutQueueLengthAndRun(schedulesFailedWatchUpdate ? 2 : 1);
checkOutputErrorsIncremental(host, emptyArray);
verifyProgram(host, watch);
}
});
if (!multiFolder) return; // With side by side file open is in inferred project without any settings
it("with tsserver", () => {
const { host, solutionBuilder, service } = createSolutionAndService();
edit(host, solutionBuilder);
host.checkTimeoutQueueLengthAndRun(schedulesFailedWatchUpdate ? 3 : 2);
verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos: orphanInfosAfterEdit });
if (revert) {
revert(host);
host.checkTimeoutQueueLengthAndRun(schedulesFailedWatchUpdate ? 3 : 2);
verifyProject(host, service, orphanInfosAfterRevert);
}
});
}
describe("verifies dependencies and watches", () => {
// Initial build
it("with tsc-watch", () => {
const { host, watch } = createSolutionAndWatchMode();
verifyProgram(host, watch);
});
if (!multiFolder) return;
it("with tsserver", () => {
const { host, service } = createSolutionAndService();
verifyProject(host, service);
});
});
describe("non local edit updates the program and watch correctly", () => {
verifyScenario({
edit: (host, solutionBuilder) => {
// edit
host.writeFile(bTs.path, `${bTs.content}\nexport function gfoo() {\n}`);
solutionBuilder.invalidateProject((bTsconfig.path.toLowerCase() as ResolvedConfigFilePath));
solutionBuilder.buildNextInvalidatedProject();
},
expectedEditErrors: emptyArray,
expectedProgramFiles,
expectedProjectFiles,
expectedWatchedFiles,
expectedProjectWatchedFiles,
expectedWatchedDirectoriesRecursive,
dependencies: defaultDependencies
});
});
describe("edit on config file", () => {
const nrefReplacer = (f: string) => f.replace("refs", "nrefs");
const nrefs: File = {
path: getFilePathInProject(project, "nrefs/a.d.ts"),
content: refs.content
};
verifyScenario({
edit: host => {
const cTsConfigJson = JSON.parse(cTsconfig.content);
host.ensureFileOrFolder(nrefs);
cTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
host.writeFile(cTsconfig.path, JSON.stringify(cTsConfigJson));
},
expectedEditErrors: emptyArray,
expectedProgramFiles: expectedProgramFiles.map(nrefReplacer),
expectedProjectFiles: expectedProjectFiles.map(nrefReplacer),
expectedWatchedFiles: expectedWatchedFiles.map(nrefReplacer),
expectedProjectWatchedFiles: expectedProjectWatchedFiles.map(nrefReplacer),
expectedWatchedDirectoriesRecursive: expectedWatchedDirectoriesRecursive.map(nrefReplacer),
dependencies: [
[aDts, [aDts]],
[bDts, [bDts, aDts]],
[nrefs.path, [nrefs.path]],
[cTs.path, [cTs.path, nrefs.path, bDts, aDts]]
],
// revert the update
revert: host => host.writeFile(cTsconfig.path, cTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
orphanInfosAfterEdit: [refs.path.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
});
});
describe("edit in referenced config file", () => {
const nrefs: File = {
path: getFilePathInProject(project, "nrefs/a.d.ts"),
content: "export declare class A {}"
};
const expectedProgramFiles = [cTs.path, bDts, nrefs.path, refs.path, libFile.path];
const expectedProjectFiles = [cTs.path, bTs.path, nrefs.path, refs.path, libFile.path];
const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
verifyScenario({
edit: host => {
const bTsConfigJson = JSON.parse(bTsconfig.content);
host.ensureFileOrFolder(nrefs);
bTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
host.writeFile(bTsconfig.path, JSON.stringify(bTsConfigJson));
},
expectedEditErrors: emptyArray,
expectedProgramFiles,
expectedProjectFiles,
expectedWatchedFiles: expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
expectedProjectWatchedFiles: expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
expectedWatchedDirectoriesRecursive: (multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive).concat(getFilePathInProject(project, "nrefs").toLowerCase()),
dependencies: [
[nrefs.path, [nrefs.path]],
[bDts, [bDts, nrefs.path]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bDts, nrefs.path]],
],
// revert the update
revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
orphanInfosAfterEdit: [aTs.path.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
});
});
describe("deleting referenced config file", () => {
const expectedProgramFiles = [cTs.path, bTs.path, refs.path, libFile.path];
const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path).map(s => s.toLowerCase());
const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
// Resolutions should change now
// Should map to b.ts instead with options from our own config
verifyScenario({
edit: host => host.deleteFile(bTsconfig.path),
schedulesFailedWatchUpdate: multiFolder,
expectedEditErrors: [
`${multiFolder ? "c/tsconfig.json" : "tsconfig.c.json"}(9,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "b" : "tsconfig.b.json"}' not found.\n`
],
expectedProgramFiles,
expectedProjectFiles: expectedProgramFiles,
expectedWatchedFiles,
expectedProjectWatchedFiles: expectedWatchedFiles,
expectedWatchedDirectoriesRecursive: multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive,
dependencies: [
[bTs.path, [bTs.path, refs.path]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bTs.path]],
],
// revert the update
revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
orphanInfosAfterEdit: [aTs.path.toLowerCase(), aTsconfig.path.toLowerCase()],
});
});
describe("deleting transitively referenced config file", () => {
verifyScenario({
edit: host => host.deleteFile(aTsconfig.path),
schedulesFailedWatchUpdate: multiFolder,
expectedEditErrors: [
`${multiFolder ? "b/tsconfig.json" : "tsconfig.b.json"}(10,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "a" : "tsconfig.a.json"}' not found.\n`
],
expectedProgramFiles: expectedProgramFiles.map(s => s.replace(aDts, aTs.path)),
expectedProjectFiles,
expectedWatchedFiles: expectedWatchedFiles.map(s => s.replace(aDts.toLowerCase(), aTs.path.toLocaleLowerCase())),
expectedProjectWatchedFiles,
expectedWatchedDirectoriesRecursive,
dependencies: [
[aTs.path, [aTs.path]],
[bDts, [bDts, aTs.path]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bDts, aTs.path]],
],
// revert the update
revert: host => host.writeFile(aTsconfig.path, aTsconfig.content),
});
});
}
describe("when config files are side by side", () => {
verifyTransitiveReferences(/*multiFolder*/ false);
it("when referenced project uses different module resolution", () => {
const bTs: File = {
path: bTsFile.path,
content: `import {A} from "a";export const b = new A();`
};
const bTsconfig: File = {
path: bTsconfigFile.path,
content: JSON.stringify({
compilerOptions: { composite: true, moduleResolution: "classic" },
files: ["b.ts"],
references: [{ path: "tsconfig.a.json" }]
})
};
const allFiles = [libFile, aTsFile, bTs, cTsFile, aTsconfigFile, bTsconfig, cTsconfigFile, refs];
const aDts = dtsFile("a"), bDts = dtsFile("b");
const expectedFiles = [jsFile("a"), aDts, jsFile("b"), bDts, jsFile("c")];
const expectedProgramFiles = [cTsFile.path, libFile.path, aDts, refs.path, bDts];
const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfigFile.path, bTsconfigFile.path, aTsconfigFile.path).map(s => s.toLowerCase());
const expectedWatchedDirectoriesRecursive = [
getFilePathInProject(project, "refs"), // Failed lookup since refs/a.ts does not exist
...projectSystem.getTypeRootsFromLocation(getProjectPath(project))
].map(s => s.toLowerCase());
const defaultDependencies: readonly [string, readonly string[]][] = [
[aDts, [aDts]],
[bDts, [bDts, aDts]],
[refs.path, [refs.path]],
[cTsFile.path, [cTsFile.path, refs.path, bDts, aDts]]
];
function getOutputFileStamps(host: TsBuildWatchSystem) {
return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host));
}
const { host, watch } = createSolutionAndWatchModeOfProject(allFiles, getProjectPath(project), "tsconfig.c.json", "tsconfig.c.json", getOutputFileStamps);
verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies);
});
});
describe("when config files are in side by side folders", () => {
verifyTransitiveReferences(/*multiFolder*/ true);
});
});
});
});
verifyTscWatch({
scenario,
subScenario: "incremental updates in verbose mode",

View File

@@ -29,6 +29,7 @@ namespace ts {
baselineSourceMap?: boolean;
baselineReadFileCalls?: boolean;
baselinePrograms?: boolean;
baselineDependencies?: boolean;
}
export type CommandLineProgram = [Program, EmitAndSemanticDiagnosticsBuilderProgram?];
@@ -73,7 +74,7 @@ namespace ts {
const {
scenario, subScenario, buildKind,
commandLineArgs, modifyFs,
baselineSourceMap, baselineReadFileCalls, baselinePrograms
baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies
} = input;
if (modifyFs) modifyFs(inputFs);
inputFs.makeReadonly();
@@ -110,7 +111,7 @@ namespace ts {
sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`);
if (baselinePrograms) {
const baseline: string[] = [];
tscWatch.baselinePrograms(baseline, getPrograms);
tscWatch.baselinePrograms(baseline, getPrograms, baselineDependencies);
sys.write(baseline.join("\n"));
}
if (baselineReadFileCalls) {

View File

@@ -299,6 +299,7 @@ namespace ts.tscWatch {
}
export interface TscWatchCheckOptions {
baselineSourceMap?: boolean;
baselineDependencies?: boolean;
}
export interface TscWatchCompileBase extends TscWatchCheckOptions {
scenario: string;
@@ -323,7 +324,7 @@ namespace ts.tscWatch {
const {
scenario, subScenario,
commandLineArgs, changes,
baselineSourceMap
baselineSourceMap, baselineDependencies
} = input;
if (!isWatch(commandLineArgs)) sys.exit = exitCode => sys.exitCode = exitCode;
@@ -342,6 +343,7 @@ namespace ts.tscWatch {
oldSnap,
getPrograms,
baselineSourceMap,
baselineDependencies,
changes,
watchOrSolution
});
@@ -381,7 +383,7 @@ namespace ts.tscWatch {
export function runWatchBaseline({
scenario, subScenario, commandLineArgs,
getPrograms, sys, baseline, oldSnap,
baselineSourceMap,
baselineSourceMap, baselineDependencies,
changes, watchOrSolution
}: RunWatchBaseline) {
baseline.push(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}`);
@@ -390,7 +392,8 @@ namespace ts.tscWatch {
getPrograms,
sys,
oldSnap,
baselineSourceMap
baselineSourceMap,
baselineDependencies,
});
for (const { caption, change, timeouts } of changes) {
@@ -401,7 +404,8 @@ namespace ts.tscWatch {
getPrograms,
sys,
oldSnap,
baselineSourceMap
baselineSourceMap,
baselineDependencies,
});
}
Harness.Baseline.runBaseline(`${isBuild(commandLineArgs) ?
@@ -420,10 +424,10 @@ namespace ts.tscWatch {
export interface WatchBaseline extends Baseline, TscWatchCheckOptions {
getPrograms: () => readonly CommandLineProgram[];
}
export function watchBaseline({ baseline, getPrograms, sys, oldSnap, baselineSourceMap }: WatchBaseline) {
export function watchBaseline({ baseline, getPrograms, sys, oldSnap, baselineSourceMap, baselineDependencies }: WatchBaseline) {
if (baselineSourceMap) generateSourceMapBaselineFiles(sys);
sys.serializeOutput(baseline);
const programs = baselinePrograms(baseline, getPrograms);
const programs = baselinePrograms(baseline, getPrograms, baselineDependencies);
sys.serializeWatches(baseline);
baseline.push(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}`, "");
sys.diff(baseline, oldSnap);
@@ -434,15 +438,15 @@ namespace ts.tscWatch {
return programs;
}
export function baselinePrograms(baseline: string[], getPrograms: () => readonly CommandLineProgram[]) {
export function baselinePrograms(baseline: string[], getPrograms: () => readonly CommandLineProgram[], baselineDependencies: boolean | undefined) {
const programs = getPrograms();
for (const program of programs) {
baselineProgram(baseline, program);
baselineProgram(baseline, program, baselineDependencies);
}
return programs;
}
function baselineProgram(baseline: string[], [program, builderProgram]: CommandLineProgram) {
function baselineProgram(baseline: string[], [program, builderProgram]: CommandLineProgram, baselineDependencies: boolean | undefined) {
const options = program.getCompilerOptions();
baseline.push(`Program root files: ${JSON.stringify(program.getRootFileNames())}`);
baseline.push(`Program options: ${JSON.stringify(options)}`);
@@ -466,6 +470,15 @@ namespace ts.tscWatch {
baseline.push("No cached semantic diagnostics 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}`);
}
}
baseline.push("");
}
export interface VerifyTscWatch extends TscWatchCompile {
@@ -492,4 +505,31 @@ namespace ts.tscWatch {
const content = Debug.checkDefined(sys.readFile(file));
sys.writeFile(file, content.replace(searchValue, replaceValue));
}
export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], defaultOptions?: BuildOptions) {
const host = createSolutionBuilderHost(system);
return ts.createSolutionBuilder(host, rootNames, defaultOptions || {});
}
export function ensureErrorFreeBuild(host: WatchedSystem, rootNames: readonly string[]) {
// ts build should succeed
const solutionBuilder = createSolutionBuilder(host, rootNames, {});
solutionBuilder.build();
assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
}
export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) {
const sys = createWatchedSystem(files, params);
const originalReadFile = sys.readFile;
const originalWrite = sys.write;
const originalWriteFile = sys.writeFile;
const solutionBuilder = createSolutionBuilder(TestFSWithWatch.changeToHostTrackingWrittenFiles(
fakes.patchHostForBuildInfoReadWrite(sys)
), solutionRoots, {});
solutionBuilder.build();
sys.readFile = originalReadFile;
sys.write = originalWrite;
sys.writeFile = originalWriteFile;
return sys;
}
}

View File

@@ -0,0 +1,299 @@
namespace ts.tscWatch {
describe("unittests:: tsc-watch:: projects with references: invoking when references are already built", () => {
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "on sample project",
sys: () => createSystemWithSolutionBuild(
["tests"],
[
libFile,
TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json"),
TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts"),
TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts"),
TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts"),
TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json"),
TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts"),
TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/tsconfig.json"),
TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/index.ts"),
],
{ currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/sample1` }
),
commandLineArgs: ["-w", "-p", "tests"],
changes: [
{
caption: "local edit in logic ts, and build logic",
change: sys => {
sys.appendFile(TestFSWithWatch.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
// There will be timeout queued even though file contents are same
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "non local edit in logic ts, and build logic",
change: sys => {
sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `export function gfoo() { }`);
const solutionBuilder = createSolutionBuilder(sys, ["logic"]);
solutionBuilder.build();
},
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "change in project reference config file builds correctly",
change: sys => {
sys.writeFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/tsconfig.json"), JSON.stringify({
compilerOptions: { composite: true, declaration: true, declarationDir: "decls" },
references: [{ path: "../core" }]
}));
const solutionBuilder = createSolutionBuilder(sys, ["logic"]);
solutionBuilder.build();
},
timeouts: checkSingleTimeoutQueueLengthAndRun
},
],
baselineDependencies: true
});
function changeCompilerOpitonsPaths(sys: WatchedSystem, config: string, newPaths: object) {
const configJson = JSON.parse(sys.readFile(config)!);
configJson.compilerOptions.paths = newPaths;
sys.writeFile(config, JSON.stringify(configJson));
}
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "on transitive references",
sys: () => createSystemWithSolutionBuild(
["tsconfig.c.json"],
[
libFile,
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"),
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json"),
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"),
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"),
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "b.ts"),
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"),
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
],
{ currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` }
),
commandLineArgs: ["-w", "-p", "tsconfig.c.json"],
changes: [
{
caption: "non local edit b ts, and build b",
change: sys => {
sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), `export function gfoo() { }`);
const solutionBuilder = createSolutionBuilder(sys, ["tsconfig.b.json"]);
solutionBuilder.build();
},
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "edit on config file",
change: sys => {
sys.ensureFileOrFolder({
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"),
content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))!
});
changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./nrefs/*"] });
},
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "Revert config file edit",
change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./refs/*"] }),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "edit in referenced config file",
change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./nrefs/*"] }),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "Revert referenced config file edit",
change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./refs/*"] }),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "deleting referenced config file",
change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json")),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "Revert deleting referenced config file",
change: sys => sys.ensureFileOrFolder(TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json")),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "deleting transitively referenced config file",
change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.a.json")),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "Revert deleting transitively referenced config file",
change: sys => sys.ensureFileOrFolder(TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json")),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
],
baselineDependencies: true,
});
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "when referenced project uses different module resolution",
sys: () => createSystemWithSolutionBuild(
["tsconfig.c.json"],
[
libFile,
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"),
{
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"),
content: JSON.stringify({
compilerOptions: { composite: true, moduleResolution: "classic" },
files: ["b.ts"],
references: [{ path: "tsconfig.a.json" }]
})
},
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"),
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"),
{
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"),
content: `import {A} from "a";export const b = new A();`
},
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"),
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
],
{ currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` }
),
commandLineArgs: ["-w", "-p", "tsconfig.c.json"],
changes: emptyArray,
baselineDependencies: true,
});
verifyTscWatch({
scenario: "projectsWithReferences",
subScenario: "on transitive references in different folders",
sys: () => createSystemWithSolutionBuild(
["c"],
[
libFile,
{
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
content: JSON.stringify({
compilerOptions: { composite: true },
files: ["index.ts"]
}),
},
{
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
content: JSON.stringify({
compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } },
files: ["index.ts"],
references: [{ path: `../a` }]
}),
},
{
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"),
content: JSON.stringify({
compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } },
files: ["index.ts"],
references: [{ path: `../b` }]
}),
},
{
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"),
content: `export class A {}`,
},
{
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"),
content: `import {A} from '@ref/a';
export const b = new A();`,
},
{
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"),
content: `import {b} from '../b';
import {X} from "@ref/a";
b;
X;`,
},
TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"),
],
{ currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` }
),
commandLineArgs: ["-w", "-p", "c"],
changes: [
{
caption: "non local edit b ts, and build b",
change: sys => {
sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`);
const solutionBuilder = createSolutionBuilder(sys, ["b"]);
solutionBuilder.build();
},
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "edit on config file",
change: sys => {
sys.ensureFileOrFolder({
path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"),
content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))!
});
changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] });
},
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "Revert config file edit",
change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "edit in referenced config file",
change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "Revert referenced config file edit",
change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }),
timeouts: checkSingleTimeoutQueueLengthAndRun
},
{
caption: "deleting referenced config file",
change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2)
},
{
caption: "Revert deleting referenced config file",
change: sys => sys.writeFile(
TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"),
JSON.stringify({
compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } },
files: ["index.ts"],
references: [{ path: `../a` }]
})
),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2)
},
{
caption: "deleting transitively referenced config file",
change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2)
},
{
caption: "Revert deleting transitively referenced config file",
change: sys => sys.writeFile(
TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"),
JSON.stringify({
compilerOptions: { composite: true },
files: ["index.ts"]
}),
),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2)
},
],
baselineDependencies: true,
});
});
}

View File

@@ -440,6 +440,13 @@ namespace ts.projectSystem {
return iterResult.value;
}
export function checkOrphanScriptInfos(service: server.ProjectService, expectedFiles: readonly string[]) {
checkArray("Orphan ScriptInfos:", arrayFrom(mapDefinedIterator(
service.filenameToScriptInfo.values(),
v => v.containingProjects.length === 0 ? v.fileName : undefined
)), expectedFiles);
}
export function checkProjectActualFiles(project: server.Project, expectedFiles: readonly string[]) {
checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}:: actual files`, project.getFileNames(), expectedFiles);
}

View File

@@ -0,0 +1,287 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: projects with references: invoking when references are already built", () => {
it("on sample project", () => {
const coreConfig = TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json");
const coreIndex = TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts");
const coreAnotherModule = TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts");
const coreSomeDecl = TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts");
const logicConfig = TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json");
const logicIndex = TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts");
const testsConfig = TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/tsconfig.json");
const testsIndex = TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/index.ts");
const host = createServerHost([libFile, coreConfig, coreIndex, coreAnotherModule, coreSomeDecl, logicConfig, logicIndex, testsConfig, testsIndex]);
const service = createProjectService(host);
service.openClientFile(testsIndex.path);
checkWatchedFilesDetailed(host, [coreConfig, coreIndex, coreAnotherModule, logicConfig, logicIndex, testsConfig, libFile].map(f => f.path.toLowerCase()), 1);
checkWatchedDirectoriesDetailed(host, emptyArray, 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, getTypeRootsFromLocation(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "tests")), 1, /*recursive*/ true);
// local edit in ts file
host.appendFile(logicIndex.path, `function foo() {}`);
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(testsConfig.path)!, [libFile.path, coreIndex.path, coreAnotherModule.path, logicIndex.path, testsIndex.path, testsConfig.path]);
// non local edit in ts file
host.appendFile(logicIndex.path, `export function gfoo() {}`);
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(testsConfig.path)!, [libFile.path, coreIndex.path, coreAnotherModule.path, logicIndex.path, testsIndex.path, testsConfig.path]);
// change in project reference config file
host.writeFile(logicConfig.path, JSON.stringify({
compilerOptions: { composite: true, declaration: true, declarationDir: "decls" },
references: [{ path: "../core" }]
}));
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(testsConfig.path)!, [libFile.path, coreIndex.path, coreAnotherModule.path, logicIndex.path, testsIndex.path, testsConfig.path]);
});
describe("on transitive references in different folders", () => {
function createService() {
const aConfig: File = {
path: `${tscWatch.projectRoot}/a/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true },
files: ["index.ts"]
}),
};
const bConfig: File = {
path: `${tscWatch.projectRoot}/b/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } },
files: ["index.ts"],
references: [{ path: `../a` }]
}),
};
const cConfig: File = {
path: `${tscWatch.projectRoot}/c/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } },
files: ["index.ts"],
references: [{ path: `../b` }]
}),
};
const aTs: File = {
path: `${tscWatch.projectRoot}/a/index.ts`,
content: `export class A {}`,
};
const bTs: File = {
path: `${tscWatch.projectRoot}/b/index.ts`,
content: `import {A} from '@ref/a';
export const b = new A();`,
};
const cTs: File = {
path: `${tscWatch.projectRoot}/c/index.ts`,
content: `import {b} from '../b';
import {X} from "@ref/a";
b;
X;`,
};
const refsTs: File = {
path: `${tscWatch.projectRoot}/refs/a.d.ts`,
content: `export class X {}
export class A {}`
};
const host = createServerHost([libFile, aConfig, bConfig, cConfig, aTs, bTs, cTs, refsTs]);
const service = createProjectService(host);
service.openClientFile(cTs.path);
return { host, service, aConfig, bConfig, cConfig, aTs, bTs, cTs, refsTs };
}
it("non local edit", () => {
const { host, service, aConfig, bConfig, cConfig, aTs, bTs, cTs, refsTs } = createService();
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, aTs.path, refsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/a`, // Failed to package json
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/refs`, // Failed lookup since refs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
checkOrphanScriptInfos(service, emptyArray);
// non local edit
host.appendFile(bTs.path, `export function gFoo() { }`);
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, aTs.path, refsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/a`, // Failed to package json
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/refs`, // Failed lookup since refs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
checkOrphanScriptInfos(service, emptyArray);
});
it("edit on config file", () => {
const { host, service, aConfig, bConfig, cConfig, aTs, bTs, cTs, refsTs } = createService();
const nRefsTs: File = {
path: `${tscWatch.projectRoot}/nrefs/a.d.ts`,
content: refsTs.content
};
const cTsConfigJson = JSON.parse(cConfig.content);
host.ensureFileOrFolder(nRefsTs);
cTsConfigJson.compilerOptions.paths = { "@ref/*": ["../nrefs/*"] };
host.writeFile(cConfig.path, JSON.stringify(cTsConfigJson));
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, aTs.path, nRefsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path, nRefsTs.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/a`, // Failed to package json
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/nrefs`, // Failed lookup since nrefs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
// Script infos arent deleted till next file open
checkOrphanScriptInfos(service, [refsTs.path]);
// revert the edit on config file
host.writeFile(cConfig.path, cConfig.content);
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, aTs.path, refsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path, nRefsTs.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/a`, // Failed to package json
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/refs`, // Failed lookup since refs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
// Script infos arent deleted till next file open
checkOrphanScriptInfos(service, [nRefsTs.path]);
});
it("edit in referenced config file", () => {
const { host, service, aConfig, bConfig, cConfig, aTs, bTs, cTs, refsTs } = createService();
const nRefsTs: File = {
path: `${tscWatch.projectRoot}/nrefs/a.d.ts`,
content: refsTs.content
};
const bTsConfigJson = JSON.parse(bConfig.content);
host.ensureFileOrFolder(nRefsTs);
bTsConfigJson.compilerOptions.paths = { "@ref/*": ["../nrefs/*"] };
host.writeFile(bConfig.path, JSON.stringify(bTsConfigJson));
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, refsTs.path, nRefsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path, nRefsTs.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/refs`, // Failed lookup since refs/a.ts does not exist
`${tscWatch.projectRoot}/nrefs`, // Failed lookup since nrefs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
// Script infos arent deleted till next file open
checkOrphanScriptInfos(service, [aTs.path]);
// revert the edit on config file
host.writeFile(bConfig.path, bConfig.content);
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, aTs.path, refsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path, nRefsTs.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/a`, // Failed to package json
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/refs`, // Failed lookup since refs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
// Script infos arent deleted till next file open
checkOrphanScriptInfos(service, [nRefsTs.path]);
});
it("deleting referenced config file", () => {
const { host, service, aConfig, bConfig, cConfig, aTs, bTs, cTs, refsTs } = createService();
host.deleteFile(bConfig.path);
host.checkTimeoutQueueLengthAndRun(3); // Schedules failed lookup invalidation
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, refsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/refs`, // Failed lookup since refs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
// Script infos arent deleted till next file open
checkOrphanScriptInfos(service, [aTs.path, aConfig.path]);
// revert
host.writeFile(bConfig.path, bConfig.content);
host.checkTimeoutQueueLengthAndRun(3); // Schedules failed lookup invalidation
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, aTs.path, refsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/a`, // Failed to package json
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/refs`, // Failed lookup since refs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
checkOrphanScriptInfos(service, emptyArray);
});
it("deleting transitively referenced config file", () => {
const { host, service, aConfig, bConfig, cConfig, aTs, bTs, cTs, refsTs } = createService();
host.deleteFile(aConfig.path);
host.checkTimeoutQueueLengthAndRun(3); // Schedules failed lookup invalidation
checkNumberOfProjects(service, { configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, aTs.path, refsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/a`, // Failed to package json
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/refs`, // Failed lookup since refs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
checkOrphanScriptInfos(service, emptyArray);
// revert
host.writeFile(aConfig.path, aConfig.content);
host.checkTimeoutQueueLengthAndRun(3); // Schedules failed lookup invalidation
checkProjectActualFiles(service.configuredProjects.get(cConfig.path)!, [libFile.path, cTs.path, cConfig.path, bTs.path, aTs.path, refsTs.path]);
checkWatchedFilesDetailed(host, [libFile.path, aTs.path, bTs.path, refsTs.path, aConfig.path, bConfig.path, cConfig.path], 1);
checkWatchedDirectoriesDetailed(host, [
tscWatch.projectRoot // watches for directories created for resolution of b
], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [
`${tscWatch.projectRoot}/a`, // Failed to package json
`${tscWatch.projectRoot}/b`, // Failed to package json
`${tscWatch.projectRoot}/refs`, // Failed lookup since refs/a.ts does not exist
...getTypeRootsFromLocation(`${tscWatch.projectRoot}/c`)
], 1, /*recursive*/ true);
checkOrphanScriptInfos(service, emptyArray);
});
});
});
}