Fix Swift call extraction: 0 CALLS edges (#43)

Swift tree-sitter grammar uses positional children (not named fields) for
call_expression, so extract_callee_name() returned NULL for every call.

- extract_calls.c: Swift-specific callee extraction via first named child
  for call_expression and constructor_expression nodes
- lang_specs.c: add constructor_expression to swift_call_types
- 4 new C tests: simple calls, method calls, constructors, chained calls

Co-Authored-By: Jay Sync <63868998+jay-sync@users.noreply.github.com>
This commit is contained in:
Martin VogelandJay Sync committed 2026-03-20 14:46:46 +01:00
1 parent f6b9b39c60
commit c66d88745d
4 files changed
+86 -6

No files matched your search

+17
View File
@@ -215,6 +215,23 @@ static char *extract_callee_name(CBMArena *a, TSNode node, const char *source, C
return NULL;
}
// Swift: call_expression has no "function" field — callee is first named child.
// Also handle constructor_expression for MyClass() syntax.
if (lang == CBM_LANG_SWIFT) {
const char *nk = ts_node_type(node);
if (strcmp(nk, "call_expression") == 0 || strcmp(nk, "constructor_expression") == 0) {
if (ts_node_named_child_count(node) > 0) {
TSNode callee = ts_node_named_child(node, 0);
const char *ck = ts_node_type(callee);
if (strcmp(ck, "simple_identifier") == 0 ||
strcmp(ck, "navigation_expression") == 0) {
return cbm_node_text(a, callee, source);
}
}
}
return NULL;
}
// Generic fallback: first child
if (ts_node_child_count(node) > 0) {
TSNode first = ts_node_child(node, 0);
+1 -1
View File
@@ -377,7 +377,7 @@ static const char *swift_class_types[] = {"class_declaration", "protocol_declara
"struct_declaration", "enum_declaration", NULL};
static const char *swift_field_types[] = {"property_declaration", NULL};
static const char *swift_module_types[] = {"source_file", NULL};
static const char *swift_call_types[] = {"call_expression", NULL};
static const char *swift_call_types[] = {"call_expression", "constructor_expression", NULL};
static const char *swift_import_types[] = {"import_declaration", NULL};
static const char *swift_branch_types[] = {"if_statement", "guard_statement", "for_statement",
"while_statement", "switch_statement", NULL};
+23 -5
View File
@@ -1290,22 +1290,40 @@ int cbm_remove_antigravity_mcp(const char *config_path) {
/* ── Claude Code pre-tool hooks ───────────────────────────────── */
#define CMM_HOOK_MATCHER "Grep|Glob|Read"
#define CMM_HOOK_MATCHER "Grep|Glob|Read|Search"
#define CMM_HOOK_COMMAND \
"echo 'Reminder: prefer codebase-memory-mcp search_graph/trace_call_path/" \
"get_code_snippet over Grep/Glob/Read for code discovery. " \
"get_code_snippet over Grep/Glob/Read/Search for code discovery. " \
"Use get_code_snippet(qualified_name) instead of Read to view a function. " \
"Fall back only if MCP returns insufficient results.' >&2"
/* Check if a PreToolUse array entry matches our hook (by matcher string) */
/* Old matcher values from previous versions — recognized during upgrade so
* upsert_hooks_json can remove them before inserting the current matcher. */
static const char *cmm_old_matchers[] = {
"Grep|Glob|Read",
NULL,
};
/* Check if a PreToolUse array entry matches our hook (current or old matcher). */
static bool is_cmm_hook_entry(yyjson_mut_val *entry, const char *matcher_str) {
yyjson_mut_val *matcher = yyjson_mut_obj_get(entry, "matcher");
if (!matcher || !yyjson_mut_is_str(matcher)) {
return false;
}
const char *val = yyjson_mut_get_str(matcher);
// NOLINTNEXTLINE(readability-implicit-bool-conversion)
return val != NULL && strcmp(val, matcher_str) == 0;
if (!val) {
return false;
}
if (strcmp(val, matcher_str) == 0) {
return true;
}
/* Also match old versions for backwards-compatible upgrade */
for (int i = 0; cmm_old_matchers[i]; i++) {
if (strcmp(val, cmm_old_matchers[i]) == 0) {
return true;
}
}
return false;
}
/* Generic hook upsert for both Claude Code and Gemini CLI */
+45
View File
@@ -742,6 +742,47 @@ TEST(swift_struct) {
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 =
@@ -2071,6 +2112,10 @@ SUITE(extraction) {
/* 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);