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
175 lines
4.4 KiB
Go
175 lines
4.4 KiB
Go
package languages
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/parser"
|
|
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
|
gotsitter "github.com/zzet/gortex/internal/parser/tsitter/golang"
|
|
)
|
|
|
|
// findGoFuncDecl walks a Go parse tree for the first function_declaration
|
|
// whose name matches and returns its node.
|
|
func findGoFuncDecl(n *sitter.Node, src []byte, name string) *sitter.Node {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
if n.Type() == "function_declaration" {
|
|
if nm := n.ChildByFieldName("name"); nm != nil && nm.Content(src) == name {
|
|
return n
|
|
}
|
|
}
|
|
for i := 0; i < int(n.ChildCount()); i++ {
|
|
if got := findGoFuncDecl(n.Child(i), src, name); got != nil {
|
|
return got
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func goBody(t *testing.T, tree *sitter.Tree, src []byte, name string) *sitter.Node {
|
|
t.Helper()
|
|
decl := findGoFuncDecl(tree.RootNode(), src, name)
|
|
if decl == nil {
|
|
t.Fatalf("function %q not found", name)
|
|
}
|
|
body := goFuncBody(decl)
|
|
if body == nil {
|
|
t.Fatalf("function %q has no body", name)
|
|
}
|
|
return body
|
|
}
|
|
|
|
func TestComplexityMetrics_GoLoopDepthAndCognitive(t *testing.T) {
|
|
src := []byte(`package p
|
|
|
|
func flat(xs []int) int {
|
|
s := 0
|
|
for _, x := range xs {
|
|
s += x
|
|
}
|
|
return s
|
|
}
|
|
|
|
func nested(m [][]int) int {
|
|
s := 0
|
|
for _, row := range m {
|
|
for _, x := range row {
|
|
if x > 0 {
|
|
s += x
|
|
}
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
`)
|
|
tree, err := parser.ParseFile(src, gotsitter.GetLanguage())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tree.Close()
|
|
|
|
flatBody := goBody(t, tree, src, "flat")
|
|
nestedBody := goBody(t, tree, src, "nested")
|
|
|
|
if d := MaxLoopDepth(flatBody, goLoopTypes, goComplexitySkip); d != 1 {
|
|
t.Errorf("flat loop depth = %d, want 1", d)
|
|
}
|
|
if d := MaxLoopDepth(nestedBody, goLoopTypes, goComplexitySkip); d != 2 {
|
|
t.Errorf("nested loop depth = %d, want 2", d)
|
|
}
|
|
|
|
flatCog := CognitiveComplexity(flatBody, goComplexityNodes, goNestingTypes, goComplexitySkip)
|
|
nestedCog := CognitiveComplexity(nestedBody, goComplexityNodes, goNestingTypes, goComplexitySkip)
|
|
if nestedCog <= flatCog {
|
|
t.Errorf("nested cognitive (%d) should exceed flat cognitive (%d)", nestedCog, flatCog)
|
|
}
|
|
if flatCog < 1 {
|
|
t.Errorf("flat cognitive = %d, want >= 1", flatCog)
|
|
}
|
|
}
|
|
|
|
func TestStampFunctionMetrics_Go(t *testing.T) {
|
|
src := []byte(`package p
|
|
|
|
func busy(m [][]int) int {
|
|
s := 0
|
|
for _, row := range m {
|
|
for _, x := range row {
|
|
if x > 0 && x < 100 {
|
|
s += x
|
|
}
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
`)
|
|
tree, err := parser.ParseFile(src, gotsitter.GetLanguage())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tree.Close()
|
|
n := &graph.Node{ID: "p.go::busy", Kind: graph.KindFunction, Name: "busy"}
|
|
StampFunctionMetrics(n, goBody(t, tree, src, "busy"), "go")
|
|
if got, _ := n.Meta["loop_depth"].(int); got != 2 {
|
|
t.Errorf("loop_depth = %v, want 2", n.Meta["loop_depth"])
|
|
}
|
|
if got, _ := n.Meta["cognitive"].(int); got < 2 {
|
|
t.Errorf("cognitive = %v, want >= 2", n.Meta["cognitive"])
|
|
}
|
|
}
|
|
|
|
// TestStampFunctionMetrics_JavaScriptEmitPaths is the regression for the
|
|
// JS-family gap: the standalone JavaScript extractor (.js/.jsx/.mjs/.cjs)
|
|
// emitted function/method nodes with no complexity meta, so every JS
|
|
// function was invisible to analyze kind=bottlenecks even though
|
|
// langComplexityTables carried "javascript"/"jsx" entries. Each of the
|
|
// five emit paths (top-level function, arrow, class method, object-literal
|
|
// method, object arrow field) carries the same doubly-nested loop, so each
|
|
// must stamp loop_depth=2.
|
|
func TestStampFunctionMetrics_JavaScriptEmitPaths(t *testing.T) {
|
|
body := `{
|
|
let s = 0;
|
|
for (const row of m) {
|
|
for (const x of row) {
|
|
if (x > 0) { s += x; }
|
|
}
|
|
}
|
|
return s;
|
|
}`
|
|
src := []byte(`function topLevel(m) ` + body + `
|
|
|
|
const arrowFn = (m) => ` + body + `;
|
|
|
|
class Svc {
|
|
method(m) ` + body + `
|
|
}
|
|
|
|
const api = {
|
|
objMethod(m) ` + body + `,
|
|
objArrow: (m) => ` + body + `,
|
|
};
|
|
`)
|
|
e := NewJavaScriptExtractor()
|
|
result, err := e.Extract("app.js", src)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
stamped := 0
|
|
for _, n := range result.Nodes {
|
|
if n.Kind != graph.KindFunction && n.Kind != graph.KindMethod {
|
|
continue
|
|
}
|
|
d, _ := n.Meta["loop_depth"].(int)
|
|
t.Logf("%s (%s): loop_depth=%v cognitive=%v", n.Name, n.Kind, n.Meta["loop_depth"], n.Meta["cognitive"])
|
|
if d == 2 {
|
|
stamped++
|
|
}
|
|
}
|
|
if stamped < 5 {
|
|
t.Errorf("functions/methods stamped with loop_depth=2 = %d, want >= 5 (one per JS emit path)", stamped)
|
|
}
|
|
}
|