Skip to content

Commit 2d9b769

Browse files
committed
cue/format: allow invalid identifiers as labels
"Manually" generated AST's may contain identifiers that are nor properly formatted. When used as a label, instead of returning an error, convert it to a string label. Change-Id: I1498636b484cfed70380620cae14fffcb54e94e5 Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2367 Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent ad3fde1 commit 2d9b769

File tree

2 files changed

+39
-4
lines changed

2 files changed

+39
-4
lines changed

cue/format/format_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,25 @@ func TestDeclLists(t *testing.T) {
388388
}
389389
}
390390

391+
func TestIncorrectIdent(t *testing.T) {
392+
testCases := []struct {
393+
ident string
394+
out string
395+
}{
396+
{"foo", "foo"},
397+
{"a.b.c", `"a.b.c"`},
398+
{"for", "for"},
399+
}
400+
for _, tc := range testCases {
401+
t.Run(tc.ident, func(t *testing.T) {
402+
b, _ := Node(&ast.Field{Label: ast.NewIdent(tc.ident), Value: ast.NewIdent("A")})
403+
if got, want := string(b), tc.out+`: A`; got != want {
404+
t.Errorf("got %q; want %q", got, want)
405+
}
406+
})
407+
}
408+
}
409+
391410
// TextX is a skeleton test that can be filled in for debugging one-off cases.
392411
// Do not remove.
393412
func TestX(t *testing.T) {

cue/format/node.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"strings"
2121

2222
"cuelang.org/go/cue/ast"
23-
"cuelang.org/go/cue/parser"
23+
"cuelang.org/go/cue/scanner"
2424
"cuelang.org/go/cue/token"
2525
)
2626

@@ -287,18 +287,34 @@ func (f *formatter) importSpec(x *ast.ImportSpec) {
287287
f.print(newline)
288288
}
289289

290+
func isValidIdent(ident string) bool {
291+
var scan scanner.Scanner
292+
scan.Init(token.NewFile("check", -1, len(ident)), []byte(ident), nil, 0)
293+
294+
_, tok, lit := scan.Scan()
295+
if tok == token.IDENT || tok.IsKeyword() {
296+
return lit == ident
297+
}
298+
return false
299+
}
300+
290301
func (f *formatter) label(l ast.Label, optional bool) {
291302
switch n := l.(type) {
292303
case *ast.Ident:
293-
f.print(n.NamePos, n)
304+
// Escape an identifier that has invalid characters. This may happen,
305+
// if the AST is not generated by the parser.
306+
if isValidIdent(n.Name) {
307+
f.print(n.NamePos, n)
308+
} else {
309+
f.print(n.NamePos, strconv.Quote(n.Name))
310+
}
294311

295312
case *ast.BasicLit:
296313
if f.cfg.simplify && n.Kind == token.STRING && len(n.Value) > 2 {
297314
s := n.Value
298315
unquoted, err := strconv.Unquote(s)
299316
if err == nil {
300-
e, _ := parser.ParseExpr("check", unquoted)
301-
if _, ok := e.(*ast.Ident); ok {
317+
if isValidIdent(unquoted) {
302318
f.print(n.ValuePos, unquoted)
303319
break
304320
}

0 commit comments

Comments
 (0)