|  | // 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. | 
|  |  | 
|  | // The eg command performs example-based refactoring. | 
|  | // For documentation, run the command, or see Help in | 
|  | // golang.org/x/tools/refactor/eg. | 
|  | package main // import "golang.org/x/tools/cmd/eg" | 
|  |  | 
|  | import ( | 
|  | "flag" | 
|  | "fmt" | 
|  | "go/ast" | 
|  | "go/format" | 
|  | "go/parser" | 
|  | "go/token" | 
|  | "go/types" | 
|  | "os" | 
|  | "os/exec" | 
|  | "path/filepath" | 
|  | "strings" | 
|  |  | 
|  | "golang.org/x/tools/go/packages" | 
|  | "golang.org/x/tools/refactor/eg" | 
|  | ) | 
|  |  | 
|  | var ( | 
|  | 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.") | 
|  | helpFlag       = flag.Bool("help", false, "show detailed help message") | 
|  | templateFlag   = flag.String("t", "", "template.go file specifying the refactoring") | 
|  | transitiveFlag = flag.Bool("transitive", false, "apply refactoring to all dependencies too") | 
|  | writeFlag      = flag.Bool("w", false, "rewrite input files in place (by default, the results are printed to standard output)") | 
|  | verboseFlag    = flag.Bool("v", false, "show verbose matcher diagnostics") | 
|  | ) | 
|  |  | 
|  | const usage = `eg: an example-based refactoring tool. | 
|  |  | 
|  | Usage: eg -t template.go [-w] [-transitive] <packages> | 
|  |  | 
|  | -help            show detailed help message | 
|  | -t template.go	 specifies the template file (use -help to see explanation) | 
|  | -w          	 causes files to be re-written in place. | 
|  | -transitive 	 causes all dependencies to be refactored too. | 
|  | -v               show verbose matcher diagnostics | 
|  | -beforeedit cmd  a command to exec before each file is modified. | 
|  | "{}" represents the name of the file. | 
|  | ` | 
|  |  | 
|  | func main() { | 
|  | if err := doMain(); err != nil { | 
|  | fmt.Fprintf(os.Stderr, "eg: %s\n", err) | 
|  | os.Exit(1) | 
|  | } | 
|  | } | 
|  |  | 
|  | func doMain() error { | 
|  | flag.Parse() | 
|  | args := flag.Args() | 
|  |  | 
|  | if *helpFlag { | 
|  | help := eg.Help // hide %s from vet | 
|  | fmt.Fprint(os.Stderr, help) | 
|  | os.Exit(2) | 
|  | } | 
|  |  | 
|  | if len(args) == 0 { | 
|  | fmt.Fprint(os.Stderr, usage) | 
|  | os.Exit(1) | 
|  | } | 
|  |  | 
|  | if *templateFlag == "" { | 
|  | return fmt.Errorf("no -t template.go file specified") | 
|  | } | 
|  |  | 
|  | tAbs, err := filepath.Abs(*templateFlag) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | template, err := os.ReadFile(tAbs) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | cfg := &packages.Config{ | 
|  | Fset:  token.NewFileSet(), | 
|  | Mode:  packages.NeedTypesInfo | packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps | packages.NeedCompiledGoFiles, | 
|  | Tests: true, | 
|  | } | 
|  |  | 
|  | pkgs, err := packages.Load(cfg, args...) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | tFile, err := parser.ParseFile(cfg.Fset, tAbs, template, parser.ParseComments) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | // Type-check the template. | 
|  | tInfo := types.Info{ | 
|  | Types:      make(map[ast.Expr]types.TypeAndValue), | 
|  | Defs:       make(map[*ast.Ident]types.Object), | 
|  | Uses:       make(map[*ast.Ident]types.Object), | 
|  | Implicits:  make(map[ast.Node]types.Object), | 
|  | Selections: make(map[*ast.SelectorExpr]*types.Selection), | 
|  | Scopes:     make(map[ast.Node]*types.Scope), | 
|  | } | 
|  | conf := types.Config{ | 
|  | Importer: pkgsImporter(pkgs), | 
|  | } | 
|  | tPkg, err := conf.Check("egtemplate", cfg.Fset, []*ast.File{tFile}, &tInfo) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | // Analyze the template. | 
|  | xform, err := eg.NewTransformer(cfg.Fset, tPkg, tFile, &tInfo, *verboseFlag) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | // Apply it to the input packages. | 
|  | var all []*packages.Package | 
|  | if *transitiveFlag { | 
|  | packages.Visit(pkgs, nil, func(p *packages.Package) { all = append(all, p) }) | 
|  | } else { | 
|  | all = pkgs | 
|  | } | 
|  | var hadErrors bool | 
|  | for _, pkg := range pkgs { | 
|  | for i, filename := range pkg.CompiledGoFiles { | 
|  | if filename == tAbs { | 
|  | // Don't rewrite the template file. | 
|  | continue | 
|  | } | 
|  | file := pkg.Syntax[i] | 
|  | n := xform.Transform(pkg.TypesInfo, pkg.Types, file) | 
|  | if n == 0 { | 
|  | continue | 
|  | } | 
|  | fmt.Fprintf(os.Stderr, "=== %s (%d matches)\n", filename, n) | 
|  | if *writeFlag { | 
|  | // Run the before-edit command (e.g. "chmod +w",  "checkout") if any. | 
|  | if *beforeeditFlag != "" { | 
|  | args := strings.Fields(*beforeeditFlag) | 
|  | // Replace "{}" with the filename, like find(1). | 
|  | for i := range args { | 
|  | if i > 0 { | 
|  | args[i] = strings.Replace(args[i], "{}", filename, -1) | 
|  | } | 
|  | } | 
|  | cmd := exec.Command(args[0], args[1:]...) | 
|  | cmd.Stdout = os.Stdout | 
|  | cmd.Stderr = os.Stderr | 
|  | if err := cmd.Run(); err != nil { | 
|  | fmt.Fprintf(os.Stderr, "Warning: edit hook %q failed (%s)\n", | 
|  | args, err) | 
|  | } | 
|  | } | 
|  | if err := eg.WriteAST(cfg.Fset, filename, file); err != nil { | 
|  | fmt.Fprintf(os.Stderr, "eg: %s\n", err) | 
|  | hadErrors = true | 
|  | } | 
|  | } else { | 
|  | format.Node(os.Stdout, cfg.Fset, file) | 
|  | } | 
|  | } | 
|  | } | 
|  | if hadErrors { | 
|  | os.Exit(1) | 
|  | } | 
|  |  | 
|  | return nil | 
|  | } | 
|  |  | 
|  | type pkgsImporter []*packages.Package | 
|  |  | 
|  | func (p pkgsImporter) Import(path string) (tpkg *types.Package, err error) { | 
|  | packages.Visit([]*packages.Package(p), func(pkg *packages.Package) bool { | 
|  | if pkg.PkgPath == path { | 
|  | tpkg = pkg.Types | 
|  | return false | 
|  | } | 
|  | return true | 
|  | }, nil) | 
|  | if tpkg != nil { | 
|  | return tpkg, nil | 
|  | } | 
|  | return nil, fmt.Errorf("package %q not found", path) | 
|  | } |