From a9d5dd5c02633386b7fc3e3ee58655e3662542b1 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 15 Feb 2025 17:54:55 -0500 Subject: [PATCH 1/2] Make auto-imports host provided --- src/services/codefixes/importFixes.ts | 8 ++- src/services/exportInfoMap.ts | 92 +++++++++++++++------------ src/services/types.ts | 3 +- 3 files changed, 59 insertions(+), 44 deletions(-) diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index b23b7c127e872..8303fdebad1cd 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -1258,8 +1258,10 @@ function getNewImportFixes( const compilerOptions = program.getCompilerOptions(); const moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host); const getChecker = createGetChecker(program, host); - const moduleResolution = getEmitModuleResolutionKind(compilerOptions); - const rejectNodeModulesRelativePaths = moduleResolutionUsesNodeModules(moduleResolution); + // const moduleResolution = getEmitModuleResolutionKind(compilerOptions); + // deno: do not reject node_modules relative paths because + // they will be corrected later on by deno + const rejectNodeModulesRelativePaths = false; // moduleResolutionUsesNodeModules(moduleResolution); const getModuleSpecifiers = fromCacheOnly ? (exportInfo: SymbolExportInfo | FutureSymbolExportInfo) => moduleSpecifiers.tryGetModuleSpecifiersFromCache(exportInfo.moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences) : (exportInfo: SymbolExportInfo | FutureSymbolExportInfo, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(exportInfo.moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences, /*options*/ undefined, /*forAutoImport*/ true); @@ -1638,7 +1640,7 @@ function getExportInfos( originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { symbol: exportedSymbol, moduleSymbol, moduleFileName: toFile?.fileName, exportKind, targetFlags: skipAlias(exportedSymbol, checker).flags, isFromPackageJson }); } } - forEachExternalModuleToImportFrom(program, host, preferences, useAutoImportProvider, (moduleSymbol, sourceFile, program, isFromPackageJson) => { + forEachExternalModuleToImportFrom(program, host, preferences, useAutoImportProvider, fromFile, (moduleSymbol, sourceFile, program, isFromPackageJson) => { const checker = program.getTypeChecker(); cancellationToken.throwIfCancellationRequested(); diff --git a/src/services/exportInfoMap.ts b/src/services/exportInfoMap.ts index dfd00ce2e9170..a8d8dd69f7b82 100644 --- a/src/services/exportInfoMap.ts +++ b/src/services/exportInfoMap.ts @@ -127,7 +127,6 @@ export interface ExportInfoMap { onFileChanged(oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean): boolean; } -/** @internal */ export interface CacheableExportInfoMapHost { getCurrentProgram(): Program | undefined; getPackageJsonAutoImportProvider(): Program | undefined; @@ -135,7 +134,6 @@ export interface CacheableExportInfoMapHost { } export type ExportMapInfoKey = string & { __exportInfoKey: void; }; -/** @internal */ export function createCacheableExportInfoMap(host: CacheableExportInfoMapHost): ExportInfoMap { let exportInfoId = 1; const exportInfo = createMultiMap(); @@ -370,10 +368,10 @@ export function isImportable( fromFile: SourceFile, toFile: SourceFile | undefined, toModule: Symbol, - preferences: UserPreferences, + _preferences: UserPreferences, packageJsonFilter: PackageJsonImportFilter | undefined, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost, - moduleSpecifierCache: ModuleSpecifierCache | undefined, + _moduleSpecifierCache: ModuleSpecifierCache | undefined, ): boolean { if (!toFile) { // Ambient module @@ -389,40 +387,44 @@ export function isImportable( Debug.assertIsDefined(toFile); if (fromFile === toFile) return false; - const cachedResult = moduleSpecifierCache?.get(fromFile.path, toFile.path, preferences, {}); - if (cachedResult?.isBlockedByPackageJsonDependencies !== undefined) { - return !cachedResult.isBlockedByPackageJsonDependencies || !!cachedResult.packageName && fileContainsPackageImport(fromFile, cachedResult.packageName); - } - - const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost); - const globalTypingsCache = moduleSpecifierResolutionHost.getGlobalTypingsCacheLocation?.(); - const hasImportablePath = !!moduleSpecifiers.forEachFileNameOfModule( - fromFile.fileName, - toFile.fileName, - moduleSpecifierResolutionHost, - /*preferSymlinks*/ false, - toPath => { - const file = program.getSourceFile(toPath); - // Determine to import using toPath only if toPath is what we were looking at - // or there doesnt exist the file in the program by the symlink - return (file === toFile || !file) && - isImportablePath( - fromFile.fileName, - toPath, - getCanonicalFileName, - globalTypingsCache, - moduleSpecifierResolutionHost, - ); - }, - ); - - if (packageJsonFilter) { - const importInfo = hasImportablePath ? packageJsonFilter.getSourceFileInfo(toFile, moduleSpecifierResolutionHost) : undefined; - moduleSpecifierCache?.setBlockedByPackageJsonDependencies(fromFile.path, toFile.path, preferences, {}, importInfo?.packageName, !importInfo?.importable); - return !!importInfo?.importable || hasImportablePath && !!importInfo?.packageName && fileContainsPackageImport(fromFile, importInfo.packageName); - } - return hasImportablePath; + // deno: any module provided by deno's host will always be importable + return true; + + // const cachedResult = moduleSpecifierCache?.get(fromFile.path, toFile.path, preferences, {}); + // if (cachedResult?.isBlockedByPackageJsonDependencies !== undefined) { + // return !cachedResult.isBlockedByPackageJsonDependencies || !!cachedResult.packageName && fileContainsPackageImport(fromFile, cachedResult.packageName); + // } + + // const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost); + // const globalTypingsCache = moduleSpecifierResolutionHost.getGlobalTypingsCacheLocation?.(); + // const hasImportablePath = !!moduleSpecifiers.forEachFileNameOfModule( + // fromFile.fileName, + // toFile.fileName, + // moduleSpecifierResolutionHost, + // /*preferSymlinks*/ false, + // toPath => { + // const file = program.getSourceFile(toPath); + // // Determine to import using toPath only if toPath is what we were looking at + // // or there doesnt exist the file in the program by the symlink + // return (file === toFile || !file) && + // isImportablePath( + // fromFile.fileName, + // toPath, + // getCanonicalFileName, + // globalTypingsCache, + // moduleSpecifierResolutionHost, + // ); + // }, + // ); + + // if (packageJsonFilter) { + // const importInfo = hasImportablePath ? packageJsonFilter.getSourceFileInfo(toFile, moduleSpecifierResolutionHost) : undefined; + // moduleSpecifierCache?.setBlockedByPackageJsonDependencies(fromFile.path, toFile.path, preferences, {}, importInfo?.packageName, !importInfo?.importable); + // return !!importInfo?.importable || hasImportablePath && !!importInfo?.packageName && fileContainsPackageImport(fromFile, importInfo.packageName); + // } + + // return hasImportablePath; } function fileContainsPackageImport(sourceFile: SourceFile, packageName: string) { @@ -458,17 +460,26 @@ export function forEachExternalModuleToImportFrom( host: LanguageServiceHost, preferences: UserPreferences, useAutoImportProvider: boolean, + referrerFile: SourceFile | FutureSourceFile, cb: (module: Symbol, moduleFile: SourceFile | undefined, program: Program, isFromPackageJson: boolean) => void, ): void { const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); const excludePatterns = preferences.autoImportFileExcludePatterns && getIsExcludedPatterns(preferences, useCaseSensitiveFileNames); - forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), excludePatterns, host, (module, file) => cb(module, file, program, /*isFromPackageJson*/ false)); + const importableFilePaths = host.getExportImportablePathsFromModule(referrerFile.path); + const files = importableFilePaths + .map(path => program.getSourceFile(path)) + .filter(s => s != null) as SourceFile[]; + + forEachExternalModule(program.getTypeChecker(), files, excludePatterns, host, (module, file) => cb(module, file, program, /*isFromPackageJson*/ false)); const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.(); if (autoImportProvider) { const start = timestamp(); const checker = program.getTypeChecker(); - forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), excludePatterns, host, (module, file) => { + const files = importableFilePaths + .map(path => autoImportProvider.getSourceFile(path)) + .filter(s => s != null) as SourceFile[]; + forEachExternalModule(autoImportProvider.getTypeChecker(), files, excludePatterns, host, (module, file) => { if (file && !program.getSourceFile(file.fileName) || !file && !checker.resolveName(module.name, /*location*/ undefined, SymbolFlags.Module, /*excludeGlobals*/ false)) { // The AutoImportProvider filters files already in the main program out of its *root* files, // but non-root files can still be present in both programs, and already in the export info map @@ -554,11 +565,12 @@ export function getExportInfoMap(importingFile: SourceFile | FutureSourceFile, h host.log?.("getExportInfoMap: cache miss or empty; calculating new results"); let moduleCount = 0; try { - forEachExternalModuleToImportFrom(program, host, preferences, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => { + forEachExternalModuleToImportFrom(program, host, preferences, /*useAutoImportProvider*/ true, importingFile, (moduleSymbol, moduleFile, program, isFromPackageJson) => { if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested(); const seenExports = new Set<__String>(); const checker = program.getTypeChecker(); const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker); + // Note: I think we shouldn't actually see resolved module symbols here, but weird merges // can cause it to happen: see 'completionsImport_mergedReExport.ts' if (defaultInfo && isImportableSymbol(defaultInfo.symbol, checker)) { diff --git a/src/services/types.ts b/src/services/types.ts index 0811d858e6a6e..cc65b46f07fdc 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -422,7 +422,8 @@ export interface LanguageServiceHost extends GetEffectiveTypeRootsHost, MinimalR /** @internal */ getPackageJsonsVisibleToFile?(fileName: string, rootDir?: string): readonly ProjectPackageJsonInfo[]; /** @internal */ getNearestAncestorDirectoryWithPackageJson?(fileName: string): string | undefined; /** @internal */ getPackageJsonsForAutoImport?(rootDir?: string): readonly ProjectPackageJsonInfo[]; - /** @internal */ getCachedExportInfoMap?(): ExportInfoMap; + getExportImportablePathsFromModule(filePath: string): string[]; + getCachedExportInfoMap?(): ExportInfoMap; /** @internal */ getModuleSpecifierCache?(): ModuleSpecifierCache; /** @internal */ setCompilerHost?(host: CompilerHost): void; /** @internal */ useSourceOfProjectReferenceRedirect?(): boolean; From 3afb77a488580965367db7fbe65953d72848b555 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 15 Feb 2025 17:56:10 -0500 Subject: [PATCH 2/2] revert blank line --- src/services/exportInfoMap.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/exportInfoMap.ts b/src/services/exportInfoMap.ts index a8d8dd69f7b82..ca1ec83059fd4 100644 --- a/src/services/exportInfoMap.ts +++ b/src/services/exportInfoMap.ts @@ -570,7 +570,6 @@ export function getExportInfoMap(importingFile: SourceFile | FutureSourceFile, h const seenExports = new Set<__String>(); const checker = program.getTypeChecker(); const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker); - // Note: I think we shouldn't actually see resolved module symbols here, but weird merges // can cause it to happen: see 'completionsImport_mergedReExport.ts' if (defaultInfo && isImportableSymbol(defaultInfo.symbol, checker)) {