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