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
201 lines
8.6 KiB
Go
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,
|
|
)
|
|
}
|
|
}
|