Repository navigation
fix(go): resolve calls inside a Go module whose go.mod is below the project root (#2322) - #2324
danusha2345 wants to merge 1 commit into
Conversation
…roject root (colbymchenry#2322) Only `<projectRoot>/go.mod` was read, so a module kept in a subdirectory (`svc/go.mod`, a `server/` backend next to a `web/` frontend, several modules side by side) had no module path: its own imports looked third-party and both `store.New()` and `s.db.CreateItem()` lost their callers. The resolver now finds, for every directory holding an indexed `.go` file, the nearest `go.mod` at or above it up to the project root (memoized per directory), and maps an import path to the module whose path equals it or is a `/`-bounded prefix of it — the longest one when modules nest, the importing file's own module when two declare the same path. The package directory is that module's directory plus the rest of the import path. The module list depends on the file set, so it is dropped with the other file-set caches. A single root `go.mod` resolves exactly as before. EXTRACTION_VERSION 27 -> 28: stored edges change for these layouts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports colbymchenry#1954's goImportedPackageDirs / goIsExternalImport onto colbymchenry#2324's per-import module lookup (getGoModuleForImport), with a small getGoModuleOfFile so a file outside every module still treats only the standard library as certainly external. EXTRACTION_VERSION 40: Go edges change for modules below the project root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…odules below the project root) into fork main
|
I re-ran the three public repos from my comment on #2322 against this branch (
Edges removed: 1 in etcd, 56 in harbor, 20 in mattermost. For example, The build and the new tests plus |
|
Thanks @danusha2345! Your module discovery landed in #2361: the nearest |
Problem
loadGoModuleread only<projectRoot>/go.mod. A module kept in a subdirectory, likesvc/go.modwith nogo.modat the root, or aserver/backend next to aweb/frontend, had no module path. Its own imports (example.com/app/svc/internal/store) looked third-party, so with the issue's code:callers Newandcallers CreateItemreturned 0 withsvc/go.mod, and 1 each with the same module at the root.resolveGoCrossPackageReferencebailed out with no module, andmatchGoFieldChainCallskipped thestore.Managerfield because the package did not count as in-module. Outsideinternal/,isGoExternalQualifiedalso dropped the call as external.Fix
.gofile, the nearestgo.modat or above it, up to the project root. This is memoized per directory, never walks the tree, and never leaves the project. The rootgo.modis always included.getGoModule()becomesgetGoModuleForImport(importPath, fromFile?). It returns the module whose path equals the import path or is a/-bounded prefix of it. When modules nest (example.com/appandexample.com/app/tools), the longest path wins, as in Go. When two modules declare the same path, the importing file's own module wins.resolveGoCrossPackageReferencemaps the import to that module's directory plus the rest of the import path. Before, the rest was implicitly taken from the project root. The other three callers (isExternalImport,isGoExternalQualified,matchGoFieldChainCall) only ask whether the import is in the project.clearCaches()drops it together withknownFiles. A module added later is picked up on the nextsync. The per-directorygo.modreads keep the resolver-lifetime convention thatgoModuleanddirAliaseshad.go.mod, the module list is[root]and the package directory is the same string as before, so edges are unchanged.EXTRACTION_VERSION27 → 28, because stored edges change for these layouts.The parallel resolver needs nothing extra: each worker builds its own
ReferenceResolverover the same database and project root, and runs the same discovery.Not in this PR
go.workis not read. Modules it lists inside the project are found anyway, because discovery starts from the Go files.useentries outside the project are not indexed.frameworks/goframe.ts) still checks only the rootgo.modforgogf/gf. The generic Go framework resolver already falls back to "any.gofile".pkg.Type(func f() *tmapi.Client) is still matched by name only. It can land on a same-named type in another package. This happens with a rootgo.modon main too; the instantiation&tmapi.Client{}resolves correctly. It accounts for 2 of the 294 new edges in the A/B below. Happy to follow up separately.Tests
__tests__/go-nested-module.test.tsindexes real temp projects with SQLite and no mocks:svc/go.modlayout: a package-qualified call intointernal/, one into a non-internalpackage, and the struct-field call;server/go.modandtools/go.modthat both have anapi.Start: each module's import resolves into its ownapi.serverresolves a call and a struct-field call intotoolsthroughtools' module path, and an import that only shares a prefix (example.com/toolsx/api) stays unresolved;go.mod: unchanged.5 of the 7 tests fail on main. The prefix guard and the root case pass on both. All 7 pass with the fix, with both kernel and wasm extraction. The existing Go, GoFrame and Gin suites,
resolution.test.ts,frameworks*.test.tsandextraction.test.tspass in both modes.A/B (edges, main → fix)
NewandCreateItemgo from 0/0 to 1/1 callers withsvc/go.mod. With the rootgo.modthey stay at 1/1.go.mod: +1callsedge, the signaling server'sdiagreport.Main(...). That is its only cross-package call inside a module, and the edge is correct. No other edge changes.replace … => ../../relay(two of the ten declare the same module path): +248calls, +16instantiates, +30references, 0 removed. Each added edge was checked against its call site:go.modthat carries the same modules in a subdirectory, plus a second module declaring the root's module path: the same +294, and nothing else changes.go.modat the root: all edges byte-identical.CODEGRAPH_PARALLEL_RESOLVE_MIN=0, 15,000 refs resolved in workers) and withCODEGRAPH_NO_PARALLEL_RESOLVE=1. Main resolves 0 of them.Thanks @GoDiao for the repro and the analysis in the issue; this follows the direction proposed there.
Fixes #2322
🤖 Generated with Claude Code