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  }