Skip to content

Commit 90676d4

Browse files
hclsyntax: Tailored error for "curly quotes"
It seems to be somewhat common for someone to share HCL code via a forum or a document and have the well-meaning word processor or CMS replace the straight quotes with curly quotes, which then lead to confusing errors when someone copies the result and tries to use it as valid HCL configuration. Here we add a special hint for that, giving a tailored error message instead of the generic "This character is not used within the language" error message. HCL has always had some of these special hints implemented here, and they were originally implemented with special token types to allow the parser handle them. However, we later refactored to do the check all at once inside the Lex* family of functions, prior to parsing, so it's now relatively straightforward to handle it as a special case of TokenInvalid rather than an entirely new token type. Perhaps later we'll rework the existing ones to also just use TokenInvalid, but that's a decision for another day.
1 parent a484440 commit 90676d4

2 files changed

Lines changed: 77 additions & 6 deletions

File tree

hclsyntax/token.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,12 +294,23 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
294294
Subject: &tok.Range,
295295
})
296296
case TokenInvalid:
297-
diags = append(diags, &hcl.Diagnostic{
298-
Severity: hcl.DiagError,
299-
Summary: "Invalid character",
300-
Detail: "This character is not used within the language.",
301-
Subject: &tok.Range,
302-
})
297+
chars := string(tok.Bytes)
298+
switch chars {
299+
case "“", "”":
300+
diags = append(diags, &hcl.Diagnostic{
301+
Severity: hcl.DiagError,
302+
Summary: "Invalid character",
303+
Detail: "\"Curly quotes\" are not valid here. These can sometimes be inadvertently introduced when sharing code via documents or discussion forums. It might help to replace the character with a \"straight quote\".",
304+
Subject: &tok.Range,
305+
})
306+
default:
307+
diags = append(diags, &hcl.Diagnostic{
308+
Severity: hcl.DiagError,
309+
Summary: "Invalid character",
310+
Detail: "This character is not used within the language.",
311+
Subject: &tok.Range,
312+
})
313+
}
303314
}
304315
}
305316
return diags

hclsyntax/token_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package hclsyntax
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/hcl/v2"
7+
)
8+
9+
func TestCheckInvalidTokensTest(t *testing.T) {
10+
tests := []struct {
11+
Input string
12+
WantSummary string
13+
WantDetail string
14+
}{
15+
{
16+
`block “invalid” {}`,
17+
`Invalid character`,
18+
`"Curly quotes" are not valid here. These can sometimes be inadvertently introduced when sharing code via documents or discussion forums. It might help to replace the character with a "straight quote".`,
19+
},
20+
{
21+
`block 'invalid' {}`,
22+
`Invalid character`,
23+
`Single quotes are not valid. Use double quotes (") to enclose strings.`,
24+
},
25+
{
26+
"block `invalid` {}",
27+
`Invalid character`,
28+
"The \"`\" character is not valid. To create a multi-line string, use the \"heredoc\" syntax, like \"<<EOT\".",
29+
},
30+
{
31+
`foo = a & b`,
32+
`Unsupported operator`,
33+
`Bitwise operators are not supported. Did you mean boolean AND ("&&")?`,
34+
},
35+
{
36+
`foo = a | b`,
37+
`Unsupported operator`,
38+
`Bitwise operators are not supported. Did you mean boolean OR ("||")?`,
39+
},
40+
{
41+
`foo = ~a`,
42+
`Unsupported operator`,
43+
`Bitwise operators are not supported. Did you mean boolean NOT ("!")?`,
44+
},
45+
}
46+
47+
for _, test := range tests {
48+
t.Run(test.Input, func(t *testing.T) {
49+
_, diags := LexConfig([]byte(test.Input), "", hcl.Pos{Line: 1, Column: 1})
50+
for _, diag := range diags {
51+
if diag.Severity == hcl.DiagError && diag.Summary == test.WantSummary && diag.Detail == test.WantDetail {
52+
return // success!
53+
}
54+
}
55+
// If we fall out here then we didn't find the diagnostic we were
56+
// looking for.
57+
t.Errorf("wrong errors\ngot: %s\nwant: %s; %s", diags.Error(), test.WantSummary, test.WantDetail)
58+
})
59+
}
60+
}

0 commit comments

Comments
 (0)