Skip to content

Commit ab8ad03

Browse files
authored
Add autofix support (#93)
1 parent c5f4437 commit ab8ad03

18 files changed

+538
-195
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/hashicorp/go-version v1.6.0
1010
github.com/hashicorp/hcl/v2 v2.17.0
1111
github.com/hashicorp/terraform-registry-address v0.2.0
12-
github.com/terraform-linters/tflint-plugin-sdk v0.16.1
12+
github.com/terraform-linters/tflint-plugin-sdk v0.16.2-0.20230605170513-64de942491dc
1313
github.com/zclconf/go-cty v1.13.2
1414
)
1515

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
407407
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
408408
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
409409
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
410-
github.com/terraform-linters/tflint-plugin-sdk v0.16.1 h1:fBfLL8KzP3pkQrNp3iQxaGoKBoMo2sFYoqmhuo6yc+A=
411-
github.com/terraform-linters/tflint-plugin-sdk v0.16.1/go.mod h1:ltxVy04PRwptL6P/Ugz2ZeTNclYapClrLn/kVFXJGzo=
410+
github.com/terraform-linters/tflint-plugin-sdk v0.16.2-0.20230605170513-64de942491dc h1:z0PQWOWfWOYps2Oo7nT/v9XASazFZITnMA+oGWZzB78=
411+
github.com/terraform-linters/tflint-plugin-sdk v0.16.2-0.20230605170513-64de942491dc/go.mod h1:ltxVy04PRwptL6P/Ugz2ZeTNclYapClrLn/kVFXJGzo=
412412
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
413413
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
414414
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=

rules/terraform_comment_syntax.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,13 @@ func (r *TerraformCommentSyntaxRule) checkComments(runner tflint.Runner, filenam
7979
}
8080

8181
if strings.HasPrefix(string(token.Bytes), "//") {
82-
if err := runner.EmitIssue(
82+
if err := runner.EmitIssueWithFix(
8383
r,
8484
"Single line comments should begin with #",
8585
token.Range,
86+
func(f tflint.Fixer) error {
87+
return f.ReplaceText(f.RangeTo("//", filename, token.Range.Start), "#")
88+
},
8689
); err != nil {
8790
return err
8891
}

rules/terraform_comment_syntax_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ func Test_TerraformCommentSyntaxRule(t *testing.T) {
1313
Content string
1414
JSON bool
1515
Expected helper.Issues
16+
Fixed string
1617
}{
1718
{
1819
Name: "hash comment",
@@ -48,6 +49,7 @@ func Test_TerraformCommentSyntaxRule(t *testing.T) {
4849
},
4950
},
5051
},
52+
Fixed: `# foo`,
5153
},
5254
{
5355
Name: "end-of-line hash comment",
@@ -82,6 +84,11 @@ variable "foo" {
8284
}
8385

8486
helper.AssertIssues(t, tc.Expected, runner.Issues)
87+
want := map[string]string{}
88+
if tc.Fixed != "" {
89+
want[filename] = tc.Fixed
90+
}
91+
helper.AssertChanges(t, want, runner.Changes())
8592
})
8693
}
8794
}

