package languages import ( "testing" "github.com/zzet/gortex/internal/graph" ) // hasPHPRefEdge reports whether edges contains an edge of kind k from `from` // to `unresolved::` whose Meta["ref_context"] equals refContext ("" to // ignore the context). Used to assert the PHP reference-form edges // emitPHPReferenceForms produces. func hasPHPRefEdge(edges []*graph.Edge, from, to string, k graph.EdgeKind, refContext string) bool { for _, e := range edges { if e.Kind != k || e.From != from || e.To != "unresolved::"+to { continue } if refContext == "" { return true } if rc, _ := e.Meta["ref_context"].(string); rc == refContext { return true } } return false } // phpRefEdgeOrigin returns the Origin of the first matching edge, or "". func phpRefEdgeOrigin(edges []*graph.Edge, from, to string, k graph.EdgeKind, refContext string) string { for _, e := range edges { if e.Kind != k || e.From != from || e.To != "unresolved::"+to { continue } if refContext != "" { if rc, _ := e.Meta["ref_context"].(string); rc != refContext { continue } } return e.Origin } return "" } // TestPHPRefForm_Instantiation verifies `new Foo(...)` and a namespaced // `new \App\Http\Client()` each emit an EdgeInstantiates from the enclosing // method to the bare constructed type. func TestPHPRefForm_Instantiation(t *testing.T) { src := []byte(` %s", methodID, tgt) } } } // TestPHPRefForm_Inheritance verifies `class X extends Base implements I, J` // emits one EdgeReferences (ref_context "inherit") per base / interface name, // attributed to the declaring class node. func TestPHPRefForm_Inheritance(t *testing.T) { src := []byte(` %s", typeID, tgt) } } } // TestPHPRefForm_Instanceof verifies `$x instanceof Foo` emits an // EdgeReferences with ref_context "cast"; a namespaced test reduces to the // bare class name. func TestPHPRefForm_Instanceof(t *testing.T) { src := []byte(` %s", methodID, tgt) } } } // TestPHPRefForm_StaticAccess verifies `Foo::CONST`, `Foo::class`, and // `Foo::method()` each emit an EdgeReferences with ref_context // "static_access" to the scope type. func TestPHPRefForm_StaticAccess(t *testing.T) { src := []byte(` %s", methodID, tgt) } } } // TestPHPRefForm_Attribute verifies a PHP 8 attribute `#[Foo]` / `#[Foo(...)]` // emits an EdgeReferences with ref_context "static_access" to the attribute // type. A class-level attribute attributes to the declaring class node; a // method-level attribute to the enclosing method. func TestPHPRefForm_Attribute(t *testing.T) { src := []byte(` Route", typeID) } if !hasPHPRefEdge(result.Edges, methodID, "HttpGet", graph.EdgeReferences, graph.RefContextStaticAccess) { t.Errorf("expected EdgeReferences (static_access) %s -> HttpGet", methodID) } } // TestPHPRefForm_OriginASTResolved verifies every reference-form edge rides // OriginASTResolved — the load-bearing tier that keeps the cross-package // guard from reverting the structural EdgeReferences edges to their // unresolved placeholder. func TestPHPRefForm_OriginASTResolved(t *testing.T) { src := []byte(` %s (%s/%s) Origin = %q, want %q", c.from, c.to, c.kind, c.refctx, got, graph.OriginASTResolved) } } } // TestPHPRefForm_Negatives confirms the scope guards: a lowercase free call // (`bar()`), an instance member call (`$this->foo()`), the relative scopes // (`self::x`, `parent::base()`, `static::make()`), and a primitive type used // in a cast / type position produce no reference-form edge. func TestPHPRefForm_Negatives(t *testing.T) { src := []byte(`foo(); self::helper(); $n = parent::base(); $m = static::make(); $s = "string"; if ($x instanceof $klass) {} } } `) result, err := NewPHPExtractor().Extract("p/Svc.php", src) if err != nil { t.Fatal(err) } for _, e := range result.Edges { if e.Kind != graph.EdgeReferences && e.Kind != graph.EdgeInstantiates { continue } rc, _ := e.Meta["ref_context"].(string) switch e.To { case "unresolved::bar", "unresolved::foo", "unresolved::this", "unresolved::self", "unresolved::static", "unresolved::parent", "unresolved::helper", "unresolved::base", "unresolved::make", "unresolved::string", "unresolved::klass", "unresolved::x": t.Errorf("reference form must not emit %s (kind=%s ref_context=%q)", e.To, e.Kind, rc) } } }