mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-07 08:43:35 +08:00
fix(hcl): index .tofu and .tfvars; only top-level blocks are Classes
- OpenTofu configuration (`.tofu`) and variable files (`.tfvars`) had no language and were never indexed (cloud-foundation-fabric: 104 `.tofu`, 208 `.tfvars`). - Every HCL `block` became a Class, nested ones included: a resource's `lifecycle`, `dynamic`/`content` and argument blocks were entities next to the resource (cloud-foundation-fabric: `dynamic` 1,342, `content` 205 beside 898 resources). Only a block directly in the file's body -- resource, data, module, variable, output, locals, provider, terraform, Terragrunt's include/dependency -- is a Class now. - A file's own assignments (a `.tfvars` file, Terragrunt `inputs`) sit one level below the root the variable walk scanned; none was extracted. They are Variables now. On real input (HCL Classes / .tofu+.tfvars files / HCL Variables), against main: cloud-foundation-fabric 6,324 -> 3,919 / 0 -> 312 / 0 -> 1,067, kubernetes/k8s.io 2,102 -> 1,590 / 0 -> 11 / 0 -> 42, kubespray 892 -> 804 / 0 -> 13 / 0 -> 174, eks-blueprints 924 -> 733, kube-hetzner 757 -> 683, deckhouse 976 -> 830; the terragrunt example keeps its 31 top-level blocks and gains 5 input Variables. Not changed here: the remaining HCL USAGE edges are generic name guesses (a `range` call resolves to a Class in <python-builtins>, module outputs to same-named outputs of unrelated modules), and the name-similarity config links now also see the 104 OpenTofu files. The nested-block, .tofu and .tfvars probes and the extension unit test fail without the change. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
@@ -6862,6 +6862,19 @@ static char *find_ini_section_name(CBMArena *a, TSNode node, const char *source)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// HCL: a top-level block (resource, data, module, variable, output, locals,
|
||||
// provider, terraform; Terragrunt's include, dependency, ...) sits directly in
|
||||
// the file's body. Blocks nested in one (lifecycle, dynamic, content, a
|
||||
// resource's own argument blocks) are part of it, not entities of their own.
|
||||
static bool hcl_block_is_top_level(TSNode node) {
|
||||
TSNode body = ts_node_parent(node);
|
||||
if (ts_node_is_null(body) || strcmp(ts_node_type(body), "body") != 0) {
|
||||
return false;
|
||||
}
|
||||
TSNode file = ts_node_parent(body);
|
||||
return !ts_node_is_null(file) && strcmp(ts_node_type(file), "config_file") == 0;
|
||||
}
|
||||
|
||||
// HCL: extract block name from identifier child.
|
||||
static char *find_hcl_block_name(CBMArena *a, TSNode node, const char *source) {
|
||||
TSNode id = cbm_find_child_by_kind(node, "identifier");
|
||||
@@ -6923,7 +6936,7 @@ static bool extract_config_class_def(CBMExtractCtx *ctx, TSNode node, const char
|
||||
// #518: index what the section SAYS, not just its title.
|
||||
docstring = extract_markdown_section_body(a, node, ctx->source);
|
||||
} else if (ctx->language == CBM_LANG_HCL && strcmp(kind, "block") == 0) {
|
||||
name = find_hcl_block_name(a, node, ctx->source);
|
||||
name = hcl_block_is_top_level(node) ? find_hcl_block_name(a, node, ctx->source) : NULL;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -10011,6 +10024,26 @@ static void extract_variables(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec
|
||||
return;
|
||||
}
|
||||
|
||||
/* HCL: a file's own assignments -- a .tfvars file, Terragrunt's `inputs`
|
||||
* -- sit in the top-level body, one level below the root the generic loop
|
||||
* scans, so none was extracted. Attributes inside blocks belong to their
|
||||
* block. */
|
||||
if (ctx->language == CBM_LANG_HCL) {
|
||||
TSNode body = cbm_find_child_by_kind(root, "body");
|
||||
uint32_t n = ts_node_is_null(body) ? 0 : ts_node_named_child_count(body);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
TSNode attr = ts_node_named_child(body, i);
|
||||
if (strcmp(ts_node_type(attr), "attribute") != 0) {
|
||||
continue;
|
||||
}
|
||||
TSNode id = cbm_find_child_by_kind(attr, "identifier");
|
||||
if (!ts_node_is_null(id)) {
|
||||
push_var_def(ctx, cbm_node_text(ctx->arena, id, ctx->source), attr);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Nix: the file's top level sits behind its header lambda(s), so the root's
|
||||
* only child is a function_expression and the generic loop below would see
|
||||
* nothing. Resolve past the header to the binding container(s) that actually
|
||||
|
||||
@@ -152,9 +152,11 @@ static const ext_entry_t EXT_TABLE[] = {
|
||||
/* Haskell */
|
||||
{".hs", CBM_LANG_HASKELL},
|
||||
|
||||
/* HCL / Terraform */
|
||||
/* HCL / Terraform / OpenTofu */
|
||||
{".hcl", CBM_LANG_HCL},
|
||||
{".tf", CBM_LANG_HCL},
|
||||
{".tofu", CBM_LANG_HCL},
|
||||
{".tfvars", CBM_LANG_HCL},
|
||||
|
||||
/* HTML */
|
||||
{".htm", CBM_LANG_HTML},
|
||||
|
||||
@@ -276,6 +276,53 @@ TEST(probe_hcl_no_spurious_imports) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Only a top-level block (resource, data, module, variable, output, ...) is an
|
||||
* entity; blocks nested in it (lifecycle, dynamic, content, the resource's own
|
||||
* argument blocks) are part of it. cloud-foundation-fabric minted 1,342
|
||||
* `dynamic` and 205 `content` Classes next to 898 resources. */
|
||||
TEST(probe_hcl_nested_blocks_are_not_classes) {
|
||||
GpfMetrics m = gpf_metrics("main.tf",
|
||||
"resource \"google_container_cluster\" \"main\" {\n"
|
||||
" name = \"main\"\n"
|
||||
" addons_config {\n"
|
||||
" http_load_balancing { disabled = false }\n"
|
||||
" }\n"
|
||||
" dynamic \"node_pool\" {\n"
|
||||
" for_each = var.pools\n"
|
||||
" content {\n"
|
||||
" name = node_pool.value.name\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" lifecycle {\n"
|
||||
" ignore_changes = [node_pool]\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
ASSERT_EQ(m.classes, 1);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* OpenTofu configuration (.tofu) is indexed like .tf. */
|
||||
TEST(probe_tofu_file_indexed) {
|
||||
GpfMetrics m = gpf_metrics("main.tofu",
|
||||
"resource \"aws_s3_bucket\" \"logs\" {\n"
|
||||
" bucket = \"logs\"\n"
|
||||
"}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
ASSERT_EQ(m.classes, 1);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A variable file's assignments are its content. */
|
||||
TEST(probe_tfvars_file_indexed) {
|
||||
GpfMetrics m = gpf_metrics("prod.tfvars",
|
||||
"region = \"europe-west1\"\n"
|
||||
"node_count = 3\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
ASSERT_TRUE(m.variables >= 2);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════
|
||||
* GROUP 2 — Kubernetes manifests (.yaml with apiVersion:)
|
||||
*
|
||||
@@ -2011,6 +2058,9 @@ SUITE(grammar_probe_f) {
|
||||
RUN_TEST(probe_hcl_module_block_node);
|
||||
RUN_TEST(probe_hcl_infra_maps_edge);
|
||||
RUN_TEST(probe_hcl_no_spurious_imports);
|
||||
RUN_TEST(probe_hcl_nested_blocks_are_not_classes);
|
||||
RUN_TEST(probe_tofu_file_indexed);
|
||||
RUN_TEST(probe_tfvars_file_indexed);
|
||||
|
||||
/* Kubernetes manifests */
|
||||
RUN_TEST(probe_k8s_pod_resource_node);
|
||||
|
||||
@@ -386,6 +386,14 @@ TEST(lang_ext_hcl2) {
|
||||
ASSERT_EQ(cbm_language_for_extension(".hcl"), CBM_LANG_HCL);
|
||||
PASS();
|
||||
}
|
||||
/* OpenTofu's own configuration files and Terraform/OpenTofu variable files
|
||||
* are HCL too; neither was indexed (cloud-foundation-fabric: 104 .tofu,
|
||||
* 208 .tfvars). */
|
||||
TEST(lang_ext_tofu_tfvars) {
|
||||
ASSERT_EQ(cbm_language_for_extension(".tofu"), CBM_LANG_HCL);
|
||||
ASSERT_EQ(cbm_language_for_extension(".tfvars"), CBM_LANG_HCL);
|
||||
PASS();
|
||||
}
|
||||
TEST(lang_ext_sql) {
|
||||
ASSERT_EQ(cbm_language_for_extension(".sql"), CBM_LANG_SQL);
|
||||
PASS();
|
||||
@@ -1588,6 +1596,7 @@ SUITE(language) {
|
||||
RUN_TEST(lang_ext_toml);
|
||||
RUN_TEST(lang_ext_hcl);
|
||||
RUN_TEST(lang_ext_hcl2);
|
||||
RUN_TEST(lang_ext_tofu_tfvars);
|
||||
RUN_TEST(lang_ext_sql);
|
||||
RUN_TEST(lang_ext_dockerfile);
|
||||
RUN_TEST(lang_ext_json);
|
||||
|
||||
Reference in New Issue
Block a user