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