mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 18:58:00 +00:00
feat(helm): extract Helm/Go-template named templates and include calls
Helm chart helpers (.tpl) and Go templates produced no semantic graph — just a
flat key flood. Wire the existing gotemplate grammar for real structure:
- .tpl files map to the gotemplate language (Helm _helpers.tpl).
- {{ define "chart.fullname" }} becomes a Function node named after the
template (the name is a string literal, not an identifier, so it's resolved
in a dedicated walker branch).
- {{ include "x" . }} and {{ template "x" . }} resolve to the referenced
template name instead of the bare `include` builtin, so they form CALLS
edges to the define'd Function. template_action is added to the call set.
Lets you trace which charts include which named templates. Tests cover define
-> Function and include/template -> CALLS, plus the .tpl mapping.
Refs #338
This commit is contained in:
1 parent
1e1d40851c
commit
a8fc901129
6 files changed
+123
-1
No files matched your search
@@ -60,6 +60,7 @@ static const char *strip_quotes(CBMArena *a, const char *text) {
|
||||
static void walk_calls(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec);
|
||||
static char *extract_callee_name(CBMArena *a, TSNode node, const char *source, CBMLanguage lang);
|
||||
static void extract_jsx_refs(CBMExtractCtx *ctx, TSNode node);
|
||||
static char *gotemplate_callee(CBMArena *a, TSNode node, const char *source);
|
||||
|
||||
// Lean 4: check if an apply node is inside a type annotation.
|
||||
// Strategy: walk up to the nearest declaration boundary; if the apply falls
|
||||
@@ -355,6 +356,15 @@ static char *extract_callee_name(CBMArena *a, TSNode node, const char *source, C
|
||||
}
|
||||
}
|
||||
|
||||
// Helm / Go templates: resolve `include "x"` / `template "x"` to the
|
||||
// referenced named template so it links to the define'd Function (#338).
|
||||
if (lang == CBM_LANG_GOTEMPLATE) {
|
||||
char *g = gotemplate_callee(a, node, source);
|
||||
if (g) {
|
||||
return g;
|
||||
}
|
||||
}
|
||||
|
||||
// Try common field-based resolution first
|
||||
char *name = extract_callee_from_fields(a, node, source);
|
||||
if (name) {
|
||||
@@ -413,6 +423,48 @@ static const char *extract_first_string_arg(CBMExtractCtx *ctx, TSNode args) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Return the (dequoted) first string-literal child of a node, or NULL.
|
||||
static char *gotemplate_string_child(CBMArena *a, TSNode parent, const char *source) {
|
||||
TSNode s = cbm_find_child_by_kind(parent, "interpreted_string_literal");
|
||||
if (ts_node_is_null(s)) {
|
||||
return NULL;
|
||||
}
|
||||
char *text = cbm_node_text(a, s, source);
|
||||
const char *v = strip_and_validate_string_arg(a, text);
|
||||
return (char *)v;
|
||||
}
|
||||
|
||||
// Resolve a Go-template / Helm call to the referenced named template:
|
||||
// {{ template "x" . }} -> template_action, name is a string child
|
||||
// {{ include "x" . }} -> function_call(include), name is first string arg
|
||||
// Returns NULL for any other node so generic resolution names the function.
|
||||
static char *gotemplate_callee(CBMArena *a, TSNode node, const char *source) {
|
||||
const char *k = ts_node_type(node);
|
||||
if (strcmp(k, "template_action") == 0) {
|
||||
return gotemplate_string_child(a, node, source);
|
||||
}
|
||||
if (strcmp(k, "function_call") == 0) {
|
||||
TSNode fn = cbm_find_child_by_kind(node, "identifier");
|
||||
if (ts_node_is_null(fn)) {
|
||||
return NULL;
|
||||
}
|
||||
char *fname = cbm_node_text(a, fn, source);
|
||||
if (!fname || (strcmp(fname, "include") != 0 && strcmp(fname, "template") != 0 &&
|
||||
strcmp(fname, "tpl") != 0)) {
|
||||
return NULL;
|
||||
}
|
||||
TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments"));
|
||||
if (ts_node_is_null(args)) {
|
||||
args = cbm_find_child_by_kind(node, "argument_list");
|
||||
}
|
||||
if (ts_node_is_null(args)) {
|
||||
return NULL;
|
||||
}
|
||||
return gotemplate_string_child(a, args, source);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Walk AST for call nodes (iterative)
|
||||
static void walk_calls(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
|
||||
TSNodeStack stack;
|
||||
|
||||
@@ -3674,6 +3674,40 @@ static void extract_cfml_function_tag(CBMExtractCtx *ctx, TSNode node) {
|
||||
cbm_defs_push(&ctx->result->defs, a, def);
|
||||
}
|
||||
|
||||
// Helm / Go template named-template definition: {{ define "chart.fullname" }} ...
|
||||
// {{ end }} (#338). The name is a string-literal child of define_action. Emit a
|
||||
// Function node so `include`/`template` references resolve to it via CALLS.
|
||||
static void extract_gotemplate_define(CBMExtractCtx *ctx, TSNode node) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNode s = cbm_find_child_by_kind(node, "interpreted_string_literal");
|
||||
if (ts_node_is_null(s)) {
|
||||
return;
|
||||
}
|
||||
char *raw = cbm_node_text(a, s, ctx->source);
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
size_t len = strlen(raw);
|
||||
if (len >= 2 && (raw[0] == '"' || raw[0] == '`')) {
|
||||
raw = cbm_arena_strndup(a, raw + 1, len - 2); // strip surrounding quotes
|
||||
}
|
||||
if (!raw || !raw[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
CBMDefinition def;
|
||||
memset(&def, 0, sizeof(def));
|
||||
def.name = raw;
|
||||
def.qualified_name = cbm_fqn_compute(a, ctx->project, ctx->rel_path, raw);
|
||||
def.label = "Function";
|
||||
def.file_path = ctx->rel_path;
|
||||
def.start_line = ts_node_start_point(node).row + TS_LINE_OFFSET;
|
||||
def.end_line = ts_node_end_point(node).row + TS_LINE_OFFSET;
|
||||
def.lines = (int)(def.end_line - def.start_line + TS_LINE_OFFSET);
|
||||
def.is_exported = true;
|
||||
cbm_defs_push(&ctx->result->defs, a, def);
|
||||
}
|
||||
|
||||
// Languages that use the C preprocessor and therefore have #define macros.
|
||||
static bool is_c_preprocessor_lang(CBMLanguage lang) {
|
||||
return lang == CBM_LANG_C || lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA ||
|
||||
@@ -3743,6 +3777,11 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec,
|
||||
// fall through: descend into the body for nested tags / calls
|
||||
}
|
||||
|
||||
if (ctx->language == CBM_LANG_GOTEMPLATE && strcmp(kind, "define_action") == 0) {
|
||||
extract_gotemplate_define(ctx, node);
|
||||
// fall through: descend into the body for nested defines
|
||||
}
|
||||
|
||||
if (cbm_kind_in_set(node, spec->function_node_types)) {
|
||||
if (!is_template_class_node(node, ctx->language)) {
|
||||
extract_func_def(ctx, node, spec);
|
||||
|
||||
@@ -1251,7 +1251,8 @@ static const char *just_import_types[] = {"import", NULL};
|
||||
static const char *just_branch_types[] = {"if_expression", NULL};
|
||||
static const char *just_assign_types[] = {"assignment", NULL};
|
||||
static const char *just_module_types[] = {"source_file", NULL};
|
||||
static const char *gotemplate_call_types[] = {"function_call", "method_call", NULL};
|
||||
static const char *gotemplate_call_types[] = {"function_call", "method_call", "template_action",
|
||||
NULL};
|
||||
static const char *gotemplate_module_types[] = {"template", NULL};
|
||||
static const char *templ_func_types[] = {"function_declaration", "method_declaration",
|
||||
"method_elem", NULL};
|
||||
|
||||
@@ -372,6 +372,7 @@ static const ext_entry_t EXT_TABLE[] = {
|
||||
|
||||
/* Go Template */
|
||||
{".gotmpl", CBM_LANG_GOTEMPLATE},
|
||||
{".tpl", CBM_LANG_GOTEMPLATE}, /* Helm _helpers.tpl named-template definitions */
|
||||
|
||||
/* Hare */
|
||||
{".ha", CBM_LANG_HARE},
|
||||
|
||||
@@ -277,6 +277,28 @@ TEST(extract_cfml_tag_issue38) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* --- Helm / Go template: named templates + include calls (#338) --- */
|
||||
TEST(extract_helm_templates_issue338) {
|
||||
CBMFileResult *r = extract("{{- define \"chart.fullname\" -}}\n"
|
||||
"{{- .Release.Name -}}\n"
|
||||
"{{- end -}}\n"
|
||||
"\n"
|
||||
"{{- define \"chart.labels\" -}}\n"
|
||||
"app: {{ include \"chart.fullname\" . }}\n"
|
||||
"chart: {{ template \"chart.fullname\" . }}\n"
|
||||
"{{- end -}}\n",
|
||||
CBM_LANG_GOTEMPLATE, "chart", "templates/_helpers.tpl");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
/* define -> Function nodes */
|
||||
ASSERT(has_def(r, "Function", "chart.fullname"));
|
||||
ASSERT(has_def(r, "Function", "chart.labels"));
|
||||
/* include / template -> CALLS to the named template (not to "include") */
|
||||
ASSERT(has_call(r, "chart.fullname"));
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* --- Java --- */
|
||||
TEST(java_class) {
|
||||
CBMFileResult *r = extract(
|
||||
@@ -2554,6 +2576,7 @@ SUITE(extraction) {
|
||||
RUN_TEST(extract_qml_issue42);
|
||||
RUN_TEST(extract_cfscript_issue38);
|
||||
RUN_TEST(extract_cfml_tag_issue38);
|
||||
RUN_TEST(extract_helm_templates_issue338);
|
||||
|
||||
/* OOP */
|
||||
RUN_TEST(java_class);
|
||||
|
||||
@@ -759,6 +759,11 @@ TEST(lang_ext_cfml) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(lang_ext_helm_tpl) {
|
||||
ASSERT_EQ(cbm_language_for_extension(".tpl"), CBM_LANG_GOTEMPLATE);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(lang_ext_janet) {
|
||||
ASSERT_EQ(cbm_language_for_extension(".janet"), CBM_LANG_JANET);
|
||||
PASS();
|
||||
@@ -1229,6 +1234,7 @@ SUITE(language) {
|
||||
RUN_TEST(lang_ext_luau);
|
||||
RUN_TEST(lang_ext_qml);
|
||||
RUN_TEST(lang_ext_cfml);
|
||||
RUN_TEST(lang_ext_helm_tpl);
|
||||
RUN_TEST(lang_ext_janet);
|
||||
RUN_TEST(lang_ext_sway);
|
||||
RUN_TEST(lang_ext_nasm);
|
||||
|
||||
Reference in new issue
Block a user