Skip to content

Commit 217d314

Browse files
authored
fix: avoid panic on new stdlib packages (#173)
1 parent ae3fc19 commit 217d314

3 files changed

Lines changed: 109 additions & 20 deletions

File tree

internal/loader/loader.go

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package loader
33
import (
44
"fmt"
55
"io/ioutil"
6+
"os"
67
"strings"
78

89
"github.com/charmbracelet/log"
@@ -145,21 +146,8 @@ func IsStandardLib(pkg *packages.Package) bool {
145146
// -> github.com/
146147
// -> false
147148
base := strings.Split(pkg.PkgPath, "/")[0]
148-
if _, ok := stdPackages[base]; ok {
149-
return ok
150-
}
151-
152-
noTestPackage := strings.Replace(base, "_test", "", -1)
153-
if _, ok := stdPackages[noTestPackage]; ok {
154-
return ok
155-
}
156-
157-
noTestPsuedoPackage := strings.Replace(base, ".test", "", -1)
158-
if _, ok := stdPackages[noTestPsuedoPackage]; ok {
159-
return ok
160-
}
161-
162-
return false
149+
_, ok := getStdlibPackages()[base]
150+
return ok
163151
}
164152

165153
func normalizePackage(opts *config.IndexOpts, pkg *packages.Package) *packages.Package {
@@ -204,11 +192,14 @@ func normalizePackage(opts *config.IndexOpts, pkg *packages.Package) *packages.P
204192
pkg.PkgPath = strings.TrimPrefix(pkg.PkgPath, "std/")
205193
} else {
206194
if pkg.Module == nil {
207-
panic(fmt.Sprintf(
208-
"Should not be possible to have nil module for userland package: %s %s",
209-
pkg,
210-
pkg.PkgPath,
211-
))
195+
log.Warn("Package has nil Module, using fallback",
196+
"package", pkg.PkgPath,
197+
"driver", os.Getenv("GOPACKAGESDRIVER"))
198+
199+
pkg.Module = &packages.Module{
200+
Path: ".",
201+
Version: ".",
202+
}
212203
}
213204
}
214205

internal/loader/stdlib_dynamic.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package loader
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
"strings"
7+
"sync"
8+
9+
"github.com/charmbracelet/log"
10+
)
11+
12+
var (
13+
stdlibOnce sync.Once
14+
stdlibMap map[string]struct{}
15+
)
16+
17+
func getStdlibPackages() map[string]struct{} {
18+
stdlibOnce.Do(func() {
19+
stdlibMap = stdPackages
20+
21+
if os.Getenv("GOPACKAGESDRIVER") != "" {
22+
log.Debug("GOPACKAGESDRIVER is set, using hardcoded stdlib list")
23+
return
24+
}
25+
26+
cmd, err := exec.LookPath("go")
27+
if err != nil {
28+
log.Warn("go command not found, using hardcoded stdlib list")
29+
return
30+
}
31+
32+
output, err := exec.Command(cmd, "list", "std").Output()
33+
if err != nil {
34+
log.Warn(
35+
"Failed to run 'go list std', using hardcoded stdlib list", "error", err)
36+
return
37+
}
38+
39+
stdlibMap = make(map[string]struct{})
40+
packages := strings.SplitSeq(strings.TrimSpace(string(output)), "\n")
41+
for pkg := range packages {
42+
if pkg == "" {
43+
continue
44+
}
45+
base := strings.Split(pkg, "/")[0]
46+
stdlibMap[base] = struct{}{}
47+
}
48+
49+
log.Debug("Successfully loaded stdlib packages dynamically", "count", len(stdlibMap))
50+
})
51+
return stdlibMap
52+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package loader
2+
3+
import (
4+
"sync"
5+
"testing"
6+
7+
"golang.org/x/tools/go/packages"
8+
)
9+
10+
func TestStdlibDetectionNoBazel(t *testing.T) {
11+
// Add a test-only package to the hardcoded list to verify different behavior
12+
// between dynamic loading and hardcoded list
13+
stdPackages["testonly"] = struct{}{}
14+
t.Cleanup(func() {
15+
delete(stdPackages, "testonly")
16+
})
17+
18+
testCases := []struct {
19+
pkgPath string
20+
isStdlib bool
21+
goPackagesDriver string
22+
}{
23+
{"fmt", true, ""},
24+
{"net/http", true, ""},
25+
{"encoding/json", true, ""},
26+
{"vendor/golang.org/x/crypto/chacha20", true, ""},
27+
{"github.com/sourcegraph/scip-go", false, ""},
28+
{"golang.org/x/tools", false, ""},
29+
{"vendorapi.com/foo", false, ""},
30+
{"testonly", false, ""},
31+
{"testonly", true, "bazel"},
32+
}
33+
34+
t.Run("DynamicLoading", func(t *testing.T) {
35+
for _, tc := range testCases {
36+
stdlibOnce = sync.Once{}
37+
t.Setenv("GOPACKAGESDRIVER", tc.goPackagesDriver)
38+
39+
pkg := &packages.Package{PkgPath: tc.pkgPath}
40+
if got := IsStandardLib(pkg); got != tc.isStdlib {
41+
t.Errorf("IsStandardLib(%q) = %v, want %v",
42+
tc.pkgPath, got, tc.isStdlib)
43+
}
44+
}
45+
})
46+
}

0 commit comments

Comments
 (0)