From c63c5756727bbd2f91448f9a2f5802f4be0c9e3f Mon Sep 17 00:00:00 2001 From: Charlie Tonneslan Date: Mon, 18 May 2026 14:00:26 -0400 Subject: [PATCH] DefaultPackagePathToName: fall back to a path-based guess when go isn't available Closes #18. packages.Load shells out to the go binary, so in environments where that's absent (WebAssembly, the Go Playground) String() returned 'go command required, not found'. When packages.Load fails or returns no name, derive the package name from the last non-version segment of the import path. That's not always the real name but it's good enough to render literals without forcing every caller to set a custom PackagePathToName. Signed-off-by: Charlie Tonneslan --- valast.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/valast.go b/valast.go index b03a7c3..280e97e 100644 --- a/valast.go +++ b/valast.go @@ -65,12 +65,63 @@ func (o *Options) packagePathToName(path string) (string, error) { } // DefaultPackagePathToName loads the specified package from disk to determine the package name. +// In environments where the `go` command is unavailable (e.g. WebAssembly or +// the Go Playground), it falls back to guessing the name from the last +// non-version segment of the import path. func DefaultPackagePathToName(path string) (string, error) { pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedName}, path) - if err != nil { - return "", err + if err == nil && len(pkgs) > 0 && pkgs[0].Name != "" { + return pkgs[0].Name, nil + } + return packagePathBasename(path), nil +} + +// packagePathBasename returns the last non-version segment of an import path +// (so example.com/foo/v2 -> foo). This matches the convention used by +// gopls and is good enough for rendering, even if it isn't always the actual +// package name. +func packagePathBasename(path string) string { + for { + base := pathBase(path) + if !isVersionSegment(base) { + return base + } + parent := pathDir(path) + if parent == "" || parent == path { + return base + } + path = parent + } +} + +func pathBase(p string) string { + for i := len(p) - 1; i >= 0; i-- { + if p[i] == '/' { + return p[i+1:] + } + } + return p +} + +func pathDir(p string) string { + for i := len(p) - 1; i >= 0; i-- { + if p[i] == '/' { + return p[:i] + } + } + return "" +} + +func isVersionSegment(s string) bool { + if len(s) < 2 || s[0] != 'v' { + return false + } + for _, c := range s[1:] { + if c < '0' || c > '9' { + return false + } } - return pkgs[0].Name, nil + return true } // String converts the value v into the equivalent Go literal syntax.