github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/hclext/structure.go (about)

     1  package hclext
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  )
     9  
    10  // BodyContent is the result of applying a hclext.BodySchema to a hcl.Body.
    11  // Unlike hcl.BodyContent, this does not have MissingItemRange.
    12  // This difference is because hcl.BodyContent is the result for a single HCL file,
    13  // while hclext.BodyContent is the result for a Terraform module.
    14  type BodyContent struct {
    15  	Attributes Attributes
    16  	Blocks     Blocks
    17  }
    18  
    19  // Blocks is a sequence of Block.
    20  type Blocks []*Block
    21  
    22  // Block represents a nested block within a hcl.Body.
    23  // Unlike hcl.Block, this has Body as hclext.BodyContent (struct), not hcl.Body (interface).
    24  // Since interface is hard to send over a wire protocol, it is designed to always return only the attributes based on the schema.
    25  // Instead, the hclext.BlockSchema can now be nested to extract the attributes within the nested block.
    26  type Block struct {
    27  	Type   string
    28  	Labels []string
    29  	Body   *BodyContent
    30  
    31  	DefRange    hcl.Range
    32  	TypeRange   hcl.Range
    33  	LabelRanges []hcl.Range
    34  }
    35  
    36  // Attributes is a set of attributes keyed by their names.
    37  // Please note that this is not strictly. Since hclext.BodyContent is the body from multiple files,
    38  // top-level attributes can have the same name (it is not possible to specify the same name within a block).
    39  // This exception is not considered here, as Terraform syntax does not allow top-level attributes.
    40  type Attributes map[string]*Attribute
    41  
    42  // Attribute represents an attribute from within a body.
    43  type Attribute struct {
    44  	Name string
    45  	Expr hcl.Expression
    46  
    47  	Range     hcl.Range
    48  	NameRange hcl.Range
    49  }
    50  
    51  // Content is a wrapper for hcl.Content for working with nested schemas.
    52  // Convert hclext.BodySchema to hcl.BodySchema, and convert hcl.BodyContent
    53  // to hclext.BodyContent. It processes the nested body recursively.
    54  func Content(body hcl.Body, schema *BodySchema) (*BodyContent, hcl.Diagnostics) {
    55  	if reflect.ValueOf(body).IsNil() {
    56  		return &BodyContent{}, hcl.Diagnostics{}
    57  	}
    58  	if schema == nil {
    59  		schema = &BodySchema{}
    60  	}
    61  
    62  	hclS := &hcl.BodySchema{
    63  		Attributes: make([]hcl.AttributeSchema, len(schema.Attributes)),
    64  		Blocks:     make([]hcl.BlockHeaderSchema, len(schema.Blocks)),
    65  	}
    66  	for idx, attrS := range schema.Attributes {
    67  		hclS.Attributes[idx] = hcl.AttributeSchema{Name: attrS.Name, Required: attrS.Required}
    68  	}
    69  	childS := map[string]*BodySchema{}
    70  	for idx, blockS := range schema.Blocks {
    71  		hclS.Blocks[idx] = hcl.BlockHeaderSchema{Type: blockS.Type, LabelNames: blockS.LabelNames}
    72  		childS[blockS.Type] = blockS.Body
    73  	}
    74  
    75  	content := &hcl.BodyContent{}
    76  	var diags hcl.Diagnostics
    77  	switch schema.Mode {
    78  	case SchemaDefaultMode:
    79  		content, diags = body.Content(hclS)
    80  	case SchemaJustAttributesMode:
    81  		content.Attributes, diags = body.JustAttributes()
    82  	default:
    83  		panic(fmt.Sprintf("invalid SchemaMode: %s", schema.Mode))
    84  	}
    85  
    86  	ret := &BodyContent{
    87  		Attributes: Attributes{},
    88  		Blocks:     make(Blocks, len(content.Blocks)),
    89  	}
    90  	for name, attr := range content.Attributes {
    91  		ret.Attributes[name] = &Attribute{
    92  			Name:      attr.Name,
    93  			Expr:      attr.Expr,
    94  			Range:     attr.Range,
    95  			NameRange: attr.NameRange,
    96  		}
    97  	}
    98  	for idx, block := range content.Blocks {
    99  		child, childDiags := Content(block.Body, childS[block.Type])
   100  		diags = diags.Extend(childDiags)
   101  
   102  		ret.Blocks[idx] = &Block{
   103  			Type:        block.Type,
   104  			Labels:      block.Labels,
   105  			Body:        child,
   106  			DefRange:    block.DefRange,
   107  			TypeRange:   block.TypeRange,
   108  			LabelRanges: block.LabelRanges,
   109  		}
   110  	}
   111  
   112  	return ret, diags
   113  }
   114  
   115  // PartialContent is a wrapper for hcl.PartialContent for working with nested schemas.
   116  // Convert hclext.BodySchema to hcl.BodySchema, and convert hcl.BodyContent
   117  // to hclext.BodyContent. It processes the nested body recursively.
   118  // Unlike hcl.PartialContent, it does not return the rest of the body.
   119  func PartialContent(body hcl.Body, schema *BodySchema) (*BodyContent, hcl.Diagnostics) {
   120  	if reflect.ValueOf(body).IsNil() {
   121  		return &BodyContent{}, hcl.Diagnostics{}
   122  	}
   123  	if schema == nil {
   124  		schema = &BodySchema{}
   125  	}
   126  
   127  	hclS := &hcl.BodySchema{
   128  		Attributes: make([]hcl.AttributeSchema, len(schema.Attributes)),
   129  		Blocks:     make([]hcl.BlockHeaderSchema, len(schema.Blocks)),
   130  	}
   131  	for idx, attrS := range schema.Attributes {
   132  		hclS.Attributes[idx] = hcl.AttributeSchema{Name: attrS.Name, Required: attrS.Required}
   133  	}
   134  	childS := map[string]*BodySchema{}
   135  	for idx, blockS := range schema.Blocks {
   136  		hclS.Blocks[idx] = hcl.BlockHeaderSchema{Type: blockS.Type, LabelNames: blockS.LabelNames}
   137  		childS[blockS.Type] = blockS.Body
   138  	}
   139  
   140  	content := &hcl.BodyContent{}
   141  	var diags hcl.Diagnostics
   142  	switch schema.Mode {
   143  	case SchemaDefaultMode:
   144  		content, _, diags = body.PartialContent(hclS)
   145  	case SchemaJustAttributesMode:
   146  		content.Attributes, diags = body.JustAttributes()
   147  	default:
   148  		panic(fmt.Sprintf("invalid SchemaMode: %s", schema.Mode))
   149  	}
   150  
   151  	ret := &BodyContent{
   152  		Attributes: Attributes{},
   153  		Blocks:     make(Blocks, len(content.Blocks)),
   154  	}
   155  	for name, attr := range content.Attributes {
   156  		ret.Attributes[name] = &Attribute{
   157  			Name:      attr.Name,
   158  			Expr:      attr.Expr,
   159  			Range:     attr.Range,
   160  			NameRange: attr.NameRange,
   161  		}
   162  	}
   163  	for idx, block := range content.Blocks {
   164  		child, childDiags := PartialContent(block.Body, childS[block.Type])
   165  		diags = diags.Extend(childDiags)
   166  
   167  		ret.Blocks[idx] = &Block{
   168  			Type:        block.Type,
   169  			Labels:      block.Labels,
   170  			Body:        child,
   171  			DefRange:    block.DefRange,
   172  			TypeRange:   block.TypeRange,
   173  			LabelRanges: block.LabelRanges,
   174  		}
   175  	}
   176  
   177  	return ret, diags
   178  }
   179  
   180  // IsEmpty returns whether the body content is empty
   181  func (b *BodyContent) IsEmpty() bool {
   182  	if b == nil {
   183  		return true
   184  	}
   185  	return len(b.Attributes) == 0 && len(b.Blocks) == 0
   186  }
   187  
   188  // Copy returns a new BodyContent based on the original.
   189  func (b *BodyContent) Copy() *BodyContent {
   190  	out := &BodyContent{
   191  		Attributes: Attributes{},
   192  		Blocks:     make(Blocks, len(b.Blocks)),
   193  	}
   194  
   195  	for k, v := range b.Attributes {
   196  		out.Attributes[k] = v.Copy()
   197  	}
   198  	copy(out.Blocks, b.Blocks)
   199  
   200  	return out
   201  }
   202  
   203  // WalkAttributes visits all attributes with the passed walker function.
   204  func (b *BodyContent) WalkAttributes(walker func(*Attribute) hcl.Diagnostics) hcl.Diagnostics {
   205  	var diags hcl.Diagnostics
   206  	for _, attr := range b.Attributes {
   207  		walkDiags := walker(attr)
   208  		diags = diags.Extend(walkDiags)
   209  	}
   210  	for _, b := range b.Blocks {
   211  		walkDiags := b.Body.WalkAttributes(walker)
   212  		diags = diags.Extend(walkDiags)
   213  	}
   214  	return diags
   215  }
   216  
   217  // AsNative returns self as hcl.Attributes
   218  func (as Attributes) AsNative() hcl.Attributes {
   219  	ret := hcl.Attributes{}
   220  	for name, attr := range as {
   221  		ret[name] = attr.AsNative()
   222  	}
   223  	return ret
   224  }
   225  
   226  // AsNative returns self as hcl.Attribute
   227  func (a *Attribute) AsNative() *hcl.Attribute {
   228  	return &hcl.Attribute{
   229  		Name:      a.Name,
   230  		Expr:      a.Expr,
   231  		Range:     a.Range,
   232  		NameRange: a.NameRange,
   233  	}
   234  }
   235  
   236  // Copy returns a new Attribute based on the original.
   237  // Note that expr can be a shallow copy. So strictly speaking
   238  // Copy is not a deep copy.
   239  func (a *Attribute) Copy() *Attribute {
   240  	return &Attribute{
   241  		Name:      a.Name,
   242  		Expr:      a.Expr,
   243  		Range:     a.Range,
   244  		NameRange: a.NameRange,
   245  	}
   246  }
   247  
   248  // OfType filters the receiving block sequence by block type name,
   249  // returning a new block sequence including only the blocks of the
   250  // requested type.
   251  func (els Blocks) OfType(typeName string) Blocks {
   252  	ret := make(Blocks, 0)
   253  	for _, el := range els {
   254  		if el.Type == typeName {
   255  			ret = append(ret, el)
   256  		}
   257  	}
   258  	return ret
   259  }
   260  
   261  // ByType transforms the receiving block sequence into a map from type
   262  // name to block sequences of only that type.
   263  func (els Blocks) ByType() map[string]Blocks {
   264  	ret := make(map[string]Blocks)
   265  	for _, el := range els {
   266  		ty := el.Type
   267  		if ret[ty] == nil {
   268  			ret[ty] = make(Blocks, 0, 1)
   269  		}
   270  		ret[ty] = append(ret[ty], el)
   271  	}
   272  	return ret
   273  }
   274  
   275  // Copy returns a new Block based on the original.
   276  func (b *Block) Copy() *Block {
   277  	out := &Block{
   278  		Type:        b.Type,
   279  		Labels:      make([]string, len(b.Labels)),
   280  		Body:        b.Body.Copy(),
   281  		DefRange:    b.DefRange,
   282  		TypeRange:   b.TypeRange,
   283  		LabelRanges: make([]hcl.Range, len(b.LabelRanges)),
   284  	}
   285  
   286  	copy(out.Labels, b.Labels)
   287  	copy(out.LabelRanges, b.LabelRanges)
   288  
   289  	return out
   290  }