d5b12e1c08
Fixes setting the MissingItemRange on the remaining body when a *hclpack.Body is partially decoded. Otherwise when the remaining body is decoded with missing fields, the diagnostic cannot point to where they should be set.
297 lines
7.9 KiB
Go
297 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,
|
|
})
|
|
}
|
|
|
|
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)
|
|
}
|