Skip to content

Commit c66d887

Browse files
DeusDatajay-sync
andcommitted
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>
1 parent f6b9b39 commit c66d887

4 files changed

Lines changed: 86 additions & 6 deletions

File tree

internal/cbm/extract_calls.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,23 @@ static char *extract_callee_name(CBMArena *a, TSNode node, const char *source, C
215215
return NULL;
216216
}
217217

218+
// Swift: call_expression has no "function" field — callee is first named child.
219+
// Also handle constructor_expression for MyClass() syntax.
220+
if (lang == CBM_LANG_SWIFT) {
221+
const char *nk = ts_node_type(node);
222+
if (strcmp(nk, "call_expression") == 0 || strcmp(nk, "constructor_expression") == 0) {
223+
if (ts_node_named_child_count(node) > 0) {
224+
TSNode callee = ts_node_named_child(node, 0);
225+
const char *ck = ts_node_type(callee);
226+
if (strcmp(ck, "simple_identifier") == 0 ||
227+
strcmp(ck, "navigation_expression") == 0) {
228+
return cbm_node_text(a, callee, source);
229+
}
230+
}
231+
}
232+
return NULL;
233+
}
234+
218235
// Generic fallback: first child
219236
if (ts_node_child_count(node) > 0) {
220237
TSNode first = ts_node_child(node, 0);

internal/cbm/lang_specs.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ static const char *swift_class_types[] = {"class_declaration", "protocol_declara
377377
"struct_declaration", "enum_declaration", NULL};
378378
static const char *swift_field_types[] = {"property_declaration", NULL};
379379
static const char *swift_module_types[] = {"source_file", NULL};
380-
static const char *swift_call_types[] = {"call_expression", NULL};
380+
static const char *swift_call_types[] = {"call_expression", "constructor_expression", NULL};
381381
static const char *swift_import_types[] = {"import_declaration", NULL};
382382
static const char *swift_branch_types[] = {"if_statement", "guard_statement", "for_statement",
383383
"while_statement", "switch_statement", NULL};

src/cli/cli.c

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,22 +1290,40 @@ int cbm_remove_antigravity_mcp(const char *config_path) {
12901290

12911291
/* ── Claude Code pre-tool hooks ───────────────────────────────── */
12921292

1293-
#define CMM_HOOK_MATCHER "Grep|Glob|Read"
1293+
#define CMM_HOOK_MATCHER "Grep|Glob|Read|Search"
12941294
#define CMM_HOOK_COMMAND \
12951295
"echo 'Reminder: prefer codebase-memory-mcp search_graph/trace_call_path/" \
1296-
"get_code_snippet over Grep/Glob/Read for code discovery. " \
1296+
"get_code_snippet over Grep/Glob/Read/Search for code discovery. " \
12971297
"Use get_code_snippet(qualified_name) instead of Read to view a function. " \
12981298
"Fall back only if MCP returns insufficient results.' >&2"
12991299

1300-
/* Check if a PreToolUse array entry matches our hook (by matcher string) */
1300+
/* Old matcher values from previous versions — recognized during upgrade so
1301+
* upsert_hooks_json can remove them before inserting the current matcher. */
1302+
static const char *cmm_old_matchers[] = {
1303+
"Grep|Glob|Read",
1304+
NULL,
1305+
};
1306+
1307+
/* Check if a PreToolUse array entry matches our hook (current or old matcher). */
13011308
static bool is_cmm_hook_entry(yyjson_mut_val *entry, const char *matcher_str) {
13021309
yyjson_mut_val *matcher = yyjson_mut_obj_get(entry, "matcher");
13031310
if (!matcher || !yyjson_mut_is_str(matcher)) {
13041311
return false;
13051312
}
13061313
const char *val = yyjson_mut_get_str(matcher);
1307-
// NOLINTNEXTLINE(readability-implicit-bool-conversion)
1308-
return val != NULL && strcmp(val, matcher_str) == 0;
1314+
if (!val) {
1315+
return false;
1316+
}
1317+
if (strcmp(val, matcher_str) == 0) {
1318+
return true;
1319+
}
1320+
/* Also match old versions for backwards-compatible upgrade */
1321+
for (int i = 0; cmm_old_matchers[i]; i++) {
1322+
if (strcmp(val, cmm_old_matchers[i]) == 0) {
1323+
return true;
1324+
}
1325+
}
1326+
return false;
13091327
}
13101328

13111329
/* Generic hook upsert for both Claude Code and Gemini CLI */

tests/test_extraction.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,47 @@ TEST(swift_struct) {
742742
PASS();
743743
}
744744

745+
/* --- Swift calls (port of PR #47 Go tests) --- */
746+
TEST(swift_simple_call) {
747+
CBMFileResult *r = extract("func main() { greet() }\nfunc greet() { print(\"hello\") }\n",
748+
CBM_LANG_SWIFT, "t", "main.swift");
749+
ASSERT_NOT_NULL(r);
750+
ASSERT_FALSE(r->has_error);
751+
ASSERT(has_call(r, "greet"));
752+
cbm_free_result(r);
753+
PASS();
754+
}
755+
756+
TEST(swift_method_call) {
757+
CBMFileResult *r = extract("class Foo {\n func bar() { baz.run() }\n}\n", CBM_LANG_SWIFT,
758+
"t", "Foo.swift");
759+
ASSERT_NOT_NULL(r);
760+
ASSERT_FALSE(r->has_error);
761+
ASSERT(has_call(r, "baz.run"));
762+
cbm_free_result(r);
763+
PASS();
764+
}
765+
766+
TEST(swift_constructor_call) {
767+
CBMFileResult *r =
768+
extract("func create() { let x = MyClass() }\n", CBM_LANG_SWIFT, "t", "create.swift");
769+
ASSERT_NOT_NULL(r);
770+
ASSERT_FALSE(r->has_error);
771+
ASSERT(has_call(r, "MyClass"));
772+
cbm_free_result(r);
773+
PASS();
774+
}
775+
776+
TEST(swift_chained_call) {
777+
CBMFileResult *r = extract("func setup() { AlarmScheduler.shared.startKeepAlive() }\n",
778+
CBM_LANG_SWIFT, "t", "setup.swift");
779+
ASSERT_NOT_NULL(r);
780+
ASSERT_FALSE(r->has_error);
781+
ASSERT(r->calls.count > 0);
782+
cbm_free_result(r);
783+
PASS();
784+
}
785+
745786
/* --- Objective-C --- */
746787
TEST(objc_interface) {
747788
CBMFileResult *r =
@@ -2071,6 +2112,10 @@ SUITE(extraction) {
20712112

20722113
/* OOP/Systems variants */
20732114
RUN_TEST(swift_struct);
2115+
RUN_TEST(swift_simple_call);
2116+
RUN_TEST(swift_method_call);
2117+
RUN_TEST(swift_constructor_call);
2118+
RUN_TEST(swift_chained_call);
20742119
RUN_TEST(objc_interface);
20752120
RUN_TEST(objc_implementation);
20762121
RUN_TEST(dart_top_level_function);

0 commit comments

Comments
 (0)