From 0d860e044fdc33cfe531babc6e6dc19fe2414b98 Mon Sep 17 00:00:00 2001 From: Kamil Kozik Date: Wed, 9 Apr 2025 17:04:51 +0200 Subject: [PATCH] `HCLReverseTransformer` - rework logic recognizing blocks --- CHANGELOG.md | 1 + hcl2/reconstructor.py | 51 ++++++++----------- .../terraform-config-json/variables.json | 4 +- test/helpers/terraform-config/variables.tf | 2 + 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c4e93bb..173a6a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - Issue parsing parenthesesed identifier (reference) as an object key ([#212](https://github.com/amplify-education/python-hcl2/pull/212)) +- Issue discarding empty lists when transforming python dictionary into Lark Tree ([#216](https://github.com/amplify-education/python-hcl2/pull/216)) ## \[7.0.1\] - 2025-03-31 diff --git a/hcl2/reconstructor.py b/hcl2/reconstructor.py index 304d51c6..bf653c05 100644 --- a/hcl2/reconstructor.py +++ b/hcl2/reconstructor.py @@ -409,40 +409,29 @@ def _newline(self, level: int, count: int = 1) -> Tree: [Token("NL_OR_COMMENT", f"\n{' ' * level}") for _ in range(count)], ) - # rules: the value of a block is always an array of dicts, - # the key is the block type - def _list_is_a_block(self, value: list) -> bool: - for obj in value: - if not self._dict_is_a_block(obj): - return False - - return True - - def _dict_is_a_block(self, sub_obj: Any) -> bool: - # if the list doesn't contain dictionaries, it's not a block - if not isinstance(sub_obj, dict): - return False + def _is_block(self, value: Any) -> bool: + if isinstance(value, dict): + block_body = value + if ( + START_LINE_KEY in block_body.keys() + or END_LINE_KEY in block_body.keys() + ): + return True - # if the sub object has "start_line" and "end_line" metadata, - # the block itself is unlabeled, but it is a block - if START_LINE_KEY in sub_obj.keys() or END_LINE_KEY in sub_obj.keys(): - return True + try: + # if block is labeled, actual body might be nested + # pylint: disable=W0612 + block_label, block_body = next(iter(value.items())) + except StopIteration: + # no more potential labels = nothing more to check + return False - # if the objects in the array have no metadata and more than 2 keys and - # no metadata, it's just an array of objects, not a block - if len(list(sub_obj)) != 1: - return False + return self._is_block(block_body) - # if the sub object has a single string key whose value is an object, - # it _could_ be a labeled block... but we'd have to check if the sub - # object is a block (recurse) - label = list(sub_obj)[0] - sub_sub_obj = sub_obj[label] - if self._dict_is_a_block(sub_sub_obj): - return True + if isinstance(value, list): + if len(value) > 0: + return self._is_block(value[0]) - # if the objects in the array have a single key whose child is not a - # block, the array is just an array of objects, not a block return False def _calculate_block_labels(self, block: dict) -> Tuple[List[str], dict]: @@ -483,7 +472,7 @@ def _transform_dict_to_body(self, hcl_dict: dict, level: int) -> Tree: identifier_name = self._name_to_identifier(key) # first, check whether the value is a "block" - if isinstance(value, list) and self._list_is_a_block(value): + if self._is_block(value): for block_v in value: block_labels, block_body_dict = self._calculate_block_labels( block_v diff --git a/test/helpers/terraform-config-json/variables.json b/test/helpers/terraform-config-json/variables.json index 088e19cd..d388080a 100644 --- a/test/helpers/terraform-config-json/variables.json +++ b/test/helpers/terraform-config-json/variables.json @@ -48,7 +48,9 @@ "bar": { "baz": 1, "${(var.account)}": 2 - } + }, + "tuple": ["${local.foo}"], + "empty_tuple": [] }, { "route53_forwarding_rule_shares": "${{for forwarding_rule_key in keys(var.route53_resolver_forwarding_rule_shares) : \"${forwarding_rule_key}\" => {\"aws_account_ids\": \"${[for account_name in var.route53_resolver_forwarding_rule_shares[forwarding_rule_key].aws_account_names : module.remote_state_subaccounts.map[account_name].outputs[\"aws_account_id\"]]}\"}}}", diff --git a/test/helpers/terraform-config/variables.tf b/test/helpers/terraform-config/variables.tf index 6047a77f..2942b8c2 100644 --- a/test/helpers/terraform-config/variables.tf +++ b/test/helpers/terraform-config/variables.tf @@ -10,6 +10,8 @@ locals { baz : 1 (var.account) : 2 } + tuple = [local.foo] + empty_tuple = [] } variable "azs" {