diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index c24b84b52..ec26770b8 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -48,6 +48,7 @@ const serviceSizeMap = new FileMap(); const configWatchers = new FileMap(); const extendedConfigWatchers = new FileMap(); const extendedConfigToTsConfigPath = new FileMap(); +const configFileForOpenFiles = new FileMap(); const pendingReloads = new FileSet(); /** @@ -81,24 +82,32 @@ export async function getService( docContext.tsSystem.useCaseSensitiveFileNames ); - const tsconfigPath = findTsConfigPath( - path, - workspaceUris, - docContext.tsSystem.fileExists, - getCanonicalFileName - ); + const tsconfigPath = + configFileForOpenFiles.get(path) ?? + findTsConfigPath(path, workspaceUris, docContext.tsSystem.fileExists, getCanonicalFileName); if (tsconfigPath) { + configFileForOpenFiles.set(path, tsconfigPath); return getServiceForTsconfig(tsconfigPath, dirname(tsconfigPath), docContext); } + // Find closer boundary: workspace uri or node_modules const nearestWorkspaceUri = workspaceUris.find((workspaceUri) => isSubPath(workspaceUri, path, getCanonicalFileName) ); + const lastNodeModulesIdx = path.split('/').lastIndexOf('node_modules') + 2; + const nearestNodeModulesBoundary = + lastNodeModulesIdx === 1 + ? undefined + : path.split('/').slice(0, lastNodeModulesIdx).join('/'); + const nearestBoundary = + (nearestNodeModulesBoundary?.length ?? 0) > (nearestWorkspaceUri?.length ?? 0) + ? nearestNodeModulesBoundary + : nearestWorkspaceUri; return getServiceForTsconfig( tsconfigPath, - (nearestWorkspaceUri && urlToPath(nearestWorkspaceUri)) ?? + (nearestBoundary && urlToPath(nearestBoundary)) ?? docContext.tsSystem.getCurrentDirectory(), docContext ); @@ -243,6 +252,7 @@ async function createLanguageService( function deleteSnapshot(filePath: string): void { svelteModuleLoader.deleteFromModuleCache(filePath); snapshotManager.delete(filePath); + configFileForOpenFiles.delete(filePath); } function updateSnapshot(documentOrFilePath: Document | string): DocumentSnapshot { @@ -517,6 +527,7 @@ async function createLanguageService( snapshotManager.dispose(); configWatchers.get(tsconfigPath)?.close(); configWatchers.delete(tsconfigPath); + configFileForOpenFiles.clear(); docContext.globalSnapshotsManager.removeChangeListener(onSnapshotChange); } @@ -565,6 +576,7 @@ async function createLanguageService( scheduleReload(fileName); } else if (kind === ts.FileWatcherEventKind.Deleted) { services.delete(fileName); + configFileForOpenFiles.clear(); } docContext.onProjectReloaded?.(); diff --git a/packages/language-server/src/plugins/typescript/utils.ts b/packages/language-server/src/plugins/typescript/utils.ts index ac42209aa..f67acc6cf 100644 --- a/packages/language-server/src/plugins/typescript/utils.ts +++ b/packages/language-server/src/plugins/typescript/utils.ts @@ -132,13 +132,19 @@ export function findTsConfigPath( ) { const searchDir = dirname(fileName); - const path = - ts.findConfigFile(searchDir, fileExists, 'tsconfig.json') || - ts.findConfigFile(searchDir, fileExists, 'jsconfig.json') || - ''; - // Don't return config files that exceed the current workspace context. - return !!path && rootUris.some((rootUri) => isSubPath(rootUri, path, getCanonicalFileName)) - ? path + const tsconfig = ts.findConfigFile(searchDir, fileExists, 'tsconfig.json') || ''; + const jsconfig = ts.findConfigFile(searchDir, fileExists, 'jsconfig.json') || ''; + // Prefer closest config file + const config = tsconfig.length >= jsconfig.length ? tsconfig : jsconfig; + + // Don't return config files that exceed the current workspace context or cross a node_modules folder + return !!config && + rootUris.some((rootUri) => isSubPath(rootUri, config, getCanonicalFileName)) && + !fileName + .substring(config.length - 13) + .split('/') + .includes('node_modules') + ? config : ''; }