cuelang.org/go@v0.13.0/tools/fix/fix.go (about) 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 "cuelang.org/go/cue/ast" 24 "cuelang.org/go/cue/ast/astutil" 25 "cuelang.org/go/cue/token" 26 ) 27 28 type Option func(*options) 29 30 type options struct { 31 simplify bool 32 } 33 34 // Simplify enables fixes that simplify the code, but are not strictly 35 // necessary. 36 func Simplify() Option { 37 return func(o *options) { o.simplify = true } 38 } 39 40 // File applies fixes to f and returns it. It alters the original f. 41 func File(f *ast.File, o ...Option) *ast.File { 42 var options options 43 for _, f := range o { 44 f(&options) 45 } 46 47 // Make sure we use the "after" function, and not the "before", 48 // because "before" will stop recursion early which creates 49 // problems with nested expressions. 50 f = astutil.Apply(f, nil, func(c astutil.Cursor) bool { 51 n := c.Node() 52 switch n := n.(type) { 53 case *ast.BinaryExpr: 54 switch n.Op { 55 case token.IDIV, token.IMOD, token.IQUO, token.IREM: 56 // Rewrite integer division operations to use builtins. 57 ast.SetRelPos(n.X, token.NoSpace) 58 c.Replace(&ast.CallExpr{ 59 // Use the __foo version to prevent accidental shadowing. 60 Fun: ast.NewIdent("__" + n.Op.String()), 61 Args: []ast.Expr{n.X, n.Y}, 62 }) 63 64 case token.ADD, token.MUL: 65 // The fix here only works when at least one argument is a 66 // literal list. It would be better to be able to use CUE 67 // to infer type information, and then apply the fix to 68 // all places where we infer a list argument. 69 x, y := n.X, n.Y 70 _, xIsList := x.(*ast.ListLit) 71 _, yIsList := y.(*ast.ListLit) 72 _, xIsConcat := concatCallArgs(x) 73 _, yIsConcat := concatCallArgs(y) 74 75 if n.Op == token.ADD { 76 if !(xIsList || xIsConcat || yIsList || yIsConcat) { 77 break 78 } 79 // Rewrite list addition to use list.Concat 80 exprs := expandConcats(x, y) 81 ast.SetRelPos(x, token.NoSpace) 82 c.Replace(ast.NewCall( 83 ast.NewSel(&ast.Ident{ 84 Name: "list", 85 Node: ast.NewImport(nil, "list"), 86 }, "Concat"), ast.NewList(exprs...)), 87 ) 88 89 } else { 90 if !(xIsList || yIsList) { 91 break 92 } 93 // Rewrite list multiplication to use list.Repeat 94 if !xIsList { 95 x, y = y, x 96 } 97 ast.SetRelPos(x, token.NoSpace) 98 c.Replace(ast.NewCall( 99 ast.NewSel(&ast.Ident{ 100 Name: "list", 101 Node: ast.NewImport(nil, "list"), 102 }, "Repeat"), x, y), 103 ) 104 } 105 } 106 } 107 return true 108 }).(*ast.File) 109 110 if options.simplify { 111 f = simplify(f) 112 } 113 114 err := astutil.Sanitize(f) 115 // TODO: this File method is public, and its signature was fixed 116 // before we started calling Sanitize. Ideally, we want to return 117 // this error, but that would require deprecating this File method, 118 // and creating a new one, which might happen in due course if we 119 // also discover that we need to be a bit more flexible than just 120 // accepting a File. 121 if err != nil { 122 panic(err) 123 } 124 return f 125 } 126 127 func expandConcats(exprs ...ast.Expr) (result []ast.Expr) { 128 for _, expr := range exprs { 129 list, ok := concatCallArgs(expr) 130 if ok { 131 result = append(result, expandConcats(list.Elts...)...) 132 } else { 133 result = append(result, expr) 134 } 135 } 136 return result 137 } 138 139 func concatCallArgs(expr ast.Expr) (*ast.ListLit, bool) { 140 call, ok := expr.(*ast.CallExpr) 141 if !ok { 142 return nil, false 143 } 144 sel, ok := call.Fun.(*ast.SelectorExpr) 145 if !ok { 146 return nil, false 147 } 148 name, ok := sel.X.(*ast.Ident) 149 if !ok || name.Name != "list" { 150 return nil, false 151 } 152 name, ok = sel.Sel.(*ast.Ident) 153 if !ok || name.Name != "Concat" { 154 return nil, false 155 } 156 if len(call.Args) != 1 { 157 return nil, false 158 } 159 list, ok := call.Args[0].(*ast.ListLit) 160 if !ok { 161 return nil, false 162 } 163 return list, true 164 }