Skip to content

feat: add Terraform/HCL language support (#199)#514

Open
stk0vrfl0w wants to merge 1 commit into
tirth8205:mainfrom
stk0vrfl0w:feat/hcl-terraform-support
Open

feat: add Terraform/HCL language support (#199)#514
stk0vrfl0w wants to merge 1 commit into
tirth8205:mainfrom
stk0vrfl0w:feat/hcl-terraform-support

Conversation

@stk0vrfl0w
Copy link
Copy Markdown

@stk0vrfl0w stk0vrfl0w commented May 25, 2026

Adds full .tf / .hcl parsing to code-review-graph, addressing issue #199.

Terraform construct Node kind Name
resource "type" "name" Class resource.type.name
data "type" "name" Class data.type.name
module "name" Class module.name
variable "name" Function var.name
output "name" Function output.name
locals { key = ... } Function local.key
provider "name" Function provider.name
terraform { ... } (skipped)

Edge types emitted:

  • CONTAINS — file -> every node
  • IMPORTS_FROM — file -> module source path (resolved when possible)
  • REFERENCES — cross-block value references via variable_expr+get_attr chains

_hcl_variable_refs / _hcl_ref_target resolve dotted expression chains:

  • var.x, local.x, module.x, data.type.name -> named graph targets
  • resource references (aws_vpc.main.id) -> resource.aws_vpc.main
  • Built-in / block-local roots suppressed (no spurious edges): _HCL_REF_PREFIXES includes each, count, self, path, terraform

_HCL_RECURSE_TYPES covers function_call + function_arguments (so var refs inside length(var.x) are extracted) and quoted_template + template_interpolation (so "${var.x}" interpolations are extracted).

_hcl_dynamic_iterator_name() extracts the iterator symbol from any dynamic block (defaulting to the block label; respecting the optional iterator = override). _walk_hcl_expressions accepts a local_names: frozenset[str] scope that accumulates iterator symbols across nested dynamic blocks, suppressing spurious resource..* edges from expressions like setting.value[...] or origin_group.key.

Covers all three cases from the Terraform dynamic-blocks documentation:

  • Default iterator (block label = iterator symbol)

  • Custom iterator via iterator =

  • Multi-level nested dynamic blocks

  • code_review_graph/parser.py — extension map, type dicts, 10+ module-level helpers, 4 class methods, _walk_hcl_expressions scope-aware iterator tracking

  • tests/fixtures/sample.tf — 243-line fixture covering resources, data sources, modules, variables, outputs, locals, providers, for_each chaining, function-call refs, template interpolations, lifecycle/replace_triggered_by, and all three dynamic block cases

  • tests/test_multilang.py — TestHCLParsing: 36 tests, all passing

  • docs/FEATURES.md — HCL/Terraform added to language list

  • docs/LLM-OPTIMIZED-REFERENCE.md — HCL/Terraform added to

Recognised and fully parsed: Terraform and OpenTofu (.tf, .hcl). OpenTofu is a direct fork of Terraform with identical block schemas.

The following tools also use .hcl files but with different top-level block schemas. Their files will be detected as language="hcl" by extension, but their block types are absent from _HCL_BLOCK_CFG and will produce a File node only (no resources, variables, or edges):

  • Terragrunt (terragrunt.hcl, terragrunt.stack.hcl) blocks: include, dependency, dependencies, remote_state, generate, inputs, unit, stack
  • Packer (*.pkr.hcl)
    blocks: source, build, packer
  • Nomad (*.hcl, *.nomad)
    blocks: job, group, task, service, network
  • Vault (*.hcl policy files)
    blocks: path
  • Consul (*.hcl config files)
    blocks: datacenter, acl, server, and others
  • Waypoint (waypoint.hcl)
    blocks: app, build, deploy, release
  • Boundary (*.hcl)
    blocks: controller, worker, listener

Adds full .tf / .hcl parsing to code-review-graph, closing issue tirth8205#199.

| Terraform construct        | Node kind | Name               |
|----------------------------|-----------|--------------------|
| resource "type" "name"     | Class     | resource.type.name |
| data "type" "name"         | Class     | data.type.name     |
| module "name"              | Class     | module.name        |
| variable "name"            | Function  | var.name           |
| output "name"              | Function  | output.name        |
| locals { key = ... }       | Function  | local.key          |
| provider "name"            | Function  | provider.name      |
| terraform { ... }          | (skipped) |                    |

Edge types emitted:
- CONTAINS    — file -> every node
- IMPORTS_FROM — file -> module source path (resolved when possible)
- REFERENCES  — cross-block value references via variable_expr+get_attr chains

_hcl_variable_refs / _hcl_ref_target resolve dotted expression chains:
- var.x, local.x, module.x, data.type.name -> named graph targets
- resource references (aws_vpc.main.id) -> resource.aws_vpc.main
- Built-in / block-local roots suppressed (no spurious edges):
  _HCL_REF_PREFIXES includes each, count, self, path, terraform

_HCL_RECURSE_TYPES covers function_call + function_arguments (so
var refs inside length(var.x) are extracted) and quoted_template +
template_interpolation (so "${var.x}" interpolations are extracted).

_hcl_dynamic_iterator_name() extracts the iterator symbol from any
dynamic block (defaulting to the block label; respecting the optional
iterator = <ident> override). _walk_hcl_expressions accepts a
local_names: frozenset[str] scope that accumulates iterator symbols
across nested dynamic blocks, suppressing spurious resource.<iter>.*
edges from expressions like setting.value[...] or origin_group.key.

Covers all three cases from the Terraform dynamic-blocks documentation:
  - Default iterator (block label = iterator symbol)
  - Custom iterator via iterator = <ident>
  - Multi-level nested dynamic blocks

- code_review_graph/parser.py — extension map, type dicts, 10+
  module-level helpers, 4 class methods, _walk_hcl_expressions
  scope-aware iterator tracking
- tests/fixtures/sample.tf — 243-line fixture covering resources,
  data sources, modules, variables, outputs, locals, providers,
  for_each chaining, function-call refs, template interpolations,
  lifecycle/replace_triggered_by, and all three dynamic block cases
- tests/test_multilang.py — TestHCLParsing: 36 tests, all passing
- docs/FEATURES.md — HCL/Terraform added to language list
- docs/LLM-OPTIMIZED-REFERENCE.md — HCL/Terraform added to
  <section name="languages">

Recognised and fully parsed: Terraform and OpenTofu (.tf, .hcl).
OpenTofu is a direct fork of Terraform with identical block schemas.

The following tools also use .hcl files but with different top-level
block schemas.  Their files will be detected as language="hcl" by
extension, but their block types are absent from _HCL_BLOCK_CFG and
will produce a File node only (no resources, variables, or edges):

  - Terragrunt  (terragrunt.hcl, terragrunt.stack.hcl)
    blocks: include, dependency, dependencies, remote_state,
            generate, inputs, unit, stack
  - Packer      (*.pkr.hcl)
    blocks: source, build, packer
  - Nomad       (*.hcl, *.nomad)
    blocks: job, group, task, service, network
  - Vault       (*.hcl policy files)
    blocks: path
  - Consul      (*.hcl config files)
    blocks: datacenter, acl, server, and others
  - Waypoint    (waypoint.hcl)
    blocks: app, build, deploy, release
  - Boundary    (*.hcl)
    blocks: controller, worker, listener
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant