1
2
3
4
5 package pipeline
6
7 import (
8 "bytes"
9 "fmt"
10 "go/ast"
11 "go/constant"
12 "go/format"
13 "go/token"
14 "io"
15 "os"
16 "strings"
17
18 "golang.org/x/tools/go/loader"
19 )
20
21 const printerType = "golang.org/x/text/message.Printer"
22
23
24
25
26
27 func Rewrite(w io.Writer, args ...string) error {
28 conf := &loader.Config{
29 AllowErrors: true,
30 }
31 prog, err := loadPackages(conf, args)
32 if err != nil {
33 return wrap(err, "")
34 }
35
36 for _, info := range prog.InitialPackages() {
37 for _, f := range info.Files {
38
39
40
41 r := rewriter{info: info, conf: conf}
42 for _, n := range info.InitOrder {
43 if t := r.info.Types[n.Rhs].Type.String(); strings.HasSuffix(t, printerType) {
44 r.printerVar = n.Lhs[0].Name()
45 }
46 }
47
48 ast.Walk(&r, f)
49
50 w := w
51 if w == nil {
52 var err error
53 if w, err = os.Create(conf.Fset.File(f.Pos()).Name()); err != nil {
54 return wrap(err, "open failed")
55 }
56 } else {
57 fmt.Fprintln(w, "---", conf.Fset.File(f.Pos()).Name())
58 }
59
60 if err := format.Node(w, conf.Fset, f); err != nil {
61 return wrap(err, "go format failed")
62 }
63 }
64 }
65
66 return nil
67 }
68
69 type rewriter struct {
70 info *loader.PackageInfo
71 conf *loader.Config
72 printerVar string
73 }
74
75
76 func (r *rewriter) print(n ast.Node) string {
77 var buf bytes.Buffer
78 format.Node(&buf, r.conf.Fset, n)
79 return buf.String()
80 }
81
82 func (r *rewriter) Visit(n ast.Node) ast.Visitor {
83
84 if _, ok := n.(*ast.BlockStmt); ok {
85 r := *r
86 return &r
87 }
88
89 stmt, ok := n.(*ast.AssignStmt)
90 if ok {
91 for _, v := range stmt.Lhs {
92 if r.printerVar == r.print(v) {
93 r.printerVar = ""
94 }
95 }
96 for i, v := range stmt.Rhs {
97 if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) {
98 r.printerVar = r.print(stmt.Lhs[i])
99 return r
100 }
101 }
102 }
103
104 spec, ok := n.(*ast.ValueSpec)
105 if ok {
106 for _, v := range spec.Names {
107 if r.printerVar == r.print(v) {
108 r.printerVar = ""
109 }
110 }
111 for i, v := range spec.Values {
112 if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) {
113 r.printerVar = r.print(spec.Names[i])
114 return r
115 }
116 }
117 }
118 if r.printerVar == "" {
119 return r
120 }
121 call, ok := n.(*ast.CallExpr)
122 if !ok {
123 return r
124 }
125
126
127 sel, ok := call.Fun.(*ast.SelectorExpr)
128 if !ok {
129 return r
130 }
131 meth := r.info.Selections[sel]
132
133 source := r.print(sel.X)
134 fun := r.print(sel.Sel)
135 if meth != nil {
136 source = meth.Recv().String()
137 fun = meth.Obj().Name()
138 }
139
140
141
142
143 m, ok := rewriteFuncs[source]
144 if !ok {
145 return r
146 }
147
148 rewriteType, ok := m[fun]
149 if !ok {
150 return r
151 }
152 ident := ast.NewIdent(r.printerVar)
153 ident.NamePos = sel.X.Pos()
154 sel.X = ident
155 if rewriteType.method != "" {
156 sel.Sel.Name = rewriteType.method
157 }
158
159
160 argn := rewriteType.arg
161 if rewriteType.format || argn >= len(call.Args) {
162 return r
163 }
164 hasConst := false
165 for _, a := range call.Args[argn:] {
166 if v := r.info.Types[a].Value; v != nil && v.Kind() == constant.String {
167 hasConst = true
168 break
169 }
170 }
171 if !hasConst {
172 return r
173 }
174 sel.Sel.Name = rewriteType.methodf
175
176
177
178 if len(call.Args) == 1 {
179 s, ok := constStr(r.info, call.Args[0])
180 if ok && !strings.Contains(s, "%") && !rewriteType.newLine {
181 return r
182 }
183 }
184
185
186 expr := &ast.BasicLit{
187 ValuePos: call.Lparen,
188 Kind: token.STRING,
189 }
190 newArgs := append(call.Args[:argn:argn], expr)
191 newStr := []string{}
192 for i, a := range call.Args[argn:] {
193 if s, ok := constStr(r.info, a); ok {
194 newStr = append(newStr, strings.Replace(s, "%", "%%", -1))
195 } else {
196 newStr = append(newStr, "%v")
197 newArgs = append(newArgs, call.Args[argn+i])
198 }
199 }
200 s := strings.Join(newStr, rewriteType.sep)
201 if rewriteType.newLine {
202 s += "\n"
203 }
204 expr.Value = fmt.Sprintf("%q", s)
205
206 call.Args = newArgs
207
208
209
210
211
212
213
214
215
216
217
218
219 return r
220 }
221
222 type rewriteType struct {
223
224
225 method string
226
227
228
229 methodf string
230
231
232
233 format bool
234
235
236
237 arg int
238
239 sep string
240 newLine bool
241 }
242
243
244
245 var rewriteFuncs = map[string]map[string]rewriteType{
246
247 "fmt": {
248 "Print": rewriteType{methodf: "Printf"},
249 "Sprint": rewriteType{methodf: "Sprintf"},
250 "Fprint": rewriteType{methodf: "Fprintf"},
251
252 "Println": rewriteType{methodf: "Printf", sep: " ", newLine: true},
253 "Sprintln": rewriteType{methodf: "Sprintf", sep: " ", newLine: true},
254 "Fprintln": rewriteType{methodf: "Fprintf", sep: " ", newLine: true},
255
256 "Printf": rewriteType{method: "Printf", format: true},
257 "Sprintf": rewriteType{method: "Sprintf", format: true},
258 "Fprintf": rewriteType{method: "Fprintf", format: true},
259 },
260 }
261
262 func constStr(info *loader.PackageInfo, e ast.Expr) (s string, ok bool) {
263 v := info.Types[e].Value
264 if v == nil || v.Kind() != constant.String {
265 return "", false
266 }
267 return constant.StringVal(v), true
268 }
269
View as plain text