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
5package source
6
7import (
8	"context"
9	"go/ast"
10	"go/types"
11
12	"golang.org/x/tools/internal/telemetry/trace"
13	errors "golang.org/x/xerrors"
14)
15
16// ReferenceInfo holds information about reference to an identifier in Go source.
17type ReferenceInfo struct {
18	Name string
19	mappedRange
20	ident         *ast.Ident
21	obj           types.Object
22	pkg           Package
23	isDeclaration bool
24}
25
26// References returns a list of references for a given identifier within the packages
27// containing i.File. Declarations appear first in the result.
28func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, error) {
29	ctx, done := trace.StartSpan(ctx, "source.References")
30	defer done()
31
32	var references []*ReferenceInfo
33
34	// If the object declaration is nil, assume it is an import spec and do not look for references.
35	if i.Declaration.obj == nil {
36		return nil, errors.Errorf("no references for an import spec")
37	}
38	info := i.pkg.GetTypesInfo()
39	if info == nil {
40		return nil, errors.Errorf("package %s has no types info", i.pkg.PkgPath())
41	}
42	if i.Declaration.wasImplicit {
43		// The definition is implicit, so we must add it separately.
44		// This occurs when the variable is declared in a type switch statement
45		// or is an implicit package name. Both implicits are local to a file.
46		references = append(references, &ReferenceInfo{
47			Name:          i.Declaration.obj.Name(),
48			mappedRange:   i.Declaration.mappedRange,
49			obj:           i.Declaration.obj,
50			pkg:           i.pkg,
51			isDeclaration: true,
52		})
53	}
54	for ident, obj := range info.Defs {
55		if obj == nil || !sameObj(obj, i.Declaration.obj) {
56			continue
57		}
58		rng, err := posToMappedRange(ctx, i.View, i.pkg, ident.Pos(), ident.End())
59		if err != nil {
60			return nil, err
61		}
62		// Add the declarations at the beginning of the references list.
63		references = append([]*ReferenceInfo{{
64			Name:          ident.Name,
65			ident:         ident,
66			obj:           obj,
67			pkg:           i.pkg,
68			isDeclaration: true,
69			mappedRange:   rng,
70		}}, references...)
71	}
72	for ident, obj := range info.Uses {
73		if obj == nil || !sameObj(obj, i.Declaration.obj) {
74			continue
75		}
76		rng, err := posToMappedRange(ctx, i.View, i.pkg, ident.Pos(), ident.End())
77		if err != nil {
78			return nil, err
79		}
80		references = append(references, &ReferenceInfo{
81			Name:        ident.Name,
82			ident:       ident,
83			pkg:         i.pkg,
84			obj:         obj,
85			mappedRange: rng,
86		})
87	}
88	return references, nil
89}
90
91// sameObj returns true if obj is the same as declObj.
92// Objects are the same if they have the some Pos and Name.
93func sameObj(obj, declObj types.Object) bool {
94	// TODO(suzmue): support the case where an identifier may have two different
95	// declaration positions.
96	return obj.Pos() == declObj.Pos() && obj.Name() == declObj.Name()
97}
98