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
262 lines
9.3 KiB
Go
262 lines
9.3 KiB
Go
package languages
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// refEdge reports whether edges contains an edge of kind k from `from` to
|
|
// `unresolved::<to>` whose Meta["ref_context"] equals refContext ("" to
|
|
// ignore the context). Used to assert the C# reference-form edges
|
|
// emitCSharpReferenceForms produces.
|
|
func hasCSharpRefEdge(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
|
|
}
|
|
|
|
// refEdgeOrigin returns the Origin of the first matching edge, or "".
|
|
func csharpRefEdgeOrigin(edges []*graph.Edge, from, to string, k graph.EdgeKind) string {
|
|
for _, e := range edges {
|
|
if e.Kind == k && e.From == from && e.To == "unresolved::"+to {
|
|
return string(e.Origin)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
const csharpRefMethodID = "x/Svc.cs::Svc.Handle"
|
|
|
|
// TestCSharpRefForm_Instantiation verifies `new Foo(...)`, `new Foo[]`,
|
|
// and `new Foo { ... }` each emit an EdgeInstantiates from the enclosing
|
|
// method to the constructed type.
|
|
func TestCSharpRefForm_Instantiation(t *testing.T) {
|
|
src := `public class Svc {
|
|
public void Handle() {
|
|
var a = new RestClient();
|
|
var b = new RestRequest[3];
|
|
var c = new RestResponse { Code = 200 };
|
|
}
|
|
}
|
|
`
|
|
_, edges := runCSharpExtract(t, "x/Svc.cs", src)
|
|
|
|
for _, tgt := range []string{"RestClient", "RestRequest", "RestResponse"} {
|
|
if !hasCSharpRefEdge(edges, csharpRefMethodID, tgt, graph.EdgeInstantiates, "") {
|
|
t.Errorf("expected EdgeInstantiates %s → %s", csharpRefMethodID, tgt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCSharpRefForm_Inheritance verifies `class X : Base, IFoo` still
|
|
// produces the inheritance edges — emitted by the existing
|
|
// emitCSharpBaseList pass (EdgeExtends for the base class, EdgeImplements
|
|
// for the interface), which the reference-form pass deliberately does not
|
|
// double-emit.
|
|
func TestCSharpRefForm_Inheritance(t *testing.T) {
|
|
src := `public class Derived : BaseSvc, IHandler {
|
|
}
|
|
`
|
|
_, edges := runCSharpExtract(t, "x/Derived.cs", src)
|
|
const typeID = "x/Derived.cs::Derived"
|
|
|
|
if !hasCSharpRefEdge(edges, typeID, "BaseSvc", graph.EdgeExtends, "") {
|
|
t.Errorf("expected EdgeExtends %s → BaseSvc", typeID)
|
|
}
|
|
if !hasCSharpRefEdge(edges, typeID, "IHandler", graph.EdgeImplements, "") {
|
|
t.Errorf("expected EdgeImplements %s → IHandler", typeID)
|
|
}
|
|
// The reference-form pass must NOT also emit a base-type EdgeReferences
|
|
// — that would double-count the inheritance edge.
|
|
if hasCSharpRefEdge(edges, typeID, "BaseSvc", graph.EdgeReferences, "") {
|
|
t.Errorf("inheritance must not double-emit EdgeReferences → BaseSvc")
|
|
}
|
|
}
|
|
|
|
// TestCSharpRefForm_CastAndTypeTest verifies `(Foo)x`, `x is Foo` (with
|
|
// and without a binding), and `x as Foo` each emit an EdgeReferences with
|
|
// ref_context "cast".
|
|
func TestCSharpRefForm_CastAndTypeTest(t *testing.T) {
|
|
src := `public class Svc {
|
|
public void Handle(object x) {
|
|
var c = (RestClient)x;
|
|
if (x is RestRequest) { }
|
|
if (x is RestResponse r) { }
|
|
var d = x as RestError;
|
|
}
|
|
}
|
|
`
|
|
_, edges := runCSharpExtract(t, "x/Svc.cs", src)
|
|
|
|
for _, tgt := range []string{"RestClient", "RestRequest", "RestResponse", "RestError"} {
|
|
if !hasCSharpRefEdge(edges, csharpRefMethodID, tgt, graph.EdgeReferences, "cast") {
|
|
t.Errorf("expected EdgeReferences (cast) %s → %s", csharpRefMethodID, tgt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCSharpRefForm_StaticAccess verifies static / const member access
|
|
// (`Foo.Empty`, `Foo.Method()`), `typeof(Foo)`, and `nameof(Foo)` emit an
|
|
// EdgeReferences with ref_context "static_access".
|
|
func TestCSharpRefForm_StaticAccess(t *testing.T) {
|
|
src := `public class Svc {
|
|
public void Handle() {
|
|
var e = RestClient.Empty;
|
|
RestRequest.DoStatic();
|
|
var t = typeof(RestResponse);
|
|
var n = nameof(RestError);
|
|
}
|
|
}
|
|
`
|
|
_, edges := runCSharpExtract(t, "x/Svc.cs", src)
|
|
|
|
for _, tgt := range []string{"RestClient", "RestRequest", "RestResponse", "RestError"} {
|
|
if !hasCSharpRefEdge(edges, csharpRefMethodID, tgt, graph.EdgeReferences, "static_access") {
|
|
t.Errorf("expected EdgeReferences (static_access) %s → %s", csharpRefMethodID, tgt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCSharpRefForm_Attribute verifies an attribute `[Foo]` / `[Foo(...)]`
|
|
// emits an EdgeReferences with ref_context "attribute" to the attribute
|
|
// type. The class-level attribute attributes to the file node (no
|
|
// enclosing function), so the From is the file ID.
|
|
func TestCSharpRefForm_Attribute(t *testing.T) {
|
|
src := `[Route("api")]
|
|
public class Svc {
|
|
[HttpGet]
|
|
public void Handle() { }
|
|
}
|
|
`
|
|
_, edges := runCSharpExtract(t, "x/Svc.cs", src)
|
|
|
|
// Class attribute — no enclosing function → file node owner.
|
|
if !hasCSharpRefEdge(edges, "x/Svc.cs", "Route", graph.EdgeReferences, "attribute") {
|
|
t.Errorf("expected EdgeReferences (attribute) from file → Route")
|
|
}
|
|
// Method attribute encloses the method body line range.
|
|
if !hasCSharpRefEdge(edges, csharpRefMethodID, "HttpGet", graph.EdgeReferences, "attribute") {
|
|
t.Errorf("expected EdgeReferences (attribute) %s → HttpGet", csharpRefMethodID)
|
|
}
|
|
}
|
|
|
|
// TestCSharpRefForm_OriginASTResolved verifies the structural reference
|
|
// edges (cast / static_access / attribute) ride OriginASTResolved — the
|
|
// load-bearing tier that keeps the cross-package guard from reverting them
|
|
// to their unresolved placeholder.
|
|
func TestCSharpRefForm_OriginASTResolved(t *testing.T) {
|
|
src := `public class Svc {
|
|
public void Handle(object x) {
|
|
var c = (RestClient)x;
|
|
var e = RestRequest.Empty;
|
|
}
|
|
}
|
|
`
|
|
_, edges := runCSharpExtract(t, "x/Svc.cs", src)
|
|
|
|
if got := csharpRefEdgeOrigin(edges, csharpRefMethodID, "RestClient", graph.EdgeReferences); got != graph.OriginASTResolved {
|
|
t.Errorf("cast edge Origin = %q, want %q", got, graph.OriginASTResolved)
|
|
}
|
|
if got := csharpRefEdgeOrigin(edges, csharpRefMethodID, "RestRequest", graph.EdgeReferences); got != graph.OriginASTResolved {
|
|
t.Errorf("static_access edge Origin = %q, want %q", got, graph.OriginASTResolved)
|
|
}
|
|
if got := csharpRefEdgeOrigin(edges, csharpRefMethodID, "RestClient", graph.EdgeInstantiates); got != "" && got != graph.OriginASTResolved {
|
|
t.Errorf("instantiate edge Origin = %q, want OriginASTResolved or unset", got)
|
|
}
|
|
}
|
|
|
|
// TestCSharpRefForm_GenericArgs verifies that the type arguments inside a
|
|
// generic spelling — in a field/var annotation, a parameter, and a nested
|
|
// generic — each emit an EdgeReferences with ref_context "generic_arg". The
|
|
// canonicalising type-use pass strips the `<…>` and would otherwise lose
|
|
// these element types. Predefined primitives (int/string) and the `var`
|
|
// keyword must NOT produce a generic_arg edge.
|
|
func TestCSharpRefForm_GenericArgs(t *testing.T) {
|
|
src := `using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
|
|
public class Svc {
|
|
private List<RestClient> _clients;
|
|
|
|
public Dictionary<string, RestError> Handle(Task<RestRequest> a) {
|
|
var d = new Dictionary<int, List<RestResponse>>();
|
|
return null;
|
|
}
|
|
}
|
|
`
|
|
_, edges := runCSharpExtract(t, "x/Svc.cs", src)
|
|
|
|
// Field annotation argument (List<RestClient>) — attributed to the file
|
|
// node, since a field declaration has no enclosing function.
|
|
if !hasCSharpRefEdge(edges, "x/Svc.cs", "RestClient", graph.EdgeReferences, "generic_arg") {
|
|
t.Errorf("expected EdgeReferences (generic_arg) from file → RestClient (field annotation)")
|
|
}
|
|
|
|
// Parameter argument (Task<RestRequest>), return-type arguments
|
|
// (Dictionary<string, RestError>), and the nested-generic arguments
|
|
// (new Dictionary<int, List<RestResponse>>()) are all inside the method,
|
|
// so they attribute to the method node.
|
|
for _, tgt := range []string{"RestRequest", "RestError", "List", "RestResponse"} {
|
|
if !hasCSharpRefEdge(edges, csharpRefMethodID, tgt, graph.EdgeReferences, "generic_arg") {
|
|
t.Errorf("expected EdgeReferences (generic_arg) %s → %s", csharpRefMethodID, tgt)
|
|
}
|
|
}
|
|
|
|
// Primitives inside the type arguments (string, int) and the `var`
|
|
// keyword must never be emitted as a generic_arg reference.
|
|
for _, e := range edges {
|
|
if rc, _ := e.Meta["ref_context"].(string); rc != "generic_arg" {
|
|
continue
|
|
}
|
|
switch e.To {
|
|
case "unresolved::string", "unresolved::int", "unresolved::var":
|
|
t.Errorf("generic_arg must not emit %s for a primitive / var", e.To)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCSharpRefForm_Negatives confirms the scope guards: a lowercase free
|
|
// call (`bar()`), an instance member access (`this.x`, `local.Foo`), a
|
|
// primitive (`int`), and `var` produce no reference-form edge.
|
|
func TestCSharpRefForm_Negatives(t *testing.T) {
|
|
src := `public class Svc {
|
|
private int _n;
|
|
public void Handle(object obj) {
|
|
bar();
|
|
this.Field = 1;
|
|
var svc = obj;
|
|
svc.Foo();
|
|
int z = 3;
|
|
var v = 4;
|
|
}
|
|
}
|
|
`
|
|
_, edges := runCSharpExtract(t, "x/Svc.cs", src)
|
|
|
|
for _, e := range edges {
|
|
if e.Kind != graph.EdgeReferences && e.Kind != graph.EdgeInstantiates {
|
|
continue
|
|
}
|
|
rc, _ := e.Meta["ref_context"].(string)
|
|
// No reference-form edge should target a lowercase / primitive
|
|
// name, the `var` keyword, or the bare instance-field name.
|
|
switch e.To {
|
|
case "unresolved::bar", "unresolved::this", "unresolved::svc",
|
|
"unresolved::obj", "unresolved::int", "unresolved::var",
|
|
"unresolved::Field", "unresolved::Foo", "unresolved::z",
|
|
"unresolved::v":
|
|
t.Errorf("reference form must not emit %s (kind=%s ref_context=%q)", e.To, e.Kind, rc)
|
|
}
|
|
}
|
|
}
|