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