hclsyntax: produce stub body when block parsing fails

The contract for our parser is that in the case of errors our result is
stil valid, though possibly incomplete, so that development tools can
still do analysis of the parts of the result we _were_ able to parse.

However, we were previously failing to meet that contract in the presence
of certain syntax errors during block parsing, where we were producing
a nil body instead of a valid empty one.

Now we'll produce an empty placeholder body if for any reason we don't
have a real one before we return from block parsing, which then allows
analysis tools to see that the containing block was present but makes
its content appear totally empty. This is always done in conjunction with
returning an error, so a calling application will not be mislead into
thinking it is a complete result even though parts are missing.
This commit is contained in:
Martin Atkins 2019-05-14 14:40:44 -07:00
parent 640445e163
commit 6a61d80ae3
2 changed files with 56 additions and 0 deletions

View File

@ -417,6 +417,17 @@ Token:
p.recoverAfterBodyItem()
}
// We must never produce a nil body, since the caller may attempt to
// do analysis of a partial result when there's an error, so we'll
// insert a placeholder if we otherwise failed to produce a valid
// body due to one of the syntax error paths above.
if body == nil && diags.HasErrors() {
body = &Body{
SrcRange: hcl.RangeBetween(oBrace.Range, cBraceRange),
EndRange: cBraceRange,
}
}
return &Block{
Type: blockType,
Labels: labels,

View File

@ -179,6 +179,51 @@ func TestParseConfig(t *testing.T) {
},
},
},
{
"block { block {} }\n",
1, // can't nest another block in the single-line block syntax
&Body{
Attributes: Attributes{},
Blocks: Blocks{
&Block{
Type: "block",
Labels: nil,
Body: &Body{
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 7, Byte: 6},
End: hcl.Pos{Line: 2, Column: 1, Byte: 19},
},
EndRange: hcl.Range{ // Parser recovery behavior leaves us after this whole construct, on the next line
Start: hcl.Pos{Line: 2, Column: 1, Byte: 19},
End: hcl.Pos{Line: 2, Column: 1, Byte: 19},
},
},
TypeRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 6, Byte: 5},
},
LabelRanges: nil,
OpenBraceRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 7, Byte: 6},
End: hcl.Pos{Line: 1, Column: 8, Byte: 7},
},
CloseBraceRange: hcl.Range{ // Parser recovery behavior leaves us after this whole construct, on the next line
Start: hcl.Pos{Line: 2, Column: 1, Byte: 19},
End: hcl.Pos{Line: 2, Column: 1, Byte: 19},
},
},
},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 2, Column: 1, Byte: 19},
},
EndRange: hcl.Range{
Start: hcl.Pos{Line: 2, Column: 1, Byte: 19},
End: hcl.Pos{Line: 2, Column: 1, Byte: 19},
},
},
},
{
"block \"foo\" {}\n",
0,