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 | "io/ioutil" |
19 | "os" |
20 | "path/filepath" |
21 | "strings" |
22 | |
23 | exec "golang.org/x/sys/execabs" |
24 | "golang.org/x/tools/go/packages" |
25 | "golang.org/x/tools/refactor/eg" |
26 | ) |
27 | |
28 | var ( |
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.") |
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 | |
39 | Usage: eg -t template.go [-w] [-transitive] <packages> |
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. |
48 | ` |
49 | |
50 | func main() { |
51 | if err := doMain(); err != nil { |
52 | fmt.Fprintf(os.Stderr, "eg: %s\n", err) |
53 | os.Exit(1) |
54 | } |
55 | } |
56 | |
57 | func doMain() error { |
58 | flag.Parse() |
59 | args := flag.Args() |
60 | |
61 | if *helpFlag { |
62 | help := eg.Help // hide %s from vet |
63 | fmt.Fprint(os.Stderr, help) |
64 | os.Exit(2) |
65 | } |
66 | |
67 | if len(args) == 0 { |
68 | fmt.Fprint(os.Stderr, usage) |
69 | os.Exit(1) |
70 | } |
71 | |
72 | if *templateFlag == "" { |
73 | return fmt.Errorf("no -t template.go file specified") |
74 | } |
75 | |
76 | tAbs, err := filepath.Abs(*templateFlag) |
77 | if err != nil { |
78 | return err |
79 | } |
80 | template, err := ioutil.ReadFile(tAbs) |
81 | if err != nil { |
82 | return err |
83 | } |
84 | |
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 | |
96 | tFile, err := parser.ParseFile(cfg.Fset, tAbs, template, parser.ParseComments) |
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) |
114 | if err != nil { |
115 | return err |
116 | } |
117 | |
118 | // Analyze the template. |
119 | xform, err := eg.NewTransformer(cfg.Fset, tPkg, tFile, &tInfo, *verboseFlag) |
120 | if err != nil { |
121 | return err |
122 | } |
123 | |
124 | // Apply it to the input packages. |
125 | var all []*packages.Package |
126 | if *transitiveFlag { |
127 | packages.Visit(pkgs, nil, func(p *packages.Package) { all = append(all, p) }) |
128 | } else { |
129 | all = pkgs |
130 | } |
131 | var hadErrors bool |
132 | for _, pkg := range pkgs { |
133 | for i, filename := range pkg.CompiledGoFiles { |
134 | if filename == tAbs { |
135 | // Don't rewrite the template file. |
136 | continue |
137 | } |
138 | file := pkg.Syntax[i] |
139 | n := xform.Transform(pkg.TypesInfo, pkg.Types, file) |
140 | if n == 0 { |
141 | continue |
142 | } |
143 | fmt.Fprintf(os.Stderr, "=== %s (%d matches)\n", filename, n) |
144 | if *writeFlag { |
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 | } |
162 | if err := eg.WriteAST(cfg.Fset, filename, file); err != nil { |
163 | fmt.Fprintf(os.Stderr, "eg: %s\n", err) |
164 | hadErrors = true |
165 | } |
166 | } else { |
167 | format.Node(os.Stdout, cfg.Fset, file) |
168 | } |
169 | } |
170 | } |
171 | if hadErrors { |
172 | os.Exit(1) |
173 | } |
174 | |
175 | return nil |
176 | } |
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 | } |
193 |
Members