Skip to content

Commit 728b2fa

Browse files
authored
Prevent autofix from producing ambiguous attribute keys (#189)
1 parent 0a16f84 commit 728b2fa

File tree

2 files changed

+127
-16
lines changed

2 files changed

+127
-16
lines changed

rules/terraform_deprecated_interpolation.go

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func (r *TerraformDeprecatedInterpolationRule) Link() string {
3939

4040
// Check emits issues on the deprecated interpolation syntax.
4141
// This logic is equivalent to the warning logic implemented in Terraform.
42-
// See https://github.com/hashicorp/terraform/pull/23348
42+
// See https://github.com/hashicorp/terraform/blob/2ce03abe480c3f40d04bd0f289762721ea280848/configs/compat_shim.go#L144-L156
4343
func (r *TerraformDeprecatedInterpolationRule) Check(runner tflint.Runner) error {
4444
path, err := runner.GetModulePath()
4545
if err != nil {
@@ -50,29 +50,76 @@ func (r *TerraformDeprecatedInterpolationRule) Check(runner tflint.Runner) error
5050
return nil
5151
}
5252

53-
diags := runner.WalkExpressions(tflint.ExprWalkFunc(func(expr hcl.Expression) hcl.Diagnostics {
54-
return r.checkForDeprecatedInterpolationsInExpr(runner, expr)
55-
}))
53+
diags := runner.WalkExpressions(&terraformDeprecatedInterpolationWalker{
54+
runner: runner,
55+
rule: r,
56+
// create some capacity so that we can deal with simple expressions
57+
// without any further allocation during our walk.
58+
contextStack: make([]terraformDeprecatedInterpolationContext, 0, 16),
59+
})
5660
if diags.HasErrors() {
5761
return diags
5862
}
5963
return nil
6064
}
6165

62-
func (r *TerraformDeprecatedInterpolationRule) checkForDeprecatedInterpolationsInExpr(runner tflint.Runner, expr hcl.Expression) hcl.Diagnostics {
63-
wrapExpr, ok := expr.(*hclsyntax.TemplateWrapExpr)
64-
if !ok {
65-
return nil
66+
type terraformDeprecatedInterpolationWalker struct {
67+
runner tflint.Runner
68+
rule *TerraformDeprecatedInterpolationRule
69+
contextStack []terraformDeprecatedInterpolationContext
70+
}
71+
72+
var _ tflint.ExprWalker = (*terraformDeprecatedInterpolationWalker)(nil)
73+
74+
type terraformDeprecatedInterpolationContext int
75+
76+
const (
77+
terraformDeprecatedInterpolationContextNormal terraformDeprecatedInterpolationContext = 0
78+
terraformDeprecatedInterpolationContextObjKey terraformDeprecatedInterpolationContext = 1
79+
)
80+
81+
func (w *terraformDeprecatedInterpolationWalker) Enter(expr hcl.Expression) hcl.Diagnostics {
82+
var err error
83+
84+
context := terraformDeprecatedInterpolationContextNormal
85+
switch expr := expr.(type) {
86+
case *hclsyntax.ObjectConsKeyExpr:
87+
context = terraformDeprecatedInterpolationContextObjKey
88+
case *hclsyntax.TemplateWrapExpr:
89+
// hclsyntax.TemplateWrapExpr is a special node type used by HCL only
90+
// for the situation where a template is just a single interpolation,
91+
// so we don't need to do anything further to distinguish that
92+
// situation. ("normal" templates are *hclsyntax.TemplateExpr.)
93+
94+
const message = "Interpolation-only expressions are deprecated in Terraform v0.12.14"
95+
switch w.currentContext() {
96+
case terraformDeprecatedInterpolationContextObjKey:
97+
// This case requires a different autofix strategy is needed
98+
// to avoid ambiguous attribute keys.
99+
err = w.runner.EmitIssueWithFix(
100+
w.rule,
101+
message,
102+
expr.Range(),
103+
func(f tflint.Fixer) error {
104+
return f.ReplaceText(expr.Range(), "(", f.TextAt(expr.Wrapped.Range()), ")")
105+
},
106+
)
107+
default:
108+
err = w.runner.EmitIssueWithFix(
109+
w.rule,
110+
message,
111+
expr.Range(),
112+
func(f tflint.Fixer) error {
113+
return f.ReplaceText(expr.Range(), f.TextAt(expr.Wrapped.Range()))
114+
},
115+
)
116+
}
66117
}
67118

68-
err := runner.EmitIssueWithFix(
69-
r,
70-
"Interpolation-only expressions are deprecated in Terraform v0.12.14",
71-
expr.Range(),
72-
func(f tflint.Fixer) error {
73-
return f.ReplaceText(expr.Range(), f.TextAt(wrapExpr.Wrapped.Range()))
74-
},
75-
)
119+
// Note the context of the current node for when we potentially visit
120+
// child nodes.
121+
w.contextStack = append(w.contextStack, context)
122+
76123
if err != nil {
77124
return hcl.Diagnostics{
78125
{
@@ -84,3 +131,15 @@ func (r *TerraformDeprecatedInterpolationRule) checkForDeprecatedInterpolationsI
84131
}
85132
return nil
86133
}
134+
135+
func (w *terraformDeprecatedInterpolationWalker) Exit(expr hcl.Expression) hcl.Diagnostics {
136+
w.contextStack = w.contextStack[:len(w.contextStack)-1]
137+
return nil
138+
}
139+
140+
func (w *terraformDeprecatedInterpolationWalker) currentContext() terraformDeprecatedInterpolationContext {
141+
if len(w.contextStack) == 0 {
142+
return terraformDeprecatedInterpolationContextNormal
143+
}
144+
return w.contextStack[len(w.contextStack)-1]
145+
}

rules/terraform_deprecated_interpolation_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,58 @@ resource "null_resource" "a" {
173173
Fixed: `
174174
resource "null_resource" "a" {
175175
triggers = var.triggers
176+
}`,
177+
},
178+
{
179+
Name: "interpolation as an object key",
180+
Content: `
181+
resource "null_resource" "a" {
182+
triggers = {
183+
"${var.triggers}" = "foo"
184+
}
185+
}`,
186+
Expected: helper.Issues{
187+
{
188+
Rule: NewTerraformDeprecatedInterpolationRule(),
189+
Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14",
190+
Range: hcl.Range{
191+
Filename: "config.tf",
192+
Start: hcl.Pos{Line: 4, Column: 5},
193+
End: hcl.Pos{Line: 4, Column: 22},
194+
},
195+
},
196+
},
197+
Fixed: `
198+
resource "null_resource" "a" {
199+
triggers = {
200+
(var.triggers) = "foo"
201+
}
202+
}`,
203+
},
204+
{
205+
Name: "interpolation in an object key",
206+
Content: `
207+
resource "null_resource" "a" {
208+
triggers = {
209+
upper("${var.triggers}") = "foo"
210+
}
211+
}`,
212+
Expected: helper.Issues{
213+
{
214+
Rule: NewTerraformDeprecatedInterpolationRule(),
215+
Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14",
216+
Range: hcl.Range{
217+
Filename: "config.tf",
218+
Start: hcl.Pos{Line: 4, Column: 11},
219+
End: hcl.Pos{Line: 4, Column: 28},
220+
},
221+
},
222+
},
223+
Fixed: `
224+
resource "null_resource" "a" {
225+
triggers = {
226+
upper(var.triggers) = "foo"
227+
}
176228
}`,
177229
},
178230
}

0 commit comments

Comments
 (0)