--- /dev/null
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package eg implements the example-based refactoring tool whose
+// command-line is defined in golang.org/x/tools/cmd/eg.
+package eg // import "golang.org/x/tools/refactor/eg"
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/format"
+ "go/printer"
+ "go/token"
+ "go/types"
+ "os"
+)
+
+const Help = `
+This tool implements example-based refactoring of expressions.
+
+The transformation is specified as a Go file defining two functions,
+'before' and 'after', of identical types. Each function body consists
+of a single statement: either a return statement with a single
+(possibly multi-valued) expression, or an expression statement. The
+'before' expression specifies a pattern and the 'after' expression its
+replacement.
+
+ package P
+ import ( "errors"; "fmt" )
+ func before(s string) error { return fmt.Errorf("%s", s) }
+ func after(s string) error { return errors.New(s) }
+
+The expression statement form is useful when the expression has no
+result, for example:
+
+ func before(msg string) { log.Fatalf("%s", msg) }
+ func after(msg string) { log.Fatal(msg) }
+
+The parameters of both functions are wildcards that may match any
+expression assignable to that type. If the pattern contains multiple
+occurrences of the same parameter, each must match the same expression
+in the input for the pattern to match. If the replacement contains
+multiple occurrences of the same parameter, the expression will be
+duplicated, possibly changing the side-effects.
+
+The tool analyses all Go code in the packages specified by the
+arguments, replacing all occurrences of the pattern with the
+substitution.
+
+So, the transform above would change this input:
+ err := fmt.Errorf("%s", "error: " + msg)
+to this output:
+ err := errors.New("error: " + msg)
+
+Identifiers, including qualified identifiers (p.X) are considered to
+match only if they denote the same object. This allows correct
+matching even in the presence of dot imports, named imports and
+locally shadowed package names in the input program.
+
+Matching of type syntax is semantic, not syntactic: type syntax in the
+pattern matches type syntax in the input if the types are identical.
+Thus, func(x int) matches func(y int).
+
+This tool was inspired by other example-based refactoring tools,
+'gofmt -r' for Go and Refaster for Java.
+
+
+LIMITATIONS
+===========
+
+EXPRESSIVENESS
+
+Only refactorings that replace one expression with another, regardless
+of the expression's context, may be expressed. Refactoring arbitrary
+statements (or sequences of statements) is a less well-defined problem
+and is less amenable to this approach.
+
+A pattern that contains a function literal (and hence statements)
+never matches.
+
+There is no way to generalize over related types, e.g. to express that
+a wildcard may have any integer type, for example.
+
+It is not possible to replace an expression by one of a different
+type, even in contexts where this is legal, such as x in fmt.Print(x).
+
+The struct literals T{x} and T{K: x} cannot both be matched by a single
+template.
+
+
+SAFETY
+
+Verifying that a transformation does not introduce type errors is very
+complex in the general case. An innocuous-looking replacement of one
+constant by another (e.g. 1 to 2) may cause type errors relating to
+array types and indices, for example. The tool performs only very
+superficial checks of type preservation.
+
+
+IMPORTS
+
+Although the matching algorithm is fully aware of scoping rules, the
+replacement algorithm is not, so the replacement code may contain
+incorrect identifier syntax for imported objects if there are dot
+imports, named imports or locally shadowed package names in the input
+program.
+
+Imports are added as needed, but they are not removed as needed.
+Run 'goimports' on the modified file for now.
+
+Dot imports are forbidden in the template.
+
+
+TIPS
+====
+
+Sometimes a little creativity is required to implement the desired
+migration. This section lists a few tips and tricks.
+
+To remove the final parameter from a function, temporarily change the
+function signature so that the final parameter is variadic, as this
+allows legal calls both with and without the argument. Then use eg to
+remove the final argument from all callers, and remove the variadic
+parameter by hand. The reverse process can be used to add a final
+parameter.
+
+To add or remove parameters other than the final one, you must do it in
+stages: (1) declare a variant function f' with a different name and the
+desired parameters; (2) use eg to transform calls to f into calls to f',
+changing the arguments as needed; (3) change the declaration of f to
+match f'; (4) use eg to rename f' to f in all calls; (5) delete f'.
+`
+
+// TODO(adonovan): expand upon the above documentation as an HTML page.
+
+// A Transformer represents a single example-based transformation.
+type Transformer struct {
+ fset *token.FileSet
+ verbose bool
+ info *types.Info // combined type info for template/input/output ASTs
+ seenInfos map[*types.Info]bool
+ wildcards map[*types.Var]bool // set of parameters in func before()
+ env map[string]ast.Expr // maps parameter name to wildcard binding
+ importedObjs map[types.Object]*ast.SelectorExpr // objects imported by after().
+ before, after ast.Expr
+ afterStmts []ast.Stmt
+ allowWildcards bool
+
+ // Working state of Transform():
+ nsubsts int // number of substitutions made
+ currentPkg *types.Package // package of current call
+}
+
+// NewTransformer returns a transformer based on the specified template,
+// a single-file package containing "before" and "after" functions as
+// described in the package documentation.
+// tmplInfo is the type information for tmplFile.
+//
+func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) {
+ // Check the template.
+ beforeSig := funcSig(tmplPkg, "before")
+ if beforeSig == nil {
+ return nil, fmt.Errorf("no 'before' func found in template")
+ }
+ afterSig := funcSig(tmplPkg, "after")
+ if afterSig == nil {
+ return nil, fmt.Errorf("no 'after' func found in template")
+ }
+
+ // TODO(adonovan): should we also check the names of the params match?
+ if !types.Identical(afterSig, beforeSig) {
+ return nil, fmt.Errorf("before %s and after %s functions have different signatures",
+ beforeSig, afterSig)
+ }
+
+ for _, imp := range tmplFile.Imports {
+ if imp.Name != nil && imp.Name.Name == "." {
+ // Dot imports are currently forbidden. We
+ // make the simplifying assumption that all
+ // imports are regular, without local renames.
+ return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value)
+ }
+ }
+ var beforeDecl, afterDecl *ast.FuncDecl
+ for _, decl := range tmplFile.Decls {
+ if decl, ok := decl.(*ast.FuncDecl); ok {
+ switch decl.Name.Name {
+ case "before":
+ beforeDecl = decl
+ case "after":
+ afterDecl = decl
+ }
+ }
+ }
+
+ before, err := soleExpr(beforeDecl)
+ if err != nil {
+ return nil, fmt.Errorf("before: %s", err)
+ }
+ afterStmts, after, err := stmtAndExpr(afterDecl)
+ if err != nil {
+ return nil, fmt.Errorf("after: %s", err)
+ }
+
+ wildcards := make(map[*types.Var]bool)
+ for i := 0; i < beforeSig.Params().Len(); i++ {
+ wildcards[beforeSig.Params().At(i)] = true
+ }
+
+ // checkExprTypes returns an error if Tb (type of before()) is not
+ // safe to replace with Ta (type of after()).
+ //
+ // Only superficial checks are performed, and they may result in both
+ // false positives and negatives.
+ //
+ // Ideally, we would only require that the replacement be assignable
+ // to the context of a specific pattern occurrence, but the type
+ // checker doesn't record that information and it's complex to deduce.
+ // A Go type cannot capture all the constraints of a given expression
+ // context, which may include the size, constness, signedness,
+ // namedness or constructor of its type, and even the specific value
+ // of the replacement. (Consider the rule that array literal keys
+ // must be unique.) So we cannot hope to prove the safety of a
+ // transformation in general.
+ Tb := tmplInfo.TypeOf(before)
+ Ta := tmplInfo.TypeOf(after)
+ if types.AssignableTo(Tb, Ta) {
+ // safe: replacement is assignable to pattern.
+ } else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 {
+ // safe: pattern has void type (must appear in an ExprStmt).
+ } else {
+ return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb)
+ }
+
+ tr := &Transformer{
+ fset: fset,
+ verbose: verbose,
+ wildcards: wildcards,
+ allowWildcards: true,
+ seenInfos: make(map[*types.Info]bool),
+ importedObjs: make(map[types.Object]*ast.SelectorExpr),
+ before: before,
+ after: after,
+ afterStmts: afterStmts,
+ }
+
+ // Combine type info from the template and input packages, and
+ // type info for the synthesized ASTs too. This saves us
+ // having to book-keep where each ast.Node originated as we
+ // construct the resulting hybrid AST.
+ tr.info = &types.Info{
+ Types: make(map[ast.Expr]types.TypeAndValue),
+ Defs: make(map[*ast.Ident]types.Object),
+ Uses: make(map[*ast.Ident]types.Object),
+ Selections: make(map[*ast.SelectorExpr]*types.Selection),
+ }
+ mergeTypeInfo(tr.info, tmplInfo)
+
+ // Compute set of imported objects required by after().
+ // TODO(adonovan): reject dot-imports in pattern
+ ast.Inspect(after, func(n ast.Node) bool {
+ if n, ok := n.(*ast.SelectorExpr); ok {
+ if _, ok := tr.info.Selections[n]; !ok {
+ // qualified ident
+ obj := tr.info.Uses[n.Sel]
+ tr.importedObjs[obj] = n
+ return false // prune
+ }
+ }
+ return true // recur
+ })
+
+ return tr, nil
+}
+
+// WriteAST is a convenience function that writes AST f to the specified file.
+func WriteAST(fset *token.FileSet, filename string, f *ast.File) (err error) {
+ fh, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err2 := fh.Close(); err != nil {
+ err = err2 // prefer earlier error
+ }
+ }()
+ return format.Node(fh, fset, f)
+}
+
+// -- utilities --------------------------------------------------------
+
+// funcSig returns the signature of the specified package-level function.
+func funcSig(pkg *types.Package, name string) *types.Signature {
+ if f, ok := pkg.Scope().Lookup(name).(*types.Func); ok {
+ return f.Type().(*types.Signature)
+ }
+ return nil
+}
+
+// soleExpr returns the sole expression in the before/after template function.
+func soleExpr(fn *ast.FuncDecl) (ast.Expr, error) {
+ if fn.Body == nil {
+ return nil, fmt.Errorf("no body")
+ }
+ if len(fn.Body.List) != 1 {
+ return nil, fmt.Errorf("must contain a single statement")
+ }
+ switch stmt := fn.Body.List[0].(type) {
+ case *ast.ReturnStmt:
+ if len(stmt.Results) != 1 {
+ return nil, fmt.Errorf("return statement must have a single operand")
+ }
+ return stmt.Results[0], nil
+
+ case *ast.ExprStmt:
+ return stmt.X, nil
+ }
+
+ return nil, fmt.Errorf("must contain a single return or expression statement")
+}
+
+// stmtAndExpr returns the expression in the last return statement as well as the preceding lines.
+func stmtAndExpr(fn *ast.FuncDecl) ([]ast.Stmt, ast.Expr, error) {
+ if fn.Body == nil {
+ return nil, nil, fmt.Errorf("no body")
+ }
+
+ n := len(fn.Body.List)
+ if n == 0 {
+ return nil, nil, fmt.Errorf("must contain at least one statement")
+ }
+
+ stmts, last := fn.Body.List[:n-1], fn.Body.List[n-1]
+
+ switch last := last.(type) {
+ case *ast.ReturnStmt:
+ if len(last.Results) != 1 {
+ return nil, nil, fmt.Errorf("return statement must have a single operand")
+ }
+ return stmts, last.Results[0], nil
+
+ case *ast.ExprStmt:
+ return stmts, last.X, nil
+ }
+
+ return nil, nil, fmt.Errorf("must end with a single return or expression statement")
+}
+
+// mergeTypeInfo adds type info from src to dst.
+func mergeTypeInfo(dst, src *types.Info) {
+ for k, v := range src.Types {
+ dst.Types[k] = v
+ }
+ for k, v := range src.Defs {
+ dst.Defs[k] = v
+ }
+ for k, v := range src.Uses {
+ dst.Uses[k] = v
+ }
+ for k, v := range src.Selections {
+ dst.Selections[k] = v
+ }
+}
+
+// (debugging only)
+func astString(fset *token.FileSet, n ast.Node) string {
+ var buf bytes.Buffer
+ printer.Fprint(&buf, fset, n)
+ return buf.String()
+}