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.
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"
30 objsToUpdate map[types.Object]bool
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
40 type PrepareItem struct {
45 // PrepareRename searches for a valid renaming at position pp.
47 // The returned usererr is intended to be displayed to the user to explain why
48 // the prepare fails. Probably we could eliminate the redundancy in returning
49 // two errors, but for now this is done defensively.
50 func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position) (_ *PrepareItem, usererr, err error) {
51 ctx, done := event.Start(ctx, "source.PrepareRename")
54 qos, err := qualifiedObjsAtProtocolPos(ctx, snapshot, f, pp)
58 node, obj, pkg := qos[0].node, qos[0].obj, qos[0].sourcePkg
59 if err := checkRenamable(obj); err != nil {
62 mr, err := posToMappedRange(snapshot, pkg, node.Pos(), node.End())
66 rng, err := mr.Range()
70 if _, isImport := node.(*ast.ImportSpec); isImport {
71 // We're not really renaming the import path.
80 // checkRenamable verifies if an obj may be renamed.
81 func checkRenamable(obj types.Object) error {
82 if v, ok := obj.(*types.Var); ok && v.Embedded() {
83 return errors.New("can't rename embedded fields: rename the type directly or name the field")
85 if obj.Name() == "_" {
86 return errors.New("can't rename \"_\"")
91 // Rename returns a map of TextEdits for each file modified when renaming a
92 // given identifier within a package.
93 func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, newName string) (map[span.URI][]protocol.TextEdit, error) {
94 ctx, done := event.Start(ctx, "source.Rename")
97 qos, err := qualifiedObjsAtProtocolPos(ctx, s, f, pp)
102 obj, pkg := qos[0].obj, qos[0].pkg
104 if err := checkRenamable(obj); err != nil {
107 if obj.Name() == newName {
108 return nil, errors.Errorf("old and new names are the same: %s", newName)
110 if !isValidIdentifier(newName) {
111 return nil, errors.Errorf("invalid identifier to rename: %q", newName)
113 if pkg == nil || pkg.IsIllTyped() {
114 return nil, errors.Errorf("package for %s is ill typed", f.URI())
116 refs, err := references(ctx, s, qos, true, false, true)
124 objsToUpdate: make(map[types.Object]bool),
127 packages: make(map[*types.Package]Package),
130 // A renaming initiated at an interface method indicates the
131 // intention to rename abstract and concrete methods as needed
132 // to preserve assignability.
133 for _, ref := range refs {
134 if obj, ok := ref.obj.(*types.Func); ok {
135 recv := obj.Type().(*types.Signature).Recv()
136 if recv != nil && IsInterface(recv.Type().Underlying()) {
137 r.changeMethods = true
142 for _, from := range refs {
143 r.packages[from.pkg.GetTypes()] = from.pkg
146 // Check that the renaming of the identifier is ok.
147 for _, ref := range refs {
149 if r.hadConflicts { // one error is enough.
154 return nil, errors.Errorf(r.errors)
157 changes, err := r.update()
161 result := make(map[span.URI][]protocol.TextEdit)
162 for uri, edits := range changes {
163 // These edits should really be associated with FileHandles for maximal correctness.
164 // For now, this is good enough.
165 fh, err := s.GetFile(ctx, uri)
169 data, err := fh.Read()
173 converter := span.NewContentConverter(uri.Filename(), data)
174 m := &protocol.ColumnMapper{
176 Converter: converter,
179 // Sort the edits first.
180 diff.SortTextEdits(edits)
181 protocolEdits, err := ToProtocolEdits(m, edits)
185 result[uri] = protocolEdits
190 // Rename all references to the identifier.
191 func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
192 result := make(map[span.URI][]diff.TextEdit)
193 seen := make(map[span.Span]bool)
195 docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
199 for _, ref := range r.refs {
200 refSpan, err := ref.spanRange.Span()
209 // Renaming a types.PkgName may result in the addition or removal of an identifier,
210 // so we deal with this separately.
211 if pkgName, ok := ref.obj.(*types.PkgName); ok && ref.isDeclaration {
212 edit, err := r.updatePkgName(pkgName)
216 result[refSpan.URI()] = append(result[refSpan.URI()], *edit)
220 // Replace the identifier with r.to.
221 edit := diff.TextEdit{
226 result[refSpan.URI()] = append(result[refSpan.URI()], edit)
228 if !ref.isDeclaration || ref.ident == nil { // uses do not have doc comments to update.
232 doc := r.docComment(ref.pkg, ref.ident)
237 // Perform the rename in doc comments declared in the original package.
238 // go/parser strips out \r\n returns from the comment text, so go
239 // line-by-line through the comment text to get the correct positions.
240 for _, comment := range doc.List {
241 if isDirective(comment.Text) {
244 lines := strings.Split(comment.Text, "\n")
245 tok := r.fset.File(comment.Pos())
246 commentLine := tok.Position(comment.Pos()).Line
247 for i, line := range lines {
248 lineStart := comment.Pos()
250 lineStart = tok.LineStart(commentLine + i)
252 for _, locs := range docRegexp.FindAllIndex([]byte(line), -1) {
253 rng := span.NewRange(r.fset, lineStart+token.Pos(locs[0]), lineStart+token.Pos(locs[1]))
254 spn, err := rng.Span()
258 result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
270 // docComment returns the doc for an identifier.
271 func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
272 _, nodes, _ := pathEnclosingInterval(r.fset, pkg, id.Pos(), id.End())
273 for _, node := range nodes {
274 switch decl := node.(type) {
281 // For {Type,Value}Spec, if the doc on the spec is absent,
282 // search for the enclosing GenDecl
299 // updatePkgName returns the updates to rename a pkgName in the import spec
300 func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) {
301 // Modify ImportSpec syntax to add or remove the Name as needed.
302 pkg := r.packages[pkgName.Pkg()]
303 _, path, _ := pathEnclosingInterval(r.fset, pkg, pkgName.Pos(), pkgName.Pos())
305 return nil, errors.Errorf("no path enclosing interval for %s", pkgName.Name())
307 spec, ok := path[1].(*ast.ImportSpec)
309 return nil, errors.Errorf("failed to update PkgName for %s", pkgName.Name())
312 var astIdent *ast.Ident // will be nil if ident is removed
313 if pkgName.Imported().Name() != r.to {
314 // ImportSpec.Name needed
315 astIdent = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to}
318 // Make a copy of the ident that just has the name and path.
319 updated := &ast.ImportSpec{
325 rng := span.NewRange(r.fset, spec.Pos(), spec.End())
326 spn, err := rng.Span()
332 format.Node(&buf, r.fset, updated)
333 newText := buf.String()
335 return &diff.TextEdit{