/* * test_extraction.c — Regression tests for the extraction module. * * Port of internal/cbm/regression_test.go (1282 LOC, ~80 test cases). * Exercises cbm_extract_file() on code snippets across 30+ languages, * verifying definitions, calls, and imports are correctly extracted. */ #include "test_framework.h" #include "cbm.h" #include "../src/foundation/compat.h" /* cbm_clock_gettime (wide-flat scaling guard) */ #include "../src/foundation/compat_fs.h" #include #include "macro_table.h" #include "iris_export_xml.h" /* ── Helpers ───────────────────────────────────────────────────── */ /* Check if any definition with the given label has the given name. */ static int has_def(CBMFileResult *r, const char *label, const char *name) { for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, label) == 0 && strcmp(r->defs.items[i].name, name) == 0) return 1; } return 0; } /* Check if any definition has the given name (any label). */ static int has_def_any(CBMFileResult *r, const char *name) { for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, name) == 0) return 1; } return 0; } /* Check if any call to the given callee exists. */ static int has_call(CBMFileResult *r, const char *callee) { for (int i = 0; i < r->calls.count; i++) { if (strstr(r->calls.items[i].callee_name, callee) != NULL) return 1; } return 0; } /* Check if any import with the given module path exists. */ static int __attribute__((unused)) has_import(CBMFileResult *r, const char *path_substr) { for (int i = 0; i < r->imports.count; i++) { if (r->imports.items[i].module_path && strstr(r->imports.items[i].module_path, path_substr) != NULL) return 1; } return 0; } /* Count definitions with a given label. */ /* Check for a definition with the given qualified name. Distinct from * find_def_by_name, which returns the first match by NAME and so cannot tell two * same-named definitions in different scopes apart — exactly the case that * attrpath qualification exists to separate. */ static int has_def_qn(CBMFileResult *r, const char *qn) { for (int i = 0; i < r->defs.count; i++) { if (r->defs.items[i].qualified_name && strcmp(r->defs.items[i].qualified_name, qn) == 0) return 1; } return 0; } static int count_defs_with_label(CBMFileResult *r, const char *label) { int count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, label) == 0) count++; } return count; } /* Convenience: extract, assert no error, return result. Caller frees. */ static CBMFileResult *extract(const char *src, CBMLanguage lang, const char *proj, const char *path) { CBMFileResult *r = cbm_extract_file(src, (int)strlen(src), lang, proj, path, 0, NULL, NULL); return r; } /* As extract(), but threads an ObjectScript macro table through. */ static CBMFileResult *extract_with_macros(const char *src, CBMLanguage lang, const char *proj, const char *path, const CBMMacroTable *mt) { CBMFileResult *r = cbm_extract_file_ex(src, (int)strlen(src), lang, proj, path, 0, NULL, NULL, mt, NULL); return r; } /* ═══════════════════════════════════════════════════════════════════ * Group A: OOP Languages * ═══════════════════════════════════════════════════════════════════ */ /* --- R: box::use imports (#218) + module$fn calls (#219) --- */ TEST(extract_r_box_use_imports_issue218) { CBMFileResult *r = extract("box::use(\n" " shiny[moduleServer, NS],\n" " app/logic/validation[validate_input],\n" ")\n" "library(dplyr)\n" "source(\"helpers.R\")\n", CBM_LANG_R, "t", "app.R"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* box::use specs → one IMPORTS edge per module (symbol list stripped). */ ASSERT(has_import(r, "shiny")); ASSERT(has_import(r, "app/logic/validation")); /* base-R imports work too. */ ASSERT(has_import(r, "dplyr")); ASSERT(has_import(r, "helpers")); cbm_free_result(r); PASS(); } TEST(extract_r_dollar_call_issue219) { CBMFileResult *r = extract("validation$validate_input(x)\n", CBM_LANG_R, "t", "app.R"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* module$fn() now produces a CALLS edge (was silently dropped). */ ASSERT(has_call(r, "validation.validate_input")); cbm_free_result(r); PASS(); } /* --- TS: object-literal arrow methods from a factory (Zustand, #341) --- */ TEST(extract_ts_factory_object_methods_issue341) { CBMFileResult *r = extract("export function createItemActions(set, get) {\n" " return {\n" " addItem: (type, id) => { return 1; },\n" " moveItem: (id, target) => { return 2; },\n" " deleteItem: (id) => { return 3; },\n" " };\n" "}\n", CBM_LANG_TYPESCRIPT, "t", "item-actions.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* The factory itself + each returned arrow method are Function nodes. */ ASSERT(has_def_any(r, "createItemActions")); ASSERT(has_def_any(r, "addItem")); ASSERT(has_def_any(r, "moveItem")); ASSERT(has_def_any(r, "deleteItem")); cbm_free_result(r); PASS(); } /* --- C/C++ preprocessor macros become Macro nodes (#375) --- */ TEST(extract_c_macros_issue375) { CBMFileResult *r = extract("#define SIMPLE_MACRO 1\n" "#define FN_MACRO(x) (2 * (x))\n" "#define EMPTY_MACRO\n" "int main(void) { return FN_MACRO(SIMPLE_MACRO); }\n", CBM_LANG_C, "p", "macros.c"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Macro", "SIMPLE_MACRO")); ASSERT(has_def(r, "Macro", "FN_MACRO")); ASSERT(has_def(r, "Macro", "EMPTY_MACRO")); ASSERT(has_def(r, "Function", "main")); /* macros don't displace function defs */ cbm_free_result(r); PASS(); } TEST(extract_cpp_macros_issue375) { CBMFileResult *r = extract("#define MAX(a, b) ((a) > (b) ? (a) : (b))\n" "#define PI 3.14159\n" "namespace n {\n" "int f() { return MAX(1, 2); }\n" "}\n", CBM_LANG_CPP, "p", "macros.cpp"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Macro", "MAX")); ASSERT(has_def(r, "Macro", "PI")); cbm_free_result(r); PASS(); } /* #1071: a function-like macro invocation whose argument is a TYPE token * (SYNTH_ALLOC_ARRAY(char, n)) makes tree-sitter's C++ grammar emit an ERROR * node — it parses `char` in expression position — which cbm_collect_error_regions * records as a `parse_partial` coverage gap, even though the file is a valid, * in-file macro use with nothing actually missing from the graph. */ TEST(extract_cpp_functionlike_macro_type_arg_no_false_parse_partial_issue1071) { CBMFileResult *r = extract("#include \n" "#include \n" "\n" "#define SYNTH_ALLOC_ARRAY(Type, Count) \\\n" " ((Type*)std::malloc(sizeof(Type) * (Count)))\n" "\n" "struct Buffer {\n" " char* data;\n" " std::size_t size;\n" "};\n" "\n" "Buffer make_buffer(std::size_t n) {\n" " Buffer b;\n" " b.data = SYNTH_ALLOC_ARRAY(char, n);\n" " b.size = n;\n" " return b;\n" "}\n", CBM_LANG_CPP, "t", "alloc.cpp"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->parse_incomplete); /* benign in-body macro call — not a coverage gap */ ASSERT(has_def(r, "Function", "make_buffer")); ASSERT(has_def(r, "Macro", "SYNTH_ALLOC_ARRAY")); cbm_free_result(r); PASS(); } /* #1071 guard: the suppression must be tight. A REAL parse error inside a * function (not a macro call) must STILL be flagged, and a top-level macro * invocation is covered by extract_cpp_preproc_macro_generated_callable_skipped_issue949. */ TEST(extract_cpp_real_in_body_error_still_flagged_issue1071) { /* `int x = ;` is a genuine syntax error inside foo()'s body — no macro * involved, so the coverage gap must not be suppressed. */ CBMFileResult *r = extract("int foo() {\n" " int x = ;\n" " return x;\n" "}\n", CBM_LANG_CPP, "t", "broken.cpp"); ASSERT_NOT_NULL(r); ASSERT_TRUE(r->parse_incomplete); /* real gap stays reported */ cbm_free_result(r); PASS(); } /* --- GDScript: AST -> graph visitor (Godot, #186) --- */ TEST(extract_gdscript_issue186) { CBMFileResult *r = extract("extends Node\n" "class_name Player\n" "\n" "var health = 100\n" "\n" "func _ready():\n" " take_damage(10)\n" "\n" "func take_damage(amount):\n" " health -= amount\n" "\n" "class Inner:\n" " func helper():\n" " pass\n", CBM_LANG_GDSCRIPT, "game", "player.gd"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "_ready")); ASSERT(has_def(r, "Function", "take_damage")); ASSERT(has_def(r, "Class", "Inner")); ASSERT(has_call(r, "take_damage")); cbm_free_result(r); PASS(); } /* --- PowerShell: AST -> graph visitor (#35) --- */ TEST(extract_powershell_issue35) { CBMFileResult *r = extract("function Get-Greeting {\n" " param($Name)\n" " Write-Output \"Hello $Name\"\n" "}\n" "\n" "function Set-Config {\n" " Get-Greeting -Name 'World'\n" "}\n", CBM_LANG_POWERSHELL, "ops", "greet.ps1"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(count_defs_with_label(r, "Function") >= 2); cbm_free_result(r); PASS(); } /* --- Luau: AST -> graph visitor (Roblox, #39) --- */ TEST(extract_luau_issue39) { CBMFileResult *r = extract("local function add(a, b)\n" " return a + b\n" "end\n" "\n" "function multiply(a, b)\n" " return add(a, a) * b\n" "end\n", CBM_LANG_LUAU, "game", "math.luau"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(count_defs_with_label(r, "Function") >= 2); cbm_free_result(r); PASS(); } /* --- QML: AST -> graph visitor (Qt, #42) --- */ TEST(extract_qml_issue42) { CBMFileResult *r = extract("import QtQuick 2.15\n" "\n" "Rectangle {\n" " id: root\n" " width: 100\n" " property int counter: 0\n" " signal clicked(int value)\n" "\n" " function increment() {\n" " counter += 1\n" " compute(counter)\n" " }\n" "\n" " function compute(n) {\n" " return n * 2\n" " }\n" "}\n", CBM_LANG_QML, "app", "Main.qml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "increment")); ASSERT(has_def(r, "Function", "compute")); ASSERT(has_call(r, "compute")); cbm_free_result(r); PASS(); } /* --- CFML script dialect: .cfc components (Lucee/ColdFusion, #38) --- */ TEST(extract_cfscript_issue38) { CBMFileResult *r = extract("component {\n" " public function getUser(numeric id) {\n" " return loadUser(id);\n" " }\n" " function loadUser(id) {\n" " return id * 2;\n" " }\n" "}\n", CBM_LANG_CFSCRIPT, "app", "User.cfc"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(count_defs_with_label(r, "Function") >= 2); ASSERT(has_def(r, "Function", "getUser")); ASSERT(has_def(r, "Function", "loadUser")); ASSERT(has_call(r, "loadUser")); cbm_free_result(r); PASS(); } /* --- CFML tag dialect: .cfm templates with (#38) --- */ TEST(extract_cfml_tag_issue38) { CBMFileResult *r = extract("\n" " \n" " \n" "\n", CBM_LANG_CFML, "app", "index.cfm"); ASSERT_NOT_NULL(r); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } /* --- Helm / Go template: named templates + include calls (#338) --- */ TEST(extract_helm_templates_issue338) { CBMFileResult *r = extract("{{- define \"chart.fullname\" -}}\n" "{{- .Release.Name -}}\n" "{{- end -}}\n" "\n" "{{- define \"chart.labels\" -}}\n" "app: {{ include \"chart.fullname\" . }}\n" "chart: {{ template \"chart.fullname\" . }}\n" "{{- end -}}\n", CBM_LANG_GOTEMPLATE, "chart", "templates/_helpers.tpl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* define -> Function nodes */ ASSERT(has_def(r, "Function", "chart.fullname")); ASSERT(has_def(r, "Function", "chart.labels")); /* include / template -> CALLS to the named template (not to "include") */ ASSERT(has_call(r, "chart.fullname")); cbm_free_result(r); PASS(); } /* --- Helm values.yaml: top-level keys only, no leaf flood (#338) --- */ TEST(extract_helm_values_toplevel_issue338) { CBMFileResult *r = extract("image:\n" " repository: nginx\n" " tag: latest\n" "replicaCount: 3\n" "service:\n" " port: 80\n", CBM_LANG_YAML, "chart", "values.yaml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Variable", "image")); ASSERT(has_def(r, "Variable", "replicaCount")); ASSERT(has_def(r, "Variable", "service")); /* Nested leaf keys must NOT explode into separate nodes. */ ASSERT(!has_def(r, "Variable", "repository")); ASSERT(!has_def(r, "Variable", "tag")); ASSERT(!has_def(r, "Variable", "port")); ASSERT_EQ(count_defs_with_label(r, "Variable"), 3); cbm_free_result(r); PASS(); } /* --- Java --- */ TEST(java_class) { CBMFileResult *r = extract( "public class Animal { private String name; public String getName() { return name; } }", CBM_LANG_JAVA, "t", "Animal.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Animal")); cbm_free_result(r); PASS(); } TEST(java_method) { CBMFileResult *r = extract( "public class Svc { public void doWork() {} public int compute(int x) { return x; } }", CBM_LANG_JAVA, "t", "Svc.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Method", "doWork")); ASSERT(has_def(r, "Method", "compute")); cbm_free_result(r); PASS(); } TEST(java_interface) { CBMFileResult *r = extract("public interface Repository { void save(Object o); Object findById(long id); }", CBM_LANG_JAVA, "t", "Repo.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def_any(r, "Repository")); cbm_free_result(r); PASS(); } /* Regression for #1234: Java interface/enum methods were emitted as both a * Method node (correct, via extract_class_methods) and a duplicate Function * node (incorrect, via walk_defs). Prevention in push_class_body_children * (gated to Java) recognizes interface_body and enum_body as class body * containers, stopping the fallback path from re-walking method_declaration * children as top-level functions. */ TEST(java_interface_no_duplicate_function_issue1234) { CBMFileResult *r = extract("public interface MarketplaceService {\n" " ReservationDTO createReservation(Authentication auth, RequestDTO req);\n" " void cancelReservation(long id);\n" "}\n", CBM_LANG_JAVA, "t", "MarketplaceService.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Interface", "MarketplaceService")); ASSERT(has_def(r, "Method", "createReservation")); ASSERT(has_def(r, "Method", "cancelReservation")); ASSERT_EQ(count_defs_with_label(r, "Function"), 0); cbm_free_result(r); PASS(); } TEST(java_enum_dedup_preserves_calls_issue1234) { CBMFileResult *r = extract("package app;\n\nenum Day {\n" " MON, TUE, WED, THU, FRI, SAT, SUN;\n\n" " public boolean isWeekend() { return this == SAT || this == SUN; }\n" " public String label() { return name().toLowerCase(); }\n}\n\n" "class DayUtil {\n" " static String describe(Day d) {\n" " return d.label() + (d.isWeekend() ? \"(rest)\" : \"(work)\");\n }\n}\n", CBM_LANG_JAVA, "t", "Day.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Enum", "Day")); ASSERT(has_def(r, "Method", "isWeekend")); ASSERT(has_def(r, "Method", "label")); /* The enum CONSTANTS must survive alongside the methods. Reaching the * methods means descending into enum_body_declarations, and the tempting * way to do that -- redirecting the shared find_class_body -- also makes * the constants unreachable, because they are siblings of that node rather * than children. find_class_member_body exists to descend for members only * and leave find_class_body (which extract_enum_members uses) alone; these * two assertions are what stop that distinction being collapsed again. */ ASSERT(has_def(r, "Variable", "MON")); ASSERT(has_def(r, "Variable", "SUN")); ASSERT(has_def(r, "Class", "DayUtil")); ASSERT(has_def(r, "Method", "describe")); ASSERT_EQ(count_defs_with_label(r, "Function"), 0); cbm_free_result(r); PASS(); } /* Regression for #279: a Java class declaring both `extends` and * `implements` must produce one INHERITS edge per base — the extends parent * AND every implements interface — with bare type names (not the keyword * text "extends Bar" / "implements Baz, Qux"). Before the fix: * 1) the field loop returned on the first match → only the superclass * was emitted, the interfaces were dropped. * 2) the emitted name was the full field text including the keyword. */ TEST(java_class_extends_and_implements) { CBMFileResult *r = extract("public class DefaultLinkTool extends DefaultDiagramTool implements " "ILinkTool, Closeable { }", CBM_LANG_JAVA, "t", "DefaultLinkTool.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Find the class def and inspect its base_classes list. */ CBMDefinition *cls = NULL; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Class") == 0 && strcmp(r->defs.items[i].name, "DefaultLinkTool") == 0) { cls = &r->defs.items[i]; break; } } ASSERT_NOT_NULL(cls); ASSERT_NOT_NULL(cls->base_classes); bool saw_super = false; bool saw_iface_a = false; bool saw_iface_b = false; for (const char **b = cls->base_classes; *b; b++) { /* The keyword-text bug would surface as "extends ..." or * "implements ..." literally inside one of the entries. */ ASSERT_NULL(strstr(*b, "extends")); ASSERT_NULL(strstr(*b, "implements")); if (strcmp(*b, "DefaultDiagramTool") == 0) saw_super = true; if (strcmp(*b, "ILinkTool") == 0) saw_iface_a = true; if (strcmp(*b, "Closeable") == 0) saw_iface_b = true; } ASSERT_TRUE(saw_super); ASSERT_TRUE(saw_iface_a); ASSERT_TRUE(saw_iface_b); cbm_free_result(r); PASS(); } /* REPRODUCTION (RED until fixed) — Python `class Animal(Base):` must extract the * BARE base name "Base", but extract_base_classes captures the whole * `superclasses` argument_list text "(Base)" instead: collect_bases_from_field * (internal/cbm/extract_defs.c) matches only type_identifier / generic_type / * qualified_name / scoped_type_identifier / user_type, while tree-sitter-python * uses a plain `identifier` node for the base — so no child matches and the * raw-field fallback grabs the argument_list text "(Base)" (parens included). * DOWNSTREAM SYMPTOM: that malformed name never resolves to the Base class node, * so EVERY Python subclass yields ZERO INHERITS edges (observed in the P6 * graph-contract suite). Fix: have collect_bases_from_field accept `identifier` * (or strip the argument_list parens). This test stays RED as the regression * guard / reproduction until the fix lands — see CLAUDE.md "Bug Fixing — * Reproduce-First". */ TEST(python_class_base_extracted_bare) { CBMFileResult *r = extract("class Base:\n pass\n\n\nclass Animal(Base):\n pass\n", CBM_LANG_PYTHON, "t", "models.py"); ASSERT_NOT_NULL(r); CBMDefinition *cls = NULL; for (int i = 0; i < r->defs.count; i++) { if (r->defs.items[i].label && strcmp(r->defs.items[i].label, "Class") == 0 && r->defs.items[i].name && strcmp(r->defs.items[i].name, "Animal") == 0) { cls = &r->defs.items[i]; break; } } ASSERT_NOT_NULL(cls); ASSERT_NOT_NULL(cls->base_classes); /* a subclass must record at least one base */ bool saw_bare_base = false; for (const char **b = cls->base_classes; *b; b++) { if (strcmp(*b, "Base") == 0) { saw_bare_base = true; } } /* CURRENTLY FAILS: base_classes holds "(Base)" (argument_list text), not the * bare "Base" needed for INHERITS resolution. */ ASSERT_TRUE(saw_bare_base); cbm_free_result(r); PASS(); } /* --- PHP --- */ TEST(php_class) { CBMFileResult *r = extract("name; } }", CBM_LANG_PHP, "t", "User.php"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "User")); ASSERT(has_def(r, "Method", "getName")); cbm_free_result(r); PASS(); } TEST(php_function) { CBMFileResult *r = extract("has_error); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } /* --- Ruby --- */ TEST(ruby_class) { CBMFileResult *r = extract("class Animal\n def initialize(name)\n @name = name\n end\n " "def speak\n puts @name\n end\nend\n", CBM_LANG_RUBY, "t", "animal.rb"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Animal")); ASSERT(has_def(r, "Method", "speak")); cbm_free_result(r); PASS(); } TEST(ruby_module) { CBMFileResult *r = extract("module Greetable\n def greet\n \"Hello\"\n end\nend\n", CBM_LANG_RUBY, "t", "greetable.rb"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def_any(r, "Greetable")); cbm_free_result(r); PASS(); } /* --- C# --- */ TEST(csharp_class) { CBMFileResult *r = extract("namespace App { public class Service { public void Run() {} public " "int Compute(int x) => x * 2; } }", CBM_LANG_CSHARP, "t", "Service.cs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Service")); ASSERT(has_def(r, "Method", "Run")); cbm_free_result(r); PASS(); } TEST(csharp_interface) { CBMFileResult *r = extract("public interface IService { void Execute(); string GetStatus(); }", CBM_LANG_CSHARP, "t", "IService.cs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def_any(r, "IService")); cbm_free_result(r); PASS(); } /* --- Swift --- */ TEST(swift_class) { CBMFileResult *r = extract("class Vehicle {\n var speed: Int = 0\n func accelerate() { " "speed += 10 }\n func stop() { speed = 0 }\n}\n", CBM_LANG_SWIFT, "t", "Vehicle.swift"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Vehicle")); ASSERT(has_def(r, "Method", "accelerate")); cbm_free_result(r); PASS(); } TEST(swift_protocol) { /* A protocol requirement is a bodyless func inside a protocol body. Swift * codebases are heavily protocol-driven, so the requirement is very often * the declaration a reader is actually looking for — before this it was * absent from the graph entirely. */ CBMFileResult *r = extract("protocol StudyRunning {\n func generate() -> String\n}\n", CBM_LANG_SWIFT, "t", "StudyRunning.swift"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Interface", "StudyRunning")); ASSERT(has_def(r, "Method", "generate")); cbm_free_result(r); PASS(); } /* --- Kotlin --- */ TEST(kotlin_function) { CBMFileResult *r = extract("fun greet(name: String): String = \"Hello $name\"\nfun main() { " "println(greet(\"World\")) }\n", CBM_LANG_KOTLIN, "t", "main.kt"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "greet")); ASSERT(has_def(r, "Function", "main")); cbm_free_result(r); PASS(); } TEST(kotlin_class) { CBMFileResult *r = extract("class User(val name: String) {\n fun display(): String = \"User: $name\"\n}\n", CBM_LANG_KOTLIN, "t", "User.kt"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "User")); cbm_free_result(r); PASS(); } /* --- Scala --- */ TEST(scala_function) { CBMFileResult *r = extract("object Main {\n def greet(name: String): String = s\"Hello $name\"\n def " "main(args: Array[String]): Unit = println(greet(\"World\"))\n}\n", CBM_LANG_SCALA, "t", "Main.scala"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Method", "greet")); cbm_free_result(r); PASS(); } TEST(scala_class) { CBMFileResult *r = extract("class Animal(val name: String) {\n def speak(): String = s\"I am $name\"\n}\n", CBM_LANG_SCALA, "t", "Animal.scala"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Animal")); cbm_free_result(r); PASS(); } /* --- Dart --- */ TEST(dart_class) { CBMFileResult *r = extract("class Animal {\n String name;\n Animal(this.name);\n String " "speak() => 'I am $name';\n}\n", CBM_LANG_DART, "t", "animal.dart"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Animal")); ASSERT(has_def(r, "Method", "speak")); cbm_free_result(r); PASS(); } /* --- Groovy --- */ TEST(groovy_class) { CBMFileResult *r = extract("class Greeter {\n String name\n String greet() { \"Hello, $name\" }\n " "static void main(args) { println new Greeter(name:'World').greet() }\n}\n", CBM_LANG_GROOVY, "t", "Greeter.groovy"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Greeter")); ASSERT(has_def(r, "Method", "greet")); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group B: Systems Languages * ═══════════════════════════════════════════════════════════════════ */ /* --- Rust --- */ TEST(rust_function) { CBMFileResult *r = extract("fn main() { println!(\"Hello\"); }\npub fn add(a: i32, b: i32) -> i32 { a + b }\n", CBM_LANG_RUST, "t", "main.rs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "main")); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } TEST(rust_struct) { CBMFileResult *r = extract("pub struct Point { pub x: f64, pub y: f64 }\nimpl Point { pub fn " "new(x: f64, y: f64) -> Self { Point { x, y } } }\n", CBM_LANG_RUST, "t", "point.rs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Struct", "Point")); ASSERT(has_def(r, "Method", "new")); cbm_free_result(r); PASS(); } /* --- Go --- */ TEST(go_function) { CBMFileResult *r = extract("package main\nfunc Greet(name string) string { return \"Hello, \" " "+ name }\nfunc main() { Greet(\"World\") }\n", CBM_LANG_GO, "t", "main.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "Greet")); ASSERT(has_def(r, "Function", "main")); cbm_free_result(r); PASS(); } TEST(go_struct) { CBMFileResult *r = extract("package main\ntype Server struct { Host string; Port int }\nfunc " "(s *Server) Start() error { return nil }\n", CBM_LANG_GO, "t", "server.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Struct", "Server")); ASSERT(has_def(r, "Method", "Start")); cbm_free_result(r); PASS(); } TEST(go_interface) { CBMFileResult *r = extract("package main\ntype Handler interface { ServeHTTP() error; Close() }\n", CBM_LANG_GO, "t", "handler.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def_any(r, "Handler")); cbm_free_result(r); PASS(); } /* --- Zig --- */ TEST(zig_function) { CBMFileResult *r = extract("const std = @import(\"std\");\npub fn add(a: i32, b: i32) i32 { return a + b; }\n", CBM_LANG_ZIG, "t", "main.zig"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } /* --- C --- */ TEST(c_function) { CBMFileResult *r = extract("int add(int a, int b) { return a + b; }\nvoid greet() { printf(\"Hello\"); }\n", CBM_LANG_C, "t", "math.c"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "add")); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } TEST(c_struct) { CBMFileResult *r = extract("struct Point { int x; int y; };\nvoid init_point(struct Point *p) { p->x = 0; }\n", CBM_LANG_C, "t", "point.c"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "init_point")); cbm_free_result(r); PASS(); } /* --- C++ --- */ TEST(cpp_class) { CBMFileResult *r = extract( "class Widget {\npublic:\n void draw() {}\n int width() const { return 0; }\n};\n", CBM_LANG_CPP, "t", "widget.cpp"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Widget")); ASSERT(has_def(r, "Method", "draw")); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group C: Scripting / Dynamic Languages * ═══════════════════════════════════════════════════════════════════ */ /* --- Python --- */ TEST(python_function) { CBMFileResult *r = extract( "def greet(name):\n return f\"Hello {name}\"\n\ndef main():\n greet(\"World\")\n", CBM_LANG_PYTHON, "t", "main.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "greet")); ASSERT(has_def(r, "Function", "main")); cbm_free_result(r); PASS(); } TEST(python_class) { CBMFileResult *r = extract("class Dog:\n def __init__(self, name):\n self.name = name\n def " "speak(self):\n return f\"Woof from {self.name}\"\n", CBM_LANG_PYTHON, "t", "dog.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Dog")); ASSERT(has_def(r, "Method", "speak")); cbm_free_result(r); PASS(); } /* --- JavaScript --- */ TEST(js_function) { CBMFileResult *r = extract("function greet(name) { return `Hello ${name}`; }\nconst add = (a, b) => a + b;\n", CBM_LANG_JAVASCRIPT, "t", "util.js"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } TEST(js_class) { CBMFileResult *r = extract("class Counter {\n constructor() { this.count = 0; }\n increment() { " "this.count++; }\n get value() { return this.count; }\n}\n", CBM_LANG_JAVASCRIPT, "t", "counter.js"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Counter")); ASSERT(has_def(r, "Method", "increment")); cbm_free_result(r); PASS(); } /* --- TypeScript --- */ TEST(ts_function) { CBMFileResult *r = extract("export function greet(name: string): string { return `Hello " "${name}`; }\nfunction helper(): void {}\n", CBM_LANG_TYPESCRIPT, "t", "util.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } TEST(ts_class) { CBMFileResult *r = extract("class Service {\n private name: string;\n constructor(name: string) { this.name " "= name; }\n getName(): string { return this.name; }\n}\n", CBM_LANG_TYPESCRIPT, "t", "service.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Service")); cbm_free_result(r); PASS(); } TEST(body_tokens_type_identifier) { CBMFileResult *r = extract("function serialize(obj: MyModel): SerializedResult {\n" " const result: SerializedResult = new SerializedResult();\n" " return result;\n" "}\n", CBM_LANG_TYPESCRIPT, "t", "serial.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "serialize") == 0) { ASSERT_NOT_NULL(r->defs.items[i].body_tokens); ASSERT(strstr(r->defs.items[i].body_tokens, "SerializedResult") != NULL); break; } } cbm_free_result(r); PASS(); } /* --- Lua --- */ TEST(lua_function) { CBMFileResult *r = extract( "function greet(name)\n return \"Hello \" .. name\nend\nlocal function helper() end\n", CBM_LANG_LUA, "t", "main.lua"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } /* --- Bash --- */ TEST(bash_function) { CBMFileResult *r = extract("greet() {\n echo \"Hello $1\"\n}\nmain() {\n greet \"World\"\n}\n", CBM_LANG_BASH, "t", "script.sh"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } /* --- Perl --- */ TEST(perl_function) { CBMFileResult *r = extract("sub greet {\n my ($name) = @_;\n return \"Hello " "$name\";\n}\nsub main { greet(\"World\"); }\n", CBM_LANG_PERL, "t", "main.pl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } /* --- R --- */ TEST(r_function) { CBMFileResult *r = extract("add <- function(x, y) x + y\nmultiply <- function(x, y) x * y\n", CBM_LANG_R, "t", "math.R"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group D: Functional Languages * ═══════════════════════════════════════════════════════════════════ */ /* --- Elixir --- */ TEST(elixir_function) { CBMFileResult *r = extract("defmodule Greeter do\n def greet(name), do: \"Hello #{name}\"\n " "defp helper, do: nil\nend\n", CBM_LANG_ELIXIR, "t", "greeter.ex"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } /* --- Haskell --- */ TEST(haskell_function) { CBMFileResult *r = extract("add :: Int -> Int -> Int\nadd x y = x + y\n\nmultiply :: Int -> " "Int -> Int\nmultiply x y = x * y\n", CBM_LANG_HASKELL, "t", "Math.hs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } /* --- OCaml --- */ TEST(ocaml_function) { CBMFileResult *r = extract("let add x y = x + y\nlet multiply x y = x * y\n", CBM_LANG_OCAML, "t", "math.ml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } /* --- Erlang --- */ TEST(erlang_function) { CBMFileResult *r = extract( "-module(math).\n-export([add/2]).\nadd(X, Y) -> X + Y.\nmultiply(X, Y) -> X * Y.\n", CBM_LANG_ERLANG, "t", "math.erl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group E: Markup / Config / Helper Languages * ═══════════════════════════════════════════════════════════════════ */ /* --- YAML --- */ TEST(yaml_variables) { CBMFileResult *r = extract("name: myapp\nversion: 1.0\ndatabase:\n host: localhost\n port: 5432\n", CBM_LANG_YAML, "t", "config.yml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* YAML should extract top-level keys as variables */ ASSERT_GT(r->defs.count, 0); cbm_free_result(r); PASS(); } /* --- HCL --- */ TEST(hcl_blocks) { CBMFileResult *r = extract("resource \"aws_instance\" \"web\" {\n ami = \"abc-123\"\n " "instance_type = \"t2.micro\"\n}\n" "variable \"region\" {\n default = \"us-east-1\"\n}\n", CBM_LANG_HCL, "t", "main.tf"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->defs.count, 0); /* Block labels are folded into the name so blocks are distinguishable (#337). */ ASSERT(has_def(r, "Class", "resource.aws_instance.web")); ASSERT(has_def(r, "Class", "variable.region")); cbm_free_result(r); PASS(); } /* --- SQL --- */ TEST(sql_create_table) { CBMFileResult *r = extract("CREATE TABLE users (\n id INTEGER PRIMARY KEY,\n name TEXT NOT " "NULL\n);\nCREATE VIEW active_users AS SELECT * FROM users;\n", CBM_LANG_SQL, "t", "schema.sql"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } /* --- Dockerfile --- */ TEST(dockerfile_stages) { CBMFileResult *r = extract( "FROM node:18 AS builder\nRUN npm install\nFROM node:18-slim\nCOPY --from=builder /app .\n", CBM_LANG_DOCKERFILE, "t", "Dockerfile"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group F: Scientific / Math Languages * ═══════════════════════════════════════════════════════════════════ */ /* --- MATLAB --- */ TEST(matlab_function) { CBMFileResult *r = extract("function y = square(x)\n y = x.^2;\nend\n", CBM_LANG_MATLAB, "t", "square.m"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "square")); cbm_free_result(r); PASS(); } /* --- Lean 4 --- */ TEST(lean_function) { CBMFileResult *r = extract("def add (x y : Nat) : Nat := x + y\n", CBM_LANG_LEAN, "t", "Math.lean"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } /* --- FORM --- */ TEST(form_procedure) { CBMFileResult *r = extract("#procedure doSomething\n id x = y;\n#endprocedure\n", CBM_LANG_FORM, "t", "test.frm"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "doSomething")); cbm_free_result(r); PASS(); } /* --- Wolfram --- */ TEST(wolfram_function) { CBMFileResult *r = extract("square[x_] := x^2\nadd[x_, y_] := x + y\n", CBM_LANG_WOLFRAM, "t", "math.wl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "square")); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } /* --- Magma --- */ TEST(magma_function) { CBMFileResult *r = extract("function Factorial(n)\n if n le 1 then\n return 1;\n end " "if;\n return n * Factorial(n - 1);\nend function;\n", CBM_LANG_MAGMA, "t", "test.m"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "Factorial")); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group G: v0.5 Expansion Languages * ═══════════════════════════════════════════════════════════════════ */ /* --- F# --- */ TEST(fsharp_function) { /* Go test only asserts >=1 def — F# name extraction is incomplete */ CBMFileResult *r = extract("module Greeter\nlet greet name = sprintf \"Hello %s\" name\n", CBM_LANG_FSHARP, "t", "Greeter.fs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- Julia --- */ TEST(julia_function) { CBMFileResult *r = extract("function add(x, y)\n x + y\nend\nadd2(x, y) = x + y\n", CBM_LANG_JULIA, "t", "math.jl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } /* --- Elm --- */ TEST(elm_function) { CBMFileResult *r = extract("add x y = x + y\nmultiply x y = x * y\n", CBM_LANG_ELM, "t", "Math.elm"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "add")); cbm_free_result(r); PASS(); } /* --- Nix --- */ TEST(nix_function) { CBMFileResult *r = extract("{ pkgs ? import {} }:\nlet\n hello = pkgs.writeShellScriptBin " "\"hello\" ''echo hello'';\nin { inherit hello; }\n", CBM_LANG_NIX, "t", "default.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } /* A Nix file's root expression is normally itself a function (`{ pkgs, lib, ... }:`) * — the near-universal library/module header. Definitions must survive that header. * Each shape below is asserted separately so a regression names the shape it broke. * `nix_function` above deliberately stays as-is: its binding is an application, not * a function, so it pins the "parses, no def" case and cannot cover this. */ TEST(nix_defs_in_let_rooted_file) { CBMFileResult *r = extract("let\n alpha = x: x + 1;\n beta = { a, b }: a + b;\nin\n" "{ inherit alpha beta; }\n", CBM_LANG_NIX, "t", "bare.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "alpha")); ASSERT(has_def(r, "Function", "beta")); cbm_free_result(r); PASS(); } TEST(nix_defs_in_attrset_rooted_file) { CBMFileResult *r = extract("{\n epsilon = x: x + 1;\n zeta = { a, b }: a + b;\n}\n", CBM_LANG_NIX, "t", "attrset.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "epsilon")); ASSERT(has_def(r, "Function", "zeta")); cbm_free_result(r); PASS(); } TEST(nix_defs_in_nested_let) { CBMFileResult *r = extract("let\n outer =\n let theta = x: x + 1;\n in theta;\n" "in\n{ inherit outer; }\n", CBM_LANG_NIX, "t", "nested.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "theta")); cbm_free_result(r); PASS(); } /* The regression this suite previously could not see: the root `{ prelude }:` matches * nix_func_types, resolves no name of its own, and — before the fix — terminated the * walk, so nothing below the header was ever visited. */ TEST(nix_defs_survive_function_header_let) { CBMFileResult *r = extract("{ prelude }:\nlet\n gamma = x: x + 1;\n" " delta = { a, b }: a + b;\nin\n{ inherit gamma delta; }\n", CBM_LANG_NIX, "t", "wrapped.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "gamma")); ASSERT(has_def(r, "Function", "delta")); cbm_free_result(r); PASS(); } TEST(nix_defs_survive_function_header_attrset) { CBMFileResult *r = extract("{ prelude }:\n{\n eta = x: x + 1;\n}\n", CBM_LANG_NIX, "t", "wrapped_attrset.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "eta")); cbm_free_result(r); PASS(); } /* A curried header — `final: prev: { … }` is the standard nixpkgs overlay signature, and * the most common multi-arm header in the ecosystem. Two nested function_expressions sit * between the file root and the body. */ TEST(nix_defs_survive_curried_header) { CBMFileResult *r = extract("final: prev: {\n kappa = x: x + 1;\n}\n", CBM_LANG_NIX, "t", "overlay.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "kappa")); cbm_free_result(r); PASS(); } /* A Nix binding's name is a PATH, and the extractor previously took only its first * segment. Convention here matches C++ namespaces — `ns::serialize` is name * `serialize`, QN `proj.file.ns.serialize` — so a Nix binding is name = leaf * segment, QN = enclosing scope + leaf. */ TEST(nix_attrset_scope_disambiguates_leaf_names) { CBMFileResult *r = extract("{\n setA = { dup = x: x + 1; };\n setB = { dup = y: y + 2; };\n}\n", CBM_LANG_NIX, "t", "collide.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Both survive. Unqualified, these shared one QN, so the second definition — * and every CALLS edge sourced from it — was silently discarded at write. */ ASSERT(count_defs_with_label(r, "Function") == 2); ASSERT(has_def_qn(r, "t.collide.setA.dup")); ASSERT(has_def_qn(r, "t.collide.setB.dup")); cbm_free_result(r); PASS(); } /* `a.b.fn = …` is sugar for `a = { b = { fn = …; }; }`. Both spellings must yield * the same name and the same QN; the leading segments are scope, not name. */ TEST(nix_dotted_attrpath_qualifies_like_nested) { CBMFileResult *r = extract("{\n wrap.deep.fn = z: z + 1;\n}\n", CBM_LANG_NIX, "t", "d.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "fn")); ASSERT(has_def_qn(r, "t.d.wrap.deep.fn")); CBMFileResult *n = extract("{\n wrap = { deep = { fn = z: z + 1; }; };\n}\n", CBM_LANG_NIX, "t", "d.nix"); ASSERT_NOT_NULL(n); ASSERT_FALSE(n->has_error); /* The equality that makes this a correctness fix rather than a preference. */ ASSERT(has_def_qn(n, "t.d.wrap.deep.fn")); cbm_free_result(n); cbm_free_result(r); PASS(); } /* A quoted segment is an ordinary name that merely needs quoting in source. The * delimiters are not part of it, and leaving them in means every consumer keying * on the name has to know to re-quote. */ TEST(nix_quoted_attr_name_strips_quotes) { CBMFileResult *r = extract("{\n \"kebab-case\" = a: a;\n svc.\"my.name\" = b: b;\n}\n", CBM_LANG_NIX, "t", "q.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "kebab-case")); ASSERT_FALSE(has_def_any(r, "\"kebab-case\"")); ASSERT(has_def_qn(r, "t.q.kebab-case")); /* A quoted segment may itself contain dots; they are part of the name, not * path separators, but the QN is a dotted string either way. */ ASSERT(has_def(r, "Function", "my.name")); cbm_free_result(r); PASS(); } /* `"${x}" = …` has no statically knowable name. Minting it produces a def named * `"${x}"` that nothing can look up or resolve a call against, so mint nothing — * the same call the Makefile dot-prefix guard makes. */ TEST(nix_interpolated_attr_mints_no_def) { CBMFileResult *r = extract("{\n \"${dynamic}\" = a: a;\n fixed = b: b;\n}\n", CBM_LANG_NIX, "t", "i.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "fixed")); ASSERT(count_defs_with_label(r, "Function") == 1); cbm_free_result(r); PASS(); } /* Nix Variables. `nix_var_types` has always declared `binding`, but no Nix name * resolver existed, so the count was unconditionally zero. * * Scope follows the rule every other language uses: extract_variables mints FILE * scope and never locals — a C++ declaration inside a function body is not a * Variable. For Nix, file scope is the `let` bindings and the returned attrset; * anything in a deeper attrset is not. Without that bound a NixOS module's * settings tree would mint a node per `enable = true`. */ TEST(nix_module_level_bindings_mint_variables) { CBMFileResult *r = extract("{ pkgs, lib, ... }:\n" "let\n" " privateConst = 42;\n" "in\n" "{\n" " exported = \"value\";\n" " services.nginx.enable = true;\n" "}\n", CBM_LANG_NIX, "t", "mod.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* A let binding is file scope in the same sense a C++ file-static is. */ ASSERT(has_def(r, "Variable", "privateConst")); ASSERT(has_def(r, "Variable", "exported")); /* The QN carries the attrpath, exactly as it does for functions. */ ASSERT(has_def(r, "Variable", "enable")); ASSERT(has_def_qn(r, "t.mod.services.nginx.enable")); cbm_free_result(r); PASS(); } /* The flood guard. Each absence assertion is paired with a positive one on the * same predicate in the same result, so none can pass by extracting nothing. */ TEST(nix_nested_bindings_are_not_module_level) { CBMFileResult *r = extract("{ pkgs }:\n" "{\n" " topLevel = 1;\n" " deep = {\n" " nested = {\n" " shouldNotAppear = 2;\n" " };\n" " };\n" "}\n", CBM_LANG_NIX, "t", "deep.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Variable", "topLevel")); /* positive control */ ASSERT_FALSE(has_def_any(r, "shouldNotAppear")); /* `deep` is an attrset — a scope, not a value — so not a Variable either. */ ASSERT_FALSE(has_def(r, "Variable", "deep")); ASSERT_FALSE(has_def(r, "Variable", "nested")); cbm_free_result(r); PASS(); } /* A lambda-valued binding is already minted as a Function by the def walk. Minting * it again here would double-count every helper in the ecosystem. */ TEST(nix_lambda_binding_is_function_not_variable) { CBMFileResult *r = extract("{\n fn = x: x + 1;\n val = 7;\n}\n", CBM_LANG_NIX, "t", "mix.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "fn")); ASSERT_FALSE(has_def(r, "Variable", "fn")); ASSERT(has_def(r, "Variable", "val")); /* positive control */ ASSERT_FALSE(has_def(r, "Function", "val")); cbm_free_result(r); PASS(); } /* Descending past the header must not mint a second def from a curried lambda's inner * arm: `iota = a: b: ...` is one named function, not two. The inner `b:` has a * function_expression parent, resolves no name, and must stay out. */ TEST(nix_curried_lambda_mints_one_def) { CBMFileResult *r = extract("{ prelude }:\nlet\n iota = a: b: a + b;\nin\n{ inherit iota; }\n", CBM_LANG_NIX, "t", "curried.nix"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "iota")); ASSERT(count_defs_with_label(r, "Function") == 1); cbm_free_result(r); PASS(); } /* --- Fortran --- */ TEST(fortran_function) { /* Fortran subroutine name extraction is incomplete — just verify no crash */ CBMFileResult *r = extract("subroutine greet(name)\n character(*), intent(in) :: name\n " "print *, 'Hello ', name\nend subroutine\n", CBM_LANG_FORTRAN, "t", "greet.f90"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group A2: Missing OOP / Systems variants * ═══════════════════════════════════════════════════════════════════ */ /* --- Swift struct --- */ TEST(swift_struct) { CBMFileResult *r = extract("struct Point {\n var x: Double\n var y: Double\n func " "distance() -> Double { return (x*x + y*y).squareRoot() }\n}\n", CBM_LANG_SWIFT, "t", "Point.swift"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Method", "distance")); cbm_free_result(r); PASS(); } /* --- Swift calls (port of PR #47 Go tests) --- */ TEST(swift_simple_call) { CBMFileResult *r = extract("func main() { greet() }\nfunc greet() { print(\"hello\") }\n", CBM_LANG_SWIFT, "t", "main.swift"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "greet")); cbm_free_result(r); PASS(); } TEST(swift_method_call) { CBMFileResult *r = extract("class Foo {\n func bar() { baz.run() }\n}\n", CBM_LANG_SWIFT, "t", "Foo.swift"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "baz.run")); cbm_free_result(r); PASS(); } TEST(swift_constructor_call) { CBMFileResult *r = extract("func create() { let x = MyClass() }\n", CBM_LANG_SWIFT, "t", "create.swift"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "MyClass")); cbm_free_result(r); PASS(); } TEST(swift_chained_call) { CBMFileResult *r = extract("func setup() { AlarmScheduler.shared.startKeepAlive() }\n", CBM_LANG_SWIFT, "t", "setup.swift"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(r->calls.count > 0); cbm_free_result(r); PASS(); } /* --- Objective-C --- */ TEST(objc_interface) { CBMFileResult *r = extract("@interface Animal : NSObject\n- (NSString *)name;\n- (void)speak;\n@end\n", CBM_LANG_OBJC, "t", "Animal.h"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } TEST(objc_implementation) { CBMFileResult *r = extract("@implementation Animal\n- (NSString *)name { return @\"Animal\"; " "}\n- (void)speak { NSLog(@\"...\"); }\n@end\n", CBM_LANG_OBJC, "t", "Animal.m"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- Dart top-level function --- */ TEST(dart_top_level_function) { CBMFileResult *r = extract( "void main() {\n print('Hello');\n}\nString greet(String name) => 'Hello $name';\n", CBM_LANG_DART, "t", "main.dart"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "main")); ASSERT(has_def(r, "Function", "greet")); cbm_free_result(r); PASS(); } /* --- Rust enum --- */ TEST(rust_enum) { CBMFileResult *r = extract("pub enum Direction { North, South, East, West }\n", CBM_LANG_RUST, "t", "dir.rs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- Zig struct --- */ TEST(zig_struct) { CBMFileResult *r = extract("const Point = struct { x: f32, y: f32, pub fn dist(self: Point) " "f32 { return self.x + self.y; } };\n", CBM_LANG_ZIG, "t", "point.zig"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- C++ function (standalone) --- */ TEST(cpp_function) { CBMFileResult *r = extract("#include \nstd::string greet(const std::string& name) { " "return \"Hello \" + name; }\nint main() { return 0; }\n", CBM_LANG_CPP, "t", "main.cpp"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* #1266: GoogleTest TEST() macros with the same name collapse into a single * node when multiple tests share a file. Each must mint a distinct Function * node whose name encodes the suite and case arguments. */ TEST(cpp_gtest_same_name_collision_issue1266) { CBMFileResult *r = extract( "namespace demo { int assembleWidget(int s) { return s * 2; } }\n" "TEST(WidgetSuite, DoublesSmallSize) { demo::assembleWidget(1); }\n" "TEST(WidgetSuite, DoublesZero) { demo::assembleWidget(0); }\n" "TEST(WidgetSuite, DoublesLargeSize) {\n" " demo::assembleWidget(1000);\n" "}\n", CBM_LANG_CPP, "t", "direct_test.cpp"); ASSERT_NOT_NULL(r); ASSERT(has_def(r, "Function", "TEST_WidgetSuite_DoublesSmallSize")); ASSERT(has_def(r, "Function", "TEST_WidgetSuite_DoublesZero")); ASSERT(has_def(r, "Function", "TEST_WidgetSuite_DoublesLargeSize")); ASSERT(!has_def(r, "Function", "TEST")); cbm_free_result(r); PASS(); } /* #1266: TEST_F fixture macro also produces unique names. */ TEST(cpp_gtest_f_unique_name_issue1266) { CBMFileResult *r = extract( "TEST_F(MyFixture, FirstTest) { doStuff(); }\n" "TEST_F(MyFixture, SecondTest) { doOtherStuff(); }\n", CBM_LANG_CPP, "t", "fixture_test.cpp"); ASSERT_NOT_NULL(r); ASSERT(has_def(r, "Function", "TEST_F_MyFixture_FirstTest")); ASSERT(has_def(r, "Function", "TEST_F_MyFixture_SecondTest")); ASSERT(!has_def(r, "Function", "TEST_F")); cbm_free_result(r); PASS(); } /* --- C++ out-of-line method definitions (#428) --- * A .cpp defining methods of a class declared elsewhere (not in this TU). * Pre-fix these were recorded as free Functions (label "Function", no * parent_class); they must be Methods linked to their enclosing class via * parent_class. (The helper also descends nested scopes `ns::Class::method` to * the immediate class, but tree-sitter-cpp parses a synthetic doubly-qualified * out-of-line def in isolation as an ERROR node, so that path is exercised by * real codebases rather than this isolated unit fixture.) */ TEST(cpp_out_of_line_method_issue428) { CBMFileResult *r = extract("void Foo::bar() {}\n" "int Foo::baz() { return 0; }\n", CBM_LANG_CPP, "t", "foo.cpp"); ASSERT_NOT_NULL(r); ASSERT(has_def(r, "Method", "bar")); ASSERT(has_def(r, "Method", "baz")); ASSERT(!has_def(r, "Function", "bar")); /* not a free function */ /* parent_class links to the enclosing class QN */ int checked = 0; for (int i = 0; i < r->defs.count; i++) { const CBMDefinition *d = &r->defs.items[i]; if (strcmp(d->name, "bar") == 0 && strcmp(d->label, "Method") == 0) { ASSERT_NOT_NULL(d->parent_class); ASSERT(strstr(d->parent_class, "Foo") != NULL); checked = 1; } } ASSERT(checked); cbm_free_result(r); PASS(); } /* --- COBOL paragraph --- */ TEST(cobol_paragraph) { CBMFileResult *r = extract("IDENTIFICATION DIVISION.\nPROGRAM-ID. HELLO.\nPROCEDURE DIVISION.\n " "DISPLAY-GREETING.\n DISPLAY 'HELLO WORLD'.\n STOP RUN.\n", CBM_LANG_COBOL, "t", "hello.cbl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- Verilog module --- */ TEST(verilog_module) { CBMFileResult *r = extract("module adder(input a, input b, output sum);\n assign sum = a + b;\nendmodule\n", CBM_LANG_VERILOG, "t", "adder.v"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- CUDA kernel --- */ TEST(cuda_kernel) { CBMFileResult *r = extract("__global__ void vectorAdd(float *a, float *b, float *c, int n) {\n " " int i = blockIdx.x * blockDim.x + threadIdx.x;\n if (i < n) " "c[i] = a[i] + b[i];\n}\nint main() { return 0; }\n", CBM_LANG_CUDA, "t", "vector.cu"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- Python decorator --- */ TEST(python_decorator) { CBMFileResult *r = extract("class Router:\n @staticmethod\n def route(path: str):\n " " def decorator(func): return func\n return decorator\n", CBM_LANG_PYTHON, "t", "router.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Router")); cbm_free_result(r); PASS(); } /* --- TypeScript interface --- */ TEST(ts_interface) { CBMFileResult *r = extract("export interface Repository { findById(id: number): T; " "save(entity: T): void; delete(id: number): void; }\n", CBM_LANG_TYPESCRIPT, "t", "repo.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- TSX component --- */ TEST(tsx_component) { CBMFileResult *r = extract( "import React from 'react';\ninterface Props { name: string; }\nexport function Greeting({ " "name }: Props) {\n return
Hello {name}
;\n}\nexport default Greeting;\n", CBM_LANG_TSX, "t", "Greeting.tsx"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "Greeting")); cbm_free_result(r); PASS(); } /* --- Lua table method --- */ TEST(lua_table_method) { CBMFileResult *r = extract("local M = {}\nfunction M.create(name)\n return { name = name }\nend\nfunction " "M.greet(self)\n return 'Hi ' .. self.name\nend\nreturn M\n", CBM_LANG_LUA, "t", "module.lua"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Should extract at least one Function from Lua table method */ int fn_count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Function") == 0) fn_count++; } ASSERT_GTE(fn_count, 1); cbm_free_result(r); PASS(); } /* --- Emacs Lisp defun --- */ TEST(emacs_lisp_defun) { CBMFileResult *r = extract("(defun greet (name)\n (message \"Hello %s\" name))\n(defun main " "()\n (greet \"World\"))\n", CBM_LANG_EMACSLISP, "t", "init.el"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "greet")); ASSERT(has_def(r, "Function", "main")); cbm_free_result(r); PASS(); } /* --- Emacs Lisp defvar --- */ TEST(emacs_lisp_defvar) { CBMFileResult *r = extract("(defvar my-count 0 \"A counter.\")\n(defcustom my-name \"World\" " "\"The name.\"\n :type 'string)\n", CBM_LANG_EMACSLISP, "t", "vars.el"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- Haskell data type --- */ TEST(haskell_data_type) { CBMFileResult *r = extract("data Shape = Circle Double | Rectangle Double Double\narea :: Shape -> " "Double\narea (Circle r) = pi * r * r\narea (Rectangle w h) = w * h\n", CBM_LANG_HASKELL, "t", "Shape.hs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- Clojure function (known limitation: defn produces list_lit) --- */ TEST(clojure_function) { CBMFileResult *r = extract("(ns greeter.core)\n(defn greet [name]\n (str \"Hello \" " "name))\n(defn -main [& args]\n (println (greet \"World\")))\n", CBM_LANG_CLOJURE, "t", "core.clj"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Clojure uses list_lit for all forms — no function defs extracted (known limitation) */ cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group E2: Missing Config / Markup Languages * ═══════════════════════════════════════════════════════════════════ */ /* --- HTML elements --- */ TEST(html_elements) { CBMFileResult *r = extract( "Test

Hello

World

", CBM_LANG_HTML, "t", "index.html"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- SQL function (CREATE FUNCTION) --- */ TEST(sql_function) { CBMFileResult *r = extract("CREATE FUNCTION get_user_count() RETURNS INTEGER AS $$ SELECT " "COUNT(*) FROM users; $$ LANGUAGE SQL;\n", CBM_LANG_SQL, "t", "funcs.sql"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } TEST(sql_ddl_node_labels) { CBMFileResult *r = extract("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);\n" "CREATE VIEW active_users AS SELECT * FROM users;\n", CBM_LANG_SQL, "t", "schema.sql"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Table", "users")); ASSERT(has_def(r, "View", "active_users")); cbm_free_result(r); PASS(); } TEST(sql_view_lineage_usages) { /* A view's FROM/JOIN relations are emitted as usages (ref_name = table), * which pass_usages later resolves into view -> table USAGE lineage edges. */ CBMFileResult *r = extract("CREATE TABLE users (id INTEGER);\n" "CREATE VIEW active_users AS SELECT * FROM users;\n", CBM_LANG_SQL, "t", "schema.sql"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int found_users = 0; for (int i = 0; i < r->usages.count; i++) { if (r->usages.items[i].ref_name && strcmp(r->usages.items[i].ref_name, "users") == 0) { found_users = 1; } } ASSERT(found_users); cbm_free_result(r); PASS(); } TEST(sql_schema_qualified_name) { /* schema-qualified DDL (schema.table) is named by the table, not the schema, * and FROM schema.table resolves to that table for lineage. */ CBMFileResult *r = extract("CREATE TABLE app.users (id INTEGER);\n" "CREATE VIEW app.active AS SELECT * FROM app.users;\n", CBM_LANG_SQL, "t", "schema.sql"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Table", "users")); ASSERT(has_def(r, "View", "active")); int found_users = 0; for (int i = 0; i < r->usages.count; i++) { if (r->usages.items[i].ref_name && strcmp(r->usages.items[i].ref_name, "users") == 0) { found_users = 1; } } ASSERT(found_users); cbm_free_result(r); PASS(); } /* --- dbt Jinja lineage --- */ /* Helper: does the file's usage list carry `name`? */ static int has_usage(CBMFileResult *r, const char *name) { for (int i = 0; i < r->usages.count; i++) { if (r->usages.items[i].ref_name && strcmp(r->usages.items[i].ref_name, name) == 0) { return 1; } } return 0; } TEST(dbt_model_and_ref_lineage) { /* A dbt model: the file stem is the model identity, and each ref() is a * dependency on another model. The SQL grammar cannot read `{{ ref(..) }}` * at all, so without the dbt pass this file yields no lineage whatsoever. */ CBMFileResult *r = extract("SELECT o.id, c.name\n" "FROM {{ ref('stg_orders') }} o\n" "JOIN {{ ref('stg_customers') }} c ON c.id = o.customer_id\n", CBM_LANG_SQL, "t", "models/marts/orders_enriched.sql"); ASSERT_NOT_NULL(r); ASSERT(has_def(r, "Model", "orders_enriched")); ASSERT(has_usage(r, "stg_orders")); ASSERT(has_usage(r, "stg_customers")); cbm_free_result(r); PASS(); } TEST(dbt_source_and_two_arg_ref) { /* Both dbt builtins name the relation in their LAST string argument: * source('group','table') -> table, and the two-argument * ref('package','model') form -> model. */ CBMFileResult *r = extract("SELECT * FROM {{ source('raw', 'customers') }}\n" "UNION ALL SELECT * FROM {{ ref('analytics', 'legacy_customers') }}\n", CBM_LANG_SQL, "t", "models/stg_customers.sql"); ASSERT_NOT_NULL(r); ASSERT(has_def(r, "Model", "stg_customers")); ASSERT(has_usage(r, "customers")); ASSERT(has_usage(r, "legacy_customers")); /* the group/package argument is not the relation */ ASSERT_FALSE(has_usage(r, "raw")); ASSERT_FALSE(has_usage(r, "analytics")); cbm_free_result(r); PASS(); } TEST(dbt_ignores_non_dbt_jinja) { /* Templated SQL is not dbt SQL. An Airflow-style parameter substitution has * Jinja but no dbt builtin, so the dbt pass must contribute NOTHING — no * Model node named after the file, and no usage minted from the template * variables. This is the gate that keeps every non-dbt repository free of * fabricated data-lineage vocabulary. * * The ordinary SQL identifier path is unaffected and still sees the literal * `FROM events`; the second extraction below is the control proving that * usage is pre-existing SQL behaviour rather than anything dbt added. */ CBMFileResult *r = extract("SELECT * FROM events WHERE day = '{{ ds }}'\n" " AND region = '{{ params.region_code }}'\n", CBM_LANG_SQL, "t", "queries/daily_events.sql"); ASSERT_NOT_NULL(r); ASSERT_FALSE(has_def(r, "Model", "daily_events")); /* Control: the same statement with the templates replaced by plain string * literals. Both parse as SQL identically, so an equal usage count is the * precise statement of "the dbt pass contributed nothing here" — stronger * than naming individual identifiers, and immune to how SQL happens to * tokenize the template text. */ CBMFileResult *plain = extract("SELECT * FROM events WHERE day = '2026-01-01'\n" " AND region = 'eu-west'\n", CBM_LANG_SQL, "t", "queries/daily_events.sql"); ASSERT_NOT_NULL(plain); ASSERT_FALSE(has_def(plain, "Model", "daily_events")); ASSERT_EQ(r->usages.count, plain->usages.count); ASSERT_EQ(r->defs.count, plain->defs.count); cbm_free_result(plain); cbm_free_result(r); PASS(); } TEST(dbt_plain_sql_untouched) { /* Plain DDL keeps producing exactly the Table/View relations it did before * the dbt pass existed — no Model node, and the FROM lineage is unchanged. */ CBMFileResult *r = extract("CREATE TABLE users (id INTEGER);\n" "CREATE VIEW active_users AS SELECT * FROM users;\n", CBM_LANG_SQL, "t", "schema.sql"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Table", "users")); ASSERT(has_def(r, "View", "active_users")); ASSERT_FALSE(has_def(r, "Model", "schema")); cbm_free_result(r); PASS(); } /* --- Meson project --- */ TEST(meson_project) { CBMFileResult *r = extract( "project('myapp', 'c', version: '1.0.0')\nexecutable('myapp', 'main.c', install: true)\n", CBM_LANG_MESON, "t", "meson.build"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- CSS rules --- */ TEST(css_rules) { CBMFileResult *r = extract( ".container { display: flex; width: 100%; }\n.button { background: #007bff; color: white; " "border: none; }\n@media (max-width: 768px) { .container { flex-direction: column; } }\n", CBM_LANG_CSS, "t", "styles.css"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- SCSS rules --- */ TEST(scss_rules) { CBMFileResult *r = extract("$primary: #007bff;\n.container {\n width: 100%;\n .button {\n " "background: $primary;\n &:hover { opacity: 0.8; }\n }\n}\n", CBM_LANG_SCSS, "t", "styles.scss"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- TOML basic --- */ TEST(toml_basic) { CBMFileResult *r = extract("[server]\nhost = \"localhost\"\nport = 8080\n\n[database]\nurl = " "\"postgres://localhost/db\"\nmax_connections = 10\n", CBM_LANG_TOML, "t", "config.toml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "server")); ASSERT(has_def(r, "Class", "database")); ASSERT(has_def(r, "Variable", "host")); ASSERT(has_def(r, "Variable", "port")); cbm_free_result(r); PASS(); } /* --- CMake function --- */ TEST(cmake_function) { CBMFileResult *r = extract( "cmake_minimum_required(VERSION 3.16)\nproject(MyApp VERSION 1.0)\nadd_executable(myapp " "main.cpp)\ntarget_compile_features(myapp PRIVATE cxx_std_17)\n", CBM_LANG_CMAKE, "t", "CMakeLists.txt"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- JSON object --- */ TEST(json_object) { CBMFileResult *r = extract("{\"name\": \"myapp\", \"version\": \"1.0.0\", \"scripts\": " "{\"build\": \"go build\", \"test\": \"go test ./...\"}}", CBM_LANG_JSON, "t", "config.json"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Variable", "name")); ASSERT(has_def(r, "Variable", "version")); cbm_free_result(r); PASS(); } /* --- Protobuf message --- */ TEST(protobuf_message) { CBMFileResult *r = extract( "syntax = \"proto3\";\npackage user;\nmessage User { int64 id = 1; string name = 2; string " "email = 3; }\nservice UserService { rpc GetUser(User) returns (User); }\n", CBM_LANG_PROTOBUF, "t", "user.proto"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- GraphQL type --- */ TEST(graphql_type) { CBMFileResult *r = extract("type User {\n id: ID!\n name: String!\n email: String!\n}\ntype " "Query {\n user(id: ID!): User\n users: [User!]!\n}\n", CBM_LANG_GRAPHQL, "t", "schema.graphql"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- Svelte component --- */ TEST(svelte_component) { CBMFileResult *r = extract("\n

{greet()}

\n", CBM_LANG_SVELTE, "t", "App.svelte"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- Vue component --- */ TEST(vue_component) { CBMFileResult *r = extract("\n\n", CBM_LANG_VUE, "t", "App.vue"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- GLSL shader --- */ TEST(glsl_shader) { CBMFileResult *r = extract( "#version 330 core\nvoid main() {\n gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n}\nvec3 " "transform(vec3 pos, mat4 mvp) {\n return (mvp * vec4(pos, 1.0)).xyz;\n}\n", CBM_LANG_GLSL, "t", "vertex.glsl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* --- VimScript function --- */ TEST(vimscript_function) { CBMFileResult *r = extract("function! SayHello()\n echo 'Hello'\nendfunction\n", CBM_LANG_VIMSCRIPT, "t", "plugin.vim"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* VimScript extraction may or may not produce named functions */ int fn_count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Function") == 0) fn_count++; } if (fn_count > 0) { ASSERT(has_def(r, "Function", "SayHello")); } cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group H: Scientific / Math — extended tests * ═══════════════════════════════════════════════════════════════════ */ /* --- MATLAB parse (simple expression) --- */ TEST(matlab_parse) { CBMFileResult *r = extract("x = 1;\ny = x + 2;\n", CBM_LANG_MATLAB, "t", "simple.matlab"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } /* --- MATLAB call --- */ TEST(matlab_call) { CBMFileResult *r = extract("function y = foo(x)\n y = inv(x);\n disp hello\nend\n", CBM_LANG_MATLAB, "t", "foo.matlab"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->calls.count, 0); ASSERT(has_call(r, "inv")); ASSERT(has_call(r, "disp")); cbm_free_result(r); PASS(); } /* --- Lean parse (theorem) --- */ TEST(lean_parse) { CBMFileResult *r = extract("theorem add_comm (a b : Nat) : a + b = b + a := by omega\n", CBM_LANG_LEAN, "t", "Comm.lean"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } /* --- Lean call (recursive fib) --- */ TEST(lean_call) { CBMFileResult *r = extract("def fib : Nat \xe2\x86\x92 Nat\n | 0 => 1\n | 1 => 1\n | n + 2 " "=> fib (n + 1) + fib n\n", CBM_LANG_LEAN, "t", "Fib.lean"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->calls.count, 0); ASSERT(has_call(r, "fib")); cbm_free_result(r); PASS(); } /* --- Lean type annotation not call --- */ TEST(lean_type_annotation_not_call) { CBMFileResult *r = extract( "def listLen (xs : List Nat) : Nat := 0\ndef greet : IO Unit := IO.println \"hi\"\n", CBM_LANG_LEAN, "t", "Types.lean"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* "List" in binder type position should NOT be extracted as a call */ for (int i = 0; i < r->calls.count; i++) { ASSERT_FALSE(strcmp(r->calls.items[i].callee_name, "List") == 0); } /* IO.println in the body should be present */ int found_println = 0; for (int i = 0; i < r->calls.count; i++) { if (strstr(r->calls.items[i].callee_name, "println") != NULL) { found_println = 1; } } ASSERT_TRUE(found_println); cbm_free_result(r); PASS(); } /* --- FORM parse (simple expression) --- */ TEST(form_parse) { CBMFileResult *r = extract("Symbols x, y;\nLocal F = x + y;\nPrint;\n.end\n", CBM_LANG_FORM, "t", "example.frm"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } /* --- FORM call (#call) --- */ TEST(form_call) { CBMFileResult *r = extract("#procedure myproc(x)\n id `x' = 0;\n#endprocedure\n#procedure " "caller()\n #call myproc(1)\n#endprocedure\n", CBM_LANG_FORM, "t", "calc.frm"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->calls.count, 0); ASSERT(has_call(r, "myproc")); cbm_free_result(r); PASS(); } /* --- Magma procedure --- */ TEST(magma_procedure) { CBMFileResult *r = extract("procedure PrintHello()\n print \"Hello\";\nend procedure;\n", CBM_LANG_MAGMA, "t", "hello.mag"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int fn_count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Function") == 0) fn_count++; } if (fn_count > 0) { ASSERT(has_def(r, "Function", "PrintHello")); } cbm_free_result(r); PASS(); } /* --- Magma parse (simple) --- */ TEST(magma_parse) { CBMFileResult *r = extract("x := 42;\ny := x + 1;\n", CBM_LANG_MAGMA, "t", "simple.mag"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } /* --- Magma import (load) --- */ TEST(magma_import) { CBMFileResult *r = extract("load \"utils.mag\";\nload \"lib/helpers.mag\";\n", CBM_LANG_MAGMA, "t", "main.mag"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->imports.count, 2); cbm_free_result(r); PASS(); } /* --- Magma call --- */ TEST(magma_call) { CBMFileResult *r = extract("function Foo(x)\n y := Bar(x);\n return y;\nend function;\n", CBM_LANG_MAGMA, "t", "calls.mag"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->calls.count, 0); ASSERT(has_call(r, "Bar")); cbm_free_result(r); PASS(); } /* --- Magma disambiguation (.m file as Magma) --- */ TEST(magma_disambiguation) { CBMFileResult *r = extract("function Factorial(n)\n if n le 1 then\n return 1;\n end " "if;\n return n * Factorial(n - 1);\nend function;\n", CBM_LANG_MAGMA, "t", "test.m"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int fn_count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Function") == 0) fn_count++; } ASSERT_GTE(fn_count, 1); if (fn_count > 0) { ASSERT(has_def(r, "Function", "Factorial")); } cbm_free_result(r); PASS(); } /* --- Wolfram function (both := and =) --- */ TEST(wolfram_function_extended) { CBMFileResult *r = extract("f[x_] := x^2\ng[x_] = x + 1\n", CBM_LANG_WOLFRAM, "t", "funcs.wl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int fn_count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Function") == 0) fn_count++; } ASSERT_GTE(fn_count, 2); ASSERT(has_def(r, "Function", "f")); ASSERT(has_def(r, "Function", "g")); cbm_free_result(r); PASS(); } /* --- Wolfram call --- */ TEST(wolfram_call) { CBMFileResult *r = extract("f[x_] := g[x] + h[x]\n", CBM_LANG_WOLFRAM, "t", "calls.wl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->calls.count, 0); ASSERT(has_call(r, "g")); ASSERT(has_call(r, "h")); /* "f" should NOT appear as a call (it's the definition LHS) */ for (int i = 0; i < r->calls.count; i++) { ASSERT_FALSE(strcmp(r->calls.items[i].callee_name, "f") == 0); } cbm_free_result(r); PASS(); } /* --- Wolfram caller attribution --- */ TEST(wolfram_caller_attribution) { CBMFileResult *r = extract("f[x_] := g[x] + h[x]\n", CBM_LANG_WOLFRAM, "t", "caller.wl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->calls.count, 0); /* Calls inside f[] should have f as enclosing function, not the module path */ for (int i = 0; i < r->calls.count; i++) { if (strcmp(r->calls.items[i].callee_name, "g") == 0 || strcmp(r->calls.items[i].callee_name, "h") == 0) { /* enclosing_func_qn must NOT be empty or the file path */ ASSERT_NOT_NULL(r->calls.items[i].enclosing_func_qn); ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "") == 0); ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "t.caller") == 0); } } cbm_free_result(r); PASS(); } /* Issue #438: a C function_definition has no `name` field — the name lives in the * declarator chain. Calls inside a C function must be attributed to the enclosing * function, not the module. Pre-fix, enclosing_func_qn fell back to the module QN. */ TEST(c_caller_attribution) { CBMFileResult *r = extract("int helper(int x) { return x; }\n" "int caller(void) { return helper(1); }\n", CBM_LANG_C, "t", "main.c"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->calls.count, 0); int saw_helper = 0; for (int i = 0; i < r->calls.count; i++) { if (strcmp(r->calls.items[i].callee_name, "helper") == 0) { saw_helper = 1; /* enclosing_func_qn must be the function, NOT empty and NOT the module QN. */ ASSERT_NOT_NULL(r->calls.items[i].enclosing_func_qn); ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "") == 0); ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "t.main") == 0); } } ASSERT(saw_helper); cbm_free_result(r); PASS(); } /* adc8304 (the dedup refactor bundled into #463) re-pointed the C/C++ enclosing- * function resolver at the canonical declarator walker: qualified names (Foo::bar) * now resolve via resolve_qualified_name(), and `type_identifier` was dropped from * the terminal-name set. These guard that out-of-line C++ method / ctor / dtor * definitions still attribute their inner calls to the enclosing function, not the * module — i.e. that the dedup did not reintroduce the #438 regression on the * qualified-declarator path. Module QN for "m.cpp" under prefix "t" is "t.m". */ TEST(cpp_out_of_line_method_caller_attribution) { CBMFileResult *r = extract("struct Foo { void bar(); };\n" "int helper(int x) { return x; }\n" "void Foo::bar() { helper(1); }\n", CBM_LANG_CPP, "t", "m.cpp"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->calls.count, 0); int saw_helper = 0; for (int i = 0; i < r->calls.count; i++) { if (strcmp(r->calls.items[i].callee_name, "helper") == 0) { saw_helper = 1; /* enclosing must be the out-of-line method, NOT empty and NOT the module. */ ASSERT_NOT_NULL(r->calls.items[i].enclosing_func_qn); ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "") == 0); ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "t.m") == 0); } } ASSERT(saw_helper); cbm_free_result(r); PASS(); } /* Out-of-line constructor (Foo::Foo) and destructor (Foo::~Foo) exercise the * identifier and destructor_name branches of resolve_qualified_name(). A call * inside either must attribute to that special member, not the module. */ TEST(cpp_out_of_line_ctor_dtor_caller_attribution) { CBMFileResult *r = extract("struct Foo { Foo(); ~Foo(); };\n" "int helper(int x) { return x; }\n" "Foo::Foo() { helper(1); }\n" "Foo::~Foo() { helper(2); }\n", CBM_LANG_CPP, "t", "m.cpp"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->calls.count, 0); int helper_calls = 0; for (int i = 0; i < r->calls.count; i++) { if (strcmp(r->calls.items[i].callee_name, "helper") == 0) { helper_calls++; ASSERT_NOT_NULL(r->calls.items[i].enclosing_func_qn); ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "") == 0); ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "t.m") == 0); } } ASSERT(helper_calls >= 1); cbm_free_result(r); PASS(); } /* --- Wolfram parse (simple assignment) --- */ TEST(wolfram_parse) { CBMFileResult *r = extract("x = 42;\ny = x + 1;\n", CBM_LANG_WOLFRAM, "t", "simple.wl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } /* --- Wolfram import --- */ TEST(wolfram_import) { CBMFileResult *r = extract("<< \"utils.wl\"\nNeeds[\"Package`\"]\n", CBM_LANG_WOLFRAM, "t", "main.wl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->imports.count, 0); cbm_free_result(r); PASS(); } /* --- Wolfram nested def --- */ TEST(wolfram_nested_def) { CBMFileResult *r = extract("main[x_] := Module[{localF}, localF[t_] := t + 1; localF[x]]\n", CBM_LANG_WOLFRAM, "t", "nested.wl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "main")); ASSERT(has_def(r, "Function", "localF")); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group I: cbm_test.go ports * ═══════════════════════════════════════════════════════════════════ */ TEST(python_docstring) { CBMFileResult *r = extract( "def compute(x, y):\n \"\"\"Compute the sum of x and y.\"\"\"\n return x + y\n", CBM_LANG_PYTHON, "test", "test.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "compute")); /* Check docstring is present */ int found = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "compute") == 0) { found = 1; ASSERT_NOT_NULL(r->defs.items[i].docstring); ASSERT_TRUE(strlen(r->defs.items[i].docstring) > 0); } } ASSERT_TRUE(found); cbm_free_result(r); PASS(); } TEST(go_function_extraction) { CBMFileResult *r = extract("package main\n\n// Greet returns a greeting.\nfunc Greet(name string) string " "{\n\treturn \"Hello, \" + name\n}\n\nfunc main() {\n\tGreet(\"world\")\n}\n", CBM_LANG_GO, "test", "main.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "Greet")); ASSERT(has_def(r, "Function", "main")); ASSERT(has_call(r, "Greet")); cbm_free_result(r); PASS(); } TEST(js_arrow_function) { CBMFileResult *r = extract("const greet = (name) => {\n return \"Hello \" + " "name;\n};\n\nconst result = greet(\"world\");\n", CBM_LANG_JAVASCRIPT, "test", "app.js"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group J: language_failures_test.go ports * ═══════════════════════════════════════════════════════════════════ */ /* CommonLisp — defun extraction (known limitation: grammar produces list_lit) */ TEST(commonlisp_defun) { CBMFileResult *r = extract("(defun hello () \"world\")\n", CBM_LANG_COMMONLISP, "test", "hello.lisp"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Known limitation: CommonLisp grammar produces list_lit, not defun nodes. * Function extraction returns 0 — this test documents the limitation. */ cbm_free_result(r); PASS(); } TEST(commonlisp_multiple_functions) { CBMFileResult *r = extract("(defun add (a b) (+ a b))\n(defun mul (a b) (* a b))\n", CBM_LANG_COMMONLISP, "test", "math.lisp"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } TEST(commonlisp_defmacro) { CBMFileResult *r = extract("(defmacro when2 (condition &body body)\n `(if ,condition (progn ,@body)))\n", CBM_LANG_COMMONLISP, "test", "macros.lisp"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); cbm_free_result(r); PASS(); } TEST(makefile_rule_as_function) { CBMFileResult *r = extract("all:\n\t@echo hello\n", CBM_LANG_MAKEFILE, "test", "Makefile"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "all")); cbm_free_result(r); PASS(); } TEST(makefile_multiple_targets) { CBMFileResult *r = extract("all: main.o\n\tgcc -o all main.o\n\nbuild:\n\tgo build ./...\n", CBM_LANG_MAKEFILE, "test", "Makefile"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "all")); ASSERT(has_def(r, "Function", "build")); cbm_free_result(r); PASS(); } TEST(makefile_variable_extraction) { CBMFileResult *r = extract("CC := gcc\nCFLAGS := -Wall\n", CBM_LANG_MAKEFILE, "test", "Makefile"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Variable extraction may or may not work depending on Makefile grammar support */ cbm_free_result(r); PASS(); } TEST(vimscript_function_extraction) { CBMFileResult *r = extract("function! SayHello()\n echo 'Hello'\nendfunction\n", CBM_LANG_VIMSCRIPT, "test", "plugin.vim"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* VimScript function extraction may or may not produce named functions */ int fn_count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Function") == 0) fn_count++; } if (fn_count > 0) { ASSERT(has_def(r, "Function", "SayHello")); } cbm_free_result(r); PASS(); } TEST(vimscript_function_without_bang) { CBMFileResult *r = extract("function MyFunc(arg)\n return arg\nendfunction\n", CBM_LANG_VIMSCRIPT, "test", "plugin.vim"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int fn_count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Function") == 0) fn_count++; } if (fn_count > 0) { ASSERT(has_def(r, "Function", "MyFunc")); } cbm_free_result(r); PASS(); } TEST(julia_function_extraction) { CBMFileResult *r = extract("function hello()\n println(\"Hello, World!\")\nend\n", CBM_LANG_JULIA, "test", "hello.jl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int fn_count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Function") == 0) fn_count++; } if (fn_count > 0) { ASSERT(has_def(r, "Function", "hello")); } cbm_free_result(r); PASS(); } TEST(julia_function_with_args) { CBMFileResult *r = extract("function add(a::Int, b::Int)::Int\n return a + b\nend\n", CBM_LANG_JULIA, "test", "math.jl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int fn_count = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Function") == 0) fn_count++; } if (fn_count > 0) { ASSERT(has_def(r, "Function", "add")); } cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Cross-cutting: Calls + Imports * ═══════════════════════════════════════════════════════════════════ */ TEST(python_calls) { CBMFileResult *r = extract("import os\ndef main():\n os.path.exists('/tmp')\n print('hello')\n", CBM_LANG_PYTHON, "t", "main.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Python unified extraction produces calls — verify at least some exist */ ASSERT_GT(r->calls.count, 0); cbm_free_result(r); PASS(); } TEST(python_iris_classMethodValue) { CBMFileResult *r = extract("import iris\n" "iris_obj = iris.cls('%Library.ObjectScript')\n" "def call_bfs(n):\n" " return iris_obj.classMethodValue('Graph.KG.TraversalBFS', 'BFSFastJson', n)\n", CBM_LANG_PYTHON, "t", "store.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "Graph.KG.TraversalBFS.BFSFastJson")); cbm_free_result(r); PASS(); } TEST(go_calls) { CBMFileResult *r = extract("package main\nimport \"fmt\"\nfunc main() { fmt.Println(\"hello\") }\n", CBM_LANG_GO, "t", "main.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "fmt.Println")); cbm_free_result(r); PASS(); } TEST(python_imports) { CBMFileResult *r = extract("import os\nfrom sys import argv\nfrom collections import defaultdict\n", CBM_LANG_PYTHON, "t", "main.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->imports.count, 0); cbm_free_result(r); PASS(); } TEST(js_imports) { CBMFileResult *r = extract("import React from 'react';\nimport { useState } from " "'react';\nconst fs = require('fs');\n", CBM_LANG_JAVASCRIPT, "t", "app.js"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->imports.count, 0); cbm_free_result(r); PASS(); } TEST(go_imports) { CBMFileResult *r = extract("package main\n\nimport \"fmt\"\nimport (\n \"os\"\n net \"net/http\"\n)\n", CBM_LANG_GO, "t", "main.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->imports.count, 0); ASSERT(has_import(r, "fmt")); cbm_free_result(r); PASS(); } TEST(java_imports) { CBMFileResult *r = extract( "import java.util.List;\nimport java.util.ArrayList;\nimport static java.lang.Math.PI;\n" "public class Foo {}\n", CBM_LANG_JAVA, "t", "Foo.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->imports.count, 0); ASSERT(has_import(r, "java.util.List")); cbm_free_result(r); PASS(); } TEST(rust_imports) { CBMFileResult *r = extract( "use std::collections::HashMap;\nuse std::io::{self, Write};\nuse serde::Serialize;\n" "fn main() {}\n", CBM_LANG_RUST, "t", "main.rs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->imports.count, 0); ASSERT(has_import(r, "std::collections::HashMap")); cbm_free_result(r); PASS(); } TEST(c_imports) { CBMFileResult *r = extract("#include \n#include \n#include " "\"mylib.h\"\n\nint main() { return 0; }\n", CBM_LANG_C, "t", "main.c"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->imports.count, 0); ASSERT(has_import(r, "stdio.h")); cbm_free_result(r); PASS(); } TEST(ruby_imports) { CBMFileResult *r = extract( "require 'json'\nrequire 'net/http'\nrequire_relative 'helpers'\n\nclass Foo; end\n", CBM_LANG_RUBY, "t", "app.rb"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->imports.count, 0); ASSERT(has_import(r, "json")); cbm_free_result(r); PASS(); } TEST(lua_imports) { CBMFileResult *r = extract("local json = require(\"dkjson\")\nlocal http = " "require(\"socket.http\")\n\nlocal function greet() end\n", CBM_LANG_LUA, "t", "main.lua"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GT(r->imports.count, 0); ASSERT(has_import(r, "dkjson")); cbm_free_result(r); PASS(); } TEST(import_stress_go) { /* Stress test: 5,000 single-line Go imports. * Verifies O(N) behaviour — would hang indefinitely with the O(N²) loop. */ const int N = 5000; /* Each line: import "pkg/NNNNN"\n = ~20 chars; total ~100KB */ int buf_size = N * 24 + 64; char *src = malloc((size_t)buf_size); ASSERT_NOT_NULL(src); int pos = 0; pos += snprintf(src + pos, (size_t)(buf_size - pos), "package stress\n"); for (int k = 0; k < N; k++) { pos += snprintf(src + pos, (size_t)(buf_size - pos), "import \"pkg/%05d\"\n", k); } CBMFileResult *r = extract(src, CBM_LANG_GO, "t", "stress.go"); free(src); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_EQ(r->imports.count, N); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Embedded-language import extraction * Host grammars (Svelte, Vue, HTML, Astro) keep \n" "

Hello {value}

\n", CBM_LANG_SVELTE, "t", "Comp.svelte"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->imports.count, 3); ASSERT(has_import(r, "Foo.svelte")); ASSERT(has_import(r, "lib/utils")); ASSERT(has_import(r, "helpers")); cbm_free_result(r); PASS(); } TEST(svelte_imports_no_script) { /* .svelte with no \n", CBM_LANG_VUE, "t", "App.vue"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->imports.count, 2); ASSERT(has_import(r, "MyComp.vue")); ASSERT(has_import(r, "vue")); cbm_free_result(r); PASS(); } TEST(html_imports_basic) { /* Plain HTML with inline ES module imports — same generic walker. */ CBMFileResult *r = extract("\n" "\n" "\n", CBM_LANG_HTML, "t", "index.html"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->imports.count, 2); ASSERT(has_import(r, "app.js")); ASSERT(has_import(r, "utils.js")); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * config_extraction_test.go ports (25 tests) * ═══════════════════════════════════════════════════════════════════ */ /* --- TOML (8 tests) --- */ TEST(toml_basic_table_and_pair) { CBMFileResult *r = extract("[database]\nhost = \"localhost\"\nport = 5432\n", CBM_LANG_TOML, "t", "config.toml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_EQ(count_defs_with_label(r, "Class"), 1); ASSERT(has_def(r, "Class", "database")); ASSERT_GTE(count_defs_with_label(r, "Variable"), 2); ASSERT(has_def(r, "Variable", "host")); ASSERT(has_def(r, "Variable", "port")); cbm_free_result(r); PASS(); } TEST(toml_nested_table) { CBMFileResult *r = extract("[server.http]\nport = 8080\n", CBM_LANG_TOML, "t", "config.toml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Class"), 1); cbm_free_result(r); PASS(); } TEST(toml_table_array_element) { CBMFileResult *r = extract("[[servers]]\nname = \"alpha\"\n[[servers]]\nname = \"beta\"\n", CBM_LANG_TOML, "t", "config.toml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Class"), 2); ASSERT_GTE(count_defs_with_label(r, "Variable"), 2); cbm_free_result(r); PASS(); } TEST(toml_dotted_key) { CBMFileResult *r = extract("database.host = \"localhost\"\n", CBM_LANG_TOML, "t", "config.toml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Variable"), 1); cbm_free_result(r); PASS(); } TEST(toml_quoted_key) { CBMFileResult *r = extract("\"unusual-key\" = \"value\"\n", CBM_LANG_TOML, "t", "config.toml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Variable"), 1); cbm_free_result(r); PASS(); } TEST(toml_empty_table) { CBMFileResult *r = extract("[empty]\n", CBM_LANG_TOML, "t", "config.toml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_EQ(count_defs_with_label(r, "Class"), 1); ASSERT(has_def(r, "Class", "empty")); ASSERT_EQ(count_defs_with_label(r, "Variable"), 0); cbm_free_result(r); PASS(); } TEST(toml_comments_only) { CBMFileResult *r = extract("# just a comment\n# another comment\n", CBM_LANG_TOML, "t", "config.toml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_EQ(count_defs_with_label(r, "Class"), 0); ASSERT_EQ(count_defs_with_label(r, "Variable"), 0); cbm_free_result(r); PASS(); } TEST(toml_boolean_and_integer_values) { CBMFileResult *r = extract("enabled = true\ncount = 42\nname = \"test\"\n", CBM_LANG_TOML, "t", "config.toml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Variable"), 3); cbm_free_result(r); PASS(); } /* --- INI (4 tests) --- */ TEST(ini_basic_section_and_setting) { CBMFileResult *r = extract("[database]\nhost = localhost\nport = 5432\n", CBM_LANG_INI, "t", "config.ini"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Class"), 1); ASSERT_GTE(count_defs_with_label(r, "Variable"), 2); cbm_free_result(r); PASS(); } TEST(ini_multiple_sections) { CBMFileResult *r = extract("[section1]\nkey1 = val1\n[section2]\nkey2 = val2\n", CBM_LANG_INI, "t", "config.ini"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Class"), 2); ASSERT_GTE(count_defs_with_label(r, "Variable"), 2); cbm_free_result(r); PASS(); } TEST(ini_global_keys) { CBMFileResult *r = extract("key1 = value1\nkey2 = value2\n", CBM_LANG_INI, "t", "config.ini"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_EQ(count_defs_with_label(r, "Class"), 0); ASSERT_GTE(count_defs_with_label(r, "Variable"), 2); cbm_free_result(r); PASS(); } TEST(ini_comments) { CBMFileResult *r = extract("; comment\n# another comment\n[section]\nkey = val\n", CBM_LANG_INI, "t", "config.ini"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Class"), 1); ASSERT_GTE(count_defs_with_label(r, "Variable"), 1); cbm_free_result(r); PASS(); } /* --- JSON (5 tests) --- */ TEST(json_basic_pair) { CBMFileResult *r = extract("{\"host\": \"localhost\", \"port\": 5432}", CBM_LANG_JSON, "t", "config.json"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Variable"), 2); ASSERT(has_def(r, "Variable", "host")); ASSERT(has_def(r, "Variable", "port")); cbm_free_result(r); PASS(); } TEST(json_nested_object) { CBMFileResult *r = extract("{\"database\": {\"host\": \"localhost\", \"port\": 5432}}", CBM_LANG_JSON, "t", "config.json"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Variable"), 3); cbm_free_result(r); PASS(); } TEST(json_empty_object) { CBMFileResult *r = extract("{}", CBM_LANG_JSON, "t", "config.json"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_EQ(count_defs_with_label(r, "Variable"), 0); cbm_free_result(r); PASS(); } TEST(json_boolean_null_values) { CBMFileResult *r = extract("{\"enabled\": true, \"value\": null, \"name\": \"test\"}", CBM_LANG_JSON, "t", "config.json"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Variable"), 3); cbm_free_result(r); PASS(); } TEST(json_package_json_deps) { CBMFileResult *r = extract("{\"name\":\"pkg\",\"dependencies\":{\"express\":\"^4.0\",\"lodash\":\"^4.17\"}}", CBM_LANG_JSON, "t", "package.json"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Variable"), 4); ASSERT(has_def(r, "Variable", "name")); ASSERT(has_def(r, "Variable", "dependencies")); ASSERT(has_def(r, "Variable", "express")); ASSERT(has_def(r, "Variable", "lodash")); cbm_free_result(r); PASS(); } /* --- XML (4 tests) --- */ TEST(xml_basic_element) { CBMFileResult *r = extract( "localhost", CBM_LANG_XML, "t", "config.xml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Class"), 3); ASSERT(has_def(r, "Class", "config")); ASSERT(has_def(r, "Class", "database")); ASSERT(has_def(r, "Class", "host")); cbm_free_result(r); PASS(); } TEST(xml_self_closing_tag) { CBMFileResult *r = extract("", CBM_LANG_XML, "t", "config.xml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Class"), 2); cbm_free_result(r); PASS(); } TEST(xml_empty_document) { CBMFileResult *r = extract("", CBM_LANG_XML, "t", "config.xml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Class"), 1); cbm_free_result(r); PASS(); } TEST(xml_multiple_children) { CBMFileResult *r = extract("", CBM_LANG_XML, "t", "config.xml"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Class"), 4); /* servers + 3x server */ cbm_free_result(r); PASS(); } /* --- Markdown (4 tests) --- */ TEST(markdown_atx_headings) { CBMFileResult *r = extract("# Title\n## Section\n### Subsection\n", CBM_LANG_MARKDOWN, "t", "README.md"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Section"), 3); ASSERT_EQ(count_defs_with_label(r, "Class"), 0); /* Markdown: Section, not Class */ cbm_free_result(r); PASS(); } TEST(markdown_setext_headings) { CBMFileResult *r = extract("Title\n=====\nSection\n------\n", CBM_LANG_MARKDOWN, "t", "README.md"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Section"), 2); cbm_free_result(r); PASS(); } TEST(markdown_heading_content) { CBMFileResult *r = extract("# Installation Guide\n## Prerequisites\n## Setup\n", CBM_LANG_MARKDOWN, "t", "README.md"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(count_defs_with_label(r, "Section"), 3); ASSERT(has_def(r, "Section", "Installation Guide")); ASSERT(has_def(r, "Section", "Prerequisites")); ASSERT(has_def(r, "Section", "Setup")); cbm_free_result(r); PASS(); } TEST(markdown_no_headings) { CBMFileResult *r = extract("Just a paragraph\n\nAnother paragraph\n", CBM_LANG_MARKDOWN, "t", "README.md"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_EQ(count_defs_with_label(r, "Section"), 0); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Python __init__.py Module QN collision regression * ═══════════════════════════════════════════════════════════════════ */ TEST(python_init_module_qn_not_collide_with_folder) { /* Bug: __init__.py Module QN was identical to the Folder QN for the * same directory, causing the Folder node to be overwritten when the * Module was upserted. The Module QN must contain "__init__" to * distinguish it from the Folder QN. */ CBMFileResult *r = extract("class Config:\n DEBUG = True\n\ndef setup():\n pass\n", CBM_LANG_PYTHON, "proj", "mypackage/__init__.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Module node must exist */ ASSERT_GTE(r->defs.count, 1); ASSERT_STR_EQ(r->defs.items[0].label, "Module"); /* Module QN must contain __init__ (not be stripped to just "proj.mypackage") */ ASSERT_NOT_NULL(r->module_qn); ASSERT_NOT_NULL(strstr(r->module_qn, "__init__")); /* But symbols inside __init__.py should NOT have __init__ in their QN */ int found_config = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "Config") == 0) { ASSERT_NOT_NULL(r->defs.items[i].qualified_name); /* Should be "proj.mypackage.Config", NOT "proj.mypackage.__init__.Config" */ ASSERT_STR_EQ(r->defs.items[i].qualified_name, "proj.mypackage.Config"); found_config = 1; } } ASSERT_EQ(found_config, 1); cbm_free_result(r); PASS(); } TEST(python_init_nested_module_qn) { /* Deeply nested __init__.py — same collision must not happen */ CBMFileResult *r = extract("def greet():\n return 'hello'\n", CBM_LANG_PYTHON, "proj", "docker-images/cloud-runs/bq-sync-api/__init__.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_NOT_NULL(r->module_qn); /* Must contain __init__ to not collide with Folder QN */ ASSERT_NOT_NULL(strstr(r->module_qn, "__init__")); cbm_free_result(r); PASS(); } TEST(js_index_module_qn_not_collide_with_folder) { /* Same bug for JS/TS index.ts files */ CBMFileResult *r = extract("export function App() { return null; }\n", CBM_LANG_TYPESCRIPT, "proj", "src/components/index.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_NOT_NULL(r->module_qn); /* Must contain "index" to not collide with Folder QN */ ASSERT_NOT_NULL(strstr(r->module_qn, "index")); cbm_free_result(r); PASS(); } TEST(python_regular_module_qn_unchanged) { /* Non-__init__.py Python files should be unaffected */ CBMFileResult *r = extract("def helper():\n pass\n", CBM_LANG_PYTHON, "proj", "mypackage/utils.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_NOT_NULL(r->module_qn); /* Regular module QN should not contain __init__ or index */ ASSERT_STR_EQ(r->module_qn, "proj.mypackage.utils"); cbm_free_result(r); PASS(); } /* Find a definition by name; returns the item or NULL. */ static const CBMDefinition *find_def_by_name(CBMFileResult *r, const char *name) { for (int i = 0; i < r->defs.count; i++) { if (r->defs.items[i].name && strcmp(r->defs.items[i].name, name) == 0) { return &r->defs.items[i]; } } return NULL; } static int decorators_contain(const CBMDefinition *d, const char *needle) { if (!d || !d->decorators) { return 0; } for (int i = 0; d->decorators[i]; i++) { if (strstr(d->decorators[i], needle)) { return 1; } } return 0; } /* Issue #382: Java Method nodes had empty decorators / signature. */ TEST(extract_java_method_annotations_issue382) { CBMFileResult *r = extract("public class C {\n" " @GetMapping(\"/x\")\n" " public String cmd(String c) { return c; }\n" "}\n", CBM_LANG_JAVA, "t", "C.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *m = find_def_by_name(r, "cmd"); ASSERT_NOT_NULL(m); ASSERT(decorators_contain(m, "GetMapping")); ASSERT_NOT_NULL(m->signature); ASSERT(m->signature[0] != '\0'); cbm_free_result(r); PASS(); } /* Issue #1005: JAX-RS splits a route across two annotations (@GET carries the * verb, a sibling @Path carries the path). Returning on the first mapping * annotation dropped every method-level @Path, and the class-level @Path * prefix was never recognized at all. */ TEST(extract_java_jaxrs_path_composition_issue1005) { CBMFileResult *r = extract("import jakarta.ws.rs.GET;\n" "import jakarta.ws.rs.Path;\n" "@Path(\"/api/v1/widgets\")\n" "public class WidgetResource {\n" " @GET\n" " public String list() { return \"\"; }\n" " @GET\n" " @Path(\"/count\")\n" " public String count() { return \"\"; }\n" "}\n", CBM_LANG_JAVA, "t", "WidgetResource.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *list = find_def_by_name(r, "list"); ASSERT_NOT_NULL(list); ASSERT_NOT_NULL(list->route_path); ASSERT_STR_EQ(list->route_path, "/api/v1/widgets"); ASSERT_STR_EQ(list->route_method, "GET"); const CBMDefinition *count = find_def_by_name(r, "count"); ASSERT_NOT_NULL(count); ASSERT_NOT_NULL(count->route_path); ASSERT_STR_EQ(count->route_path, "/api/v1/widgets/count"); ASSERT_STR_EQ(count->route_method, "GET"); cbm_free_result(r); PASS(); } /* A comment between decorators must not drop the decorators above it. * Comments are NAMED tree-sitter nodes, so the prev-sibling walk used to stop * at one — a documented route (@Post + @HttpCode above an explanatory comment) * silently lost those decorators and disappeared from route/authz queries. */ TEST(extract_ts_decorators_survive_interleaved_comment) { CBMFileResult *r = extract("class AuthController {\n" " @Post('login')\n" " @HttpCode(HttpStatus.OK)\n" " // throttled per IP and per account\n" " @Throttle({ default: { ttl: 900_000, limit: 5 } })\n" " async login(dto: LoginDto) { return 1; }\n" "}\n", CBM_LANG_TYPESCRIPT, "t", "auth.controller.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *m = find_def_by_name(r, "login"); ASSERT_NOT_NULL(m); ASSERT(decorators_contain(m, "Throttle")); /* below the comment — always worked */ ASSERT(decorators_contain(m, "HttpCode")); /* above the comment — was dropped */ ASSERT(decorators_contain(m, "Post")); /* above the comment — was dropped */ cbm_free_result(r); PASS(); } /* Find an in-body call by its raw callee text; returns the call or NULL. */ static const CBMCall *find_call_by_callee(CBMFileResult *r, const char *callee) { for (int i = 0; i < r->calls.count; i++) { if (r->calls.items[i].callee_name && strcmp(r->calls.items[i].callee_name, callee) == 0) { return &r->calls.items[i]; } } return NULL; } /* Issue #1006: JS/TS template-literal URLs must flatten ${...} substitutions * to the canonical "{}" placeholder, both as call arguments (HTTP_CALLS) and * as URL-shaped string_refs collected from const/return positions. */ TEST(extract_ts_template_string_url_issue1006) { CBMFileResult *r = extract("export function detailPath(id: string): string {\n" " return `/api/v1/things/${id}/detail`;\n" "}\n" "export function load(id: string) {\n" " return fetch(`/api/v1/things/${id}`);\n" "}\n", CBM_LANG_TYPESCRIPT, "t", "paths.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMCall *c = find_call_by_callee(r, "fetch"); ASSERT_NOT_NULL(c); ASSERT_NOT_NULL(c->first_string_arg); ASSERT_STR_EQ(c->first_string_arg, "/api/v1/things/{}"); int found = 0; for (int i = 0; i < r->string_refs.count; i++) { if (r->string_refs.items[i].value && strcmp(r->string_refs.items[i].value, "/api/v1/things/{}/detail") == 0) { found = 1; break; } } ASSERT(found); cbm_free_result(r); PASS(); } /* Issue #1249: a mux route built as `configVar + "/literal"` (Go's idiomatic * configurable-base-path pattern) must index the literal suffix, both for a * route registration and for an outbound URL built the same way. A real BFF * with 47 such registrations produced only 9 Route nodes before this fix. */ TEST(extract_go_binary_concat_url_issue1249) { CBMFileResult *r = extract("package main\n" "import \"net/http\"\n" "func setup(mux *http.ServeMux, base string) {\n" " mux.HandleFunc(base+\"/login\", loginHandler)\n" "}\n" "func report(host string, port string) {\n" " http.Get(\"http://\" + host + \":\" + port + \"/log\")\n" "}\n", CBM_LANG_GO, "t", "routes.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMCall *reg = find_call_by_callee(r, "mux.HandleFunc"); ASSERT_NOT_NULL(reg); ASSERT_NOT_NULL(reg->first_string_arg); ASSERT_STR_EQ(reg->first_string_arg, "/login"); const CBMCall *out = find_call_by_callee(r, "http.Get"); ASSERT_NOT_NULL(out); ASSERT_NOT_NULL(out->first_string_arg); ASSERT_STR_EQ(out->first_string_arg, "/log"); cbm_free_result(r); PASS(); } /* Same issue: when the right side of the concatenation is not itself a * literal (`base + suffixVar`), there is no literal route to recover. The * fix must leave this unresolved rather than fabricate a path. */ TEST(extract_go_binary_concat_url_no_literal_suffix_issue1249) { CBMFileResult *r = extract("package main\n" "func setup(mux *http.ServeMux, base string, suffix string) {\n" " mux.HandleFunc(base+suffix, dynHandler)\n" "}\n", CBM_LANG_GO, "t", "routes.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMCall *reg = find_call_by_callee(r, "mux.HandleFunc"); ASSERT_NOT_NULL(reg); ASSERT_NULL(reg->first_string_arg); cbm_free_result(r); PASS(); } /* Reproduce-first: Java module QN must derive from the CONTAINING DIRECTORY, not * the filename stem, so a top-level class `Outer` in `Outer.java` is `t.Outer`, * NOT the doubled `t.Outer.Outer`. The nested method def QN must also equal the * QN the textual calls-enclosing path records for an in-body call (the * lsp_resolve join keys on exact caller_qn == enclosing_func_qn equality). */ TEST(extract_java_no_double_class_qn) { CBMFileResult *r = extract("class Outer {\n" " int helper(int x) { return x + 2; }\n" " class Inner {\n" " int run(int v) { return helper(v); }\n" " }\n" "}\n", CBM_LANG_JAVA, "t", "Outer.java"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Module QN is the directory (root) → just the project. */ ASSERT_NOT_NULL(r->module_qn); ASSERT_STR_EQ(r->module_qn, "t"); /* No def QN anywhere may double the top-level class name. */ for (int i = 0; i < r->defs.count; i++) { const char *qn = r->defs.items[i].qualified_name; if (qn) { ASSERT_EQ(strstr(qn, "Outer.Outer"), NULL); } } /* The nested class and its method carry the single-form QN. */ const CBMDefinition *outer = find_def_by_name(r, "Outer"); ASSERT_NOT_NULL(outer); ASSERT_STR_EQ(outer->qualified_name, "t.Outer"); const CBMDefinition *run = find_def_by_name(r, "run"); ASSERT_NOT_NULL(run); ASSERT_STR_EQ(run->qualified_name, "t.Outer.Inner.run"); /* The in-body call to helper() must be attributed to the SAME QN as the * method def — this is the equality the LSP cross-resolution join relies on * for nested classes (the lsp_outer_dispatch repro). */ const CBMCall *call = find_call_by_callee(r, "helper"); ASSERT_NOT_NULL(call); ASSERT_NOT_NULL(call->enclosing_func_qn); ASSERT_STR_EQ(call->enclosing_func_qn, run->qualified_name); cbm_free_result(r); PASS(); } /* Reproduce-first: Go module QN must derive from the CONTAINING DIRECTORY * (package), not the filename stem, so a type/method in `myapp/db/conn.go` * belongs to module `proj.myapp.db` and is NOT polluted with the `.conn.` * filename segment. */ TEST(extract_go_no_filename_in_module_qn) { CBMFileResult *r = extract("package db\n\n" "type Conn struct{}\n\n" "func (c *Conn) Query() {}\n", CBM_LANG_GO, "proj", "myapp/db/conn.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Module is the directory `myapp/db`, NOT `myapp/db/conn`. */ ASSERT_NOT_NULL(r->module_qn); ASSERT_STR_EQ(r->module_qn, "proj.myapp.db"); /* The type and method QNs must not contain the filename segment `.conn.`. */ const CBMDefinition *conn = find_def_by_name(r, "Conn"); ASSERT_NOT_NULL(conn); ASSERT_STR_EQ(conn->qualified_name, "proj.myapp.db.Conn"); /* Go method nodes keep a FLAT QN (module + name) with a separate * parent_class link to the receiver type — the QN must carry the * directory-based module and NOT the `.conn.` filename segment. */ const CBMDefinition *query = find_def_by_name(r, "Query"); ASSERT_NOT_NULL(query); ASSERT_STR_EQ(query->qualified_name, "proj.myapp.db.Query"); ASSERT_EQ(strstr(query->qualified_name, ".conn."), NULL); /* The method's parent_class must match the type node QN (for DEFINES_METHOD). */ ASSERT_NOT_NULL(query->parent_class); ASSERT_STR_EQ(query->parent_class, "proj.myapp.db.Conn"); cbm_free_result(r); PASS(); } /* Issue #213: large TS files were indexed as a File node with zero children. */ TEST(extract_large_ts_has_functions_issue213) { enum { NFUNCS = 4000 }; size_t cap = (size_t)NFUNCS * 80 + 64; char *src = (char *)malloc(cap); ASSERT_NOT_NULL(src); size_t off = 0; for (int i = 0; i < NFUNCS; i++) { off += (size_t)snprintf(src + off, cap - off, "export function fn%d(a: number): number { return a + %d; }\n", i, i); } CBMFileResult *r = cbm_extract_file(src, (int)off, CBM_LANG_TYPESCRIPT, "t", "big.ts", 0, NULL, NULL); ASSERT_NOT_NULL(r); int fns = count_defs_with_label(r, "Function"); ASSERT_GT(fns, 0); /* must not silently produce zero children */ cbm_free_result(r); free(src); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group: per-function complexity metrics (Tier A — local AST metrics) * * cbm_compute_complexity stamps each Function/Method with cyclomatic, * cognitive, loop_count and loop_depth in the same tree-sitter walk that * extracts the definition. loop_depth (max nested-loop depth) is the * polynomial-degree proxy used as a queryable bottleneck signal. * ═══════════════════════════════════════════════════════════════════ */ /* Return the first definition with the given name, or NULL. */ static const CBMDefinition *find_def(CBMFileResult *r, const char *name) { for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, name) == 0) return &r->defs.items[i]; } return NULL; } TEST(complexity_nested_loops_depth) { CBMFileResult *r = extract("package p\n" "func deepLoops() {\n" " for i := 0; i < 10; i++ {\n" " for j := 0; j < 10; j++ {\n" " for k := 0; k < 10; k++ {\n" " doWork()\n" " }\n" " }\n" " }\n" "}\n", CBM_LANG_GO, "t", "deep.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "deepLoops"); ASSERT_NOT_NULL(d); ASSERT_EQ(d->loop_depth, 3); /* three nested for-loops */ ASSERT_EQ(d->loop_count, 3); cbm_free_result(r); PASS(); } TEST(complexity_loop_with_branch) { CBMFileResult *r = extract("package p\n" "func single() {\n" " for i := 0; i < 10; i++ {\n" " if i > 5 {\n" " doWork()\n" " }\n" " }\n" "}\n", CBM_LANG_GO, "t", "single.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "single"); ASSERT_NOT_NULL(d); ASSERT_EQ(d->loop_depth, 1); ASSERT_EQ(d->loop_count, 1); /* the nested `if` contributes a branch, so cyclomatic > 1 and the * nesting-weighted cognitive score is non-zero. */ ASSERT_GT(d->complexity, 1); ASSERT_GT(d->cognitive, 0); cbm_free_result(r); PASS(); } TEST(complexity_flat_no_loops) { CBMFileResult *r = extract("package p\n" "func flat() {\n" " doWork()\n" " doMore()\n" "}\n", CBM_LANG_GO, "t", "flat.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "flat"); ASSERT_NOT_NULL(d); ASSERT_EQ(d->loop_depth, 0); ASSERT_EQ(d->loop_count, 0); cbm_free_result(r); PASS(); } /* A linear-scan call (contains) inside a loop → hidden O(n^2) signal. */ TEST(complexity_linear_scan_in_loop) { CBMFileResult *r = extract("package p\n" "func scanInLoop(xs []int, t int) bool {\n" " for i := 0; i < len(xs); i++ {\n" " if contains(xs, t) {\n" " return true\n" " }\n" " }\n" " return false\n" "}\n", CBM_LANG_GO, "t", "scan.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "scanInLoop"); ASSERT_NOT_NULL(d); ASSERT_GT(d->linear_scan_in_loop, 0); /* contains() called inside the for-loop */ cbm_free_result(r); PASS(); } /* Self-call inside a loop, not guarded by any conditional → recursion_in_loop * and unguarded_recursion both set. */ TEST(complexity_recursion_in_loop_unguarded) { CBMFileResult *r = extract("package p\n" "func recurInLoop(n int) {\n" " for i := 0; i < n; i++ {\n" " recurInLoop(n - 1)\n" " }\n" "}\n", CBM_LANG_GO, "t", "recur.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "recurInLoop"); ASSERT_NOT_NULL(d); ASSERT_TRUE(d->is_recursive); ASSERT_TRUE(d->recursion_in_loop); ASSERT_TRUE(d->unguarded_recursion); /* no self-call inside a conditional */ cbm_free_result(r); PASS(); } /* Self-call inside an `if` (a base-case guard) → recursive but NOT unguarded. */ TEST(complexity_guarded_recursion) { CBMFileResult *r = extract("package p\n" "func guarded(n int) int {\n" " if n > 0 {\n" " return guarded(n - 1)\n" " }\n" " return 0\n" "}\n", CBM_LANG_GO, "t", "guarded.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "guarded"); ASSERT_NOT_NULL(d); ASSERT_TRUE(d->is_recursive); ASSERT_FALSE(d->recursion_in_loop); ASSERT_FALSE(d->unguarded_recursion); /* self-call is guarded by `if n > 0` */ cbm_free_result(r); PASS(); } /* #599: super().save() inside a method named save is a parent-class call — * the receiver is super(), never self — so it must NOT flag self-recursion. * super()-ONLY fixture: no self.save() alongside, so the assertion cannot pass * vacuously off a genuine self-call. */ TEST(complexity_super_only_not_recursive) { CBMFileResult *r = extract("class B(A):\n" " def save(self):\n" " super().save()\n", CBM_LANG_PYTHON, "t", "super_only.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "save"); ASSERT_NOT_NULL(d); ASSERT_FALSE(d->is_recursive); /* parent-class call, not self-recursion */ ASSERT_FALSE(d->unguarded_recursion); cbm_free_result(r); PASS(); } /* #599: a same-named call on an unrelated receiver (axios.get inside a * function also named get) is delegation, not self-recursion. */ TEST(complexity_same_name_other_receiver_not_recursive) { CBMFileResult *r = extract("function get(url) {\n" " return axios.get(url);\n" "}\n", CBM_LANG_JAVASCRIPT, "t", "axios_get.js"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "get"); ASSERT_NOT_NULL(d); ASSERT_FALSE(d->is_recursive); /* axios.get targets axios, not this fn */ ASSERT_FALSE(d->unguarded_recursion); cbm_free_result(r); PASS(); } /* Guard: genuine self-recursion through self/this receivers still trips the * detector after the receiver-aware narrowing (#599). */ TEST(complexity_self_receiver_still_recursive) { /* Python: self.recur() — same object. */ CBMFileResult *r = extract("class C:\n" " def recur(self, n):\n" " if n > 0:\n" " self.recur(n - 1)\n", CBM_LANG_PYTHON, "t", "self_recur.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "recur"); ASSERT_NOT_NULL(d); ASSERT_TRUE(d->is_recursive); ASSERT_FALSE(d->unguarded_recursion); /* guarded by `if n > 0` */ cbm_free_result(r); /* JS: this.step() — same object. */ r = extract("class C {\n" " step(n) {\n" " if (n > 0) { this.step(n - 1); }\n" " }\n" "}\n", CBM_LANG_JAVASCRIPT, "t", "this_step.js"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); d = find_def(r, "step"); ASSERT_NOT_NULL(d); ASSERT_TRUE(d->is_recursive); ASSERT_FALSE(d->unguarded_recursion); /* guarded by `if (n > 0)` */ cbm_free_result(r); PASS(); } /* #599: chained receiver — self.obj.recur() inside recur targets self's FIELD * obj, a different object. The whole receiver chain ("self.obj") must be * compared, not just its first segment ("self"). */ TEST(complexity_chained_receiver_not_self) { CBMFileResult *r = extract("class C:\n" " def recur(self, n):\n" " self.obj.recur(n)\n", CBM_LANG_PYTHON, "t", "chained_recur.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "recur"); ASSERT_NOT_NULL(d); ASSERT_FALSE(d->is_recursive); /* self.obj is not self */ ASSERT_FALSE(d->unguarded_recursion); cbm_free_result(r); PASS(); } /* #599: Go method receiver — the enclosing def's own receiver identifier * (`s` in `func (s *Store) save()`) is whitelisted dynamically from * CBMDefinition.receiver, so s.save() still counts as self-recursion while * s.backup.save() (a field's same-named method) does not. */ TEST(complexity_go_method_receiver_self_recursion) { CBMFileResult *r = extract("package p\n" "type Store struct{}\n" "func (s *Store) save(n int) {\n" " if n > 0 {\n" " s.save(n - 1)\n" " }\n" "}\n", CBM_LANG_GO, "t", "store.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "save"); ASSERT_NOT_NULL(d); ASSERT_TRUE(d->is_recursive); /* s.save() == receiver s → self */ ASSERT_FALSE(d->unguarded_recursion); /* guarded by `if n > 0` */ cbm_free_result(r); /* Same-named method on a field of the receiver: NOT self-recursion. */ r = extract("package p\n" "type Store struct{ backup *Store }\n" "func (s *Store) save(n int) {\n" " s.backup.save(n)\n" "}\n", CBM_LANG_GO, "t", "store_backup.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); d = find_def(r, "save"); ASSERT_NOT_NULL(d); ASSERT_FALSE(d->is_recursive); /* s.backup is not s */ ASSERT_FALSE(d->unguarded_recursion); cbm_free_result(r); PASS(); } /* #876: the delegation/store bucket left open after #599 — same-named calls * whose receiver is a CALL RESULT (_get_store().get), a module singleton * (_default.check), or a local cursor inside a loop (cur.execute) are not * self-recursion. All three are the reporter's exact v0.8.1 false positives; * fixed by the receiver-aware narrowing (87091ed), guarded here so the * bucket never regresses. */ TEST(complexity_delegation_receivers_not_recursive_issue876) { /* Store wrapper: _get_store().get() inside get — receiver is the call * result, a different object. */ CBMFileResult *r = extract("def _get_store():\n" " return object()\n" "\n" "def get(collection, record_id):\n" " rec = _get_store().get(collection, record_id)\n" " return rec\n", CBM_LANG_PYTHON, "t", "soil.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "get"); ASSERT_NOT_NULL(d); ASSERT_FALSE(d->is_recursive); /* _get_store() result is not this fn */ ASSERT_FALSE(d->unguarded_recursion); cbm_free_result(r); /* Module-singleton delegation: _default.check() inside check. */ r = extract("class _Checker:\n" " def check(self, app_id, tool_name):\n" " return (True, \"ok\")\n" "\n" "_default = _Checker()\n" "\n" "def check(app_id, tool_name):\n" " return _default.check(app_id, tool_name)\n", CBM_LANG_PYTHON, "t", "gleipnir.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* The module-level wrapper (not the method) must stay clean. */ d = NULL; for (int i = 0; i < r->defs.count; i++) { if (r->defs.items[i].name && strcmp(r->defs.items[i].name, "check") == 0 && strcmp(r->defs.items[i].label, "Function") == 0) { d = &r->defs.items[i]; } } ASSERT_NOT_NULL(d); ASSERT_FALSE(d->is_recursive); /* _default is not this fn */ ASSERT_FALSE(d->unguarded_recursion); cbm_free_result(r); /* Same-named method on a local, called inside a loop (willow.nuke case): * neither recursive nor recursion_in_loop. */ r = extract("def execute(conn, paths):\n" " for p in paths:\n" " cur = conn.cursor()\n" " cur.execute(\"DELETE FROM t WHERE p = %s\", (p,))\n" " return True\n", CBM_LANG_PYTHON, "t", "nuke.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); d = find_def(r, "execute"); ASSERT_NOT_NULL(d); ASSERT_FALSE(d->is_recursive); /* cur is not this fn */ ASSERT_FALSE(d->unguarded_recursion); ASSERT_FALSE(d->recursion_in_loop); cbm_free_result(r); PASS(); } /* Deep chained member access + parameter count structure smells. */ TEST(complexity_access_depth_and_params) { CBMFileResult *r = extract("package p\n" "func deepAccess(x Foo, a int, b int, c int) int {\n" " return x.alpha.beta.gamma.delta\n" "}\n", CBM_LANG_GO, "t", "access.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); const CBMDefinition *d = find_def(r, "deepAccess"); ASSERT_NOT_NULL(d); ASSERT_GT(d->max_access_depth, 2); /* x.alpha.beta.gamma.delta */ ASSERT_GTE(d->param_count, 3); /* x, a, b, c (grouping may vary) */ cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Perl call-graph noise (#459 follow-up) * ═══════════════════════════════════════════════════════════════════ */ /* Count calls whose callee_name is exactly `name` (has_call is substring). */ static int count_calls_exact(CBMFileResult *r, const char *name) { int n = 0; for (int i = 0; i < r->calls.count; i++) { if (r->calls.items[i].callee_name && strcmp(r->calls.items[i].callee_name, name) == 0) n++; } return n; } /* (b) A dotted config string must never be extracted as a callee. */ TEST(extract_perl_config_string_not_a_callee) { CBMFileResult *r = extract("package C;\n" "sub run {\n" " my $cfg = { \"log4perl.appender.File.utf8\" => 1 };\n" " helper();\n" "}\n" "sub helper { return 1; }\n" "1;\n", CBM_LANG_PERL, "t", "app.pl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* No callee may contain a '.' (config/string tokens are rejected). */ for (int i = 0; i < r->calls.count; i++) { ASSERT_TRUE(strchr(r->calls.items[i].callee_name, '.') == NULL); } /* (d) The genuine intra-file function call is still extracted. */ ASSERT_TRUE(count_calls_exact(r, "helper") >= 1); cbm_free_result(r); PASS(); } /* (a) A Perl builtin call is extracted as a non-method callee. Suppression of * the resulting CALLS edge happens in the resolver (see test_registry.c / * end-to-end); extraction itself keeps the bare builtin token. */ TEST(extract_perl_builtin_call_is_function_not_method) { CBMFileResult *r = extract("package B;\n" "sub run {\n" " my @x;\n" " push @x, 1;\n" " keys %h;\n" "}\n" "1;\n", CBM_LANG_PERL, "t", "b.pl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* push / keys are extracted (they are valid identifiers) ... */ ASSERT_TRUE(has_call(r, "push")); /* ... and crucially are NOT flagged as method calls. */ for (int i = 0; i < r->calls.count; i++) { ASSERT_FALSE(r->calls.items[i].is_method); } cbm_free_result(r); PASS(); } /* (c) An arrow/method call is extracted with is_method=true so the resolver * can suppress generic short-name matching for it. */ TEST(extract_perl_method_call_flags_is_method) { CBMFileResult *r = extract("package M;\n" "sub run {\n" " my $self = shift;\n" " $self->commit();\n" " $dbh->commit();\n" " helper();\n" "}\n" "sub helper { return 1; }\n" "1;\n", CBM_LANG_PERL, "t", "m.pl"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Every "commit" call is a method call (is_method set). */ int commit_calls = 0; for (int i = 0; i < r->calls.count; i++) { if (strcmp(r->calls.items[i].callee_name, "commit") == 0) { commit_calls++; ASSERT_TRUE(r->calls.items[i].is_method); } /* The genuine function call is NOT a method. */ if (strcmp(r->calls.items[i].callee_name, "helper") == 0) { ASSERT_FALSE(r->calls.items[i].is_method); } } ASSERT_TRUE(commit_calls >= 1); /* (d) genuine intra-file function call still extracted. */ ASSERT_TRUE(count_calls_exact(r, "helper") >= 1); cbm_free_result(r); PASS(); } /* Languages OUTSIDE the is_method flag set (only Perl and TS/JS/TSX set it) must * be unaffected: a Go method call never sets is_method. */ TEST(extract_flag_exempt_method_call_not_flagged_is_method) { CBMFileResult *r = extract("package m\n" "func run(o Obj) { o.Commit(); helper() }\n", CBM_LANG_GO, "t", "x.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); for (int i = 0; i < r->calls.count; i++) { ASSERT_FALSE(r->calls.items[i].is_method); } cbm_free_result(r); PASS(); } /* TS/JS/TSX receiver-aware flag (#592/#606; same intent as the Perl flag above). * A member call x.foo() with a non-this/super receiver is flagged is_method so * the resolver can suppress a weak short-name match (`re.test()` must not bind a * project `test`); a bare call is not flagged. */ TEST(extract_ts_member_call_flags_is_method) { CBMFileResult *r = extract("const re = /^a+$/;\n" "export function checkFormat(s: string) { return re.test(s); }\n" "function helper() { return 1; }\n" "export function run() { return helper(); }\n", CBM_LANG_TYPESCRIPT, "t", "a.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int member = 0; int bare = 0; for (int i = 0; i < r->calls.count; i++) { const char *cn = r->calls.items[i].callee_name; if (strcmp(cn, "re.test") == 0) { member++; ASSERT_TRUE(r->calls.items[i].is_method); } if (strcmp(cn, "helper") == 0) { bare++; ASSERT_FALSE(r->calls.items[i].is_method); } } ASSERT_TRUE(member >= 1); /* re.test() flagged */ ASSERT_TRUE(bare >= 1); /* helper() not flagged */ cbm_free_result(r); PASS(); } /* this/super receivers keep the enclosing-class target, where a weak * namespace-proximity match is usually correct — so they are NOT flagged. A * new_expression has no member receiver and is never flagged either. */ TEST(extract_ts_this_super_receiver_not_flagged) { CBMFileResult *r = extract("class A extends B {\n" " m() { this.helper(); super.render(); return new A(); }\n" " helper() {}\n" "}\n", CBM_LANG_TYPESCRIPT, "t", "b.ts"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); /* Every call here has a self receiver (this/super) or is a new-expression, * so NONE may be flagged. */ for (int i = 0; i < r->calls.count; i++) { ASSERT_FALSE(r->calls.items[i].is_method); } /* Sanity: the this/super member calls were actually extracted. */ ASSERT_TRUE(has_call(r, "helper")); /* this.helper */ ASSERT_TRUE(has_call(r, "render")); /* super.render */ cbm_free_result(r); PASS(); } /* JS dialect behaves like TS (the flag is gated on the language set, not the * dialect). Replaces the pre-#592 test that asserted JS never flags is_method. */ TEST(extract_js_member_call_flags_is_method) { CBMFileResult *r = extract("function run(o){ o.commit(); helper(); }\n", CBM_LANG_JAVASCRIPT, "t", "x.js"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int member = 0; int bare = 0; for (int i = 0; i < r->calls.count; i++) { const char *cn = r->calls.items[i].callee_name; if (strcmp(cn, "o.commit") == 0) { member++; ASSERT_TRUE(r->calls.items[i].is_method); } if (strcmp(cn, "helper") == 0) { bare++; ASSERT_FALSE(r->calls.items[i].is_method); } } ASSERT_TRUE(member >= 1); /* o.commit() flagged */ ASSERT_TRUE(bare >= 1); /* helper() not flagged */ cbm_free_result(r); PASS(); } /* #961: a C function whose body braces are split across #ifdef/#else * branches (one open brace per branch, a single shared close) parses with * an ERROR region on the raw source — both branches are present at once — * and the defs walk silently dropped the function while its callers stayed * (cbm_path_within_root, handle_process_kill). The preprocessed second * pass (simplecpp picks one branch, same-file token lines stay aligned) * must recover the definition with its original line. */ TEST(extract_c_ifdef_split_brace_fn_recovered_issue961) { CBMFileResult *r = extract("static int a(void) { return 1; }\n" "static int b(void) { return 2; }\n" "int split_brace_fn(int x) {\n" "#ifdef _WIN32\n" " if (a() && b()) {\n" "#else\n" " if (a() || b()) {\n" "#endif\n" " x += 1;\n" " }\n" " return x;\n" "}\n" "int after_fn(int x) { return x; }\n", CBM_LANG_C, "t", "split.c"); ASSERT_NOT_NULL(r); const CBMDefinition *d = find_def(r, "split_brace_fn"); if (!d) { fprintf(stderr, " [961] FAIL split_brace_fn dropped (defs walk lost the " "#ifdef-split function)\n"); } ASSERT_NOT_NULL(d); ASSERT_EQ((int)d->start_line, 3); /* Error recovery must stay localized: neighbours extract either way. */ ASSERT_NOT_NULL(find_def(r, "a")); ASSERT_NOT_NULL(find_def(r, "after_fn")); cbm_free_result(r); PASS(); } static const char *CPP_PREPROC_SIGNATURE_GAP_SRC = "struct Rect {};\n" "struct IBinder {};\n" "struct IRegionSamplingListener {};\n" "typedef int status_t;\n" "\n" "class SurfaceFlinger {\n" "public:\n" " status_t addRegionSamplingListener(const Rect&, const IBinder&,\n" " const IRegionSamplingListener&, bool);\n" " status_t addRegionSamplingListener(const Rect&, const IBinder&,\n" " const IRegionSamplingListener&);\n" " void commit();\n" " void composite();\n" "};\n" "\n" "#ifdef FLYME_GRAPHICS_EXTEND_LUMARGB\n" "status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,\n" " const IBinder& stopLayerHandle,\n" " const IRegionSamplingListener& listener,\n" " const bool rgbSample) {\n" "#else\n" "status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,\n" " const IBinder& stopLayerHandle,\n" " const IRegionSamplingListener& listener) " "{\n" "#endif\n" " return 0;\n" "}\n" "\n" "void SurfaceFlinger::commit() {}\n" "\n" "void SurfaceFlinger::composite() {}\n"; /* #946 fixture from the original report: both preprocessor choices must keep * raw definitions primary while recovering later methods at original lines. */ TEST(extract_cpp_preproc_signature_gap_issue946) { const char *defines[] = {"FLYME_GRAPHICS_EXTEND_LUMARGB", NULL}; for (int enabled = 0; enabled < 2; enabled++) { CBMFileResult *r = cbm_extract_file( CPP_PREPROC_SIGNATURE_GAP_SRC, (int)strlen(CPP_PREPROC_SIGNATURE_GAP_SRC), CBM_LANG_CPP, "t", "SurfaceFlinger.cpp", 0, enabled ? defines : NULL, NULL); ASSERT_NOT_NULL(r); const CBMDefinition *add = find_def(r, "addRegionSamplingListener"); const CBMDefinition *commit = find_def(r, "commit"); const CBMDefinition *composite = find_def(r, "composite"); ASSERT_NOT_NULL(add); ASSERT_NOT_NULL(commit); ASSERT_NOT_NULL(composite); ASSERT_EQ(add->start_line, 22u); ASSERT_EQ(add->end_line, 27u); ASSERT_EQ(commit->start_line, 29u); ASSERT_EQ(commit->end_line, 29u); ASSERT_EQ(composite->start_line, 31u); ASSERT_EQ(composite->end_line, 31u); cbm_free_result(r); } PASS(); } /* Macro expansion can produce callable-looking AST nodes, but no callable * definition exists in the original span; recovery must fail closed. */ TEST(extract_cpp_preproc_macro_generated_callable_skipped_issue949) { const char *src = "#define MAKE_FN(name) int name() { return 1; }\n" "#ifdef ENABLE_GENERATED\n" "MAKE_FN(generated)\n" "#endif\n" "int visible() { return 0; }\n"; const char *defines[] = {"ENABLE_GENERATED", NULL}; CBMFileResult *r = cbm_extract_file(src, (int)strlen(src), CBM_LANG_CPP, "t", "macro.cpp", 0, defines, NULL); ASSERT_NOT_NULL(r); ASSERT_NULL(find_def(r, "generated")); ASSERT_NOT_NULL(find_def(r, "visible")); ASSERT_TRUE(r->parse_incomplete); ASSERT_GTE(r->error_region_count, 1); ASSERT_NOT_NULL(r->error_ranges); cbm_free_result(r); PASS(); } /* #949 follow-up: an included header shifts physical lines in simplecpp's * expanded output. The #1050 name-on-same-line guard skipped this recoverable * definition; explicit source ownership mapping must restore its original * coordinates while keeping header definitions out of the main file. */ TEST(extract_c_ifdef_split_brace_after_include_remapped_issue949) { char tmpdir[512]; snprintf(tmpdir, sizeof(tmpdir), "%s/cbm_line_map_XXXXXX", cbm_tmpdir()); ASSERT_NOT_NULL(cbm_mkdtemp(tmpdir)); char header_path[512]; snprintf(header_path, sizeof(header_path), "%s/padding.h", tmpdir); FILE *header = cbm_fopen(header_path, "wb"); ASSERT_NOT_NULL(header); for (int i = 0; i < 40; i++) { ASSERT_GTE(fprintf(header, "static int header_pad_%d(void) { return %d; }\n", i, i), 0); } ASSERT_EQ(fclose(header), 0); const char *includes[] = {tmpdir, NULL}; const char *src = "#include \"padding.h\"\n" "static int before(void) { return 1; }\n" "int shifted_split(int x) {\n" "#ifdef _WIN32\n" " if (before()) {\n" "#else\n" " if (x > 0) {\n" "#endif\n" " x += 1;\n" " }\n" " return x;\n" "}\n" "int after(void) { return 2; }\n"; CBMFileResult *r = cbm_extract_file(src, (int)strlen(src), CBM_LANG_C, "t", "shifted.c", 0, NULL, includes); cbm_unlink(header_path); cbm_rmdir(tmpdir); ASSERT_NOT_NULL(r); const CBMDefinition *d = find_def(r, "shifted_split"); ASSERT_NOT_NULL(d); ASSERT_EQ(d->start_line, 3u); ASSERT_EQ(d->end_line, 12u); ASSERT_NULL(find_def(r, "header_pad_0")); ASSERT_NOT_NULL(find_def(r, "after")); ASSERT_FALSE(r->parse_incomplete); ASSERT_EQ(r->error_region_count, 0); ASSERT_NULL(r->error_ranges); cbm_free_result(r); PASS(); } /* #961 inverse guard: a clean C file must not gain duplicate or phantom * defs from the recovery path (it only engages on raw-parse ERROR regions). */ TEST(extract_c_clean_file_no_recovery_duplicates_issue961) { CBMFileResult *r = extract("#ifdef _WIN32\n" "static int w(void) { return 1; }\n" "#else\n" "static int u(void) { return 2; }\n" "#endif\n" "int use(int x) { return x; }\n", CBM_LANG_C, "t", "clean.c"); ASSERT_NOT_NULL(r); int use_count = 0; for (int i = 0; i < r->defs.count; i++) { if (r->defs.items[i].name && strcmp(r->defs.items[i].name, "use") == 0) use_count++; } ASSERT_EQ(use_count, 1); cbm_free_result(r); PASS(); } /* #668: walk_defs used a fixed `walk_defs_frame_t stack[4096]` — a ~160 KB * C-stack frame that overflowed small thread stacks (the reporter's crash was in * the "definitions pass" on a large SQL file), and whose `top < 4096` push guards * SILENTLY DROPPED every top-level definition past 4096. The growable heap stack * fixes both: a file with > 4096 top-level defs must extract ALL of them. RED * before the fix (extracted count capped near 4096), GREEN after. */ TEST(walk_defs_no_truncation_over_4096_issue668) { enum { N = 5000 }; /* N top-level Python defs → N Function defs, all direct module children, so * walk_defs pushes all N children at once — the >4096 truncation site. */ size_t cap = (size_t)N * 24 + 1; char *src = (char *)malloc(cap); ASSERT_NOT_NULL(src); size_t off = 0; for (int i = 0; i < N; i++) { off += (size_t)snprintf(src + off, cap - off, "def f%d(): pass\n", i); } CBMFileResult *r = extract(src, CBM_LANG_PYTHON, "t", "big.py"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int nfuncs = count_defs_with_label(r, "Function"); ASSERT(nfuncs >= N); /* all N present — not truncated at the old 4096 cap */ cbm_free_result(r); free(src); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Suite * ═══════════════════════════════════════════════════════════════════ */ /* Rust: inline #[test]/#[tokio::test] functions must be marked is_test so the * store.c `is_test != 1` filter excludes them from graph context. Detection is * otherwise file-path-based (cbm_is_test_file), so test fns in a regular .rs * file leak. (#855) */ TEST(extract_rust_test_attr_marks_is_test_issue855) { CBMFileResult *r = extract("pub fn real_fn() {}\n" "\n" "#[test]\n" "fn sync_test() {}\n" "\n" "#[tokio::test]\n" "async fn async_test() {}\n", CBM_LANG_RUST, "t", "src/lib.rs"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int real = -1, sync = -1, asyn = -1; for (int i = 0; i < r->defs.count; i++) { const char *n = r->defs.items[i].name; if (!n) { continue; } if (strcmp(n, "real_fn") == 0) { real = r->defs.items[i].is_test ? 1 : 0; } else if (strcmp(n, "sync_test") == 0) { sync = r->defs.items[i].is_test ? 1 : 0; } else if (strcmp(n, "async_test") == 0) { asyn = r->defs.items[i].is_test ? 1 : 0; } } ASSERT(real >= 0 && sync >= 0 && asyn >= 0 && "all three fns extracted"); ASSERT(real == 0 && "real_fn is NOT a test"); ASSERT(sync == 1 && "#[test] fn is_test"); ASSERT(asyn == 1 && "#[tokio::test] fn is_test"); cbm_free_result(r); PASS(); } /* Function.is_test must follow the file's test-directory membership, not just * a test_/_test basename convention: a helper under tests/ with none of those * naming conventions (tests/helpers/fixtures.c) was previously indexed as an * ordinary, non-test Function — invisible to store.c's `is_test != 1` * dead-code/search filters even though trace_path's own independent path * check already treated the same file as a test (#1294). */ TEST(extract_c_test_dir_marks_is_test_issue1294) { const char *src = "void helper(void) {}\n"; /* Regression: a test_*-named file directly under tests/ must remain a * test (this already worked before the fix). */ CBMFileResult *r1 = extract(src, CBM_LANG_C, "t", "tests/test_pipeline.c"); ASSERT_NOT_NULL(r1); ASSERT_FALSE(r1->has_error); ASSERT(has_def(r1, "Function", "helper")); int reg = -1; for (int i = 0; i < r1->defs.count; i++) { if (strcmp(r1->defs.items[i].label, "Function") == 0 && r1->defs.items[i].name && strcmp(r1->defs.items[i].name, "helper") == 0) { reg = r1->defs.items[i].is_test ? 1 : 0; } } ASSERT(reg == 1 && "test_*.c directly under tests/ is a test (regression)"); cbm_free_result(r1); /* Positive: a file under tests/ that matches none of the test_/_test * naming conventions must now ALSO be a test. */ CBMFileResult *r2 = extract(src, CBM_LANG_C, "t", "tests/helpers/fixtures.c"); ASSERT_NOT_NULL(r2); ASSERT_FALSE(r2->has_error); ASSERT(has_def(r2, "Function", "helper")); int nonconv = -1; for (int i = 0; i < r2->defs.count; i++) { if (strcmp(r2->defs.items[i].label, "Function") == 0 && r2->defs.items[i].name && strcmp(r2->defs.items[i].name, "helper") == 0) { nonconv = r2->defs.items[i].is_test ? 1 : 0; } } ASSERT(nonconv == 1 && "non-test_-named file under tests/ is now a test"); cbm_free_result(r2); /* Negative: a file with no test/ directory or test_/_test naming in its * path must never be flagged — this fix must not widen detection beyond * the tests/ (and sibling) directory tree. */ CBMFileResult *r3 = extract(src, CBM_LANG_C, "t", "src/pipeline/helper.c"); ASSERT_NOT_NULL(r3); ASSERT_FALSE(r3->has_error); ASSERT(has_def(r3, "Function", "helper")); int outside = -1; for (int i = 0; i < r3->defs.count; i++) { if (strcmp(r3->defs.items[i].label, "Function") == 0 && r3->defs.items[i].name && strcmp(r3->defs.items[i].name, "helper") == 0) { outside = r3->defs.items[i].is_test ? 1 : 0; } } ASSERT(outside == 0 && "file outside tests/ is never a test"); cbm_free_result(r3); PASS(); } /* Same convergence for Method definitions (push_method_def), which take a * separate code path from free functions: a class method on a class defined * under tests/ with no test_/_test naming (e.g. tests/helpers/base.py) must * be marked is_test too, and a method on the same class outside tests/ must * not be (#1294). */ TEST(extract_python_method_test_dir_marks_is_test_issue1294) { const char *src = "class Foo:\n" " def helper(self):\n" " pass\n"; /* Python's LSP layer injects synthetic builtin stub Methods (str.upper, * dict.get, ...) into defs.items alongside real ones (py_builtins.c), so * matching must key on name, not just label="Method". */ CBMFileResult *r1 = extract(src, CBM_LANG_PYTHON, "t", "tests/helpers/base.py"); ASSERT_NOT_NULL(r1); ASSERT_FALSE(r1->has_error); ASSERT(has_def(r1, "Method", "helper")); int in_tests = -1; for (int i = 0; i < r1->defs.count; i++) { if (strcmp(r1->defs.items[i].label, "Method") == 0 && r1->defs.items[i].name && strcmp(r1->defs.items[i].name, "helper") == 0) { in_tests = r1->defs.items[i].is_test ? 1 : 0; } } ASSERT(in_tests == 1 && "method on a class under tests/helpers/ is a test"); cbm_free_result(r1); CBMFileResult *r2 = extract(src, CBM_LANG_PYTHON, "t", "app/models/base.py"); ASSERT_NOT_NULL(r2); ASSERT_FALSE(r2->has_error); ASSERT(has_def(r2, "Method", "helper")); int outside_tests = -1; for (int i = 0; i < r2->defs.count; i++) { if (strcmp(r2->defs.items[i].label, "Method") == 0 && r2->defs.items[i].name && strcmp(r2->defs.items[i].name, "helper") == 0) { outside_tests = r2->defs.items[i].is_test ? 1 : 0; } } ASSERT(outside_tests == 0 && "method on a class outside tests/ is never a test"); cbm_free_result(r2); PASS(); } /* #1017: docstring truncation at MAX_COMMENT_LEN (500 bytes) can split a * multi-byte UTF-8 character, leaving an incomplete byte sequence. * Craft a Go comment whose 498th-500th bytes are a 3-byte CJK character * (U+6210 = 成 = e6 88 90). The raw byte truncation at offset 500 lands * one byte past the character start, splitting it. After the fix the * truncated string must end on a complete codepoint boundary. */ TEST(docstring_utf8_truncation_boundary_issue1017) { /* Build a comment: "// " (3 bytes) + 495 ASCII 'A' + "成成成" (9 bytes) * Total comment text = 3 + 495 + 9 = 507 bytes. * MAX_COMMENT_LEN = 500. The first kanji (成 = e6 88 90) occupies * offsets 498-500, so text[500] = '\0' keeps bytes 0-499: the lead * byte 0xe6 plus one continuation 0x88 — an incomplete 2-of-3 sequence. * Before fix: the truncated string ended with that broken pair. */ char comment[600]; int off = 0; comment[off++] = '/'; comment[off++] = '/'; comment[off++] = ' '; for (int i = 0; i < 495; i++) comment[off++] = 'A'; /* U+6210 (成) = 0xe6 0x88 0x90 — 3-byte UTF-8 */ const char *kanji = "\xe6\x88\x90"; for (int k = 0; k < 3; k++) { memcpy(comment + off, kanji, 3); off += 3; } comment[off] = '\0'; /* Wrap in a Go function so the comment becomes the docstring. */ char src[800]; snprintf(src, sizeof(src), "package main\n\n%s\nfunc Compute() {}\n", comment); CBMFileResult *r = extract(src, CBM_LANG_GO, "test", "main.go"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "Compute")); const char *doc = NULL; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "Compute") == 0) { doc = r->defs.items[i].docstring; break; } } ASSERT_NOT_NULL(doc); /* Verify every byte in the truncated docstring is valid UTF-8: * no trailing incomplete multi-byte sequence. */ size_t len = strlen(doc); ASSERT_TRUE(len <= 500); const unsigned char *u = (const unsigned char *)doc; size_t i = 0; while (i < len) { unsigned char c = u[i]; int seq_len; if (c < 0x80) seq_len = 1; else if ((c & 0xE0) == 0xC0) seq_len = 2; else if ((c & 0xF0) == 0xE0) seq_len = 3; else if ((c & 0xF8) == 0xF0) seq_len = 4; else FAIL("invalid UTF-8 lead byte"); ASSERT_TRUE(i + (size_t)seq_len <= len); for (int j = 1; j < seq_len; j++) ASSERT_TRUE((u[i + (size_t)j] & 0xC0) == 0x80); i += (size_t)seq_len; } cbm_free_result(r); PASS(); } /* Reproduce-first (ms-typescript reallyLargeFile.ts, 2026-07-07): a file * whose root node has hundreds of thousands of FLAT SIBLINGS (580k //// * comment lines in the 3.5 MB fourslash fixture) hung extraction for over * 15 minutes: walk_defs pushed children via index-based ts_node_child(i), * which is O(i) per call in tree-sitter — O(n^2) per wide node (~1.7e11 * iterator steps on the real file; 100% of stack samples inside * ts_node_child_iterator_next). The supervisor then killed the silent * worker as a hang and, via the stale extraction marker, quarantined * INNOCENT files on every retry. * * This fixture is an 80k-sibling flat file: quadratic child access needs * minutes under ASan; the linear TSTreeCursor collection finishes in * milliseconds. The 30 s bound has ~100x headroom over the fixed cost — * RED on index-based child pushes, GREEN on the cursor walk. The def-count * guard keeps the test honest: extraction must actually process the whole * breadth, not skip it. */ /* Extract a wide-flat comment-sibling fixture of n lines (the exact shape of * ms-typescript's reallyLargeFile.ts: hundreds of thousands of flat comment * children under the root, plus sparse real defs so the breadth check cannot * pass vacuously). Returns elapsed milliseconds; stores the def count. */ static long extract_wide_flat_ms(int n, int *out_defs) { const size_t cap = (size_t)n * 24 + (size_t)8192; /* "// wide filler N\n" <= 24 chars */ char *src = malloc(cap); if (!src) { return -1; } size_t off = 0; /* CONSTANT def count (10), independent of n: defs that scale WITH n make * the fixture superlinear through the separate per-def sibling-scan cost * (O(defs x siblings)) — on windows-CLANG64 ASan that pushed the ratio * of LINEAR walk code to 43x. Ten spread-out defs keep the breadth check * honest while the sibling-scan term stays 10 x n = linear. */ const int def_stride = n / 10; for (int i = 0; i < n; i++) { off += (size_t)snprintf(src + off, cap - off, "// wide filler %d\n", i); if (i % def_stride == 0) { off += (size_t)snprintf(src + off, cap - off, "var wide_a%d = %d;\n", i, i); } } struct timespec a; struct timespec b; cbm_clock_gettime(CLOCK_MONOTONIC, &a); CBMFileResult *r = cbm_extract_file(src, (int)off, CBM_LANG_JAVASCRIPT, "proj", "wide.js", 0, NULL, NULL); cbm_clock_gettime(CLOCK_MONOTONIC, &b); free(src); if (!r) { return -1; } *out_defs = r->defs.count; cbm_free_result(r); return (b.tv_sec - a.tv_sec) * 1000L + (b.tv_nsec - a.tv_nsec) / 1000000L; } /* Best-of-N. Timing noise only ever ADDS time, so the minimum of a few runs is * the cheapest good estimate of the noise-free cost. A single sample at each * size made the RATIO carry the noise of BOTH measurements: on a loaded Windows * VM this read 184ms -> 9387ms (51x) for code that measures ~20x unloaded, and * tripped a bound calibrated for exactly that linear case. * * Deliberately NOT solved by raising WF_RATIO_MAX: the bound sits where it does * because linear (~20x) and quadratic (~128x) are each >=2x away from it, so * inflating it moves the test toward the very signal it exists to catch. This * keeps the threshold and removes the variance instead. */ static long extract_wide_flat_ms_best_of(int n, int reps, int *out_defs) { long best = -1; for (int i = 0; i < reps; i++) { int defs = 0; long ms = extract_wide_flat_ms(n, &defs); if (ms < 0) { return ms; } if (best < 0 || ms < best) { best = ms; *out_defs = defs; } } return best; } TEST(extract_wide_flat_file_is_linear) { /* SCALING-RATIO guard: assert the COMPLEXITY CLASS, not a wall-clock * bound. Index-based ts_node_child(i) child loops are O(i) per call — * quadratic per wide node — and hung a 580k-sibling file for hours * (ms-typescript reallyLargeFile.ts). An absolute time bound conflates * machine speed with complexity: the gcc-13-ARM ASan CI leg runs this * extraction ~200x slower than clang at the SAME (measured perfectly * linear: 27.1s/54.2s/108.3s for 50k/100k/200k) complexity, and flunked * a 30s bound on linear code. Growing the input 4x must grow the time * ~4x when linear and ~16x when quadratic; the 10x bound splits those * decisively on every toolchain. The 120ms floor keeps clock noise from * mattering on fast machines. */ /* Growth and bound CALIBRATED FROM MEASUREMENT, not models. The ASan * test build carries a large LINEAR per-line baseline (~31us/line on * clang-macOS, ~540us/line on gcc-13-ARM) that dilutes small-growth * ratios: at 8x growth the measured quadratic ratio was 22.4 — a 24x * bound false-passed the known-quadratic pre-merge walk. At 20x growth * the measured ratios are ~20x for linear code (both toolchains) and * ~128x for the quadratic walk (clang-macOS) — bound 40 sits >=2x from * both. The 120ms floor keeps clock noise irrelevant on fast hosts. */ enum { WF_SMALL = 20 * 1000, WF_BIG = 400 * 1000, WF_RATIO_MAX = 40, WF_FLOOR_MS = 120 }; int defs_small = 0; int defs_big = 0; /* Small is cheap, so sample it more; big dominates runtime, so twice is the * affordable compromise that still discards one unlucky sample. */ long t_small = extract_wide_flat_ms_best_of(WF_SMALL, 3, &defs_small); long t_big = extract_wide_flat_ms_best_of(WF_BIG, 2, &defs_big); ASSERT_GTE(t_small, 0); ASSERT_GTE(t_big, 0); /* Anti-vacuous guard: the breadth was actually walked at both sizes. */ ASSERT_GTE(defs_small, 8); ASSERT_GTE(defs_big, 8); fprintf(stderr, " [wide-flat] t(%d)=%ldms t(%d)=%ldms\n", WF_SMALL, t_small, WF_BIG, t_big); long base = t_small > WF_FLOOR_MS ? t_small : WF_FLOOR_MS; if (t_big > WF_RATIO_MAX * base) { char msg[160]; snprintf(msg, sizeof(msg), "wide-flat scaling 20x input: %ldms -> %ldms (> %dx base %ldms) — " "quadratic child access", t_small, t_big, WF_RATIO_MAX, base); FAIL(msg); } PASS(); } #if defined(CBM_CALL_REFERENCE_LOOKUP_TEST_API) && CBM_CALL_REFERENCE_LOOKUP_TEST_API /* A flat block of value-reference statements exercises occurrence-role * classification for every identifier. The old parent/child field lookup * restarts at the block's first child for each statement, so 8x more source * produces about 64x more lookup work. Count work instead of wall time: this * RED is deterministic, sanitizer-independent, and finishes quickly even on * the known-quadratic implementation. */ static uint64_t extract_wide_reference_field_work(int statement_count, int *out_usages, uint64_t *out_slow_parent_fallbacks) { static const char prefix[] = "function target() {}\nfunction wide() {\n"; static const char statement[] = " target;\n"; static const char suffix[] = "}\n"; size_t capacity = sizeof(prefix) + (size_t)statement_count * sizeof(statement) + sizeof(suffix); char *source = malloc(capacity); if (!source) { return UINT64_MAX; } size_t offset = 0; memcpy(source + offset, prefix, sizeof(prefix) - 1U); offset += sizeof(prefix) - 1U; for (int i = 0; i < statement_count; i++) { memcpy(source + offset, statement, sizeof(statement) - 1U); offset += sizeof(statement) - 1U; } memcpy(source + offset, suffix, sizeof(suffix)); offset += sizeof(suffix) - 1U; cbm_usage_field_lookup_test_reset(); CBMFileResult *result = cbm_extract_file(source, (int)offset, CBM_LANG_JAVASCRIPT, "proj", "wide-references.js", 0, NULL, NULL); free(source); if (!result) { return UINT64_MAX; } int usages = 0; for (int i = 0; i < result->usages.count; i++) { if (result->usages.items[i].ref_name && strcmp(result->usages.items[i].ref_name, "target") == 0) { usages++; } } uint64_t work = cbm_usage_field_lookup_test_work(); *out_slow_parent_fallbacks = cbm_usage_slow_parent_fallback_test_count(); cbm_free_result(result); *out_usages = usages; return work; } TEST(extract_wide_flat_reference_fields_are_linear) { enum { SMALL = 128, BIG = 1024, INPUT_GROWTH = 8, WORK_RATIO_MAX = 12 }; int small_usages = 0; int big_usages = 0; uint64_t small_slow_parent_fallbacks = 0; uint64_t big_slow_parent_fallbacks = 0; uint64_t small_work = extract_wide_reference_field_work(SMALL, &small_usages, &small_slow_parent_fallbacks); uint64_t big_work = extract_wide_reference_field_work(BIG, &big_usages, &big_slow_parent_fallbacks); ASSERT_TRUE(small_work != UINT64_MAX); ASSERT_TRUE(big_work != UINT64_MAX); ASSERT_EQ(small_usages, SMALL); ASSERT_EQ(big_usages, BIG); ASSERT_EQ(small_slow_parent_fallbacks, 0); ASSERT_EQ(big_slow_parent_fallbacks, 0); ASSERT_GTE(small_work, (uint64_t)SMALL); fprintf(stderr, " [wide-reference-fields] work(%d)=%llu work(%d)=%llu input_growth=%dx\n", SMALL, (unsigned long long)small_work, BIG, (unsigned long long)big_work, INPUT_GROWTH); uint64_t maximum = small_work * WORK_RATIO_MAX + 256U; if (big_work > maximum) { char message[192]; snprintf(message, sizeof(message), "wide-reference field lookup grew from %llu to %llu for %dx input " "(maximum %dx + 256) -- quadratic sibling scan", (unsigned long long)small_work, (unsigned long long)big_work, INPUT_GROWTH, WORK_RATIO_MAX); FAIL(message); } PASS(); } #endif /* =================================================================== * Group H3: ObjectScript return type extraction * =================================================================== */ TEST(objectscript_udl_method_return_type) { CBMFileResult *r = extract("Class MyApp.Factory Extends %RegisteredObject\n" "{\n" "Method GetAdapter() As EnsLib.SQL.OutboundAdapter\n" "{\n" " Quit ##class(EnsLib.SQL.OutboundAdapter).%New()\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Factory.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); bool found_rt = false; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "GetAdapter") == 0) { ASSERT_NOT_NULL(r->defs.items[i].return_type); ASSERT(strstr(r->defs.items[i].return_type, "EnsLib.SQL.OutboundAdapter") != NULL); found_rt = true; } } ASSERT(found_rt); cbm_free_result(r); PASS(); } TEST(objectscript_udl_scalar_return_type_not_resolved) { CBMFileResult *r = extract("Class MyApp.Counter Extends %RegisteredObject\n" "{\n" "Method GetName() As %String\n" "{\n" " Quit \"hello\"\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Counter.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "GetName") == 0) { ASSERT_NOT_NULL(r->defs.items[i].return_type); ASSERT(strstr(r->defs.items[i].return_type, "%String") != NULL); } } cbm_free_result(r); PASS(); } /* =================================================================== * Group H2: ObjectScript macro expansion * =================================================================== */ TEST(objectscript_udl_class) { CBMFileResult *r = extract("Class MyApp.Patient Extends %Persistent\n{\n}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Patient.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "MyApp.Patient")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_methods_after_goto_label) { CBMFileResult *r = extract("Class Graph.KG.Test Extends %RegisteredObject\n" "{\n" "ClassMethod First() As %String\n" "{\n" " If 1 { Goto Done }\n" "Done\n" " Quit \"x\"\n" "}\n" "ClassMethod Second() As %String\n" "{\n" " Quit \"y\"\n" "}\n" "ClassMethod Third() As %String\n" "{\n" " Quit \"z\"\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Test.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "Graph.KG.Test")); ASSERT(has_def(r, "Method", "First")); ASSERT(has_def(r, "Method", "Second")); ASSERT(has_def(r, "Method", "Third")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_methods) { CBMFileResult *r = extract("Class MyApp.Utils Extends %RegisteredObject\n" "{\n" "ClassMethod Format(pVal As %String) As %String\n" "{\n" " Quit pVal\n" "}\n" "Method Save() As %Status\n" "{\n" " Quit ..%Save()\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Utils.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "MyApp.Utils")); ASSERT(has_def(r, "Method", "Format")); ASSERT(has_def(r, "Method", "Save")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_base_classes) { CBMFileResult *r = extract("Class MyApp.Patient Extends %Persistent\n" "{\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Patient.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "MyApp.Patient")); int found = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "MyApp.Patient") == 0) { found = 1; ASSERT_NOT_NULL(r->defs.items[i].base_classes); ASSERT_NOT_NULL(r->defs.items[i].base_classes[0]); ASSERT_STR_EQ(r->defs.items[i].base_classes[0], "%Persistent"); } } ASSERT_TRUE(found); cbm_free_result(r); PASS(); } TEST(objectscript_udl_multiple_bases) { CBMFileResult *r = extract("Class MyApp.Dual Extends (MyApp.Base, %RegisteredObject)\n" "{\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Dual.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); int found = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "MyApp.Dual") == 0) { found = 1; ASSERT_NOT_NULL(r->defs.items[i].base_classes); ASSERT_NOT_NULL(r->defs.items[i].base_classes[0]); ASSERT_NOT_NULL(r->defs.items[i].base_classes[1]); } } ASSERT_TRUE(found); cbm_free_result(r); PASS(); } TEST(objectscript_udl_properties) { CBMFileResult *r = extract("Class MyApp.Patient Extends %Persistent\n" "{\n" "Property Name As %String;\n" "Property DOB As %Date;\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Patient.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "MyApp.Patient")); ASSERT(has_def(r, "Variable", "Name")); ASSERT(has_def(r, "Variable", "DOB")); cbm_free_result(r); PASS(); } TEST(objectscript_routine_tags) { CBMFileResult *r = extract("UTILS\n" " Quit\n" "\n" "Format(value,fmt)\n" " Set result = $ZDate(value, fmt)\n" " Quit result\n" "\n" "Log(msg)\n" " Write msg,!\n" " Quit\n", CBM_LANG_OBJECTSCRIPT_ROUTINE, "t", "Utils.mac"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Function", "Format")); ASSERT(has_def(r, "Function", "Log")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_query_member) { CBMFileResult *r = extract("Class MyApp.Repo Extends %Persistent\n" "{\n" "Query FindAll(name As %String) As %SQLQuery { SELECT * FROM MyApp_Repo }\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Repo.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Class", "MyApp.Repo")); ASSERT(has_def(r, "Method", "FindAll")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_index_member) { CBMFileResult *r = extract("Class MyApp.Repo Extends %Persistent\n" "{\n" "Property Name As %String;\n" "Index NameIdx On Name;\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Repo.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Index", "NameIdx")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_xdata_member) { CBMFileResult *r = extract("Class MyApp.Service Extends %CSP.REST\n" "{\n" "XData UrlMap { }\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Service.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "XData", "UrlMap")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_trigger_member) { CBMFileResult *r = extract("Class MyApp.Log Extends %Persistent\n" "{\n" "Trigger AfterInsert [ Event = INSERT ] { }\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Log.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Trigger", "AfterInsert")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_trigger_body_quit) { CBMFileResult *r = extract("Class MyApp.Patient Extends %Persistent\n" "{\n" "Trigger OnDeleteSQL [ Event = DELETE, Time = AFTER ] {\n" " Quit\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Patient.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Trigger", "OnDeleteSQL")); for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Trigger") == 0 && strcmp(r->defs.items[i].name, "OnDeleteSQL") == 0) { ASSERT_NOT_NULL(r->defs.items[i].docstring); ASSERT(strstr(r->defs.items[i].docstring, "trigger_body") != NULL); ASSERT(strstr(r->defs.items[i].docstring, "Quit") != NULL); break; } } cbm_free_result(r); PASS(); } TEST(objectscript_udl_trigger_body_tokens) { CBMFileResult *r = extract("Class MyApp.Order Extends %Persistent\n" "{\n" "Trigger AfterInsert [ Event = INSERT, Time = AFTER ] {\n" " Set id = ..%Id()\n" " Do ##class(MyApp.Audit).Log(id)\n" " Quit\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Order.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Trigger", "AfterInsert")); for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].label, "Trigger") == 0 && strcmp(r->defs.items[i].name, "AfterInsert") == 0) { ASSERT_NOT_NULL(r->defs.items[i].docstring); ASSERT(strstr(r->defs.items[i].docstring, "trigger_body") != NULL); ASSERT_NOT_NULL(r->defs.items[i].body_tokens); ASSERT(strstr(r->defs.items[i].body_tokens, "Log") != NULL || strstr(r->defs.items[i].body_tokens, "Audit") != NULL || strstr(r->defs.items[i].body_tokens, "id") != NULL); break; } } cbm_free_result(r); PASS(); } TEST(objectscript_udl_self_call_relative_dot_method) { CBMFileResult *r = extract("Class HS.Flash.UpdateManager Extends Ens.BusinessProcess\n" "{\n" "Method MakeMRNUpToDate(pRequest As HS.Message.FlashQueueUpdate) As %Status\n" "{\n" " Set tSC = ..processStreamlet(pSession, pTS, tMPIID, tSourceMRN, ii)\n" " Quit tSC\n" "}\n" "Method processStreamlet(pSession As %Integer) As %Status\n" "{\n" " Quit $$$OK\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "UpdateManager.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Method", "MakeMRNUpToDate")); ASSERT(has_call(r, "HS.Flash.UpdateManager.processStreamlet")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_calls_typed_new) { CBMFileResult *r = extract("Class MyApp.Caller Extends %RegisteredObject\n" "{\n" "Method Run() As %Status\n" "{\n" " Set adapter = ##class(EnsLib.SQL.OutboundAdapter).%New()\n" " Do adapter.ExecuteQuery(\"SELECT 1\")\n" " Quit $$$OK\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Caller.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "EnsLib.SQL.OutboundAdapter.ExecuteQuery")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_ensemble_production_def_parses_items) { CBMFileResult *r = extract("Class Sample.Production Extends Ens.Production\n" "{\n" "XData ProductionDefinition\n" "{\n" "\n" " \n" " \n" " \n" " \n" "\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Production.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "XData", "ProductionDefinition")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_ensemble_production_def_hs_settings) { CBMFileResult *r = extract( "Class HS.Flash.Production Extends Ens.Production\n" "{\n" "XData ProductionDefinition\n" "{\n" "\n" " \n" " FHIROps\n" " PatientOps\n" " ConformOps\n" " \n" "\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "HSProduction.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "XData", "ProductionDefinition")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_ensemble_production_def_absent_no_error) { CBMFileResult *r = extract("Class Sample.NonProduction Extends %Persistent\n" "{\n" "Method DoSomething() As %Status\n" "{\n" " Quit $$$OK\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "NonProduction.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(!has_def(r, "XData", "ProductionDefinition")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_calls_typed_param) { CBMFileResult *r = extract("Class MyApp.Handler Extends %RegisteredObject\n" "{\n" "Method Process(req As Ens.Request) As %Status\n" "{\n" " Do req.Send()\n" " Quit $$$OK\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Handler.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "Ens.Request.Send")); cbm_free_result(r); PASS(); } TEST(objectscript_udl_calls_typed_property) { CBMFileResult *r = extract("Class MyApp.Service Extends Ens.BusinessService\n" "{\n" "Property Adapter As EnsLib.SQL.InboundAdapter;\n" "Method OnProcessInput() As %Status\n" "{\n" " Do ..Adapter.ExecuteQuery(\"SELECT 1\")\n" " Quit $$$OK\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Service.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "EnsLib.SQL.InboundAdapter.ExecuteQuery")); cbm_free_result(r); PASS(); } /* =================================================================== * Group H2: ObjectScript macro expansion * =================================================================== */ TEST(objectscript_macro_expand_system) { CBMMacroTable mt; cbm_macro_table_init_system(&mt); CBMFileResult *r = extract_with_macros("Class MyApp.Caller Extends %RegisteredObject\n" "{\n" "Method Run(sc As %Status) As %Status\n" "{\n" " If $$$ISERR(sc) { Quit sc }\n" " Quit $$$OK\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Caller.cls", &mt); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "%SYSTEM.Status.IsError")); cbm_free_result(r); PASS(); } /* ═══════════════════════════════════════════════════════════════════ * Group H3: ObjectScript DATA_FLOWS argument extraction * ═══════════════════════════════════════════════════════════════════ */ static int find_call_args(const CBMFileResult *r, const char *callee, const char **out_arg0, const char **out_arg1) { if (out_arg0) *out_arg0 = NULL; if (out_arg1) *out_arg1 = NULL; for (int i = 0; i < r->calls.count; i++) { if (strstr(r->calls.items[i].callee_name, callee)) { if (out_arg0 && r->calls.items[i].arg_count > 0) *out_arg0 = r->calls.items[i].args[0].expr; if (out_arg1 && r->calls.items[i].arg_count > 1) *out_arg1 = r->calls.items[i].args[1].expr; return r->calls.items[i].arg_count; } } return -1; } TEST(objectscript_data_flows_class_method_args) { CBMFileResult *r = extract("Class MyApp.Caller Extends %RegisteredObject\n" "{\n" "Method Run() As %Status\n" "{\n" " Set sql = \"SELECT 1\"\n" " Do ##class(MyApp.Utils).Transform(sql, \"JSON\")\n" " Quit $$$OK\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Caller.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "MyApp.Utils.Transform")); const char *arg0 = NULL; const char *arg1 = NULL; int argc = find_call_args(r, "MyApp.Utils.Transform", &arg0, &arg1); ASSERT(argc == 2); ASSERT_NOT_NULL(arg0); ASSERT(strstr(arg0, "sql") != NULL); cbm_free_result(r); PASS(); } TEST(objectscript_macro_expand_local) { CBMMacroTable mt; cbm_macro_table_init_system(&mt); CBMArena arena; cbm_arena_init(&arena); const char *inc_content = "ROUTINE MyApp.Include [Type=INC]\n" "#define MyCheck(%sc) ##class(MyApp.Utils).Validate(%sc)\n"; cbm_parse_inc_file(&mt, &arena, inc_content); CBMFileResult *r = extract_with_macros("Class MyApp.Caller Extends %RegisteredObject\n" "{\n" "Method Run(sc As %Status) As %Status\n" "{\n" " If $$$MyCheck(sc) { Quit $$$OK }\n" " Quit $$$OK\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Caller.cls", &mt); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "MyApp.Utils.Validate")); cbm_free_result(r); cbm_arena_destroy(&arena); PASS(); } TEST(objectscript_macro_constant_no_extra_call) { CBMMacroTable mt; cbm_macro_table_init_system(&mt); CBMArena arena; cbm_arena_init(&arena); const char *inc_content = "ROUTINE MyApp.Include [Type=INC]\n" "#define MyConst 42\n"; cbm_parse_inc_file(&mt, &arena, inc_content); CBMFileResult *r = extract_with_macros("Class MyApp.Caller Extends %RegisteredObject\n" "{\n" "Method Run() As %Integer\n" "{\n" " Set x = $$$MyConst\n" " Quit x\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Caller.cls", &mt); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(!has_call(r, "$$$MyConst")); cbm_free_result(r); cbm_arena_destroy(&arena); PASS(); } TEST(objectscript_data_flows_instance_method_args) { CBMFileResult *r = extract("Class MyApp.Service Extends %RegisteredObject\n" "{\n" "Method Run() As %Status\n" "{\n" " Set adapter = ##class(EnsLib.SQL.OutboundAdapter).%New()\n" " Do adapter.ExecuteQuery(\"SELECT 1\")\n" " Quit $$$OK\n" "}\n" "}\n", CBM_LANG_OBJECTSCRIPT_UDL, "t", "Service.cls"); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "EnsLib.SQL.OutboundAdapter.ExecuteQuery")); const char *arg0 = NULL; int argc = find_call_args(r, "EnsLib.SQL.OutboundAdapter.ExecuteQuery", &arg0, NULL); ASSERT(argc == 1); ASSERT_NOT_NULL(arg0); cbm_free_result(r); PASS(); } /* =================================================================== * Group H4: IRIS Export XML → UDL transcoder * =================================================================== */ #define SIMPLE_EXPORT \ "\n" \ "\n" \ "\n" \ "%RegisteredObject\n" \ "\n" \ "%String\n" \ "\n" \ "\n" \ "\n" \ "\n" TEST(iris_export_xml_simple_class) { CBMArena arena; cbm_arena_init(&arena); int count = 0; char **udl = cbm_iris_export_to_udl(&arena, SIMPLE_EXPORT, (int)strlen(SIMPLE_EXPORT), &count); ASSERT_NOT_NULL(udl); ASSERT(count == 1); ASSERT_NOT_NULL(udl[0]); ASSERT(strstr(udl[0], "Test.Simple") != NULL); ASSERT(strstr(udl[0], "%RegisteredObject") != NULL); ASSERT(strstr(udl[0], "Hello") != NULL); ASSERT(strstr(udl[0], "Quit \"hello\"") != NULL); cbm_arena_destroy(&arena); PASS(); } #define CLASSMETHOD_EXPORT \ "\n" \ "\n" \ "\n" \ "\n" \ "1\n" \ "pArg:%String,pFlag:%Boolean=0\n" \ "%Status\n" \ "\n" \ "\n" \ "\n" \ "\n" TEST(iris_export_xml_classmethod) { CBMArena arena; cbm_arena_init(&arena); int count = 0; char **udl = cbm_iris_export_to_udl(&arena, CLASSMETHOD_EXPORT, (int)strlen(CLASSMETHOD_EXPORT), &count); ASSERT_NOT_NULL(udl); ASSERT(count == 1); ASSERT(strstr(udl[0], "ClassMethod") != NULL); ASSERT(strstr(udl[0], "pArg") != NULL); ASSERT(strstr(udl[0], "pFlag") != NULL); ASSERT(strstr(udl[0], "%Status") != NULL); cbm_arena_destroy(&arena); PASS(); } #define MEMBER_EXPORT \ "\n" \ "\n" \ "\n" \ "\n" \ "%String\n" \ "\n" \ "\n" \ "\n" \ "1\n" \ "\n" \ "\n" \ "Name\n" \ "1\n" \ "\n" \ "\n" \ "\n" TEST(iris_export_xml_property_parameter_index) { CBMArena arena; cbm_arena_init(&arena); int count = 0; char **udl = cbm_iris_export_to_udl(&arena, MEMBER_EXPORT, (int)strlen(MEMBER_EXPORT), &count); ASSERT_NOT_NULL(udl); ASSERT(count == 1); ASSERT(strstr(udl[0], "Property Name") != NULL); ASSERT(strstr(udl[0], "%String") != NULL); ASSERT(strstr(udl[0], "Parameter VERSION") != NULL); ASSERT(strstr(udl[0], "Index NameIdx") != NULL); cbm_arena_destroy(&arena); PASS(); } #define CALLS_EXPORT \ "\n" \ "\n" \ "\n" \ "%RegisteredObject\n" \ "\n" \ "1\n" \ "%Status\n" \ "\n" \ "\n" \ "\n" \ "\n" TEST(iris_export_xml_calls_extracted) { CBMArena arena; cbm_arena_init(&arena); int count = 0; char **udl = cbm_iris_export_to_udl(&arena, CALLS_EXPORT, (int)strlen(CALLS_EXPORT), &count); ASSERT_NOT_NULL(udl); ASSERT(count == 1); CBMFileResult *r = cbm_extract_file(udl[0], (int)strlen(udl[0]), CBM_LANG_OBJECTSCRIPT_UDL, "t", "Caller.cls", 0, NULL, NULL); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT(has_call(r, "Target.Worker.Execute")); cbm_free_result(r); cbm_arena_destroy(&arena); PASS(); } #define MULTI_EXPORT \ "\n" \ "\n" \ "\n" \ "\n" \ "\n" \ "\n" \ "\n" \ "\n" \ "\n" TEST(iris_export_xml_multi_class) { CBMArena arena; cbm_arena_init(&arena); int count = 0; char **udl = cbm_iris_export_to_udl(&arena, MULTI_EXPORT, (int)strlen(MULTI_EXPORT), &count); ASSERT_NOT_NULL(udl); ASSERT(count == 2); ASSERT(strstr(udl[0], "Test.First") != NULL || strstr(udl[1], "Test.First") != NULL); ASSERT(strstr(udl[0], "Test.Second") != NULL || strstr(udl[1], "Test.Second") != NULL); cbm_arena_destroy(&arena); PASS(); } SUITE(extraction) { /* Initialize extraction library */ cbm_init(); /* Wide-flat-file linearity (ms-typescript hang) */ RUN_TEST(extract_wide_flat_file_is_linear); #if defined(CBM_CALL_REFERENCE_LOOKUP_TEST_API) && CBM_CALL_REFERENCE_LOOKUP_TEST_API RUN_TEST(extract_wide_flat_reference_fields_are_linear); #endif /* Perl call-graph noise (#459 follow-up) */ RUN_TEST(extract_perl_config_string_not_a_callee); RUN_TEST(extract_perl_builtin_call_is_function_not_method); RUN_TEST(extract_perl_method_call_flags_is_method); RUN_TEST(extract_flag_exempt_method_call_not_flagged_is_method); RUN_TEST(extract_ts_member_call_flags_is_method); RUN_TEST(extract_ts_this_super_receiver_not_flagged); RUN_TEST(extract_js_member_call_flags_is_method); /* InterSystems ObjectScript (UDL / routine / Export XML). */ RUN_TEST(objectscript_udl_class); RUN_TEST(objectscript_udl_methods_after_goto_label); RUN_TEST(objectscript_udl_methods); RUN_TEST(objectscript_udl_base_classes); RUN_TEST(objectscript_udl_multiple_bases); RUN_TEST(objectscript_udl_properties); RUN_TEST(objectscript_routine_tags); RUN_TEST(objectscript_udl_query_member); RUN_TEST(objectscript_udl_index_member); RUN_TEST(objectscript_udl_xdata_member); RUN_TEST(objectscript_udl_trigger_member); RUN_TEST(objectscript_udl_trigger_body_quit); RUN_TEST(objectscript_udl_trigger_body_tokens); RUN_TEST(objectscript_udl_ensemble_production_def_parses_items); RUN_TEST(objectscript_udl_ensemble_production_def_hs_settings); RUN_TEST(objectscript_udl_ensemble_production_def_absent_no_error); RUN_TEST(objectscript_udl_self_call_relative_dot_method); RUN_TEST(objectscript_udl_calls_typed_new); RUN_TEST(objectscript_udl_calls_typed_param); RUN_TEST(objectscript_udl_calls_typed_property); RUN_TEST(objectscript_macro_expand_system); RUN_TEST(objectscript_macro_expand_local); RUN_TEST(objectscript_macro_constant_no_extra_call); RUN_TEST(objectscript_udl_method_return_type); RUN_TEST(objectscript_udl_scalar_return_type_not_resolved); RUN_TEST(objectscript_data_flows_class_method_args); RUN_TEST(objectscript_data_flows_instance_method_args); RUN_TEST(iris_export_xml_simple_class); RUN_TEST(iris_export_xml_classmethod); RUN_TEST(iris_export_xml_property_parameter_index); RUN_TEST(iris_export_xml_calls_extracted); RUN_TEST(iris_export_xml_multi_class); /* R box-module imports + member calls */ RUN_TEST(extract_r_box_use_imports_issue218); RUN_TEST(extract_r_dollar_call_issue219); RUN_TEST(extract_ts_factory_object_methods_issue341); RUN_TEST(extract_c_macros_issue375); RUN_TEST(extract_cpp_macros_issue375); RUN_TEST(extract_cpp_functionlike_macro_type_arg_no_false_parse_partial_issue1071); RUN_TEST(extract_cpp_real_in_body_error_still_flagged_issue1071); RUN_TEST(extract_gdscript_issue186); RUN_TEST(extract_powershell_issue35); RUN_TEST(extract_luau_issue39); RUN_TEST(extract_qml_issue42); RUN_TEST(extract_cfscript_issue38); RUN_TEST(extract_cfml_tag_issue38); RUN_TEST(extract_helm_templates_issue338); RUN_TEST(extract_helm_values_toplevel_issue338); /* OOP */ RUN_TEST(java_class); RUN_TEST(java_method); RUN_TEST(java_interface); RUN_TEST(java_interface_no_duplicate_function_issue1234); RUN_TEST(java_enum_dedup_preserves_calls_issue1234); RUN_TEST(java_class_extends_and_implements); RUN_TEST(python_class_base_extracted_bare); RUN_TEST(php_class); RUN_TEST(php_function); RUN_TEST(ruby_class); RUN_TEST(ruby_module); RUN_TEST(csharp_class); RUN_TEST(csharp_interface); RUN_TEST(swift_class); RUN_TEST(swift_protocol); RUN_TEST(kotlin_function); RUN_TEST(kotlin_class); RUN_TEST(scala_function); RUN_TEST(scala_class); RUN_TEST(dart_class); RUN_TEST(groovy_class); /* Systems */ RUN_TEST(rust_function); RUN_TEST(rust_struct); RUN_TEST(go_function); RUN_TEST(go_struct); RUN_TEST(go_interface); RUN_TEST(zig_function); RUN_TEST(c_function); RUN_TEST(c_struct); RUN_TEST(cpp_class); /* Scripting */ RUN_TEST(python_function); RUN_TEST(python_class); RUN_TEST(js_function); RUN_TEST(js_class); RUN_TEST(ts_function); RUN_TEST(ts_class); RUN_TEST(body_tokens_type_identifier); RUN_TEST(lua_function); RUN_TEST(bash_function); RUN_TEST(perl_function); RUN_TEST(r_function); /* Functional */ RUN_TEST(elixir_function); RUN_TEST(haskell_function); RUN_TEST(ocaml_function); RUN_TEST(erlang_function); /* Markup/Config */ RUN_TEST(yaml_variables); RUN_TEST(hcl_blocks); RUN_TEST(sql_create_table); RUN_TEST(dockerfile_stages); /* Scientific */ RUN_TEST(matlab_function); RUN_TEST(lean_function); RUN_TEST(form_procedure); RUN_TEST(wolfram_function); RUN_TEST(magma_function); /* v0.5 expansion */ RUN_TEST(fsharp_function); RUN_TEST(julia_function); RUN_TEST(elm_function); RUN_TEST(nix_function); RUN_TEST(nix_defs_in_let_rooted_file); RUN_TEST(nix_defs_in_attrset_rooted_file); RUN_TEST(nix_defs_in_nested_let); RUN_TEST(nix_defs_survive_function_header_let); RUN_TEST(nix_defs_survive_function_header_attrset); RUN_TEST(nix_defs_survive_curried_header); RUN_TEST(nix_attrset_scope_disambiguates_leaf_names); RUN_TEST(nix_dotted_attrpath_qualifies_like_nested); RUN_TEST(nix_quoted_attr_name_strips_quotes); RUN_TEST(nix_interpolated_attr_mints_no_def); RUN_TEST(nix_module_level_bindings_mint_variables); RUN_TEST(nix_nested_bindings_are_not_module_level); RUN_TEST(nix_lambda_binding_is_function_not_variable); RUN_TEST(nix_curried_lambda_mints_one_def); RUN_TEST(fortran_function); /* OOP/Systems variants */ RUN_TEST(swift_struct); RUN_TEST(swift_simple_call); RUN_TEST(swift_method_call); RUN_TEST(swift_constructor_call); RUN_TEST(swift_chained_call); RUN_TEST(objc_interface); RUN_TEST(objc_implementation); RUN_TEST(dart_top_level_function); RUN_TEST(rust_enum); RUN_TEST(zig_struct); RUN_TEST(cpp_function); RUN_TEST(cpp_gtest_same_name_collision_issue1266); RUN_TEST(cpp_gtest_f_unique_name_issue1266); RUN_TEST(cpp_out_of_line_method_issue428); RUN_TEST(cobol_paragraph); RUN_TEST(verilog_module); RUN_TEST(cuda_kernel); RUN_TEST(python_decorator); RUN_TEST(ts_interface); RUN_TEST(tsx_component); RUN_TEST(lua_table_method); RUN_TEST(emacs_lisp_defun); RUN_TEST(emacs_lisp_defvar); RUN_TEST(haskell_data_type); RUN_TEST(clojure_function); /* Config/Markup */ RUN_TEST(html_elements); RUN_TEST(sql_function); RUN_TEST(sql_ddl_node_labels); RUN_TEST(sql_view_lineage_usages); RUN_TEST(sql_schema_qualified_name); RUN_TEST(dbt_model_and_ref_lineage); RUN_TEST(dbt_source_and_two_arg_ref); RUN_TEST(dbt_ignores_non_dbt_jinja); RUN_TEST(dbt_plain_sql_untouched); RUN_TEST(meson_project); RUN_TEST(css_rules); RUN_TEST(scss_rules); RUN_TEST(toml_basic); RUN_TEST(cmake_function); RUN_TEST(json_object); RUN_TEST(protobuf_message); RUN_TEST(graphql_type); RUN_TEST(svelte_component); RUN_TEST(vue_component); RUN_TEST(glsl_shader); RUN_TEST(vimscript_function); /* Scientific extended */ RUN_TEST(matlab_parse); RUN_TEST(matlab_call); RUN_TEST(lean_parse); RUN_TEST(lean_call); RUN_TEST(lean_type_annotation_not_call); RUN_TEST(form_parse); RUN_TEST(form_call); RUN_TEST(magma_procedure); RUN_TEST(magma_parse); RUN_TEST(magma_import); RUN_TEST(magma_call); RUN_TEST(magma_disambiguation); RUN_TEST(wolfram_function_extended); RUN_TEST(wolfram_call); RUN_TEST(wolfram_caller_attribution); RUN_TEST(c_caller_attribution); RUN_TEST(cpp_out_of_line_method_caller_attribution); RUN_TEST(cpp_out_of_line_ctor_dtor_caller_attribution); RUN_TEST(wolfram_parse); RUN_TEST(wolfram_import); RUN_TEST(wolfram_nested_def); /* cbm_test.go ports */ RUN_TEST(python_docstring); RUN_TEST(go_function_extraction); RUN_TEST(js_arrow_function); /* language_failures_test.go ports */ RUN_TEST(commonlisp_defun); RUN_TEST(commonlisp_multiple_functions); RUN_TEST(commonlisp_defmacro); RUN_TEST(makefile_rule_as_function); RUN_TEST(makefile_multiple_targets); RUN_TEST(makefile_variable_extraction); RUN_TEST(vimscript_function_extraction); RUN_TEST(vimscript_function_without_bang); RUN_TEST(julia_function_extraction); RUN_TEST(julia_function_with_args); /* Cross-cutting */ RUN_TEST(python_calls); RUN_TEST(python_iris_classMethodValue); RUN_TEST(go_calls); RUN_TEST(python_imports); RUN_TEST(js_imports); RUN_TEST(go_imports); RUN_TEST(java_imports); RUN_TEST(rust_imports); RUN_TEST(c_imports); RUN_TEST(ruby_imports); RUN_TEST(lua_imports); RUN_TEST(import_stress_go); RUN_TEST(svelte_imports_basic); RUN_TEST(svelte_imports_no_script); RUN_TEST(vue_imports_basic); RUN_TEST(html_imports_basic); /* config_extraction_test.go ports */ RUN_TEST(toml_basic_table_and_pair); RUN_TEST(toml_nested_table); RUN_TEST(toml_table_array_element); RUN_TEST(toml_dotted_key); RUN_TEST(toml_quoted_key); RUN_TEST(toml_empty_table); RUN_TEST(toml_comments_only); RUN_TEST(toml_boolean_and_integer_values); RUN_TEST(ini_basic_section_and_setting); RUN_TEST(ini_multiple_sections); RUN_TEST(ini_global_keys); RUN_TEST(ini_comments); RUN_TEST(json_basic_pair); RUN_TEST(json_nested_object); RUN_TEST(json_empty_object); RUN_TEST(json_boolean_null_values); RUN_TEST(json_package_json_deps); RUN_TEST(xml_basic_element); RUN_TEST(xml_self_closing_tag); RUN_TEST(xml_empty_document); RUN_TEST(xml_multiple_children); RUN_TEST(markdown_atx_headings); RUN_TEST(markdown_setext_headings); RUN_TEST(markdown_heading_content); RUN_TEST(markdown_no_headings); /* __init__.py / index.ts Module QN collision regression */ RUN_TEST(python_init_module_qn_not_collide_with_folder); RUN_TEST(python_init_nested_module_qn); RUN_TEST(js_index_module_qn_not_collide_with_folder); RUN_TEST(python_regular_module_qn_unchanged); RUN_TEST(extract_java_method_annotations_issue382); RUN_TEST(extract_java_jaxrs_path_composition_issue1005); RUN_TEST(extract_ts_template_string_url_issue1006); RUN_TEST(extract_go_binary_concat_url_issue1249); RUN_TEST(extract_go_binary_concat_url_no_literal_suffix_issue1249); RUN_TEST(extract_java_no_double_class_qn); RUN_TEST(extract_go_no_filename_in_module_qn); RUN_TEST(extract_large_ts_has_functions_issue213); /* Per-function complexity metrics (Tier A) */ RUN_TEST(complexity_nested_loops_depth); RUN_TEST(complexity_loop_with_branch); RUN_TEST(complexity_flat_no_loops); RUN_TEST(complexity_linear_scan_in_loop); RUN_TEST(complexity_recursion_in_loop_unguarded); RUN_TEST(complexity_guarded_recursion); RUN_TEST(complexity_super_only_not_recursive); RUN_TEST(complexity_same_name_other_receiver_not_recursive); RUN_TEST(complexity_self_receiver_still_recursive); RUN_TEST(complexity_chained_receiver_not_self); RUN_TEST(complexity_go_method_receiver_self_recursion); RUN_TEST(complexity_delegation_receivers_not_recursive_issue876); RUN_TEST(complexity_access_depth_and_params); RUN_TEST(extract_c_ifdef_split_brace_fn_recovered_issue961); RUN_TEST(extract_cpp_preproc_signature_gap_issue946); RUN_TEST(extract_cpp_preproc_macro_generated_callable_skipped_issue949); RUN_TEST(extract_c_ifdef_split_brace_after_include_remapped_issue949); RUN_TEST(extract_c_clean_file_no_recovery_duplicates_issue961); RUN_TEST(walk_defs_no_truncation_over_4096_issue668); RUN_TEST(extract_rust_test_attr_marks_is_test_issue855); RUN_TEST(extract_c_test_dir_marks_is_test_issue1294); RUN_TEST(extract_python_method_test_dir_marks_is_test_issue1294); RUN_TEST(docstring_utf8_truncation_boundary_issue1017); RUN_TEST(extract_ts_decorators_survive_interleaved_comment); cbm_shutdown(); }