rules/terraform_locals_order.go (111 lines of code) (raw):
package rules
import (
"fmt"
"github.com/terraform-linters/tflint-plugin-sdk/logger"
"sort"
"strings"
"github.com/Azure/tflint-ruleset-basic-ext/project"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
)
// TerraformLocalsOrderRule checks whether comments use the preferred syntax
type TerraformLocalsOrderRule struct {
tflint.DefaultRule
}
// NewTerraformLocalsOrderRule returns a new rule
func NewTerraformLocalsOrderRule() *TerraformLocalsOrderRule {
return &TerraformLocalsOrderRule{}
}
// Name returns the rule name
func (r *TerraformLocalsOrderRule) Name() string {
return "terraform_locals_order"
}
// Enabled returns whether the rule is enabled by default
func (r *TerraformLocalsOrderRule) Enabled() bool {
return false
}
// Severity returns the rule severity
func (r *TerraformLocalsOrderRule) Severity() tflint.Severity {
return tflint.NOTICE
}
// Link returns the rule reference link
func (r *TerraformLocalsOrderRule) Link() string {
return project.ReferenceLink(r.Name())
}
// Check checks whether single line comments is used
func (r *TerraformLocalsOrderRule) Check(runner tflint.Runner) error {
files, err := runner.GetFiles()
if err != nil {
return err
}
for _, file := range files {
if subErr := r.checkFile(runner, file); subErr != nil {
err = multierror.Append(err, subErr)
}
}
return err
}
func (r *TerraformLocalsOrderRule) checkFile(runner tflint.Runner, file *hcl.File) error {
body, ok := file.Body.(*hclsyntax.Body)
if !ok {
logger.Debug("skip terraform_locals_order check since it's not hcl file")
return nil
}
blocks := body.Blocks
var err error
for _, block := range blocks {
if block.Type != "locals" {
continue
}
if subErr := r.checkLocalsOrder(runner, block); subErr != nil {
err = multierror.Append(err, subErr)
}
}
return err
}
func (r *TerraformLocalsOrderRule) checkLocalsOrder(runner tflint.Runner, block *hclsyntax.Block) error {
attributes := r.attributesInLineOrder(block)
if r.sorted(attributes) {
return nil
}
return r.suggestedOrder(runner, block, attributes)
}
func (r *TerraformLocalsOrderRule) suggestedOrder(runner tflint.Runner, block *hclsyntax.Block, attributes []*hclsyntax.Attribute) error {
sort.Slice(attributes, func(x, y int) bool {
return attributes[x].Name < attributes[y].Name
})
file, err := runner.GetFile(block.Range().Filename)
if err != nil {
return err
}
var localsHclTxts []string
for _, a := range attributes {
localsHclTxts = append(localsHclTxts, string(a.SrcRange.SliceBytes(file.Bytes)))
}
localsHclTxt := strings.Join(localsHclTxts, "\n")
localsHclTxt = fmt.Sprintf("%s {\n%s\n}", block.Type, localsHclTxt)
formattedTxt := string(hclwrite.Format([]byte(localsHclTxt)))
return runner.EmitIssue(
r,
fmt.Sprintf("Recommended locals order:\n%s", formattedTxt),
block.DefRange(),
)
}
func (r *TerraformLocalsOrderRule) sorted(attributes []*hclsyntax.Attribute) bool {
var attrNames []string
for _, a := range attributes {
attrNames = append(attrNames, a.Name)
}
return sort.StringsAreSorted(attrNames)
}
func (r *TerraformLocalsOrderRule) attributesInLineOrder(block *hclsyntax.Block) []*hclsyntax.Attribute {
var attributes []*hclsyntax.Attribute
for _, attribute := range block.Body.Attributes {
attributes = append(attributes, attribute)
}
sort.Slice(attributes, func(x, y int) bool {
posX := attributes[x].SrcRange.Start
posY := attributes[y].SrcRange.Start
if posX.Line == posY.Line {
return posX.Column < posY.Column
}
return posX.Line < posY.Line
})
return attributes
}