Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / refactor / rename / spec.go
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 package rename
6
7 // This file contains logic related to specifying a renaming: parsing of
8 // the flags as a form of query, and finding the object(s) it denotes.
9 // See Usage for flag details.
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 // A spec specifies an entity to rename.
31 //
32 // It is populated from an -offset flag or -from query;
33 // see Usage for the allowed -from query forms.
34 //
35 type spec struct {
36         // pkg is the package containing the position
37         // specified by the -from or -offset flag.
38         // If filename == "", our search for the 'from' entity
39         // is restricted to this package.
40         pkg string
41
42         // The original name of the entity being renamed.
43         // If the query had a ::from component, this is that;
44         // otherwise it's the last segment, e.g.
45         //   (encoding/json.Decoder).from
46         //   encoding/json.from
47         fromName string
48
49         // -- The remaining fields are private to this file.  All are optional. --
50
51         // The query's ::x suffix, if any.
52         searchFor string
53
54         // e.g. "Decoder" in "(encoding/json.Decoder).fieldOrMethod"
55         //                or "encoding/json.Decoder
56         pkgMember string
57
58         // e.g. fieldOrMethod in "(encoding/json.Decoder).fieldOrMethod"
59         typeMember string
60
61         // Restricts the query to this file.
62         // Implied by -from="file.go::x" and -offset flags.
63         filename string
64
65         // Byte offset of the 'from' identifier within the file named 'filename'.
66         // -offset mode only.
67         offset int
68 }
69
70 // parseFromFlag interprets the "-from" flag value as a renaming specification.
71 // See Usage in rename.go for valid formats.
72 func parseFromFlag(ctxt *build.Context, fromFlag string) (*spec, error) {
73         var spec spec
74         var main string // sans "::x" suffix
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                         // error
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                 // main is "filename.go"
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                 // main is one of:
106                 //  "importpath"
107                 //  "importpath".member
108                 //  (*"importpath".type).fieldormethod           (parens and star optional)
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         // Sanitize the package.
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 // parseObjectSpec parses main as one of the non-filename forms of
142 // object specification.
143 func parseObjectSpec(spec *spec, main string) error {
144         // Parse main as a Go expression, albeit a strange one.
145         e, _ := parser.ParseExpr(main)
146
147         if pkg := parseImportPath(e); pkg != "" {
148                 // e.g. bytes or "encoding/json": a package
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                 // Strip off star constructor, if any.
161                 if star, ok := x.(*ast.StarExpr); ok {
162                         x = star.X
163                 }
164
165                 if pkg := parseImportPath(x); pkg != "" {
166                         // package member e.g. "encoding/json".HTMLEscape
167                         spec.pkg = pkg              // e.g. "encoding/json"
168                         spec.pkgMember = e.Sel.Name // e.g. "HTMLEscape"
169                         spec.fromName = e.Sel.Name
170                         return nil
171                 }
172
173                 if x, ok := x.(*ast.SelectorExpr); ok {
174                         // field/method of type e.g. ("encoding/json".Decoder).Decode
175                         y := unparen(x.X)
176                         if pkg := parseImportPath(y); pkg != "" {
177                                 spec.pkg = pkg               // e.g. "encoding/json"
178                                 spec.pkgMember = x.Sel.Name  // e.g. "Decoder"
179                                 spec.typeMember = e.Sel.Name // e.g. "Decode"
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 // parseImportPath returns the import path of the package denoted by e.
190 // Any import path may be represented as a string literal;
191 // single-segment import paths (e.g. "bytes") may also be represented as
192 // ast.Ident.  parseImportPath returns "" for all other expressions.
193 func parseImportPath(e ast.Expr) string {
194         switch e := e.(type) {
195         case *ast.Ident:
196                 return e.Name // e.g. bytes
197
198         case *ast.BasicLit:
199                 if e.Kind == token.STRING {
200                         pkgname, _ := strconv.Unquote(e.Value)
201                         return pkgname // e.g. "encoding/json"
202                 }
203         }
204         return ""
205 }
206
207 // parseOffsetFlag interprets the "-offset" flag value as a renaming specification.
208 func parseOffsetFlag(ctxt *build.Context, offsetFlag string) (*spec, error) {
209         var spec spec
210         // Validate -offset, e.g. file.go:#123
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         // Parse the file and check there's an identifier at that offset.
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 // For source trees built with 'go build', the -from or -offset
263 // spec identifies exactly one initial 'from' object to rename ,
264 // but certain proprietary build systems allow a single file to
265 // appear in multiple packages (e.g. the test package contains a
266 // copy of its library), so there may be multiple objects for
267 // the same source entity.
268
269 func findFromObjects(iprog *loader.Program, spec *spec) ([]types.Object, error) {
270         if spec.filename != "" {
271                 return findFromObjectsInFile(iprog, spec)
272         }
273
274         // Search for objects defined in specified package.
275
276         // TODO(adonovan): the iprog.ImportMap has an entry {"main": ...}
277         // for main packages, even though that's not an import path.
278         // Seems like a bug.
279         //
280         // pkg := iprog.ImportMap[spec.pkg]
281         // if pkg == nil {
282         //      return fmt.Errorf("cannot find package %s", spec.pkg) // can't happen?
283         // }
284         // info := iprog.AllPackages[pkg]
285
286         // Workaround: lookup by value.
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                 // ambiguous "*" scope query
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                 // restrict to specified filename
313                 // NB: under certain proprietary build systems, a given
314                 // filename may appear in multiple packages.
315                 for _, f := range info.Files {
316                         thisFile := iprog.Fset.File(f.Pos())
317                         if !sameFile(thisFile.Name(), spec.filename) {
318                                 continue
319                         }
320                         // This package contains the query file.
321
322                         if spec.offset != 0 {
323                                 // We cannot refactor generated files since position information is invalidated.
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                                 // Search for a specific ident by file/offset.
329                                 id := identAtOffset(iprog.Fset, f, spec.offset)
330                                 if id == nil {
331                                         // can't happen?
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                                                 // Ident without Object.
339
340                                                 // Package clause?
341                                                 pos := thisFile.Pos(spec.offset)
342                                                 _, path, _ := iprog.PathEnclosingInterval(pos, pos)
343                                                 if len(path) == 2 { // [Ident File]
344                                                         // TODO(adonovan): support this case.
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                                                 // Implicit y in "switch y := x.(type) {"?
350                                                 if obj := typeSwitchVar(&info.Info, path); obj != nil {
351                                                         return []types.Object{obj}, nil
352                                                 }
353
354                                                 // Probably a type error.
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                                 // do a package-wide query
366                                 objects, err := findObjects(info, spec)
367                                 if err != nil {
368                                         return nil, err
369                                 }
370
371                                 // filter results: only objects defined in thisFile
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                 // can't happen?
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                 // [Ident AssignStmt TypeSwitchStmt...]
399                 if sw, ok := path[2].(*ast.TypeSwitchStmt); ok {
400                         // choose the first case.
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 // On success, findObjects returns the list of objects named
413 // spec.fromName matching the spec.  On success, the result has exactly
414 // one element unless spec.searchFor!="", in which case it has at least one
415 // element.
416 //
417 func findObjects(info *loader.PackageInfo, spec *spec) ([]types.Object, error) {
418         if spec.pkgMember == "" {
419                 if spec.searchFor == "" {
420                         panic(spec)
421                 }
422                 objects := searchDefs(&info.Info, spec.searchFor)
423                 if objects == nil {
424                         return nil, fmt.Errorf("no object %q declared in package %q",
425                                 spec.searchFor, info.Pkg.Path())
426                 }
427                 return objects, nil
428         }
429
430         pkgMember := info.Pkg.Scope().Lookup(spec.pkgMember)
431         if pkgMember == nil {
432                 return nil, fmt.Errorf("package %q has no member %q",
433                         info.Pkg.Path(), spec.pkgMember)
434         }
435
436         var searchFunc *types.Func
437         if spec.typeMember == "" {
438                 // package member
439                 if spec.searchFor == "" {
440                         return []types.Object{pkgMember}, nil
441                 }
442
443                 // Search within pkgMember, which must be a function.
444                 searchFunc, _ = pkgMember.(*types.Func)
445                 if searchFunc == nil {
446                         return nil, fmt.Errorf("cannot search for %q within %s %q",
447                                 spec.searchFor, objectKind(pkgMember), pkgMember)
448                 }
449         } else {
450                 // field/method of type
451                 // e.g. (encoding/json.Decoder).Decode
452                 // or ::x within it.
453
454                 tName, _ := pkgMember.(*types.TypeName)
455                 if tName == nil {
456                         return nil, fmt.Errorf("%s.%s is a %s, not a type",
457                                 info.Pkg.Path(), pkgMember.Name(), objectKind(pkgMember))
458                 }
459
460                 // search within named type.
461                 obj, _, _ := types.LookupFieldOrMethod(tName.Type(), true, info.Pkg, spec.typeMember)
462                 if obj == nil {
463                         return nil, fmt.Errorf("cannot find field or method %q of %s %s.%s",
464                                 spec.typeMember, typeKind(tName.Type()), info.Pkg.Path(), tName.Name())
465                 }
466
467                 if spec.searchFor == "" {
468                         // If it is an embedded field, return the type of the field.
469                         if v, ok := obj.(*types.Var); ok && v.Anonymous() {
470                                 switch t := v.Type().(type) {
471                                 case *types.Pointer:
472                                         return []types.Object{t.Elem().(*types.Named).Obj()}, nil
473                                 case *types.Named:
474                                         return []types.Object{t.Obj()}, nil
475                                 }
476                         }
477                         return []types.Object{obj}, nil
478                 }
479
480                 searchFunc, _ = obj.(*types.Func)
481                 if searchFunc == nil {
482                         return nil, fmt.Errorf("cannot search for local name %q within %s (%s.%s).%s; need a function",
483                                 spec.searchFor, objectKind(obj), info.Pkg.Path(), tName.Name(),
484                                 obj.Name())
485                 }
486                 if isInterface(tName.Type()) {
487                         return nil, fmt.Errorf("cannot search for local name %q within abstract method (%s.%s).%s",
488                                 spec.searchFor, info.Pkg.Path(), tName.Name(), searchFunc.Name())
489                 }
490         }
491
492         // -- search within function or method --
493
494         decl := funcDecl(info, searchFunc)
495         if decl == nil {
496                 return nil, fmt.Errorf("cannot find syntax for %s", searchFunc) // can't happen?
497         }
498
499         var objects []types.Object
500         for _, obj := range searchDefs(&info.Info, spec.searchFor) {
501                 // We use positions, not scopes, to determine whether
502                 // the obj is within searchFunc.  This is clumsy, but the
503                 // alternative, using the types.Scope tree, doesn't
504                 // account for non-lexical objects like fields and
505                 // interface methods.
506                 if decl.Pos() <= obj.Pos() && obj.Pos() < decl.End() && obj != searchFunc {
507                         objects = append(objects, obj)
508                 }
509         }
510         if objects == nil {
511                 return nil, fmt.Errorf("no local definition of %q within %s",
512                         spec.searchFor, searchFunc)
513         }
514         return objects, nil
515 }
516
517 func funcDecl(info *loader.PackageInfo, fn *types.Func) *ast.FuncDecl {
518         for _, f := range info.Files {
519                 for _, d := range f.Decls {
520                         if d, ok := d.(*ast.FuncDecl); ok && info.Defs[d.Name] == fn {
521                                 return d
522                         }
523                 }
524         }
525         return nil
526 }
527
528 func searchDefs(info *types.Info, name string) []types.Object {
529         var objects []types.Object
530         for id, obj := range info.Defs {
531                 if obj == nil {
532                         // e.g. blank ident.
533                         // TODO(adonovan): but also implicit y in
534                         //    switch y := x.(type)
535                         // Needs some thought.
536                         continue
537                 }
538                 if id.Name == name {
539                         objects = append(objects, obj)
540                 }
541         }
542         return objects
543 }
544
545 func identAtOffset(fset *token.FileSet, f *ast.File, offset int) *ast.Ident {
546         var found *ast.Ident
547         ast.Inspect(f, func(n ast.Node) bool {
548                 if id, ok := n.(*ast.Ident); ok {
549                         idpos := fset.Position(id.Pos()).Offset
550                         if idpos <= offset && offset < idpos+len(id.Name) {
551                                 found = id
552                         }
553                 }
554                 return found == nil // keep traversing only until found
555         })
556         return found
557 }
558
559 // ambiguityError returns an error describing an ambiguous "*" scope query.
560 func ambiguityError(fset *token.FileSet, objects []types.Object) error {
561         var buf bytes.Buffer
562         for i, obj := range objects {
563                 if i > 0 {
564                         buf.WriteString(", ")
565                 }
566                 posn := fset.Position(obj.Pos())
567                 fmt.Fprintf(&buf, "%s at %s:%d:%d",
568                         objectKind(obj), filepath.Base(posn.Filename), posn.Line, posn.Column)
569         }
570         return fmt.Errorf("ambiguous specifier %s matches %s",
571                 objects[0].Name(), buf.String())
572 }
573
574 // Matches cgo generated comment as well as the proposed standard:
575 //      https://golang.org/s/generatedcode
576 var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
577
578 // generated reports whether ast.File is a generated file.
579 func generated(f *ast.File, tokenFile *token.File) bool {
580
581         // Iterate over the comments in the file
582         for _, commentGroup := range f.Comments {
583                 for _, comment := range commentGroup.List {
584                         if matched := generatedRx.MatchString(comment.Text); matched {
585                                 // Check if comment is at the beginning of the line in source
586                                 if pos := tokenFile.Position(comment.Slash); pos.Column == 1 {
587                                         return true
588                                 }
589                         }
590                 }
591         }
592         return false
593 }