hcl/hclsyntax/structure.go
Martin Atkins 6c4344623b Unfold the "hcl" directory up into the root
The main HCL package is more visible this way, and so it's easier than
having to pick it out from dozens of other package directories.
2019-09-09 16:08:19 -07:00

395 lines
9.9 KiB
Go

package hclsyntax
import (
"fmt"
"strings"
"github.com/hashicorp/hcl/v2"
)
// AsHCLBlock returns the block data expressed as a *hcl.Block.
func (b *Block) AsHCLBlock() *hcl.Block {
if b == nil {
return nil
}
lastHeaderRange := b.TypeRange
if len(b.LabelRanges) > 0 {
lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1]
}
return &hcl.Block{
Type: b.Type,
Labels: b.Labels,
Body: b.Body,
DefRange: hcl.RangeBetween(b.TypeRange, lastHeaderRange),
TypeRange: b.TypeRange,
LabelRanges: b.LabelRanges,
}
}
// Body is the implementation of hcl.Body for the HCL native syntax.
type Body struct {
Attributes Attributes
Blocks Blocks
// These are used with PartialContent to produce a "remaining items"
// body to return. They are nil on all bodies fresh out of the parser.
hiddenAttrs map[string]struct{}
hiddenBlocks map[string]struct{}
SrcRange hcl.Range
EndRange hcl.Range // Final token of the body, for reporting missing items
}
// Assert that *Body implements hcl.Body
var assertBodyImplBody hcl.Body = &Body{}
func (b *Body) walkChildNodes(w internalWalkFunc) {
w(b.Attributes)
w(b.Blocks)
}
func (b *Body) Range() hcl.Range {
return b.SrcRange
}
func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
content, remainHCL, diags := b.PartialContent(schema)
// No we'll see if anything actually remains, to produce errors about
// extraneous items.
remain := remainHCL.(*Body)
for name, attr := range b.Attributes {
if _, hidden := remain.hiddenAttrs[name]; !hidden {
var suggestions []string
for _, attrS := range schema.Attributes {
if _, defined := content.Attributes[attrS.Name]; defined {
continue
}
suggestions = append(suggestions, attrS.Name)
}
suggestion := nameSuggestion(name, suggestions)
if suggestion != "" {
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
} else {
// Is there a block of the same name?
for _, blockS := range schema.Blocks {
if blockS.Type == name {
suggestion = fmt.Sprintf(" Did you mean to define a block of type %q?", name)
break
}
}
}
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsupported argument",
Detail: fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion),
Subject: &attr.NameRange,
})
}
}
for _, block := range b.Blocks {
blockTy := block.Type
if _, hidden := remain.hiddenBlocks[blockTy]; !hidden {
var suggestions []string
for _, blockS := range schema.Blocks {
suggestions = append(suggestions, blockS.Type)
}
suggestion := nameSuggestion(blockTy, suggestions)
if suggestion != "" {
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
} else {
// Is there an attribute of the same name?
for _, attrS := range schema.Attributes {
if attrS.Name == blockTy {
suggestion = fmt.Sprintf(" Did you mean to define argument %q? If so, use the equals sign to assign it a value.", blockTy)
break
}
}
}
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsupported block type",
Detail: fmt.Sprintf("Blocks of type %q are not expected here.%s", blockTy, suggestion),
Subject: &block.TypeRange,
})
}
}
return content, diags
}
func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
attrs := make(hcl.Attributes)
var blocks hcl.Blocks
var diags hcl.Diagnostics
hiddenAttrs := make(map[string]struct{})
hiddenBlocks := make(map[string]struct{})
if b.hiddenAttrs != nil {
for k, v := range b.hiddenAttrs {
hiddenAttrs[k] = v
}
}
if b.hiddenBlocks != nil {
for k, v := range b.hiddenBlocks {
hiddenBlocks[k] = v
}
}
for _, attrS := range schema.Attributes {
name := attrS.Name
attr, exists := b.Attributes[name]
_, hidden := hiddenAttrs[name]
if hidden || !exists {
if attrS.Required {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing required argument",
Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
Subject: b.MissingItemRange().Ptr(),
})
}
continue
}
hiddenAttrs[name] = struct{}{}
attrs[name] = attr.AsHCLAttribute()
}
blocksWanted := make(map[string]hcl.BlockHeaderSchema)
for _, blockS := range schema.Blocks {
blocksWanted[blockS.Type] = blockS
}
for _, block := range b.Blocks {
if _, hidden := hiddenBlocks[block.Type]; hidden {
continue
}
blockS, wanted := blocksWanted[block.Type]
if !wanted {
continue
}
if len(block.Labels) > len(blockS.LabelNames) {
name := block.Type
if len(blockS.LabelNames) == 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Extraneous label for %s", name),
Detail: fmt.Sprintf(
"No labels are expected for %s blocks.", name,
),
Subject: block.LabelRanges[0].Ptr(),
Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
})
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Extraneous label for %s", name),
Detail: fmt.Sprintf(
"Only %d labels (%s) are expected for %s blocks.",
len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "), name,
),
Subject: block.LabelRanges[len(blockS.LabelNames)].Ptr(),
Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
})
}
continue
}
if len(block.Labels) < len(blockS.LabelNames) {
name := block.Type
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Missing %s for %s", blockS.LabelNames[len(block.Labels)], name),
Detail: fmt.Sprintf(
"All %s blocks must have %d labels (%s).",
name, len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "),
),
Subject: &block.OpenBraceRange,
Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
})
continue
}
blocks = append(blocks, block.AsHCLBlock())
}
// We hide blocks only after we've processed all of them, since otherwise
// we can't process more than one of the same type.
for _, blockS := range schema.Blocks {
hiddenBlocks[blockS.Type] = struct{}{}
}
remain := &Body{
Attributes: b.Attributes,
Blocks: b.Blocks,
hiddenAttrs: hiddenAttrs,
hiddenBlocks: hiddenBlocks,
SrcRange: b.SrcRange,
EndRange: b.EndRange,
}
return &hcl.BodyContent{
Attributes: attrs,
Blocks: blocks,
MissingItemRange: b.MissingItemRange(),
}, remain, diags
}
func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
attrs := make(hcl.Attributes)
var diags hcl.Diagnostics
if len(b.Blocks) > 0 {
example := b.Blocks[0]
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unexpected %q block", example.Type),
Detail: "Blocks are not allowed here.",
Subject: &example.TypeRange,
})
// we will continue processing anyway, and return the attributes
// we are able to find so that certain analyses can still be done
// in the face of errors.
}
if b.Attributes == nil {
return attrs, diags
}
for name, attr := range b.Attributes {
if _, hidden := b.hiddenAttrs[name]; hidden {
continue
}
attrs[name] = attr.AsHCLAttribute()
}
return attrs, diags
}
func (b *Body) MissingItemRange() hcl.Range {
return hcl.Range{
Filename: b.SrcRange.Filename,
Start: b.SrcRange.Start,
End: b.SrcRange.Start,
}
}
// Attributes is the collection of attribute definitions within a body.
type Attributes map[string]*Attribute
func (a Attributes) walkChildNodes(w internalWalkFunc) {
for _, attr := range a {
w(attr)
}
}
// Range returns the range of some arbitrary point within the set of
// attributes, or an invalid range if there are no attributes.
//
// This is provided only to complete the Node interface, but has no practical
// use.
func (a Attributes) Range() hcl.Range {
// An attributes doesn't really have a useful range to report, since
// it's just a grouping construct. So we'll arbitrarily take the
// range of one of the attributes, or produce an invalid range if we have
// none. In practice, there's little reason to ask for the range of
// an Attributes.
for _, attr := range a {
return attr.Range()
}
return hcl.Range{
Filename: "<unknown>",
}
}
// Attribute represents a single attribute definition within a body.
type Attribute struct {
Name string
Expr Expression
SrcRange hcl.Range
NameRange hcl.Range
EqualsRange hcl.Range
}
func (a *Attribute) walkChildNodes(w internalWalkFunc) {
w(a.Expr)
}
func (a *Attribute) Range() hcl.Range {
return a.SrcRange
}
// AsHCLAttribute returns the block data expressed as a *hcl.Attribute.
func (a *Attribute) AsHCLAttribute() *hcl.Attribute {
if a == nil {
return nil
}
return &hcl.Attribute{
Name: a.Name,
Expr: a.Expr,
Range: a.SrcRange,
NameRange: a.NameRange,
}
}
// Blocks is the list of nested blocks within a body.
type Blocks []*Block
func (bs Blocks) walkChildNodes(w internalWalkFunc) {
for _, block := range bs {
w(block)
}
}
// Range returns the range of some arbitrary point within the list of
// blocks, or an invalid range if there are no blocks.
//
// This is provided only to complete the Node interface, but has no practical
// use.
func (bs Blocks) Range() hcl.Range {
if len(bs) > 0 {
return bs[0].Range()
}
return hcl.Range{
Filename: "<unknown>",
}
}
// Block represents a nested block structure
type Block struct {
Type string
Labels []string
Body *Body
TypeRange hcl.Range
LabelRanges []hcl.Range
OpenBraceRange hcl.Range
CloseBraceRange hcl.Range
}
func (b *Block) walkChildNodes(w internalWalkFunc) {
w(b.Body)
}
func (b *Block) Range() hcl.Range {
return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange)
}
func (b *Block) DefRange() hcl.Range {
return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange)
}