|
4 | 4 | from lark import Tree, Token |
5 | 5 | from hcl2.rules import tokens |
6 | 6 | from hcl2.rules.base import BlockRule |
| 7 | +from hcl2.rules.containers import ObjectElemRule |
7 | 8 | from hcl2.rules.for_expressions import ForIntroRule, ForTupleExprRule, ForObjectExprRule |
8 | 9 | from hcl2.rules.literal_rules import IdentifierRule |
9 | 10 | from hcl2.rules.strings import StringRule |
@@ -76,14 +77,20 @@ def _should_add_space_before( |
76 | 77 | or self._last_token_name |
77 | 78 | in [tokens.COLON.lark_name(), tokens.QMARK.lark_name()] |
78 | 79 | ): |
| 80 | + # COLON may already carry leading whitespace from the grammar |
| 81 | + if token_type == tokens.COLON.lark_name() and str( |
| 82 | + current_node |
| 83 | + ).startswith((" ", "\t")): |
| 84 | + return False |
79 | 85 | return True |
80 | 86 |
|
81 | | - # Space after |
| 87 | + # Space before colon in for_intro |
82 | 88 | if ( |
83 | 89 | parent_rule_name == ForIntroRule.lark_name() |
84 | 90 | and token_type == tokens.COLON.lark_name() |
85 | 91 | ): |
86 | | - |
| 92 | + if str(current_node).startswith((" ", "\t")): |
| 93 | + return False |
87 | 94 | return True |
88 | 95 |
|
89 | 96 | # Space after commas in tuples and function arguments... |
@@ -120,10 +127,28 @@ def _should_add_space_before( |
120 | 127 | return True |
121 | 128 |
|
122 | 129 | # Space after ellipsis in function arguments |
| 130 | + # ... except before newlines which provide their own whitespace |
123 | 131 | if self._last_token_name == tokens.ELLIPSIS.lark_name(): |
| 132 | + if token_type == "NL_OR_COMMENT": |
| 133 | + return False |
124 | 134 | return True |
125 | 135 |
|
126 | | - if tokens.EQ.lark_name() in [token_type, self._last_token_name]: |
| 136 | + # Space around EQ and COLON separators in attributes/object elements. |
| 137 | + # Both terminals may carry leading whitespace from the original |
| 138 | + # source (e.g. " =" for aligned attributes, " :" for object |
| 139 | + # elements). Skip the automatic space when the token already |
| 140 | + # provides it. COLON only gets space if it already has leading |
| 141 | + # whitespace (unlike EQ which always gets at least one space). |
| 142 | + if token_type == tokens.EQ.lark_name(): |
| 143 | + if str(current_node).startswith((" ", "\t")): |
| 144 | + return False |
| 145 | + return True |
| 146 | + if token_type == tokens.COLON.lark_name(): |
| 147 | + return False |
| 148 | + if self._last_token_name == tokens.EQ.lark_name(): |
| 149 | + # Don't add space before newlines which provide their own whitespace |
| 150 | + if token_type == "NL_OR_COMMENT": |
| 151 | + return False |
127 | 152 | return True |
128 | 153 |
|
129 | 154 | # Don't add space around operator tokens inside unary_op |
@@ -158,14 +183,16 @@ def _should_add_space_before( |
158 | 183 | ): |
159 | 184 | return True |
160 | 185 |
|
161 | | - # Space after colon in for expressions (before value expression, |
162 | | - # but not before newline/comment which provides its own whitespace) |
| 186 | + # Space after colon in for expressions and object elements |
| 187 | + # (before value expression, but not before newline/comment |
| 188 | + # which provides its own whitespace) |
163 | 189 | if ( |
164 | 190 | self._last_token_name == tokens.COLON.lark_name() |
165 | 191 | and parent_rule_name |
166 | 192 | in [ |
167 | 193 | ForTupleExprRule.lark_name(), |
168 | 194 | ForObjectExprRule.lark_name(), |
| 195 | + ObjectElemRule.lark_name(), |
169 | 196 | ] |
170 | 197 | and rule_name != "new_line_or_comment" |
171 | 198 | ): |
@@ -253,6 +280,14 @@ def reconstruct(self, tree: Tree, postproc=None) -> str: |
253 | 280 | if postproc: |
254 | 281 | result = postproc(result) |
255 | 282 |
|
| 283 | + # The grammar's body rule ends with an optional new_line_or_comment |
| 284 | + # which captures the final newline. The parser often produces two |
| 285 | + # NL_OR_COMMENT tokens for a single trailing newline (the statement |
| 286 | + # separator plus the EOF newline), resulting in a spurious blank line. |
| 287 | + # Strip exactly one trailing newline when there are two or more. |
| 288 | + if result.endswith("\n\n"): |
| 289 | + result = result[:-1] |
| 290 | + |
256 | 291 | # Ensure file ends with newline |
257 | 292 | if result and not result.endswith("\n"): |
258 | 293 | result += "\n" |
|
0 commit comments