diff --git a/CHANGELOG.md b/CHANGELOG.md index 61dc143a..275c7d32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## \[Unreleased\] -- Nothing yet. +### Fixed + +- Parse keywords as block types and block labels, not only as attribute names. `in { ... }`, `for { ... }`, `for_each { ... }`, and `resource in { ... }` all failed to parse even though the matching attribute form `in = 1` worked. Keywords are reserved only inside expressions; in a body they are ordinary names. ([#355](https://github.com/amplify-education/python-hcl2/pull/355)) ## \[8.1.3\] - 2026-08-26 diff --git a/hcl2/hcl2.lark b/hcl2/hcl2.lark index 12e55fb9..6e91a44c 100644 --- a/hcl2/hcl2.lark +++ b/hcl2/hcl2.lark @@ -113,7 +113,7 @@ start : body body : (new_line_or_comment? (attribute | block))* new_line_or_comment? attribute : _attribute_name EQ expression _attribute_name : identifier | keyword | literal_value -block : identifier (identifier | string)* new_line_or_comment? LBRACE body RBRACE +block : _attribute_name (_attribute_name | string)* new_line_or_comment? LBRACE body RBRACE // Whitespace and comments new_line_or_comment: ( NL_OR_COMMENT )+ diff --git a/hcl2/transformer.py b/hcl2/transformer.py index 2d5e9a64..defa5a04 100644 --- a/hcl2/transformer.py +++ b/hcl2/transformer.py @@ -130,6 +130,14 @@ def body(self, meta: Meta, args) -> BodyRule: @v_args(meta=True) def block(self, meta: Meta, args) -> BlockRule: + # _attribute_name is flattened, so the block type and bare labels may be + # KeywordRule or LiteralValueRule; normalize so labels are all one type. + args = [ + IdentifierRule([NAME(a.token.value)], meta) + if isinstance(a, (KeywordRule, LiteralValueRule)) + else a + for a in args + ] return BlockRule(args, meta) @v_args(meta=True) diff --git a/test/integration/hcl2_original/resource_keyword_block.tf b/test/integration/hcl2_original/resource_keyword_block.tf new file mode 100644 index 00000000..8877b766 --- /dev/null +++ b/test/integration/hcl2_original/resource_keyword_block.tf @@ -0,0 +1,31 @@ +resource "custom_provider_resource" "resource_name" { + name = "resource_name" + + if { + value = "block_value1" + } + + in { + value = "block_value2" + } + + for { + value = "block_value3" + } + + for_each { + value = "block_value4" + } + + true { + value = "block_value5" + } +} + +in "labeled_block" { + value = "top_level_value" +} + +resource in { + value = "keyword_label_value" +} diff --git a/test/integration/hcl2_reconstructed/resource_keyword_block.tf b/test/integration/hcl2_reconstructed/resource_keyword_block.tf new file mode 100644 index 00000000..1cc4dd7e --- /dev/null +++ b/test/integration/hcl2_reconstructed/resource_keyword_block.tf @@ -0,0 +1,37 @@ +resource "custom_provider_resource" "resource_name" { + name = "resource_name" + + if { + value = "block_value1" + } + + + in { + value = "block_value2" + } + + + for { + value = "block_value3" + } + + + for_each { + value = "block_value4" + } + + + true { + value = "block_value5" + } +} + + +resource in { + value = "keyword_label_value" +} + + +in "labeled_block" { + value = "top_level_value" +} diff --git a/test/integration/json_reserialized/resource_keyword_block.json b/test/integration/json_reserialized/resource_keyword_block.json new file mode 100644 index 00000000..4584f9c3 --- /dev/null +++ b/test/integration/json_reserialized/resource_keyword_block.json @@ -0,0 +1,56 @@ +{ + "resource": [ + { + "\"custom_provider_resource\"": { + "\"resource_name\"": { + "name": "\"resource_name\"", + "if": [ + { + "value": "\"block_value1\"", + "__is_block__": true + } + ], + "in": [ + { + "value": "\"block_value2\"", + "__is_block__": true + } + ], + "for": [ + { + "value": "\"block_value3\"", + "__is_block__": true + } + ], + "for_each": [ + { + "value": "\"block_value4\"", + "__is_block__": true + } + ], + "true": [ + { + "value": "\"block_value5\"", + "__is_block__": true + } + ], + "__is_block__": true + } + } + }, + { + "in": { + "value": "\"keyword_label_value\"", + "__is_block__": true + } + } + ], + "in": [ + { + "\"labeled_block\"": { + "value": "\"top_level_value\"", + "__is_block__": true + } + } + ] +} diff --git a/test/integration/json_serialized/resource_keyword_block.json b/test/integration/json_serialized/resource_keyword_block.json new file mode 100644 index 00000000..4584f9c3 --- /dev/null +++ b/test/integration/json_serialized/resource_keyword_block.json @@ -0,0 +1,56 @@ +{ + "resource": [ + { + "\"custom_provider_resource\"": { + "\"resource_name\"": { + "name": "\"resource_name\"", + "if": [ + { + "value": "\"block_value1\"", + "__is_block__": true + } + ], + "in": [ + { + "value": "\"block_value2\"", + "__is_block__": true + } + ], + "for": [ + { + "value": "\"block_value3\"", + "__is_block__": true + } + ], + "for_each": [ + { + "value": "\"block_value4\"", + "__is_block__": true + } + ], + "true": [ + { + "value": "\"block_value5\"", + "__is_block__": true + } + ], + "__is_block__": true + } + } + }, + { + "in": { + "value": "\"keyword_label_value\"", + "__is_block__": true + } + } + ], + "in": [ + { + "\"labeled_block\"": { + "value": "\"top_level_value\"", + "__is_block__": true + } + } + ] +}