summaryrefslogtreecommitdiff
path: root/hclsyntax/parser_test.go
diff options
context:
space:
mode:
authorMartin Atkins <mart@degeneration.co.uk>2021-11-02 17:06:26 -0700
committerMartin Atkins <mart@degeneration.co.uk>2021-11-03 14:05:33 -0700
commit81b680d1562e4ee83af40a3917d13659bb455a8d (patch)
tree3e17d681016867f4561e5360f17b80e474fe1046 /hclsyntax/parser_test.go
parente84201c45df4fce4e9dfaba9e8aaa8730d24dd25 (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.go269
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)
+ }
+ }
+ })
+ }
+}