f7764c6954
Fixes an issue where the name range may be incorrect in case there are multiple attributes and one of them is wrong. Another attribute's name range could overwrite the previous one as the attr variable is overwritten in the for loop.
298 lines
7.9 KiB
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)
|
|
}
|