6c4344623b
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.
395 lines
9.9 KiB
Go
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)
|
|
}
|