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	"bytes"
9	"context"
10	"go/ast"
11	"go/format"
12	"go/token"
13	"go/types"
14	"regexp"
15
16	"golang.org/x/tools/go/types/typeutil"
17	"golang.org/x/tools/internal/lsp/diff"
18	"golang.org/x/tools/internal/lsp/protocol"
19	"golang.org/x/tools/internal/span"
20	"golang.org/x/tools/internal/telemetry/trace"
21	"golang.org/x/tools/refactor/satisfy"
22	errors "golang.org/x/xerrors"
23)
24
25type renamer struct {
26	ctx                context.Context
27	fset               *token.FileSet
28	refs               []*ReferenceInfo
29	objsToUpdate       map[types.Object]bool
30	hadConflicts       bool
31	errors             string
32	from, to           string
33	satisfyConstraints map[satisfy.Constraint]bool
34	packages           map[*types.Package]Package // may include additional packages that are a rdep of pkg
35	msets              typeutil.MethodSetCache
36	changeMethods      bool
37}
38
39type PrepareItem struct {
40	Range protocol.Range
41	Text  string
42}
43
44func (i *IdentifierInfo) PrepareRename(ctx context.Context) (*PrepareItem, error) {
45	ctx, done := trace.StartSpan(ctx, "source.PrepareRename")
46	defer done()
47
48	// TODO(rstambler): We should handle this in a better way.
49	// If the object declaration is nil, assume it is an import spec.
50	if i.Declaration.obj == nil {
51		// Find the corresponding package name for this import spec
52		// and rename that instead.
53		ident, err := i.getPkgName(ctx)
54		if err != nil {
55			return nil, err
56		}
57		i = ident
58	}
59
60	// Do not rename builtin identifiers.
61	if i.Declaration.obj.Parent() == types.Universe {
62		return nil, errors.Errorf("cannot rename builtin %q", i.Name)
63	}
64	rng, err := i.mappedRange.Range()
65	if err != nil {
66		return nil, err
67	}
68	return &PrepareItem{
69		Range: rng,
70		Text:  i.Name,
71	}, nil
72}
73
74// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package.
75func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.URI][]protocol.TextEdit, error) {
76	ctx, done := trace.StartSpan(ctx, "source.Rename")
77	defer done()
78
79	// TODO(rstambler): We should handle this in a better way.
80	// If the object declaration is nil, assume it is an import spec.
81	if i.Declaration.obj == nil {
82		// Find the corresponding package name for this import spec
83		// and rename that instead.
84		ident, err := i.getPkgName(ctx)
85		if err != nil {
86			return nil, err
87		}
88		return ident.Rename(ctx, newName)
89	}
90	if i.Name == newName {
91		return nil, errors.Errorf("old and new names are the same: %s", newName)
92	}
93	if !isValidIdentifier(newName) {
94		return nil, errors.Errorf("invalid identifier to rename: %q", i.Name)
95	}
96	// Do not rename builtin identifiers.
97	if i.Declaration.obj.Parent() == types.Universe {
98		return nil, errors.Errorf("cannot rename builtin %q", i.Name)
99	}
100	if i.pkg == nil || i.pkg.IsIllTyped() {
101		return nil, errors.Errorf("package for %s is ill typed", i.File.File().Identity().URI)
102	}
103	// Do not rename identifiers declared in another package.
104	if i.pkg.GetTypes() != i.Declaration.obj.Pkg() {
105		return nil, errors.Errorf("failed to rename because %q is declared in package %q", i.Name, i.Declaration.obj.Pkg().Name())
106	}
107
108	refs, err := i.References(ctx)
109	if err != nil {
110		return nil, err
111	}
112
113	r := renamer{
114		ctx:          ctx,
115		fset:         i.Snapshot.View().Session().Cache().FileSet(),
116		refs:         refs,
117		objsToUpdate: make(map[types.Object]bool),
118		from:         i.Name,
119		to:           newName,
120		packages:     make(map[*types.Package]Package),
121	}
122	for _, from := range refs {
123		r.packages[from.pkg.GetTypes()] = from.pkg
124	}
125
126	// Check that the renaming of the identifier is ok.
127	for _, ref := range refs {
128		r.check(ref.obj)
129		if r.hadConflicts { // one error is enough.
130			break
131		}
132	}
133	if r.hadConflicts {
134		return nil, errors.Errorf(r.errors)
135	}
136
137	changes, err := r.update()
138	if err != nil {
139		return nil, err
140	}
141	result := make(map[span.URI][]protocol.TextEdit)
142	for uri, edits := range changes {
143		// These edits should really be associated with FileHandles for maximal correctness.
144		// For now, this is good enough.
145		f, err := i.Snapshot.View().GetFile(ctx, uri)
146		if err != nil {
147			return nil, err
148		}
149		fh := i.Snapshot.Handle(ctx, f)
150		data, _, err := fh.Read(ctx)
151		if err != nil {
152			return nil, err
153		}
154		converter := span.NewContentConverter(uri.Filename(), data)
155		m := &protocol.ColumnMapper{
156			URI:       uri,
157			Converter: converter,
158			Content:   data,
159		}
160		// Sort the edits first.
161		diff.SortTextEdits(edits)
162		protocolEdits, err := ToProtocolEdits(m, edits)
163		if err != nil {
164			return nil, err
165		}
166		result[uri] = protocolEdits
167	}
168	return result, nil
169}
170
171// getPkgName gets the pkg name associated with an identifer representing
172// the import path in an import spec.
173func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error) {
174	ph, err := i.pkg.File(i.URI())
175	if err != nil {
176		return nil, err
177	}
178	file, _, _, err := ph.Cached()
179	if err != nil {
180		return nil, err
181	}
182	var namePos token.Pos
183	for _, spec := range file.Imports {
184		if spec.Path.Pos() == i.spanRange.Start {
185			namePos = spec.Pos()
186			break
187		}
188	}
189	if !namePos.IsValid() {
190		return nil, errors.Errorf("import spec not found for %q", i.Name)
191	}
192	// Look for the object defined at NamePos.
193	for _, obj := range i.pkg.GetTypesInfo().Defs {
194		pkgName, ok := obj.(*types.PkgName)
195		if ok && pkgName.Pos() == namePos {
196			return getPkgNameIdentifier(ctx, i, pkgName)
197		}
198	}
199	for _, obj := range i.pkg.GetTypesInfo().Implicits {
200		pkgName, ok := obj.(*types.PkgName)
201		if ok && pkgName.Pos() == namePos {
202			return getPkgNameIdentifier(ctx, i, pkgName)
203		}
204	}
205	return nil, errors.Errorf("no package name for %q", i.Name)
206}
207
208// getPkgNameIdentifier returns an IdentifierInfo representing pkgName.
209// pkgName must be in the same package and file as ident.
210func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *types.PkgName) (*IdentifierInfo, error) {
211	decl := Declaration{
212		obj:         pkgName,
213		wasImplicit: true,
214	}
215	var err error
216	if decl.mappedRange, err = objToMappedRange(ctx, ident.Snapshot.View(), ident.pkg, decl.obj); err != nil {
217		return nil, err
218	}
219	if decl.node, err = objToNode(ctx, ident.Snapshot.View(), ident.pkg, decl.obj); err != nil {
220		return nil, err
221	}
222	return &IdentifierInfo{
223		Snapshot:         ident.Snapshot,
224		Name:             pkgName.Name(),
225		mappedRange:      decl.mappedRange,
226		File:             ident.File,
227		Declaration:      decl,
228		pkg:              ident.pkg,
229		wasEmbeddedField: false,
230		qf:               ident.qf,
231	}, nil
232}
233
234// Rename all references to the identifier.
235func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
236	result := make(map[span.URI][]diff.TextEdit)
237	seen := make(map[span.Span]bool)
238
239	docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
240	if err != nil {
241		return nil, err
242	}
243	for _, ref := range r.refs {
244		refSpan, err := ref.spanRange.Span()
245		if err != nil {
246			return nil, err
247		}
248		if seen[refSpan] {
249			continue
250		}
251		seen[refSpan] = true
252
253		// Renaming a types.PkgName may result in the addition or removal of an identifier,
254		// so we deal with this separately.
255		if pkgName, ok := ref.obj.(*types.PkgName); ok && ref.isDeclaration {
256			edit, err := r.updatePkgName(pkgName)
257			if err != nil {
258				return nil, err
259			}
260			result[refSpan.URI()] = append(result[refSpan.URI()], *edit)
261			continue
262		}
263
264		// Replace the identifier with r.to.
265		edit := diff.TextEdit{
266			Span:    refSpan,
267			NewText: r.to,
268		}
269
270		result[refSpan.URI()] = append(result[refSpan.URI()], edit)
271
272		if !ref.isDeclaration || ref.ident == nil { // uses do not have doc comments to update.
273			continue
274		}
275
276		doc := r.docComment(ref.pkg, ref.ident)
277		if doc == nil {
278			continue
279		}
280
281		// Perform the rename in doc comments declared in the original package.
282		for _, comment := range doc.List {
283			for _, locs := range docRegexp.FindAllStringIndex(comment.Text, -1) {
284				rng := span.NewRange(r.fset, comment.Pos()+token.Pos(locs[0]), comment.Pos()+token.Pos(locs[1]))
285				spn, err := rng.Span()
286				if err != nil {
287					return nil, err
288				}
289				result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
290					Span:    spn,
291					NewText: r.to,
292				})
293			}
294		}
295	}
296
297	return result, nil
298}
299
300// docComment returns the doc for an identifier.
301func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
302	_, nodes, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, id.Pos(), id.End())
303	for _, node := range nodes {
304		switch decl := node.(type) {
305		case *ast.FuncDecl:
306			return decl.Doc
307		case *ast.Field:
308			return decl.Doc
309		case *ast.GenDecl:
310			return decl.Doc
311		// For {Type,Value}Spec, if the doc on the spec is absent,
312		// search for the enclosing GenDecl
313		case *ast.TypeSpec:
314			if decl.Doc != nil {
315				return decl.Doc
316			}
317		case *ast.ValueSpec:
318			if decl.Doc != nil {
319				return decl.Doc
320			}
321		case *ast.Ident:
322		default:
323			return nil
324		}
325	}
326	return nil
327}
328
329// updatePkgName returns the updates to rename a pkgName in the import spec
330func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) {
331	// Modify ImportSpec syntax to add or remove the Name as needed.
332	pkg := r.packages[pkgName.Pkg()]
333	_, path, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, pkgName.Pos(), pkgName.Pos())
334	if len(path) < 2 {
335		return nil, errors.Errorf("no path enclosing interval for %s", pkgName.Name())
336	}
337	spec, ok := path[1].(*ast.ImportSpec)
338	if !ok {
339		return nil, errors.Errorf("failed to update PkgName for %s", pkgName.Name())
340	}
341
342	var astIdent *ast.Ident // will be nil if ident is removed
343	if pkgName.Imported().Name() != r.to {
344		// ImportSpec.Name needed
345		astIdent = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to}
346	}
347
348	// Make a copy of the ident that just has the name and path.
349	updated := &ast.ImportSpec{
350		Name:   astIdent,
351		Path:   spec.Path,
352		EndPos: spec.EndPos,
353	}
354
355	rng := span.NewRange(r.fset, spec.Pos(), spec.End())
356	spn, err := rng.Span()
357	if err != nil {
358		return nil, err
359	}
360
361	var buf bytes.Buffer
362	format.Node(&buf, r.fset, updated)
363	newText := buf.String()
364
365	return &diff.TextEdit{
366		Span:    spn,
367		NewText: newText,
368	}, nil
369}
370