From d5d5d3b7f44ab2d747efb35ad03ee54b2a4eb3ac Mon Sep 17 00:00:00 2001 From: Andrew Hundt Date: Wed, 1 Jul 2026 09:30:10 -0400 Subject: [PATCH] fix(extract): restore meson command calls Use the vendored Meson grammar's normal_command node and command field to extract Meson command callees such as project and executable. Add exact call-edge canaries for Meson and Just. The rebuilt failed-first extraction run showed Meson missing project/executable calls while the Just canary already passed through the existing generic function_call path. Validation: git diff --check; bash scripts/check-source-safety.sh; make -j8 -f Makefile.cbm build/c/test-runner; CBM_ONLY_SUITE=extraction ./build/c/test-runner passed 246/246. Signed-off-by: Andrew Hundt --- internal/cbm/extract_calls.c | 13 +++++++++++++ internal/cbm/lang_specs.c | 2 +- tests/test_extraction.c | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/internal/cbm/extract_calls.c b/internal/cbm/extract_calls.c index 7a38e0de4..b9f9cb280 100644 --- a/internal/cbm/extract_calls.c +++ b/internal/cbm/extract_calls.c @@ -778,6 +778,15 @@ static char *extract_make_callee(CBMArena *a, TSNode node, const char *source, c return ts_node_is_null(fn) ? NULL : cbm_node_text(a, fn, source); } +// Meson normal_command stores its callee in the command field. +static char *extract_meson_callee(CBMArena *a, TSNode node, const char *source, const char *nk) { + if (strcmp(nk, "normal_command") != 0) { + return NULL; + } + TSNode cmd = ts_node_child_by_field_name(node, TS_FIELD("command")); + return ts_node_is_null(cmd) ? NULL : cbm_node_text(a, cmd, source); +} + // Puppet function_call stores its callee as the first named child. static char *extract_puppet_callee(CBMArena *a, TSNode node, const char *source, const char *nk) { @@ -852,6 +861,10 @@ static char *extract_callee_lang_specific(CBMArena *a, TSNode node, const char * char *c = extract_make_callee(a, node, source, nk); return c ? c : extract_scripting_callee(a, node, source, lang, nk); } + if (lang == CBM_LANG_MESON) { + char *c = extract_meson_callee(a, node, source, nk); + return c ? c : extract_scripting_callee(a, node, source, lang, nk); + } if (lang == CBM_LANG_PUPPET) { char *c = extract_puppet_callee(a, node, source, nk); return c ? c : extract_scripting_callee(a, node, source, lang, nk); diff --git a/internal/cbm/lang_specs.c b/internal/cbm/lang_specs.c index 971595800..869e9fcd8 100644 --- a/internal/cbm/lang_specs.c +++ b/internal/cbm/lang_specs.c @@ -889,7 +889,7 @@ static const char *svelte_branch_types[] = {"if_statement", "each_statement", "a // ==================== MESON ==================== static const char *meson_func_types[] = {"function_expression", NULL}; static const char *meson_module_types[] = {"source_file", NULL}; -static const char *meson_call_types[] = {"function_expression", "command", NULL}; +static const char *meson_call_types[] = {"normal_command", "function_expression", NULL}; static const char *meson_branch_types[] = {"if_statement", "foreach_statement", NULL}; static const char *meson_var_types[] = {"assignment_statement", NULL}; diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 97cc2e498..029a51b78 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -1638,6 +1638,8 @@ TEST(meson_project) { ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_GTE(r->defs.count, 1); + ASSERT(has_call_exact(r, "project")); + ASSERT(has_call_exact(r, "executable")); cbm_free_result(r); PASS(); } @@ -2193,6 +2195,18 @@ TEST(makefile_builtin_call_edges) { PASS(); } +TEST(just_function_call_edge) { + CBMFileResult *r = extract("NAME := uppercase('hello')\n" + "default:\n" + "\techo {{ NAME }}\n", + CBM_LANG_JUST, "test", "justfile"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_call_exact(r, "uppercase")); + cbm_free_result(r); + PASS(); +} + TEST(puppet_function_call_edge) { CBMFileResult *r = extract("notice('ready')\n", CBM_LANG_PUPPET, "test", "site.pp"); ASSERT_NOT_NULL(r); @@ -3430,6 +3444,7 @@ SUITE(extraction) { RUN_TEST(makefile_multiple_targets); RUN_TEST(makefile_variable_extraction); RUN_TEST(makefile_builtin_call_edges); + RUN_TEST(just_function_call_edge); RUN_TEST(puppet_function_call_edge); RUN_TEST(vimscript_function_extraction); RUN_TEST(vimscript_function_without_bang);