github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/cmd/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  // The eg command performs example-based refactoring.
     6  // For documentation, run the command, or see Help in
     7  // golang.org/x/tools/refactor/eg.
     8  package main // import "github.com/jhump/golang-x-tools/cmd/eg"
     9  
    10  import (
    11  	"flag"
    12  	"fmt"
    13  	"go/ast"
    14  	"go/format"
    15  	"go/parser"
    16  	"go/token"
    17  	"go/types"
    18  	"io/ioutil"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	exec "golang.org/x/sys/execabs"
    24  	"github.com/jhump/golang-x-tools/go/packages"
    25  	"github.com/jhump/golang-x-tools/refactor/eg"
    26  )
    27  
    28  var (
    29  	beforeeditFlag = flag.String("beforeedit", "", "A command to exec before each file is edited (e.g. chmod, checkout).  Whitespace delimits argument words.  The string '{}' is replaced by the file name.")
    30  	helpFlag       = flag.Bool("help", false, "show detailed help message")
    31  	templateFlag   = flag.String("t", "", "template.go file specifying the refactoring")
    32  	transitiveFlag = flag.Bool("transitive", false, "apply refactoring to all dependencies too")
    33  	writeFlag      = flag.Bool("w", false, "rewrite input files in place (by default, the results are printed to standard output)")
    34  	verboseFlag    = flag.Bool("v", false, "show verbose matcher diagnostics")
    35  )
    36  
    37  const usage = `eg: an example-based refactoring tool.
    38  
    39  Usage: eg -t template.go [-w] [-transitive] <packages>
    40  
    41  -help            show detailed help message
    42  -t template.go	 specifies the template file (use -help to see explanation)
    43  -w          	 causes files to be re-written in place.
    44  -transitive 	 causes all dependencies to be refactored too.
    45  -v               show verbose matcher diagnostics
    46  -beforeedit cmd  a command to exec before each file is modified.
    47                   "{}" represents the name of the file.
    48  `
    49  
    50  func main() {
    51  	if err := doMain(); err != nil {
    52  		fmt.Fprintf(os.Stderr, "eg: %s\n", err)
    53  		os.Exit(1)
    54  	}
    55  }
    56  
    57  func doMain() error {
    58  	flag.Parse()
    59  	args := flag.Args()
    60  
    61  	if *helpFlag {
    62  		help := eg.Help // hide %s from vet
    63  		fmt.Fprint(os.Stderr, help)
    64  		os.Exit(2)
    65  	}
    66  
    67  	if len(args) == 0 {
    68  		fmt.Fprint(os.Stderr, usage)
    69  		os.Exit(1)
    70  	}
    71  
    72  	if *templateFlag == "" {
    73  		return fmt.Errorf("no -t template.go file specified")
    74  	}
    75  
    76  	tAbs, err := filepath.Abs(*templateFlag)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	template, err := ioutil.ReadFile(tAbs)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	cfg := &packages.Config{
    86  		Fset:  token.NewFileSet(),
    87  		Mode:  packages.NeedTypesInfo | packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps | packages.NeedCompiledGoFiles,
    88  		Tests: true,
    89  	}
    90  
    91  	pkgs, err := packages.Load(cfg, args...)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	tFile, err := parser.ParseFile(cfg.Fset, tAbs, template, parser.ParseComments)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	// Type-check the template.
   102  	tInfo := types.Info{
   103  		Types:      make(map[ast.Expr]types.TypeAndValue),
   104  		Defs:       make(map[*ast.Ident]types.Object),
   105  		Uses:       make(map[*ast.Ident]types.Object),
   106  		Implicits:  make(map[ast.Node]types.Object),
   107  		Selections: make(map[*ast.SelectorExpr]*types.Selection),
   108  		Scopes:     make(map[ast.Node]*types.Scope),
   109  	}
   110  	conf := types.Config{
   111  		Importer: pkgsImporter(pkgs),
   112  	}
   113  	tPkg, err := conf.Check("egtemplate", cfg.Fset, []*ast.File{tFile}, &tInfo)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	// Analyze the template.
   119  	xform, err := eg.NewTransformer(cfg.Fset, tPkg, tFile, &tInfo, *verboseFlag)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	// Apply it to the input packages.
   125  	var all []*packages.Package
   126  	if *transitiveFlag {
   127  		packages.Visit(pkgs, nil, func(p *packages.Package) { all = append(all, p) })
   128  	} else {
   129  		all = pkgs
   130  	}
   131  	var hadErrors bool
   132  	for _, pkg := range pkgs {
   133  		for i, filename := range pkg.CompiledGoFiles {
   134  			if filename == tAbs {
   135  				// Don't rewrite the template file.
   136  				continue
   137  			}
   138  			file := pkg.Syntax[i]
   139  			n := xform.Transform(pkg.TypesInfo, pkg.Types, file)
   140  			if n == 0 {
   141  				continue
   142  			}
   143  			fmt.Fprintf(os.Stderr, "=== %s (%d matches)\n", filename, n)
   144  			if *writeFlag {
   145  				// Run the before-edit command (e.g. "chmod +w",  "checkout") if any.
   146  				if *beforeeditFlag != "" {
   147  					args := strings.Fields(*beforeeditFlag)
   148  					// Replace "{}" with the filename, like find(1).
   149  					for i := range args {
   150  						if i > 0 {
   151  							args[i] = strings.Replace(args[i], "{}", filename, -1)
   152  						}
   153  					}
   154  					cmd := exec.Command(args[0], args[1:]...)
   155  					cmd.Stdout = os.Stdout
   156  					cmd.Stderr = os.Stderr
   157  					if err := cmd.Run(); err != nil {
   158  						fmt.Fprintf(os.Stderr, "Warning: edit hook %q failed (%s)\n",
   159  							args, err)
   160  					}
   161  				}
   162  				if err := eg.WriteAST(cfg.Fset, filename, file); err != nil {
   163  					fmt.Fprintf(os.Stderr, "eg: %s\n", err)
   164  					hadErrors = true
   165  				}
   166  			} else {
   167  				format.Node(os.Stdout, cfg.Fset, file)
   168  			}
   169  		}
   170  	}
   171  	if hadErrors {
   172  		os.Exit(1)
   173  	}
   174  
   175  	return nil
   176  }
   177  
   178  type pkgsImporter []*packages.Package
   179  
   180  func (p pkgsImporter) Import(path string) (tpkg *types.Package, err error) {
   181  	packages.Visit([]*packages.Package(p), func(pkg *packages.Package) bool {
   182  		if pkg.PkgPath == path {
   183  			tpkg = pkg.Types
   184  			return false
   185  		}
   186  		return true
   187  	}, nil)
   188  	if tpkg != nil {
   189  		return tpkg, nil
   190  	}
   191  	return nil, fmt.Errorf("package %q not found", path)
   192  }