rules/terraform_deprecated_index.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,18 @@ func (r *TerraformDeprecatedIndexRule) Check(runner tflint.Runner) error {
7272
r.checkLegacyTraversalIndex(runner, expr.Traversal, file.Bytes)
7373
case *hclsyntax.SplatExpr:
7474
if strings.HasPrefix(string(expr.MarkerRange.SliceBytes(file.Bytes)), ".") {
75-
if err := runner.EmitIssue(
75+
if err := runner.EmitIssueWithFix(
7676
r,
7777
"List items should be accessed using square brackets",
7878
expr.MarkerRange,
79+
func(f tflint.Fixer) error {
80+
return f.ReplaceText(expr.MarkerRange, "[*]")
81+
},
7982
); err != nil {
8083
return hcl.Diagnostics{
8184
{
8285
Severity: hcl.DiagError,
83-
Summary: "failed to call EmitIssue()",
86+
Summary: "failed to call EmitIssueWithFix()",
8487
Detail: err.Error(),
8588
},
8689
}
@@ -98,17 +101,20 @@ func (r *TerraformDeprecatedIndexRule) Check(runner tflint.Runner) error {
98101

99102
func (r *TerraformDeprecatedIndexRule) checkLegacyTraversalIndex(runner tflint.Runner, traversal hcl.Traversal, file []byte) hcl.Diagnostics {
100103
for _, t := range traversal {
101-
if _, ok := t.(hcl.TraverseIndex); ok {
104+
if tn, ok := t.(hcl.TraverseIndex); ok {
102105
if strings.HasPrefix(string(t.SourceRange().SliceBytes(file)), ".") {
103-
if err := runner.EmitIssue(
106+
if err := runner.EmitIssueWithFix(
104107
r,
105108
"List items should be accessed using square brackets",
106109
t.SourceRange(),
110+
func(f tflint.Fixer) error {
111+
return f.ReplaceText(t.SourceRange(), "[", f.ValueText(tn.Key), "]")
112+
},
107113
); err != nil {
108114
return hcl.Diagnostics{
109115
{
110116
Severity: hcl.DiagError,
111-
Summary: "failed to call EmitIssue()",
117+
Summary: "failed to call EmitIssueWithFix()",
112118
Detail: err.Error(),
113119
},
114120
}

rules/terraform_deprecated_index_test.go

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ func Test_TerraformDeprecatedIndexRule(t *testing.T) {
1313
Content string
1414
JSON bool
1515
Expected helper.Issues
16+
Fixed string
1617
}{
1718
{
1819
Name: "deprecated dot index style",
1920
Content: `
2021
locals {
21-
list = ["a"]
22+
list = ["a"]
2223
value = list.0
2324
}
2425
`,
@@ -39,13 +40,19 @@ locals {
3940
},
4041
},
4142
},
43+
Fixed: `
44+
locals {
45+
list = ["a"]
46+
value = list[0]
47+
}
48+
`,
4249
},
4350
{
4451
Name: "deprecated dot splat index style",
4552
Content: `
4653
locals {
47-
maplist = [{a = "b"}]
48-
values = maplist.*.a
54+
maplist = [{ a = "b" }]
55+
values = maplist.*.a
4956
}
5057
`,
5158
Expected: helper.Issues{
@@ -56,21 +63,27 @@ locals {
5663
Filename: "config.tf",
5764
Start: hcl.Pos{
5865
Line: 4,
59-
Column: 19,
66+
Column: 20,
6067
},
6168
End: hcl.Pos{
6269
Line: 4,
63-
Column: 21,
70+
Column: 22,
6471
},
6572
},
6673
},
6774
},
75+
Fixed: `
76+
locals {
77+
maplist = [{ a = "b" }]
78+
values = maplist[*].a
79+
}
80+
`,
6881
},
6982
{
7083
Name: "attribute access",
7184
Content: `
7285
locals {
73-
map = {a = "b"}
86+
map = { a = "b" }
7487
value = map.a
7588
}
7689
`,
@@ -90,9 +103,9 @@ locals {
90103
Content: `
91104
locals {
92105
servers = <<EOF
93-
%{ for ip in aws_instance.example[*].private_ip }
106+
%{for ip in aws_instance.example[*].private_ip}
94107
server ${ip}
95-
%{ endfor }
108+
%{endfor}
96109
EOF
97110
}
98111
`,
@@ -101,14 +114,14 @@ EOF
101114
{
102115
Name: "directive: invalid",
103116
Content: `
104-
locals {
105-
servers = <<EOF
106-
%{ for ip in aws_instance.example.*.private_ip }
107-
server ${ip}
108-
%{ endfor }
109-
EOF
110-
}
111-
`,
117+
locals {
118+
servers = <<EOF
119+
%{for ip in aws_instance.example.*.private_ip}
120+
server ${ip}
121+
%{endfor}
122+
EOF
123+
}
124+
`,
112125
Expected: helper.Issues{
113126
{
114127
Rule: NewTerraformDeprecatedIndexRule(),
@@ -117,22 +130,31 @@ EOF
117130
Filename: "config.tf",
118131
Start: hcl.Pos{
119132
Line: 4,
120-
Column: 36,
133+
Column: 33,
121134
},
122135
End: hcl.Pos{
123136
Line: 4,
124-
Column: 38,
137+
Column: 35,
125138
},
126139
},
127140
},
128141
},
142+
Fixed: `
143+
locals {
144+
servers = <<EOF
145+
%{for ip in aws_instance.example[*].private_ip}
146+
server ${ip}
147+
%{endfor}
148+
EOF
149+
}
150+
`,
129151
},
130152
{
131153
Name: "legacy splat and legacy index",
132154
Content: `
133155
locals {
134156
nested_list = [["a"]]
135-
value = nested_list.*.0
157+
value = nested_list.*.0
136158
}
137159
`,
138160
Expected: helper.Issues{
@@ -143,11 +165,11 @@ locals {
143165
Filename: "config.tf",
144166
Start: hcl.Pos{
145167
Line: 4,
146-
Column: 22,
168+
Column: 28,
147169
},
148170
End: hcl.Pos{
149171
Line: 4,
150-
Column: 24,
172+
Column: 30,
151173
},
152174
},
153175
},
@@ -158,21 +180,27 @@ locals {
158180
Filename: "config.tf",
159181
Start: hcl.Pos{
160182
Line: 4,
161-
Column: 24,
183+
Column: 30,
162184
},
163185
End: hcl.Pos{
164186
Line: 4,
165-
Column: 26,
187+
Column: 32,
166188
},
167189
},
168190
},
169191
},
192+
Fixed: `
193+
locals {
194+
nested_list = [["a"]]
195+
value = nested_list[*][0]
196+
}
197+
`,
170198
},
171199
{
172200
Name: "complex expression",
173201
Content: `
174202
locals {
175-
create_namespace = true
203+
create_namespace = true
176204
kubernetes_namespace = local.create_namespace ? join("", kubernetes_namespace.default.*.id) : var.kubernetes_namespace
177205
}
178206
`,
@@ -193,6 +221,12 @@ locals {
193221
},
194222
},
195223
},
224+
Fixed: `
225+
locals {
226+
create_namespace = true
227+
kubernetes_namespace = local.create_namespace ? join("", kubernetes_namespace.default[*].id) : var.kubernetes_namespace
228+
}
229+
`,
196230
},
197231
{
198232
Name: "json invalid",
@@ -221,6 +255,13 @@ locals {
221255
},
222256
},
223257
},
258+
Fixed: `
259+
{
260+
"locals": {
261+
"list": ["a"],
262+
"value": "${list[0]}"
263+
}
264+
}`,
224265
},
225266
{
226267
Name: "json valid",
@@ -264,6 +305,11 @@ locals {
264305
}
265306

266307
helper.AssertIssues(t, tc.Expected, runner.Issues)
308+
want := map[string]string{}
309+
if tc.Fixed != "" {
310+
want[filename] = tc.Fixed
311+
}
312+
helper.AssertChanges(t, want, runner.Changes())
267313
})
268314
}
269315
}

rules/terraform_deprecated_interpolation.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,20 +60,24 @@ func (r *TerraformDeprecatedInterpolationRule) Check(runner tflint.Runner) error
6060
}
6161

6262
func (r *TerraformDeprecatedInterpolationRule) checkForDeprecatedInterpolationsInExpr(runner tflint.Runner, expr hcl.Expression) hcl.Diagnostics {
63-
if _, ok := expr.(*hclsyntax.TemplateWrapExpr); !ok {
63+
wrapExpr, ok := expr.(*hclsyntax.TemplateWrapExpr)
64+
if !ok {
6465
return nil
6566
}
6667

67-
err := runner.EmitIssue(
68+
err := runner.EmitIssueWithFix(
6869
r,
6970
"Interpolation-only expressions are deprecated in Terraform v0.12.14",
7071
expr.Range(),
72+
func(f tflint.Fixer) error {
73+
return f.ReplaceText(expr.Range(), f.TextAt(wrapExpr.Wrapped.Range()))
74+
},
7175
)
7276
if err != nil {
7377
return hcl.Diagnostics{
7478
{
7579
Severity: hcl.DiagError,
76-
Summary: "failed to call EmitIssue()",
80+
Summary: "failed to call EmitIssueWithFix()",
7781
Detail: err.Error(),
7882
},
7983
}

0 commit comments

Comments
 (0)