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  }