cue/format: only format valid identifiers

Change-Id: Ic71bdacc4168671b34cdf61f0713b9012f0a0b38
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4722
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast/ident.go b/cue/ast/ident.go
index bbd4aea..a37c572 100644
--- a/cue/ast/ident.go
+++ b/cue/ast/ident.go
@@ -34,6 +34,9 @@
 
 // IsValidIdent reports whether str is a valid identifier.
 func IsValidIdent(ident string) bool {
+	if ident == "" {
+		return false
+	}
 	for i, r := range ident {
 		if isLetter(r) || r == '_' || r == '$' {
 			continue
diff --git a/cue/format/node.go b/cue/format/node.go
index 734a2a7..86309f4 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -59,7 +59,7 @@
 		goto unsupported
 	}
 
-	return nil
+	return s.errs
 
 unsupported:
 	return fmt.Errorf("cue/format: unsupported node type %T", node)
@@ -404,8 +404,8 @@
 	case *ast.Ident:
 		// Escape an identifier that has invalid characters. This may happen,
 		// if the AST is not generated by the parser.
-		name, err := ast.QuoteIdent(n.Name)
-		if err != nil {
+		name := n.Name
+		if !ast.IsValidIdent(name) {
 			name = strconv.Quote(n.Name)
 		}
 		f.print(n.NamePos, name)
diff --git a/cue/format/node_test.go b/cue/format/node_test.go
index dde17d9..b544f3b 100644
--- a/cue/format/node_test.go
+++ b/cue/format/node_test.go
@@ -15,6 +15,7 @@
 package format
 
 import (
+	"strings"
 	"testing"
 
 	"cuelang.org/go/cue/ast"
@@ -43,6 +44,11 @@
 		// Force a new struct.
 		out: `foo: bar :: {
 }`,
+	}, {
+		desc: "label with invalid identifier",
+		node: &ast.Field{Label: &ast.Ident{}, Value: ast.NewString("foo")},
+		// Force a new struct.
+		out: `"": "foo"`,
 	}}
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
@@ -58,3 +64,27 @@
 		})
 	}
 }
+
+func TestErrors(t *testing.T) {
+	testCases := []struct {
+		desc string
+		node ast.Node
+		err  string
+	}{{
+		desc: "empty identifier",
+		node: ast.NewIdent(""),
+		err:  "invalid identifier",
+	}}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			b, err := Node(tc.node)
+			if err == nil {
+				t.Fatalf("expected error, found %q", b)
+			}
+			got := err.Error()
+			if !strings.Contains(got, tc.err) {
+				t.Errorf("\ngot  %v;\nwant %v", got, tc.err)
+			}
+		})
+	}
+}
diff --git a/cue/format/printer.go b/cue/format/printer.go
index 5258638..c9f43a3 100644
--- a/cue/format/printer.go
+++ b/cue/format/printer.go
@@ -21,6 +21,7 @@
 	"text/tabwriter"
 
 	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/token"
 )
 
@@ -42,6 +43,8 @@
 	output      []byte
 	indent      int
 	spaceBefore bool
+
+	errs errors.Error
 }
 
 type line int
@@ -51,6 +54,10 @@
 	p.pos = token.Position{Line: 1, Column: 1}
 }
 
+func (p *printer) errf(n ast.Node, format string, args ...interface{}) {
+	p.errs = errors.Append(p.errs, errors.Newf(n.Pos(), format, args...))
+}
+
 const debug = false
 
 func (p *printer) internalError(msg ...interface{}) {
@@ -127,8 +134,9 @@
 
 	case *ast.Ident:
 		data = x.Name
-		if q, err := ast.QuoteIdent(data); err == nil {
-			data = q
+		if !ast.IsValidIdent(data) {
+			p.errf(x, "invalid identifier %q", x.Name)
+			data = "*bad identifier*"
 		}
 		impliedComma = true
 		p.lastTok = token.IDENT
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
index 51c0577..64123e4 100644
--- a/cue/format/testdata/expressions.golden
+++ b/cue/format/testdata/expressions.golden
@@ -4,6 +4,8 @@
 	a:   1  // comment
 	aaa: 22 // comment
 
+	"": 3
+
 	b: 3
 
 	c: b: a:       4
diff --git a/cue/format/testdata/expressions.input b/cue/format/testdata/expressions.input
index 16f42d4..f36c7ef 100644
--- a/cue/format/testdata/expressions.input
+++ b/cue/format/testdata/expressions.input
@@ -4,6 +4,8 @@
     a: 1 // comment
     aaa: 22 // comment
 
+    "": 3
+    
     b: 3
 
     c b a:  4
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index 3d9f5c5..e5eb5b6 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -173,6 +173,12 @@
 		`,
 		"a: {b: {c: d}}, c: a, d: a.b, e: a.b.c, \"f\": f, [X=_]: X",
 	}, {
+		"empty fields",
+		`
+		"": 3
+		`,
+		`"": 3`,
+	}, {
 		"expressions",
 		`	a: (2 + 3) * 5
 			b: (2 + 3) + 4