github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/source/references.go (about) 1 // Copyright 2019 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 source 6 7 import ( 8 "context" 9 "fmt" 10 "go/ast" 11 "go/token" 12 "go/types" 13 "sort" 14 15 "github.com/powerman/golang-tools/internal/event" 16 "github.com/powerman/golang-tools/internal/lsp/protocol" 17 "github.com/powerman/golang-tools/internal/span" 18 errors "golang.org/x/xerrors" 19 ) 20 21 // ReferenceInfo holds information about reference to an identifier in Go source. 22 type ReferenceInfo struct { 23 Name string 24 MappedRange 25 ident *ast.Ident 26 obj types.Object 27 pkg Package 28 isDeclaration bool 29 } 30 31 // References returns a list of references for a given identifier within the packages 32 // containing i.File. Declarations appear first in the result. 33 func References(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, includeDeclaration bool) ([]*ReferenceInfo, error) { 34 ctx, done := event.Start(ctx, "source.References") 35 defer done() 36 37 qualifiedObjs, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp) 38 // Don't return references for builtin types. 39 if errors.Is(err, errBuiltin) { 40 return nil, nil 41 } 42 if err != nil { 43 return nil, err 44 } 45 46 refs, err := references(ctx, s, qualifiedObjs, includeDeclaration, true, false) 47 if err != nil { 48 return nil, err 49 } 50 51 toSort := refs 52 if includeDeclaration { 53 toSort = refs[1:] 54 } 55 sort.Slice(toSort, func(i, j int) bool { 56 x := CompareURI(toSort[i].URI(), toSort[j].URI()) 57 if x == 0 { 58 return toSort[i].ident.Pos() < toSort[j].ident.Pos() 59 } 60 return x < 0 61 }) 62 return refs, nil 63 } 64 65 // references is a helper function to avoid recomputing qualifiedObjsAtProtocolPos. 66 func references(ctx context.Context, snapshot Snapshot, qos []qualifiedObject, includeDeclaration, includeInterfaceRefs, includeEmbeddedRefs bool) ([]*ReferenceInfo, error) { 67 var ( 68 references []*ReferenceInfo 69 seen = make(map[token.Pos]bool) 70 ) 71 72 pos := qos[0].obj.Pos() 73 if pos == token.NoPos { 74 return nil, fmt.Errorf("no position for %s", qos[0].obj) 75 } 76 filename := snapshot.FileSet().Position(pos).Filename 77 pgf, err := qos[0].pkg.File(span.URIFromPath(filename)) 78 if err != nil { 79 return nil, err 80 } 81 declIdent, err := findIdentifier(ctx, snapshot, qos[0].pkg, pgf, qos[0].obj.Pos()) 82 if err != nil { 83 return nil, err 84 } 85 // Make sure declaration is the first item in the response. 86 if includeDeclaration { 87 references = append(references, &ReferenceInfo{ 88 MappedRange: declIdent.MappedRange, 89 Name: qos[0].obj.Name(), 90 ident: declIdent.ident, 91 obj: qos[0].obj, 92 pkg: declIdent.pkg, 93 isDeclaration: true, 94 }) 95 } 96 97 for _, qo := range qos { 98 var searchPkgs []Package 99 100 // Only search dependents if the object is exported. 101 if qo.obj.Exported() { 102 reverseDeps, err := snapshot.GetReverseDependencies(ctx, qo.pkg.ID()) 103 if err != nil { 104 return nil, err 105 } 106 searchPkgs = append(searchPkgs, reverseDeps...) 107 } 108 // Add the package in which the identifier is declared. 109 searchPkgs = append(searchPkgs, qo.pkg) 110 for _, pkg := range searchPkgs { 111 for ident, obj := range pkg.GetTypesInfo().Uses { 112 // For instantiated objects (as in methods or fields on instantiated 113 // types), we may not have pointer-identical objects but still want to 114 // consider them references. 115 if !equalOrigin(obj, qo.obj) { 116 // If ident is not a use of qo.obj, skip it, with one exception: 117 // uses of an embedded field can be considered references of the 118 // embedded type name 119 if !includeEmbeddedRefs { 120 continue 121 } 122 v, ok := obj.(*types.Var) 123 if !ok || !v.Embedded() { 124 continue 125 } 126 named, ok := v.Type().(*types.Named) 127 if !ok || named.Obj() != qo.obj { 128 continue 129 } 130 } 131 if seen[ident.Pos()] { 132 continue 133 } 134 seen[ident.Pos()] = true 135 rng, err := posToMappedRange(snapshot, pkg, ident.Pos(), ident.End()) 136 if err != nil { 137 return nil, err 138 } 139 references = append(references, &ReferenceInfo{ 140 Name: ident.Name, 141 ident: ident, 142 pkg: pkg, 143 obj: obj, 144 MappedRange: rng, 145 }) 146 } 147 } 148 } 149 150 // When searching on type name, don't include interface references -- they 151 // would be things like all references to Stringer for any type that 152 // happened to have a String method. 153 _, isType := declIdent.Declaration.obj.(*types.TypeName) 154 if includeInterfaceRefs && !isType { 155 declRange, err := declIdent.Range() 156 if err != nil { 157 return nil, err 158 } 159 fh, err := snapshot.GetFile(ctx, declIdent.URI()) 160 if err != nil { 161 return nil, err 162 } 163 interfaceRefs, err := interfaceReferences(ctx, snapshot, fh, declRange.Start) 164 if err != nil { 165 return nil, err 166 } 167 references = append(references, interfaceRefs...) 168 } 169 170 return references, nil 171 } 172 173 // equalOrigin reports whether obj1 and obj2 have equivalent origin object. 174 // This may be the case even if obj1 != obj2, if one or both of them is 175 // instantiated. 176 func equalOrigin(obj1, obj2 types.Object) bool { 177 return obj1.Pkg() == obj2.Pkg() && obj1.Pos() == obj2.Pos() && obj1.Name() == obj2.Name() 178 } 179 180 // interfaceReferences returns the references to the interfaces implemented by 181 // the type or method at the given position. 182 func interfaceReferences(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]*ReferenceInfo, error) { 183 implementations, err := implementations(ctx, s, f, pp) 184 if err != nil { 185 if errors.Is(err, ErrNotAType) { 186 return nil, nil 187 } 188 return nil, err 189 } 190 191 var refs []*ReferenceInfo 192 for _, impl := range implementations { 193 implRefs, err := references(ctx, s, []qualifiedObject{impl}, false, false, false) 194 if err != nil { 195 return nil, err 196 } 197 refs = append(refs, implRefs...) 198 } 199 return refs, nil 200 }