2018-11-10 17:36:26 +00:00
|
|
|
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.
|
2018-11-11 00:00:53 +00:00
|
|
|
//
|
|
|
|
// 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.
|
2018-11-10 17:36:26 +00:00
|
|
|
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.
|
2018-11-11 00:00:53 +00:00
|
|
|
//
|
|
|
|
// 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.
|
2018-11-10 17:36:26 +00:00
|
|
|
func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
2019-01-10 09:43:00 +00:00
|
|
|
remain := &Body{
|
|
|
|
MissingItemRange_: b.MissingItemRange_,
|
|
|
|
}
|
2018-11-10 17:36:26 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-18 14:30:39 +00:00
|
|
|
|
2018-11-10 17:36:26 +00:00
|
|
|
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),
|
2019-04-18 14:30:39 +00:00
|
|
|
Subject: attr.NameRange.Ptr(),
|
2018-11-10 17:36:26 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
blocksWanted := make(map[string]hcl.BlockHeaderSchema)
|
|
|
|
for _, blockS := range schema.Blocks {
|
|
|
|
blocksWanted[blockS.Type] = blockS
|
|
|
|
}
|
|
|
|
|
|
|
|
var blocks []*hcl.Block
|
|
|
|
for _, block := range b.ChildBlocks {
|
2019-01-10 07:43:00 +00:00
|
|
|
// 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
|
|
|
|
|
2018-11-10 17:36:26 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-11-11 03:25:19 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-10 17:36:26 +00:00
|
|
|
// 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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-11 03:25:19 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-11-10 17:36:26 +00:00
|
|
|
// 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,
|
|
|
|
}
|
|
|
|
}
|
2018-11-11 03:25:19 +00:00
|
|
|
|
|
|
|
func (a *Attribute) addRanges(rngs map[hcl.Range]struct{}) {
|
|
|
|
rngs[a.Range] = struct{}{}
|
|
|
|
rngs[a.NameRange] = struct{}{}
|
|
|
|
a.Expr.addRanges(rngs)
|
|
|
|
}
|