hcl/parser: cleaner error handling for objects with empty keys #158

The way that the contents of an object (objectList()) was parsed before
was weirdly implicit in expecting RBRACE as the ending token. This makes
that expectation explicit, which fixes a parse error that could occur
with an object that ends in an empty assign to an RBRACE.

Before this, the parser would accept this as expected behavior because
the object "ended" since the unexpected token was an RBRACE.
This commit is contained in:
Mitchell Hashimoto 2016-10-18 17:33:55 -07:00
parent 6f5bfed9a0
commit 57bf849243
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
5 changed files with 53 additions and 14 deletions

View File

@ -50,7 +50,7 @@ func (p *Parser) Parse() (*ast.File, error) {
scerr = &PosError{Pos: pos, Err: errors.New(msg)} scerr = &PosError{Pos: pos, Err: errors.New(msg)}
} }
f.Node, err = p.objectList() f.Node, err = p.objectList(false)
if scerr != nil { if scerr != nil {
return nil, scerr return nil, scerr
} }
@ -62,11 +62,23 @@ func (p *Parser) Parse() (*ast.File, error) {
return f, nil return f, nil
} }
func (p *Parser) objectList() (*ast.ObjectList, error) { // objectList parses a list of items within an object (generally k/v pairs).
// The parameter" obj" tells this whether to we are within an object (braces:
// '{', '}') or just at the top level. If we're within an object, we end
// at an RBRACE.
func (p *Parser) objectList(obj bool) (*ast.ObjectList, error) {
defer un(trace(p, "ParseObjectList")) defer un(trace(p, "ParseObjectList"))
node := &ast.ObjectList{} node := &ast.ObjectList{}
for { for {
if obj {
tok := p.scan()
p.unscan()
if tok.Type == token.RBRACE {
break
}
}
n, err := p.objectItem() n, err := p.objectItem()
if err == errEofToken { if err == errEofToken {
break // we are finished break // we are finished
@ -288,7 +300,7 @@ func (p *Parser) objectType() (*ast.ObjectType, error) {
Lbrace: p.tok.Pos, Lbrace: p.tok.Pos,
} }
l, err := p.objectList() l, err := p.objectList(true)
// if we hit RBRACE, we are good to go (means we parsed all Items), if it's // if we hit RBRACE, we are good to go (means we parsed all Items), if it's
// not a RBRACE, it's an syntax error and we just return it. // not a RBRACE, it's an syntax error and we just return it.
@ -296,9 +308,9 @@ func (p *Parser) objectType() (*ast.ObjectType, error) {
return nil, err return nil, err
} }
// If there is no error, we should be at a RBRACE to end the object // No error, scan and expect the ending to be a brace
if p.tok.Type != token.RBRACE { if tok := p.scan(); tok.Type != token.RBRACE {
return nil, fmt.Errorf("object expected closing RBRACE got: %s", p.tok.Type) return nil, fmt.Errorf("object expected closing RBRACE got: %s", tok.Type)
} }
o.List = l o.List = l

View File

@ -307,6 +307,8 @@ func TestObjectType(t *testing.T) {
} }
for _, l := range literals { for _, l := range literals {
t.Logf("Source: %s", l.src)
p := newParser([]byte(l.src)) p := newParser([]byte(l.src))
// p.enableTrace = true // p.enableTrace = true
item, err := p.objectItem() item, err := p.objectItem()
@ -494,20 +496,34 @@ func TestParse(t *testing.T) {
"object_key_without_value.hcl", "object_key_without_value.hcl",
true, true,
}, },
{
"object_key_assign_without_value.hcl",
true,
},
{
"object_key_assign_without_value2.hcl",
true,
},
{
"object_key_assign_without_value3.hcl",
true,
},
} }
const fixtureDir = "./test-fixtures" const fixtureDir = "./test-fixtures"
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.Name)) d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.Name))
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
_, err = Parse(d) v, err := Parse(d)
if (err != nil) != tc.Err { if (err != nil) != tc.Err {
t.Fatalf("Input: %s\n\nError: %s", tc.Name, err) t.Fatalf("Input: %s\n\nError: %s\n\nAST: %#v", tc.Name, err, v)
} }
})
} }
} }

View File

@ -0,0 +1,3 @@
foo {
bar =
}

View File

@ -0,0 +1,4 @@
foo {
baz = 7
bar =
}

View File

@ -0,0 +1,4 @@
foo {
bar =
baz = 7
}