blob: 1629b801cd4d3c972fecb26bd1ca3a73b5aa8614 [file] [log] [blame]
Rebecca Stambler92778472021-01-05 23:05:35 -05001// 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 Donovanbfcffc62014-04-02 12:24:55 -04005// The eg command performs example-based refactoring.
Alan Donovanb8d26f52014-11-13 12:34:25 -05006// For documentation, run the command, or see Help in
Shenghou Maa47975e2014-12-14 15:42:37 -05007// golang.org/x/tools/refactor/eg.
David Symonds24257c82014-12-09 15:00:58 +11008package main // import "golang.org/x/tools/cmd/eg"
Alan Donovanbfcffc62014-04-02 12:24:55 -04009
10import (
11 "flag"
12 "fmt"
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -070013 "go/ast"
Leo Rudberg7a948932017-07-12 19:47:01 -040014 "go/format"
Alan Donovanbfcffc62014-04-02 12:24:55 -040015 "go/parser"
Alan Donovanbfcffc62014-04-02 12:24:55 -040016 "go/token"
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -070017 "go/types"
18 "io/ioutil"
Alan Donovanbfcffc62014-04-02 12:24:55 -040019 "os"
Josh Bleecher Snyderf5a40052020-09-29 17:37:17 -070020 "path/filepath"
Alan Donovan4abc8432014-08-26 15:52:40 -040021 "strings"
Alan Donovanbfcffc62014-04-02 12:24:55 -040022
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -070023 exec "golang.org/x/sys/execabs"
24 "golang.org/x/tools/go/packages"
Andrew Gerrand5ebbcd12014-11-10 08:50:40 +110025 "golang.org/x/tools/refactor/eg"
Alan Donovanbfcffc62014-04-02 12:24:55 -040026)
27
28var (
Alan Donovan4abc8432014-08-26 15:52:40 -040029 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 Donovanbfcffc62014-04-02 12:24:55 -040030 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
37const usage = `eg: an example-based refactoring tool.
38
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -070039Usage: eg -t template.go [-w] [-transitive] <packages>
Alan Donovan78251632015-03-05 14:55:52 -050040
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 Snyder28e7a3b2020-09-29 17:17:27 -070048`
Alan Donovanbfcffc62014-04-02 12:24:55 -040049
50func main() {
51 if err := doMain(); err != nil {
Alan Donovan41620822014-11-17 12:58:28 -050052 fmt.Fprintf(os.Stderr, "eg: %s\n", err)
Alan Donovanbfcffc62014-04-02 12:24:55 -040053 os.Exit(1)
54 }
55}
56
57func doMain() error {
58 flag.Parse()
59 args := flag.Args()
60
61 if *helpFlag {
Zvonimir Pavlinovic2cdcc602021-11-05 11:42:54 -070062 help := eg.Help // hide %s from vet
63 fmt.Fprint(os.Stderr, help)
Alan Donovanbfcffc62014-04-02 12:24:55 -040064 os.Exit(2)
65 }
66
Alan Donovan78251632015-03-05 14:55:52 -050067 if len(args) == 0 {
68 fmt.Fprint(os.Stderr, usage)
69 os.Exit(1)
70 }
71
Alan Donovanbfcffc62014-04-02 12:24:55 -040072 if *templateFlag == "" {
73 return fmt.Errorf("no -t template.go file specified")
74 }
75
Josh Bleecher Snyderf5a40052020-09-29 17:37:17 -070076 tAbs, err := filepath.Abs(*templateFlag)
77 if err != nil {
78 return err
79 }
80 template, err := ioutil.ReadFile(tAbs)
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -070081 if err != nil {
Alan Donovanbfcffc62014-04-02 12:24:55 -040082 return err
83 }
84
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -070085 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 Snyderf5a40052020-09-29 17:37:17 -070096 tFile, err := parser.ParseFile(cfg.Fset, tAbs, template, parser.ParseComments)
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -070097 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 Donovanbfcffc62014-04-02 12:24:55 -0400114 if err != nil {
115 return err
116 }
117
118 // Analyze the template.
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -0700119 xform, err := eg.NewTransformer(cfg.Fset, tPkg, tFile, &tInfo, *verboseFlag)
Alan Donovanbfcffc62014-04-02 12:24:55 -0400120 if err != nil {
121 return err
122 }
123
124 // Apply it to the input packages.
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -0700125 var all []*packages.Package
Alan Donovanbfcffc62014-04-02 12:24:55 -0400126 if *transitiveFlag {
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -0700127 packages.Visit(pkgs, nil, func(p *packages.Package) { all = append(all, p) })
Alan Donovanbfcffc62014-04-02 12:24:55 -0400128 } else {
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -0700129 all = pkgs
Alan Donovanbfcffc62014-04-02 12:24:55 -0400130 }
131 var hadErrors bool
132 for _, pkg := range pkgs {
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -0700133 for i, filename := range pkg.CompiledGoFiles {
Josh Bleecher Snyderf5a40052020-09-29 17:37:17 -0700134 if filename == tAbs {
135 // Don't rewrite the template file.
136 continue
137 }
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -0700138 file := pkg.Syntax[i]
139 n := xform.Transform(pkg.TypesInfo, pkg.Types, file)
Alan Donovanbfcffc62014-04-02 12:24:55 -0400140 if n == 0 {
141 continue
142 }
Alan Donovan4abc8432014-08-26 15:52:40 -0400143 fmt.Fprintf(os.Stderr, "=== %s (%d matches)\n", filename, n)
Alan Donovanbfcffc62014-04-02 12:24:55 -0400144 if *writeFlag {
Alan Donovan4abc8432014-08-26 15:52:40 -0400145 // 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 Snyder28e7a3b2020-09-29 17:17:27 -0700162 if err := eg.WriteAST(cfg.Fset, filename, file); err != nil {
Alan Donovan41620822014-11-17 12:58:28 -0500163 fmt.Fprintf(os.Stderr, "eg: %s\n", err)
Alan Donovanbfcffc62014-04-02 12:24:55 -0400164 hadErrors = true
165 }
166 } else {
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -0700167 format.Node(os.Stdout, cfg.Fset, file)
Alan Donovanbfcffc62014-04-02 12:24:55 -0400168 }
169 }
170 }
171 if hadErrors {
172 os.Exit(1)
173 }
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -0700174
Alan Donovanbfcffc62014-04-02 12:24:55 -0400175 return nil
176}
Josh Bleecher Snyder28e7a3b2020-09-29 17:17:27 -0700177
178type pkgsImporter []*packages.Package
179
180func (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}