mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-08 08:53:43 +08:00
fix(elixir): populate first_string_arg for Elixir calls
tree-sitter-elixir attaches no field name to a call's arguments node. Its whole field set is key, left, operand, operator, quoted_start, quoted_end, right, target and value, so ts_node_child_by_field_name(node, "arguments") is always null for Elixir and call.first_string_arg was never populated for any Elixir call. Every downstream signal that keys off a call's string argument was therefore dead for Elixir: Phoenix route paths, HTTP/async service URLs, and config keys. Nothing reported an error — the field was simply always NULL. The definition side already knows this: extract_defs.c carries elixir_call_args() with a positional second-child fallback for exactly this reason. This adds the same fallback on the call side, gated to `call` nodes, since Elixir's other call kinds (`dot`, the `|>` binary_operator) carry no arguments node in that position. The new test pins the field directly rather than a downstream effect. Signed-off-by: Henry Hazan <henry@teramine.io>
This commit is contained in:
@@ -515,6 +515,17 @@ static char *extract_objc_callee(CBMArena *a, TSNode node, const char *source, c
|
||||
return ts_node_is_null(selector) ? NULL : cbm_node_text(a, selector, source);
|
||||
}
|
||||
|
||||
/* tree-sitter-elixir gives a call's arguments node no field name, so it is
|
||||
* found positionally. Mirrors elixir_call_args() in extract_defs.c, which the
|
||||
* definition side has always used for the same reason. */
|
||||
static TSNode elixir_call_arguments_fallback(TSNode node) {
|
||||
TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments"));
|
||||
if (ts_node_is_null(args) && ts_node_child_count(node) > 1) {
|
||||
args = ts_node_child(node, 1);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
// Erlang: extract callee from call node's first child.
|
||||
static char *extract_erlang_callee(CBMArena *a, TSNode node, const char *source, const char *nk) {
|
||||
if (strcmp(nk, "call") != 0 || ts_node_child_count(node) == 0) {
|
||||
@@ -3311,6 +3322,21 @@ CBMInvocationDescriptor handle_calls(CBMExtractCtx *ctx, TSNode node, const CBML
|
||||
}
|
||||
|
||||
TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments"));
|
||||
/* tree-sitter-elixir attaches NO field name to a call's arguments
|
||||
* node — its whole field set is key, left, operand, operator,
|
||||
* quoted_start, quoted_end, right, target, value — so the lookup
|
||||
* above is always null for Elixir and first_string_arg was never
|
||||
* populated for ANY Elixir call. That silently disabled every
|
||||
* downstream signal keyed off a call's string argument: Phoenix
|
||||
* route paths, HTTP/async service URLs, and config keys.
|
||||
* cbm_elixir_call_args is the shared second-child fallback the
|
||||
* definition side already uses. Restricted to `call` nodes —
|
||||
* Elixir's other call kinds (`dot`, the `|>` binary_operator) have
|
||||
* no arguments node in that position. */
|
||||
if (ts_node_is_null(args) && ctx->language == CBM_LANG_ELIXIR &&
|
||||
strcmp(ts_node_type(node), "call") == 0) {
|
||||
args = elixir_call_arguments_fallback(node);
|
||||
}
|
||||
// ObjectScript stores args under oref_method/method_args, not the
|
||||
// generic "arguments" field; macro arguments add one wrapper.
|
||||
if (ts_node_is_null(args) && is_objectscript_language(ctx->language)) {
|
||||
|
||||
@@ -1005,6 +1005,33 @@ TEST(elixir_function) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* tree-sitter-elixir gives a call's arguments node no field name, so the
|
||||
* generic `arguments` field lookup returns null and first_string_arg was never
|
||||
* populated for any Elixir call — Phoenix route paths, service URLs and config
|
||||
* keys all key off it. */
|
||||
TEST(elixir_call_string_argument) {
|
||||
CBMFileResult *r = extract("defmodule Sample do\n"
|
||||
" def run do\n"
|
||||
" get(\"/wallets\", WalletController)\n"
|
||||
" end\n"
|
||||
"end\n",
|
||||
CBM_LANG_ELIXIR, "t", "sample.ex");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
int seen = 0;
|
||||
for (int i = 0; i < r->calls.count; i++) {
|
||||
if (strcmp(r->calls.items[i].callee_name, "get") != 0) {
|
||||
continue;
|
||||
}
|
||||
seen = 1;
|
||||
ASSERT_NOT_NULL(r->calls.items[i].first_string_arg);
|
||||
ASSERT_STR_EQ("/wallets", r->calls.items[i].first_string_arg);
|
||||
}
|
||||
ASSERT_EQ(1, seen);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* --- Haskell --- */
|
||||
TEST(haskell_function) {
|
||||
CBMFileResult *r = extract("add :: Int -> Int -> Int\nadd x y = x + y\n\nmultiply :: Int -> "
|
||||
@@ -5432,6 +5459,7 @@ SUITE(extraction) {
|
||||
|
||||
/* Functional */
|
||||
RUN_TEST(elixir_function);
|
||||
RUN_TEST(elixir_call_string_argument);
|
||||
RUN_TEST(haskell_function);
|
||||
RUN_TEST(ocaml_function);
|
||||
RUN_TEST(erlang_function);
|
||||
|
||||
Reference in New Issue
Block a user