rules/nested_block.go (211 lines of code) (raw):
package rules
import (
"fmt"
"math"
"sort"
"strings"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
)
// NestedBlock is a wrapper of the nested block
type NestedBlock struct {
File *hcl.File
Block *hclsyntax.Block
Name string
SortField string
Range hcl.Range
HeadMetaArgs *HeadMetaArgs
Args *Args
NestedBlocks *NestedBlocks
ParentBlockNames []string
emit func(block Block) error
}
// CheckBlock checks the nestedBlock recursively to find the block not in order,
// and invoke the emit function on that block
func (b *NestedBlock) CheckBlock() error {
if !b.CheckOrder() {
return b.emit(b)
}
if b.NestedBlocks == nil {
return nil
}
var err error
for _, nb := range b.NestedBlocks.Blocks {
if subErr := nb.CheckBlock(); subErr != nil {
err = multierror.Append(err, subErr)
}
}
return err
}
// DefRange gets the definition range of the nested block
func (b *NestedBlock) DefRange() hcl.Range {
return b.Block.DefRange()
}
// CheckOrder checks whether the nestedBlock is sorted
func (b *NestedBlock) CheckOrder() bool {
return b.checkSubSectionOrder() && b.checkGap()
}
// ToString prints the sorted block
func (b *NestedBlock) ToString() string {
headMeta := toString(b.HeadMetaArgs)
args := toString(b.Args)
nb := toString(b.NestedBlocks)
var codes []string
for _, c := range []string{headMeta, args, nb} {
if c != "" {
codes = append(codes, c)
}
}
code := strings.Join(codes, "\n\n")
blockHead := string(b.Block.DefRange().SliceBytes(b.File.Bytes))
if strings.TrimSpace(code) == "" {
code = fmt.Sprintf("%s {}", blockHead)
} else {
code = fmt.Sprintf("%s {\n%s\n}", blockHead, code)
}
return string(hclwrite.Format([]byte(code)))
}
// NestedBlocks is the collection of nestedBlocks with the same type
type NestedBlocks struct {
Blocks []*NestedBlock
Range *hcl.Range
}
// CheckOrder checks whether this type of nestedBlocks are sorted
func (b *NestedBlocks) CheckOrder() bool {
return true
}
// ToString prints this type of nestedBlocks in order
func (b *NestedBlocks) ToString() string {
if b == nil {
return ""
}
sortedBlocks := make([]*NestedBlock, len(b.Blocks))
copy(sortedBlocks, b.Blocks)
sort.Slice(sortedBlocks, func(i, j int) bool {
return sortedBlocks[i].Range.Start.Line < sortedBlocks[j].Range.Start.Line
})
var lines []string
for _, nb := range sortedBlocks {
lines = append(lines, nb.ToString())
}
return string(hclwrite.Format([]byte(strings.Join(lines, "\n"))))
}
// GetRange returns the entire range of this type of nestedBlocks
func (b *NestedBlocks) GetRange() *hcl.Range {
if b == nil {
return nil
}
return b.Range
}
func (b *NestedBlocks) add(arg *NestedBlock) {
b.Blocks = append(b.Blocks, arg)
if b.Range == nil {
b.Range = &hcl.Range{
Filename: arg.Range.Filename,
Start: hcl.Pos{Line: math.MaxInt},
End: hcl.Pos{Line: -1},
}
}
if b.Range.Start.Line > arg.Range.Start.Line {
b.Range.Start = arg.Range.Start
}
if b.Range.End.Line < arg.Range.End.Line {
b.Range.End = arg.Range.End
}
}
func (b *NestedBlock) buildAttributes(attributes hclsyntax.Attributes) {
attrs := attributesByLines(attributes)
for _, attr := range attrs {
attrName := attr.Name
arg := buildAttrArg(attr, b.File)
if IsHeadMeta(attrName) {
b.addHeadMeta(arg)
continue
}
b.addAttribute(arg)
}
}
func (b *NestedBlock) buildNestedBlocks(nestedBlock hclsyntax.Blocks) {
for _, nb := range nestedBlock {
b.buildNestedBlock(nb)
}
}
func (b *NestedBlock) buildNestedBlock(nestedBlock *hclsyntax.Block) {
var nestedBlockName, sortField string
switch nestedBlock.Type {
case "dynamic":
nestedBlockName = nestedBlock.Labels[0]
sortField = strings.Join(nestedBlock.Labels, "")
default:
nestedBlockName = nestedBlock.Type
sortField = nestedBlock.Type
}
var parentBlockNames []string
if nestedBlockName == "content" && b.Block.Type == "dynamic" {
parentBlockNames = b.ParentBlockNames
} else {
parentBlockNames = append(b.ParentBlockNames, nestedBlockName)
}
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)
b.addNestedBlock(nb)
}
func (b *NestedBlock) addHeadMeta(arg *Arg) {
if b.HeadMetaArgs == nil {
b.HeadMetaArgs = &HeadMetaArgs{}
}
b.HeadMetaArgs.add(arg)
}
func (b *NestedBlock) addAttribute(arg *Arg) {
if b.Args == nil {
b.Args = &Args{}
}
b.Args.add(arg)
}
func (b *NestedBlock) addNestedBlock(nb *NestedBlock) {
if b.NestedBlocks == nil {
b.NestedBlocks = &NestedBlocks{}
}
b.NestedBlocks.add(nb)
}
func (b *NestedBlock) checkSubSectionOrder() bool {
sections := []Section{
b.HeadMetaArgs,
b.Args,
b.NestedBlocks,
}
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 *NestedBlock) checkGap() bool {
lastEndLine := -2
ranges := []*hcl.Range{
b.HeadMetaArgs.GetRange(),
b.Args.GetRange(),
b.NestedBlocks.GetRange(),
}
for _, r := range ranges {
if r == nil {
continue
}
if r.Start.Line-lastEndLine < 2 {
return false
}
lastEndLine = r.End.Line
}
return true
}