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
6.9 KiB
Go
262 lines
6.9 KiB
Go
package languages
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/parser"
|
|
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
|
"github.com/zzet/gortex/internal/parser/tsitter/scala"
|
|
)
|
|
|
|
func TestScalaAST_Debug(t *testing.T) {
|
|
src := []byte(`import scala.collection.mutable
|
|
|
|
trait Repository {
|
|
def findById(id: String): Option[User]
|
|
def save(user: User): Unit
|
|
}
|
|
|
|
class UserService(repo: Repository) {
|
|
def getUser(id: String): Option[User] = {
|
|
repo.findById(id)
|
|
}
|
|
}
|
|
|
|
object Main {
|
|
def main(args: Array[String]): Unit = {
|
|
println("Hello")
|
|
}
|
|
}
|
|
|
|
case class User(name: String, email: String)
|
|
`)
|
|
lang := scala.GetLanguage()
|
|
tree, err := parser.ParseFile(src, lang)
|
|
require.NoError(t, err)
|
|
defer tree.Close()
|
|
|
|
root := tree.RootNode()
|
|
var walk func(n *sitter.Node, depth int)
|
|
walk = func(n *sitter.Node, depth int) {
|
|
indent := ""
|
|
for i := 0; i < depth; i++ {
|
|
indent += " "
|
|
}
|
|
if n.IsNamed() {
|
|
t.Logf("%s%s [%d:%d - %d:%d] %q", indent, n.Type(),
|
|
n.StartPoint().Row, n.StartPoint().Column,
|
|
n.EndPoint().Row, n.EndPoint().Column,
|
|
truncate(n.Content(src), 80))
|
|
}
|
|
for i := 0; i < int(n.ChildCount()); i++ {
|
|
walk(n.Child(i), depth+1)
|
|
}
|
|
}
|
|
walk(root, 0)
|
|
}
|
|
|
|
func TestScalaExtractor_ClassWithMethods(t *testing.T) {
|
|
src := []byte(`class UserService(repo: Repository) {
|
|
def getUser(id: String): Option[User] = {
|
|
repo.findById(id)
|
|
}
|
|
|
|
def deleteUser(id: String): Unit = {
|
|
repo.delete(id)
|
|
}
|
|
}
|
|
`)
|
|
e := NewScalaExtractor()
|
|
result, err := e.Extract("UserService.scala", src)
|
|
require.NoError(t, err)
|
|
|
|
types := nodesOfKind(result.Nodes, graph.KindType)
|
|
require.Len(t, types, 1)
|
|
assert.Equal(t, "UserService", types[0].Name)
|
|
|
|
methods := nodesOfKind(result.Nodes, graph.KindMethod)
|
|
require.Len(t, methods, 2)
|
|
names := []string{methods[0].Name, methods[1].Name}
|
|
assert.Contains(t, names, "getUser")
|
|
assert.Contains(t, names, "deleteUser")
|
|
|
|
memberEdges := edgesOfKind(result.Edges, graph.EdgeMemberOf)
|
|
require.Len(t, memberEdges, 2)
|
|
for _, edge := range memberEdges {
|
|
assert.Equal(t, "UserService.scala::UserService", edge.To)
|
|
}
|
|
}
|
|
|
|
func TestScalaExtractor_Trait(t *testing.T) {
|
|
src := []byte(`trait Repository {
|
|
def findById(id: String): Option[User]
|
|
def save(user: User): Unit
|
|
}
|
|
`)
|
|
e := NewScalaExtractor()
|
|
result, err := e.Extract("Repository.scala", src)
|
|
require.NoError(t, err)
|
|
|
|
ifaces := nodesOfKind(result.Nodes, graph.KindInterface)
|
|
require.Len(t, ifaces, 1, "expected 1 interface (trait), got %d", len(ifaces))
|
|
assert.Equal(t, "Repository", ifaces[0].Name)
|
|
|
|
// Trait should have method names in Meta.
|
|
meta := ifaces[0].Meta
|
|
require.NotNil(t, meta)
|
|
methodNames, ok := meta["methods"]
|
|
require.True(t, ok, "expected Meta[\"methods\"] on trait")
|
|
methodList, ok := methodNames.([]string)
|
|
require.True(t, ok)
|
|
assert.Contains(t, methodList, "findById")
|
|
assert.Contains(t, methodList, "save")
|
|
}
|
|
|
|
func TestScalaExtractor_Object(t *testing.T) {
|
|
src := []byte(`object Main {
|
|
def main(args: Array[String]): Unit = {
|
|
println("Hello")
|
|
}
|
|
|
|
def helper(): Int = 42
|
|
}
|
|
`)
|
|
e := NewScalaExtractor()
|
|
result, err := e.Extract("Main.scala", src)
|
|
require.NoError(t, err)
|
|
|
|
types := nodesOfKind(result.Nodes, graph.KindType)
|
|
require.Len(t, types, 1)
|
|
assert.Equal(t, "Main", types[0].Name)
|
|
|
|
methods := nodesOfKind(result.Nodes, graph.KindMethod)
|
|
require.Len(t, methods, 2)
|
|
names := []string{methods[0].Name, methods[1].Name}
|
|
assert.Contains(t, names, "main")
|
|
assert.Contains(t, names, "helper")
|
|
|
|
memberEdges := edgesOfKind(result.Edges, graph.EdgeMemberOf)
|
|
require.Len(t, memberEdges, 2)
|
|
for _, edge := range memberEdges {
|
|
assert.Equal(t, "Main.scala::Main", edge.To)
|
|
}
|
|
}
|
|
|
|
func TestScalaExtractor_Imports(t *testing.T) {
|
|
src := []byte(`import scala.collection.mutable
|
|
import java.util.UUID
|
|
import com.example.service.UserService
|
|
|
|
object App {
|
|
def run(): Unit = {}
|
|
}
|
|
`)
|
|
e := NewScalaExtractor()
|
|
result, err := e.Extract("App.scala", src)
|
|
require.NoError(t, err)
|
|
|
|
imports := edgesOfKind(result.Edges, graph.EdgeImports)
|
|
require.Len(t, imports, 3)
|
|
}
|
|
|
|
func TestScalaExtractor_CaseClass(t *testing.T) {
|
|
src := []byte(`case class User(name: String, email: String)
|
|
`)
|
|
e := NewScalaExtractor()
|
|
result, err := e.Extract("User.scala", src)
|
|
require.NoError(t, err)
|
|
|
|
types := nodesOfKind(result.Nodes, graph.KindType)
|
|
require.Len(t, types, 1)
|
|
assert.Equal(t, "User", types[0].Name)
|
|
}
|
|
|
|
func TestScalaExtractor_CallSites(t *testing.T) {
|
|
src := []byte(`object Main {
|
|
def main(args: Array[String]): Unit = {
|
|
println("Hello")
|
|
greet("world")
|
|
}
|
|
|
|
def greet(name: String): Unit = {
|
|
println(name)
|
|
}
|
|
}
|
|
`)
|
|
e := NewScalaExtractor()
|
|
result, err := e.Extract("Main.scala", src)
|
|
require.NoError(t, err)
|
|
|
|
calls := edgesOfKind(result.Edges, graph.EdgeCalls)
|
|
assert.GreaterOrEqual(t, len(calls), 2, "expected at least 2 call edges")
|
|
|
|
targets := make(map[string]bool)
|
|
for _, c := range calls {
|
|
targets[c.To] = true
|
|
}
|
|
assert.True(t, targets["unresolved::*.println"], "missing println call")
|
|
assert.True(t, targets["unresolved::*.greet"], "missing greet call")
|
|
}
|
|
|
|
// Unused import to suppress compiler error for fmt if needed.
|
|
var _ = fmt.Sprint
|
|
|
|
// TestScalaExtensionAndPackageScope is part of the C9 set: a Scala 3 extension
|
|
// method attributes to its receiver type, and package scope is stamped.
|
|
func TestScalaExtensionAndPackageScope(t *testing.T) {
|
|
src := []byte("package com.app.util\n" +
|
|
"extension (s: String)\n" +
|
|
" def shout: String = s.toUpperCase\n")
|
|
res, err := NewScalaExtractor().Extract("e.scala", src)
|
|
require.NoError(t, err)
|
|
var shout *graph.Node
|
|
for _, n := range res.Nodes {
|
|
if n.Name == "shout" {
|
|
shout = n
|
|
}
|
|
}
|
|
require.NotNil(t, shout, "extension method should be a node")
|
|
assert.Equal(t, graph.KindMethod, shout.Kind)
|
|
assert.Equal(t, "String", shout.Meta["receiver"], "attributed to the extended type")
|
|
assert.Equal(t, true, shout.Meta["extension"])
|
|
assert.Equal(t, "com.app.util", shout.Meta["scope_pkg"])
|
|
}
|
|
|
|
func TestScalaExtractor_FactoryChainReceiver(t *testing.T) {
|
|
src := []byte("object M {\n" +
|
|
" def builder(): Widget = new Widget()\n" +
|
|
" def run() = {\n" +
|
|
" builder().withX().build()\n" +
|
|
" }\n" +
|
|
"}\n")
|
|
res, err := NewScalaExtractor().Extract("w.scala", src)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var withX, build *graph.Edge
|
|
for _, e := range res.Edges {
|
|
if e.Kind != graph.EdgeCalls {
|
|
continue
|
|
}
|
|
switch e.To {
|
|
case "unresolved::*.withX":
|
|
withX = e
|
|
case "unresolved::*.build":
|
|
build = e
|
|
}
|
|
}
|
|
if withX == nil || build == nil {
|
|
t.Fatal("withX()/build() call edges not found")
|
|
}
|
|
if withX.Meta["receiver_type"] != "Widget" {
|
|
t.Errorf("withX receiver_type = %v, want Widget (builder() factory)", withX.Meta["receiver_type"])
|
|
}
|
|
if got, _ := build.Meta["receiver_expr"].(string); got != "builder().withX()" {
|
|
t.Errorf("build receiver_expr = %q, want builder().withX()", got)
|
|
}
|
|
}
|