fix(extract): def-name + callee extraction for scss/sql/cobol/elm/teal

Several grammars produced no Function def and/or no CALLS edge because the
name/callee is not in the standard `name`/`function` field. Add precise
per-grammar extraction (verified against the actual tree-sitter parse trees):

- SCSS: function_statement/mixin_statement name is a `name` CHILD (not a field);
  the call node is `include_statement` (callee = its identifier child). Also fix
  scss_call_types, which listed "call_expression" instead of include_statement.
- SQL: create_function / invocation carry the name on a nested
  object_reference > `name` field.
- COBOL: call_statement's callee is its `x` string-literal (program name),
  dequoted.
- Elm: value_declaration name is functionDeclarationLeft > lower_case_identifier;
  function_call_expr callee is target > value_expr > name (value_qid).
- Teal: the `local function foo()` form carries the name on a `function_name`
  child rather than the `name` field.

Reproduced by the repro_grammar_* and repro_invariant_enclosing_parity suites
(SCSS/SQL/COBOL/Elm/Teal), which were RED for missing defs/calls.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-06-27 02:15:52 +02:00
parent f8a7e8b715
commit a044a71d59
3 changed files with 142 additions and 1 deletions
+90
View File
@@ -78,6 +78,7 @@ const char **cbm_string_dispatch_suffixes(CBMLanguage lang) {
// Forward declarations
static char *extract_callee_name(CBMArena *a, TSNode node, const char *source, CBMLanguage lang);
static char *gotemplate_callee(CBMArena *a, TSNode node, const char *source);
static const char *strip_and_validate_string_arg(CBMArena *a, char *text);
// Lean 4: check if an apply node is inside a type annotation.
// Strategy: walk up to the nearest declaration boundary; if the apply falls
@@ -614,10 +615,99 @@ static char *extract_dart_callee(CBMArena *a, TSNode node, const char *source, c
return NULL;
}
// SCSS: an `@include foo;` is an include_statement whose callee is its
// `identifier` child (the mixin name).
static char *extract_scss_callee(CBMArena *a, TSNode node, const char *source, const char *nk) {
if (strcmp(nk, "include_statement") != 0) {
return NULL;
}
TSNode id = cbm_find_child_by_kind(node, "identifier");
return ts_node_is_null(id) ? NULL : cbm_node_text(a, id, source);
}
// SQL: an `invocation` node's callee is nested object_reference > `name` field
// (the same shape as a create_function's name).
static char *extract_sql_callee(CBMArena *a, TSNode node, const char *source, const char *nk) {
if (strcmp(nk, "invocation") != 0) {
return NULL;
}
TSNode oref = cbm_find_child_by_kind(node, "object_reference");
if (ts_node_is_null(oref)) {
return NULL;
}
TSNode nm = ts_node_child_by_field_name(oref, TS_FIELD("name"));
return ts_node_is_null(nm) ? NULL : cbm_node_text(a, nm, source);
}
// COBOL: a `CALL 'HELPER'` is a call_statement whose `x` field is a string
// literal naming the called program; the callee is that string sans quotes.
static char *extract_cobol_callee(CBMArena *a, TSNode node, const char *source, const char *nk) {
if (strcmp(nk, "call_statement") != 0) {
return NULL;
}
TSNode x = ts_node_child_by_field_name(node, TS_FIELD("x"));
if (ts_node_is_null(x)) {
x = cbm_find_child_by_kind(node, "string");
}
if (ts_node_is_null(x)) {
return NULL;
}
char *text = cbm_node_text(a, x, source);
return (char *)strip_and_validate_string_arg(a, text);
}
// Elm: a `function_call_expr` has a `target` field; the callee identifier is
// target > value_expr > `name` field (value_qid) > lower_case_identifier.
static char *extract_elm_callee(CBMArena *a, TSNode node, const char *source, const char *nk) {
if (strcmp(nk, "function_call_expr") != 0) {
return NULL;
}
TSNode target = ts_node_child_by_field_name(node, TS_FIELD("target"));
if (ts_node_is_null(target)) {
return NULL;
}
TSNode ve = strcmp(ts_node_type(target), "value_expr") == 0
? target
: cbm_find_child_by_kind(target, "value_expr");
if (ts_node_is_null(ve)) {
return NULL;
}
TSNode qid = ts_node_child_by_field_name(ve, TS_FIELD("name"));
if (ts_node_is_null(qid)) {
qid = cbm_find_child_by_kind(ve, "value_qid");
}
if (ts_node_is_null(qid)) {
return NULL;
}
TSNode id = cbm_find_child_by_kind(qid, "lower_case_identifier");
if (ts_node_is_null(id)) {
// module-qualified call: emit the whole qualified id text
return cbm_node_text(a, qid, source);
}
return cbm_node_text(a, id, source);
}
static char *extract_callee_lang_specific(CBMArena *a, TSNode node, const char *source,
CBMLanguage lang) {
const char *nk = ts_node_type(node);
if (lang == CBM_LANG_SCSS) {
char *c = extract_scss_callee(a, node, source, nk);
return c ? c : extract_scripting_callee(a, node, source, lang, nk);
}
if (lang == CBM_LANG_SQL) {
char *c = extract_sql_callee(a, node, source, nk);
return c ? c : extract_scripting_callee(a, node, source, lang, nk);
}
if (lang == CBM_LANG_COBOL) {
char *c = extract_cobol_callee(a, node, source, nk);
return c ? c : extract_scripting_callee(a, node, source, lang, nk);
}
if (lang == CBM_LANG_ELM) {
char *c = extract_elm_callee(a, node, source, nk);
return c ? c : extract_scripting_callee(a, node, source, lang, nk);
}
if (lang == CBM_LANG_CLOJURE || lang == CBM_LANG_COMMONLISP || lang == CBM_LANG_SCHEME ||
lang == CBM_LANG_FENNEL || lang == CBM_LANG_RACKET || lang == CBM_LANG_EMACSLISP) {
return extract_lisp_callee(a, node, source, nk);
+51
View File
@@ -826,6 +826,57 @@ TSNode cbm_resolve_func_name(TSNode node, CBMLanguage lang) {
}
}
/* Teal: the `local function foo()` form reduces to a function_statement
* whose name is carried on a `function_name` child rather than the `name`
* field (the field is only populated for the bare `function foo()` form).
* func_name_node() already handled the field case above; here we cover the
* function_name child so local functions also produce a Function def. */
if (lang == CBM_LANG_TEAL && (strcmp(kind, "function_statement") == 0 ||
strcmp(kind, "function_signature") == 0)) {
TSNode fn = cbm_find_child_by_kind(node, "function_name");
if (!ts_node_is_null(fn)) {
return fn;
}
}
/* SCSS: function_statement/mixin_statement have no `name` field; the def
* name is a plain `name` child node. */
if (lang == CBM_LANG_SCSS &&
(strcmp(kind, "function_statement") == 0 || strcmp(kind, "mixin_statement") == 0)) {
TSNode nm = cbm_find_child_by_kind(node, "name");
if (!ts_node_is_null(nm)) {
return nm;
}
}
/* SQL: create_function has no `name` field; the function name is nested as
* object_reference > `name` field (an identifier). */
if (lang == CBM_LANG_SQL && strcmp(kind, "create_function") == 0) {
TSNode oref = cbm_find_child_by_kind(node, "object_reference");
if (!ts_node_is_null(oref)) {
TSNode nm = ts_node_child_by_field_name(oref, TS_FIELD("name"));
if (!ts_node_is_null(nm)) {
return nm;
}
}
}
/* Elm: value_declaration carries its name on the
* `functionDeclarationLeft` field's function_declaration_left child,
* whose first lower_case_identifier is the function name. */
if (lang == CBM_LANG_ELM && strcmp(kind, "value_declaration") == 0) {
TSNode lhs = ts_node_child_by_field_name(node, TS_FIELD("functionDeclarationLeft"));
if (ts_node_is_null(lhs)) {
lhs = cbm_find_child_by_kind(node, "function_declaration_left");
}
if (!ts_node_is_null(lhs)) {
TSNode nm = cbm_find_child_by_kind(lhs, "lower_case_identifier");
if (!ts_node_is_null(nm)) {
return nm;
}
}
}
/* Pine Script: function_declaration_statement carries the name on the
* `function` field (or `method` field for the method form), not `name`. */
if (lang == CBM_LANG_PINE && strcmp(kind, "function_declaration_statement") == 0) {
+1 -1
View File
@@ -636,7 +636,7 @@ static const char *css_import_types[] = {"import_statement", NULL};
// ==================== SCSS ====================
static const char *scss_func_types[] = {"mixin_statement", "function_statement", NULL};
static const char *scss_module_types[] = {"stylesheet", NULL};
static const char *scss_call_types[] = {"call_expression", NULL};
static const char *scss_call_types[] = {"call_expression", "include_statement", NULL};
static const char *scss_import_types[] = {"import_statement", "use_statement", "include_statement",
NULL};
static const char *scss_branch_types[] = {"if_statement", NULL};