Files
zzet--gortex/internal/resolver/bare_name_scope_bind_test.go
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

201 lines
8.6 KiB
Go

package resolver
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zzet/gortex/internal/graph"
)
// TestBindBareNameScopeRefs_LocalWins covers the headline case: a
// function declares a KindLocal `key1`; an EdgeReads to
// `unresolved::key1` originating from that function's body should be
// rewritten to point at the KindLocal node.
func TestBindBareNameScopeRefs_LocalWins(t *testing.T) {
g := graph.New()
owner := "pkg/foo.go::Handler"
g.AddNode(&graph.Node{ID: owner, Kind: graph.KindFunction, Name: "Handler", FilePath: "pkg/foo.go", Language: "go"})
localID := owner + "#local:key1@+3"
g.AddNode(&graph.Node{
ID: localID, Kind: graph.KindLocal, Name: "key1",
FilePath: "pkg/foo.go", StartLine: 3, EndLine: 3, Language: "go",
})
g.AddEdge(&graph.Edge{From: localID, To: owner, Kind: graph.EdgeMemberOf, FilePath: "pkg/foo.go", Line: 3})
edge := &graph.Edge{
From: owner, To: "unresolved::key1",
Kind: graph.EdgeReads, FilePath: "pkg/foo.go", Line: 5,
}
g.AddEdge(edge)
r := New(g)
r.bindBareNameScopeRefs()
assert.Equal(t, localID, edge.To, "EdgeReads must be rewritten to the in-scope KindLocal")
}
// TestBindBareNameScopeRefs_FromBindingResolvesToOwner — the From of
// the edge is itself a per-binding ID (`<func>#local:x@+N`); the
// pass should strip the suffix to recover the enclosing function and
// still bind correctly.
func TestBindBareNameScopeRefs_FromBindingResolvesToOwner(t *testing.T) {
g := graph.New()
owner := "pkg/foo.go::Handler"
g.AddNode(&graph.Node{ID: owner, Kind: graph.KindFunction, Name: "Handler", FilePath: "pkg/foo.go", Language: "go"})
keyID := owner + "#local:key@+2"
g.AddNode(&graph.Node{ID: keyID, Kind: graph.KindLocal, Name: "key", FilePath: "pkg/foo.go", StartLine: 2, Language: "go"})
g.AddEdge(&graph.Edge{From: keyID, To: owner, Kind: graph.EdgeMemberOf})
from := owner + "#local:out@+5"
g.AddNode(&graph.Node{ID: from, Kind: graph.KindLocal, Name: "out", FilePath: "pkg/foo.go", StartLine: 5, Language: "go"})
g.AddEdge(&graph.Edge{From: from, To: owner, Kind: graph.EdgeMemberOf})
edge := &graph.Edge{From: from, To: "unresolved::key", Kind: graph.EdgeValueFlow, Line: 5}
g.AddEdge(edge)
New(g).bindBareNameScopeRefs()
assert.Equal(t, keyID, edge.To, "From with #local: suffix must still resolve via enclosing function")
}
// TestBindBareNameScopeRefs_ParamFallback covers the Go-shadowing
// fallback: when no local matches, the parameter with the same name
// wins.
func TestBindBareNameScopeRefs_ParamFallback(t *testing.T) {
g := graph.New()
owner := "pkg/foo.go::Handler"
g.AddNode(&graph.Node{ID: owner, Kind: graph.KindFunction, Name: "Handler", FilePath: "pkg/foo.go", Language: "go"})
paramID := owner + "#param:req"
g.AddNode(&graph.Node{ID: paramID, Kind: graph.KindParam, Name: "req", FilePath: "pkg/foo.go", Language: "go"})
g.AddEdge(&graph.Edge{From: paramID, To: owner, Kind: graph.EdgeParamOf})
edge := &graph.Edge{From: owner, To: "unresolved::req", Kind: graph.EdgeReads, Line: 3}
g.AddEdge(edge)
New(g).bindBareNameScopeRefs()
assert.Equal(t, paramID, edge.To, "no matching local — param with same name must take over")
}
// TestBindBareNameScopeRefs_LocalShadowsParam — both a param and a
// local share the same name; the local wins (Go shadowing).
func TestBindBareNameScopeRefs_LocalShadowsParam(t *testing.T) {
g := graph.New()
owner := "pkg/foo.go::Handler"
g.AddNode(&graph.Node{ID: owner, Kind: graph.KindFunction, Name: "Handler", FilePath: "pkg/foo.go", Language: "go"})
paramID := owner + "#param:x"
g.AddNode(&graph.Node{ID: paramID, Kind: graph.KindParam, Name: "x", FilePath: "pkg/foo.go", Language: "go"})
g.AddEdge(&graph.Edge{From: paramID, To: owner, Kind: graph.EdgeParamOf})
localID := owner + "#local:x@+4"
g.AddNode(&graph.Node{ID: localID, Kind: graph.KindLocal, Name: "x", FilePath: "pkg/foo.go", StartLine: 4, Language: "go"})
g.AddEdge(&graph.Edge{From: localID, To: owner, Kind: graph.EdgeMemberOf})
edge := &graph.Edge{From: owner, To: "unresolved::x", Kind: graph.EdgeReads, Line: 6}
g.AddEdge(edge)
New(g).bindBareNameScopeRefs()
assert.Equal(t, localID, edge.To, "KindLocal must shadow KindParam with the same name")
}
// TestBindBareNameScopeRefs_RefBeforeDeclLeftAlone — a reference
// whose line is BEFORE the local's StartLine can't possibly bind to
// that local. The pass must leave the edge unresolved rather than
// reach backwards.
func TestBindBareNameScopeRefs_RefBeforeDeclLeftAlone(t *testing.T) {
g := graph.New()
owner := "pkg/foo.go::Handler"
g.AddNode(&graph.Node{ID: owner, Kind: graph.KindFunction, Name: "Handler", FilePath: "pkg/foo.go", Language: "go"})
localID := owner + "#local:tmp@+10"
g.AddNode(&graph.Node{ID: localID, Kind: graph.KindLocal, Name: "tmp", FilePath: "pkg/foo.go", StartLine: 10, Language: "go"})
g.AddEdge(&graph.Edge{From: localID, To: owner, Kind: graph.EdgeMemberOf})
edge := &graph.Edge{From: owner, To: "unresolved::tmp", Kind: graph.EdgeReads, Line: 3}
g.AddEdge(edge)
New(g).bindBareNameScopeRefs()
assert.Equal(t, "unresolved::tmp", edge.To, "reference before declaration must not bind")
}
// TestBindBareNameScopeRefs_LatestShadowWins covers the standard "last
// shadow in scope" rule when two locals share a name across scopes:
// the binding declared on the higher line (closer to the reference)
// wins.
func TestBindBareNameScopeRefs_LatestShadowWins(t *testing.T) {
g := graph.New()
owner := "pkg/foo.go::Handler"
g.AddNode(&graph.Node{ID: owner, Kind: graph.KindFunction, Name: "Handler", FilePath: "pkg/foo.go", Language: "go"})
earlier := owner + "#local:err@+2"
g.AddNode(&graph.Node{ID: earlier, Kind: graph.KindLocal, Name: "err", FilePath: "pkg/foo.go", StartLine: 2, Language: "go"})
g.AddEdge(&graph.Edge{From: earlier, To: owner, Kind: graph.EdgeMemberOf})
later := owner + "#local:err@+8"
g.AddNode(&graph.Node{ID: later, Kind: graph.KindLocal, Name: "err", FilePath: "pkg/foo.go", StartLine: 8, Language: "go"})
g.AddEdge(&graph.Edge{From: later, To: owner, Kind: graph.EdgeMemberOf})
edge := &graph.Edge{From: owner, To: "unresolved::err", Kind: graph.EdgeReads, Line: 12}
g.AddEdge(edge)
New(g).bindBareNameScopeRefs()
assert.Equal(t, later, edge.To, "the most recent shadow before the reference line must win")
}
// TestBindBareNameScopeRefs_AmbiguousLeftAlone — two locals with the
// same name declared on the same line (shouldn't happen in valid Go
// but defensive): the pass must leave the edge unresolved rather
// than pick an arbitrary winner.
func TestBindBareNameScopeRefs_AmbiguousLeftAlone(t *testing.T) {
g := graph.New()
owner := "pkg/foo.go::Handler"
g.AddNode(&graph.Node{ID: owner, Kind: graph.KindFunction, Name: "Handler", FilePath: "pkg/foo.go", Language: "go"})
a := owner + "#local:err@+5"
b := owner + "#local:err@+5#1"
g.AddNode(&graph.Node{ID: a, Kind: graph.KindLocal, Name: "err", FilePath: "pkg/foo.go", StartLine: 5, Language: "go"})
g.AddNode(&graph.Node{ID: b, Kind: graph.KindLocal, Name: "err", FilePath: "pkg/foo.go", StartLine: 5, Language: "go"})
g.AddEdge(&graph.Edge{From: a, To: owner, Kind: graph.EdgeMemberOf})
g.AddEdge(&graph.Edge{From: b, To: owner, Kind: graph.EdgeMemberOf})
edge := &graph.Edge{From: owner, To: "unresolved::err", Kind: graph.EdgeReads, Line: 7}
g.AddEdge(edge)
New(g).bindBareNameScopeRefs()
assert.Equal(t, "unresolved::err", edge.To, "ambiguous candidates on same line must leave the edge unresolved")
}
// TestBindBareNameScopeRefs_QualifiedNotTouched ensures the pass only
// fires on bare names — qualified shapes (`*.Method`, `pkg.Name`,
// `unresolved::pyrel::...`) are left to other passes.
func TestBindBareNameScopeRefs_QualifiedNotTouched(t *testing.T) {
g := graph.New()
owner := "pkg/foo.go::Handler"
g.AddNode(&graph.Node{ID: owner, Kind: graph.KindFunction, Name: "Handler", FilePath: "pkg/foo.go", Language: "go"})
// Even if a local matches the unqualified part, the qualified
// shapes must be left alone.
g.AddNode(&graph.Node{ID: owner + "#local:Foo@+2", Kind: graph.KindLocal, Name: "Foo", FilePath: "pkg/foo.go", StartLine: 2, Language: "go"})
g.AddEdge(&graph.Edge{From: owner + "#local:Foo@+2", To: owner, Kind: graph.EdgeMemberOf})
keep := []*graph.Edge{
{From: owner, To: "unresolved::*.Foo", Kind: graph.EdgeReads, Line: 5},
{From: owner, To: "unresolved::pkg.Foo", Kind: graph.EdgeReads, Line: 6},
{From: owner, To: "unresolved::pyrel::./foo", Kind: graph.EdgeReads, Line: 7},
}
for _, e := range keep {
g.AddEdge(e)
}
New(g).bindBareNameScopeRefs()
for _, e := range keep {
assert.True(t,
e.To == "unresolved::*.Foo" || e.To == "unresolved::pkg.Foo" || e.To == "unresolved::pyrel::./foo",
"qualified shape %q must stay untouched", e.To,
)
}
}