cmd/cue: make fmt rewrite deprecated cue
- rewrite string labels to identifiers
- remove block comments
Issue #87
Change-Id: I523814add1081abbd677ae74bd1556c5beb4abd4
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3190
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/fix.go b/cmd/cue/cmd/fix.go
new file mode 100644
index 0000000..a3fd8a2
--- /dev/null
+++ b/cmd/cue/cmd/fix.go
@@ -0,0 +1,147 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+ "strconv"
+ "strings"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/ast/astutil"
+ "cuelang.org/go/cue/parser"
+ "cuelang.org/go/cue/token"
+)
+
+func fix(f *ast.File) *ast.File {
+ // Rewrite block comments to regular comments.
+ ast.Walk(f, func(n ast.Node) bool {
+ switch x := n.(type) {
+ case *ast.CommentGroup:
+ comments := []*ast.Comment{}
+ for _, c := range x.List {
+ s := c.Text
+ if !strings.HasPrefix(s, "/*") || !strings.HasSuffix(s, "*/") {
+ comments = append(comments, c)
+ continue
+ }
+ if x.Position > 0 {
+ // Moving to the end doesn't work, as it still
+ // may inject at a false line break position.
+ x.Position = 0
+ x.Doc = true
+ }
+ s = strings.TrimSpace(s[2 : len(s)-2])
+ for _, s := range strings.Split(s, "\n") {
+ for i := 0; i < 3; i++ {
+ if strings.HasPrefix(s, " ") || strings.HasPrefix(s, "*") {
+ s = s[1:]
+ }
+ }
+ comments = append(comments, &ast.Comment{Text: "// " + s})
+ }
+ }
+ x.List = comments
+ return false
+ }
+ return true
+ }, nil)
+
+ // Rewrite strings fields that are referenced.
+ referred := map[ast.Node]string{}
+ ast.Walk(f, func(n ast.Node) bool {
+ if i, ok := n.(*ast.Ident); ok {
+ str, err := ast.ParseIdent(i)
+ if err != nil {
+ return false
+ }
+ referred[i.Node] = str
+ }
+ return true
+ }, nil)
+
+ f = astutil.Apply(f, func(c astutil.Cursor) bool {
+ n := c.Node()
+ switch x := n.(type) {
+ case *ast.Field:
+ m, ok := referred[x.Value]
+ if !ok {
+ break
+ }
+ b, ok := x.Label.(*ast.BasicLit)
+ if !ok || b.Kind != token.STRING {
+ break
+ }
+ str, err := strconv.Unquote(b.Value)
+ if err != nil || str != m {
+ break
+ }
+ str, err = ast.QuoteIdent(str)
+ if err != nil {
+ return false
+ }
+ x.Label = astutil.CopyMeta(ast.NewIdent(str), x.Label).(ast.Label)
+ }
+ return true
+ }, nil).(*ast.File)
+
+ // Rewrite slice expression.
+ f = astutil.Apply(f, func(c astutil.Cursor) bool {
+ n := c.Node()
+ getVal := func(n ast.Expr) ast.Expr {
+ if n == nil {
+ return nil
+ }
+ if id, ok := n.(*ast.Ident); ok && id.Name == "_" {
+ return nil
+ }
+ return n
+ }
+ switch x := n.(type) {
+ case *ast.SliceExpr:
+ ast.SetRelPos(x.X, token.NoRelPos)
+
+ lo := getVal(x.Low)
+ hi := getVal(x.High)
+ if lo == nil { // a[:j]
+ lo = mustParseExpr("0")
+ astutil.CopyMeta(lo, x.Low)
+ }
+ if hi == nil { // a[i:]
+ hi = &ast.CallExpr{
+ Fun: ast.NewIdent("len"),
+ Args: []ast.Expr{x.X},
+ }
+ astutil.CopyMeta(lo, x.High)
+ }
+ if pkg := c.Import("list"); pkg != nil {
+ c.Replace(&ast.CallExpr{
+ Fun: &ast.SelectorExpr{X: pkg, Sel: ast.NewIdent("Slice")},
+ Args: []ast.Expr{x.X, lo, hi},
+ })
+ }
+ }
+ return true
+ }, nil).(*ast.File)
+
+ return f
+}
+
+func mustParseExpr(expr string) ast.Expr {
+ ex, err := parser.ParseExpr("fix", expr)
+ if err != nil {
+ panic(err)
+ }
+ return ex
+}
diff --git a/cmd/cue/cmd/fix_test.go b/cmd/cue/cmd/fix_test.go
new file mode 100644
index 0000000..cb70740
--- /dev/null
+++ b/cmd/cue/cmd/fix_test.go
@@ -0,0 +1,175 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+ "testing"
+
+ "cuelang.org/go/cue/format"
+ "cuelang.org/go/cue/parser"
+)
+
+func TestFix(t *testing.T) {
+ testCases := []struct {
+ name string
+ in string
+ out string
+ }{{
+ name: "referenced string fields",
+ in: `package foo
+
+"foo": 3
+"foo-bar": 2
+"baz": ` + "`foo-bar`" + `
+
+a: {
+ "qux": 3
+ "qux-quux": qux
+ "qaz": ` + "`qux-quux`" + `
+}
+`,
+ out: `package foo
+
+"foo": 3
+` + "`foo-bar`" + `: 2
+"baz": ` + "`foo-bar`" + `
+
+a: {
+ qux: 3
+ ` + "`qux-quux`" + `: qux
+ "qaz": ` + "`qux-quux`" + `
+}
+`,
+ }, {
+ name: "comprehensions",
+ in: `
+package fix
+
+"\(k)": v for k, v in src
+
+"\(k)": v <-
+ for k, v in src
+
+/* foo
+ bar
+ */
+
+a: 3 + /* foo */ 5
+ `,
+ out: `package fix
+
+for k, v in src {
+ "\(k)": v
+}
+// foo
+// bar
+for k, v in src {
+ "\(k)": v
+}
+
+a:
+ // foo
+ 3 + 5
+`,
+ }, {
+ name: "comments",
+ in: `package foo
+
+a: /* b */ 3 + 5
+a: 3 /* c */ + 5
+a: 3 + /* d */ 5
+a: 3 + 5 /* e
+f */
+`,
+ out: `package foo
+
+// b
+a: 3 + 5
+a:
+ // c
+ 3 + 5
+a:
+ // d
+ 3 + 5
+// e
+// f
+a: 3 + 5
+`,
+ }, {
+ name: "slice",
+ in: `package foo
+
+// keep comment
+l[3:4] // and this one
+
+a: len(l[3:4])
+b: len(l[a:_])
+c: len(l[_:x])
+d: len(l[_:_])
+`,
+ out: `package foo
+
+import list6c6973 "list"
+
+// keep comment
+list6c6973.Slice(l, 3, 4)// and this one
+
+a: len(list6c6973.Slice(l, 3, 4))
+b: len(list6c6973.Slice(l, a, len(l)))
+c: len(list6c6973.Slice(l, 0, x))
+d: len(list6c6973.Slice(l, 0, len(l)))
+`,
+ }, {
+ name: "slice2",
+ in: `package foo
+
+import "list"
+
+a: list.Contains("foo")
+b: len(l[_:_])
+`,
+ out: `package foo
+
+import (
+ "list"
+ list6c6973 "list"
+)
+
+a: list.Contains("foo")
+b: len(list6c6973.Slice(l, 0, len(l)))
+`,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ f, err := parser.ParseFile("", tc.in, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ n := fix(f)
+ b, err := format.Node(n)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got := string(b)
+ if got != tc.out {
+ t.Errorf("got %v; want %v", got, tc.out)
+ }
+ _, err = parser.ParseFile("rewritten", got, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ })
+ }
+}
diff --git a/cmd/cue/cmd/fmt.go b/cmd/cue/cmd/fmt.go
index 93298a2..c569f0f 100644
--- a/cmd/cue/cmd/fmt.go
+++ b/cmd/cue/cmd/fmt.go
@@ -20,6 +20,7 @@
"cuelang.org/go/cue/format"
"cuelang.org/go/cue/load"
+ "cuelang.org/go/cue/parser"
"github.com/spf13/cobra"
)
@@ -53,7 +54,13 @@
opts = append(opts, format.Simplify())
}
- b, err = format.Source(b, opts...)
+ f, err := parser.ParseFile(fullpath, b, parser.ParseComments)
+ if err != nil {
+ return err
+ }
+ n := fix(f)
+
+ b, err = format.Node(n, opts...)
if err != nil {
return err
}
diff --git a/cue/ast/astutil/util.go b/cue/ast/astutil/util.go
index 2a237b3..cbd0629 100644
--- a/cue/ast/astutil/util.go
+++ b/cue/ast/astutil/util.go
@@ -24,3 +24,22 @@
}
ast.SetComments(to, from.Comments())
}
+
+// CopyPosition sets the position of one node to another.
+func CopyPosition(to, from ast.Node) {
+ if from == nil {
+ return
+ }
+ ast.SetPos(to, from.Pos())
+}
+
+// CopyMeta copies comments and position information from one node to another.
+// It returns the destination node.
+func CopyMeta(to, from ast.Node) ast.Node {
+ if from == nil {
+ return to
+ }
+ ast.SetComments(to, from.Comments())
+ ast.SetPos(to, from.Pos())
+ return to
+}
diff --git a/cue/ast/astutil/walk.go b/cue/ast/astutil/walk.go
index e115c32..6a24234 100644
--- a/cue/ast/astutil/walk.go
+++ b/cue/ast/astutil/walk.go
@@ -175,9 +175,7 @@
walkDeclList(v, n.Decls)
case *ast.Package:
- if n.Name != nil {
- walk(v, n.Name)
- }
+ // The package identifier isn't really an identifier. Skip it.
case *ast.ListComprehension:
walk(v, n.Expr)
diff --git a/cue/format/format.go b/cue/format/format.go
index f1274e1..e83a98c 100644
--- a/cue/format/format.go
+++ b/cue/format/format.go
@@ -336,6 +336,9 @@
f.Print(c)
if isEnd {
f.Print(newline)
+ if cg.Doc {
+ f.Print(nooverride)
+ }
}
}
}
diff --git a/internal/third_party/yaml/decode.go b/internal/third_party/yaml/decode.go
index 00a7e19..749374e 100644
--- a/internal/third_party/yaml/decode.go
+++ b/internal/third_party/yaml/decode.go
@@ -323,6 +323,7 @@
func (d *decoder) attachDocComments(m yaml_mark_t, pos int8, expr ast.Node) {
comments := []*ast.Comment{}
+ line := 0
for len(d.p.parser.comments) > 0 {
c := d.p.parser.comments[0]
if c.mark.index >= m.index {
@@ -333,10 +334,11 @@
Text: "//" + c.text[1:],
})
d.p.parser.comments = d.p.parser.comments[1:]
+ line = c.mark.line
}
if len(comments) > 0 {
expr.AddComment(&ast.CommentGroup{
- Doc: pos == 0,
+ Doc: pos == 0 && line+1 == m.line,
Position: pos,
List: comments,
})