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

213 lines
7.3 KiB
Go

package languages
import (
"testing"
"github.com/zzet/gortex/internal/graph"
)
// kotlinUseKindEdges returns the ref_context-tagged reference edges the Kotlin
// extractor emitted for src, keyed for assertion convenience. Each entry is
// (kind, target, ref_context).
type kotlinRefEdge struct {
kind graph.EdgeKind
to string
useKind string
}
func extractKotlinRefEdges(t *testing.T, src string) []kotlinRefEdge {
t.Helper()
res, err := NewKotlinExtractor().Extract("app/Sample.kt", []byte(src))
if err != nil {
t.Fatalf("extract: %v", err)
}
var out []kotlinRefEdge
for _, e := range res.Edges {
uk := ""
if e.Meta != nil {
uk, _ = e.Meta["ref_context"].(string)
}
out = append(out, kotlinRefEdge{kind: e.Kind, to: e.To, useKind: uk})
}
return out
}
func hasRefEdge(edges []kotlinRefEdge, kind graph.EdgeKind, to, useKind string) bool {
for _, e := range edges {
if e.kind == kind && e.to == to && e.useKind == useKind {
return true
}
}
return false
}
// countTo counts edges whose target equals `to`, regardless of kind/ref_context.
func countTo(edges []kotlinRefEdge, to string) int {
n := 0
for _, e := range edges {
if e.to == to {
n++
}
}
return n
}
// TestKotlinReferenceForm_Instantiation: a Kotlin constructor call has no
// `new` — `OkHttpClient()` is a call_expression whose callee is a Capitalized
// simple_identifier. It must emit an EdgeInstantiates to the type, NOT a flat
// EdgeCalls to `unresolved::*.OkHttpClient`.
func TestKotlinReferenceForm_Instantiation(t *testing.T) {
edges := extractKotlinRefEdges(t, `package p
fun build() {
val c = OkHttpClient()
}
`)
if !hasRefEdge(edges, graph.EdgeInstantiates, "unresolved::OkHttpClient", "instantiate") {
t.Fatalf("expected EdgeInstantiates -> unresolved::OkHttpClient (ref_context=instantiate); got %+v", edges)
}
// The redundant constructor "call" edge must be suppressed.
for _, e := range edges {
if e.kind == graph.EdgeCalls && e.to == "unresolved::*.OkHttpClient" {
t.Fatalf("constructor call must not also emit EdgeCalls -> unresolved::*.OkHttpClient")
}
}
}
// TestKotlinReferenceForm_NestedInstantiation: `OkHttpClient.Builder()`
// constructs Builder (instantiate -> Builder) and statically references the
// head type OkHttpClient (static_access -> OkHttpClient).
func TestKotlinReferenceForm_NestedInstantiation(t *testing.T) {
edges := extractKotlinRefEdges(t, `package p
fun build() {
val b = OkHttpClient.Builder()
}
`)
if !hasRefEdge(edges, graph.EdgeInstantiates, "unresolved::Builder", "instantiate") {
t.Fatalf("expected EdgeInstantiates -> unresolved::Builder; got %+v", edges)
}
if !hasRefEdge(edges, graph.EdgeReferences, "unresolved::OkHttpClient", "static_access") {
t.Fatalf("expected EdgeReferences -> unresolved::OkHttpClient (static_access) for the head type; got %+v", edges)
}
}
// TestKotlinReferenceForm_Cast: `x as OkHttpClient` (as_expression) emits a
// cast reference to the type.
func TestKotlinReferenceForm_Cast(t *testing.T) {
edges := extractKotlinRefEdges(t, `package p
fun coerce(x: Any) {
val c = x as OkHttpClient
val d = x as? OkHttpClient
}
`)
if !hasRefEdge(edges, graph.EdgeReferences, "unresolved::OkHttpClient", "cast") {
t.Fatalf("expected EdgeReferences -> unresolved::OkHttpClient (cast); got %+v", edges)
}
}
// TestKotlinReferenceForm_TypeTest: `x is OkHttpClient` (check_expression)
// emits a reference to the tested type.
func TestKotlinReferenceForm_TypeTest(t *testing.T) {
edges := extractKotlinRefEdges(t, `package p
fun probe(x: Any) {
if (x is OkHttpClient) {}
if (x !is OkHttpClient) {}
}
`)
if !hasRefEdge(edges, graph.EdgeReferences, "unresolved::OkHttpClient", "cast") {
t.Fatalf("expected EdgeReferences -> unresolved::OkHttpClient for the is/!is test; got %+v", edges)
}
}
// TestKotlinReferenceForm_StaticAccess: `OkHttpClient.DEFAULT` (a
// navigation_expression with a Capitalized head, not a call) references the
// head type.
func TestKotlinReferenceForm_StaticAccess(t *testing.T) {
edges := extractKotlinRefEdges(t, `package p
fun read() {
val v = OkHttpClient.DEFAULT
}
`)
if !hasRefEdge(edges, graph.EdgeReferences, "unresolved::OkHttpClient", "static_access") {
t.Fatalf("expected EdgeReferences -> unresolved::OkHttpClient (static_access); got %+v", edges)
}
}
// TestKotlinReferenceForm_Inheritance: `class X : Bar(), Iface` emits an
// EdgeExtends to the superclass (constructor_invocation) and an EdgeReferences
// to the bare-user_type supertype/interface, both attributed to the class.
func TestKotlinReferenceForm_Inheritance(t *testing.T) {
edges := extractKotlinRefEdges(t, `package p
class X : Bar(), Iface {
}
`)
if !hasRefEdge(edges, graph.EdgeExtends, "unresolved::Bar", "inherit") {
t.Fatalf("expected EdgeExtends -> unresolved::Bar (inherit); got %+v", edges)
}
if !hasRefEdge(edges, graph.EdgeReferences, "unresolved::Iface", "inherit") {
t.Fatalf("expected EdgeReferences -> unresolved::Iface (inherit); got %+v", edges)
}
}
// TestKotlinReferenceForm_NoFalsePositives: a lowercase function call, a
// lowercase receiver access, and a primitive type must emit NO ref_context edge.
func TestKotlinReferenceForm_NoFalsePositives(t *testing.T) {
edges := extractKotlinRefEdges(t, `package p
fun run(svc: Service) {
foo()
svc.doThing()
val n: Int = 3
val s = "x"
val list = mutableListOf(1, 2)
}
`)
for _, e := range edges {
if e.useKind == "" {
continue
}
switch e.to {
case "unresolved::foo", "unresolved::*.foo",
"unresolved::doThing", "unresolved::*.doThing",
"unresolved::Int", "unresolved::n", "unresolved::s",
"unresolved::svc", "unresolved::list", "unresolved::mutableListOf":
t.Fatalf("false positive: emitted ref_context edge %+v for a non-type reference", e)
}
}
// Specifically: no instantiate edge for the lowercase `mutableListOf` call,
// no static_access for the lowercase `svc` receiver, no reference for `Int`.
if countTo(edges, "unresolved::Int") != 0 {
t.Fatalf("primitive Int must not produce a reference edge")
}
if hasRefEdge(edges, graph.EdgeReferences, "unresolved::svc", "static_access") {
t.Fatalf("lowercase receiver svc must not produce a static_access reference")
}
}
// TestKotlinReferenceForm_GenericArgs: type arguments inside a generic — in any
// type position (property annotation, parameter, nested return) — are captured
// as generic_arg references. The type-annotation pass strips the `<…>` and keeps
// only the head type, so without this the element types are invisible to
// find_usages. Primitive args (String) are dropped.
func TestKotlinReferenceForm_GenericArgs(t *testing.T) {
edges := extractKotlinRefEdges(t, `package p
class Holder {
val client: Lazy<OkHttpClient> = lazy { something() }
fun chain(list: List<Interceptor>): Map<String, List<Response>> {
return emptyMap()
}
}
`)
for _, want := range []string{
"unresolved::OkHttpClient", // Lazy<OkHttpClient> property annotation
"unresolved::Interceptor", // List<Interceptor> parameter
"unresolved::Response", // nested Map<String, List<Response>> return
} {
if !hasRefEdge(edges, graph.EdgeReferences, want, "generic_arg") {
t.Fatalf("expected EdgeReferences -> %s (generic_arg); got %+v", want, edges)
}
}
// Primitive generic args (String) must not produce a reference edge.
if countTo(edges, "unresolved::String") != 0 {
t.Fatalf("primitive String generic arg must not produce a reference edge; got %+v", edges)
}
}