// Copyright 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package source import ( "context" "go/ast" "go/token" "go/types" "sort" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/span" errors "golang.org/x/xerrors" ) // ReferenceInfo holds information about reference to an identifier in Go source. type ReferenceInfo struct { Name string MappedRange ident *ast.Ident obj types.Object pkg Package isDeclaration bool } // References returns a list of references for a given identifier within the packages // containing i.File. Declarations appear first in the result. func References(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, includeDeclaration bool) ([]*ReferenceInfo, error) { ctx, done := event.Start(ctx, "source.References") defer done() qualifiedObjs, err := qualifiedObjsAtProtocolPos(ctx, s, f, pp) // Don't return references for builtin types. if errors.Is(err, errBuiltin) { return nil, nil } if err != nil { return nil, err } refs, err := references(ctx, s, qualifiedObjs, includeDeclaration, true) if err != nil { return nil, err } toSort := refs if includeDeclaration { toSort = refs[1:] } sort.Slice(toSort, func(i, j int) bool { x := span.CompareURI(toSort[i].URI(), toSort[j].URI()) if x == 0 { return toSort[i].ident.Pos() < toSort[j].ident.Pos() } return x < 0 }) return refs, nil } // references is a helper function to avoid recomputing qualifiedObjsAtProtocolPos. func references(ctx context.Context, snapshot Snapshot, qos []qualifiedObject, includeDeclaration, includeInterfaceRefs bool) ([]*ReferenceInfo, error) { var ( references []*ReferenceInfo seen = make(map[token.Position]bool) ) filename := snapshot.FileSet().Position(qos[0].obj.Pos()).Filename pgf, err := qos[0].pkg.File(span.URIFromPath(filename)) if err != nil { return nil, err } declIdent, err := findIdentifier(ctx, snapshot, qos[0].pkg, pgf.File, qos[0].obj.Pos()) if err != nil { return nil, err } // Make sure declaration is the first item in the response. if includeDeclaration { references = append(references, &ReferenceInfo{ MappedRange: declIdent.MappedRange, Name: qos[0].obj.Name(), ident: declIdent.ident, obj: qos[0].obj, pkg: declIdent.pkg, isDeclaration: true, }) } for _, qo := range qos { var searchPkgs []Package // Only search dependents if the object is exported. if qo.obj.Exported() { reverseDeps, err := snapshot.GetReverseDependencies(ctx, qo.pkg.ID()) if err != nil { return nil, err } searchPkgs = append(searchPkgs, reverseDeps...) } // Add the package in which the identifier is declared. searchPkgs = append(searchPkgs, qo.pkg) for _, pkg := range searchPkgs { for ident, obj := range pkg.GetTypesInfo().Uses { if obj != qo.obj { continue } pos := snapshot.FileSet().Position(ident.Pos()) if seen[pos] { continue } seen[pos] = true rng, err := posToMappedRange(snapshot, pkg, ident.Pos(), ident.End()) if err != nil { return nil, err } references = append(references, &ReferenceInfo{ Name: ident.Name, ident: ident, pkg: pkg, obj: obj, MappedRange: rng, }) } } } if includeInterfaceRefs { declRange, err := declIdent.Range() if err != nil { return nil, err } fh, err := snapshot.GetFile(ctx, declIdent.URI()) if err != nil { return nil, err } interfaceRefs, err := interfaceReferences(ctx, snapshot, fh, declRange.Start) if err != nil { return nil, err } references = append(references, interfaceRefs...) } return references, nil } // interfaceReferences returns the references to the interfaces implemeneted by // the type or method at the given position. func interfaceReferences(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]*ReferenceInfo, error) { implementations, err := implementations(ctx, s, f, pp) if err != nil { if errors.Is(err, ErrNotAType) { return nil, nil } return nil, err } var refs []*ReferenceInfo for _, impl := range implementations { implRefs, err := references(ctx, s, []qualifiedObject{impl}, false, false) if err != nil { return nil, err } refs = append(refs, implRefs...) } return refs, nil }