golang.org/x/tools@v0.21.0/refactor/eg/eg.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package eg implements the example-based refactoring tool whose 6 // command-line is defined in golang.org/x/tools/cmd/eg. 7 package eg // import "golang.org/x/tools/refactor/eg" 8 9 import ( 10 "bytes" 11 "fmt" 12 "go/ast" 13 "go/format" 14 "go/printer" 15 "go/token" 16 "go/types" 17 "os" 18 ) 19 20 const Help = ` 21 This tool implements example-based refactoring of expressions. 22 23 The transformation is specified as a Go file defining two functions, 24 'before' and 'after', of identical types. Each function body consists 25 of a single statement: either a return statement with a single 26 (possibly multi-valued) expression, or an expression statement. The 27 'before' expression specifies a pattern and the 'after' expression its 28 replacement. 29 30 package P 31 import ( "errors"; "fmt" ) 32 func before(s string) error { return fmt.Errorf("%s", s) } 33 func after(s string) error { return errors.New(s) } 34 35 The expression statement form is useful when the expression has no 36 result, for example: 37 38 func before(msg string) { log.Fatalf("%s", msg) } 39 func after(msg string) { log.Fatal(msg) } 40 41 The parameters of both functions are wildcards that may match any 42 expression assignable to that type. If the pattern contains multiple 43 occurrences of the same parameter, each must match the same expression 44 in the input for the pattern to match. If the replacement contains 45 multiple occurrences of the same parameter, the expression will be 46 duplicated, possibly changing the side-effects. 47 48 The tool analyses all Go code in the packages specified by the 49 arguments, replacing all occurrences of the pattern with the 50 substitution. 51 52 So, the transform above would change this input: 53 err := fmt.Errorf("%s", "error: " + msg) 54 to this output: 55 err := errors.New("error: " + msg) 56 57 Identifiers, including qualified identifiers (p.X) are considered to 58 match only if they denote the same object. This allows correct 59 matching even in the presence of dot imports, named imports and 60 locally shadowed package names in the input program. 61 62 Matching of type syntax is semantic, not syntactic: type syntax in the 63 pattern matches type syntax in the input if the types are identical. 64 Thus, func(x int) matches func(y int). 65 66 This tool was inspired by other example-based refactoring tools, 67 'gofmt -r' for Go and Refaster for Java. 68 69 70 LIMITATIONS 71 =========== 72 73 EXPRESSIVENESS 74 75 Only refactorings that replace one expression with another, regardless 76 of the expression's context, may be expressed. Refactoring arbitrary 77 statements (or sequences of statements) is a less well-defined problem 78 and is less amenable to this approach. 79 80 A pattern that contains a function literal (and hence statements) 81 never matches. 82 83 There is no way to generalize over related types, e.g. to express that 84 a wildcard may have any integer type, for example. 85 86 It is not possible to replace an expression by one of a different 87 type, even in contexts where this is legal, such as x in fmt.Print(x). 88 89 The struct literals T{x} and T{K: x} cannot both be matched by a single 90 template. 91 92 93 SAFETY 94 95 Verifying that a transformation does not introduce type errors is very 96 complex in the general case. An innocuous-looking replacement of one 97 constant by another (e.g. 1 to 2) may cause type errors relating to 98 array types and indices, for example. The tool performs only very 99 superficial checks of type preservation. 100 101 102 IMPORTS 103 104 Although the matching algorithm is fully aware of scoping rules, the 105 replacement algorithm is not, so the replacement code may contain 106 incorrect identifier syntax for imported objects if there are dot 107 imports, named imports or locally shadowed package names in the input 108 program. 109 110 Imports are added as needed, but they are not removed as needed. 111 Run 'goimports' on the modified file for now. 112 113 Dot imports are forbidden in the template. 114 115 116 TIPS 117 ==== 118 119 Sometimes a little creativity is required to implement the desired 120 migration. This section lists a few tips and tricks. 121 122 To remove the final parameter from a function, temporarily change the 123 function signature so that the final parameter is variadic, as this 124 allows legal calls both with and without the argument. Then use eg to 125 remove the final argument from all callers, and remove the variadic 126 parameter by hand. The reverse process can be used to add a final 127 parameter. 128 129 To add or remove parameters other than the final one, you must do it in 130 stages: (1) declare a variant function f' with a different name and the 131 desired parameters; (2) use eg to transform calls to f into calls to f', 132 changing the arguments as needed; (3) change the declaration of f to 133 match f'; (4) use eg to rename f' to f in all calls; (5) delete f'. 134 ` 135 136 // TODO(adonovan): expand upon the above documentation as an HTML page. 137 138 // A Transformer represents a single example-based transformation. 139 type Transformer struct { 140 fset *token.FileSet 141 verbose bool 142 info *types.Info // combined type info for template/input/output ASTs 143 seenInfos map[*types.Info]bool 144 wildcards map[*types.Var]bool // set of parameters in func before() 145 env map[string]ast.Expr // maps parameter name to wildcard binding 146 importedObjs map[types.Object]*ast.SelectorExpr // objects imported by after(). 147 before, after ast.Expr 148 afterStmts []ast.Stmt 149 allowWildcards bool 150 151 // Working state of Transform(): 152 nsubsts int // number of substitutions made 153 currentPkg *types.Package // package of current call 154 } 155 156 // NewTransformer returns a transformer based on the specified template, 157 // a single-file package containing "before" and "after" functions as 158 // described in the package documentation. 159 // tmplInfo is the type information for tmplFile. 160 func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) { 161 // Check the template. 162 beforeSig := funcSig(tmplPkg, "before") 163 if beforeSig == nil { 164 return nil, fmt.Errorf("no 'before' func found in template") 165 } 166 afterSig := funcSig(tmplPkg, "after") 167 if afterSig == nil { 168 return nil, fmt.Errorf("no 'after' func found in template") 169 } 170 171 // TODO(adonovan): should we also check the names of the params match? 172 if !types.Identical(afterSig, beforeSig) { 173 return nil, fmt.Errorf("before %s and after %s functions have different signatures", 174 beforeSig, afterSig) 175 } 176 177 for _, imp := range tmplFile.Imports { 178 if imp.Name != nil && imp.Name.Name == "." { 179 // Dot imports are currently forbidden. We 180 // make the simplifying assumption that all 181 // imports are regular, without local renames. 182 return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value) 183 } 184 } 185 var beforeDecl, afterDecl *ast.FuncDecl 186 for _, decl := range tmplFile.Decls { 187 if decl, ok := decl.(*ast.FuncDecl); ok { 188 switch decl.Name.Name { 189 case "before": 190 beforeDecl = decl 191 case "after": 192 afterDecl = decl 193 } 194 } 195 } 196 197 before, err := soleExpr(beforeDecl) 198 if err != nil { 199 return nil, fmt.Errorf("before: %s", err) 200 } 201 afterStmts, after, err := stmtAndExpr(afterDecl) 202 if err != nil { 203 return nil, fmt.Errorf("after: %s", err) 204 } 205 206 wildcards := make(map[*types.Var]bool) 207 for i := 0; i < beforeSig.Params().Len(); i++ { 208 wildcards[beforeSig.Params().At(i)] = true 209 } 210 211 // checkExprTypes returns an error if Tb (type of before()) is not 212 // safe to replace with Ta (type of after()). 213 // 214 // Only superficial checks are performed, and they may result in both 215 // false positives and negatives. 216 // 217 // Ideally, we would only require that the replacement be assignable 218 // to the context of a specific pattern occurrence, but the type 219 // checker doesn't record that information and it's complex to deduce. 220 // A Go type cannot capture all the constraints of a given expression 221 // context, which may include the size, constness, signedness, 222 // namedness or constructor of its type, and even the specific value 223 // of the replacement. (Consider the rule that array literal keys 224 // must be unique.) So we cannot hope to prove the safety of a 225 // transformation in general. 226 Tb := tmplInfo.TypeOf(before) 227 Ta := tmplInfo.TypeOf(after) 228 if types.AssignableTo(Tb, Ta) { 229 // safe: replacement is assignable to pattern. 230 } else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 { 231 // safe: pattern has void type (must appear in an ExprStmt). 232 } else { 233 return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb) 234 } 235 236 tr := &Transformer{ 237 fset: fset, 238 verbose: verbose, 239 wildcards: wildcards, 240 allowWildcards: true, 241 seenInfos: make(map[*types.Info]bool), 242 importedObjs: make(map[types.Object]*ast.SelectorExpr), 243 before: before, 244 after: after, 245 afterStmts: afterStmts, 246 } 247 248 // Combine type info from the template and input packages, and 249 // type info for the synthesized ASTs too. This saves us 250 // having to book-keep where each ast.Node originated as we 251 // construct the resulting hybrid AST. 252 tr.info = &types.Info{ 253 Types: make(map[ast.Expr]types.TypeAndValue), 254 Defs: make(map[*ast.Ident]types.Object), 255 Uses: make(map[*ast.Ident]types.Object), 256 Selections: make(map[*ast.SelectorExpr]*types.Selection), 257 } 258 mergeTypeInfo(tr.info, tmplInfo) 259 260 // Compute set of imported objects required by after(). 261 // TODO(adonovan): reject dot-imports in pattern 262 ast.Inspect(after, func(n ast.Node) bool { 263 if n, ok := n.(*ast.SelectorExpr); ok { 264 if _, ok := tr.info.Selections[n]; !ok { 265 // qualified ident 266 obj := tr.info.Uses[n.Sel] 267 tr.importedObjs[obj] = n 268 return false // prune 269 } 270 } 271 return true // recur 272 }) 273 274 return tr, nil 275 } 276 277 // WriteAST is a convenience function that writes AST f to the specified file. 278 func WriteAST(fset *token.FileSet, filename string, f *ast.File) (err error) { 279 fh, err := os.Create(filename) 280 if err != nil { 281 return err 282 } 283 284 defer func() { 285 if err2 := fh.Close(); err != nil { 286 err = err2 // prefer earlier error 287 } 288 }() 289 return format.Node(fh, fset, f) 290 } 291 292 // -- utilities -------------------------------------------------------- 293 294 // funcSig returns the signature of the specified package-level function. 295 func funcSig(pkg *types.Package, name string) *types.Signature { 296 if f, ok := pkg.Scope().Lookup(name).(*types.Func); ok { 297 return f.Type().(*types.Signature) 298 } 299 return nil 300 } 301 302 // soleExpr returns the sole expression in the before/after template function. 303 func soleExpr(fn *ast.FuncDecl) (ast.Expr, error) { 304 if fn.Body == nil { 305 return nil, fmt.Errorf("no body") 306 } 307 if len(fn.Body.List) != 1 { 308 return nil, fmt.Errorf("must contain a single statement") 309 } 310 switch stmt := fn.Body.List[0].(type) { 311 case *ast.ReturnStmt: 312 if len(stmt.Results) != 1 { 313 return nil, fmt.Errorf("return statement must have a single operand") 314 } 315 return stmt.Results[0], nil 316 317 case *ast.ExprStmt: 318 return stmt.X, nil 319 } 320 321 return nil, fmt.Errorf("must contain a single return or expression statement") 322 } 323 324 // stmtAndExpr returns the expression in the last return statement as well as the preceding lines. 325 func stmtAndExpr(fn *ast.FuncDecl) ([]ast.Stmt, ast.Expr, error) { 326 if fn.Body == nil { 327 return nil, nil, fmt.Errorf("no body") 328 } 329 330 n := len(fn.Body.List) 331 if n == 0 { 332 return nil, nil, fmt.Errorf("must contain at least one statement") 333 } 334 335 stmts, last := fn.Body.List[:n-1], fn.Body.List[n-1] 336 337 switch last := last.(type) { 338 case *ast.ReturnStmt: 339 if len(last.Results) != 1 { 340 return nil, nil, fmt.Errorf("return statement must have a single operand") 341 } 342 return stmts, last.Results[0], nil 343 344 case *ast.ExprStmt: 345 return stmts, last.X, nil 346 } 347 348 return nil, nil, fmt.Errorf("must end with a single return or expression statement") 349 } 350 351 // mergeTypeInfo adds type info from src to dst. 352 func mergeTypeInfo(dst, src *types.Info) { 353 for k, v := range src.Types { 354 dst.Types[k] = v 355 } 356 for k, v := range src.Defs { 357 dst.Defs[k] = v 358 } 359 for k, v := range src.Uses { 360 dst.Uses[k] = v 361 } 362 for k, v := range src.Selections { 363 dst.Selections[k] = v 364 } 365 } 366 367 // (debugging only) 368 func astString(fset *token.FileSet, n ast.Node) string { 369 var buf bytes.Buffer 370 printer.Fprint(&buf, fset, n) 371 return buf.String() 372 }