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  }