1package hclsyntax 2 3import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/hcl2/hcl" 8) 9 10// AsHCLBlock returns the block data expressed as a *hcl.Block. 11func (b *Block) AsHCLBlock() *hcl.Block { 12 if b == nil { 13 return nil 14 } 15 16 lastHeaderRange := b.TypeRange 17 if len(b.LabelRanges) > 0 { 18 lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1] 19 } 20 21 return &hcl.Block{ 22 Type: b.Type, 23 Labels: b.Labels, 24 Body: b.Body, 25 26 DefRange: hcl.RangeBetween(b.TypeRange, lastHeaderRange), 27 TypeRange: b.TypeRange, 28 LabelRanges: b.LabelRanges, 29 } 30} 31 32// Body is the implementation of hcl.Body for the HCL native syntax. 33type Body struct { 34 Attributes Attributes 35 Blocks Blocks 36 37 // These are used with PartialContent to produce a "remaining items" 38 // body to return. They are nil on all bodies fresh out of the parser. 39 hiddenAttrs map[string]struct{} 40 hiddenBlocks map[string]struct{} 41 42 SrcRange hcl.Range 43 EndRange hcl.Range // Final token of the body, for reporting missing items 44} 45 46// Assert that *Body implements hcl.Body 47var assertBodyImplBody hcl.Body = &Body{} 48 49func (b *Body) walkChildNodes(w internalWalkFunc) { 50 w(b.Attributes) 51 w(b.Blocks) 52} 53 54func (b *Body) Range() hcl.Range { 55 return b.SrcRange 56} 57 58func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { 59 content, remainHCL, diags := b.PartialContent(schema) 60 61 // No we'll see if anything actually remains, to produce errors about 62 // extraneous items. 63 remain := remainHCL.(*Body) 64 65 for name, attr := range b.Attributes { 66 if _, hidden := remain.hiddenAttrs[name]; !hidden { 67 var suggestions []string 68 for _, attrS := range schema.Attributes { 69 if _, defined := content.Attributes[attrS.Name]; defined { 70 continue 71 } 72 suggestions = append(suggestions, attrS.Name) 73 } 74 suggestion := nameSuggestion(name, suggestions) 75 if suggestion != "" { 76 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) 77 } else { 78 // Is there a block of the same name? 79 for _, blockS := range schema.Blocks { 80 if blockS.Type == name { 81 suggestion = fmt.Sprintf(" Did you mean to define a block of type %q?", name) 82 break 83 } 84 } 85 } 86 87 diags = append(diags, &hcl.Diagnostic{ 88 Severity: hcl.DiagError, 89 Summary: "Unsupported argument", 90 Detail: fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion), 91 Subject: &attr.NameRange, 92 }) 93 } 94 } 95 96 for _, block := range b.Blocks { 97 blockTy := block.Type 98 if _, hidden := remain.hiddenBlocks[blockTy]; !hidden { 99 var suggestions []string 100 for _, blockS := range schema.Blocks { 101 suggestions = append(suggestions, blockS.Type) 102 } 103 suggestion := nameSuggestion(blockTy, suggestions) 104 if suggestion != "" { 105 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) 106 } else { 107 // Is there an attribute of the same name? 108 for _, attrS := range schema.Attributes { 109 if attrS.Name == blockTy { 110 suggestion = fmt.Sprintf(" Did you mean to define argument %q? If so, use the equals sign to assign it a value.", blockTy) 111 break 112 } 113 } 114 } 115 116 diags = append(diags, &hcl.Diagnostic{ 117 Severity: hcl.DiagError, 118 Summary: "Unsupported block type", 119 Detail: fmt.Sprintf("Blocks of type %q are not expected here.%s", blockTy, suggestion), 120 Subject: &block.TypeRange, 121 }) 122 } 123 } 124 125 return content, diags 126} 127 128func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { 129 attrs := make(hcl.Attributes) 130 var blocks hcl.Blocks 131 var diags hcl.Diagnostics 132 hiddenAttrs := make(map[string]struct{}) 133 hiddenBlocks := make(map[string]struct{}) 134 135 if b.hiddenAttrs != nil { 136 for k, v := range b.hiddenAttrs { 137 hiddenAttrs[k] = v 138 } 139 } 140 if b.hiddenBlocks != nil { 141 for k, v := range b.hiddenBlocks { 142 hiddenBlocks[k] = v 143 } 144 } 145 146 for _, attrS := range schema.Attributes { 147 name := attrS.Name 148 attr, exists := b.Attributes[name] 149 _, hidden := hiddenAttrs[name] 150 if hidden || !exists { 151 if attrS.Required { 152 diags = append(diags, &hcl.Diagnostic{ 153 Severity: hcl.DiagError, 154 Summary: "Missing required argument", 155 Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name), 156 Subject: b.MissingItemRange().Ptr(), 157 }) 158 } 159 continue 160 } 161 162 hiddenAttrs[name] = struct{}{} 163 attrs[name] = attr.AsHCLAttribute() 164 } 165 166 blocksWanted := make(map[string]hcl.BlockHeaderSchema) 167 for _, blockS := range schema.Blocks { 168 blocksWanted[blockS.Type] = blockS 169 } 170 171 for _, block := range b.Blocks { 172 if _, hidden := hiddenBlocks[block.Type]; hidden { 173 continue 174 } 175 blockS, wanted := blocksWanted[block.Type] 176 if !wanted { 177 continue 178 } 179 180 if len(block.Labels) > len(blockS.LabelNames) { 181 name := block.Type 182 if len(blockS.LabelNames) == 0 { 183 diags = append(diags, &hcl.Diagnostic{ 184 Severity: hcl.DiagError, 185 Summary: fmt.Sprintf("Extraneous label for %s", name), 186 Detail: fmt.Sprintf( 187 "No labels are expected for %s blocks.", name, 188 ), 189 Subject: block.LabelRanges[0].Ptr(), 190 Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(), 191 }) 192 } else { 193 diags = append(diags, &hcl.Diagnostic{ 194 Severity: hcl.DiagError, 195 Summary: fmt.Sprintf("Extraneous label for %s", name), 196 Detail: fmt.Sprintf( 197 "Only %d labels (%s) are expected for %s blocks.", 198 len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "), name, 199 ), 200 Subject: block.LabelRanges[len(blockS.LabelNames)].Ptr(), 201 Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(), 202 }) 203 } 204 continue 205 } 206 207 if len(block.Labels) < len(blockS.LabelNames) { 208 name := block.Type 209 diags = append(diags, &hcl.Diagnostic{ 210 Severity: hcl.DiagError, 211 Summary: fmt.Sprintf("Missing %s for %s", blockS.LabelNames[len(block.Labels)], name), 212 Detail: fmt.Sprintf( 213 "All %s blocks must have %d labels (%s).", 214 name, len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "), 215 ), 216 Subject: &block.OpenBraceRange, 217 Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(), 218 }) 219 continue 220 } 221 222 blocks = append(blocks, block.AsHCLBlock()) 223 } 224 225 // We hide blocks only after we've processed all of them, since otherwise 226 // we can't process more than one of the same type. 227 for _, blockS := range schema.Blocks { 228 hiddenBlocks[blockS.Type] = struct{}{} 229 } 230 231 remain := &Body{ 232 Attributes: b.Attributes, 233 Blocks: b.Blocks, 234 235 hiddenAttrs: hiddenAttrs, 236 hiddenBlocks: hiddenBlocks, 237 238 SrcRange: b.SrcRange, 239 EndRange: b.EndRange, 240 } 241 242 return &hcl.BodyContent{ 243 Attributes: attrs, 244 Blocks: blocks, 245 246 MissingItemRange: b.MissingItemRange(), 247 }, remain, diags 248} 249 250func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { 251 attrs := make(hcl.Attributes) 252 var diags hcl.Diagnostics 253 254 if len(b.Blocks) > 0 { 255 example := b.Blocks[0] 256 diags = append(diags, &hcl.Diagnostic{ 257 Severity: hcl.DiagError, 258 Summary: fmt.Sprintf("Unexpected %q block", example.Type), 259 Detail: "Blocks are not allowed here.", 260 Subject: &example.TypeRange, 261 }) 262 // we will continue processing anyway, and return the attributes 263 // we are able to find so that certain analyses can still be done 264 // in the face of errors. 265 } 266 267 if b.Attributes == nil { 268 return attrs, diags 269 } 270 271 for name, attr := range b.Attributes { 272 if _, hidden := b.hiddenAttrs[name]; hidden { 273 continue 274 } 275 attrs[name] = attr.AsHCLAttribute() 276 } 277 278 return attrs, diags 279} 280 281func (b *Body) MissingItemRange() hcl.Range { 282 return hcl.Range{ 283 Filename: b.SrcRange.Filename, 284 Start: b.SrcRange.Start, 285 End: b.SrcRange.Start, 286 } 287} 288 289// Attributes is the collection of attribute definitions within a body. 290type Attributes map[string]*Attribute 291 292func (a Attributes) walkChildNodes(w internalWalkFunc) { 293 for _, attr := range a { 294 w(attr) 295 } 296} 297 298// Range returns the range of some arbitrary point within the set of 299// attributes, or an invalid range if there are no attributes. 300// 301// This is provided only to complete the Node interface, but has no practical 302// use. 303func (a Attributes) Range() hcl.Range { 304 // An attributes doesn't really have a useful range to report, since 305 // it's just a grouping construct. So we'll arbitrarily take the 306 // range of one of the attributes, or produce an invalid range if we have 307 // none. In practice, there's little reason to ask for the range of 308 // an Attributes. 309 for _, attr := range a { 310 return attr.Range() 311 } 312 return hcl.Range{ 313 Filename: "<unknown>", 314 } 315} 316 317// Attribute represents a single attribute definition within a body. 318type Attribute struct { 319 Name string 320 Expr Expression 321 322 SrcRange hcl.Range 323 NameRange hcl.Range 324 EqualsRange hcl.Range 325} 326 327func (a *Attribute) walkChildNodes(w internalWalkFunc) { 328 w(a.Expr) 329} 330 331func (a *Attribute) Range() hcl.Range { 332 return a.SrcRange 333} 334 335// AsHCLAttribute returns the block data expressed as a *hcl.Attribute. 336func (a *Attribute) AsHCLAttribute() *hcl.Attribute { 337 if a == nil { 338 return nil 339 } 340 return &hcl.Attribute{ 341 Name: a.Name, 342 Expr: a.Expr, 343 344 Range: a.SrcRange, 345 NameRange: a.NameRange, 346 } 347} 348 349// Blocks is the list of nested blocks within a body. 350type Blocks []*Block 351 352func (bs Blocks) walkChildNodes(w internalWalkFunc) { 353 for _, block := range bs { 354 w(block) 355 } 356} 357 358// Range returns the range of some arbitrary point within the list of 359// blocks, or an invalid range if there are no blocks. 360// 361// This is provided only to complete the Node interface, but has no practical 362// use. 363func (bs Blocks) Range() hcl.Range { 364 if len(bs) > 0 { 365 return bs[0].Range() 366 } 367 return hcl.Range{ 368 Filename: "<unknown>", 369 } 370} 371 372// Block represents a nested block structure 373type Block struct { 374 Type string 375 Labels []string 376 Body *Body 377 378 TypeRange hcl.Range 379 LabelRanges []hcl.Range 380 OpenBraceRange hcl.Range 381 CloseBraceRange hcl.Range 382} 383 384func (b *Block) walkChildNodes(w internalWalkFunc) { 385 w(b.Body) 386} 387 388func (b *Block) Range() hcl.Range { 389 return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange) 390} 391 392func (b *Block) DefRange() hcl.Range { 393 return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange) 394} 395