1 // Copyright 2019 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package fix contains functionality for writing CUE files with legacy 16 // syntax to newer ones. 17 // 18 // Note: the transformations that are supported in this package will change 19 // over time. 20 package fix 21 22 import ( 23 "strings" 24 25 "cuelang.org/go/cue/ast" 26 "cuelang.org/go/cue/ast/astutil" 27 "cuelang.org/go/cue/token" 28 ) 29 30 type Option func(*options) 31 32 type options struct { 33 simplify bool 34 } 35 36 // Simplify enables fixes that simplify the code, but are not strictly 37 // necessary. 38 func Simplify() Option { 39 return func(o *options) { o.simplify = true } 40 } 41 42 // File applies fixes to f and returns it. It alters the original f. 43 func File(f *ast.File, o ...Option) *ast.File { 44 var options options 45 for _, f := range o { 46 f(&options) 47 } 48 49 // Rewrite integer division operations to use builtins. 50 f = astutil.Apply(f, func(c astutil.Cursor) bool { 51 n := c.Node() 52 switch x := n.(type) { 53 case *ast.BinaryExpr: 54 switch x.Op { 55 case token.IDIV, token.IMOD, token.IQUO, token.IREM: 56 ast.SetRelPos(x.X, token.NoSpace) 57 c.Replace(&ast.CallExpr{ 58 // Use the __foo version to prevent accidental shadowing. 59 Fun: ast.NewIdent("__" + x.Op.String()), 60 Args: []ast.Expr{x.X, x.Y}, 61 }) 62 } 63 } 64 return true 65 }, nil).(*ast.File) 66 67 // Rewrite block comments to regular comments. 68 ast.Walk(f, func(n ast.Node) bool { 69 switch x := n.(type) { 70 case *ast.CommentGroup: 71 comments := []*ast.Comment{} 72 for _, c := range x.List { 73 s := c.Text 74 if !strings.HasPrefix(s, "/*") || !strings.HasSuffix(s, "*/") { 75 comments = append(comments, c) 76 continue 77 } 78 if x.Position > 0 { 79 // Moving to the end doesn't work, as it still 80 // may inject at a false line break position. 81 x.Position = 0 82 x.Doc = true 83 } 84 s = strings.TrimSpace(s[2 : len(s)-2]) 85 for _, s := range strings.Split(s, "\n") { 86 for i := 0; i < 3; i++ { 87 if strings.HasPrefix(s, " ") || strings.HasPrefix(s, "*") { 88 s = s[1:] 89 } 90 } 91 comments = append(comments, &ast.Comment{Text: "// " + s}) 92 } 93 } 94 x.List = comments 95 return false 96 } 97 return true 98 }, nil) 99 100 // TODO: we are probably reintroducing slices. Disable for now. 101 // 102 // Rewrite slice expression. 103 // f = astutil.Apply(f, func(c astutil.Cursor) bool { 104 // n := c.Node() 105 // getVal := func(n ast.Expr) ast.Expr { 106 // if n == nil { 107 // return nil 108 // } 109 // if id, ok := n.(*ast.Ident); ok && id.Name == "_" { 110 // return nil 111 // } 112 // return n 113 // } 114 // switch x := n.(type) { 115 // case *ast.SliceExpr: 116 // ast.SetRelPos(x.X, token.NoRelPos) 117 118 // lo := getVal(x.Low) 119 // hi := getVal(x.High) 120 // if lo == nil { // a[:j] 121 // lo = mustParseExpr("0") 122 // astutil.CopyMeta(lo, x.Low) 123 // } 124 // if hi == nil { // a[i:] 125 // hi = ast.NewCall(ast.NewIdent("len"), x.X) 126 // astutil.CopyMeta(lo, x.High) 127 // } 128 // if pkg := c.Import("list"); pkg != nil { 129 // c.Replace(ast.NewCall(ast.NewSel(pkg, "Slice"), x.X, lo, hi)) 130 // } 131 // } 132 // return true 133 // }, nil).(*ast.File) 134 135 if options.simplify { 136 f = simplify(f) 137 } 138 139 return f 140 } 141