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 <mpvl@golang.org>
diff --git a/cue/format/format_test.go b/cue/format/format_test.go
index 6316c7b..2e5b66f 100644
--- a/cue/format/format_test.go
+++ b/cue/format/format_test.go
@@ -388,6 +388,25 @@
}
}
+func TestIncorrectIdent(t *testing.T) {
+ testCases := []struct {
+ ident string
+ out string
+ }{
+ {"foo", "foo"},
+ {"a.b.c", `"a.b.c"`},
+ {"for", "for"},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.ident, func(t *testing.T) {
+ b, _ := Node(&ast.Field{Label: ast.NewIdent(tc.ident), Value: ast.NewIdent("A")})
+ if got, want := string(b), tc.out+`: A`; got != want {
+ t.Errorf("got %q; want %q", got, want)
+ }
+ })
+ }
+}
+
// TextX is a skeleton test that can be filled in for debugging one-off cases.
// Do not remove.
func TestX(t *testing.T) {
diff --git a/cue/format/node.go b/cue/format/node.go
index 8cecc49..e373f4f 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -20,7 +20,7 @@
"strings"
"cuelang.org/go/cue/ast"
- "cuelang.org/go/cue/parser"
+ "cuelang.org/go/cue/scanner"
"cuelang.org/go/cue/token"
)
@@ -287,18 +287,34 @@
f.print(newline)
}
+func isValidIdent(ident string) bool {
+ var scan scanner.Scanner
+ scan.Init(token.NewFile("check", -1, len(ident)), []byte(ident), nil, 0)
+
+ _, tok, lit := scan.Scan()
+ if tok == token.IDENT || tok.IsKeyword() {
+ return lit == ident
+ }
+ return false
+}
+
func (f *formatter) label(l ast.Label, optional bool) {
switch n := l.(type) {
case *ast.Ident:
- f.print(n.NamePos, n)
+ // Escape an identifier that has invalid characters. This may happen,
+ // if the AST is not generated by the parser.
+ if isValidIdent(n.Name) {
+ f.print(n.NamePos, n)
+ } else {
+ f.print(n.NamePos, strconv.Quote(n.Name))
+ }
case *ast.BasicLit:
if f.cfg.simplify && n.Kind == token.STRING && len(n.Value) > 2 {
s := n.Value
unquoted, err := strconv.Unquote(s)
if err == nil {
- e, _ := parser.ParseExpr("check", unquoted)
- if _, ok := e.(*ast.Ident); ok {
+ if isValidIdent(unquoted) {
f.print(n.ValuePos, unquoted)
break
}