rules/resource_block.go (201 lines of code) (raw):

package rules import ( "fmt" "strings" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclwrite" ) // Block is an interface offering general APIs on resource/nested block type Block interface { // CheckBlock checks the resourceBlock/nestedBlock recursively to find the block not in order, // and invoke the emit function on that block CheckBlock() error // ToString prints the sorted block ToString() string // DefRange gets the definition range of the block DefRange() hcl.Range } // ResourceBlock is the wrapper of a resource block type ResourceBlock struct { File *hcl.File Block *hclsyntax.Block HeadMetaArgs *HeadMetaArgs Args *Args NestedBlocks *NestedBlocks TailMetaArgs *Args TailMetaNestedBlocks *NestedBlocks ParentBlockNames []string emit func(block Block) error } // CheckBlock checks the resource block and nested block recursively to find the block not in order, // and invoke the emit function on that block func (b *ResourceBlock) CheckBlock() error { if !b.CheckOrder() { return b.emit(b) } if b.NestedBlocks == nil { return nil } for _, nb := range b.NestedBlocks.Blocks { if err := nb.CheckBlock(); err != nil { return err } } return nil } // DefRange gets the definition range of the resource block func (b *ResourceBlock) DefRange() hcl.Range { return b.Block.DefRange() } // BuildResourceBlock Build the root block wrapper using hclsyntax.Block func BuildResourceBlock(block *hclsyntax.Block, file *hcl.File, emitter func(block Block) error) *ResourceBlock { b := &ResourceBlock{ File: file, Block: block, ParentBlockNames: []string{block.Type, block.Labels[0]}, emit: emitter, } b.buildArgs(block.Body.Attributes) b.buildNestedBlocks(block.Body.Blocks) return b } // CheckOrder checks whether the resourceBlock is sorted func (b *ResourceBlock) CheckOrder() bool { return b.sectionsSorted() && b.gaped() } // ToString prints the sorted resource block func (b *ResourceBlock) ToString() string { headMetaTxt := toString(b.HeadMetaArgs) argTxt := toString(b.Args) nbTxt := toString(b.NestedBlocks) tailMetaArgTxt := toString(b.TailMetaArgs) tailMetaNbTxt := toString(b.TailMetaNestedBlocks) var txts []string for _, subTxt := range []string{headMetaTxt, argTxt, nbTxt, tailMetaArgTxt, tailMetaNbTxt} { if subTxt != "" { txts = append(txts, subTxt) } } txt := strings.Join(txts, "\n\n") blockHead := string(b.Block.DefRange().SliceBytes(b.File.Bytes)) if strings.TrimSpace(txt) == "" { txt = fmt.Sprintf("%s {}", blockHead) } else { txt = fmt.Sprintf("%s {\n%s\n}", blockHead, txt) } return string(hclwrite.Format([]byte(txt))) } func (b *ResourceBlock) buildArgs(attributes hclsyntax.Attributes) { attrs := attributesByLines(attributes) for _, attr := range attrs { attrName := attr.Name arg := buildAttrArg(attr, b.File) if IsHeadMeta(attrName) { b.addHeadMetaArg(arg) continue } if IsTailMeta(attrName) { b.addTailMetaArg(arg) continue } b.addArgs(arg) } } func (b *ResourceBlock) buildNestedBlock(nestedBlock *hclsyntax.Block) *NestedBlock { nestedBlockName := nestedBlock.Type sortField := nestedBlock.Type if nestedBlock.Type == "dynamic" { nestedBlockName = nestedBlock.Labels[0] sortField = strings.Join(nestedBlock.Labels, "") } parentBlockNames := append(b.ParentBlockNames, nestedBlockName) if b.Block.Type == "dynamic" && nestedBlockName == "content" { parentBlockNames = b.ParentBlockNames } nb := &NestedBlock{ Name: nestedBlockName, SortField: sortField, Range: nestedBlock.Range(), Block: nestedBlock, ParentBlockNames: parentBlockNames, File: b.File, emit: b.emit, } nb.buildAttributes(nestedBlock.Body.Attributes) nb.buildNestedBlocks(nestedBlock.Body.Blocks) return nb } func (b *ResourceBlock) buildNestedBlocks(nestedBlocks hclsyntax.Blocks) { for _, nestedBlock := range nestedBlocks { nb := b.buildNestedBlock(nestedBlock) if IsTailMeta(nb.Name) { b.addTailMetaNestedBlock(nb) continue } b.addNestedBlock(nb) } } func (b *ResourceBlock) sectionsSorted() bool { sections := []Section{ b.HeadMetaArgs, b.Args, b.NestedBlocks, b.TailMetaArgs, b.TailMetaNestedBlocks, } lastEndLine := -1 for _, s := range sections { if !s.CheckOrder() { return false } r := s.GetRange() if r == nil { continue } if r.Start.Line <= lastEndLine { return false } lastEndLine = r.End.Line } return true } func (b *ResourceBlock) gaped() bool { ranges := []*hcl.Range{ b.HeadMetaArgs.GetRange(), b.Args.GetRange(), b.NestedBlocks.GetRange(), b.TailMetaArgs.GetRange(), b.TailMetaNestedBlocks.GetRange(), } lastEndLine := -2 for _, r := range ranges { if r == nil { continue } if r.Start.Line-lastEndLine < 2 { return false } lastEndLine = r.End.Line } return true } func (b *ResourceBlock) addHeadMetaArg(arg *Arg) { if b.HeadMetaArgs == nil { b.HeadMetaArgs = &HeadMetaArgs{} } b.HeadMetaArgs.add(arg) } func (b *ResourceBlock) addTailMetaArg(arg *Arg) { if b.TailMetaArgs == nil { b.TailMetaArgs = &Args{} } b.TailMetaArgs.add(arg) } func (b *ResourceBlock) addTailMetaNestedBlock(nb *NestedBlock) { if b.TailMetaNestedBlocks == nil { b.TailMetaNestedBlocks = &NestedBlocks{} } b.TailMetaNestedBlocks.add(nb) } func (b *ResourceBlock) addArgs(arg *Arg) { if b.Args == nil { b.Args = &Args{} } b.Args.add(arg) } func (b *ResourceBlock) addNestedBlock(nb *NestedBlock) { if b.NestedBlocks == nil { b.NestedBlocks = &NestedBlocks{} } b.NestedBlocks.add(nb) }