1
2
3
4
5
6
7
8 package main
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
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
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
118 xform, err := eg.NewTransformer(cfg.Fset, tPkg, tFile, &tInfo, *verboseFlag)
119 if err != nil {
120 return err
121 }
122
123
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
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
145 if *beforeeditFlag != "" {
146 args := strings.Fields(*beforeeditFlag)
147
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 }
192
View as plain text