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)
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			lines := strings.Split(comment.Text, "\n")
220			tok := r.fset.File(comment.Pos())
221			commentLine := tok.Position(comment.Pos()).Line
222			for i, line := range lines {
223				lineStart := comment.Pos()
224				if i > 0 {
225					lineStart = tok.LineStart(commentLine + i)
226				}
227				for _, locs := range docRegexp.FindAllIndex([]byte(line), -1) {
228					rng := span.NewRange(r.fset, lineStart+token.Pos(locs[0]), lineStart+token.Pos(locs[1]))
229					spn, err := rng.Span()
230					if err != nil {
231						return nil, err
232					}
233					result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
234						Span:    spn,
235						NewText: r.to,
236					})
237				}
238			}
239		}
240	}
241
242	return result, nil
243}
244
245// docComment returns the doc for an identifier.
246func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
247	_, nodes, _ := pathEnclosingInterval(r.fset, pkg, id.Pos(), id.End())
248	for _, node := range nodes {
249		switch decl := node.(type) {
250		case *ast.FuncDecl:
251			return decl.Doc
252		case *ast.Field:
253			return decl.Doc
254		case *ast.GenDecl:
255			return decl.Doc
256		// For {Type,Value}Spec, if the doc on the spec is absent,
257		// search for the enclosing GenDecl
258		case *ast.TypeSpec:
259			if decl.Doc != nil {
260				return decl.Doc
261			}
262		case *ast.ValueSpec:
263			if decl.Doc != nil {
264				return decl.Doc
265			}
266		case *ast.Ident:
267		default:
268			return nil
269		}
270	}
271	return nil
272}
273
274// updatePkgName returns the updates to rename a pkgName in the import spec
275func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) {
276	// Modify ImportSpec syntax to add or remove the Name as needed.
277	pkg := r.packages[pkgName.Pkg()]
278	_, path, _ := pathEnclosingInterval(r.fset, pkg, pkgName.Pos(), pkgName.Pos())
279	if len(path) < 2 {
280		return nil, errors.Errorf("no path enclosing interval for %s", pkgName.Name())
281	}
282	spec, ok := path[1].(*ast.ImportSpec)
283	if !ok {
284		return nil, errors.Errorf("failed to update PkgName for %s", pkgName.Name())
285	}
286
287	var astIdent *ast.Ident // will be nil if ident is removed
288	if pkgName.Imported().Name() != r.to {
289		// ImportSpec.Name needed
290		astIdent = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to}
291	}
292
293	// Make a copy of the ident that just has the name and path.
294	updated := &ast.ImportSpec{
295		Name:   astIdent,
296		Path:   spec.Path,
297		EndPos: spec.EndPos,
298	}
299
300	rng := span.NewRange(r.fset, spec.Pos(), spec.End())
301	spn, err := rng.Span()
302	if err != nil {
303		return nil, err
304	}
305
306	var buf bytes.Buffer
307	format.Node(&buf, r.fset, updated)
308	newText := buf.String()
309
310	return &diff.TextEdit{
311		Span:    spn,
312		NewText: newText,
313	}, nil
314}
315