2017-09-11 23:40:37 +00:00
|
|
|
package hclsyntax
|
2017-05-24 15:04:54 +00:00
|
|
|
|
|
|
|
import (
|
2017-06-03 14:29:48 +00:00
|
|
|
"fmt"
|
2017-06-03 16:04:16 +00:00
|
|
|
"strings"
|
2017-06-03 14:29:48 +00:00
|
|
|
|
2019-09-09 23:08:19 +00:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
2017-05-24 15:04:54 +00:00
|
|
|
)
|
|
|
|
|
2017-09-12 01:36:56 +00:00
|
|
|
// AsHCLBlock returns the block data expressed as a *hcl.Block.
|
|
|
|
func (b *Block) AsHCLBlock() *hcl.Block {
|
2018-07-28 20:17:51 +00:00
|
|
|
if b == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-05-24 15:04:54 +00:00
|
|
|
lastHeaderRange := b.TypeRange
|
|
|
|
if len(b.LabelRanges) > 0 {
|
|
|
|
lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1]
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
return &hcl.Block{
|
2017-05-24 15:04:54 +00:00
|
|
|
Type: b.Type,
|
|
|
|
Labels: b.Labels,
|
|
|
|
Body: b.Body,
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
DefRange: hcl.RangeBetween(b.TypeRange, lastHeaderRange),
|
2017-05-24 15:04:54 +00:00
|
|
|
TypeRange: b.TypeRange,
|
|
|
|
LabelRanges: b.LabelRanges,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-27 18:42:01 +00:00
|
|
|
// Body is the implementation of hcl.Body for the HCL native syntax.
|
2017-05-24 15:04:54 +00:00
|
|
|
type Body struct {
|
|
|
|
Attributes Attributes
|
|
|
|
Blocks Blocks
|
|
|
|
|
2017-06-03 14:29:48 +00:00
|
|
|
// 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{}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
SrcRange hcl.Range
|
|
|
|
EndRange hcl.Range // Final token of the body, for reporting missing items
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
// Assert that *Body implements hcl.Body
|
|
|
|
var assertBodyImplBody hcl.Body = &Body{}
|
2017-05-24 15:04:54 +00:00
|
|
|
|
|
|
|
func (b *Body) walkChildNodes(w internalWalkFunc) {
|
2018-09-26 14:38:43 +00:00
|
|
|
w(b.Attributes)
|
|
|
|
w(b.Blocks)
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *Body) Range() hcl.Range {
|
2017-05-24 15:04:54 +00:00
|
|
|
return b.SrcRange
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
2017-09-12 01:36:56 +00:00
|
|
|
content, remainHCL, diags := b.PartialContent(schema)
|
2017-06-03 16:04:16 +00:00
|
|
|
|
|
|
|
// No we'll see if anything actually remains, to produce errors about
|
|
|
|
// extraneous items.
|
2017-09-12 01:36:56 +00:00
|
|
|
remain := remainHCL.(*Body)
|
2017-06-03 16:04:16 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2018-11-10 16:40:13 +00:00
|
|
|
Summary: "Unsupported argument",
|
|
|
|
Detail: fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion),
|
2017-06-03 16:04:16 +00:00
|
|
|
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 {
|
2018-11-10 16:40:13 +00:00
|
|
|
suggestion = fmt.Sprintf(" Did you mean to define argument %q? If so, use the equals sign to assign it a value.", blockTy)
|
2017-06-03 16:04:16 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2017-06-03 16:04:16 +00:00
|
|
|
Summary: "Unsupported block type",
|
|
|
|
Detail: fmt.Sprintf("Blocks of type %q are not expected here.%s", blockTy, suggestion),
|
|
|
|
Subject: &block.TypeRange,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return content, diags
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
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
|
2017-06-03 16:04:16 +00:00
|
|
|
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 {
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2018-11-10 16:40:13 +00:00
|
|
|
Summary: "Missing required argument",
|
|
|
|
Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
|
2017-06-03 16:04:16 +00:00
|
|
|
Subject: b.MissingItemRange().Ptr(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
hiddenAttrs[name] = struct{}{}
|
2017-09-12 01:36:56 +00:00
|
|
|
attrs[name] = attr.AsHCLAttribute()
|
2017-06-03 16:04:16 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
blocksWanted := make(map[string]hcl.BlockHeaderSchema)
|
2017-06-03 16:04:16 +00:00
|
|
|
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 {
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2017-06-03 16:04:16 +00:00
|
|
|
Summary: fmt.Sprintf("Extraneous label for %s", name),
|
|
|
|
Detail: fmt.Sprintf(
|
|
|
|
"No labels are expected for %s blocks.", name,
|
|
|
|
),
|
|
|
|
Subject: block.LabelRanges[0].Ptr(),
|
2017-09-11 23:40:37 +00:00
|
|
|
Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
|
2017-06-03 16:04:16 +00:00
|
|
|
})
|
|
|
|
} else {
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2017-06-03 16:04:16 +00:00
|
|
|
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(),
|
2017-09-11 23:40:37 +00:00
|
|
|
Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
|
2017-06-03 16:04:16 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(block.Labels) < len(blockS.LabelNames) {
|
|
|
|
name := block.Type
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2017-06-03 16:04:16 +00:00
|
|
|
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,
|
2017-09-11 23:40:37 +00:00
|
|
|
Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
|
2017-06-03 16:04:16 +00:00
|
|
|
})
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-09-12 01:36:56 +00:00
|
|
|
blocks = append(blocks, block.AsHCLBlock())
|
2017-06-03 16:04:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
return &hcl.BodyContent{
|
2017-06-03 16:04:16 +00:00
|
|
|
Attributes: attrs,
|
|
|
|
Blocks: blocks,
|
2017-06-03 23:46:49 +00:00
|
|
|
|
|
|
|
MissingItemRange: b.MissingItemRange(),
|
2017-06-03 16:04:16 +00:00
|
|
|
}, remain, diags
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
|
|
|
attrs := make(hcl.Attributes)
|
|
|
|
var diags hcl.Diagnostics
|
2017-06-03 14:29:48 +00:00
|
|
|
|
|
|
|
if len(b.Blocks) > 0 {
|
|
|
|
example := b.Blocks[0]
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2019-02-14 01:14:54 +00:00
|
|
|
Summary: fmt.Sprintf("Unexpected %q block", example.Type),
|
2017-06-03 14:29:48 +00:00
|
|
|
Detail: "Blocks are not allowed here.",
|
2019-02-14 01:14:54 +00:00
|
|
|
Subject: &example.TypeRange,
|
2017-06-03 14:29:48 +00:00
|
|
|
})
|
|
|
|
// 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
|
|
|
|
}
|
2017-09-12 01:36:56 +00:00
|
|
|
attrs[name] = attr.AsHCLAttribute()
|
2017-06-03 14:29:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return attrs, diags
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *Body) MissingItemRange() hcl.Range {
|
2019-04-02 20:08:43 +00:00
|
|
|
return hcl.Range{
|
|
|
|
Filename: b.SrcRange.Filename,
|
|
|
|
Start: b.SrcRange.Start,
|
|
|
|
End: b.SrcRange.Start,
|
|
|
|
}
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Attributes is the collection of attribute definitions within a body.
|
|
|
|
type Attributes map[string]*Attribute
|
|
|
|
|
|
|
|
func (a Attributes) walkChildNodes(w internalWalkFunc) {
|
2018-09-26 14:38:43 +00:00
|
|
|
for _, attr := range a {
|
|
|
|
w(attr)
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2017-09-11 23:40:37 +00:00
|
|
|
func (a Attributes) Range() hcl.Range {
|
2017-05-24 15:04:54 +00:00
|
|
|
// 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()
|
|
|
|
}
|
2017-09-11 23:40:37 +00:00
|
|
|
return hcl.Range{
|
2017-05-24 15:04:54 +00:00
|
|
|
Filename: "<unknown>",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attribute represents a single attribute definition within a body.
|
|
|
|
type Attribute struct {
|
|
|
|
Name string
|
|
|
|
Expr Expression
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
SrcRange hcl.Range
|
|
|
|
NameRange hcl.Range
|
|
|
|
EqualsRange hcl.Range
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Attribute) walkChildNodes(w internalWalkFunc) {
|
2018-09-26 14:38:43 +00:00
|
|
|
w(a.Expr)
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (a *Attribute) Range() hcl.Range {
|
2017-06-02 15:12:33 +00:00
|
|
|
return a.SrcRange
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-09-12 01:36:56 +00:00
|
|
|
// AsHCLAttribute returns the block data expressed as a *hcl.Attribute.
|
|
|
|
func (a *Attribute) AsHCLAttribute() *hcl.Attribute {
|
2018-07-28 20:17:51 +00:00
|
|
|
if a == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2017-09-11 23:40:37 +00:00
|
|
|
return &hcl.Attribute{
|
2017-06-03 14:29:48 +00:00
|
|
|
Name: a.Name,
|
|
|
|
Expr: a.Expr,
|
|
|
|
|
|
|
|
Range: a.SrcRange,
|
|
|
|
NameRange: a.NameRange,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-24 15:04:54 +00:00
|
|
|
// Blocks is the list of nested blocks within a body.
|
|
|
|
type Blocks []*Block
|
|
|
|
|
|
|
|
func (bs Blocks) walkChildNodes(w internalWalkFunc) {
|
2018-09-26 14:38:43 +00:00
|
|
|
for _, block := range bs {
|
|
|
|
w(block)
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2017-09-11 23:40:37 +00:00
|
|
|
func (bs Blocks) Range() hcl.Range {
|
2017-05-24 15:04:54 +00:00
|
|
|
if len(bs) > 0 {
|
|
|
|
return bs[0].Range()
|
|
|
|
}
|
2017-09-11 23:40:37 +00:00
|
|
|
return hcl.Range{
|
2017-05-24 15:04:54 +00:00
|
|
|
Filename: "<unknown>",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Block represents a nested block structure
|
|
|
|
type Block struct {
|
|
|
|
Type string
|
|
|
|
Labels []string
|
|
|
|
Body *Body
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
TypeRange hcl.Range
|
|
|
|
LabelRanges []hcl.Range
|
|
|
|
OpenBraceRange hcl.Range
|
|
|
|
CloseBraceRange hcl.Range
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Block) walkChildNodes(w internalWalkFunc) {
|
2018-09-26 14:38:43 +00:00
|
|
|
w(b.Body)
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *Block) Range() hcl.Range {
|
|
|
|
return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange)
|
2017-05-24 15:04:54 +00:00
|
|
|
}
|
2018-11-26 23:35:46 +00:00
|
|
|
|
|
|
|
func (b *Block) DefRange() hcl.Range {
|
|
|
|
return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange)
|
|
|
|
}
|