zclsyntax: Body.Content and Body.PartialContent
This commit is contained in:
parent
f9da844479
commit
cad3e7957f
@ -2,6 +2,7 @@ package zclsyntax
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zclconf/go-zcl/zcl"
|
||||
)
|
||||
@ -51,11 +52,193 @@ func (b *Body) Range() zcl.Range {
|
||||
}
|
||||
|
||||
func (b *Body) Content(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Diagnostics) {
|
||||
panic("Body.Content not yet implemented")
|
||||
content, remainZCL, diags := b.PartialContent(schema)
|
||||
|
||||
// No we'll see if anything actually remains, to produce errors about
|
||||
// extraneous items.
|
||||
remain := remainZCL.(*Body)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Unsupported attribute",
|
||||
Detail: fmt.Sprintf("An attribute named %q is not expected here.%s", name, suggestion),
|
||||
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 {
|
||||
suggestion = fmt.Sprintf(" Did you mean to define attribute %q?", blockTy)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Unsupported block type",
|
||||
Detail: fmt.Sprintf("Blocks of type %q are not expected here.%s", blockTy, suggestion),
|
||||
Subject: &block.TypeRange,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return content, diags
|
||||
}
|
||||
|
||||
func (b *Body) PartialContent(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Body, zcl.Diagnostics) {
|
||||
panic("Body.PartialContent not yet implemented")
|
||||
attrs := make(zcl.Attributes)
|
||||
var blocks zcl.Blocks
|
||||
var diags zcl.Diagnostics
|
||||
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 {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Missing required attribute",
|
||||
Detail: fmt.Sprintf("The attribute %q is required, but no definition was found.", attrS.Name),
|
||||
Subject: b.MissingItemRange().Ptr(),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
hiddenAttrs[name] = struct{}{}
|
||||
attrs[name] = attr.AsZCLAttribute()
|
||||
}
|
||||
|
||||
blocksWanted := make(map[string]zcl.BlockHeaderSchema)
|
||||
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 {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: fmt.Sprintf("Extraneous label for %s", name),
|
||||
Detail: fmt.Sprintf(
|
||||
"No labels are expected for %s blocks.", name,
|
||||
),
|
||||
Subject: block.LabelRanges[0].Ptr(),
|
||||
Context: zcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
|
||||
})
|
||||
} else {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
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(),
|
||||
Context: zcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if len(block.Labels) < len(blockS.LabelNames) {
|
||||
name := block.Type
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
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,
|
||||
Context: zcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
blocks = append(blocks, block.AsZCLBlock())
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
return &zcl.BodyContent{
|
||||
Attributes: attrs,
|
||||
Blocks: blocks,
|
||||
}, remain, diags
|
||||
}
|
||||
|
||||
func (b *Body) JustAttributes() (zcl.Attributes, zcl.Diagnostics) {
|
||||
|
@ -10,6 +10,416 @@ import (
|
||||
"github.com/zclconf/go-zcl/zcl"
|
||||
)
|
||||
|
||||
func TestBodyContent(t *testing.T) {
|
||||
tests := []struct {
|
||||
body *Body
|
||||
schema *zcl.BodySchema
|
||||
partial bool
|
||||
want *zcl.BodyContent
|
||||
diagCount int
|
||||
}{
|
||||
{
|
||||
&Body{},
|
||||
&zcl.BodySchema{},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
0,
|
||||
},
|
||||
|
||||
// Attributes
|
||||
{
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"foo": &Attribute{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Attributes: []zcl.AttributeSchema{
|
||||
{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{
|
||||
"foo": &zcl.Attribute{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"foo": &Attribute{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
1, // attribute "foo" is not expected
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"foo": &Attribute{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{},
|
||||
true,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
0, // in partial mode, so extra "foo" is acceptable
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Attributes: Attributes{},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Attributes: []zcl.AttributeSchema{
|
||||
{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
0, // "foo" not required, so no error
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Attributes: Attributes{},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Attributes: []zcl.AttributeSchema{
|
||||
{
|
||||
Name: "foo",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
1, // "foo" is required
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"foo": &Attribute{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
1, // attribute "foo" not expected (it's defined as a block)
|
||||
},
|
||||
|
||||
// Blocks
|
||||
{
|
||||
&Body{
|
||||
Blocks: Blocks{
|
||||
&Block{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
Blocks: zcl.Blocks{
|
||||
{
|
||||
Type: "foo",
|
||||
Body: (*Body)(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Blocks: Blocks{
|
||||
&Block{
|
||||
Type: "foo",
|
||||
},
|
||||
&Block{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
Blocks: zcl.Blocks{
|
||||
{
|
||||
Type: "foo",
|
||||
Body: (*Body)(nil),
|
||||
},
|
||||
{
|
||||
Type: "foo",
|
||||
Body: (*Body)(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Blocks: Blocks{
|
||||
&Block{
|
||||
Type: "foo",
|
||||
},
|
||||
&Block{
|
||||
Type: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
Blocks: zcl.Blocks{
|
||||
{
|
||||
Type: "foo",
|
||||
Body: (*Body)(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
1, // blocks of type "bar" not expected
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Blocks: Blocks{
|
||||
&Block{
|
||||
Type: "foo",
|
||||
},
|
||||
&Block{
|
||||
Type: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
Blocks: zcl.Blocks{
|
||||
{
|
||||
Type: "foo",
|
||||
Body: (*Body)(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
0, // extra "bar" allowed because we're in partial mode
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Blocks: Blocks{
|
||||
&Block{
|
||||
Type: "foo",
|
||||
Labels: []string{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
LabelNames: []string{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
Blocks: zcl.Blocks{
|
||||
{
|
||||
Type: "foo",
|
||||
Labels: []string{"bar"},
|
||||
Body: (*Body)(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Blocks: Blocks{
|
||||
&Block{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
LabelNames: []string{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
1, // missing label "name"
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Blocks: Blocks{
|
||||
&Block{
|
||||
Type: "foo",
|
||||
Labels: []string{"bar"},
|
||||
|
||||
LabelRanges: []zcl.Range{{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
1, // no labels expected
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Blocks: Blocks{
|
||||
&Block{
|
||||
Type: "foo",
|
||||
Labels: []string{"bar", "baz"},
|
||||
|
||||
LabelRanges: []zcl.Range{{}, {}},
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
LabelNames: []string{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
1, // too many labels
|
||||
},
|
||||
{
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"foo": &Attribute{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
},
|
||||
1, // should've been a block, not an attribute
|
||||
},
|
||||
}
|
||||
|
||||
prettyConfig := &pretty.Config{
|
||||
Diffable: true,
|
||||
IncludeUnexported: true,
|
||||
PrintStringers: true,
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
||||
var got *zcl.BodyContent
|
||||
var diags zcl.Diagnostics
|
||||
if test.partial {
|
||||
got, _, diags = test.body.PartialContent(test.schema)
|
||||
} else {
|
||||
got, diags = test.body.Content(test.schema)
|
||||
}
|
||||
|
||||
if len(diags) != test.diagCount {
|
||||
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
|
||||
for _, diag := range diags {
|
||||
t.Logf(" - %s", diag.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf(
|
||||
"wrong result\ndiff: %s",
|
||||
prettyConfig.Compare(test.want, got),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBodyJustAttributes(t *testing.T) {
|
||||
tests := []struct {
|
||||
body *Body
|
||||
|
Loading…
Reference in New Issue
Block a user