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
176 lines
4.3 KiB
Go
176 lines
4.3 KiB
Go
package languages
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
func TestPascalExtractor_Unit(t *testing.T) {
|
|
src := []byte(`unit Shapes;
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, Classes;
|
|
|
|
type
|
|
TCircle = class
|
|
public
|
|
constructor Create(R: Double);
|
|
function Area: Double;
|
|
end;
|
|
|
|
implementation
|
|
|
|
constructor TCircle.Create(R: Double);
|
|
begin
|
|
end;
|
|
|
|
function TCircle.Area: Double;
|
|
begin
|
|
Result := 3.14 * 1.0;
|
|
end;
|
|
|
|
procedure Hello;
|
|
begin
|
|
WriteLn('hi');
|
|
end;
|
|
|
|
end.
|
|
`)
|
|
e := NewPascalExtractor()
|
|
require.Equal(t, "pascal", e.Language())
|
|
|
|
res, err := e.Extract("Shapes.pas", src)
|
|
require.NoError(t, err)
|
|
|
|
var gotUnit, gotType, gotCreate, gotArea, gotHello bool
|
|
for _, n := range res.Nodes {
|
|
// Methods carry the bare name and a class-qualified ID — the
|
|
// convention shared with every other language — instead of jamming
|
|
// the qualified name into Name as the old regex extractor did.
|
|
switch n.ID {
|
|
case "Shapes.pas::Shapes":
|
|
gotUnit = true
|
|
case "Shapes.pas::TCircle":
|
|
gotType = true
|
|
case "Shapes.pas::TCircle.Create":
|
|
gotCreate = true
|
|
case "Shapes.pas::TCircle.Area":
|
|
gotArea = true
|
|
case "Shapes.pas::Hello":
|
|
gotHello = true
|
|
}
|
|
}
|
|
var gotUses bool
|
|
for _, ed := range res.Edges {
|
|
if ed.Kind == graph.EdgeImports && ed.To == "unresolved::import::SysUtils" {
|
|
gotUses = true
|
|
}
|
|
}
|
|
assert.True(t, gotUnit)
|
|
assert.True(t, gotType)
|
|
assert.True(t, gotCreate)
|
|
assert.True(t, gotArea)
|
|
assert.True(t, gotHello)
|
|
assert.True(t, gotUses)
|
|
}
|
|
|
|
func TestPascalExtractor_EmptyInput(t *testing.T) {
|
|
res, err := NewPascalExtractor().Extract("e.pas", []byte(""))
|
|
require.NoError(t, err)
|
|
require.Len(t, res.Nodes, 1)
|
|
assert.Equal(t, graph.KindFile, res.Nodes[0].Kind)
|
|
}
|
|
|
|
// TestPascalParenlessCallEdges is the C1 named test: the tree-sitter extractor
|
|
// emits call edges for both parenthesised (`Compute(x)`) and paren-less (`Baz;`)
|
|
// calls — the sharpest regression, since the old regex extractor emitted zero —
|
|
// and resolves same-file callees directly to their definition at a higher
|
|
// provenance tier than an unresolved external call.
|
|
func TestPascalParenlessCallEdges(t *testing.T) {
|
|
src := []byte(`unit MyUnit;
|
|
interface
|
|
type
|
|
TFoo = class
|
|
public
|
|
function Bar(x: Integer): string;
|
|
procedure Baz;
|
|
end;
|
|
implementation
|
|
function TFoo.Bar(x: Integer): string;
|
|
begin
|
|
Result := Compute(x);
|
|
Baz;
|
|
end;
|
|
procedure TFoo.Baz;
|
|
begin
|
|
DoThing;
|
|
end;
|
|
end.
|
|
`)
|
|
res, err := NewPascalExtractor().Extract("MyUnit.pas", src)
|
|
require.NoError(t, err)
|
|
|
|
type edge struct {
|
|
from, to string
|
|
origin string
|
|
}
|
|
var calls []edge
|
|
for _, e := range res.Edges {
|
|
if e.Kind == graph.EdgeCalls {
|
|
calls = append(calls, edge{e.From, e.To, e.Origin})
|
|
}
|
|
}
|
|
|
|
has := func(from, to, origin string) bool {
|
|
for _, c := range calls {
|
|
if c.from == from && c.to == to && c.origin == origin {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Parenthesised call to an external proc — unresolved, name-only tier.
|
|
assert.True(t, has("MyUnit.pas::TFoo.Bar", "unresolved::*.Compute", graph.OriginTextMatched),
|
|
"parenthesised call Compute(x) should emit an edge; got %v", calls)
|
|
// Paren-less call to a same-file method — resolved directly, higher tier.
|
|
assert.True(t, has("MyUnit.pas::TFoo.Bar", "MyUnit.pas::TFoo.Baz", graph.OriginASTResolved),
|
|
"paren-less call Baz; should resolve in-file to TFoo.Baz; got %v", calls)
|
|
// Paren-less call to an external proc — unresolved.
|
|
assert.True(t, has("MyUnit.pas::TFoo.Baz", "unresolved::*.DoThing", graph.OriginTextMatched),
|
|
"paren-less call DoThing; should emit an edge; got %v", calls)
|
|
}
|
|
|
|
func TestPascalExtractor_FactoryChainReceiver(t *testing.T) {
|
|
src := []byte("procedure Run;\nbegin\n Builder().WithX().Build();\nend;\n")
|
|
res, err := NewPascalExtractor().Extract("w.pas", src)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var build *graph.Edge
|
|
seen := map[string]int{}
|
|
for _, e := range res.Edges {
|
|
if e.Kind == graph.EdgeCalls {
|
|
seen[e.To]++
|
|
if e.To == "unresolved::*.Build" {
|
|
build = e
|
|
}
|
|
}
|
|
}
|
|
if build == nil {
|
|
t.Fatal("Build() call edge not found")
|
|
}
|
|
if got, _ := build.Meta["receiver_expr"].(string); got != "Builder().WithX()" {
|
|
t.Errorf("receiver_expr = %q, want Builder().WithX()", got)
|
|
}
|
|
if seen["unresolved::*.Build"] != 1 {
|
|
t.Errorf("Build() emitted %d times, want exactly 1", seen["unresolved::*.Build"])
|
|
}
|
|
}
|