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