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	"strings"
16
17	"golang.org/x/tools/go/types/typeutil"
18	"golang.org/x/tools/internal/event"
19	"golang.org/x/tools/internal/lsp/diff"
20	"golang.org/x/tools/internal/lsp/protocol"
21	"golang.org/x/tools/internal/span"
22	"golang.org/x/tools/refactor/satisfy"
23	errors "golang.org/x/xerrors"
24)
25
26type renamer struct {
27	ctx                context.Context
28	fset               *token.FileSet
29	refs               []*ReferenceInfo
30	objsToUpdate       map[types.Object]bool
31	hadConflicts       bool
32	errors             string
33	from, to           string
34	satisfyConstraints map[satisfy.Constraint]bool
35	packages           map[*types.Package]Package // may include additional packages that are a rdep of pkg
36	msets              typeutil.MethodSetCache
37	changeMethods      bool
38}
39
40type PrepareItem struct {
41	Range protocol.Range
42	Text  string
43}
44
45func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position) (*PrepareItem, error) {
46	ctx, done := event.Start(ctx, "source.PrepareRename")
47	defer done()
48
49	qos, err := qualifiedObjsAtProtocolPos(ctx, snapshot, f, pp)
50	if err != nil {
51		return nil, err
52	}
53	node, obj, pkg := qos[0].node, qos[0].obj, qos[0].sourcePkg
54	mr, err := posToMappedRange(snapshot, pkg, node.Pos(), node.End())
55	if err != nil {
56		return nil, err
57	}
58	rng, err := mr.Range()
59	if err != nil {
60		return nil, err
61	}
62	if _, isImport := node.(*ast.ImportSpec); isImport {
63		// We're not really renaming the import path.
64		rng.End = rng.Start
65	}
66	return &PrepareItem{
67		Range: rng,
68		Text:  obj.Name(),
69	}, nil
70}
71
72// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package.
73func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, newName string) (map[span.URI][]protocol.TextEdit, error) {
74	ctx, done := event.Start(ctx, "source.Rename")
75	defer done()
76
77	qos, err := qualifiedObjsAtProtocolPos(ctx, s, f, pp)
78	if err != nil {
79		return nil, err
80	}
81
82	obj := qos[0].obj
83	pkg := qos[0].pkg
84
85	if obj.Name() == newName {
86		return nil, errors.Errorf("old and new names are the same: %s", newName)
87	}
88	if !isValidIdentifier(newName) {
89		return nil, errors.Errorf("invalid identifier to rename: %q", newName)
90	}
91	if pkg == nil || pkg.IsIllTyped() {
92		return nil, errors.Errorf("package for %s is ill typed", f.URI())
93	}
94	refs, err := references(ctx, s, qos, true, false, true)
95	if err != nil {
96		return nil, err
97	}
98	r := renamer{
99		ctx:          ctx,
100		fset:         s.FileSet(),
101		refs:         refs,
102		objsToUpdate: make(map[types.Object]bool),
103		from:         obj.Name(),
104		to:           newName,
105		packages:     make(map[*types.Package]Package),
106	}
107
108	// A renaming initiated at an interface method indicates the
109	// intention to rename abstract and concrete methods as needed
110	// to preserve assignability.
111	for _, ref := range refs {
112		if obj, ok := ref.obj.(*types.Func); ok {
113			recv := obj.Type().(*types.Signature).Recv()
114			if recv != nil && IsInterface(recv.Type().Underlying()) {
115				r.changeMethods = true
116				break
117			}
118		}
119	}
120	for _, from := range refs {
121		r.packages[from.pkg.GetTypes()] = from.pkg
122	}
123
124	// Check that the renaming of the identifier is ok.
125	for _, ref := range refs {
126		r.check(ref.obj)
127		if r.hadConflicts { // one error is enough.
128			break
129		}
130	}
131	if r.hadConflicts {
132		return nil, errors.Errorf(r.errors)
133	}
134
135	changes, err := r.update()
136	if err != nil {
137		return nil, err
138	}
139	result := make(map[span.URI][]protocol.TextEdit)
140	for uri, edits := range changes {
141		// These edits should really be associated with FileHandles for maximal correctness.
142		// For now, this is good enough.
143		fh, err := s.GetFile(ctx, uri)
144		if err != nil {
145			return nil, err
146		}
147		data, err := fh.Read()
148		if err != nil {
149			return nil, err
150		}
151		converter := span.NewContentConverter(uri.Filename(), data)
152		m := &protocol.ColumnMapper{
153			URI:       uri,
154			Converter: converter,
155			Content:   data,
156		}
157		// Sort the edits first.
158		diff.SortTextEdits(edits)
159		protocolEdits, err := ToProtocolEdits(m, edits)
160		if err != nil {
161			return nil, err
162		}
163		result[uri] = protocolEdits
164	}
165	return result, nil
166}
167
168// Rename all references to the identifier.
169func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
170	result := make(map[span.URI][]diff.TextEdit)
171	seen := make(map[span.Span]bool)
172
173	docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
174	if err != nil {
175		return nil, err
176	}
177	for _, ref := range r.refs {
178		refSpan, err := ref.spanRange.Span()
179		if err != nil {
180			return nil, err
181		}
182		if seen[refSpan] {
183			continue
184		}
185		seen[refSpan] = true
186
187		// Renaming a types.PkgName may result in the addition or removal of an identifier,
188		// so we deal with this separately.
189		if pkgName, ok := ref.obj.(*types.PkgName); ok && ref.isDeclaration {
190			edit, err := r.updatePkgName(pkgName)
191			if err != nil {
192				return nil, err
193			}
194			result[refSpan.URI()] = append(result[refSpan.URI()], *edit)
195			continue
196		}
197
198		// Replace the identifier with r.to.
199		edit := diff.TextEdit{
200			Span:    refSpan,
201			NewText: r.to,
202		}
203
204		result[refSpan.URI()] = append(result[refSpan.URI()], edit)
205
206		if !ref.isDeclaration || ref.ident == nil { // uses do not have doc comments to update.
207			continue
208		}
209
210		doc := r.docComment(ref.pkg, ref.ident)
211		if doc == nil {
212			continue
213		}
214
215		// Perform the rename in doc comments declared in the original package.
216		// go/parser strips out \r\n returns from the comment text, so go
217		// line-by-line through the comment text to get the correct positions.
218		for _, comment := range doc.List {
219			if isDirective(comment.Text) {
220				continue
221			}
222			lines := strings.Split(comment.Text, "\n")
223			tok := r.fset.File(comment.Pos())
224			commentLine := tok.Position(comment.Pos()).Line
225			for i, line := range lines {
226				lineStart := comment.Pos()
227				if i > 0 {
228					lineStart = tok.LineStart(commentLine + i)
229				}
230				for _, locs := range docRegexp.FindAllIndex([]byte(line), -1) {
231					rng := span.NewRange(r.fset, lineStart+token.Pos(locs[0]), lineStart+token.Pos(locs[1]))
232					spn, err := rng.Span()
233					if err != nil {
234						return nil, err
235					}
236					result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
237						Span:    spn,
238						NewText: r.to,
239					})
240				}
241			}
242		}
243	}
244
245	return result, nil
246}
247
248// docComment returns the doc for an identifier.
249func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
250	_, nodes, _ := pathEnclosingInterval(r.fset, pkg, id.Pos(), id.End())
251	for _, node := range nodes {
252		switch decl := node.(type) {
253		case *ast.FuncDecl:
254			return decl.Doc
255		case *ast.Field:
256			return decl.Doc
257		case *ast.GenDecl:
258			return decl.Doc
259		// For {Type,Value}Spec, if the doc on the spec is absent,
260		// search for the enclosing GenDecl
261		case *ast.TypeSpec:
262			if decl.Doc != nil {
263				return decl.Doc
264			}
265		case *ast.ValueSpec:
266			if decl.Doc != nil {
267				return decl.Doc
268			}
269		case *ast.Ident:
270		default:
271			return nil
272		}
273	}
274	return nil
275}
276
277// updatePkgName returns the updates to rename a pkgName in the import spec
278func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) {
279	// Modify ImportSpec syntax to add or remove the Name as needed.
280	pkg := r.packages[pkgName.Pkg()]
281	_, path, _ := pathEnclosingInterval(r.fset, pkg, pkgName.Pos(), pkgName.Pos())
282	if len(path) < 2 {
283		return nil, errors.Errorf("no path enclosing interval for %s", pkgName.Name())
284	}
285	spec, ok := path[1].(*ast.ImportSpec)
286	if !ok {
287		return nil, errors.Errorf("failed to update PkgName for %s", pkgName.Name())
288	}
289
290	var astIdent *ast.Ident // will be nil if ident is removed
291	if pkgName.Imported().Name() != r.to {
292		// ImportSpec.Name needed
293		astIdent = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to}
294	}
295
296	// Make a copy of the ident that just has the name and path.
297	updated := &ast.ImportSpec{
298		Name:   astIdent,
299		Path:   spec.Path,
300		EndPos: spec.EndPos,
301	}
302
303	rng := span.NewRange(r.fset, spec.Pos(), spec.End())
304	spn, err := rng.Span()
305	if err != nil {
306		return nil, err
307	}
308
309	var buf bytes.Buffer
310	format.Node(&buf, r.fset, updated)
311	newText := buf.String()
312
313	return &diff.TextEdit{
314		Span:    spn,
315		NewText: newText,
316	}, nil
317}
318