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