Split resolutionCache and watchApi tests into its own unittest
This commit is contained in:
parent
47200acfcd
commit
7b9b0f8da7
|
@ -37,8 +37,8 @@
|
||||||
"runner.ts",
|
"runner.ts",
|
||||||
|
|
||||||
"unittests/extractTestHelpers.ts",
|
"unittests/extractTestHelpers.ts",
|
||||||
"unittests/tsserverProjectSystem.ts",
|
"unittests/tscWatchHelpers.ts",
|
||||||
"unittests/typingsInstaller.ts",
|
"unittests/tsserverHelpers.ts",
|
||||||
|
|
||||||
"unittests/asserts.ts",
|
"unittests/asserts.ts",
|
||||||
"unittests/base64.ts",
|
"unittests/base64.ts",
|
||||||
|
@ -72,6 +72,7 @@
|
||||||
"unittests/projectErrors.ts",
|
"unittests/projectErrors.ts",
|
||||||
"unittests/projectReferences.ts",
|
"unittests/projectReferences.ts",
|
||||||
"unittests/publicApi.ts",
|
"unittests/publicApi.ts",
|
||||||
|
"unittests/resolutionCache.ts",
|
||||||
"unittests/reuseProgramStructure.ts",
|
"unittests/reuseProgramStructure.ts",
|
||||||
"unittests/session.ts",
|
"unittests/session.ts",
|
||||||
"unittests/semver.ts",
|
"unittests/semver.ts",
|
||||||
|
@ -86,8 +87,11 @@
|
||||||
"unittests/tsbuildWatchMode.ts",
|
"unittests/tsbuildWatchMode.ts",
|
||||||
"unittests/tsconfigParsing.ts",
|
"unittests/tsconfigParsing.ts",
|
||||||
"unittests/tscWatchMode.ts",
|
"unittests/tscWatchMode.ts",
|
||||||
|
"unittests/tsserverProjectSystem.ts",
|
||||||
|
"unittests/typingsInstaller.ts",
|
||||||
"unittests/versionCache.ts",
|
"unittests/versionCache.ts",
|
||||||
"unittests/watchEnvironment.ts",
|
"unittests/watchEnvironment.ts",
|
||||||
|
"unittests/watchApi.ts",
|
||||||
"unittests/evaluation/asyncArrow.ts",
|
"unittests/evaluation/asyncArrow.ts",
|
||||||
"unittests/evaluation/asyncGenerator.ts",
|
"unittests/evaluation/asyncGenerator.ts",
|
||||||
"unittests/evaluation/forAwaitOf.ts",
|
"unittests/evaluation/forAwaitOf.ts",
|
||||||
|
|
981
src/testRunner/unittests/resolutionCache.ts
Normal file
981
src/testRunner/unittests/resolutionCache.ts
Normal file
|
@ -0,0 +1,981 @@
|
||||||
|
namespace ts.tscWatch {
|
||||||
|
describe("resolutionCache:: tsc-watch module resolution caching", () => {
|
||||||
|
it("works", () => {
|
||||||
|
const root = {
|
||||||
|
path: "/a/d/f0.ts",
|
||||||
|
content: `import {x} from "f1"`
|
||||||
|
};
|
||||||
|
const imported = {
|
||||||
|
path: "/a/f1.ts",
|
||||||
|
content: `foo()`
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = [root, imported, libFile];
|
||||||
|
const host = createWatchedSystem(files);
|
||||||
|
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
|
||||||
|
|
||||||
|
const f1IsNotModule = getDiagnosticOfFileFromProgram(watch(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path);
|
||||||
|
const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo");
|
||||||
|
|
||||||
|
// ensure that imported file was found
|
||||||
|
checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]);
|
||||||
|
|
||||||
|
const originalFileExists = host.fileExists;
|
||||||
|
{
|
||||||
|
const newContent = `import {x} from "f1"
|
||||||
|
var x: string = 1;`;
|
||||||
|
root.content = newContent;
|
||||||
|
host.reloadFS(files);
|
||||||
|
|
||||||
|
// patch fileExists to make sure that disk is not touched
|
||||||
|
host.fileExists = notImplemented;
|
||||||
|
|
||||||
|
// trigger synchronization to make sure that import will be fetched from the cache
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
|
||||||
|
// ensure file has correct number of errors after edit
|
||||||
|
checkOutputErrorsIncremental(host, [
|
||||||
|
f1IsNotModule,
|
||||||
|
getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"),
|
||||||
|
cannotFindFoo
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let fileExistsIsCalled = false;
|
||||||
|
host.fileExists = (fileName): boolean => {
|
||||||
|
if (fileName === "lib.d.ts") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fileExistsIsCalled = true;
|
||||||
|
assert.isTrue(fileName.indexOf("/f2.") !== -1);
|
||||||
|
return originalFileExists.call(host, fileName);
|
||||||
|
};
|
||||||
|
|
||||||
|
root.content = `import {x} from "f2"`;
|
||||||
|
host.reloadFS(files);
|
||||||
|
|
||||||
|
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
|
||||||
|
// ensure file has correct number of errors after edit
|
||||||
|
checkOutputErrorsIncremental(host, [
|
||||||
|
getDiagnosticModuleNotFoundOfFile(watch(), root, "f2")
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.isTrue(fileExistsIsCalled);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let fileExistsCalled = false;
|
||||||
|
host.fileExists = (fileName): boolean => {
|
||||||
|
if (fileName === "lib.d.ts") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fileExistsCalled = true;
|
||||||
|
assert.isTrue(fileName.indexOf("/f1.") !== -1);
|
||||||
|
return originalFileExists.call(host, fileName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const newContent = `import {x} from "f1"`;
|
||||||
|
root.content = newContent;
|
||||||
|
|
||||||
|
host.reloadFS(files);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
|
||||||
|
checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]);
|
||||||
|
assert.isTrue(fileExistsCalled);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads missing files from disk", () => {
|
||||||
|
const root = {
|
||||||
|
path: `/a/foo.ts`,
|
||||||
|
content: `import {x} from "bar"`
|
||||||
|
};
|
||||||
|
|
||||||
|
const imported = {
|
||||||
|
path: `/a/bar.d.ts`,
|
||||||
|
content: `export const y = 1;`
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = [root, libFile];
|
||||||
|
const host = createWatchedSystem(files);
|
||||||
|
const originalFileExists = host.fileExists;
|
||||||
|
|
||||||
|
let fileExistsCalledForBar = false;
|
||||||
|
host.fileExists = fileName => {
|
||||||
|
if (fileName === "lib.d.ts") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fileExistsCalledForBar) {
|
||||||
|
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalFileExists.call(host, fileName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
|
||||||
|
|
||||||
|
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
||||||
|
checkOutputErrorsInitial(host, [
|
||||||
|
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
|
||||||
|
]);
|
||||||
|
|
||||||
|
fileExistsCalledForBar = false;
|
||||||
|
root.content = `import {y} from "bar"`;
|
||||||
|
host.reloadFS(files.concat(imported));
|
||||||
|
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
checkOutputErrorsIncremental(host, emptyArray);
|
||||||
|
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => {
|
||||||
|
const root = {
|
||||||
|
path: `/a/foo.ts`,
|
||||||
|
content: `import {x} from "bar"`
|
||||||
|
};
|
||||||
|
|
||||||
|
const imported = {
|
||||||
|
path: `/a/bar.d.ts`,
|
||||||
|
content: `export const y = 1;export const x = 10;`
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = [root, libFile];
|
||||||
|
const filesWithImported = files.concat(imported);
|
||||||
|
const host = createWatchedSystem(filesWithImported);
|
||||||
|
const originalFileExists = host.fileExists;
|
||||||
|
let fileExistsCalledForBar = false;
|
||||||
|
host.fileExists = fileName => {
|
||||||
|
if (fileName === "lib.d.ts") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fileExistsCalledForBar) {
|
||||||
|
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
|
||||||
|
}
|
||||||
|
return originalFileExists.call(host, fileName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
|
||||||
|
|
||||||
|
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
||||||
|
checkOutputErrorsInitial(host, emptyArray);
|
||||||
|
|
||||||
|
fileExistsCalledForBar = false;
|
||||||
|
host.reloadFS(files);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
|
||||||
|
checkOutputErrorsIncremental(host, [
|
||||||
|
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
|
||||||
|
]);
|
||||||
|
|
||||||
|
fileExistsCalledForBar = false;
|
||||||
|
host.reloadFS(filesWithImported);
|
||||||
|
host.checkTimeoutQueueLengthAndRun(1);
|
||||||
|
checkOutputErrorsIncremental(host, emptyArray);
|
||||||
|
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works when module resolution changes to ambient module", () => {
|
||||||
|
const root = {
|
||||||
|
path: "/a/b/foo.ts",
|
||||||
|
content: `import * as fs from "fs";`
|
||||||
|
};
|
||||||
|
|
||||||
|
const packageJson = {
|
||||||
|
path: "/a/b/node_modules/@types/node/package.json",
|
||||||
|
content: `
|
||||||
|
{
|
||||||
|
"main": ""
|
||||||
|
}
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodeType = {
|
||||||
|
path: "/a/b/node_modules/@types/node/index.d.ts",
|
||||||
|
content: `
|
||||||
|
declare module "fs" {
|
||||||
|
export interface Stats {
|
||||||
|
isFile(): boolean;
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = [root, libFile];
|
||||||
|
const filesWithNodeType = files.concat(packageJson, nodeType);
|
||||||
|
const host = createWatchedSystem(files, { currentDirectory: "/a/b" });
|
||||||
|
|
||||||
|
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { });
|
||||||
|
|
||||||
|
checkOutputErrorsInitial(host, [
|
||||||
|
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
|
||||||
|
]);
|
||||||
|
|
||||||
|
host.reloadFS(filesWithNodeType);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
checkOutputErrorsIncremental(host, emptyArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works when included file with ambient module changes", () => {
|
||||||
|
const root = {
|
||||||
|
path: "/a/b/foo.ts",
|
||||||
|
content: `
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as u from "url";
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
const file = {
|
||||||
|
path: "/a/b/bar.d.ts",
|
||||||
|
content: `
|
||||||
|
declare module "url" {
|
||||||
|
export interface Url {
|
||||||
|
href?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileContentWithFS = `
|
||||||
|
declare module "fs" {
|
||||||
|
export interface Stats {
|
||||||
|
isFile(): boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const files = [root, file, libFile];
|
||||||
|
const host = createWatchedSystem(files, { currentDirectory: "/a/b" });
|
||||||
|
|
||||||
|
const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {});
|
||||||
|
|
||||||
|
checkOutputErrorsInitial(host, [
|
||||||
|
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
|
||||||
|
]);
|
||||||
|
|
||||||
|
file.content += fileContentWithFS;
|
||||||
|
host.reloadFS(files);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
checkOutputErrorsIncremental(host, emptyArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works when reusing program with files from external library", () => {
|
||||||
|
interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; }
|
||||||
|
const configDir = "/a/b/projects/myProject/src/";
|
||||||
|
const file1: File = {
|
||||||
|
path: configDir + "file1.ts",
|
||||||
|
content: 'import module1 = require("module1");\nmodule1("hello");'
|
||||||
|
};
|
||||||
|
const file2: File = {
|
||||||
|
path: configDir + "file2.ts",
|
||||||
|
content: 'import module11 = require("module1");\nmodule11("hello");'
|
||||||
|
};
|
||||||
|
const module1: File = {
|
||||||
|
path: "/a/b/projects/myProject/node_modules/module1/index.js",
|
||||||
|
content: "module.exports = options => { return options.toString(); }"
|
||||||
|
};
|
||||||
|
const configFile: File = {
|
||||||
|
path: configDir + "tsconfig.json",
|
||||||
|
content: JSON.stringify({
|
||||||
|
compilerOptions: {
|
||||||
|
allowJs: true,
|
||||||
|
rootDir: ".",
|
||||||
|
outDir: "../dist",
|
||||||
|
moduleResolution: "node",
|
||||||
|
maxNodeModuleJsDepth: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const outDirFolder = "/a/b/projects/myProject/dist/";
|
||||||
|
const programFiles = [file1, file2, module1, libFile];
|
||||||
|
const host = createWatchedSystem(programFiles.concat(configFile), { currentDirectory: "/a/b/projects/myProject/" });
|
||||||
|
const watch = createWatchOfConfigFile(configFile.path, host);
|
||||||
|
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
|
||||||
|
checkOutputErrorsInitial(host, emptyArray);
|
||||||
|
const expectedFiles: ExpectedFile[] = [
|
||||||
|
createExpectedEmittedFile(file1),
|
||||||
|
createExpectedEmittedFile(file2),
|
||||||
|
createExpectedToNotEmitFile("index.js"),
|
||||||
|
createExpectedToNotEmitFile("src/index.js"),
|
||||||
|
createExpectedToNotEmitFile("src/file1.js"),
|
||||||
|
createExpectedToNotEmitFile("src/file2.js"),
|
||||||
|
createExpectedToNotEmitFile("lib.js"),
|
||||||
|
createExpectedToNotEmitFile("lib.d.ts")
|
||||||
|
];
|
||||||
|
verifyExpectedFiles(expectedFiles);
|
||||||
|
|
||||||
|
file1.content += "\n;";
|
||||||
|
expectedFiles[0].content += ";\n"; // Only emit file1 with this change
|
||||||
|
expectedFiles[1].isExpectedToEmit = false;
|
||||||
|
host.reloadFS(programFiles.concat(configFile));
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
|
||||||
|
checkOutputErrorsIncremental(host, emptyArray);
|
||||||
|
verifyExpectedFiles(expectedFiles);
|
||||||
|
|
||||||
|
|
||||||
|
function verifyExpectedFiles(expectedFiles: ExpectedFile[]) {
|
||||||
|
forEach(expectedFiles, f => {
|
||||||
|
assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit"));
|
||||||
|
if (f.isExpectedToEmit) {
|
||||||
|
assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createExpectedToNotEmitFile(fileName: string): ExpectedFile {
|
||||||
|
return {
|
||||||
|
path: outDirFolder + fileName,
|
||||||
|
isExpectedToEmit: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createExpectedEmittedFile(file: File): ExpectedFile {
|
||||||
|
return {
|
||||||
|
path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js,
|
||||||
|
isExpectedToEmit: true,
|
||||||
|
content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works when renaming node_modules folder that already contains @types folder", () => {
|
||||||
|
const currentDirectory = "/user/username/projects/myproject";
|
||||||
|
const file: File = {
|
||||||
|
path: `${currentDirectory}/a.ts`,
|
||||||
|
content: `import * as q from "qqq";`
|
||||||
|
};
|
||||||
|
const module: File = {
|
||||||
|
path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`,
|
||||||
|
content: "export {}"
|
||||||
|
};
|
||||||
|
const files = [file, module, libFile];
|
||||||
|
const host = createWatchedSystem(files, { currentDirectory });
|
||||||
|
const watch = createWatchOfFilesAndCompilerOptions([file.path], host);
|
||||||
|
|
||||||
|
checkProgramActualFiles(watch(), [file.path, libFile.path]);
|
||||||
|
checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]);
|
||||||
|
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||||
|
checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true);
|
||||||
|
|
||||||
|
host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]);
|
||||||
|
checkOutputErrorsIncremental(host, emptyArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ignores files/folder changes in node_modules that start with '.'", () => {
|
||||||
|
const projectPath = "/user/username/projects/project";
|
||||||
|
const npmCacheFile: File = {
|
||||||
|
path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`,
|
||||||
|
content: JSON.stringify({ something: 10 })
|
||||||
|
};
|
||||||
|
const file1: File = {
|
||||||
|
path: `${projectPath}/test.ts`,
|
||||||
|
content: `import { x } from "somemodule";`
|
||||||
|
};
|
||||||
|
const file2: File = {
|
||||||
|
path: `${projectPath}/node_modules/somemodule/index.d.ts`,
|
||||||
|
content: `export const x = 10;`
|
||||||
|
};
|
||||||
|
const files = [libFile, file1, file2];
|
||||||
|
const expectedFiles = files.map(f => f.path);
|
||||||
|
it("when watching node_modules in inferred project for failed lookup", () => {
|
||||||
|
const host = createWatchedSystem(files);
|
||||||
|
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1);
|
||||||
|
checkProgramActualFiles(watch(), expectedFiles);
|
||||||
|
host.checkTimeoutQueueLength(0);
|
||||||
|
|
||||||
|
host.ensureFileOrFolder(npmCacheFile);
|
||||||
|
host.checkTimeoutQueueLength(0);
|
||||||
|
});
|
||||||
|
it("when watching node_modules as part of wild card directories in config project", () => {
|
||||||
|
const config: File = {
|
||||||
|
path: `${projectPath}/tsconfig.json`,
|
||||||
|
content: "{}"
|
||||||
|
};
|
||||||
|
const host = createWatchedSystem(files.concat(config));
|
||||||
|
const watch = createWatchOfConfigFile(config.path, host);
|
||||||
|
checkProgramActualFiles(watch(), expectedFiles);
|
||||||
|
host.checkTimeoutQueueLength(0);
|
||||||
|
|
||||||
|
host.ensureFileOrFolder(npmCacheFile);
|
||||||
|
host.checkTimeoutQueueLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resolutionCache:: tsc-watch with modules linked to sibling folder", () => {
|
||||||
|
const projectRoot = "/user/username/projects/project";
|
||||||
|
const mainPackageRoot = `${projectRoot}/main`;
|
||||||
|
const linkedPackageRoot = `${projectRoot}/linked-package`;
|
||||||
|
const mainFile: File = {
|
||||||
|
path: `${mainPackageRoot}/index.ts`,
|
||||||
|
content: "import { Foo } from '@scoped/linked-package'"
|
||||||
|
};
|
||||||
|
const config: File = {
|
||||||
|
path: `${mainPackageRoot}/tsconfig.json`,
|
||||||
|
content: JSON.stringify({
|
||||||
|
compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." },
|
||||||
|
files: ["index.ts"]
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const linkedPackageInMain: SymLink = {
|
||||||
|
path: `${mainPackageRoot}/node_modules/@scoped/linked-package`,
|
||||||
|
symLink: `${linkedPackageRoot}`
|
||||||
|
};
|
||||||
|
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: File = {
|
||||||
|
path: `${linkedPackageRoot}/dist/index.d.ts`,
|
||||||
|
content: "export * from './other';"
|
||||||
|
};
|
||||||
|
const linkedPackageOther: File = {
|
||||||
|
path: `${linkedPackageRoot}/dist/other.d.ts`,
|
||||||
|
content: 'export declare const Foo = "BAR";'
|
||||||
|
};
|
||||||
|
|
||||||
|
it("verify watched directories", () => {
|
||||||
|
const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther];
|
||||||
|
const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot });
|
||||||
|
createWatchOfConfigFile("tsconfig.json", host);
|
||||||
|
checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1);
|
||||||
|
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||||
|
checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ts.projectSystem {
|
||||||
|
function createHostModuleResolutionTrace(host: TestServerHost & ModuleResolutionHost) {
|
||||||
|
const resolutionTrace: string[] = [];
|
||||||
|
host.trace = resolutionTrace.push.bind(resolutionTrace);
|
||||||
|
return resolutionTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("resolutionCache:: tsserverProjectSystem extra resolution pass in lshost", () => {
|
||||||
|
it("can load typings that are proper modules", () => {
|
||||||
|
const file1 = {
|
||||||
|
path: "/a/b/app.js",
|
||||||
|
content: `var x = require("lib")`
|
||||||
|
};
|
||||||
|
const lib = {
|
||||||
|
path: "/a/cache/node_modules/@types/lib/index.d.ts",
|
||||||
|
content: "export let x = 1"
|
||||||
|
};
|
||||||
|
const host: TestServerHost & ModuleResolutionHost = createServerHost([file1, lib]);
|
||||||
|
const resolutionTrace = createHostModuleResolutionTrace(host);
|
||||||
|
const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller("/a/cache", /*throttleLimit*/5, host) });
|
||||||
|
|
||||||
|
projectService.setCompilerOptionsForInferredProjects({ traceResolution: true, allowJs: true });
|
||||||
|
projectService.openClientFile(file1.path);
|
||||||
|
projectService.checkNumberOfProjects({ inferredProjects: 1 });
|
||||||
|
const proj = projectService.inferredProjects[0];
|
||||||
|
|
||||||
|
assert.deepEqual(resolutionTrace, [
|
||||||
|
"======== Resolving module 'lib' from '/a/b/app.js'. ========",
|
||||||
|
"Module resolution kind is not specified, using 'NodeJs'.",
|
||||||
|
"Loading module 'lib' from 'node_modules' folder, target file type 'TypeScript'.",
|
||||||
|
"Directory '/a/b/node_modules' does not exist, skipping all lookups in it.",
|
||||||
|
"Directory '/a/node_modules' does not exist, skipping all lookups in it.",
|
||||||
|
"Directory '/node_modules' does not exist, skipping all lookups in it.",
|
||||||
|
"Loading module 'lib' from 'node_modules' folder, target file type 'JavaScript'.",
|
||||||
|
"Directory '/a/b/node_modules' does not exist, skipping all lookups in it.",
|
||||||
|
"Directory '/a/node_modules' does not exist, skipping all lookups in it.",
|
||||||
|
"Directory '/node_modules' does not exist, skipping all lookups in it.",
|
||||||
|
"======== Module name 'lib' was not resolved. ========",
|
||||||
|
`Auto discovery for typings is enabled in project '${proj.getProjectName()}'. Running extra resolution pass for module 'lib' using cache location '/a/cache'.`,
|
||||||
|
"File '/a/cache/node_modules/lib.d.ts' does not exist.",
|
||||||
|
"File '/a/cache/node_modules/@types/lib/package.json' does not exist.",
|
||||||
|
"File '/a/cache/node_modules/@types/lib.d.ts' does not exist.",
|
||||||
|
"File '/a/cache/node_modules/@types/lib/index.d.ts' exist - use it as a name resolution result.",
|
||||||
|
]);
|
||||||
|
checkProjectActualFiles(proj, [file1.path, lib.path]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resolutionCache:: tsserverProjectSystem module resolution caching", () => {
|
||||||
|
const projectLocation = "/user/username/projects/myproject";
|
||||||
|
const configFile: File = {
|
||||||
|
path: `${projectLocation}/tsconfig.json`,
|
||||||
|
content: JSON.stringify({ compilerOptions: { traceResolution: true } })
|
||||||
|
};
|
||||||
|
|
||||||
|
function getModules(module1Path: string, module2Path: string) {
|
||||||
|
const module1: File = {
|
||||||
|
path: module1Path,
|
||||||
|
content: `export function module1() {}`
|
||||||
|
};
|
||||||
|
const module2: File = {
|
||||||
|
path: module2Path,
|
||||||
|
content: `export function module2() {}`
|
||||||
|
};
|
||||||
|
return { module1, module2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyTrace(resolutionTrace: string[], expected: string[]) {
|
||||||
|
assert.deepEqual(resolutionTrace, expected);
|
||||||
|
resolutionTrace.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpectedFileDoesNotExistResolutionTrace(host: TestServerHost, expectedTrace: string[], foundModule: boolean, module: File, directory: string, file: string, ignoreIfParentMissing?: boolean) {
|
||||||
|
if (!foundModule) {
|
||||||
|
const path = combinePaths(directory, file);
|
||||||
|
if (!ignoreIfParentMissing || host.directoryExists(getDirectoryPath(path))) {
|
||||||
|
if (module.path === path) {
|
||||||
|
foundModule = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
expectedTrace.push(`File '${path}' does not exist.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpectedMissedLocationResolutionTrace(host: TestServerHost, expectedTrace: string[], dirPath: string, module: File, moduleName: string, useNodeModules: boolean, cacheLocation?: string) {
|
||||||
|
let foundModule = false;
|
||||||
|
forEachAncestorDirectory(dirPath, dirPath => {
|
||||||
|
if (dirPath === cacheLocation) {
|
||||||
|
return foundModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directory = useNodeModules ? combinePaths(dirPath, nodeModules) : dirPath;
|
||||||
|
if (useNodeModules && !foundModule && !host.directoryExists(directory)) {
|
||||||
|
expectedTrace.push(`Directory '${directory}' does not exist, skipping all lookups in it.`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/package.json`, /*ignoreIfParentMissing*/ true);
|
||||||
|
foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.ts`);
|
||||||
|
foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.tsx`);
|
||||||
|
foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.d.ts`);
|
||||||
|
foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/index.ts`, /*ignoreIfParentMissing*/ true);
|
||||||
|
if (useNodeModules && !foundModule) {
|
||||||
|
expectedTrace.push(`Directory '${directory}/@types' does not exist, skipping all lookups in it.`);
|
||||||
|
}
|
||||||
|
return foundModule ? true : undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpectedResolutionTraceHeader(expectedTrace: string[], file: File, moduleName: string) {
|
||||||
|
expectedTrace.push(
|
||||||
|
`======== Resolving module '${moduleName}' from '${file.path}'. ========`,
|
||||||
|
`Module resolution kind is not specified, using 'NodeJs'.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpectedResolutionTraceFooter(expectedTrace: string[], module: File, moduleName: string, addRealPathTrace: boolean, ignoreModuleFileFound?: boolean) {
|
||||||
|
if (!ignoreModuleFileFound) {
|
||||||
|
expectedTrace.push(`File '${module.path}' exist - use it as a name resolution result.`);
|
||||||
|
}
|
||||||
|
if (addRealPathTrace) {
|
||||||
|
expectedTrace.push(`Resolving real path for '${module.path}', result '${module.path}'.`);
|
||||||
|
}
|
||||||
|
expectedTrace.push(`======== Module name '${moduleName}' was successfully resolved to '${module.path}'. ========`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpectedRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) {
|
||||||
|
getExpectedResolutionTraceHeader(expectedTrace, file, moduleName);
|
||||||
|
expectedTrace.push(`Loading module as file / folder, candidate module location '${removeFileExtension(module.path)}', target file type 'TypeScript'.`);
|
||||||
|
getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(normalizePath(combinePaths(getDirectoryPath(file.path), moduleName))), module, moduleName.substring(moduleName.lastIndexOf("/") + 1), /*useNodeModules*/ false);
|
||||||
|
getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false);
|
||||||
|
return expectedTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpectedNonRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) {
|
||||||
|
getExpectedResolutionTraceHeader(expectedTrace, file, moduleName);
|
||||||
|
expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`);
|
||||||
|
getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true);
|
||||||
|
getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ true);
|
||||||
|
return expectedTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpectedNonRelativeModuleResolutionFromCacheTrace(host: TestServerHost, file: File, module: File, moduleName: string, cacheLocation: string, expectedTrace: string[] = []) {
|
||||||
|
getExpectedResolutionTraceHeader(expectedTrace, file, moduleName);
|
||||||
|
expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`);
|
||||||
|
getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true, cacheLocation);
|
||||||
|
expectedTrace.push(`Resolution for module '${moduleName}' was found in cache from location '${cacheLocation}'.`);
|
||||||
|
getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false, /*ignoreModuleFileFound*/ true);
|
||||||
|
return expectedTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpectedReusingResolutionFromOldProgram(file: File, moduleName: string) {
|
||||||
|
return `Reusing resolution of module '${moduleName}' to file '${file.path}' from old program.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File, extraExpectedDirectories?: ReadonlyArray<string>) {
|
||||||
|
const expectedRecursiveDirectories = arrayToSet([projectLocation, `${projectLocation}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)]);
|
||||||
|
checkWatchedFiles(host, mapDefined(files, f => {
|
||||||
|
if (f === openFile) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const indexOfNodeModules = f.path.indexOf("/node_modules/");
|
||||||
|
if (indexOfNodeModules === -1) {
|
||||||
|
return f.path;
|
||||||
|
}
|
||||||
|
expectedRecursiveDirectories.set(f.path.substr(0, indexOfNodeModules + "/node_modules".length), true);
|
||||||
|
return undefined;
|
||||||
|
}));
|
||||||
|
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||||
|
checkWatchedDirectories(host, arrayFrom(expectedRecursiveDirectories.keys()), /*recursive*/ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("from files in same folder", () => {
|
||||||
|
function getFiles(fileContent: string) {
|
||||||
|
const file1: File = {
|
||||||
|
path: `${projectLocation}/src/file1.ts`,
|
||||||
|
content: fileContent
|
||||||
|
};
|
||||||
|
const file2: File = {
|
||||||
|
path: `${projectLocation}/src/file2.ts`,
|
||||||
|
content: fileContent
|
||||||
|
};
|
||||||
|
return { file1, file2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
it("relative module name", () => {
|
||||||
|
const module1Name = "./module1";
|
||||||
|
const module2Name = "../module2";
|
||||||
|
const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
|
||||||
|
const { file1, file2 } = getFiles(fileContent);
|
||||||
|
const { module1, module2 } = getModules(`${projectLocation}/src/module1.ts`, `${projectLocation}/module2.ts`);
|
||||||
|
const files = [module1, module2, file1, file2, configFile, libFile];
|
||||||
|
const host = createServerHost(files);
|
||||||
|
const resolutionTrace = createHostModuleResolutionTrace(host);
|
||||||
|
const service = createProjectService(host);
|
||||||
|
service.openClientFile(file1.path);
|
||||||
|
const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
|
||||||
|
verifyTrace(resolutionTrace, expectedTrace);
|
||||||
|
verifyWatchesWithConfigFile(host, files, file1);
|
||||||
|
|
||||||
|
file1.content += fileContent;
|
||||||
|
file2.content += fileContent;
|
||||||
|
host.reloadFS(files);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
verifyTrace(resolutionTrace, [
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
|
||||||
|
]);
|
||||||
|
verifyWatchesWithConfigFile(host, files, file1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("non relative module name", () => {
|
||||||
|
const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/src`];
|
||||||
|
const module1Name = "module1";
|
||||||
|
const module2Name = "module2";
|
||||||
|
const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
|
||||||
|
const { file1, file2 } = getFiles(fileContent);
|
||||||
|
const { module1, module2 } = getModules(`${projectLocation}/src/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`);
|
||||||
|
const files = [module1, module2, file1, file2, configFile, libFile];
|
||||||
|
const host = createServerHost(files);
|
||||||
|
const resolutionTrace = createHostModuleResolutionTrace(host);
|
||||||
|
const service = createProjectService(host);
|
||||||
|
service.openClientFile(file1.path);
|
||||||
|
const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name);
|
||||||
|
getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
|
||||||
|
verifyTrace(resolutionTrace, expectedTrace);
|
||||||
|
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
|
||||||
|
|
||||||
|
file1.content += fileContent;
|
||||||
|
file2.content += fileContent;
|
||||||
|
host.reloadFS(files);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
verifyTrace(resolutionTrace, [
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
|
||||||
|
]);
|
||||||
|
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("from files in different folders", () => {
|
||||||
|
function getFiles(fileContent1: string, fileContent2 = fileContent1, fileContent3 = fileContent1, fileContent4 = fileContent1) {
|
||||||
|
const file1: File = {
|
||||||
|
path: `${projectLocation}/product/src/file1.ts`,
|
||||||
|
content: fileContent1
|
||||||
|
};
|
||||||
|
const file2: File = {
|
||||||
|
path: `${projectLocation}/product/src/feature/file2.ts`,
|
||||||
|
content: fileContent2
|
||||||
|
};
|
||||||
|
const file3: File = {
|
||||||
|
path: `${projectLocation}/product/test/src/file3.ts`,
|
||||||
|
content: fileContent3
|
||||||
|
};
|
||||||
|
const file4: File = {
|
||||||
|
path: `${projectLocation}/product/test/file4.ts`,
|
||||||
|
content: fileContent4
|
||||||
|
};
|
||||||
|
return { file1, file2, file3, file4 };
|
||||||
|
}
|
||||||
|
|
||||||
|
it("relative module name", () => {
|
||||||
|
const module1Name = "./module1";
|
||||||
|
const module2Name = "../module2";
|
||||||
|
const module3Name = "../module1";
|
||||||
|
const module4Name = "../../module2";
|
||||||
|
const module5Name = "../../src/module1";
|
||||||
|
const module6Name = "../src/module1";
|
||||||
|
const fileContent1 = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
|
||||||
|
const fileContent2 = `import { module1 } from "${module3Name}";import { module2 } from "${module4Name}";`;
|
||||||
|
const fileContent3 = `import { module1 } from "${module5Name}";import { module2 } from "${module4Name}";`;
|
||||||
|
const fileContent4 = `import { module1 } from "${module6Name}";import { module2 } from "${module2Name}";`;
|
||||||
|
const { file1, file2, file3, file4 } = getFiles(fileContent1, fileContent2, fileContent3, fileContent4);
|
||||||
|
const { module1, module2 } = getModules(`${projectLocation}/product/src/module1.ts`, `${projectLocation}/product/module2.ts`);
|
||||||
|
const files = [module1, module2, file1, file2, file3, file4, configFile, libFile];
|
||||||
|
const host = createServerHost(files);
|
||||||
|
const resolutionTrace = createHostModuleResolutionTrace(host);
|
||||||
|
const service = createProjectService(host);
|
||||||
|
service.openClientFile(file1.path);
|
||||||
|
const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file2, module1, module3Name, expectedTrace);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file2, module2, module4Name, expectedTrace);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file4, module1, module6Name, expectedTrace);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file4, module2, module2Name, expectedTrace);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file3, module1, module5Name, expectedTrace);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file3, module2, module4Name, expectedTrace);
|
||||||
|
verifyTrace(resolutionTrace, expectedTrace);
|
||||||
|
verifyWatchesWithConfigFile(host, files, file1);
|
||||||
|
|
||||||
|
file1.content += fileContent1;
|
||||||
|
file2.content += fileContent2;
|
||||||
|
file3.content += fileContent3;
|
||||||
|
file4.content += fileContent4;
|
||||||
|
host.reloadFS(files);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
|
||||||
|
verifyTrace(resolutionTrace, [
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
|
||||||
|
]);
|
||||||
|
verifyWatchesWithConfigFile(host, files, file1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("non relative module name", () => {
|
||||||
|
const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/product`];
|
||||||
|
const module1Name = "module1";
|
||||||
|
const module2Name = "module2";
|
||||||
|
const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
|
||||||
|
const { file1, file2, file3, file4 } = getFiles(fileContent);
|
||||||
|
const { module1, module2 } = getModules(`${projectLocation}/product/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`);
|
||||||
|
const files = [module1, module2, file1, file2, file3, file4, configFile, libFile];
|
||||||
|
const host = createServerHost(files);
|
||||||
|
const resolutionTrace = createHostModuleResolutionTrace(host);
|
||||||
|
const service = createProjectService(host);
|
||||||
|
service.openClientFile(file1.path);
|
||||||
|
const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name);
|
||||||
|
getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${projectLocation}/product`, expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${projectLocation}/product`, expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace);
|
||||||
|
verifyTrace(resolutionTrace, expectedTrace);
|
||||||
|
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
|
||||||
|
|
||||||
|
file1.content += fileContent;
|
||||||
|
file2.content += fileContent;
|
||||||
|
file3.content += fileContent;
|
||||||
|
file4.content += fileContent;
|
||||||
|
host.reloadFS(files);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
|
||||||
|
verifyTrace(resolutionTrace, [
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
|
||||||
|
]);
|
||||||
|
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("non relative module name from inferred project", () => {
|
||||||
|
const module1Name = "module1";
|
||||||
|
const module2Name = "module2";
|
||||||
|
const file2Name = "./feature/file2";
|
||||||
|
const file3Name = "../test/src/file3";
|
||||||
|
const file4Name = "../test/file4";
|
||||||
|
const importModuleContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
|
||||||
|
const { file1, file2, file3, file4 } = getFiles(`import "${file2Name}"; import "${file4Name}"; import "${file3Name}"; ${importModuleContent}`, importModuleContent, importModuleContent, importModuleContent);
|
||||||
|
const { module1, module2 } = getModules(`${projectLocation}/product/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`);
|
||||||
|
const files = [module1, module2, file1, file2, file3, file4, libFile];
|
||||||
|
const host = createServerHost(files);
|
||||||
|
const resolutionTrace = createHostModuleResolutionTrace(host);
|
||||||
|
const service = createProjectService(host);
|
||||||
|
service.setCompilerOptionsForInferredProjects({ traceResolution: true });
|
||||||
|
service.openClientFile(file1.path);
|
||||||
|
const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, file2, file2Name);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file1, file4, file4Name, expectedTrace);
|
||||||
|
getExpectedRelativeModuleResolutionTrace(host, file1, file3, file3Name, expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name, expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${projectLocation}/product`, expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${projectLocation}/product`, expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace);
|
||||||
|
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace);
|
||||||
|
verifyTrace(resolutionTrace, expectedTrace);
|
||||||
|
|
||||||
|
const currentDirectory = getDirectoryPath(file1.path);
|
||||||
|
const watchedFiles = mapDefined(files, f => f === file1 || f.path.indexOf("/node_modules/") !== -1 ? undefined : f.path);
|
||||||
|
forEachAncestorDirectory(currentDirectory, d => {
|
||||||
|
watchedFiles.push(combinePaths(d, "tsconfig.json"), combinePaths(d, "jsconfig.json"));
|
||||||
|
});
|
||||||
|
const watchedRecursiveDirectories = getTypeRootsFromLocation(currentDirectory).concat([
|
||||||
|
`${currentDirectory}/node_modules`, `${currentDirectory}/feature`, `${projectLocation}/product/${nodeModules}`,
|
||||||
|
`${projectLocation}/${nodeModules}`, `${projectLocation}/product/test/${nodeModules}`,
|
||||||
|
`${projectLocation}/product/test/src/${nodeModules}`
|
||||||
|
]);
|
||||||
|
checkWatches();
|
||||||
|
|
||||||
|
file1.content += importModuleContent;
|
||||||
|
file2.content += importModuleContent;
|
||||||
|
file3.content += importModuleContent;
|
||||||
|
file4.content += importModuleContent;
|
||||||
|
host.reloadFS(files);
|
||||||
|
host.runQueuedTimeoutCallbacks();
|
||||||
|
|
||||||
|
verifyTrace(resolutionTrace, [
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, file2Name),
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, file4Name),
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, file3Name),
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
|
||||||
|
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
|
||||||
|
]);
|
||||||
|
checkWatches();
|
||||||
|
|
||||||
|
function checkWatches() {
|
||||||
|
checkWatchedFiles(host, watchedFiles);
|
||||||
|
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||||
|
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when watching directories for failed lookup locations in amd resolution", () => {
|
||||||
|
const projectRoot = "/user/username/projects/project";
|
||||||
|
const nodeFile: File = {
|
||||||
|
path: `${projectRoot}/src/typings/node.d.ts`,
|
||||||
|
content: `
|
||||||
|
declare module "fs" {
|
||||||
|
export interface something {
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
};
|
||||||
|
const electronFile: File = {
|
||||||
|
path: `${projectRoot}/src/typings/electron.d.ts`,
|
||||||
|
content: `
|
||||||
|
declare module 'original-fs' {
|
||||||
|
import * as fs from 'fs';
|
||||||
|
export = fs;
|
||||||
|
}`
|
||||||
|
};
|
||||||
|
const srcFile: File = {
|
||||||
|
path: `${projectRoot}/src/somefolder/srcfile.ts`,
|
||||||
|
content: `
|
||||||
|
import { x } from "somefolder/module1";
|
||||||
|
import { x } from "somefolder/module2";
|
||||||
|
const y = x;`
|
||||||
|
};
|
||||||
|
const moduleFile: File = {
|
||||||
|
path: `${projectRoot}/src/somefolder/module1.ts`,
|
||||||
|
content: `
|
||||||
|
export const x = 10;`
|
||||||
|
};
|
||||||
|
const configFile: File = {
|
||||||
|
path: `${projectRoot}/src/tsconfig.json`,
|
||||||
|
content: JSON.stringify({
|
||||||
|
compilerOptions: {
|
||||||
|
module: "amd",
|
||||||
|
moduleResolution: "classic",
|
||||||
|
target: "es5",
|
||||||
|
outDir: "../out",
|
||||||
|
baseUrl: "./",
|
||||||
|
typeRoots: ["typings"]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
function verifyModuleResolution(useNodeFile: boolean) {
|
||||||
|
const files = [...(useNodeFile ? [nodeFile] : []), electronFile, srcFile, moduleFile, configFile, libFile];
|
||||||
|
const host = createServerHost(files);
|
||||||
|
const service = createProjectService(host);
|
||||||
|
service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, projectRoot);
|
||||||
|
checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path));
|
||||||
|
checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1);
|
||||||
|
if (useNodeFile) {
|
||||||
|
checkWatchedDirectories(host, emptyArray, /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
checkWatchedDirectoriesDetailed(host, [`${projectRoot}`, `${projectRoot}/src`], 1, /*recursive*/ false); // failed lookup for fs
|
||||||
|
}
|
||||||
|
const expectedWatchedDirectories = createMap<number>();
|
||||||
|
expectedWatchedDirectories.set(`${projectRoot}/src`, 1); // Wild card
|
||||||
|
expectedWatchedDirectories.set(`${projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2
|
||||||
|
expectedWatchedDirectories.set(`${projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2
|
||||||
|
expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2
|
||||||
|
expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs
|
||||||
|
expectedWatchedDirectories.set(`${projectRoot}/src/typings`, useNodeFile ? 1 : 2); // typeroot directory + failed lookup if not using node file
|
||||||
|
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("when resolves to ambient module", () => {
|
||||||
|
verifyModuleResolution(/*useNodeFile*/ true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when resolution fails", () => {
|
||||||
|
verifyModuleResolution(/*useNodeFile*/ false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ignores files/folder changes in node_modules that start with '.'", () => {
|
||||||
|
const projectPath = "/user/username/projects/project";
|
||||||
|
const npmCacheFile: File = {
|
||||||
|
path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`,
|
||||||
|
content: JSON.stringify({ something: 10 })
|
||||||
|
};
|
||||||
|
const file1: File = {
|
||||||
|
path: `${projectPath}/test.ts`,
|
||||||
|
content: `import { x } from "somemodule";`
|
||||||
|
};
|
||||||
|
const file2: File = {
|
||||||
|
path: `${projectPath}/node_modules/somemodule/index.d.ts`,
|
||||||
|
content: `export const x = 10;`
|
||||||
|
};
|
||||||
|
it("when watching node_modules in inferred project for failed lookup/closed script infos", () => {
|
||||||
|
const files = [libFile, file1, file2];
|
||||||
|
const host = createServerHost(files);
|
||||||
|
const service = createProjectService(host);
|
||||||
|
service.openClientFile(file1.path);
|
||||||
|
checkNumberOfProjects(service, { inferredProjects: 1 });
|
||||||
|
const project = service.inferredProjects[0];
|
||||||
|
checkProjectActualFiles(project, files.map(f => f.path));
|
||||||
|
(project as ResolutionCacheHost).maxNumberOfFilesToIterateForInvalidation = 1;
|
||||||
|
host.checkTimeoutQueueLength(0);
|
||||||
|
|
||||||
|
host.ensureFileOrFolder(npmCacheFile);
|
||||||
|
host.checkTimeoutQueueLength(0);
|
||||||
|
});
|
||||||
|
it("when watching node_modules as part of wild card directories in config project", () => {
|
||||||
|
const config: File = {
|
||||||
|
path: `${projectPath}/tsconfig.json`,
|
||||||
|
content: "{}"
|
||||||
|
};
|
||||||
|
const files = [libFile, file1, file2, config];
|
||||||
|
const host = createServerHost(files);
|
||||||
|
const service = createProjectService(host);
|
||||||
|
service.openClientFile(file1.path);
|
||||||
|
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||||
|
const project = Debug.assertDefined(service.configuredProjects.get(config.path));
|
||||||
|
checkProjectActualFiles(project, files.map(f => f.path));
|
||||||
|
host.checkTimeoutQueueLength(0);
|
||||||
|
|
||||||
|
host.ensureFileOrFolder(npmCacheFile);
|
||||||
|
host.checkTimeoutQueueLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
namespace ts.tscWatch {
|
namespace ts.tscWatch {
|
||||||
export import libFile = TestFSWithWatch.libFile;
|
|
||||||
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
|
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
|
||||||
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
|
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
|
||||||
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
|
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
|
||||||
|
|
237
src/testRunner/unittests/tscWatchHelpers.ts
Normal file
237
src/testRunner/unittests/tscWatchHelpers.ts
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
namespace ts.tscWatch {
|
||||||
|
export import WatchedSystem = TestFSWithWatch.TestServerHost;
|
||||||
|
export type File = TestFSWithWatch.File;
|
||||||
|
export type SymLink = TestFSWithWatch.SymLink;
|
||||||
|
export import libFile = TestFSWithWatch.libFile;
|
||||||
|
export import createWatchedSystem = TestFSWithWatch.createWatchedSystem;
|
||||||
|
export import checkArray = TestFSWithWatch.checkArray;
|
||||||
|
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
|
||||||
|
export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
|
||||||
|
export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
|
||||||
|
export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
|
||||||
|
export import checkOutputContains = TestFSWithWatch.checkOutputContains;
|
||||||
|
export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
|
||||||
|
|
||||||
|
export function checkProgramActualFiles(program: Program, expectedFiles: ReadonlyArray<string>) {
|
||||||
|
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkProgramRootFiles(program: Program, expectedFiles: ReadonlyArray<string>) {
|
||||||
|
checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWatchOfConfigFileReturningBuilder(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||||
|
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
|
||||||
|
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
||||||
|
const watch = createWatchProgram(compilerHost);
|
||||||
|
return () => watch.getCurrentProgram();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||||
|
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
|
||||||
|
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
||||||
|
const watch = createWatchProgram(compilerHost);
|
||||||
|
return () => watch.getCurrentProgram().getProgram();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||||
|
const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, host);
|
||||||
|
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
||||||
|
const watch = createWatchProgram(compilerHost);
|
||||||
|
return () => watch.getCurrentProgram().getProgram();
|
||||||
|
}
|
||||||
|
|
||||||
|
//function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) {
|
||||||
|
// return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function getEmittedLineForSingleFileOutput(filename: string, host: WatchedSystem) {
|
||||||
|
// return `TSFILE: ${filename}${host.newLine}`;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//interface FileOrFolderEmit extends File {
|
||||||
|
// output?: string;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function getFileOrFolderEmit(file: File, getOutput?: (file: File) => string): FileOrFolderEmit {
|
||||||
|
// const result = file as FileOrFolderEmit;
|
||||||
|
// if (getOutput) {
|
||||||
|
// result.output = getOutput(file);
|
||||||
|
// }
|
||||||
|
// return result;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function getEmittedLines(files: FileOrFolderEmit[]) {
|
||||||
|
// const seen = createMap<true>();
|
||||||
|
// const result: string[] = [];
|
||||||
|
// for (const { output } of files) {
|
||||||
|
// if (output && !seen.has(output)) {
|
||||||
|
// seen.set(output, true);
|
||||||
|
// result.push(output);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return result;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function checkAffectedLines(host: WatchedSystem, affectedFiles: FileOrFolderEmit[], allEmittedFiles: string[]) {
|
||||||
|
// const expectedAffectedFiles = getEmittedLines(affectedFiles);
|
||||||
|
// const expectedNonAffectedFiles = mapDefined(allEmittedFiles, line => contains(expectedAffectedFiles, line) ? undefined : line);
|
||||||
|
// checkOutputContains(host, expectedAffectedFiles);
|
||||||
|
// checkOutputDoesNotContain(host, expectedNonAffectedFiles);
|
||||||
|
//}
|
||||||
|
|
||||||
|
const elapsedRegex = /^Elapsed:: [0-9]+ms/;
|
||||||
|
function checkOutputErrors(
|
||||||
|
host: WatchedSystem,
|
||||||
|
logsBeforeWatchDiagnostic: string[] | undefined,
|
||||||
|
preErrorsWatchDiagnostic: Diagnostic,
|
||||||
|
logsBeforeErrors: string[] | undefined,
|
||||||
|
errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>,
|
||||||
|
disableConsoleClears?: boolean | undefined,
|
||||||
|
...postErrorsWatchDiagnostics: Diagnostic[]
|
||||||
|
) {
|
||||||
|
let screenClears = 0;
|
||||||
|
const outputs = host.getOutput();
|
||||||
|
const expectedOutputCount = 1 + errors.length + postErrorsWatchDiagnostics.length +
|
||||||
|
(logsBeforeWatchDiagnostic ? logsBeforeWatchDiagnostic.length : 0) + (logsBeforeErrors ? logsBeforeErrors.length : 0);
|
||||||
|
assert.equal(outputs.length, expectedOutputCount, JSON.stringify(outputs));
|
||||||
|
let index = 0;
|
||||||
|
forEach(logsBeforeWatchDiagnostic, log => assertLog("logsBeforeWatchDiagnostic", log));
|
||||||
|
assertWatchDiagnostic(preErrorsWatchDiagnostic);
|
||||||
|
forEach(logsBeforeErrors, log => assertLog("logBeforeError", log));
|
||||||
|
// Verify errors
|
||||||
|
forEach(errors, assertDiagnostic);
|
||||||
|
forEach(postErrorsWatchDiagnostics, assertWatchDiagnostic);
|
||||||
|
assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears");
|
||||||
|
host.clearOutput();
|
||||||
|
|
||||||
|
function isDiagnostic(diagnostic: Diagnostic | string): diagnostic is Diagnostic {
|
||||||
|
return !!(diagnostic as Diagnostic).messageText;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertDiagnostic(diagnostic: Diagnostic | string) {
|
||||||
|
const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic;
|
||||||
|
assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected));
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertLog(caption: string, expected: string) {
|
||||||
|
const actual = outputs[index];
|
||||||
|
assert.equal(actual.replace(elapsedRegex, ""), expected.replace(elapsedRegex, ""), getOutputAtFailedMessage(caption, expected));
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertWatchDiagnostic(diagnostic: Diagnostic) {
|
||||||
|
const expected = getWatchDiagnosticWithoutDate(diagnostic);
|
||||||
|
if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) {
|
||||||
|
assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`);
|
||||||
|
screenClears++;
|
||||||
|
}
|
||||||
|
assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected));
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputAtFailedMessage(caption: string, expectedOutput: string) {
|
||||||
|
return `Expected ${caption}: ${JSON.stringify(expectedOutput)} at ${index} in ${JSON.stringify(outputs)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWatchDiagnosticWithoutDate(diagnostic: Diagnostic) {
|
||||||
|
const newLines = contains(screenStartingMessageCodes, diagnostic.code)
|
||||||
|
? `${host.newLine}${host.newLine}`
|
||||||
|
: host.newLine;
|
||||||
|
return ` - ${flattenDiagnosticMessageText(diagnostic.messageText, host.newLine)}${newLines}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createErrorsFoundCompilerDiagnostic(errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>) {
|
||||||
|
return errors.length === 1
|
||||||
|
? createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes)
|
||||||
|
: createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) {
|
||||||
|
checkOutputErrors(
|
||||||
|
host,
|
||||||
|
/*logsBeforeWatchDiagnostic*/ undefined,
|
||||||
|
createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode),
|
||||||
|
logsBeforeErrors,
|
||||||
|
errors,
|
||||||
|
disableConsoleClears,
|
||||||
|
createErrorsFoundCompilerDiagnostic(errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
|
||||||
|
checkOutputErrors(
|
||||||
|
host,
|
||||||
|
logsBeforeWatchDiagnostic,
|
||||||
|
createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation),
|
||||||
|
logsBeforeErrors,
|
||||||
|
errors,
|
||||||
|
disableConsoleClears,
|
||||||
|
createErrorsFoundCompilerDiagnostic(errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
|
||||||
|
checkOutputErrors(
|
||||||
|
host,
|
||||||
|
logsBeforeWatchDiagnostic,
|
||||||
|
createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation),
|
||||||
|
logsBeforeErrors,
|
||||||
|
errors,
|
||||||
|
disableConsoleClears);
|
||||||
|
assert.equal(host.exitCode, expectedExitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic {
|
||||||
|
return {
|
||||||
|
file,
|
||||||
|
start,
|
||||||
|
length,
|
||||||
|
|
||||||
|
messageText: text,
|
||||||
|
category: message.category,
|
||||||
|
code: message.code,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//function getDiagnosticWithoutFile(message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
|
||||||
|
// let text = getLocaleSpecificMessage(message);
|
||||||
|
|
||||||
|
// if (arguments.length > 1) {
|
||||||
|
// text = formatStringFromArgs(text, arguments, 1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return getDiagnosticOfFileFrom(/*file*/ undefined, text, /*start*/ undefined, /*length*/ undefined, message);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
|
||||||
|
// let text = getLocaleSpecificMessage(message);
|
||||||
|
|
||||||
|
// if (arguments.length > 4) {
|
||||||
|
// text = formatStringFromArgs(text, arguments, 4);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return getDiagnosticOfFileFrom(file, text, start, length, message);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function getUnknownCompilerOption(program: Program, configFile: File, option: string) {
|
||||||
|
// const quotedOption = `"${option}"`;
|
||||||
|
// return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option);
|
||||||
|
//}
|
||||||
|
|
||||||
|
export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
|
||||||
|
let text = getLocaleSpecificMessage(message);
|
||||||
|
|
||||||
|
if (arguments.length > 5) {
|
||||||
|
text = formatStringFromArgs(text, arguments, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase()))!,
|
||||||
|
text, start, length, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) {
|
||||||
|
const quotedModuleName = `"${moduleName}"`;
|
||||||
|
return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0, moduleName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,45 +1,4 @@
|
||||||
namespace ts.tscWatch {
|
namespace ts.tscWatch {
|
||||||
export import WatchedSystem = TestFSWithWatch.TestServerHost;
|
|
||||||
export type File = TestFSWithWatch.File;
|
|
||||||
export type SymLink = TestFSWithWatch.SymLink;
|
|
||||||
export import createWatchedSystem = TestFSWithWatch.createWatchedSystem;
|
|
||||||
export import checkArray = TestFSWithWatch.checkArray;
|
|
||||||
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
|
|
||||||
export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
|
|
||||||
export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
|
|
||||||
export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
|
|
||||||
export import checkOutputContains = TestFSWithWatch.checkOutputContains;
|
|
||||||
export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
|
|
||||||
|
|
||||||
export function checkProgramActualFiles(program: Program, expectedFiles: ReadonlyArray<string>) {
|
|
||||||
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function checkProgramRootFiles(program: Program, expectedFiles: ReadonlyArray<string>) {
|
|
||||||
checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createWatchOfConfigFileReturningBuilder(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
|
|
||||||
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
|
|
||||||
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
|
||||||
const watch = createWatchProgram(compilerHost);
|
|
||||||
return () => watch.getCurrentProgram();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
|
|
||||||
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
|
|
||||||
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
|
||||||
const watch = createWatchProgram(compilerHost);
|
|
||||||
return () => watch.getCurrentProgram().getProgram();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) {
|
|
||||||
const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, host);
|
|
||||||
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
|
||||||
const watch = createWatchProgram(compilerHost);
|
|
||||||
return () => watch.getCurrentProgram().getProgram();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) {
|
function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) {
|
||||||
return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`;
|
return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`;
|
||||||
}
|
}
|
||||||
|
@ -79,108 +38,6 @@ namespace ts.tscWatch {
|
||||||
checkOutputDoesNotContain(host, expectedNonAffectedFiles);
|
checkOutputDoesNotContain(host, expectedNonAffectedFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
const elapsedRegex = /^Elapsed:: [0-9]+ms/;
|
|
||||||
function checkOutputErrors(
|
|
||||||
host: WatchedSystem,
|
|
||||||
logsBeforeWatchDiagnostic: string[] | undefined,
|
|
||||||
preErrorsWatchDiagnostic: Diagnostic,
|
|
||||||
logsBeforeErrors: string[] | undefined,
|
|
||||||
errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>,
|
|
||||||
disableConsoleClears?: boolean | undefined,
|
|
||||||
...postErrorsWatchDiagnostics: Diagnostic[]
|
|
||||||
) {
|
|
||||||
let screenClears = 0;
|
|
||||||
const outputs = host.getOutput();
|
|
||||||
const expectedOutputCount = 1 + errors.length + postErrorsWatchDiagnostics.length +
|
|
||||||
(logsBeforeWatchDiagnostic ? logsBeforeWatchDiagnostic.length : 0) + (logsBeforeErrors ? logsBeforeErrors.length : 0);
|
|
||||||
assert.equal(outputs.length, expectedOutputCount, JSON.stringify(outputs));
|
|
||||||
let index = 0;
|
|
||||||
forEach(logsBeforeWatchDiagnostic, log => assertLog("logsBeforeWatchDiagnostic", log));
|
|
||||||
assertWatchDiagnostic(preErrorsWatchDiagnostic);
|
|
||||||
forEach(logsBeforeErrors, log => assertLog("logBeforeError", log));
|
|
||||||
// Verify errors
|
|
||||||
forEach(errors, assertDiagnostic);
|
|
||||||
forEach(postErrorsWatchDiagnostics, assertWatchDiagnostic);
|
|
||||||
assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears");
|
|
||||||
host.clearOutput();
|
|
||||||
|
|
||||||
function isDiagnostic(diagnostic: Diagnostic | string): diagnostic is Diagnostic {
|
|
||||||
return !!(diagnostic as Diagnostic).messageText;
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertDiagnostic(diagnostic: Diagnostic | string) {
|
|
||||||
const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic;
|
|
||||||
assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected));
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertLog(caption: string, expected: string) {
|
|
||||||
const actual = outputs[index];
|
|
||||||
assert.equal(actual.replace(elapsedRegex, ""), expected.replace(elapsedRegex, ""), getOutputAtFailedMessage(caption, expected));
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertWatchDiagnostic(diagnostic: Diagnostic) {
|
|
||||||
const expected = getWatchDiagnosticWithoutDate(diagnostic);
|
|
||||||
if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) {
|
|
||||||
assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`);
|
|
||||||
screenClears++;
|
|
||||||
}
|
|
||||||
assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected));
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOutputAtFailedMessage(caption: string, expectedOutput: string) {
|
|
||||||
return `Expected ${caption}: ${JSON.stringify(expectedOutput)} at ${index} in ${JSON.stringify(outputs)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWatchDiagnosticWithoutDate(diagnostic: Diagnostic) {
|
|
||||||
const newLines = contains(screenStartingMessageCodes, diagnostic.code)
|
|
||||||
? `${host.newLine}${host.newLine}`
|
|
||||||
: host.newLine;
|
|
||||||
return ` - ${flattenDiagnosticMessageText(diagnostic.messageText, host.newLine)}${newLines}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createErrorsFoundCompilerDiagnostic(errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>) {
|
|
||||||
return errors.length === 1
|
|
||||||
? createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes)
|
|
||||||
: createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) {
|
|
||||||
checkOutputErrors(
|
|
||||||
host,
|
|
||||||
/*logsBeforeWatchDiagnostic*/ undefined,
|
|
||||||
createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode),
|
|
||||||
logsBeforeErrors,
|
|
||||||
errors,
|
|
||||||
disableConsoleClears,
|
|
||||||
createErrorsFoundCompilerDiagnostic(errors));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
|
|
||||||
checkOutputErrors(
|
|
||||||
host,
|
|
||||||
logsBeforeWatchDiagnostic,
|
|
||||||
createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation),
|
|
||||||
logsBeforeErrors,
|
|
||||||
errors,
|
|
||||||
disableConsoleClears,
|
|
||||||
createErrorsFoundCompilerDiagnostic(errors));
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
|
|
||||||
checkOutputErrors(
|
|
||||||
host,
|
|
||||||
logsBeforeWatchDiagnostic,
|
|
||||||
createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation),
|
|
||||||
logsBeforeErrors,
|
|
||||||
errors,
|
|
||||||
disableConsoleClears);
|
|
||||||
assert.equal(host.exitCode, expectedExitCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic {
|
function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic {
|
||||||
return {
|
return {
|
||||||
file,
|
file,
|
||||||
|
@ -218,22 +75,6 @@ namespace ts.tscWatch {
|
||||||
return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option);
|
return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
|
|
||||||
let text = getLocaleSpecificMessage(message);
|
|
||||||
|
|
||||||
if (arguments.length > 5) {
|
|
||||||
text = formatStringFromArgs(text, arguments, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase()))!,
|
|
||||||
text, start, length, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) {
|
|
||||||
const quotedModuleName = `"${moduleName}"`;
|
|
||||||
return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0, moduleName);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("tsc-watch program updates", () => {
|
describe("tsc-watch program updates", () => {
|
||||||
const commonFile1: File = {
|
const commonFile1: File = {
|
||||||
path: "/a/b/commonFile1.ts",
|
path: "/a/b/commonFile1.ts",
|
||||||
|
@ -2343,411 +2184,6 @@ interface Document {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("tsc-watch module resolution caching", () => {
|
|
||||||
it("works", () => {
|
|
||||||
const root = {
|
|
||||||
path: "/a/d/f0.ts",
|
|
||||||
content: `import {x} from "f1"`
|
|
||||||
};
|
|
||||||
const imported = {
|
|
||||||
path: "/a/f1.ts",
|
|
||||||
content: `foo()`
|
|
||||||
};
|
|
||||||
|
|
||||||
const files = [root, imported, libFile];
|
|
||||||
const host = createWatchedSystem(files);
|
|
||||||
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
|
|
||||||
|
|
||||||
const f1IsNotModule = getDiagnosticOfFileFromProgram(watch(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path);
|
|
||||||
const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo");
|
|
||||||
|
|
||||||
// ensure that imported file was found
|
|
||||||
checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]);
|
|
||||||
|
|
||||||
const originalFileExists = host.fileExists;
|
|
||||||
{
|
|
||||||
const newContent = `import {x} from "f1"
|
|
||||||
var x: string = 1;`;
|
|
||||||
root.content = newContent;
|
|
||||||
host.reloadFS(files);
|
|
||||||
|
|
||||||
// patch fileExists to make sure that disk is not touched
|
|
||||||
host.fileExists = notImplemented;
|
|
||||||
|
|
||||||
// trigger synchronization to make sure that import will be fetched from the cache
|
|
||||||
host.runQueuedTimeoutCallbacks();
|
|
||||||
|
|
||||||
// ensure file has correct number of errors after edit
|
|
||||||
checkOutputErrorsIncremental(host, [
|
|
||||||
f1IsNotModule,
|
|
||||||
getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"),
|
|
||||||
cannotFindFoo
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let fileExistsIsCalled = false;
|
|
||||||
host.fileExists = (fileName): boolean => {
|
|
||||||
if (fileName === "lib.d.ts") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
fileExistsIsCalled = true;
|
|
||||||
assert.isTrue(fileName.indexOf("/f2.") !== -1);
|
|
||||||
return originalFileExists.call(host, fileName);
|
|
||||||
};
|
|
||||||
|
|
||||||
root.content = `import {x} from "f2"`;
|
|
||||||
host.reloadFS(files);
|
|
||||||
|
|
||||||
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
|
|
||||||
host.runQueuedTimeoutCallbacks();
|
|
||||||
|
|
||||||
// ensure file has correct number of errors after edit
|
|
||||||
checkOutputErrorsIncremental(host, [
|
|
||||||
getDiagnosticModuleNotFoundOfFile(watch(), root, "f2")
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert.isTrue(fileExistsIsCalled);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let fileExistsCalled = false;
|
|
||||||
host.fileExists = (fileName): boolean => {
|
|
||||||
if (fileName === "lib.d.ts") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
fileExistsCalled = true;
|
|
||||||
assert.isTrue(fileName.indexOf("/f1.") !== -1);
|
|
||||||
return originalFileExists.call(host, fileName);
|
|
||||||
};
|
|
||||||
|
|
||||||
const newContent = `import {x} from "f1"`;
|
|
||||||
root.content = newContent;
|
|
||||||
|
|
||||||
host.reloadFS(files);
|
|
||||||
host.runQueuedTimeoutCallbacks();
|
|
||||||
|
|
||||||
checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]);
|
|
||||||
assert.isTrue(fileExistsCalled);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("loads missing files from disk", () => {
|
|
||||||
const root = {
|
|
||||||
path: `/a/foo.ts`,
|
|
||||||
content: `import {x} from "bar"`
|
|
||||||
};
|
|
||||||
|
|
||||||
const imported = {
|
|
||||||
path: `/a/bar.d.ts`,
|
|
||||||
content: `export const y = 1;`
|
|
||||||
};
|
|
||||||
|
|
||||||
const files = [root, libFile];
|
|
||||||
const host = createWatchedSystem(files);
|
|
||||||
const originalFileExists = host.fileExists;
|
|
||||||
|
|
||||||
let fileExistsCalledForBar = false;
|
|
||||||
host.fileExists = fileName => {
|
|
||||||
if (fileName === "lib.d.ts") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!fileExistsCalledForBar) {
|
|
||||||
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return originalFileExists.call(host, fileName);
|
|
||||||
};
|
|
||||||
|
|
||||||
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
|
|
||||||
|
|
||||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
|
||||||
checkOutputErrorsInitial(host, [
|
|
||||||
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
|
|
||||||
]);
|
|
||||||
|
|
||||||
fileExistsCalledForBar = false;
|
|
||||||
root.content = `import {y} from "bar"`;
|
|
||||||
host.reloadFS(files.concat(imported));
|
|
||||||
|
|
||||||
host.runQueuedTimeoutCallbacks();
|
|
||||||
checkOutputErrorsIncremental(host, emptyArray);
|
|
||||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => {
|
|
||||||
const root = {
|
|
||||||
path: `/a/foo.ts`,
|
|
||||||
content: `import {x} from "bar"`
|
|
||||||
};
|
|
||||||
|
|
||||||
const imported = {
|
|
||||||
path: `/a/bar.d.ts`,
|
|
||||||
content: `export const y = 1;export const x = 10;`
|
|
||||||
};
|
|
||||||
|
|
||||||
const files = [root, libFile];
|
|
||||||
const filesWithImported = files.concat(imported);
|
|
||||||
const host = createWatchedSystem(filesWithImported);
|
|
||||||
const originalFileExists = host.fileExists;
|
|
||||||
let fileExistsCalledForBar = false;
|
|
||||||
host.fileExists = fileName => {
|
|
||||||
if (fileName === "lib.d.ts") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!fileExistsCalledForBar) {
|
|
||||||
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
|
|
||||||
}
|
|
||||||
return originalFileExists.call(host, fileName);
|
|
||||||
};
|
|
||||||
|
|
||||||
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
|
|
||||||
|
|
||||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
|
||||||
checkOutputErrorsInitial(host, emptyArray);
|
|
||||||
|
|
||||||
fileExistsCalledForBar = false;
|
|
||||||
host.reloadFS(files);
|
|
||||||
host.runQueuedTimeoutCallbacks();
|
|
||||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
|
|
||||||
checkOutputErrorsIncremental(host, [
|
|
||||||
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
|
|
||||||
]);
|
|
||||||
|
|
||||||
fileExistsCalledForBar = false;
|
|
||||||
host.reloadFS(filesWithImported);
|
|
||||||
host.checkTimeoutQueueLengthAndRun(1);
|
|
||||||
checkOutputErrorsIncremental(host, emptyArray);
|
|
||||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("works when module resolution changes to ambient module", () => {
|
|
||||||
const root = {
|
|
||||||
path: "/a/b/foo.ts",
|
|
||||||
content: `import * as fs from "fs";`
|
|
||||||
};
|
|
||||||
|
|
||||||
const packageJson = {
|
|
||||||
path: "/a/b/node_modules/@types/node/package.json",
|
|
||||||
content: `
|
|
||||||
{
|
|
||||||
"main": ""
|
|
||||||
}
|
|
||||||
`
|
|
||||||
};
|
|
||||||
|
|
||||||
const nodeType = {
|
|
||||||
path: "/a/b/node_modules/@types/node/index.d.ts",
|
|
||||||
content: `
|
|
||||||
declare module "fs" {
|
|
||||||
export interface Stats {
|
|
||||||
isFile(): boolean;
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
};
|
|
||||||
|
|
||||||
const files = [root, libFile];
|
|
||||||
const filesWithNodeType = files.concat(packageJson, nodeType);
|
|
||||||
const host = createWatchedSystem(files, { currentDirectory: "/a/b" });
|
|
||||||
|
|
||||||
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { });
|
|
||||||
|
|
||||||
checkOutputErrorsInitial(host, [
|
|
||||||
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
|
|
||||||
]);
|
|
||||||
|
|
||||||
host.reloadFS(filesWithNodeType);
|
|
||||||
host.runQueuedTimeoutCallbacks();
|
|
||||||
checkOutputErrorsIncremental(host, emptyArray);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("works when included file with ambient module changes", () => {
|
|
||||||
const root = {
|
|
||||||
path: "/a/b/foo.ts",
|
|
||||||
content: `
|
|
||||||
import * as fs from "fs";
|
|
||||||
import * as u from "url";
|
|
||||||
`
|
|
||||||
};
|
|
||||||
|
|
||||||
const file = {
|
|
||||||
path: "/a/b/bar.d.ts",
|
|
||||||
content: `
|
|
||||||
declare module "url" {
|
|
||||||
export interface Url {
|
|
||||||
href?: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
};
|
|
||||||
|
|
||||||
const fileContentWithFS = `
|
|
||||||
declare module "fs" {
|
|
||||||
export interface Stats {
|
|
||||||
isFile(): boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const files = [root, file, libFile];
|
|
||||||
const host = createWatchedSystem(files, { currentDirectory: "/a/b" });
|
|
||||||
|
|
||||||
const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {});
|
|
||||||
|
|
||||||
checkOutputErrorsInitial(host, [
|
|
||||||
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
|
|
||||||
]);
|
|
||||||
|
|
||||||
file.content += fileContentWithFS;
|
|
||||||
host.reloadFS(files);
|
|
||||||
host.runQueuedTimeoutCallbacks();
|
|
||||||
checkOutputErrorsIncremental(host, emptyArray);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("works when reusing program with files from external library", () => {
|
|
||||||
interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; }
|
|
||||||
const configDir = "/a/b/projects/myProject/src/";
|
|
||||||
const file1: File = {
|
|
||||||
path: configDir + "file1.ts",
|
|
||||||
content: 'import module1 = require("module1");\nmodule1("hello");'
|
|
||||||
};
|
|
||||||
const file2: File = {
|
|
||||||
path: configDir + "file2.ts",
|
|
||||||
content: 'import module11 = require("module1");\nmodule11("hello");'
|
|
||||||
};
|
|
||||||
const module1: File = {
|
|
||||||
path: "/a/b/projects/myProject/node_modules/module1/index.js",
|
|
||||||
content: "module.exports = options => { return options.toString(); }"
|
|
||||||
};
|
|
||||||
const configFile: File = {
|
|
||||||
path: configDir + "tsconfig.json",
|
|
||||||
content: JSON.stringify({
|
|
||||||
compilerOptions: {
|
|
||||||
allowJs: true,
|
|
||||||
rootDir: ".",
|
|
||||||
outDir: "../dist",
|
|
||||||
moduleResolution: "node",
|
|
||||||
maxNodeModuleJsDepth: 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const outDirFolder = "/a/b/projects/myProject/dist/";
|
|
||||||
const programFiles = [file1, file2, module1, libFile];
|
|
||||||
const host = createWatchedSystem(programFiles.concat(configFile), { currentDirectory: "/a/b/projects/myProject/" });
|
|
||||||
const watch = createWatchOfConfigFile(configFile.path, host);
|
|
||||||
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
|
|
||||||
checkOutputErrorsInitial(host, emptyArray);
|
|
||||||
const expectedFiles: ExpectedFile[] = [
|
|
||||||
createExpectedEmittedFile(file1),
|
|
||||||
createExpectedEmittedFile(file2),
|
|
||||||
createExpectedToNotEmitFile("index.js"),
|
|
||||||
createExpectedToNotEmitFile("src/index.js"),
|
|
||||||
createExpectedToNotEmitFile("src/file1.js"),
|
|
||||||
createExpectedToNotEmitFile("src/file2.js"),
|
|
||||||
createExpectedToNotEmitFile("lib.js"),
|
|
||||||
createExpectedToNotEmitFile("lib.d.ts")
|
|
||||||
];
|
|
||||||
verifyExpectedFiles(expectedFiles);
|
|
||||||
|
|
||||||
file1.content += "\n;";
|
|
||||||
expectedFiles[0].content += ";\n"; // Only emit file1 with this change
|
|
||||||
expectedFiles[1].isExpectedToEmit = false;
|
|
||||||
host.reloadFS(programFiles.concat(configFile));
|
|
||||||
host.runQueuedTimeoutCallbacks();
|
|
||||||
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
|
|
||||||
checkOutputErrorsIncremental(host, emptyArray);
|
|
||||||
verifyExpectedFiles(expectedFiles);
|
|
||||||
|
|
||||||
|
|
||||||
function verifyExpectedFiles(expectedFiles: ExpectedFile[]) {
|
|
||||||
forEach(expectedFiles, f => {
|
|
||||||
assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit"));
|
|
||||||
if (f.isExpectedToEmit) {
|
|
||||||
assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createExpectedToNotEmitFile(fileName: string): ExpectedFile {
|
|
||||||
return {
|
|
||||||
path: outDirFolder + fileName,
|
|
||||||
isExpectedToEmit: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createExpectedEmittedFile(file: File): ExpectedFile {
|
|
||||||
return {
|
|
||||||
path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js,
|
|
||||||
isExpectedToEmit: true,
|
|
||||||
content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("works when renaming node_modules folder that already contains @types folder", () => {
|
|
||||||
const currentDirectory = "/user/username/projects/myproject";
|
|
||||||
const file: File = {
|
|
||||||
path: `${currentDirectory}/a.ts`,
|
|
||||||
content: `import * as q from "qqq";`
|
|
||||||
};
|
|
||||||
const module: File = {
|
|
||||||
path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`,
|
|
||||||
content: "export {}"
|
|
||||||
};
|
|
||||||
const files = [file, module, libFile];
|
|
||||||
const host = createWatchedSystem(files, { currentDirectory });
|
|
||||||
const watch = createWatchOfFilesAndCompilerOptions([file.path], host);
|
|
||||||
|
|
||||||
checkProgramActualFiles(watch(), [file.path, libFile.path]);
|
|
||||||
checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]);
|
|
||||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
|
||||||
checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true);
|
|
||||||
|
|
||||||
host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`);
|
|
||||||
host.runQueuedTimeoutCallbacks();
|
|
||||||
checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]);
|
|
||||||
checkOutputErrorsIncremental(host, emptyArray);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("ignores files/folder changes in node_modules that start with '.'", () => {
|
|
||||||
const projectPath = "/user/username/projects/project";
|
|
||||||
const npmCacheFile: File = {
|
|
||||||
path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`,
|
|
||||||
content: JSON.stringify({ something: 10 })
|
|
||||||
};
|
|
||||||
const file1: File = {
|
|
||||||
path: `${projectPath}/test.ts`,
|
|
||||||
content: `import { x } from "somemodule";`
|
|
||||||
};
|
|
||||||
const file2: File = {
|
|
||||||
path: `${projectPath}/node_modules/somemodule/index.d.ts`,
|
|
||||||
content: `export const x = 10;`
|
|
||||||
};
|
|
||||||
const files = [libFile, file1, file2];
|
|
||||||
const expectedFiles = files.map(f => f.path);
|
|
||||||
it("when watching node_modules in inferred project for failed lookup", () => {
|
|
||||||
const host = createWatchedSystem(files);
|
|
||||||
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1);
|
|
||||||
checkProgramActualFiles(watch(), expectedFiles);
|
|
||||||
host.checkTimeoutQueueLength(0);
|
|
||||||
|
|
||||||
host.ensureFileOrFolder(npmCacheFile);
|
|
||||||
host.checkTimeoutQueueLength(0);
|
|
||||||
});
|
|
||||||
it("when watching node_modules as part of wild card directories in config project", () => {
|
|
||||||
const config: File = {
|
|
||||||
path: `${projectPath}/tsconfig.json`,
|
|
||||||
content: "{}"
|
|
||||||
};
|
|
||||||
const host = createWatchedSystem(files.concat(config));
|
|
||||||
const watch = createWatchOfConfigFile(config.path, host);
|
|
||||||
checkProgramActualFiles(watch(), expectedFiles);
|
|
||||||
host.checkTimeoutQueueLength(0);
|
|
||||||
|
|
||||||
host.ensureFileOrFolder(npmCacheFile);
|
|
||||||
host.checkTimeoutQueueLength(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("tsc-watch with when module emit is specified as node", () => {
|
describe("tsc-watch with when module emit is specified as node", () => {
|
||||||
it("when instead of filechanged recursive directory watcher is invoked", () => {
|
it("when instead of filechanged recursive directory watcher is invoked", () => {
|
||||||
const configFile: File = {
|
const configFile: File = {
|
||||||
|
@ -2883,85 +2319,4 @@ declare module "fs" {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("tsc-watch with modules linked to sibling folder", () => {
|
|
||||||
const projectRoot = "/user/username/projects/project";
|
|
||||||
const mainPackageRoot = `${projectRoot}/main`;
|
|
||||||
const linkedPackageRoot = `${projectRoot}/linked-package`;
|
|
||||||
const mainFile: File = {
|
|
||||||
path: `${mainPackageRoot}/index.ts`,
|
|
||||||
content: "import { Foo } from '@scoped/linked-package'"
|
|
||||||
};
|
|
||||||
const config: File = {
|
|
||||||
path: `${mainPackageRoot}/tsconfig.json`,
|
|
||||||
content: JSON.stringify({
|
|
||||||
compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." },
|
|
||||||
files: ["index.ts"]
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const linkedPackageInMain: SymLink = {
|
|
||||||
path: `${mainPackageRoot}/node_modules/@scoped/linked-package`,
|
|
||||||
symLink: `${linkedPackageRoot}`
|
|
||||||
};
|
|
||||||
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: File = {
|
|
||||||
path: `${linkedPackageRoot}/dist/index.d.ts`,
|
|
||||||
content: "export * from './other';"
|
|
||||||
};
|
|
||||||
const linkedPackageOther: File = {
|
|
||||||
path: `${linkedPackageRoot}/dist/other.d.ts`,
|
|
||||||
content: 'export declare const Foo = "BAR";'
|
|
||||||
};
|
|
||||||
|
|
||||||
it("verify watched directories", () => {
|
|
||||||
const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther];
|
|
||||||
const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot });
|
|
||||||
createWatchOfConfigFile("tsconfig.json", host);
|
|
||||||
checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1);
|
|
||||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
|
||||||
checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("tsc-watch with custom module resolution", () => {
|
|
||||||
const projectRoot = "/user/username/projects/project";
|
|
||||||
const configFileJson: any = {
|
|
||||||
compilerOptions: { module: "commonjs", resolveJsonModule: true },
|
|
||||||
files: ["index.ts"]
|
|
||||||
};
|
|
||||||
const mainFile: File = {
|
|
||||||
path: `${projectRoot}/index.ts`,
|
|
||||||
content: "import settings from './settings.json';"
|
|
||||||
};
|
|
||||||
const config: File = {
|
|
||||||
path: `${projectRoot}/tsconfig.json`,
|
|
||||||
content: JSON.stringify(configFileJson)
|
|
||||||
};
|
|
||||||
const settingsJson: File = {
|
|
||||||
path: `${projectRoot}/settings.json`,
|
|
||||||
content: JSON.stringify({ content: "Print this" })
|
|
||||||
};
|
|
||||||
|
|
||||||
it("verify that module resolution with json extension works when returned without extension", () => {
|
|
||||||
const files = [libFile, mainFile, config, settingsJson];
|
|
||||||
const host = createWatchedSystem(files, { currentDirectory: projectRoot });
|
|
||||||
const compilerHost = createWatchCompilerHostOfConfigFile(config.path, {}, host);
|
|
||||||
const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path);
|
|
||||||
compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => {
|
|
||||||
const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost);
|
|
||||||
const resolvedModule = result.resolvedModule!;
|
|
||||||
return {
|
|
||||||
resolvedFileName: resolvedModule.resolvedFileName,
|
|
||||||
isExternalLibraryImport: resolvedModule.isExternalLibraryImport,
|
|
||||||
originalFileName: resolvedModule.originalPath,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const watch = createWatchProgram(compilerHost);
|
|
||||||
const program = watch.getCurrentProgram().getProgram();
|
|
||||||
checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
679
src/testRunner/unittests/tsserverHelpers.ts
Normal file
679
src/testRunner/unittests/tsserverHelpers.ts
Normal file
|
@ -0,0 +1,679 @@
|
||||||
|
namespace ts.projectSystem {
|
||||||
|
export import TI = server.typingsInstaller;
|
||||||
|
export import protocol = server.protocol;
|
||||||
|
export import CommandNames = server.CommandNames;
|
||||||
|
|
||||||
|
export import TestServerHost = TestFSWithWatch.TestServerHost;
|
||||||
|
export type File = TestFSWithWatch.File;
|
||||||
|
export type SymLink = TestFSWithWatch.SymLink;
|
||||||
|
export type Folder = TestFSWithWatch.Folder;
|
||||||
|
export import createServerHost = TestFSWithWatch.createServerHost;
|
||||||
|
export import checkArray = TestFSWithWatch.checkArray;
|
||||||
|
export import libFile = TestFSWithWatch.libFile;
|
||||||
|
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
|
||||||
|
export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
|
||||||
|
export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
|
||||||
|
export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
|
||||||
|
|
||||||
|
//const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/;
|
||||||
|
//function mapOutputToJson(s: string) {
|
||||||
|
// return convertToObject(
|
||||||
|
// parseJsonText("json.json", s.replace(outputEventRegex, "")),
|
||||||
|
// []
|
||||||
|
// );
|
||||||
|
//}
|
||||||
|
|
||||||
|
export const customTypesMap = {
|
||||||
|
path: <Path>"/typesMap.json",
|
||||||
|
content: `{
|
||||||
|
"typesMap": {
|
||||||
|
"jquery": {
|
||||||
|
"match": "jquery(-(\\\\.?\\\\d+)+)?(\\\\.intellisense)?(\\\\.min)?\\\\.js$",
|
||||||
|
"types": ["jquery"]
|
||||||
|
},
|
||||||
|
"quack": {
|
||||||
|
"match": "/duckquack-(\\\\d+)\\\\.min\\\\.js",
|
||||||
|
"types": ["duck-types"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"simpleMap": {
|
||||||
|
"Bacon": "baconjs",
|
||||||
|
"bliss": "blissfuljs",
|
||||||
|
"commander": "commander",
|
||||||
|
"cordova": "cordova",
|
||||||
|
"react": "react",
|
||||||
|
"lodash": "lodash"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PostExecAction {
|
||||||
|
readonly success: boolean;
|
||||||
|
readonly callback: TI.RequestCompletedAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nullLogger: server.Logger = {
|
||||||
|
close: noop,
|
||||||
|
hasLevel: () => false,
|
||||||
|
loggingEnabled: () => false,
|
||||||
|
perftrc: noop,
|
||||||
|
info: noop,
|
||||||
|
msg: noop,
|
||||||
|
startGroup: noop,
|
||||||
|
endGroup: noop,
|
||||||
|
getLogFileName: () => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createHasErrorMessageLogger() {
|
||||||
|
let hasErrorMsg = false;
|
||||||
|
const { close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc } = nullLogger;
|
||||||
|
const logger: server.Logger = {
|
||||||
|
close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc,
|
||||||
|
msg: (s, type) => {
|
||||||
|
Debug.fail(`Error: ${s}, type: ${type}`);
|
||||||
|
hasErrorMsg = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return { logger, hasErrorMsg: () => hasErrorMsg };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller {
|
||||||
|
protected projectService!: server.ProjectService;
|
||||||
|
constructor(
|
||||||
|
readonly globalTypingsCacheLocation: string,
|
||||||
|
throttleLimit: number,
|
||||||
|
installTypingHost: server.ServerHost,
|
||||||
|
readonly typesRegistry = createMap<MapLike<string>>(),
|
||||||
|
log?: TI.Log) {
|
||||||
|
super(installTypingHost, globalTypingsCacheLocation, TestFSWithWatch.safeList.path, customTypesMap.path, throttleLimit, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected postExecActions: PostExecAction[] = [];
|
||||||
|
|
||||||
|
isKnownTypesPackageName = notImplemented;
|
||||||
|
installPackage = notImplemented;
|
||||||
|
inspectValue = notImplemented;
|
||||||
|
|
||||||
|
executePendingCommands() {
|
||||||
|
const actionsToRun = this.postExecActions;
|
||||||
|
this.postExecActions = [];
|
||||||
|
for (const action of actionsToRun) {
|
||||||
|
action.callback(action.success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPendingCommands(expectedCount: number) {
|
||||||
|
assert.equal(this.postExecActions.length, expectedCount, `Expected ${expectedCount} post install actions`);
|
||||||
|
}
|
||||||
|
|
||||||
|
onProjectClosed = noop;
|
||||||
|
|
||||||
|
attach(projectService: server.ProjectService) {
|
||||||
|
this.projectService = projectService;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstallTypingHost() {
|
||||||
|
return this.installTypingHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
|
||||||
|
this.addPostExecAction("success", cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendResponse(response: server.SetTypings | server.InvalidateCachedTypings) {
|
||||||
|
this.projectService.updateTypingsForProject(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
|
||||||
|
const request = server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation);
|
||||||
|
this.install(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) {
|
||||||
|
const out = isString(stdout) ? stdout : createNpmPackageJsonString(stdout);
|
||||||
|
const action: PostExecAction = {
|
||||||
|
success: !!out,
|
||||||
|
callback: cb
|
||||||
|
};
|
||||||
|
this.postExecActions.push(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNpmPackageJsonString(installedTypings: string[]): string {
|
||||||
|
const dependencies: MapLike<any> = {};
|
||||||
|
for (const typing of installedTypings) {
|
||||||
|
dependencies[typing] = "1.0.0";
|
||||||
|
}
|
||||||
|
return JSON.stringify({ dependencies });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTypesRegistry(...list: string[]): Map<MapLike<string>> {
|
||||||
|
const versionMap = {
|
||||||
|
"latest": "1.3.0",
|
||||||
|
"ts2.0": "1.0.0",
|
||||||
|
"ts2.1": "1.0.0",
|
||||||
|
"ts2.2": "1.2.0",
|
||||||
|
"ts2.3": "1.3.0",
|
||||||
|
"ts2.4": "1.3.0",
|
||||||
|
"ts2.5": "1.3.0",
|
||||||
|
"ts2.6": "1.3.0",
|
||||||
|
"ts2.7": "1.3.0"
|
||||||
|
};
|
||||||
|
const map = createMap<MapLike<string>>();
|
||||||
|
for (const l of list) {
|
||||||
|
map.set(l, versionMap);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toExternalFile(fileName: string): protocol.ExternalFile {
|
||||||
|
return { fileName };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toExternalFiles(fileNames: string[]) {
|
||||||
|
return map(fileNames, toExternalFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fileStats(nonZeroStats: Partial<server.FileStats>): server.FileStats {
|
||||||
|
return { ts: 0, tsSize: 0, tsx: 0, tsxSize: 0, dts: 0, dtsSize: 0, js: 0, jsSize: 0, jsx: 0, jsxSize: 0, deferred: 0, deferredSize: 0, ...nonZeroStats };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigFileDiagnostic {
|
||||||
|
fileName: string | undefined;
|
||||||
|
start: number | undefined;
|
||||||
|
length: number | undefined;
|
||||||
|
messageText: string;
|
||||||
|
category: DiagnosticCategory;
|
||||||
|
code: number;
|
||||||
|
reportsUnnecessary?: {};
|
||||||
|
source?: string;
|
||||||
|
relatedInformation?: DiagnosticRelatedInformation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TestServerEventManager {
|
||||||
|
private events: server.ProjectServiceEvent[] = [];
|
||||||
|
readonly session: TestSession;
|
||||||
|
readonly service: server.ProjectService;
|
||||||
|
readonly host: TestServerHost;
|
||||||
|
constructor(files: File[], suppressDiagnosticEvents?: boolean) {
|
||||||
|
this.host = createServerHost(files);
|
||||||
|
this.session = createSession(this.host, {
|
||||||
|
canUseEvents: true,
|
||||||
|
eventHandler: event => this.events.push(event),
|
||||||
|
suppressDiagnosticEvents,
|
||||||
|
});
|
||||||
|
this.service = this.session.getProjectService();
|
||||||
|
}
|
||||||
|
|
||||||
|
getEvents(): ReadonlyArray<server.ProjectServiceEvent> {
|
||||||
|
const events = this.events;
|
||||||
|
this.events = [];
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEvent<T extends server.ProjectServiceEvent>(eventName: T["eventName"]): T["data"] {
|
||||||
|
let eventData: T["data"] | undefined;
|
||||||
|
filterMutate(this.events, e => {
|
||||||
|
if (e.eventName === eventName) {
|
||||||
|
if (eventData !== undefined) {
|
||||||
|
assert(false, "more than one event found");
|
||||||
|
}
|
||||||
|
eventData = e.data;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return Debug.assertDefined(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasZeroEvent<T extends server.ProjectServiceEvent>(eventName: T["eventName"]) {
|
||||||
|
this.events.forEach(event => assert.notEqual(event.eventName, eventName));
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSingleConfigFileDiagEvent(configFileName: string, triggerFile: string, errors: ReadonlyArray<ConfigFileDiagnostic>) {
|
||||||
|
const eventData = this.getEvent<server.ConfigFileDiagEvent>(server.ConfigFileDiagEvent);
|
||||||
|
assert.equal(eventData.configFileName, configFileName);
|
||||||
|
assert.equal(eventData.triggerFile, triggerFile);
|
||||||
|
const actual = eventData.diagnostics.map(({ file, messageText, ...rest }) => ({ fileName: file && file.fileName, messageText: isString(messageText) ? messageText : "", ...rest }));
|
||||||
|
if (errors) {
|
||||||
|
assert.deepEqual(actual, errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>, configFile = "/tsconfig.json"): void {
|
||||||
|
assert.deepEqual<server.ProjectInfoTelemetryEventData>(this.getEvent<server.ProjectInfoTelemetryEvent>(server.ProjectInfoTelemetryEvent), {
|
||||||
|
projectId: sys.createSHA256Hash!(configFile),
|
||||||
|
fileStats: fileStats({ ts: 1 }),
|
||||||
|
compilerOptions: {},
|
||||||
|
extends: false,
|
||||||
|
files: false,
|
||||||
|
include: false,
|
||||||
|
exclude: false,
|
||||||
|
compileOnSave: false,
|
||||||
|
typeAcquisition: {
|
||||||
|
enable: false,
|
||||||
|
exclude: false,
|
||||||
|
include: false,
|
||||||
|
},
|
||||||
|
configFileName: "tsconfig.json",
|
||||||
|
projectType: "configured",
|
||||||
|
languageServiceEnabled: true,
|
||||||
|
version,
|
||||||
|
...partial,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
assertOpenFileTelemetryEvent(info: server.OpenFileInfo): void {
|
||||||
|
assert.deepEqual<server.OpenFileInfoTelemetryEventData>(this.getEvent<server.OpenFileInfoTelemetryEvent>(server.OpenFileInfoTelemetryEvent), { info });
|
||||||
|
}
|
||||||
|
assertNoOpenFilesTelemetryEvent(): void {
|
||||||
|
this.hasZeroEvent<server.OpenFileInfoTelemetryEvent>(server.OpenFileInfoTelemetryEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TestSession extends server.Session {
|
||||||
|
private seq = 0;
|
||||||
|
public events: protocol.Event[] = [];
|
||||||
|
public host!: TestServerHost;
|
||||||
|
|
||||||
|
getProjectService() {
|
||||||
|
return this.projectService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSeq() {
|
||||||
|
return this.seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNextSeq() {
|
||||||
|
return this.seq + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public executeCommandSeq<T extends server.protocol.Request>(request: Partial<T>) {
|
||||||
|
this.seq++;
|
||||||
|
request.seq = this.seq;
|
||||||
|
request.type = "request";
|
||||||
|
return this.executeCommand(<T>request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event<T extends object>(body: T, eventName: string) {
|
||||||
|
this.events.push(server.toEvent(eventName, body));
|
||||||
|
super.event(body, eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearMessages() {
|
||||||
|
clear(this.events);
|
||||||
|
this.host.clearOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSession(host: server.ServerHost, opts: Partial<server.SessionOptions> = {}) {
|
||||||
|
if (opts.typingsInstaller === undefined) {
|
||||||
|
opts.typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/ 5, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.eventHandler !== undefined) {
|
||||||
|
opts.canUseEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionOptions: server.SessionOptions = {
|
||||||
|
host,
|
||||||
|
cancellationToken: server.nullCancellationToken,
|
||||||
|
useSingleInferredProject: false,
|
||||||
|
useInferredProjectPerProjectRoot: false,
|
||||||
|
typingsInstaller: undefined!, // TODO: GH#18217
|
||||||
|
byteLength: Utils.byteLength,
|
||||||
|
hrtime: process.hrtime,
|
||||||
|
logger: opts.logger || createHasErrorMessageLogger().logger,
|
||||||
|
canUseEvents: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return new TestSession({ ...sessionOptions, ...opts });
|
||||||
|
}
|
||||||
|
|
||||||
|
//function createSessionWithEventTracking<T extends server.ProjectServiceEvent>(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) {
|
||||||
|
// const events: T[] = [];
|
||||||
|
// const session = createSession(host, {
|
||||||
|
// eventHandler: e => {
|
||||||
|
// if (e.eventName === eventName || eventNames.some(eventName => e.eventName === eventName)) {
|
||||||
|
// events.push(e as T);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return { session, events };
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function createSessionWithDefaultEventHandler<T extends protocol.AnyEvent>(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial<server.SessionOptions> = {}) {
|
||||||
|
// const session = createSession(host, { canUseEvents: true, ...opts });
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// session,
|
||||||
|
// getEvents,
|
||||||
|
// clearEvents
|
||||||
|
// };
|
||||||
|
|
||||||
|
// function getEvents() {
|
||||||
|
// return mapDefined(host.getOutput(), s => {
|
||||||
|
// const e = mapOutputToJson(s);
|
||||||
|
// return (isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function clearEvents() {
|
||||||
|
// session.clearMessages();
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
export interface CreateProjectServiceParameters {
|
||||||
|
cancellationToken?: HostCancellationToken;
|
||||||
|
logger?: server.Logger;
|
||||||
|
useSingleInferredProject?: boolean;
|
||||||
|
typingsInstaller?: server.ITypingsInstaller;
|
||||||
|
eventHandler?: server.ProjectServiceEventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TestProjectService extends server.ProjectService {
|
||||||
|
constructor(host: server.ServerHost, logger: server.Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean,
|
||||||
|
typingsInstaller: server.ITypingsInstaller, eventHandler: server.ProjectServiceEventHandler, opts: Partial<server.ProjectServiceOptions> = {}) {
|
||||||
|
super({
|
||||||
|
host,
|
||||||
|
logger,
|
||||||
|
cancellationToken,
|
||||||
|
useSingleInferredProject,
|
||||||
|
useInferredProjectPerProjectRoot: false,
|
||||||
|
typingsInstaller,
|
||||||
|
typesMapLocation: customTypesMap.path,
|
||||||
|
eventHandler,
|
||||||
|
...opts
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNumberOfProjects(count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) {
|
||||||
|
checkNumberOfProjects(this, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function createProjectService(host: server.ServerHost, parameters: CreateProjectServiceParameters = {}, options?: Partial<server.ProjectServiceOptions>) {
|
||||||
|
const cancellationToken = parameters.cancellationToken || server.nullCancellationToken;
|
||||||
|
const logger = parameters.logger || createHasErrorMessageLogger().logger;
|
||||||
|
const useSingleInferredProject = parameters.useSingleInferredProject !== undefined ? parameters.useSingleInferredProject : false;
|
||||||
|
return new TestProjectService(host, logger, cancellationToken, useSingleInferredProject, parameters.typingsInstaller!, parameters.eventHandler!, options); // TODO: GH#18217
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) {
|
||||||
|
assert.equal(projectService.configuredProjects.size, expected, `expected ${expected} configured project(s)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkNumberOfExternalProjects(projectService: server.ProjectService, expected: number) {
|
||||||
|
assert.equal(projectService.externalProjects.length, expected, `expected ${expected} external project(s)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkNumberOfInferredProjects(projectService: server.ProjectService, expected: number) {
|
||||||
|
assert.equal(projectService.inferredProjects.length, expected, `expected ${expected} inferred project(s)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkNumberOfProjects(projectService: server.ProjectService, count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) {
|
||||||
|
checkNumberOfConfiguredProjects(projectService, count.configuredProjects || 0);
|
||||||
|
checkNumberOfExternalProjects(projectService, count.externalProjects || 0);
|
||||||
|
checkNumberOfInferredProjects(projectService, count.inferredProjects || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configuredProjectAt(projectService: server.ProjectService, index: number) {
|
||||||
|
const values = projectService.configuredProjects.values();
|
||||||
|
while (index > 0) {
|
||||||
|
values.next();
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
return values.next().value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkProjectActualFiles(project: server.Project, expectedFiles: ReadonlyArray<string>) {
|
||||||
|
checkArray(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
//function checkProjectRootFiles(project: server.Project, expectedFiles: ReadonlyArray<string>) {
|
||||||
|
// checkArray(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles);
|
||||||
|
//}
|
||||||
|
|
||||||
|
export function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) {
|
||||||
|
dir = normalizePath(dir);
|
||||||
|
const result: string[] = [];
|
||||||
|
forEachAncestorDirectory(dir, ancestor => {
|
||||||
|
if (mapAncestor(ancestor)) {
|
||||||
|
result.push(combinePaths(ancestor, path2));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRootsToWatchWithAncestorDirectory(dir: string, path2: string) {
|
||||||
|
return mapCombinedPathsInAncestor(dir, path2, ancestor => ancestor.split(directorySeparator).length > 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nodeModules = "node_modules";
|
||||||
|
//function getNodeModuleDirectories(dir: string) {
|
||||||
|
// return getRootsToWatchWithAncestorDirectory(dir, nodeModules);
|
||||||
|
//}
|
||||||
|
|
||||||
|
export const nodeModulesAtTypes = "node_modules/@types";
|
||||||
|
export function getTypeRootsFromLocation(currentDirectory: string) {
|
||||||
|
return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
//function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) {
|
||||||
|
// return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) {
|
||||||
|
// checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path));
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function protocolLocationFromSubstring(str: string, substring: string): protocol.Location {
|
||||||
|
// const start = str.indexOf(substring);
|
||||||
|
// Debug.assert(start !== -1);
|
||||||
|
// return protocolToLocation(str)(start);
|
||||||
|
//}
|
||||||
|
//function protocolToLocation(text: string): (pos: number) => protocol.Location {
|
||||||
|
// const lineStarts = computeLineStarts(text);
|
||||||
|
// return pos => {
|
||||||
|
// const x = computeLineAndCharacterOfPosition(lineStarts, pos);
|
||||||
|
// return { line: x.line + 1, offset: x.character + 1 };
|
||||||
|
// };
|
||||||
|
//}
|
||||||
|
//function protocolTextSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.TextSpan {
|
||||||
|
// const span = textSpanFromSubstring(str, substring, options);
|
||||||
|
// const toLocation = protocolToLocation(str);
|
||||||
|
// return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) };
|
||||||
|
//}
|
||||||
|
//function protocolRenameSpanFromSubstring(
|
||||||
|
// str: string,
|
||||||
|
// substring: string,
|
||||||
|
// options?: SpanFromSubstringOptions,
|
||||||
|
// prefixSuffixText?: { readonly prefixText?: string, readonly suffixText?: string },
|
||||||
|
//): protocol.RenameTextSpan {
|
||||||
|
// return { ...protocolTextSpanFromSubstring(str, substring, options), ...prefixSuffixText };
|
||||||
|
//}
|
||||||
|
//function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan {
|
||||||
|
// const start = nthIndexOf(str, substring, options ? options.index : 0);
|
||||||
|
// Debug.assert(start !== -1);
|
||||||
|
// return createTextSpan(start, substring.length);
|
||||||
|
//}
|
||||||
|
//function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs {
|
||||||
|
// return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) };
|
||||||
|
//}
|
||||||
|
//function protocolFileSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileSpan {
|
||||||
|
// return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring, options) };
|
||||||
|
//}
|
||||||
|
//function documentSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): DocumentSpan {
|
||||||
|
// return { fileName: file.path, textSpan: textSpanFromSubstring(file.content, substring, options) };
|
||||||
|
//}
|
||||||
|
//function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation {
|
||||||
|
// return documentSpanFromSubstring(file, substring, options);
|
||||||
|
//}
|
||||||
|
//interface SpanFromSubstringOptions {
|
||||||
|
// readonly index: number;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function nthIndexOf(str: string, substr: string, n: number): number {
|
||||||
|
// let index = -1;
|
||||||
|
// for (; n >= 0; n--) {
|
||||||
|
// index = str.indexOf(substr, index + 1);
|
||||||
|
// if (index === -1) return -1;
|
||||||
|
// }
|
||||||
|
// return index;
|
||||||
|
//}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test server cancellation token used to mock host token cancellation requests.
|
||||||
|
* The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls
|
||||||
|
* should be made before canceling the token. The id of the request to cancel should be set with
|
||||||
|
* setRequestToCancel();
|
||||||
|
*/
|
||||||
|
export class TestServerCancellationToken implements server.ServerCancellationToken {
|
||||||
|
private currentId: number | undefined = -1;
|
||||||
|
private requestToCancel = -1;
|
||||||
|
private isCancellationRequestedCount = 0;
|
||||||
|
|
||||||
|
constructor(private cancelAfterRequest = 0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
setRequest(requestId: number) {
|
||||||
|
this.currentId = requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRequestToCancel(requestId: number) {
|
||||||
|
this.resetToken();
|
||||||
|
this.requestToCancel = requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetRequest(requestId: number) {
|
||||||
|
assert.equal(requestId, this.currentId, "unexpected request id in cancellation");
|
||||||
|
this.currentId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
isCancellationRequested() {
|
||||||
|
this.isCancellationRequestedCount++;
|
||||||
|
// If the request id is the request to cancel and isCancellationRequestedCount
|
||||||
|
// has been met then cancel the request. Ex: cancel the request if it is a
|
||||||
|
// nav bar request & isCancellationRequested() has already been called three times.
|
||||||
|
return this.requestToCancel === this.currentId && this.isCancellationRequestedCount >= this.cancelAfterRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetToken() {
|
||||||
|
this.currentId = -1;
|
||||||
|
this.isCancellationRequestedCount = 0;
|
||||||
|
this.requestToCancel = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeSessionRequest<T>(command: string, args: T): protocol.Request {
|
||||||
|
return {
|
||||||
|
seq: 0,
|
||||||
|
type: "request",
|
||||||
|
command,
|
||||||
|
arguments: args
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function executeSessionRequest<TRequest extends protocol.Request, TResponse extends protocol.Response>(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): TResponse["body"] {
|
||||||
|
return session.executeCommand(makeSessionRequest(command, args)).response as TResponse["body"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function executeSessionRequestNoResponse<TRequest extends protocol.Request>(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): void {
|
||||||
|
session.executeCommand(makeSessionRequest(command, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openFilesForSession(files: ReadonlyArray<File | { readonly file: File | string, readonly projectRootPath: string }>, session: server.Session): void {
|
||||||
|
for (const file of files) {
|
||||||
|
session.executeCommand(makeSessionRequest<protocol.OpenRequestArgs>(CommandNames.Open,
|
||||||
|
"projectRootPath" in file ? { file: typeof file.file === "string" ? file.file : file.file.path, projectRootPath: file.projectRootPath } : { file: file.path }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeFilesForSession(files: ReadonlyArray<File>, session: server.Session): void {
|
||||||
|
for (const file of files) {
|
||||||
|
session.executeCommand(makeSessionRequest<protocol.FileRequestArgs>(CommandNames.Close, { file: file.path }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//interface ErrorInformation {
|
||||||
|
// diagnosticMessage: DiagnosticMessage;
|
||||||
|
// errorTextArguments?: string[];
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function getProtocolDiagnosticMessage({ diagnosticMessage, errorTextArguments = [] }: ErrorInformation) {
|
||||||
|
// return formatStringFromArgs(diagnosticMessage.message, errorTextArguments);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function verifyDiagnostics(actual: server.protocol.Diagnostic[], expected: ErrorInformation[]) {
|
||||||
|
// const expectedErrors = expected.map(getProtocolDiagnosticMessage);
|
||||||
|
// assert.deepEqual(actual.map(diag => flattenDiagnosticMessageText(diag.text, "\n")), expectedErrors);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function verifyNoDiagnostics(actual: server.protocol.Diagnostic[]) {
|
||||||
|
// verifyDiagnostics(actual, []);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function checkErrorMessage(session: TestSession, eventName: protocol.DiagnosticEventKind, diagnostics: protocol.DiagnosticEventBody, isMostRecent = false): void {
|
||||||
|
// checkNthEvent(session, server.toEvent(eventName, diagnostics), 0, isMostRecent);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray<string> = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}, relatedInformation?: protocol.DiagnosticRelatedInformation[]): protocol.Diagnostic {
|
||||||
|
// return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, relatedInformation, source: undefined };
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number, isMostRecent = true): void {
|
||||||
|
// checkNthEvent(session, server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, isMostRecent);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function checkProjectUpdatedInBackgroundEvent(session: TestSession, openFiles: string[]) {
|
||||||
|
// checkNthEvent(session, server.toEvent("projectsUpdatedInBackground", { openFiles }), 0, /*isMostRecent*/ true);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function checkNoDiagnosticEvents(session: TestSession) {
|
||||||
|
// for (const event of session.events) {
|
||||||
|
// assert.isFalse(event.event.endsWith("Diag"), JSON.stringify(event));
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function checkNthEvent(session: TestSession, expectedEvent: protocol.Event, index: number, isMostRecent: boolean) {
|
||||||
|
// const events = session.events;
|
||||||
|
// assert.deepEqual(events[index], expectedEvent, `Expected ${JSON.stringify(expectedEvent)} at ${index} in ${JSON.stringify(events)}`);
|
||||||
|
|
||||||
|
// const outputs = session.host.getOutput();
|
||||||
|
// assert.equal(outputs[index], server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, session.host.newLine));
|
||||||
|
|
||||||
|
// if (isMostRecent) {
|
||||||
|
// assert.strictEqual(events.length, index + 1, JSON.stringify(events));
|
||||||
|
// assert.strictEqual(outputs.length, index + 1, JSON.stringify(outputs));
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem {
|
||||||
|
// return {
|
||||||
|
// ...protocolFileSpanFromSubstring(file, text, options),
|
||||||
|
// isDefinition,
|
||||||
|
// isWriteAccess: isDefinition,
|
||||||
|
// lineText,
|
||||||
|
// };
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function makeReferenceEntry(file: File, isDefinition: boolean, text: string, options?: SpanFromSubstringOptions): ReferenceEntry {
|
||||||
|
// return {
|
||||||
|
// ...documentSpanFromSubstring(file, text, options),
|
||||||
|
// isDefinition,
|
||||||
|
// isWriteAccess: isDefinition,
|
||||||
|
// isInString: undefined,
|
||||||
|
// };
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: ReadonlyArray<File>): void {
|
||||||
|
// openFilesForSession([file], session);
|
||||||
|
// const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false));
|
||||||
|
// const program = project.getCurrentProgram()!;
|
||||||
|
// const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true);
|
||||||
|
// closeFilesForSession([file], session);
|
||||||
|
|
||||||
|
// Debug.assert(!output.emitSkipped);
|
||||||
|
// assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false })));
|
||||||
|
//}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,4 @@
|
||||||
namespace ts.projectSystem {
|
namespace ts.projectSystem {
|
||||||
import TI = server.typingsInstaller;
|
|
||||||
import validatePackageName = JsTyping.validatePackageName;
|
import validatePackageName = JsTyping.validatePackageName;
|
||||||
import PackageNameValidationResult = JsTyping.PackageNameValidationResult;
|
import PackageNameValidationResult = JsTyping.PackageNameValidationResult;
|
||||||
|
|
||||||
|
|
40
src/testRunner/unittests/watchApi.ts
Normal file
40
src/testRunner/unittests/watchApi.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
namespace ts.tscWatch {
|
||||||
|
describe("watchAPI:: tsc-watch with custom module resolution", () => {
|
||||||
|
const projectRoot = "/user/username/projects/project";
|
||||||
|
const configFileJson: any = {
|
||||||
|
compilerOptions: { module: "commonjs", resolveJsonModule: true },
|
||||||
|
files: ["index.ts"]
|
||||||
|
};
|
||||||
|
const mainFile: File = {
|
||||||
|
path: `${projectRoot}/index.ts`,
|
||||||
|
content: "import settings from './settings.json';"
|
||||||
|
};
|
||||||
|
const config: File = {
|
||||||
|
path: `${projectRoot}/tsconfig.json`,
|
||||||
|
content: JSON.stringify(configFileJson)
|
||||||
|
};
|
||||||
|
const settingsJson: File = {
|
||||||
|
path: `${projectRoot}/settings.json`,
|
||||||
|
content: JSON.stringify({ content: "Print this" })
|
||||||
|
};
|
||||||
|
|
||||||
|
it("verify that module resolution with json extension works when returned without extension", () => {
|
||||||
|
const files = [libFile, mainFile, config, settingsJson];
|
||||||
|
const host = createWatchedSystem(files, { currentDirectory: projectRoot });
|
||||||
|
const compilerHost = createWatchCompilerHostOfConfigFile(config.path, {}, host);
|
||||||
|
const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path);
|
||||||
|
compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => {
|
||||||
|
const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost);
|
||||||
|
const resolvedModule = result.resolvedModule!;
|
||||||
|
return {
|
||||||
|
resolvedFileName: resolvedModule.resolvedFileName,
|
||||||
|
isExternalLibraryImport: resolvedModule.isExternalLibraryImport,
|
||||||
|
originalFileName: resolvedModule.originalPath,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const watch = createWatchProgram(compilerHost);
|
||||||
|
const program = watch.getCurrentProgram().getProgram();
|
||||||
|
checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -262,5 +262,55 @@ namespace ts {
|
||||||
verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling);
|
verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => {
|
||||||
|
function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) {
|
||||||
|
const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`;
|
||||||
|
const configFile: File = {
|
||||||
|
path: root + "project/tsconfig.json",
|
||||||
|
content: "{}"
|
||||||
|
};
|
||||||
|
const file1: File = {
|
||||||
|
path: root + "project/file1.ts",
|
||||||
|
content: "let x = 10;"
|
||||||
|
};
|
||||||
|
const file2: File = {
|
||||||
|
path: root + "project/file2.ts",
|
||||||
|
content: "let y = 10;"
|
||||||
|
};
|
||||||
|
const files = [configFile, file1, file2, libFile];
|
||||||
|
const host = createServerHost(files, { useWindowsStylePaths: true });
|
||||||
|
const projectService = createProjectService(host);
|
||||||
|
projectService.openClientFile(file1.path);
|
||||||
|
const project = projectService.configuredProjects.get(configFile.path)!;
|
||||||
|
assert.isDefined(project);
|
||||||
|
const winsowsStyleLibFilePath = "c:/" + libFile.path.substring(1);
|
||||||
|
checkProjectActualFiles(project, files.map(f => f === libFile ? winsowsStyleLibFilePath : f.path));
|
||||||
|
checkWatchedFiles(host, mapDefined(files, f => f === libFile ? winsowsStyleLibFilePath : f === file1 ? undefined : f.path));
|
||||||
|
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||||
|
checkWatchedDirectories(host, [
|
||||||
|
root + "project",
|
||||||
|
root + "project/node_modules/@types"
|
||||||
|
].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyRootedDirectoryWatch(rootedPath: string) {
|
||||||
|
it("When project is in rootFolder of style c:/", () => {
|
||||||
|
verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("When files at some folder other than root", () => {
|
||||||
|
verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("for rootFolder of style c:/", () => {
|
||||||
|
verifyRootedDirectoryWatch("c:/");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("for rootFolder of style c:/users/username", () => {
|
||||||
|
verifyRootedDirectoryWatch("c:/users/username/");
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue