diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go index b61abc8dc0..18a5a2b6d2 100644 --- a/internal/ls/autoimports.go +++ b/internal/ls/autoimports.go @@ -91,7 +91,9 @@ func (e *exportInfoMap) get(importingFile tspath.Path, ch *checker.Checker, key if e.usableByFileName != importingFile { return nil } - return core.Map(e.exportInfo.Get(key), func(info CachedSymbolExportInfo) *SymbolExportInfo { return e.rehydrateCachedInfo(ch, info) }) + return core.MapNonNil(e.exportInfo.Get(key), func(info CachedSymbolExportInfo) *SymbolExportInfo { + return e.tryRehydrateCachedInfo(ch, info) + }) } func (e *exportInfoMap) add( @@ -227,12 +229,20 @@ func (e *exportInfoMap) search( symbolName = info[0].capitalizedSymbolName } if matches(symbolName, info[0].targetFlags) { - rehydrated := core.Map(info, func(info CachedSymbolExportInfo) *SymbolExportInfo { - return e.rehydrateCachedInfo(ch, info) - }) - filtered := core.FilterIndex(rehydrated, func(r *SymbolExportInfo, i int, _ []*SymbolExportInfo) bool { - return e.isNotShadowedByDeeperNodeModulesPackage(r, info[i].packageName) - }) + // Rehydrate and filter in one pass to maintain correspondence between cached and rehydrated info + var rehydrated []*SymbolExportInfo + for i, cachedInfo := range info { + rehydratedInfo := e.tryRehydrateCachedInfo(ch, cachedInfo) + if rehydratedInfo == nil { + // Symbol or module is no longer available, skip it + continue + } + if !e.isNotShadowedByDeeperNodeModulesPackage(rehydratedInfo, info[i].packageName) { + continue + } + rehydrated = append(rehydrated, rehydratedInfo) + } + filtered := rehydrated if len(filtered) > 0 { if res := action(filtered, symbolName, ambientModuleName != "", key); res != nil { return res @@ -254,7 +264,10 @@ func (e *exportInfoMap) isNotShadowedByDeeperNodeModulesPackage(info *SymbolExpo return !ok || strings.HasPrefix(info.moduleFileName, packageDeepestNodeModulesPath) } -func (e *exportInfoMap) rehydrateCachedInfo(ch *checker.Checker, info CachedSymbolExportInfo) *SymbolExportInfo { +// tryRehydrateCachedInfo attempts to restore a SymbolExportInfo from cached data. +// Returns the info if successful, or nil if the symbol or module can no longer be found +// (e.g., due to module resolution changes or removed exports). +func (e *exportInfoMap) tryRehydrateCachedInfo(ch *checker.Checker, info CachedSymbolExportInfo) *SymbolExportInfo { if info.symbol != nil && info.moduleSymbol != nil { return &SymbolExportInfo{ symbol: info.symbol, @@ -287,7 +300,8 @@ func (e *exportInfoMap) rehydrateCachedInfo(ch *checker.Checker, info CachedSymb } } if moduleSymbol == nil { - panic(fmt.Sprintf("Could not find module symbol for %s in exportInfoMap", info.moduleName)) + // TODO(andrewbranch): panic(fmt.Sprintf("Could not find module symbol for %s in exportInfoMap", info.moduleName)) + return nil } symbol := core.Coalesce(info.symbol, cachedSymbol) if symbol == nil { @@ -299,7 +313,8 @@ func (e *exportInfoMap) rehydrateCachedInfo(ch *checker.Checker, info CachedSymb } if symbol == nil { - panic(fmt.Sprintf("Could not find symbol '%s' by key '%s' in module %s", info.symbolName, info.symbolTableKey, moduleSymbol.Name)) + // TODO(andrewbranch): panic(fmt.Sprintf("Could not find symbol '%s' by key '%s' in module %s", info.symbolName, info.symbolTableKey, moduleSymbol.Name)) + return nil } e.symbols[info.id] = symbolExportEntry{symbol, moduleSymbol} return &SymbolExportInfo{