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 }