diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 962a31d5023..ba69e4db966 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3296,7 +3296,7 @@ "category": "Message", "code": 6146 }, - "Resolution for module '{0}' was found in cache.": { + "Resolution for module '{0}' was found in cache from location '{1}'.": { "category": "Message", "code": 6147 }, diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index ddb0f5cbb5d..de59e640192 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -335,8 +335,20 @@ namespace ts { } export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache { - const directoryToModuleNameMap = createMap>(); - const moduleNameToDirectoryMap = createMap(); + return createModuleResolutionCacheWithMaps( + createMap>(), + createMap(), + currentDirectory, + getCanonicalFileName + ); + } + + /*@internal*/ + export function createModuleResolutionCacheWithMaps( + directoryToModuleNameMap: Map>, + moduleNameToDirectoryMap: Map, + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache { return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName }; @@ -445,7 +457,7 @@ namespace ts { if (result) { if (traceEnabled) { - trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache, moduleName); + trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); } } else { @@ -1188,7 +1200,7 @@ namespace ts { const result = cache && cache.get(containingDirectory); if (result) { if (traceEnabled) { - trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache, moduleName); + trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); } return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } }; } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index e907d2d62b7..01c7bc59fdd 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -28,6 +28,7 @@ namespace ts { interface ResolutionWithFailedLookupLocations { readonly failedLookupLocations: ReadonlyArray; isInvalidated?: boolean; + refCount?: number; } interface ResolutionWithResolvedFileName { @@ -42,6 +43,7 @@ namespace ts { export interface ResolutionCacheHost extends ModuleResolutionHost { toPath(fileName: string): Path; + getCanonicalFileName: GetCanonicalFileName; getCompilationSettings(): CompilerOptions; watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; onInvalidatedResolution(): void; @@ -78,18 +80,25 @@ namespace ts { let filesWithInvalidatedResolutions: Map | undefined; let allFilesHaveInvalidatedResolution = false; + const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); + const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); + // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. // The key in the map is source file's path. // The values are Map of resolutions with key being name lookedup. const resolvedModuleNames = createMap>(); const perDirectoryResolvedModuleNames = createMap>(); + const nonRelaticeModuleNameCache = createMap(); + const moduleResolutionCache = createModuleResolutionCacheWithMaps( + perDirectoryResolvedModuleNames, + nonRelaticeModuleNameCache, + getCurrentDirectory(), + resolutionHost.getCanonicalFileName + ); const resolvedTypeReferenceDirectives = createMap>(); const perDirectoryResolvedTypeReferenceDirectives = createMap>(); - const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); - const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); - /** * These are the extensions that failed lookup files will have by default, * any other extension of failed lookup will be store that path in custom failed lookup path @@ -173,6 +182,7 @@ namespace ts { function clearPerDirectoryResolutions() { perDirectoryResolvedModuleNames.clear(); + nonRelaticeModuleNameCache.clear(); perDirectoryResolvedTypeReferenceDirectives.clear(); } @@ -189,7 +199,7 @@ namespace ts { } function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { - const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host); + const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache); // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts if (!resolutionHost.getGlobalCache) { return primaryResult; @@ -248,17 +258,11 @@ namespace ts { perDirectoryResolution.set(name, resolution); } resolutionsInFile.set(name, resolution); - if (resolution.failedLookupLocations) { - if (existingResolution && existingResolution.failedLookupLocations) { - watchAndStopWatchDiffFailedLookupLocations(resolution, existingResolution); - } - else { - watchFailedLookupLocationOfResolution(resolution, 0); - } - } - else if (existingResolution) { + watchFailedLookupLocationOfResolution(resolution); + if (existingResolution) { stopWatchFailedLookupLocationOfResolution(existingResolution); } + if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { filesWithChangedSetOfUnresolvedImports.push(path); // reset log changes to avoid recording the same file multiple times @@ -390,80 +394,98 @@ namespace ts { return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); } - function watchAndStopWatchDiffFailedLookupLocations(resolution: ResolutionWithFailedLookupLocations, existingResolution: ResolutionWithFailedLookupLocations) { - const failedLookupLocations = resolution.failedLookupLocations; - const existingFailedLookupLocations = existingResolution.failedLookupLocations; - for (let index = 0; index < failedLookupLocations.length; index++) { - if (index === existingFailedLookupLocations.length) { - // Additional failed lookup locations, watch from this index - watchFailedLookupLocationOfResolution(resolution, index); - return; - } - else if (failedLookupLocations[index] !== existingFailedLookupLocations[index]) { - // Different failed lookup locations, - // Watch new resolution failed lookup locations from this index and - // stop watching existing resolutions from this index - watchFailedLookupLocationOfResolution(resolution, index); - stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, index); - return; + function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { + // No need to set the resolution refCount + if (!resolution.failedLookupLocations || !resolution.failedLookupLocations.length) { + return; + } + + if (resolution.refCount !== undefined) { + resolution.refCount++; + return; + } + + resolution.refCount = 1; + const { failedLookupLocations } = resolution; + let setAtRoot = false; + for (const failedLookupLocation of failedLookupLocations) { + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const { dir, dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + if (!ignore) { + // If the failed lookup location path is not one of the supported extensions, + // store it in the custom path + if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { + const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; + customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); + } + if (dirPath === rootPath) { + setAtRoot = true; + } + else { + setDirectoryWatcher(dir, dirPath); + } } } - // All new failed lookup locations are already watched (and are same), - // Stop watching failed lookup locations of existing resolution after failed lookup locations length - stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, failedLookupLocations.length); + if (setAtRoot) { + setDirectoryWatcher(rootDir, rootPath); + } } - function watchFailedLookupLocationOfResolution({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) { - for (let i = startIndex; i < failedLookupLocations.length; i++) { - const failedLookupLocation = failedLookupLocations[i]; - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - // If the failed lookup location path is not one of the supported extensions, - // store it in the custom path - if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { - const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; - customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); - } - const { dir, dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - if (!ignore) { - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); - if (dirWatcher) { - dirWatcher.refCount++; - } - else { - directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 }); - } - } + function setDirectoryWatcher(dir: string, dirPath: Path) { + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + if (dirWatcher) { + dirWatcher.refCount++; + } + else { + directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 }); } } function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { - if (resolution.failedLookupLocations) { - stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0); + if (!resolution.failedLookupLocations || !resolution.failedLookupLocations.length) { + return; + } + + resolution.refCount!--; + if (resolution.refCount) { + return; + } + + const { failedLookupLocations } = resolution; + let removeAtRoot = false; + for (const failedLookupLocation of failedLookupLocations) { + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const { dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + if (!ignore) { + const refCount = customFailedLookupPaths.get(failedLookupLocationPath); + if (refCount) { + if (refCount === 1) { + customFailedLookupPaths.delete(failedLookupLocationPath); + } + else { + Debug.assert(refCount > 1); + customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); + } + } + + if (dirPath === rootPath) { + removeAtRoot = true; + } + else { + removeDirectoryWatcher(dirPath); + } + } + } + if (removeAtRoot) { + removeDirectoryWatcher(rootPath); } } - function stopWatchFailedLookupLocationOfResolutionFrom({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) { - for (let i = startIndex; i < failedLookupLocations.length; i++) { - const failedLookupLocation = failedLookupLocations[i]; - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - const refCount = customFailedLookupPaths.get(failedLookupLocationPath); - if (refCount) { - if (refCount === 1) { - customFailedLookupPaths.delete(failedLookupLocationPath); - } - else { - Debug.assert(refCount > 1); - customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); - } - } - const { dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - if (!ignore) { - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); - // Do not close the watcher yet since it might be needed by other failed lookup locations. - dirWatcher.refCount--; - } - } + function removeDirectoryWatcher(dirPath: string) { + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + // Do not close the watcher yet since it might be needed by other failed lookup locations. + dirWatcher.refCount--; } function createDirectoryWatcher(directory: string, dirPath: Path) { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 66f224d431e..f596896aacb 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -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 }; } @@ -3210,8 +3216,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 }); @@ -7043,4 +7048,353 @@ 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, 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: 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, 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: 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 getExpectedNonRelativeModuleResolutionFromCacheTrace(host: TestServerHost, file: FileOrFolder, module: FileOrFolder, 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*/ true, /*ignoreModuleFileFound*/ 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); + 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); + + 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); + 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 ? 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); + } + }); + }); + }); } diff --git a/src/server/project.ts b/src/server/project.ts index dc949b4b221..bfd039f26fb 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -210,6 +210,9 @@ namespace ts.server { /*@internal*/ public directoryStructureHost: DirectoryStructureHost; + /*@internal*/ + public readonly getCanonicalFileName: GetCanonicalFileName; + /*@internal*/ constructor( /*@internal*/readonly projectName: string, @@ -224,6 +227,7 @@ namespace ts.server { currentDirectory: string | undefined) { this.directoryStructureHost = directoryStructureHost; this.currentDirectory = this.projectService.getNormalizedAbsolutePath(currentDirectory || ""); + this.getCanonicalFileName = this.projectService.toCanonicalFileName; this.cancellationToken = new ThrottledCancellationToken(this.projectService.cancellationToken, this.projectService.throttleWaitMilliseconds); if (!this.compilerOptions) { @@ -238,7 +242,10 @@ namespace ts.server { this.setInternalCompilerOptionsForEmittingJsFiles(); const host = this.projectService.host; - if (host.trace) { + if (this.projectService.logger.loggingEnabled()) { + this.trace = s => this.writeLog(s); + } + else if (host.trace) { this.trace = s => host.trace(s); } diff --git a/tests/baselines/reference/cacheResolutions.trace.json b/tests/baselines/reference/cacheResolutions.trace.json index bf3653b489a..e23322299e6 100644 --- a/tests/baselines/reference/cacheResolutions.trace.json +++ b/tests/baselines/reference/cacheResolutions.trace.json @@ -27,9 +27,9 @@ "File '/tslib.jsx' does not exist.", "======== Module name 'tslib' was not resolved. ========", "======== Resolving module 'tslib' from '/a/b/c/lib1.ts'. ========", - "Resolution for module 'tslib' was found in cache.", + "Resolution for module 'tslib' was found in cache from location '/a/b/c'.", "======== Module name 'tslib' was not resolved. ========", "======== Resolving module 'tslib' from '/a/b/c/lib2.ts'. ========", - "Resolution for module 'tslib' was found in cache.", + "Resolution for module 'tslib' was found in cache from location '/a/b/c'.", "======== Module name 'tslib' was not resolved. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/cachedModuleResolution1.trace.json b/tests/baselines/reference/cachedModuleResolution1.trace.json index c9f40c56796..48997f9784d 100644 --- a/tests/baselines/reference/cachedModuleResolution1.trace.json +++ b/tests/baselines/reference/cachedModuleResolution1.trace.json @@ -13,7 +13,7 @@ "======== Resolving module 'foo' from '/a/b/c/lib.ts'. ========", "Explicitly specified module resolution kind: 'NodeJs'.", "Loading module 'foo' from 'node_modules' folder, target file type 'TypeScript'.", - "Resolution for module 'foo' was found in cache.", + "Resolution for module 'foo' was found in cache from location '/a/b/c'.", "Resolving real path for '/a/b/node_modules/foo.d.ts', result '/a/b/node_modules/foo.d.ts'.", "======== Module name 'foo' was successfully resolved to '/a/b/node_modules/foo.d.ts'. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/cachedModuleResolution2.trace.json b/tests/baselines/reference/cachedModuleResolution2.trace.json index a7fc2414f03..197d3a708e3 100644 --- a/tests/baselines/reference/cachedModuleResolution2.trace.json +++ b/tests/baselines/reference/cachedModuleResolution2.trace.json @@ -13,7 +13,7 @@ "Loading module 'foo' from 'node_modules' folder, target file type 'TypeScript'.", "Directory '/a/b/c/d/e/node_modules' does not exist, skipping all lookups in it.", "Directory '/a/b/c/d/node_modules' does not exist, skipping all lookups in it.", - "Resolution for module 'foo' was found in cache.", + "Resolution for module 'foo' was found in cache from location '/a/b/c'.", "Resolving real path for '/a/b/node_modules/foo.d.ts', result '/a/b/node_modules/foo.d.ts'.", "======== Module name 'foo' was successfully resolved to '/a/b/node_modules/foo.d.ts'. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/cachedModuleResolution3.trace.json b/tests/baselines/reference/cachedModuleResolution3.trace.json index 6cbab2e0796..2f401df9ef5 100644 --- a/tests/baselines/reference/cachedModuleResolution3.trace.json +++ b/tests/baselines/reference/cachedModuleResolution3.trace.json @@ -16,6 +16,6 @@ "======== Module name 'foo' was successfully resolved to '/a/b/foo.d.ts'. ========", "======== Resolving module 'foo' from '/a/b/c/lib.ts'. ========", "Explicitly specified module resolution kind: 'Classic'.", - "Resolution for module 'foo' was found in cache.", + "Resolution for module 'foo' was found in cache from location '/a/b/c'.", "======== Module name 'foo' was successfully resolved to '/a/b/foo.d.ts'. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/cachedModuleResolution4.trace.json b/tests/baselines/reference/cachedModuleResolution4.trace.json index c100c0c3814..7771f99ca9f 100644 --- a/tests/baselines/reference/cachedModuleResolution4.trace.json +++ b/tests/baselines/reference/cachedModuleResolution4.trace.json @@ -16,6 +16,6 @@ "File '/a/b/c/d/foo.ts' does not exist.", "File '/a/b/c/d/foo.tsx' does not exist.", "File '/a/b/c/d/foo.d.ts' does not exist.", - "Resolution for module 'foo' was found in cache.", + "Resolution for module 'foo' was found in cache from location '/a/b/c'.", "======== Module name 'foo' was successfully resolved to '/a/b/foo.d.ts'. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/cachedModuleResolution5.trace.json b/tests/baselines/reference/cachedModuleResolution5.trace.json index b09cc7a35be..4438192b155 100644 --- a/tests/baselines/reference/cachedModuleResolution5.trace.json +++ b/tests/baselines/reference/cachedModuleResolution5.trace.json @@ -13,7 +13,7 @@ "======== Resolving module 'foo' from '/a/b/lib.ts'. ========", "Explicitly specified module resolution kind: 'NodeJs'.", "Loading module 'foo' from 'node_modules' folder, target file type 'TypeScript'.", - "Resolution for module 'foo' was found in cache.", + "Resolution for module 'foo' was found in cache from location '/a/b'.", "Resolving real path for '/a/b/node_modules/foo.d.ts', result '/a/b/node_modules/foo.d.ts'.", "======== Module name 'foo' was successfully resolved to '/a/b/node_modules/foo.d.ts'. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/cachedModuleResolution6.trace.json b/tests/baselines/reference/cachedModuleResolution6.trace.json index 50c1c15b3e1..5d43bb94dd2 100644 --- a/tests/baselines/reference/cachedModuleResolution6.trace.json +++ b/tests/baselines/reference/cachedModuleResolution6.trace.json @@ -19,6 +19,6 @@ "======== Resolving module 'foo' from '/a/b/c/lib.ts'. ========", "Explicitly specified module resolution kind: 'NodeJs'.", "Loading module 'foo' from 'node_modules' folder, target file type 'TypeScript'.", - "Resolution for module 'foo' was found in cache.", + "Resolution for module 'foo' was found in cache from location '/a/b/c'.", "======== Module name 'foo' was not resolved. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/cachedModuleResolution7.trace.json b/tests/baselines/reference/cachedModuleResolution7.trace.json index fa1db9d115e..49fc1e75b0c 100644 --- a/tests/baselines/reference/cachedModuleResolution7.trace.json +++ b/tests/baselines/reference/cachedModuleResolution7.trace.json @@ -17,6 +17,6 @@ "Loading module 'foo' from 'node_modules' folder, target file type 'TypeScript'.", "Directory '/a/b/c/d/e/node_modules' does not exist, skipping all lookups in it.", "Directory '/a/b/c/d/node_modules' does not exist, skipping all lookups in it.", - "Resolution for module 'foo' was found in cache.", + "Resolution for module 'foo' was found in cache from location '/a/b/c'.", "======== Module name 'foo' was not resolved. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/cachedModuleResolution8.trace.json b/tests/baselines/reference/cachedModuleResolution8.trace.json index e5241278349..9b7c37c8568 100644 --- a/tests/baselines/reference/cachedModuleResolution8.trace.json +++ b/tests/baselines/reference/cachedModuleResolution8.trace.json @@ -40,6 +40,6 @@ "======== Module name 'foo' was not resolved. ========", "======== Resolving module 'foo' from '/a/b/c/lib.ts'. ========", "Explicitly specified module resolution kind: 'Classic'.", - "Resolution for module 'foo' was found in cache.", + "Resolution for module 'foo' was found in cache from location '/a/b/c'.", "======== Module name 'foo' was not resolved. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/cachedModuleResolution9.trace.json b/tests/baselines/reference/cachedModuleResolution9.trace.json index bd148cc7ee2..b07e32abf5e 100644 --- a/tests/baselines/reference/cachedModuleResolution9.trace.json +++ b/tests/baselines/reference/cachedModuleResolution9.trace.json @@ -34,6 +34,6 @@ "File '/a/b/c/d/foo.ts' does not exist.", "File '/a/b/c/d/foo.tsx' does not exist.", "File '/a/b/c/d/foo.d.ts' does not exist.", - "Resolution for module 'foo' was found in cache.", + "Resolution for module 'foo' was found in cache from location '/a/b/c'.", "======== Module name 'foo' was not resolved. ========" ] \ No newline at end of file diff --git a/tests/baselines/reference/typeReferenceDirectives12.trace.json b/tests/baselines/reference/typeReferenceDirectives12.trace.json index 62a3b31fa1c..12f8d938c1d 100644 --- a/tests/baselines/reference/typeReferenceDirectives12.trace.json +++ b/tests/baselines/reference/typeReferenceDirectives12.trace.json @@ -16,7 +16,7 @@ "Resolving real path for '/types/lib/index.d.ts', result '/types/lib/index.d.ts'.", "======== Type reference directive 'lib' was successfully resolved to '/types/lib/index.d.ts', primary: true. ========", "======== Resolving module './main' from '/mod1.ts'. ========", - "Resolution for module './main' was found in cache.", + "Resolution for module './main' was found in cache from location '/'.", "======== Module name './main' was successfully resolved to '/main.ts'. ========", "======== Resolving type reference directive 'lib', containing file '/__inferred type names__.ts', root directory '/types'. ========", "Resolving with primary search path '/types'.", diff --git a/tests/baselines/reference/typeReferenceDirectives9.trace.json b/tests/baselines/reference/typeReferenceDirectives9.trace.json index 62a3b31fa1c..12f8d938c1d 100644 --- a/tests/baselines/reference/typeReferenceDirectives9.trace.json +++ b/tests/baselines/reference/typeReferenceDirectives9.trace.json @@ -16,7 +16,7 @@ "Resolving real path for '/types/lib/index.d.ts', result '/types/lib/index.d.ts'.", "======== Type reference directive 'lib' was successfully resolved to '/types/lib/index.d.ts', primary: true. ========", "======== Resolving module './main' from '/mod1.ts'. ========", - "Resolution for module './main' was found in cache.", + "Resolution for module './main' was found in cache from location '/'.", "======== Module name './main' was successfully resolved to '/main.ts'. ========", "======== Resolving type reference directive 'lib', containing file '/__inferred type names__.ts', root directory '/types'. ========", "Resolving with primary search path '/types'.",