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
220 lines
6.9 KiB
Go
220 lines
6.9 KiB
Go
package languages
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// scalaRefEdge is a flattened view of a reference-form edge for assertions.
|
|
type scalaRefEdge struct {
|
|
to string // canonical type name (after the unresolved:: prefix)
|
|
kind graph.EdgeKind
|
|
refCtx string
|
|
}
|
|
|
|
// extractScalaRefEdges runs the Scala extractor over src and returns every
|
|
// instantiate / structural-reference edge keyed by canonical target type.
|
|
func extractScalaRefEdges(t *testing.T, src string) []scalaRefEdge {
|
|
t.Helper()
|
|
res, err := NewScalaExtractor().Extract("demo.scala", []byte(src))
|
|
if err != nil {
|
|
t.Fatalf("Extract: %v", err)
|
|
}
|
|
var out []scalaRefEdge
|
|
for _, e := range res.Edges {
|
|
if e.Kind != graph.EdgeInstantiates && e.Kind != graph.EdgeReferences {
|
|
continue
|
|
}
|
|
to := strings.TrimPrefix(e.To, "unresolved::")
|
|
ctx, _ := e.Meta["ref_context"].(string)
|
|
out = append(out, scalaRefEdge{to: to, kind: e.Kind, refCtx: ctx})
|
|
// Every reference-form edge must be stamped OriginASTResolved so the
|
|
// cross-package guard does not revert the bare unresolved target.
|
|
if e.Origin != graph.OriginASTResolved {
|
|
t.Errorf("edge to %s kind=%s origin=%q, want %q", e.To, e.Kind, e.Origin, graph.OriginASTResolved)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// scalaHasRefEdge reports whether edges contains one matching to/kind/refCtx.
|
|
func scalaHasRefEdge(edges []scalaRefEdge, to string, kind graph.EdgeKind, refCtx string) bool {
|
|
for _, e := range edges {
|
|
if e.to == to && e.kind == kind && e.refCtx == refCtx {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// countRefEdgesTo counts reference-form edges whose target is `to`.
|
|
func countRefEdgesTo(edges []scalaRefEdge, to string) int {
|
|
n := 0
|
|
for _, e := range edges {
|
|
if e.to == to {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func TestScalaRefFormInstantiation(t *testing.T) {
|
|
src := `
|
|
package demo
|
|
class Widget
|
|
object Cat
|
|
class Factory {
|
|
def make(): Widget = new Widget()
|
|
def cat(): Cat = Cat()
|
|
}
|
|
`
|
|
edges := extractScalaRefEdges(t, src)
|
|
if !scalaHasRefEdge(edges, "Widget", graph.EdgeInstantiates, "") {
|
|
t.Errorf("expected instantiate edge to Widget (new Widget()), got %+v", edges)
|
|
}
|
|
if !scalaHasRefEdge(edges, "Cat", graph.EdgeInstantiates, "") {
|
|
t.Errorf("expected instantiate edge to Cat (Cat() apply), got %+v", edges)
|
|
}
|
|
}
|
|
|
|
func TestScalaRefFormInheritance(t *testing.T) {
|
|
src := `
|
|
package demo
|
|
class Base
|
|
trait T1
|
|
trait T2
|
|
class X extends Base with T1 with T2
|
|
object O extends Foo
|
|
`
|
|
edges := extractScalaRefEdges(t, src)
|
|
for _, want := range []string{"Base", "T1", "T2"} {
|
|
if !scalaHasRefEdge(edges, want, graph.EdgeReferences, graph.RefContextInherit) {
|
|
t.Errorf("expected inherit ref to %s, got %+v", want, edges)
|
|
}
|
|
}
|
|
if !scalaHasRefEdge(edges, "Foo", graph.EdgeReferences, graph.RefContextInherit) {
|
|
t.Errorf("expected inherit ref to Foo (object O extends Foo), got %+v", edges)
|
|
}
|
|
}
|
|
|
|
func TestScalaRefFormCasts(t *testing.T) {
|
|
src := `
|
|
package demo
|
|
class Checker {
|
|
def is(x: Any): Boolean = x.isInstanceOf[Foo]
|
|
def as(x: Any): Foo = x.asInstanceOf[Foo]
|
|
def m(x: Any): String = x match {
|
|
case _: Bar => "b"
|
|
case d: Baz => "z"
|
|
case _ => "?"
|
|
}
|
|
}
|
|
`
|
|
edges := extractScalaRefEdges(t, src)
|
|
for _, want := range []string{"Foo", "Bar", "Baz"} {
|
|
if !scalaHasRefEdge(edges, want, graph.EdgeReferences, graph.RefContextCast) {
|
|
t.Errorf("expected cast ref to %s, got %+v", want, edges)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScalaRefFormStaticAccess(t *testing.T) {
|
|
src := `
|
|
package demo
|
|
class User {
|
|
def color(): String = Color.Red
|
|
def max(): Int = Config.MAX
|
|
}
|
|
`
|
|
edges := extractScalaRefEdges(t, src)
|
|
if !scalaHasRefEdge(edges, "Color", graph.EdgeReferences, graph.RefContextStaticAccess) {
|
|
t.Errorf("expected static_access ref to Color, got %+v", edges)
|
|
}
|
|
if !scalaHasRefEdge(edges, "Config", graph.EdgeReferences, graph.RefContextStaticAccess) {
|
|
t.Errorf("expected static_access ref to Config, got %+v", edges)
|
|
}
|
|
}
|
|
|
|
// TestScalaRefFormGenericArgs covers Scala's square-bracket generic arguments
|
|
// (`List[Foo]`, `Map[String, Bar]`, `Future[Seq[Repo]]`, parameterized
|
|
// supertypes) across every position. The type-annotation pass keeps only the
|
|
// container head or the single unwrapped element, so an argument in a non-unwrap
|
|
// slot or nested deeper would never surface to find_usages without these
|
|
// generic_arg reference edges.
|
|
func TestScalaRefFormGenericArgs(t *testing.T) {
|
|
src := `
|
|
package demo
|
|
class Repo extends Base[Scope] with Mixin[Region] {
|
|
val one: List[Widget] = Nil
|
|
var lookup: Map[String, Account] = Map()
|
|
def f(a: Set[Audit], b: Seq[Log]): Option[Result] = None
|
|
val deep: Map[String, List[Invoice]] = Map()
|
|
val nested: Future[Seq[Tenant]] = null
|
|
}
|
|
`
|
|
edges := extractScalaRefEdges(t, src)
|
|
|
|
// Every element type, in every position, surfaces as a generic_arg ref.
|
|
for _, want := range []string{
|
|
"Scope", // supertype type-arg: extends Base[Scope]
|
|
"Region", // mixin type-arg: with Mixin[Region]
|
|
"Widget", // val annotation: List[Widget]
|
|
"Account", // var annotation slot: Map[String, Account]
|
|
"Audit", // parameter: Set[Audit]
|
|
"Log", // parameter: Seq[Log]
|
|
"Result", // return type: Option[Result]
|
|
"Invoice", // deep-nested: Map[String, List[Invoice]]
|
|
"Tenant", // nested: Future[Seq[Tenant]]
|
|
} {
|
|
if !scalaHasRefEdge(edges, want, graph.EdgeReferences, graph.RefContextGenericArg) {
|
|
t.Errorf("expected generic_arg ref to %s, got %+v", want, edges)
|
|
}
|
|
}
|
|
|
|
// The String key of every Map is a primitive — filtered everywhere.
|
|
if countRefEdgesTo(edges, "String") != 0 {
|
|
t.Errorf("primitive String key must not emit a generic_arg ref, got %+v", edges)
|
|
}
|
|
}
|
|
|
|
func TestScalaRefFormNegatives(t *testing.T) {
|
|
src := `
|
|
package demo
|
|
class Thing {
|
|
val owner: Person = null
|
|
def lower(): Int = foo()
|
|
def self(): Int = this.x
|
|
def low(): Int = lowercaseObj.field
|
|
def num(p: Int): Int = p
|
|
def prim(): Boolean = p.isInstanceOf[Int]
|
|
}
|
|
`
|
|
edges := extractScalaRefEdges(t, src)
|
|
|
|
// `foo()` is a lowercase apply — not construction.
|
|
if scalaHasRefEdge(edges, "foo", graph.EdgeInstantiates, "") {
|
|
t.Errorf("lowercase apply foo() must not instantiate, got %+v", edges)
|
|
}
|
|
// `this.x` head is `this` — never static access.
|
|
if countRefEdgesTo(edges, "this") != 0 {
|
|
t.Errorf("this.x must not emit static_access, got %+v", edges)
|
|
}
|
|
// `lowercaseObj.field` head is lowercase — not static access.
|
|
if scalaHasRefEdge(edges, "lowercaseObj", graph.EdgeReferences, graph.RefContextStaticAccess) {
|
|
t.Errorf("lowercase select must not emit static_access, got %+v", edges)
|
|
}
|
|
// `Int` is a primitive — filtered everywhere (annotation, cast, etc.).
|
|
if countRefEdgesTo(edges, "Int") != 0 {
|
|
t.Errorf("primitive Int must not emit any reference-form edge, got %+v", edges)
|
|
}
|
|
// `Person` here is only a val type annotation — owned by #143's typed_as
|
|
// pass, never re-emitted as instantiate/inherit/cast/static_access.
|
|
for _, e := range edges {
|
|
if e.to == "Person" {
|
|
t.Errorf("val type annotation Person must not produce a reference-form edge, got %+v", e)
|
|
}
|
|
}
|
|
}
|