From 458817bc04a65c544a71d46ec62e2d49aa7ca69a Mon Sep 17 00:00:00 2001 From: Kamil Kozik Date: Wed, 16 Apr 2025 14:11:01 +0200 Subject: [PATCH] reintroduce parsing of string interpolations nested deeper than 2 times (originally implemented by @weaversam8 in #177) and fixed `STRING_CHARS` regex to treat escaped interpolation as a regular string --- CHANGELOG.md | 4 +++ README.md | 30 ++++--------------- hcl2/hcl2.lark | 11 +++---- .../string_interpolations.json | 2 +- .../terraform-config/string_interpolations.tf | 1 + test/unit/test_builder.py | 1 + 6 files changed, 19 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 616578bb..3ceffae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## \[Unreleased\] +### Added + +- Possibility to parse deeply nested interpolations (formerly a Limitation), Thanks again, @weaversam8 ([#223](https://github.com/amplify-education/python-hcl2/pull/223)) + ### Fixed - Issue parsing ellipsis in a separate line within `for` expression ([#221](https://github.com/amplify-education/python-hcl2/pull/221)) diff --git a/README.md b/README.md index 0454e9d5..1ff75876 100644 --- a/README.md +++ b/README.md @@ -87,33 +87,15 @@ We’ll try to answer any PR’s promptly. ## Limitations -### Error parsing string interpolations nested more than 2 times - -- Parsing following example is expected to throw out an exception and fail: - ```terraform - locals { - foo = "foo" - name = "prefix1-${"prefix2-${"${local.foo}-bar"}"}" //should interpolate into "prefix1-prefix2-foo-bar" but fails - } - ``` - We recommend working around this by modifying the configuration in the following manner: - ```terraform - locals { - foo = "foo" - foo_bar = "${local.foo}-bar" - name = "prefix1-${"prefix2-${local.foo_bar}"}" //interpolates into "prefix1-prefix2-foo-bar" - } - ``` - ### Using inline expression as an object key - Object key can be an expression as long as it is wrapped in parentheses: ```terraform - locals { - foo = "bar" - baz = { - (format("key_prefix_%s", local.foo)) : "value" - # format("key_prefix_%s", local.foo) : "value" this will fail - } + locals { + foo = "bar" + baz = { + (format("key_prefix_%s", local.foo)) : "value" + # format("key_prefix_%s", local.foo) : "value" this will fail } + } ``` diff --git a/hcl2/hcl2.lark b/hcl2/hcl2.lark index 64769aac..9bd41e18 100644 --- a/hcl2/hcl2.lark +++ b/hcl2/hcl2.lark @@ -1,7 +1,7 @@ start : body body : (new_line_or_comment? (attribute | block))* new_line_or_comment? attribute : identifier EQ expression -block : identifier (identifier | STRING_LIT)* new_line_or_comment? "{" body "}" +block : identifier (identifier | STRING_LIT | string_with_interpolation)* new_line_or_comment? "{" body "}" new_line_or_comment: ( NL_OR_COMMENT )+ NL_OR_COMMENT: /\n[ \t]*/ | /#.*\n/ | /\/\/.*\n/ | /\/\*(.|\n)*?(\*\/)/ @@ -45,6 +45,7 @@ expr_term : LPAR new_line_or_comment? expression new_line_or_comment? RPAR | float_lit | int_lit | STRING_LIT + | string_with_interpolation | tuple | object | function_call @@ -59,10 +60,10 @@ expr_term : LPAR new_line_or_comment? expression new_line_or_comment? RPAR | for_tuple_expr | for_object_expr -STRING_LIT : "\"" (STRING_CHARS | INTERPOLATION)* "\"" -STRING_CHARS : /(?:(?!\${)([^"\\]|\\.))+/+ // any character except '"" unless inside a interpolation string -NESTED_INTERPOLATION : "${" /[^}]+/ "}" -INTERPOLATION : "${" (/(?:(?!\${)([^}]))+/ | NESTED_INTERPOLATION)+ "}" +STRING_LIT : "\"" STRING_CHARS? "\"" +STRING_CHARS : /(?:(?!\${)([^"\\]|\\.|\$\$))+/ // any character except '"', including escaped $$ +string_with_interpolation: "\"" (STRING_CHARS)* interpolation_maybe_nested (STRING_CHARS | interpolation_maybe_nested)* "\"" +interpolation_maybe_nested: "${" expression "}" int_lit : NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+ diff --git a/test/helpers/terraform-config-json/string_interpolations.json b/test/helpers/terraform-config-json/string_interpolations.json index 76bd5454..136920e6 100644 --- a/test/helpers/terraform-config-json/string_interpolations.json +++ b/test/helpers/terraform-config-json/string_interpolations.json @@ -1 +1 @@ -{"locals": [{"simple_interpolation": "prefix:${var.foo}-suffix", "embedded_interpolation": "(long substring without interpolation); ${module.special_constants.aws_accounts[\"aaa-${local.foo}-${local.bar}\"]}/us-west-2/key_foo", "escaped_interpolation": "prefix:$${aws:username}-suffix"}]} +{"locals": [{"simple_interpolation": "prefix:${var.foo}-suffix", "embedded_interpolation": "(long substring without interpolation); ${module.special_constants.aws_accounts[\"aaa-${local.foo}-${local.bar}\"]}/us-west-2/key_foo", "deeply_nested_interpolation": "prefix1-${\"prefix2-${\"prefix3-${local.foo}\"}\"}", "escaped_interpolation": "prefix:$${aws:username}-suffix"}]} diff --git a/test/helpers/terraform-config/string_interpolations.tf b/test/helpers/terraform-config/string_interpolations.tf index eed3be33..3b6ddea9 100644 --- a/test/helpers/terraform-config/string_interpolations.tf +++ b/test/helpers/terraform-config/string_interpolations.tf @@ -1,5 +1,6 @@ locals { simple_interpolation = "prefix:${var.foo}-suffix" embedded_interpolation = "(long substring without interpolation); ${module.special_constants.aws_accounts["aaa-${local.foo}-${local.bar}"]}/us-west-2/key_foo" + deeply_nested_interpolation = "prefix1-${"prefix2-${"prefix3-${local.foo}"}"}" escaped_interpolation = "prefix:$${aws:username}-suffix" } diff --git a/test/unit/test_builder.py b/test/unit/test_builder.py index 1dfb5b23..9c913505 100644 --- a/test/unit/test_builder.py +++ b/test/unit/test_builder.py @@ -73,6 +73,7 @@ def test_locals_embedded_interpolation_tf(self): "simple_interpolation": "prefix:${var.foo}-suffix", "embedded_interpolation": "(long substring without interpolation); " '${module.special_constants.aws_accounts["aaa-${local.foo}-${local.bar}"]}/us-west-2/key_foo', + "deeply_nested_interpolation": 'prefix1-${"prefix2-${"prefix3-${local.foo}"}"}', "escaped_interpolation": "prefix:$${aws:username}-suffix", }