Files
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

111 lines
4.7 KiB
Go

package resolver
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zzet/gortex/internal/graph"
)
func fn(id, name, file, retType string) *graph.Node {
n := &graph.Node{ID: id, Kind: graph.KindFunction, Name: name, FilePath: file}
if retType != "" {
n.Meta = map[string]any{"return_type": retType}
}
return n
}
func method(id, name, file, recv, retType string) *graph.Node {
m := map[string]any{"receiver": recv}
if retType != "" {
m["return_type"] = retType
}
return &graph.Node{ID: id, Kind: graph.KindMethod, Name: name, FilePath: file, Meta: m}
}
// TestResolveFactoryChains_CrossFile pins that a fluent chain whose return types
// and methods live in different files resolves the final method.
func TestResolveFactoryChains_CrossFile(t *testing.T) {
g := graph.New()
g.AddNode(fn("a.go::New", "New", "a.go", "Builder"))
g.AddNode(&graph.Node{ID: "a.go::Builder", Kind: graph.KindType, Name: "Builder", FilePath: "a.go"})
g.AddNode(method("a.go::Builder.With", "With", "a.go", "Builder", "Builder"))
g.AddNode(method("a.go::Builder.Build", "Build", "a.go", "Builder", "Widget"))
g.AddNode(&graph.Node{ID: "b.go::Widget", Kind: graph.KindType, Name: "Widget", FilePath: "b.go"})
g.AddNode(method("b.go::Widget.Run", "Run", "b.go", "Widget", ""))
g.AddNode(fn("main.go::main", "main", "main.go", ""))
e := &graph.Edge{
From: "main.go::main", To: "unresolved::*.Run", Kind: graph.EdgeCalls,
FilePath: "main.go", Meta: map[string]any{"receiver_expr": "New().With(x).Build()"},
}
g.AddEdge(e)
assert.Equal(t, 1, ResolveFactoryChains(g))
assert.Equal(t, "b.go::Widget.Run", e.To)
assert.Equal(t, "factory_chain", e.Meta["via"])
assert.Equal(t, graph.OriginASTInferred, e.Origin)
}
// TestResolveFactoryChains_Conformance pins that a factory returning an
// interface resolves the chained method to a unique concrete implementor, with
// the conformance flag set.
func TestResolveFactoryChains_Conformance(t *testing.T) {
g := graph.New()
g.AddNode(fn("a.go::factory", "factory", "a.go", "Iface"))
g.AddNode(&graph.Node{ID: "a.go::Iface", Kind: graph.KindInterface, Name: "Iface", FilePath: "a.go"})
g.AddNode(&graph.Node{ID: "b.go::Impl", Kind: graph.KindType, Name: "Impl", FilePath: "b.go"})
g.AddNode(method("b.go::Impl.bar", "bar", "b.go", "Impl", ""))
g.AddEdge(&graph.Edge{From: "b.go::Impl", To: "a.go::Iface", Kind: graph.EdgeImplements})
g.AddNode(fn("m.go::run", "run", "m.go", ""))
e := &graph.Edge{
From: "m.go::run", To: "unresolved::*.bar", Kind: graph.EdgeCalls,
FilePath: "m.go", Meta: map[string]any{"receiver_expr": "factory()"},
}
g.AddEdge(e)
assert.Equal(t, 1, ResolveFactoryChains(g))
assert.Equal(t, "b.go::Impl.bar", e.To, "chained method binds to the concrete implementor")
assert.Equal(t, true, e.Meta["conformance_walked"])
}
// TestResolveFactoryChains_AmbiguousConformanceDropped pins that two
// implementors declaring the method drop the edge.
func TestResolveFactoryChains_AmbiguousConformanceDropped(t *testing.T) {
g := graph.New()
g.AddNode(fn("a.go::factory", "factory", "a.go", "Iface"))
g.AddNode(&graph.Node{ID: "a.go::Iface", Kind: graph.KindInterface, Name: "Iface", FilePath: "a.go"})
g.AddNode(&graph.Node{ID: "b.go::A", Kind: graph.KindType, Name: "A", FilePath: "b.go"})
g.AddNode(method("b.go::A.bar", "bar", "b.go", "A", ""))
g.AddNode(&graph.Node{ID: "c.go::B", Kind: graph.KindType, Name: "B", FilePath: "c.go"})
g.AddNode(method("c.go::B.bar", "bar", "c.go", "B", ""))
g.AddEdge(&graph.Edge{From: "b.go::A", To: "a.go::Iface", Kind: graph.EdgeImplements})
g.AddEdge(&graph.Edge{From: "c.go::B", To: "a.go::Iface", Kind: graph.EdgeImplements})
e := &graph.Edge{
From: "m.go::run", To: "unresolved::*.bar", Kind: graph.EdgeCalls,
FilePath: "m.go", Meta: map[string]any{"receiver_expr": "factory()"},
}
g.AddNode(fn("m.go::run", "run", "m.go", ""))
g.AddEdge(e)
assert.Equal(t, 0, ResolveFactoryChains(g), "ambiguous implementor dropped")
}
// TestResolveFactoryChains_DoesNotOverrideResolved pins that an already-resolved
// (e.g. LSP-confirmed) edge is never re-targeted.
func TestResolveFactoryChains_DoesNotOverrideResolved(t *testing.T) {
g := graph.New()
g.AddNode(fn("a.go::New", "New", "a.go", "Widget"))
g.AddNode(&graph.Node{ID: "a.go::Widget", Kind: graph.KindType, Name: "Widget", FilePath: "a.go"})
g.AddNode(method("a.go::Widget.Run", "Run", "a.go", "Widget", ""))
g.AddNode(method("lsp.go::Other.Run", "Run", "lsp.go", "Other", ""))
e := &graph.Edge{
From: "m.go::main", To: "lsp.go::Other.Run", Kind: graph.EdgeCalls,
Origin: graph.OriginLSPResolved, FilePath: "m.go",
Meta: map[string]any{"receiver_expr": "New()"},
}
g.AddEdge(e)
assert.Equal(t, 0, ResolveFactoryChains(g))
assert.Equal(t, "lsp.go::Other.Run", e.To, "resolved edge is not overridden")
}