zclsyntax: Body.Content and Body.PartialContent

This commit is contained in:
Martin Atkins 2017-06-03 09:04:16 -07:00
parent f9da844479
commit cad3e7957f
2 changed files with 595 additions and 2 deletions

View File

@ -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) {

View File

@ -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