Rebecca Stambler | 9277847 | 2021-01-05 23:05:35 -0500 | [diff] [blame] | 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 | |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 5 | // The eg command performs example-based refactoring. |
Alan Donovan | b8d26f5 | 2014-11-13 12:34:25 -0500 | [diff] [blame] | 6 | // For documentation, run the command, or see Help in |
Shenghou Ma | a47975e | 2014-12-14 15:42:37 -0500 | [diff] [blame] | 7 | // golang.org/x/tools/refactor/eg. |
David Symonds | 24257c8 | 2014-12-09 15:00:58 +1100 | [diff] [blame] | 8 | package main // import "golang.org/x/tools/cmd/eg" |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 9 | |
| 10 | import ( |
| 11 | "flag" |
| 12 | "fmt" |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 13 | "go/ast" |
Leo Rudberg | 7a94893 | 2017-07-12 19:47:01 -0400 | [diff] [blame] | 14 | "go/format" |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 15 | "go/parser" |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 16 | "go/token" |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 17 | "go/types" |
| 18 | "io/ioutil" |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 19 | "os" |
Josh Bleecher Snyder | f5a4005 | 2020-09-29 17:37:17 -0700 | [diff] [blame] | 20 | "path/filepath" |
Alan Donovan | 4abc843 | 2014-08-26 15:52:40 -0400 | [diff] [blame] | 21 | "strings" |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 22 | |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 23 | exec "golang.org/x/sys/execabs" |
| 24 | "golang.org/x/tools/go/packages" |
Andrew Gerrand | 5ebbcd1 | 2014-11-10 08:50:40 +1100 | [diff] [blame] | 25 | "golang.org/x/tools/refactor/eg" |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 26 | ) |
| 27 | |
| 28 | var ( |
Alan Donovan | 4abc843 | 2014-08-26 15:52:40 -0400 | [diff] [blame] | 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.") |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 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 | |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 39 | Usage: eg -t template.go [-w] [-transitive] <packages> |
Alan Donovan | 7825163 | 2015-03-05 14:55:52 -0500 | [diff] [blame] | 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. |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 48 | ` |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 49 | |
| 50 | func main() { |
| 51 | if err := doMain(); err != nil { |
Alan Donovan | 4162082 | 2014-11-17 12:58:28 -0500 | [diff] [blame] | 52 | fmt.Fprintf(os.Stderr, "eg: %s\n", err) |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 53 | os.Exit(1) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | func doMain() error { |
| 58 | flag.Parse() |
| 59 | args := flag.Args() |
| 60 | |
| 61 | if *helpFlag { |
Zvonimir Pavlinovic | 2cdcc60 | 2021-11-05 11:42:54 -0700 | [diff] [blame] | 62 | help := eg.Help // hide %s from vet |
| 63 | fmt.Fprint(os.Stderr, help) |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 64 | os.Exit(2) |
| 65 | } |
| 66 | |
Alan Donovan | 7825163 | 2015-03-05 14:55:52 -0500 | [diff] [blame] | 67 | if len(args) == 0 { |
| 68 | fmt.Fprint(os.Stderr, usage) |
| 69 | os.Exit(1) |
| 70 | } |
| 71 | |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 72 | if *templateFlag == "" { |
| 73 | return fmt.Errorf("no -t template.go file specified") |
| 74 | } |
| 75 | |
Josh Bleecher Snyder | f5a4005 | 2020-09-29 17:37:17 -0700 | [diff] [blame] | 76 | tAbs, err := filepath.Abs(*templateFlag) |
| 77 | if err != nil { |
| 78 | return err |
| 79 | } |
| 80 | template, err := ioutil.ReadFile(tAbs) |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 81 | if err != nil { |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 82 | return err |
| 83 | } |
| 84 | |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 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 | |
Josh Bleecher Snyder | f5a4005 | 2020-09-29 17:37:17 -0700 | [diff] [blame] | 96 | tFile, err := parser.ParseFile(cfg.Fset, tAbs, template, parser.ParseComments) |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 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) |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 114 | if err != nil { |
| 115 | return err |
| 116 | } |
| 117 | |
| 118 | // Analyze the template. |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 119 | xform, err := eg.NewTransformer(cfg.Fset, tPkg, tFile, &tInfo, *verboseFlag) |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 120 | if err != nil { |
| 121 | return err |
| 122 | } |
| 123 | |
| 124 | // Apply it to the input packages. |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 125 | var all []*packages.Package |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 126 | if *transitiveFlag { |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 127 | packages.Visit(pkgs, nil, func(p *packages.Package) { all = append(all, p) }) |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 128 | } else { |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 129 | all = pkgs |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 130 | } |
| 131 | var hadErrors bool |
| 132 | for _, pkg := range pkgs { |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 133 | for i, filename := range pkg.CompiledGoFiles { |
Josh Bleecher Snyder | f5a4005 | 2020-09-29 17:37:17 -0700 | [diff] [blame] | 134 | if filename == tAbs { |
| 135 | // Don't rewrite the template file. |
| 136 | continue |
| 137 | } |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 138 | file := pkg.Syntax[i] |
| 139 | n := xform.Transform(pkg.TypesInfo, pkg.Types, file) |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 140 | if n == 0 { |
| 141 | continue |
| 142 | } |
Alan Donovan | 4abc843 | 2014-08-26 15:52:40 -0400 | [diff] [blame] | 143 | fmt.Fprintf(os.Stderr, "=== %s (%d matches)\n", filename, n) |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 144 | if *writeFlag { |
Alan Donovan | 4abc843 | 2014-08-26 15:52:40 -0400 | [diff] [blame] | 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 | } |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 162 | if err := eg.WriteAST(cfg.Fset, filename, file); err != nil { |
Alan Donovan | 4162082 | 2014-11-17 12:58:28 -0500 | [diff] [blame] | 163 | fmt.Fprintf(os.Stderr, "eg: %s\n", err) |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 164 | hadErrors = true |
| 165 | } |
| 166 | } else { |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 167 | format.Node(os.Stdout, cfg.Fset, file) |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 168 | } |
| 169 | } |
| 170 | } |
| 171 | if hadErrors { |
| 172 | os.Exit(1) |
| 173 | } |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 174 | |
Alan Donovan | bfcffc6 | 2014-04-02 12:24:55 -0400 | [diff] [blame] | 175 | return nil |
| 176 | } |
Josh Bleecher Snyder | 28e7a3b | 2020-09-29 17:17:27 -0700 | [diff] [blame] | 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 | } |