Add tests for module resolution order and reuse

This commit is contained in:
Sheetal Nandi 2018-02-22 09:17:00 -08:00
parent 0b248d5e29
commit 403b7d8604

View File

@ -145,6 +145,12 @@ namespace ts.projectSystem {
return map;
}
function createHostModuleResolutionTrace(host: TestServerHost & ModuleResolutionHost) {
const resolutionTrace: string[] = [];
host.trace = resolutionTrace.push.bind(resolutionTrace);
return resolutionTrace;
}
export function toExternalFile(fileName: string): protocol.ExternalFile {
return { fileName };
}
@ -3201,8 +3207,7 @@ namespace ts.projectSystem {
content: "export let x = 1"
};
const host: TestServerHost & ModuleResolutionHost = createServerHost([file1, lib]);
const resolutionTrace: string[] = [];
host.trace = resolutionTrace.push.bind(resolutionTrace);
const resolutionTrace = createHostModuleResolutionTrace(host);
const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller("/a/cache", /*throttleLimit*/5, host) });
projectService.setCompilerOptionsForInferredProjects({ traceResolution: true, allowJs: true });
@ -6971,4 +6976,338 @@ namespace ts.projectSystem {
assert.deepEqual(diagnostics, []);
});
});
describe("tsserverProjectSystem module resolution caching", () => {
const projectLocation = "/user/username/projects/myproject";
const configFile: FileOrFolder = {
path: `${projectLocation}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { traceResolution: true } })
};
function getModules(module1Path: string, module2Path: string) {
const module1: FileOrFolder = {
path: module1Path,
content: `export function module1() {}`
};
const module2: FileOrFolder = {
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: FileOrFolder, 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: FileOrFolder, moduleName: string, useNodeModules: boolean) {
let foundModule = false;
forEachAncestorDirectory(dirPath, dirPath => {
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: FileOrFolder, moduleName: string) {
expectedTrace.push(
`======== Resolving module '${moduleName}' from '${file.path}'. ========`,
`Module resolution kind is not specified, using 'NodeJs'.`
);
}
function getExpectedResolutionTraceFooter(expectedTrace: string[], module: FileOrFolder, moduleName: string, addRealPathTrace: boolean) {
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: FileOrFolder, module: FileOrFolder, 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: FileOrFolder, module: FileOrFolder, 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 getExpectedReusingResolutionFromOldProgram(file: FileOrFolder, moduleName: string) {
return `Reusing resolution of module '${moduleName}' to file '${file.path}' from old program.`;
}
function verifyWatchesWithConfigFile(host: TestServerHost, files: FileOrFolder[], openFile: FileOrFolder) {
checkWatchedFiles(host, mapDefined(files, f => f === openFile ? undefined : f.path));
checkWatchedDirectories(host, [], /*recursive*/ false);
const configDirectory = getDirectoryPath(configFile.path);
checkWatchedDirectories(host, [configDirectory, `${configDirectory}/${nodeModulesAtTypes}`], /*recursive*/ true);
}
describe("from files in same folder", () => {
function getFiles(fileContent: string) {
const file1: FileOrFolder = {
path: `${projectLocation}/src/file1.ts`,
content: fileContent
};
const file2: FileOrFolder = {
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 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);
file1.content += fileContent;
file2.content += fileContent;
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
verifyTrace(resolutionTrace, [
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
]);
verifyWatchesWithConfigFile(host, files, file1);
});
});
describe("from files in different folders", () => {
function getFiles(fileContent1: string, fileContent2 = fileContent1, fileContent3 = fileContent1, fileContent4 = fileContent1) {
const file1: FileOrFolder = {
path: `${projectLocation}/product/src/file1.ts`,
content: fileContent1
};
const file2: FileOrFolder = {
path: `${projectLocation}/product/src/feature/file2.ts`,
content: fileContent2
};
const file3: FileOrFolder = {
path: `${projectLocation}/product/test/src/file3.ts`,
content: fileContent3
};
const file4: FileOrFolder = {
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 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);
getExpectedNonRelativeModuleResolutionTrace(host, file2, module1, module1Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file2, module2, module2Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file4, module1, module1Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file4, module2, module2Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file3, module1, module1Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file3, module2, module2Name, expectedTrace);
verifyTrace(resolutionTrace, expectedTrace);
verifyWatchesWithConfigFile(host, files, file1);
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);
});
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);
getExpectedNonRelativeModuleResolutionTrace(host, file2, module1, module1Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file2, module2, module2Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file4, module1, module1Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file4, module2, module2Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file3, module1, module1Name, expectedTrace);
getExpectedNonRelativeModuleResolutionTrace(host, file3, module2, module2Name, expectedTrace);
verifyTrace(resolutionTrace, expectedTrace);
const currentDirectory = getDirectoryPath(file1.path);
const watchedFiles = mapDefined(files, f => f === file1 ? undefined : f.path);
forEachAncestorDirectory(currentDirectory, d => {
watchedFiles.push(combinePaths(d, "tsconfig.json"), combinePaths(d, "jsconfig.json"));
});
const watchedRecursiveDirectories = getTypeRootsFromLocation(currentDirectory).concat([
currentDirectory, `${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);
}
});
});
});
}