Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
51 changes: 20 additions & 31 deletions hcl2/reconstructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion test/helpers/terraform-config-json/variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -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\"]]}\"}}}",
Expand Down
2 changes: 2 additions & 0 deletions test/helpers/terraform-config/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ locals {
baz : 1
(var.account) : 2
}
tuple = [local.foo]
empty_tuple = []
}

variable "azs" {
Expand Down