hcl/hclpack/structure.go

298 lines
7.9 KiB
Go

package hclpack
import (
"fmt"
"github.com/hashicorp/hcl2/hcl"
)
// Body is an implementation of hcl.Body.
type Body struct {
Attributes map[string]Attribute
ChildBlocks []Block
MissingItemRange_ hcl.Range
}
var _ hcl.Body = (*Body)(nil)
// Content is an implementation of the method of the same name on hcl.Body.
//
// When Content is called directly on a hclpack.Body, all child block bodies
// are guaranteed to be of type *hclpack.Body, so callers can type-assert
// to obtain a child Body in order to serialize it separately if needed.
func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
return b.content(schema, nil)
}
// PartialContent is an implementation of the method of the same name on hcl.Body.
//
// The returned "remain" body may share some backing objects with the receiver,
// so neither the receiver nor the returned remain body, or any descendent
// objects within them, may be mutated after this method is used.
//
// When Content is called directly on a hclpack.Body, all child block bodies
// and the returned "remain" body are guaranteed to be of type *hclpack.Body,
// so callers can type-assert to obtain a child Body in order to serialize it
// separately if needed.
func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
remain := &Body{
MissingItemRange_: b.MissingItemRange_,
}
content, diags := b.content(schema, remain)
return content, remain, diags
}
func (b *Body) content(schema *hcl.BodySchema, remain *Body) (*hcl.BodyContent, hcl.Diagnostics) {
if b == nil {
b = &Body{} // We'll treat a nil body like an empty one, for convenience
}
var diags hcl.Diagnostics
var attrs map[string]*hcl.Attribute
var attrUsed map[string]struct{}
if len(b.Attributes) > 0 {
attrs = make(map[string]*hcl.Attribute, len(b.Attributes))
attrUsed = make(map[string]struct{}, len(b.Attributes))
}
for _, attrS := range schema.Attributes {
name := attrS.Name
attr, exists := b.Attributes[name]
if !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_,
})
}
continue
}
attrs[name] = attr.asHCLAttribute(name)
attrUsed[name] = struct{}{}
}
for name, attr := range b.Attributes {
if _, used := attrUsed[name]; used {
continue
}
if remain != nil {
remain.setAttribute(name, attr)
continue
}
var suggestions []string
for _, attrS := range schema.Attributes {
if _, defined := 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.Ptr(),
})
}
blocksWanted := make(map[string]hcl.BlockHeaderSchema)
for _, blockS := range schema.Blocks {
blocksWanted[blockS.Type] = blockS
}
var blocks []*hcl.Block
for _, block := range b.ChildBlocks {
// Redeclare block on stack so the pointer to the body is set on the
// correct block. https://github.com/hashicorp/hcl2/issues/72
block := block
blockTy := block.Type
blockS, wanted := blocksWanted[blockTy]
if !wanted {
if remain != nil {
remain.appendBlock(block)
continue
}
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?", 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,
})
continue
}
if len(block.Labels) != len(blockS.LabelNames) {
if len(blockS.LabelNames) == 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Extraneous label for %s", blockTy),
Detail: fmt.Sprintf(
"No labels are expected for %s blocks.", blockTy,
),
Subject: &block.DefRange,
Context: &block.DefRange,
})
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Wrong label count for %s", blockTy),
Detail: fmt.Sprintf(
"%s blocks expect %d label(s), but got %d.",
blockTy, len(blockS.LabelNames), len(block.Labels),
),
Subject: &block.DefRange,
Context: &block.DefRange,
})
}
continue
}
blocks = append(blocks, block.asHCLBlock())
}
return &hcl.BodyContent{
Attributes: attrs,
Blocks: blocks,
MissingItemRange: b.MissingItemRange_,
}, diags
}
// JustAttributes is an implementation of the method of the same name on hcl.Body.
func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
var diags hcl.Diagnostics
if len(b.ChildBlocks) > 0 {
for _, block := range b.ChildBlocks {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unexpected %s block", block.Type),
Detail: "Blocks are not allowed here.",
Context: &block.TypeRange,
})
}
// We'll continue processing anyway, and return any attributes we find
// so that the caller can do careful partial analysis.
}
if len(b.Attributes) == 0 {
return nil, diags
}
ret := make(hcl.Attributes, len(b.Attributes))
for n, a := range b.Attributes {
ret[n] = a.asHCLAttribute(n)
}
return ret, diags
}
// MissingItemRange is an implementation of the method of the same name on hcl.Body.
func (b *Body) MissingItemRange() hcl.Range {
return b.MissingItemRange_
}
func (b *Body) setAttribute(name string, attr Attribute) {
if b.Attributes == nil {
b.Attributes = make(map[string]Attribute)
}
b.Attributes[name] = attr
}
func (b *Body) appendBlock(block Block) {
b.ChildBlocks = append(b.ChildBlocks, block)
}
func (b *Body) addRanges(rngs map[hcl.Range]struct{}) {
rngs[b.MissingItemRange_] = struct{}{}
for _, attr := range b.Attributes {
attr.addRanges(rngs)
}
for _, block := range b.ChildBlocks {
block.addRanges(rngs)
}
}
// Block represents a nested block within a body.
type Block struct {
Type string
Labels []string
Body Body
DefRange, TypeRange hcl.Range
LabelRanges []hcl.Range
}
func (b *Block) asHCLBlock() *hcl.Block {
return &hcl.Block{
Type: b.Type,
Labels: b.Labels,
Body: &b.Body,
TypeRange: b.TypeRange,
DefRange: b.DefRange,
LabelRanges: b.LabelRanges,
}
}
func (b *Block) addRanges(rngs map[hcl.Range]struct{}) {
rngs[b.DefRange] = struct{}{}
rngs[b.TypeRange] = struct{}{}
for _, rng := range b.LabelRanges {
rngs[rng] = struct{}{}
}
b.Body.addRanges(rngs)
}
// Attribute represents an attribute definition within a body.
type Attribute struct {
Expr Expression
Range, NameRange hcl.Range
}
func (a *Attribute) asHCLAttribute(name string) *hcl.Attribute {
return &hcl.Attribute{
Name: name,
Expr: &a.Expr,
Range: a.Range,
NameRange: a.NameRange,
}
}
func (a *Attribute) addRanges(rngs map[hcl.Range]struct{}) {
rngs[a.Range] = struct{}{}
rngs[a.NameRange] = struct{}{}
a.Expr.addRanges(rngs)
}