diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go index b61abc8dc0..9ee37fa1e2 100644 --- a/internal/ls/autoimports.go +++ b/internal/ls/autoimports.go @@ -21,6 +21,11 @@ import ( "github.com/microsoft/typescript-go/internal/tspath" ) +// isValidSliceRange checks if the given start and end indices are valid for slicing a string/slice of the given length. +func isValidSliceRange(start, end, length int) bool { + return start >= 0 && end >= 0 && start <= end && end <= length +} + type SymbolExportInfo struct { symbol *ast.Symbol moduleSymbol *ast.Symbol @@ -117,16 +122,27 @@ func (e *exportInfoMap) add( topLevelNodeModulesIndex := nodeModulesPathParts.TopLevelNodeModulesIndex topLevelPackageNameIndex := nodeModulesPathParts.TopLevelPackageNameIndex packageRootIndex := nodeModulesPathParts.PackageRootIndex - packageName = module.UnmangleScopedPackageName(modulespecifiers.GetPackageNameFromTypesPackageName(moduleFile.FileName()[topLevelPackageNameIndex+1 : packageRootIndex])) - if strings.HasPrefix(string(importingFile), string(moduleFile.Path())[0:topLevelNodeModulesIndex]) { - nodeModulesPath := moduleFile.FileName()[0 : topLevelPackageNameIndex+1] - if prevDeepestNodeModulesPath, ok := e.packages[packageName]; ok { - prevDeepestNodeModulesIndex := strings.Index(prevDeepestNodeModulesPath, "/node_modules/") - if topLevelNodeModulesIndex > prevDeepestNodeModulesIndex { + + // Bounds check to prevent slice bounds out of range panic + fileName := moduleFile.FileName() + if isValidSliceRange(topLevelPackageNameIndex+1, packageRootIndex, len(fileName)) { + packageName = module.UnmangleScopedPackageName(modulespecifiers.GetPackageNameFromTypesPackageName(fileName[topLevelPackageNameIndex+1 : packageRootIndex])) + } + + // Bounds check for module path slice + modulePath := string(moduleFile.Path()) + if topLevelNodeModulesIndex >= 0 && topLevelNodeModulesIndex <= len(modulePath) && strings.HasPrefix(string(importingFile), modulePath[0:topLevelNodeModulesIndex]) { + // Bounds check for node modules path slice + if topLevelPackageNameIndex >= 0 && topLevelPackageNameIndex+1 <= len(fileName) { + nodeModulesPath := fileName[0 : topLevelPackageNameIndex+1] + if prevDeepestNodeModulesPath, ok := e.packages[packageName]; ok { + prevDeepestNodeModulesIndex := strings.Index(prevDeepestNodeModulesPath, "/node_modules/") + if topLevelNodeModulesIndex > prevDeepestNodeModulesIndex { + e.packages[packageName] = nodeModulesPath + } + } else { e.packages[packageName] = nodeModulesPath } - } else { - e.packages[packageName] = nodeModulesPath } } } @@ -153,6 +169,10 @@ func (e *exportInfoMap) add( names = getNamesForExportedSymbol(namedSymbol, ch, core.ScriptTargetNone) } + // Bounds check to prevent slice bounds out of range panic + if len(names) == 0 { + return + } symbolName := names[0] if symbolNameMatch != nil && !symbolNameMatch(symbolName) { return @@ -663,12 +683,23 @@ func (l *LanguageService) createPackageJsonImportFilter(fromFile *ast.SourceFile sourceFileCache := map[*ast.SourceFile]packageJsonFilterResult{} getNodeModuleRootSpecifier := func(fullSpecifier string) string { - components := tspath.GetPathComponents(modulespecifiers.GetPackageNameFromTypesPackageName(fullSpecifier), "")[1:] + components := tspath.GetPathComponents(modulespecifiers.GetPackageNameFromTypesPackageName(fullSpecifier), "") + // Bounds check to prevent slice bounds out of range panic + if len(components) < 2 { + return "" + } + components = components[1:] // Scoped packages - if strings.HasPrefix(components[0], "@") { + if len(components) > 0 && strings.HasPrefix(components[0], "@") { + if len(components) < 2 { + return components[0] + } return fmt.Sprintf("%s/%s", components[0], components[1]) } - return components[0] + if len(components) > 0 { + return components[0] + } + return "" } moduleSpecifierIsCoveredByPackageJson := func(specifier string) bool { diff --git a/internal/ls/autoimportsexportinfo.go b/internal/ls/autoimportsexportinfo.go index 65de5e82ad..55918bf8fb 100644 --- a/internal/ls/autoimportsexportinfo.go +++ b/internal/ls/autoimportsexportinfo.go @@ -100,6 +100,10 @@ func (l *LanguageService) searchExportInfosForCompletions( return false } // Do not try to auto-import something with a lowercase first letter for a JSX tag + if len(symbolName) == 0 { + symbolNameMatches[symbolName] = false + return false + } firstChar := rune(symbolName[0]) if isRightOfOpenTag && (firstChar < 'A' || firstChar > 'Z') { symbolNameMatches[symbolName] = false