diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 4855ec9a702a4..564cb4db2c409 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -317,9 +317,13 @@ namespace ts.moduleSpecifiers { // If the module could be imported by a directory name, use that directory's name const moduleSpecifier = packageNameOnly ? moduleFileName : getDirectoryOrExtensionlessFileName(moduleFileName); + const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); // Get a path that's relative to node_modules or the importing file's path // if node_modules folder is in this folder or any of its parent folders, no need to keep it. - if (!startsWith(sourceDirectory, getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)))) return undefined; + const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)); + if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) { + return undefined; + } // If the module was found in @types, get the actual Node package name const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 64415c08d2e9f..15a56361b8ae5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6149,6 +6149,8 @@ namespace ts { readFile?(path: string): string | undefined; /* @internal */ getProbableSymlinks?(files: readonly SourceFile[]): ReadonlyMap; + /* @internal */ + getGlobalTypingsCacheLocation?(): string | undefined; } // Note: this used to be deprecated in our public API, but is still used internally diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 67fc45c480700..5ad82483c6ec8 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -220,6 +220,10 @@ namespace Harness.LanguageService { return !!this.typesRegistry && this.typesRegistry.has(name); } + getGlobalTypingsCacheLocation() { + return "/Library/Caches/typescript"; + } + installPackage = ts.notImplemented; getCompilationSettings() { return this.settings; } diff --git a/src/server/project.ts b/src/server/project.ts index 3f8dfa07c98c4..c98e7660b3838 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -307,6 +307,11 @@ namespace ts.server { return this.typingsCache.installPackage({ ...options, projectName: this.projectName, projectRootPath: this.toPath(this.currentDirectory) }); } + /*@internal*/ + getGlobalTypingsCacheLocation() { + return this.getGlobalCache(); + } + private get typingsCache(): TypingsCache { return this.projectService.typingsCache; } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 44adb40c554d2..a43aa7f6f9d3c 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -631,6 +631,7 @@ namespace ts.codefix { let filteredCount = 0; const packageJson = filterByPackageJson && createAutoImportFilter(from, program, host); const allSourceFiles = program.getSourceFiles(); + const globalTypingsCache = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); forEachExternalModule(program.getTypeChecker(), allSourceFiles, (module, sourceFile) => { if (sourceFile === undefined) { if (!packageJson || packageJson.allowsImportingAmbientModule(module, allSourceFiles)) { @@ -640,7 +641,10 @@ namespace ts.codefix { filteredCount++; } } - else if (sourceFile && sourceFile !== from && isImportablePath(from.fileName, sourceFile.fileName)) { + else if (sourceFile && + sourceFile !== from && + isImportablePath(from.fileName, sourceFile.fileName, hostGetCanonicalFileName(host), globalTypingsCache) + ) { if (!packageJson || packageJson.allowsImportingSourceFile(sourceFile, allSourceFiles)) { cb(module); } @@ -669,10 +673,13 @@ namespace ts.codefix { * Don't include something from a `node_modules` that isn't actually reachable by a global import. * A relative import to node_modules is usually a bad idea. */ - function isImportablePath(fromPath: string, toPath: string): boolean { + function isImportablePath(fromPath: string, toPath: string, getCanonicalFileName: GetCanonicalFileName, globalCachePath?: string): boolean { // If it's in a `node_modules` but is not reachable from here via a global import, don't bother. const toNodeModules = forEachAncestorDirectory(toPath, ancestor => getBaseFileName(ancestor) === "node_modules" ? ancestor : undefined); - return toNodeModules === undefined || startsWith(fromPath, getDirectoryPath(toNodeModules)); + const toNodeModulesParent = toNodeModules && getDirectoryPath(getCanonicalFileName(toNodeModules)); + return toNodeModulesParent === undefined + || startsWith(getCanonicalFileName(fromPath), toNodeModulesParent) + || (!!globalCachePath && startsWith(getCanonicalFileName(globalCachePath), toNodeModulesParent)); } export function moduleSymbolToValidIdentifier(moduleSymbol: Symbol, target: ScriptTarget): string { @@ -718,6 +725,7 @@ namespace ts.codefix { readFile: maybeBind(host, host.readFile), useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), getProbableSymlinks: maybeBind(host, host.getProbableSymlinks) || program.getProbableSymlinks, + getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation), }; let usesNodeCoreModules: boolean | undefined; diff --git a/src/services/types.ts b/src/services/types.ts index 65544d5677d62..811fa40c58081 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -239,6 +239,8 @@ namespace ts { resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[]; /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; + /* @internal */ + getGlobalTypingsCacheLocation?(): string | undefined; /* * Required for full import and type reference completions. diff --git a/tests/cases/fourslash/importFixesGlobalTypingsCache.ts b/tests/cases/fourslash/importFixesGlobalTypingsCache.ts new file mode 100644 index 0000000000000..21c8ddf0ebe05 --- /dev/null +++ b/tests/cases/fourslash/importFixesGlobalTypingsCache.ts @@ -0,0 +1,18 @@ +/// + +// @Filename: /project/tsconfig.json +//// { "compilerOptions": { "allowJs": true, "checkJs": true } } + +// @Filename: /Library/Caches/typescript/node_modules/@types/react-router-dom/package.json +//// { "name": "react-router-dom" } + +// @Filename: /Library/Caches/typescript/node_modules/@types/react-router-dom/index.d.ts +////export class BrowserRouter {} + +// @Filename: /project/index.js +////BrowserRouter/**/ + +goTo.file("/project/index.js"); +verify.importFixAtPosition([`import { BrowserRouter } from "react-router-dom"; + +BrowserRouter`]);