1
2
3
4
5 package rename
6
7
8
9
10
11 import (
12 "bytes"
13 "fmt"
14 "go/ast"
15 "go/build"
16 "go/parser"
17 "go/token"
18 "go/types"
19 "log"
20 "os"
21 "path/filepath"
22 "regexp"
23 "strconv"
24 "strings"
25
26 "golang.org/x/tools/go/buildutil"
27 "golang.org/x/tools/go/loader"
28 )
29
30
31
32
33
34 type spec struct {
35
36
37
38
39 pkg string
40
41
42
43
44
45
46 fromName string
47
48
49
50
51 searchFor string
52
53
54
55 pkgMember string
56
57
58 typeMember string
59
60
61
62 filename string
63
64
65
66 offset int
67 }
68
69
70
71 func parseFromFlag(ctxt *build.Context, fromFlag string) (*spec, error) {
72 var spec spec
73 var main string
74 switch parts := strings.Split(fromFlag, "::"); len(parts) {
75 case 1:
76 main = parts[0]
77 case 2:
78 main = parts[0]
79 spec.searchFor = parts[1]
80 if parts[1] == "" {
81
82 }
83 default:
84 return nil, fmt.Errorf("-from %q: invalid identifier specification (see -help for formats)", fromFlag)
85 }
86
87 if strings.HasSuffix(main, ".go") {
88
89 if spec.searchFor == "" {
90 return nil, fmt.Errorf("-from: filename %q must have a ::name suffix", main)
91 }
92 spec.filename = main
93 if !buildutil.FileExists(ctxt, spec.filename) {
94 return nil, fmt.Errorf("no such file: %s", spec.filename)
95 }
96
97 bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename)
98 if err != nil {
99 return nil, err
100 }
101 spec.pkg = bp.ImportPath
102
103 } else {
104
105
106
107
108 if err := parseObjectSpec(&spec, main); err != nil {
109 return nil, err
110 }
111 }
112
113 if spec.searchFor != "" {
114 spec.fromName = spec.searchFor
115 }
116
117 cwd, err := os.Getwd()
118 if err != nil {
119 return nil, err
120 }
121
122
123 bp, err := ctxt.Import(spec.pkg, cwd, build.FindOnly)
124 if err != nil {
125 return nil, fmt.Errorf("can't find package %q", spec.pkg)
126 }
127 spec.pkg = bp.ImportPath
128
129 if !isValidIdentifier(spec.fromName) {
130 return nil, fmt.Errorf("-from: invalid identifier %q", spec.fromName)
131 }
132
133 if Verbose {
134 log.Printf("-from spec: %+v", spec)
135 }
136
137 return &spec, nil
138 }
139
140
141
142 func parseObjectSpec(spec *spec, main string) error {
143
144 e, _ := parser.ParseExpr(main)
145
146 if pkg := parseImportPath(e); pkg != "" {
147
148 spec.pkg = pkg
149 if spec.searchFor == "" {
150 return fmt.Errorf("-from %q: package import path %q must have a ::name suffix",
151 main, main)
152 }
153 return nil
154 }
155
156 if e, ok := e.(*ast.SelectorExpr); ok {
157 x := unparen(e.X)
158
159
160 if star, ok := x.(*ast.StarExpr); ok {
161 x = star.X
162 }
163
164 if pkg := parseImportPath(x); pkg != "" {
165
166 spec.pkg = pkg
167 spec.pkgMember = e.Sel.Name
168 spec.fromName = e.Sel.Name
169 return nil
170 }
171
172 if x, ok := x.(*ast.SelectorExpr); ok {
173
174 y := unparen(x.X)
175 if pkg := parseImportPath(y); pkg != "" {
176 spec.pkg = pkg
177 spec.pkgMember = x.Sel.Name
178 spec.typeMember = e.Sel.Name
179 spec.fromName = e.Sel.Name
180 return nil
181 }
182 }
183 }
184
185 return fmt.Errorf("-from %q: invalid expression", main)
186 }
187
188
189
190
191
192 func parseImportPath(e ast.Expr) string {
193 switch e := e.(type) {
194 case *ast.Ident:
195 return e.Name
196
197 case *ast.BasicLit:
198 if e.Kind == token.STRING {
199 pkgname, _ := strconv.Unquote(e.Value)
200 return pkgname
201 }
202 }
203 return ""
204 }
205
206
207 func parseOffsetFlag(ctxt *build.Context, offsetFlag string) (*spec, error) {
208 var spec spec
209
210 parts := strings.Split(offsetFlag, ":#")
211 if len(parts) != 2 {
212 return nil, fmt.Errorf("-offset %q: invalid offset specification", offsetFlag)
213 }
214
215 spec.filename = parts[0]
216 if !buildutil.FileExists(ctxt, spec.filename) {
217 return nil, fmt.Errorf("no such file: %s", spec.filename)
218 }
219
220 bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename)
221 if err != nil {
222 return nil, err
223 }
224 spec.pkg = bp.ImportPath
225
226 for _, r := range parts[1] {
227 if !isDigit(r) {
228 return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag)
229 }
230 }
231 spec.offset, err = strconv.Atoi(parts[1])
232 if err != nil {
233 return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag)
234 }
235
236
237 fset := token.NewFileSet()
238 f, err := buildutil.ParseFile(fset, ctxt, nil, wd, spec.filename, parser.ParseComments)
239 if err != nil {
240 return nil, fmt.Errorf("-offset %q: cannot parse file: %s", offsetFlag, err)
241 }
242
243 id := identAtOffset(fset, f, spec.offset)
244 if id == nil {
245 return nil, fmt.Errorf("-offset %q: no identifier at this position", offsetFlag)
246 }
247
248 spec.fromName = id.Name
249
250 return &spec, nil
251 }
252
253 var wd = func() string {
254 wd, err := os.Getwd()
255 if err != nil {
256 panic("cannot get working directory: " + err.Error())
257 }
258 return wd
259 }()
260
261
262
263
264
265
266
267
268 func findFromObjects(iprog *loader.Program, spec *spec) ([]types.Object, error) {
269 if spec.filename != "" {
270 return findFromObjectsInFile(iprog, spec)
271 }
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286 var info *loader.PackageInfo
287 var pkg *types.Package
288 for pkg, info = range iprog.AllPackages {
289 if pkg.Path() == spec.pkg {
290 break
291 }
292 }
293 if info == nil {
294 return nil, fmt.Errorf("package %q was not loaded", spec.pkg)
295 }
296
297 objects, err := findObjects(info, spec)
298 if err != nil {
299 return nil, err
300 }
301 if len(objects) > 1 {
302
303 return nil, ambiguityError(iprog.Fset, objects)
304 }
305 return objects, nil
306 }
307
308 func findFromObjectsInFile(iprog *loader.Program, spec *spec) ([]types.Object, error) {
309 var fromObjects []types.Object
310 for _, info := range iprog.AllPackages {
311
312
313
314 for _, f := range info.Files {
315 thisFile := iprog.Fset.File(f.Pos())
316 if !sameFile(thisFile.Name(), spec.filename) {
317 continue
318 }
319
320
321 if spec.offset != 0 {
322
323 if generated(f, thisFile) {
324 return nil, fmt.Errorf("cannot rename identifiers in generated file containing DO NOT EDIT marker: %s", thisFile.Name())
325 }
326
327
328 id := identAtOffset(iprog.Fset, f, spec.offset)
329 if id == nil {
330
331 return nil, fmt.Errorf("identifier not found")
332 }
333 obj := info.Uses[id]
334 if obj == nil {
335 obj = info.Defs[id]
336 if obj == nil {
337
338
339
340 pos := thisFile.Pos(spec.offset)
341 _, path, _ := iprog.PathEnclosingInterval(pos, pos)
342 if len(path) == 2 {
343
344 return nil, fmt.Errorf("cannot rename %q: renaming package clauses is not yet supported",
345 path[1].(*ast.File).Name.Name)
346 }
347
348
349 if obj := typeSwitchVar(&info.Info, path); obj != nil {
350 return []types.Object{obj}, nil
351 }
352
353
354 return nil, fmt.Errorf("cannot find object for %q", id.Name)
355 }
356 }
357 if obj.Pkg() == nil {
358 return nil, fmt.Errorf("cannot rename predeclared identifiers (%s)", obj)
359
360 }
361
362 fromObjects = append(fromObjects, obj)
363 } else {
364
365 objects, err := findObjects(info, spec)
366 if err != nil {
367 return nil, err
368 }
369
370
371 var filtered []types.Object
372 for _, obj := range objects {
373 if iprog.Fset.File(obj.Pos()) == thisFile {
374 filtered = append(filtered, obj)
375 }
376 }
377 if len(filtered) == 0 {
378 return nil, fmt.Errorf("no object %q declared in file %s",
379 spec.fromName, spec.filename)
380 } else if len(filtered) > 1 {
381 return nil, ambiguityError(iprog.Fset, filtered)
382 }
383 fromObjects = append(fromObjects, filtered[0])
384 }
385 break
386 }
387 }
388 if len(fromObjects) == 0 {
389
390 return nil, fmt.Errorf("file %s was not part of the loaded program", spec.filename)
391 }
392 return fromObjects, nil
393 }
394
395 func typeSwitchVar(info *types.Info, path []ast.Node) types.Object {
396 if len(path) > 3 {
397
398 if sw, ok := path[2].(*ast.TypeSwitchStmt); ok {
399
400 if len(sw.Body.List) > 0 {
401 obj := info.Implicits[sw.Body.List[0].(*ast.CaseClause)]
402 if obj != nil {
403 return obj
404 }
405 }
406 }
407 }
408 return nil
409 }
410
411
412
413
414
415 func findObjects(info *loader.PackageInfo, spec *spec) ([]types.Object, error) {
416 if spec.pkgMember == "" {
417 if spec.searchFor == "" {
418 panic(spec)
419 }
420 objects := searchDefs(&info.Info, spec.searchFor)
421 if objects == nil {
422 return nil, fmt.Errorf("no object %q declared in package %q",
423 spec.searchFor, info.Pkg.Path())
424 }
425 return objects, nil
426 }
427
428 pkgMember := info.Pkg.Scope().Lookup(spec.pkgMember)
429 if pkgMember == nil {
430 return nil, fmt.Errorf("package %q has no member %q",
431 info.Pkg.Path(), spec.pkgMember)
432 }
433
434 var searchFunc *types.Func
435 if spec.typeMember == "" {
436
437 if spec.searchFor == "" {
438 return []types.Object{pkgMember}, nil
439 }
440
441
442 searchFunc, _ = pkgMember.(*types.Func)
443 if searchFunc == nil {
444 return nil, fmt.Errorf("cannot search for %q within %s %q",
445 spec.searchFor, objectKind(pkgMember), pkgMember)
446 }
447 } else {
448
449
450
451
452 tName, _ := pkgMember.(*types.TypeName)
453 if tName == nil {
454 return nil, fmt.Errorf("%s.%s is a %s, not a type",
455 info.Pkg.Path(), pkgMember.Name(), objectKind(pkgMember))
456 }
457
458
459 obj, _, _ := types.LookupFieldOrMethod(tName.Type(), true, info.Pkg, spec.typeMember)
460 if obj == nil {
461 return nil, fmt.Errorf("cannot find field or method %q of %s %s.%s",
462 spec.typeMember, typeKind(tName.Type()), info.Pkg.Path(), tName.Name())
463 }
464
465 if spec.searchFor == "" {
466
467 if v, ok := obj.(*types.Var); ok && v.Anonymous() {
468 switch t := v.Type().(type) {
469 case *types.Pointer:
470 return []types.Object{t.Elem().(*types.Named).Obj()}, nil
471 case *types.Named:
472 return []types.Object{t.Obj()}, nil
473 }
474 }
475 return []types.Object{obj}, nil
476 }
477
478 searchFunc, _ = obj.(*types.Func)
479 if searchFunc == nil {
480 return nil, fmt.Errorf("cannot search for local name %q within %s (%s.%s).%s; need a function",
481 spec.searchFor, objectKind(obj), info.Pkg.Path(), tName.Name(),
482 obj.Name())
483 }
484 if isInterface(tName.Type()) {
485 return nil, fmt.Errorf("cannot search for local name %q within abstract method (%s.%s).%s",
486 spec.searchFor, info.Pkg.Path(), tName.Name(), searchFunc.Name())
487 }
488 }
489
490
491
492 decl := funcDecl(info, searchFunc)
493 if decl == nil {
494 return nil, fmt.Errorf("cannot find syntax for %s", searchFunc)
495 }
496
497 var objects []types.Object
498 for _, obj := range searchDefs(&info.Info, spec.searchFor) {
499
500
501
502
503
504 if decl.Pos() <= obj.Pos() && obj.Pos() < decl.End() && obj != searchFunc {
505 objects = append(objects, obj)
506 }
507 }
508 if objects == nil {
509 return nil, fmt.Errorf("no local definition of %q within %s",
510 spec.searchFor, searchFunc)
511 }
512 return objects, nil
513 }
514
515 func funcDecl(info *loader.PackageInfo, fn *types.Func) *ast.FuncDecl {
516 for _, f := range info.Files {
517 for _, d := range f.Decls {
518 if d, ok := d.(*ast.FuncDecl); ok && info.Defs[d.Name] == fn {
519 return d
520 }
521 }
522 }
523 return nil
524 }
525
526 func searchDefs(info *types.Info, name string) []types.Object {
527 var objects []types.Object
528 for id, obj := range info.Defs {
529 if obj == nil {
530
531
532
533
534 continue
535 }
536 if id.Name == name {
537 objects = append(objects, obj)
538 }
539 }
540 return objects
541 }
542
543 func identAtOffset(fset *token.FileSet, f *ast.File, offset int) *ast.Ident {
544 var found *ast.Ident
545 ast.Inspect(f, func(n ast.Node) bool {
546 if id, ok := n.(*ast.Ident); ok {
547 idpos := fset.Position(id.Pos()).Offset
548 if idpos <= offset && offset < idpos+len(id.Name) {
549 found = id
550 }
551 }
552 return found == nil
553 })
554 return found
555 }
556
557
558 func ambiguityError(fset *token.FileSet, objects []types.Object) error {
559 var buf bytes.Buffer
560 for i, obj := range objects {
561 if i > 0 {
562 buf.WriteString(", ")
563 }
564 posn := fset.Position(obj.Pos())
565 fmt.Fprintf(&buf, "%s at %s:%d:%d",
566 objectKind(obj), filepath.Base(posn.Filename), posn.Line, posn.Column)
567 }
568 return fmt.Errorf("ambiguous specifier %s matches %s",
569 objects[0].Name(), buf.String())
570 }
571
572
573
574
575 var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
576
577
578 func generated(f *ast.File, tokenFile *token.File) bool {
579
580
581 for _, commentGroup := range f.Comments {
582 for _, comment := range commentGroup.List {
583 if matched := generatedRx.MatchString(comment.Text); matched {
584
585 if pos := tokenFile.Position(comment.Slash); pos.Column == 1 {
586 return true
587 }
588 }
589 }
590 }
591 return false
592 }
593
View as plain text