mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-16 05:58:32 -06:00
Add tests for module resolution order and reuse
This commit is contained in:
parent
0b248d5e29
commit
403b7d8604
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user