diff --git a/go.mod b/go.mod index f9703bd..1eb0134 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hcl/v2 v2.16.2 github.com/hashicorp/terraform-registry-address v0.2.0 - github.com/terraform-linters/tflint-plugin-sdk v0.16.1 + github.com/terraform-linters/tflint-plugin-sdk v0.16.2-0.20230521152637-98b8c1069ddb github.com/zclconf/go-cty v1.13.1 ) diff --git a/go.sum b/go.sum index 3859a9c..c7c840a 100644 --- a/go.sum +++ b/go.sum @@ -407,8 +407,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/terraform-linters/tflint-plugin-sdk v0.16.1 h1:fBfLL8KzP3pkQrNp3iQxaGoKBoMo2sFYoqmhuo6yc+A= -github.com/terraform-linters/tflint-plugin-sdk v0.16.1/go.mod h1:ltxVy04PRwptL6P/Ugz2ZeTNclYapClrLn/kVFXJGzo= +github.com/terraform-linters/tflint-plugin-sdk v0.16.2-0.20230521152637-98b8c1069ddb h1:7oOZV08MmoTqSyEm1C340d5AkdgTZxkK9zcBIi58Tus= +github.com/terraform-linters/tflint-plugin-sdk v0.16.2-0.20230521152637-98b8c1069ddb/go.mod h1:ltxVy04PRwptL6P/Ugz2ZeTNclYapClrLn/kVFXJGzo= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= diff --git a/rules/terraform_comment_syntax.go b/rules/terraform_comment_syntax.go index ff162ef..ef27203 100644 --- a/rules/terraform_comment_syntax.go +++ b/rules/terraform_comment_syntax.go @@ -79,10 +79,13 @@ func (r *TerraformCommentSyntaxRule) checkComments(runner tflint.Runner, filenam } if strings.HasPrefix(string(token.Bytes), "//") { - if err := runner.EmitIssue( + if err := runner.EmitIssueWithFix( r, "Single line comments should begin with #", token.Range, + func(f *tflint.Fixer) error { + return f.ReplaceText(f.RangeTo("//", filename, token.Range.Start), "#") + }, ); err != nil { return err } diff --git a/rules/terraform_comment_syntax_test.go b/rules/terraform_comment_syntax_test.go index a120121..ded48ea 100644 --- a/rules/terraform_comment_syntax_test.go +++ b/rules/terraform_comment_syntax_test.go @@ -13,6 +13,7 @@ func Test_TerraformCommentSyntaxRule(t *testing.T) { Content string JSON bool Expected helper.Issues + Fixed string }{ { Name: "hash comment", @@ -48,6 +49,7 @@ func Test_TerraformCommentSyntaxRule(t *testing.T) { }, }, }, + Fixed: `# foo`, }, { Name: "end-of-line hash comment", @@ -82,6 +84,11 @@ variable "foo" { } helper.AssertIssues(t, tc.Expected, runner.Issues) + want := map[string]string{} + if tc.Fixed != "" { + want[filename] = tc.Fixed + } + helper.AssertChanges(t, want, runner.Changes()) }) } } diff --git a/rules/terraform_deprecated_index.go b/rules/terraform_deprecated_index.go index 1700b8e..b44903c 100644 --- a/rules/terraform_deprecated_index.go +++ b/rules/terraform_deprecated_index.go @@ -1,6 +1,7 @@ package rules import ( + "fmt" "strings" "github.com/hashicorp/hcl/v2" @@ -66,15 +67,18 @@ func (r *TerraformDeprecatedIndexRule) Check(runner tflint.Runner) error { r.checkLegacyTraversalIndex(runner, expr.Traversal, file.Bytes) case *hclsyntax.SplatExpr: if strings.HasPrefix(string(expr.MarkerRange.SliceBytes(file.Bytes)), ".") { - if err := runner.EmitIssue( + if err := runner.EmitIssueWithFix( r, "List items should be accessed using square brackets", expr.MarkerRange, + func(f *tflint.Fixer) error { + return f.ReplaceText(expr.MarkerRange, "[*]") + }, ); err != nil { return hcl.Diagnostics{ { Severity: hcl.DiagError, - Summary: "failed to call EmitIssue()", + Summary: "failed to call EmitIssueWithFix()", Detail: err.Error(), }, } @@ -92,17 +96,21 @@ func (r *TerraformDeprecatedIndexRule) Check(runner tflint.Runner) error { func (r *TerraformDeprecatedIndexRule) checkLegacyTraversalIndex(runner tflint.Runner, traversal hcl.Traversal, file []byte) hcl.Diagnostics { for _, t := range traversal { - if _, ok := t.(hcl.TraverseIndex); ok { + if tn, ok := t.(hcl.TraverseIndex); ok { if strings.HasPrefix(string(t.SourceRange().SliceBytes(file)), ".") { - if err := runner.EmitIssue( + if err := runner.EmitIssueWithFix( r, "List items should be accessed using square brackets", t.SourceRange(), + func(f *tflint.Fixer) error { + // TODO: handle non-numeric indices + return f.ReplaceText(t.SourceRange(), fmt.Sprintf("[%s]", tn.Key.AsBigFloat().Text('f', -1))) + }, ); err != nil { return hcl.Diagnostics{ { Severity: hcl.DiagError, - Summary: "failed to call EmitIssue()", + Summary: "failed to call EmitIssueWithFix()", Detail: err.Error(), }, } diff --git a/rules/terraform_deprecated_index_test.go b/rules/terraform_deprecated_index_test.go index 6a81936..178dc39 100644 --- a/rules/terraform_deprecated_index_test.go +++ b/rules/terraform_deprecated_index_test.go @@ -12,6 +12,7 @@ func Test_TerraformDeprecatedIndexRule(t *testing.T) { Name string Content string Expected helper.Issues + Fixed string }{ { Name: "deprecated dot index style", @@ -38,6 +39,12 @@ locals { }, }, }, + Fixed: ` +locals { + list = ["a"] + value = list[0] +} +`, }, { Name: "deprecated dot splat index style", @@ -64,6 +71,12 @@ locals { }, }, }, + Fixed: ` +locals { + maplist = [{a = "b"}] + values = maplist[*].a +} +`, }, { Name: "attribute access", @@ -100,14 +113,14 @@ EOF { Name: "directive: invalid", Content: ` - locals { - servers = < 0 { @@ -107,14 +108,7 @@ func (r *RuleSet) ApplyConfig(body *hclext.BodyContent) error { return nil } -// Check runs inspection for each rule by applying Runner. -func (r *RuleSet) Check(rr tflint.Runner) error { - runner := NewRunner(rr) - - for _, rule := range r.EnabledRules { - if err := rule.Check(runner); err != nil { - return fmt.Errorf("Failed to check `%s` rule: %s", rule.Name(), err) - } - } - return nil +// NewRunner injects a custom runner +func (r *RuleSet) NewRunner(runner tflint.Runner) (tflint.Runner, error) { + return NewRunner(runner), nil } diff --git a/terraform/runner.go b/terraform/runner.go index 48f1ab4..48d41c4 100644 --- a/terraform/runner.go +++ b/terraform/runner.go @@ -93,8 +93,9 @@ func (r *Runner) GetLocals() (map[string]*Local, hcl.Diagnostics) { for name, attr := range attrs { locals[name] = &Local{ - Name: attr.Name, - DefRange: attr.Range, + Name: attr.Name, + Attribute: attr, + DefRange: attr.Range, } } } diff --git a/terraform/runner_test.go b/terraform/runner_test.go index 1f630ea..e7bd206 100644 --- a/terraform/runner_test.go +++ b/terraform/runner_test.go @@ -165,6 +165,7 @@ locals { opts := []cmp.Option{ cmpopts.IgnoreFields(hcl.Pos{}, "Byte"), + cmpopts.IgnoreFields(Local{}, "Attribute"), } if diff := cmp.Diff(got, test.want, opts...); diff != "" { t.Error(diff) diff --git a/terraform/terraform.go b/terraform/terraform.go index a35ec68..c919c5a 100644 --- a/terraform/terraform.go +++ b/terraform/terraform.go @@ -59,8 +59,9 @@ func decodeModuleCall(block *hclext.Block) (*ModuleCall, hcl.Diagnostics) { // Local represents a single entry from a "locals" block. type Local struct { - Name string - DefRange hcl.Range + Name string + Attribute *hcl.Attribute + DefRange hcl.Range } // ProviderRef represents a reference to a provider like `provider = google.europe` in a resource or module.