diff options
| author | Martin Atkins <mart@degeneration.co.uk> | 2021-11-02 17:06:26 -0700 |
|---|---|---|
| committer | Martin Atkins <mart@degeneration.co.uk> | 2021-11-03 14:05:33 -0700 |
| commit | 81b680d1562e4ee83af40a3917d13659bb455a8d (patch) | |
| tree | 3e17d681016867f4561e5360f17b80e474fe1046 /hclsyntax/parser_test.go | |
| parent | e84201c45df4fce4e9dfaba9e8aaa8730d24dd25 (diff) | |
hclsyntax: Special error messages for EOF in certain contexts
For parts of the input that are delimited by start and end tokens, our
typical pattern is to keep scanning for items until we reach the end token,
or to generate an error if we encounter a token that isn't valid.
In some common examples of that we'll now treat TokenEOF as special and
report a different message about the element being unclosed, because it
seems common in practice for folks to leave off closing delimiters and
then be confused about HCL reporting a parse error at the end of the
file. Instead, we'll now report the error from the perspective of the
opening token(s) and describe that construct as "unclosed", because the
EOF range is generally less useful than any range that actually contains
some relevant characters.
This is not totally comprehensive for all cases, but covers some
situations that I've seen folks ask about, and some others that were
similar enough to those that it was easy to modify them in the same ways.
This does actually change one of the error ranges constrained by the
specification test suite, but in practice we're not actually using that
test suite to represent the "specification" for HCL, so it's better to
change the hypothetical specification to call for a better error reporting
behavior than to retain the old behavior just because we happened to
encoded in the (unfinished) test suite.
Diffstat (limited to 'hclsyntax/parser_test.go')
| -rw-r--r-- | hclsyntax/parser_test.go | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/hclsyntax/parser_test.go b/hclsyntax/parser_test.go index 8b083da..e347d39 100644 --- a/hclsyntax/parser_test.go +++ b/hclsyntax/parser_test.go @@ -2565,3 +2565,272 @@ block "valid" {} }) } } + +func TestParseConfigDiagnostics(t *testing.T) { + // This test function is a variant of TestParseConfig which tests for + // specific error messages for certain kinds of invalid input where we + // intend to produce a particular helpful error message. + + tests := map[string]struct { + input string + want hcl.Diagnostics + }{ + "unclosed multi-line block (no contents)": { + "blah {\n", + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unclosed configuration block", + Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + }, + }, + }, + }, + "unclosed multi-line block (after one argument)": { + "blah {\n a = 1\n", + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unclosed configuration block", + Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + }, + }, + }, + }, + "unclosed single-line block (no contents)": { + "blah {", + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unclosed configuration block", + Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + }, + }, + }, + }, + "unclosed single-line block (after its argument)": { + "blah { a = 1", + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unclosed configuration block", + Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + }, + Context: &hcl.Range{ // In this case we can also report a context because we detect this error in a more convenient place in the parser + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + }, + }, + }, + }, + "unclosed object constructor (before element separator)": { + `foo = { a = 1`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unterminated object constructor expression", + Detail: "There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + }, + }, + }, + "unclosed object constructor (before equals)": { + `foo = { a `, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unterminated object constructor expression", + Detail: "There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + }, + }, + }, + "unclosed tuple constructor (before element separator)": { + `foo = [ a`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unterminated tuple constructor expression", + Detail: "There is no corresponding closing bracket before the end of the file. This may be caused by incorrect bracket nesting elsewhere in this file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + }, + }, + }, + "unclosed function call": { + `foo = boop("a"`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unterminated function call", + Detail: "There is no closing parenthesis for this function call before the end of the file. This may be caused by incorrect parethesis nesting elsewhere in this file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + }, + }, + }, + "unclosed grouping parentheses": { + `foo = (1`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unbalanced parentheses", + Detail: "Expected a closing parenthesis to terminate the expression.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + }, + Context: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + }, + }, + }, + }, + "unclosed template interpolation at EOF": { + `foo = "${a`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unclosed template interpolation sequence", + Detail: "There is no closing brace for this interpolation sequence before the end of the file. This might be caused by incorrect nesting inside the given expression.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 10, Byte: 9}, + }, + }, + }, + }, + "unclosed quoted template interpolation at closing quote": { + `foo = "${a"`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unclosed template interpolation sequence", + Detail: "There is no closing brace for this interpolation sequence before the end of the quoted template. This might be caused by incorrect nesting inside the given expression.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 10, Byte: 9}, + }, + }, + }, + }, + "unclosed quoted template at literal part": { + `foo = "${a}`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Unterminated template string", + Detail: "No closing marker was found for the string.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + Context: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + }, + }, + }, + + // Some of our "unclosed" situations happen at a less convenient time + // when we only know we're waiting for an expression, so those get + // an error message with much less context. + "unclosed object constructor (before any expression)": { + `foo = {`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Missing expression", + Detail: "Expected the start of an expression, but found the end of the file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + }, + }, + }, + "unclosed tuple constructor (before any expression)": { + `foo = [`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Missing expression", + Detail: "Expected the start of an expression, but found the end of the file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + }, + }, + }, + "unclosed function call (before any argument)": { + `foo = foo(`, + hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Missing expression", + Detail: "Expected the start of an expression, but found the end of the file.", + Subject: &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + End: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + }, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Logf("\n%s", test.input) + _, diags := ParseConfig([]byte(test.input), "test.hcl", hcl.InitialPos) + + if diff := deep.Equal(diags, test.want); diff != nil { + for _, problem := range diff { + t.Errorf(problem) + } + } + }) + } +} |
