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  }