github.com/jackie-feng/tools@v0.0.0-20191231093943-4ebd680984ae/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 github.com/jackie-feng/tools/cmd/eg. 7 package eg // import "github.com/jackie-feng/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 // 161 func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) { 162 // Check the template. 163 beforeSig := funcSig(tmplPkg, "before") 164 if beforeSig == nil { 165 return nil, fmt.Errorf("no 'before' func found in template") 166 } 167 afterSig := funcSig(tmplPkg, "after") 168 if afterSig == nil { 169 return nil, fmt.Errorf("no 'after' func found in template") 170 } 171 172 // TODO(adonovan): should we also check the names of the params match? 173 if !types.Identical(afterSig, beforeSig) { 174 return nil, fmt.Errorf("before %s and after %s functions have different signatures", 175 beforeSig, afterSig) 176 } 177 178 for _, imp := range tmplFile.Imports { 179 if imp.Name != nil && imp.Name.Name == "." { 180 // Dot imports are currently forbidden. We 181 // make the simplifying assumption that all 182 // imports are regular, without local renames. 183 return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value) 184 } 185 } 186 var beforeDecl, afterDecl *ast.FuncDecl 187 for _, decl := range tmplFile.Decls { 188 if decl, ok := decl.(*ast.FuncDecl); ok { 189 switch decl.Name.Name { 190 case "before": 191 beforeDecl = decl 192 case "after": 193 afterDecl = decl 194 } 195 } 196 } 197 198 before, err := soleExpr(beforeDecl) 199 if err != nil { 200 return nil, fmt.Errorf("before: %s", err) 201 } 202 afterStmts, after, err := stmtAndExpr(afterDecl) 203 if err != nil { 204 return nil, fmt.Errorf("after: %s", err) 205 } 206 207 wildcards := make(map[*types.Var]bool) 208 for i := 0; i < beforeSig.Params().Len(); i++ { 209 wildcards[beforeSig.Params().At(i)] = true 210 } 211 212 // checkExprTypes returns an error if Tb (type of before()) is not 213 // safe to replace with Ta (type of after()). 214 // 215 // Only superficial checks are performed, and they may result in both 216 // false positives and negatives. 217 // 218 // Ideally, we would only require that the replacement be assignable 219 // to the context of a specific pattern occurrence, but the type 220 // checker doesn't record that information and it's complex to deduce. 221 // A Go type cannot capture all the constraints of a given expression 222 // context, which may include the size, constness, signedness, 223 // namedness or constructor of its type, and even the specific value 224 // of the replacement. (Consider the rule that array literal keys 225 // must be unique.) So we cannot hope to prove the safety of a 226 // transformation in general. 227 Tb := tmplInfo.TypeOf(before) 228 Ta := tmplInfo.TypeOf(after) 229 if types.AssignableTo(Tb, Ta) { 230 // safe: replacement is assignable to pattern. 231 } else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 { 232 // safe: pattern has void type (must appear in an ExprStmt). 233 } else { 234 return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb) 235 } 236 237 tr := &Transformer{ 238 fset: fset, 239 verbose: verbose, 240 wildcards: wildcards, 241 allowWildcards: true, 242 seenInfos: make(map[*types.Info]bool), 243 importedObjs: make(map[types.Object]*ast.SelectorExpr), 244 before: before, 245 after: after, 246 afterStmts: afterStmts, 247 } 248 249 // Combine type info from the template and input packages, and 250 // type info for the synthesized ASTs too. This saves us 251 // having to book-keep where each ast.Node originated as we 252 // construct the resulting hybrid AST. 253 tr.info = &types.Info{ 254 Types: make(map[ast.Expr]types.TypeAndValue), 255 Defs: make(map[*ast.Ident]types.Object), 256 Uses: make(map[*ast.Ident]types.Object), 257 Selections: make(map[*ast.SelectorExpr]*types.Selection), 258 } 259 mergeTypeInfo(tr.info, tmplInfo) 260 261 // Compute set of imported objects required by after(). 262 // TODO(adonovan): reject dot-imports in pattern 263 ast.Inspect(after, func(n ast.Node) bool { 264 if n, ok := n.(*ast.SelectorExpr); ok { 265 if _, ok := tr.info.Selections[n]; !ok { 266 // qualified ident 267 obj := tr.info.Uses[n.Sel] 268 tr.importedObjs[obj] = n 269 return false // prune 270 } 271 } 272 return true // recur 273 }) 274 275 return tr, nil 276 } 277 278 // WriteAST is a convenience function that writes AST f to the specified file. 279 func WriteAST(fset *token.FileSet, filename string, f *ast.File) (err error) { 280 fh, err := os.Create(filename) 281 if err != nil { 282 return err 283 } 284 285 defer func() { 286 if err2 := fh.Close(); err != nil { 287 err = err2 // prefer earlier error 288 } 289 }() 290 return format.Node(fh, fset, f) 291 } 292 293 // -- utilities -------------------------------------------------------- 294 295 // funcSig returns the signature of the specified package-level function. 296 func funcSig(pkg *types.Package, name string) *types.Signature { 297 if f, ok := pkg.Scope().Lookup(name).(*types.Func); ok { 298 return f.Type().(*types.Signature) 299 } 300 return nil 301 } 302 303 // soleExpr returns the sole expression in the before/after template function. 304 func soleExpr(fn *ast.FuncDecl) (ast.Expr, error) { 305 if fn.Body == nil { 306 return nil, fmt.Errorf("no body") 307 } 308 if len(fn.Body.List) != 1 { 309 return nil, fmt.Errorf("must contain a single statement") 310 } 311 switch stmt := fn.Body.List[0].(type) { 312 case *ast.ReturnStmt: 313 if len(stmt.Results) != 1 { 314 return nil, fmt.Errorf("return statement must have a single operand") 315 } 316 return stmt.Results[0], nil 317 318 case *ast.ExprStmt: 319 return stmt.X, nil 320 } 321 322 return nil, fmt.Errorf("must contain a single return or expression statement") 323 } 324 325 // stmtAndExpr returns the expression in the last return statement as well as the preceding lines. 326 func stmtAndExpr(fn *ast.FuncDecl) ([]ast.Stmt, ast.Expr, error) { 327 if fn.Body == nil { 328 return nil, nil, fmt.Errorf("no body") 329 } 330 331 n := len(fn.Body.List) 332 if n == 0 { 333 return nil, nil, fmt.Errorf("must contain at least one statement") 334 } 335 336 stmts, last := fn.Body.List[:n-1], fn.Body.List[n-1] 337 338 switch last := last.(type) { 339 case *ast.ReturnStmt: 340 if len(last.Results) != 1 { 341 return nil, nil, fmt.Errorf("return statement must have a single operand") 342 } 343 return stmts, last.Results[0], nil 344 345 case *ast.ExprStmt: 346 return stmts, last.X, nil 347 } 348 349 return nil, nil, fmt.Errorf("must end with a single return or expression statement") 350 } 351 352 // mergeTypeInfo adds type info from src to dst. 353 func mergeTypeInfo(dst, src *types.Info) { 354 for k, v := range src.Types { 355 dst.Types[k] = v 356 } 357 for k, v := range src.Defs { 358 dst.Defs[k] = v 359 } 360 for k, v := range src.Uses { 361 dst.Uses[k] = v 362 } 363 for k, v := range src.Selections { 364 dst.Selections[k] = v 365 } 366 } 367 368 // (debugging only) 369 func astString(fset *token.FileSet, n ast.Node) string { 370 var buf bytes.Buffer 371 printer.Fprint(&buf, fset, n) 372 return buf.String() 373 }