blob: 6bbe91afae67498b638ea06448ede17d08b56f74 [file] [log] [blame]
Suzy Mueller1fa56832019-06-11 15:09:43 -04001// 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
Suzy Mueller4adf7a72019-06-18 10:23:37 -04005package source
6
7import (
Suzy Muellerf80f6712019-06-27 14:01:56 -04008 "bytes"
Suzy Mueller4adf7a72019-06-18 10:23:37 -04009 "context"
Robert Findley37590b32022-04-19 18:08:06 -040010 "errors"
11 "fmt"
Suzy Mueller70d37142019-06-20 15:24:17 -040012 "go/ast"
Suzy Muellerf80f6712019-06-27 14:01:56 -040013 "go/format"
Suzy Mueller4adf7a72019-06-18 10:23:37 -040014 "go/token"
15 "go/types"
Suzy Mueller70d37142019-06-20 15:24:17 -040016 "regexp"
Rebecca Stambler55a0fde2020-07-02 00:57:55 -040017 "strings"
Suzy Mueller4adf7a72019-06-18 10:23:37 -040018
Suzy Mueller1fa56832019-06-11 15:09:43 -040019 "golang.org/x/tools/go/types/typeutil"
Ian Cottrellcf0cb922020-04-17 09:32:56 -040020 "golang.org/x/tools/internal/event"
Ian Cottrell85edb9e2019-08-19 19:28:08 -040021 "golang.org/x/tools/internal/lsp/diff"
Suzy Muellerc9403062019-08-22 13:31:03 -040022 "golang.org/x/tools/internal/lsp/protocol"
Suzy Mueller4adf7a72019-06-18 10:23:37 -040023 "golang.org/x/tools/internal/span"
Suzy Mueller1fa56832019-06-11 15:09:43 -040024 "golang.org/x/tools/refactor/satisfy"
Suzy Mueller4adf7a72019-06-18 10:23:37 -040025)
26
Suzy Mueller1fa56832019-06-11 15:09:43 -040027type renamer struct {
Rebecca Stambler252024b2019-06-21 17:00:02 -040028 ctx context.Context
Suzy Mueller1fa56832019-06-11 15:09:43 -040029 fset *token.FileSet
Suzy Mueller1fa56832019-06-11 15:09:43 -040030 refs []*ReferenceInfo
31 objsToUpdate map[types.Object]bool
32 hadConflicts bool
33 errors string
34 from, to string
35 satisfyConstraints map[satisfy.Constraint]bool
Dan Kortschak40779212022-03-12 10:35:13 +103036 packages map[*types.Package]Package // may include additional packages that are a dep of pkg
Suzy Mueller1fa56832019-06-11 15:09:43 -040037 msets typeutil.MethodSetCache
38 changeMethods bool
39}
40
Suzy Muellerc9403062019-08-22 13:31:03 -040041type PrepareItem struct {
Rebecca Stambler8159a2d2019-09-05 20:04:28 -040042 Range protocol.Range
Suzy Muellerc9403062019-08-22 13:31:03 -040043 Text string
44}
45
Rob Findleyc2bea792021-01-15 15:46:22 -050046// PrepareRename searches for a valid renaming at position pp.
47//
48// The returned usererr is intended to be displayed to the user to explain why
49// the prepare fails. Probably we could eliminate the redundancy in returning
50// two errors, but for now this is done defensively.
51func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position) (_ *PrepareItem, usererr, err error) {
Dylan Le9580c842022-07-21 15:23:17 -040052 fileRenameSupported := false
53 for _, op := range snapshot.View().Options().SupportedResourceOperations {
54 if op == protocol.Rename {
55 fileRenameSupported = true
56 break
57 }
58 }
59
60 // Find position of the package name declaration
61 pgf, err := snapshot.ParseGo(ctx, f, ParseFull)
62 if err != nil {
63 return nil, err, err
64 }
65 inPackageName, err := isInPackageName(ctx, snapshot, f, pgf, pp)
66 if err != nil {
67 return nil, err, err
68 }
69
70 if inPackageName && !fileRenameSupported {
71 err := errors.New("can't rename packages: LSP client does not support file renaming")
72 return nil, err, err
73 }
74
Ian Cottrell7b212d62020-04-20 12:14:12 -040075 ctx, done := event.Start(ctx, "source.PrepareRename")
Suzy Muellerc9403062019-08-22 13:31:03 -040076 defer done()
77
Rob Findley6932d222021-08-06 15:36:09 -040078 qos, err := qualifiedObjsAtProtocolPos(ctx, snapshot, f.URI(), pp)
Rebecca Stambler8159a2d2019-09-05 20:04:28 -040079 if err != nil {
Rob Findleyc2bea792021-01-15 15:46:22 -050080 return nil, nil, err
Rebecca Stambler8159a2d2019-09-05 20:04:28 -040081 }
Rebecca Stamblereca45d42020-03-26 23:25:15 -040082 node, obj, pkg := qos[0].node, qos[0].obj, qos[0].sourcePkg
Rob Findleyc2bea792021-01-15 15:46:22 -050083 if err := checkRenamable(obj); err != nil {
84 return nil, err, err
85 }
Heschi Kreinickf29cbc72020-07-28 17:00:10 -040086 mr, err := posToMappedRange(snapshot, pkg, node.Pos(), node.End())
Muir Mandersf80fb1d2019-12-17 21:06:31 -080087 if err != nil {
Rob Findleyc2bea792021-01-15 15:46:22 -050088 return nil, nil, err
Muir Mandersf80fb1d2019-12-17 21:06:31 -080089 }
Muir Mandersf80fb1d2019-12-17 21:06:31 -080090 rng, err := mr.Range()
91 if err != nil {
Rob Findleyc2bea792021-01-15 15:46:22 -050092 return nil, nil, err
Muir Mandersf80fb1d2019-12-17 21:06:31 -080093 }
Rebecca Stamblereca45d42020-03-26 23:25:15 -040094 if _, isImport := node.(*ast.ImportSpec); isImport {
Muir Mandersf80fb1d2019-12-17 21:06:31 -080095 // We're not really renaming the import path.
96 rng.End = rng.Start
97 }
Suzy Muellerc9403062019-08-22 13:31:03 -040098 return &PrepareItem{
Rebecca Stambler8159a2d2019-09-05 20:04:28 -040099 Range: rng,
Rebecca Stamblereca45d42020-03-26 23:25:15 -0400100 Text: obj.Name(),
Rob Findleyc2bea792021-01-15 15:46:22 -0500101 }, nil, nil
Suzy Muellerc9403062019-08-22 13:31:03 -0400102}
103
Rob Findleyc2bea792021-01-15 15:46:22 -0500104// checkRenamable verifies if an obj may be renamed.
105func checkRenamable(obj types.Object) error {
106 if v, ok := obj.(*types.Var); ok && v.Embedded() {
107 return errors.New("can't rename embedded fields: rename the type directly or name the field")
108 }
109 if obj.Name() == "_" {
110 return errors.New("can't rename \"_\"")
111 }
112 return nil
113}
114
115// Rename returns a map of TextEdits for each file modified when renaming a
116// given identifier within a package.
Muir Mandersf80fb1d2019-12-17 21:06:31 -0800117func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, newName string) (map[span.URI][]protocol.TextEdit, error) {
Ian Cottrell7b212d62020-04-20 12:14:12 -0400118 ctx, done := event.Start(ctx, "source.Rename")
Ian Cottrell75aaaba2019-06-26 22:46:12 -0400119 defer done()
Rebecca Stamblerb667c4c2019-07-11 21:05:55 -0400120
Dylan Lebd3f5242022-07-29 16:50:30 -0400121 pgf, err := s.ParseGo(ctx, f, ParseFull)
122 if err != nil {
123 return nil, err
124 }
125 inPackageName, err := isInPackageName(ctx, s, f, pgf, pp)
126 if err != nil {
127 return nil, err
128 }
129
130 if inPackageName {
131 renamingPkg, err := s.PackageForFile(ctx, f.URI(), TypecheckAll, NarrowestPackage)
132 if err != nil {
133 return nil, err
134 }
135
136 result := make(map[span.URI][]protocol.TextEdit)
137 // Rename internal references to the package in the renaming package
138 // Todo(dle): need more investigation on case when pkg.GoFiles != pkg.CompiledGoFiles if using cgo.
139 for _, f := range renamingPkg.CompiledGoFiles() {
140 pkgNameMappedRange := NewMappedRange(f.Tok, f.Mapper, f.File.Name.Pos(), f.File.Name.End())
141 rng, err := pkgNameMappedRange.Range()
142 if err != nil {
143 return nil, err
144 }
145 result[f.URI] = []protocol.TextEdit{
146 {
147 Range: rng,
148 NewText: newName,
149 },
150 }
151 }
152
153 return result, nil
154 }
155
Rob Findley6932d222021-08-06 15:36:09 -0400156 qos, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp)
Suzy Mueller4adf7a72019-06-18 10:23:37 -0400157 if err != nil {
158 return nil, err
159 }
160
Rob Findleyc2bea792021-01-15 15:46:22 -0500161 obj, pkg := qos[0].obj, qos[0].pkg
Muir Mandersf80fb1d2019-12-17 21:06:31 -0800162
Rob Findleyc2bea792021-01-15 15:46:22 -0500163 if err := checkRenamable(obj); err != nil {
164 return nil, err
165 }
Muir Mandersf80fb1d2019-12-17 21:06:31 -0800166 if obj.Name() == newName {
Robert Findley37590b32022-04-19 18:08:06 -0400167 return nil, fmt.Errorf("old and new names are the same: %s", newName)
Muir Mandersf80fb1d2019-12-17 21:06:31 -0800168 }
169 if !isValidIdentifier(newName) {
Robert Findley37590b32022-04-19 18:08:06 -0400170 return nil, fmt.Errorf("invalid identifier to rename: %q", newName)
Muir Mandersf80fb1d2019-12-17 21:06:31 -0800171 }
Muir Mandersf80fb1d2019-12-17 21:06:31 -0800172 if pkg == nil || pkg.IsIllTyped() {
Robert Findley37590b32022-04-19 18:08:06 -0400173 return nil, fmt.Errorf("package for %s is ill typed", f.URI())
Muir Mandersf80fb1d2019-12-17 21:06:31 -0800174 }
Rob Findley45115c12021-01-10 11:10:32 -0500175 refs, err := references(ctx, s, qos, true, false, true)
Muir Mandersf80fb1d2019-12-17 21:06:31 -0800176 if err != nil {
177 return nil, err
178 }
Suzy Mueller1fa56832019-06-11 15:09:43 -0400179 r := renamer{
Suzy Mueller6cfa5562019-06-27 14:09:03 -0400180 ctx: ctx,
Heschi Kreinickf29cbc72020-07-28 17:00:10 -0400181 fset: s.FileSet(),
Suzy Mueller1fa56832019-06-11 15:09:43 -0400182 refs: refs,
183 objsToUpdate: make(map[types.Object]bool),
Muir Mandersf80fb1d2019-12-17 21:06:31 -0800184 from: obj.Name(),
Suzy Mueller1fa56832019-06-11 15:09:43 -0400185 to: newName,
186 packages: make(map[*types.Package]Package),
187 }
Rebecca Stamblerf8240f72020-07-12 00:26:29 -0400188
189 // A renaming initiated at an interface method indicates the
190 // intention to rename abstract and concrete methods as needed
191 // to preserve assignability.
192 for _, ref := range refs {
193 if obj, ok := ref.obj.(*types.Func); ok {
194 recv := obj.Type().(*types.Signature).Recv()
Danish Duaacefd222020-09-04 15:37:47 -0400195 if recv != nil && IsInterface(recv.Type().Underlying()) {
Rebecca Stamblerf8240f72020-07-12 00:26:29 -0400196 r.changeMethods = true
197 break
198 }
199 }
200 }
Suzy Mueller7b25e352019-07-08 18:53:01 -0700201 for _, from := range refs {
Suzy Mueller9065c182019-08-15 10:29:18 -0400202 r.packages[from.pkg.GetTypes()] = from.pkg
Suzy Mueller7b25e352019-07-08 18:53:01 -0700203 }
Suzy Mueller1fa56832019-06-11 15:09:43 -0400204
205 // Check that the renaming of the identifier is ok.
Suzy Mueller9065c182019-08-15 10:29:18 -0400206 for _, ref := range refs {
207 r.check(ref.obj)
Suzy Muellerd5940c82019-08-16 12:23:59 -0400208 if r.hadConflicts { // one error is enough.
209 break
210 }
Suzy Mueller1fa56832019-06-11 15:09:43 -0400211 }
212 if r.hadConflicts {
Robert Findley37590b32022-04-19 18:08:06 -0400213 return nil, fmt.Errorf(r.errors)
Suzy Mueller1fa56832019-06-11 15:09:43 -0400214 }
215
Suzy Muellera0f5e6c2019-07-15 17:02:40 -0400216 changes, err := r.update()
217 if err != nil {
218 return nil, err
219 }
Dylan Lebd3f5242022-07-29 16:50:30 -0400220
Rebecca Stambler2ca71802019-09-06 14:55:14 -0400221 result := make(map[span.URI][]protocol.TextEdit)
222 for uri, edits := range changes {
Rebecca Stambler2dc213d2019-09-16 18:17:51 -0400223 // These edits should really be associated with FileHandles for maximal correctness.
224 // For now, this is good enough.
Heschi Kreinickecd3fc42020-06-08 15:21:24 -0400225 fh, err := s.GetFile(ctx, uri)
Rebecca Stambler2ca71802019-09-06 14:55:14 -0400226 if err != nil {
227 return nil, err
228 }
Heschi Kreinickecd3fc42020-06-08 15:21:24 -0400229 data, err := fh.Read()
Rebecca Stambler2dc213d2019-09-16 18:17:51 -0400230 if err != nil {
231 return nil, err
232 }
Robert Findley9d7bf952022-05-12 23:23:17 -0400233 m := protocol.NewColumnMapper(uri, data)
Rebecca Stambler2dc213d2019-09-16 18:17:51 -0400234 // Sort the edits first.
235 diff.SortTextEdits(edits)
Rebecca Stambler2ca71802019-09-06 14:55:14 -0400236 protocolEdits, err := ToProtocolEdits(m, edits)
237 if err != nil {
238 return nil, err
239 }
240 result[uri] = protocolEdits
Suzy Muellera0f5e6c2019-07-15 17:02:40 -0400241 }
Rebecca Stambler2ca71802019-09-06 14:55:14 -0400242 return result, nil
Suzy Mueller1fa56832019-06-11 15:09:43 -0400243}
244
245// Rename all references to the identifier.
Ian Cottrell85edb9e2019-08-19 19:28:08 -0400246func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
247 result := make(map[span.URI][]diff.TextEdit)
Suzy Mueller7b25e352019-07-08 18:53:01 -0700248 seen := make(map[span.Span]bool)
Suzy Mueller1fa56832019-06-11 15:09:43 -0400249
Edward Mullerd1f65652019-06-24 21:48:30 +0000250 docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
251 if err != nil {
252 return nil, err
253 }
Suzy Mueller1fa56832019-06-11 15:09:43 -0400254 for _, ref := range r.refs {
Robert Findleyea608152022-05-23 18:50:04 -0400255 refSpan, err := ref.Span()
Suzy Mueller4adf7a72019-06-18 10:23:37 -0400256 if err != nil {
257 return nil, err
258 }
Suzy Mueller7b25e352019-07-08 18:53:01 -0700259 if seen[refSpan] {
260 continue
261 }
262 seen[refSpan] = true
Suzy Mueller4adf7a72019-06-18 10:23:37 -0400263
Suzy Muellerf80f6712019-06-27 14:01:56 -0400264 // Renaming a types.PkgName may result in the addition or removal of an identifier,
265 // so we deal with this separately.
266 if pkgName, ok := ref.obj.(*types.PkgName); ok && ref.isDeclaration {
267 edit, err := r.updatePkgName(pkgName)
268 if err != nil {
269 return nil, err
270 }
271 result[refSpan.URI()] = append(result[refSpan.URI()], *edit)
272 continue
273 }
274
275 // Replace the identifier with r.to.
Ian Cottrell85edb9e2019-08-19 19:28:08 -0400276 edit := diff.TextEdit{
Suzy Mueller4adf7a72019-06-18 10:23:37 -0400277 Span: refSpan,
Suzy Mueller1fa56832019-06-11 15:09:43 -0400278 NewText: r.to,
Suzy Mueller4adf7a72019-06-18 10:23:37 -0400279 }
Suzy Mueller6cfa5562019-06-27 14:09:03 -0400280
Suzy Mueller1fa56832019-06-11 15:09:43 -0400281 result[refSpan.URI()] = append(result[refSpan.URI()], edit)
Suzy Mueller70d37142019-06-20 15:24:17 -0400282
Suzy Muellerf80f6712019-06-27 14:01:56 -0400283 if !ref.isDeclaration || ref.ident == nil { // uses do not have doc comments to update.
Edward Muller1a55b152019-06-27 16:17:07 +0000284 continue
Suzy Mueller70d37142019-06-20 15:24:17 -0400285 }
286
Suzy Mueller9065c182019-08-15 10:29:18 -0400287 doc := r.docComment(ref.pkg, ref.ident)
Suzy Muellerf80f6712019-06-27 14:01:56 -0400288 if doc == nil {
Edward Muller1a55b152019-06-27 16:17:07 +0000289 continue
290 }
291
Suzy Muellerf80f6712019-06-27 14:01:56 -0400292 // Perform the rename in doc comments declared in the original package.
Rebecca Stambler55a0fde2020-07-02 00:57:55 -0400293 // go/parser strips out \r\n returns from the comment text, so go
294 // line-by-line through the comment text to get the correct positions.
Edward Muller1a55b152019-06-27 16:17:07 +0000295 for _, comment := range doc.List {
Rebecca Stamblerc64668f2020-11-02 22:59:26 -0500296 if isDirective(comment.Text) {
297 continue
298 }
Rebecca Stambler55a0fde2020-07-02 00:57:55 -0400299 lines := strings.Split(comment.Text, "\n")
Alan Donovanb929f3b2022-06-01 15:20:56 -0400300 tokFile := r.fset.File(comment.Pos())
301 commentLine := tokFile.Line(comment.Pos())
Rebecca Stambler55a0fde2020-07-02 00:57:55 -0400302 for i, line := range lines {
303 lineStart := comment.Pos()
304 if i > 0 {
Alan Donovanb929f3b2022-06-01 15:20:56 -0400305 lineStart = tokFile.LineStart(commentLine + i)
Edward Muller1a55b152019-06-27 16:17:07 +0000306 }
Rebecca Stambler55a0fde2020-07-02 00:57:55 -0400307 for _, locs := range docRegexp.FindAllIndex([]byte(line), -1) {
Alan Donovanb929f3b2022-06-01 15:20:56 -0400308 rng := span.NewRange(tokFile, lineStart+token.Pos(locs[0]), lineStart+token.Pos(locs[1]))
Rebecca Stambler55a0fde2020-07-02 00:57:55 -0400309 spn, err := rng.Span()
310 if err != nil {
311 return nil, err
312 }
313 result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
314 Span: spn,
315 NewText: r.to,
316 })
317 }
Edward Muller1a55b152019-06-27 16:17:07 +0000318 }
319 }
Suzy Mueller4adf7a72019-06-18 10:23:37 -0400320 }
321
Suzy Mueller1fa56832019-06-11 15:09:43 -0400322 return result, nil
Suzy Mueller4adf7a72019-06-18 10:23:37 -0400323}
Suzy Mueller70d37142019-06-20 15:24:17 -0400324
325// docComment returns the doc for an identifier.
326func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
Alan Donovanb929f3b2022-06-01 15:20:56 -0400327 _, tokFile, nodes, _ := pathEnclosingInterval(r.fset, pkg, id.Pos(), id.End())
Suzy Mueller70d37142019-06-20 15:24:17 -0400328 for _, node := range nodes {
329 switch decl := node.(type) {
330 case *ast.FuncDecl:
331 return decl.Doc
332 case *ast.Field:
333 return decl.Doc
334 case *ast.GenDecl:
335 return decl.Doc
336 // For {Type,Value}Spec, if the doc on the spec is absent,
337 // search for the enclosing GenDecl
338 case *ast.TypeSpec:
339 if decl.Doc != nil {
340 return decl.Doc
341 }
342 case *ast.ValueSpec:
343 if decl.Doc != nil {
344 return decl.Doc
345 }
346 case *ast.Ident:
Shoshin Nikitaccff7322021-06-11 16:53:29 +0000347 case *ast.AssignStmt:
348 // *ast.AssignStmt doesn't have an associated comment group.
349 // So, we try to find a comment just before the identifier.
350
351 // Try to find a comment group only for short variable declarations (:=).
352 if decl.Tok != token.DEFINE {
353 return nil
354 }
355
Alan Donovanb929f3b2022-06-01 15:20:56 -0400356 identLine := tokFile.Line(id.Pos())
357 for _, comment := range nodes[len(nodes)-1].(*ast.File).Comments {
Shoshin Nikitaccff7322021-06-11 16:53:29 +0000358 if comment.Pos() > id.Pos() {
359 // Comment is after the identifier.
360 continue
361 }
362
Alan Donovanb929f3b2022-06-01 15:20:56 -0400363 lastCommentLine := tokFile.Line(comment.End())
Shoshin Nikitaccff7322021-06-11 16:53:29 +0000364 if lastCommentLine+1 == identLine {
365 return comment
366 }
367 }
Suzy Mueller70d37142019-06-20 15:24:17 -0400368 default:
369 return nil
370 }
371 }
372 return nil
373}
Suzy Muellerf80f6712019-06-27 14:01:56 -0400374
375// updatePkgName returns the updates to rename a pkgName in the import spec
Ian Cottrell85edb9e2019-08-19 19:28:08 -0400376func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) {
Suzy Muellerf80f6712019-06-27 14:01:56 -0400377 // Modify ImportSpec syntax to add or remove the Name as needed.
378 pkg := r.packages[pkgName.Pkg()]
Alan Donovanb929f3b2022-06-01 15:20:56 -0400379 _, tokFile, path, _ := pathEnclosingInterval(r.fset, pkg, pkgName.Pos(), pkgName.Pos())
Suzy Muellerf80f6712019-06-27 14:01:56 -0400380 if len(path) < 2 {
Robert Findley37590b32022-04-19 18:08:06 -0400381 return nil, fmt.Errorf("no path enclosing interval for %s", pkgName.Name())
Suzy Muellerf80f6712019-06-27 14:01:56 -0400382 }
383 spec, ok := path[1].(*ast.ImportSpec)
384 if !ok {
Robert Findley37590b32022-04-19 18:08:06 -0400385 return nil, fmt.Errorf("failed to update PkgName for %s", pkgName.Name())
Suzy Muellerf80f6712019-06-27 14:01:56 -0400386 }
387
388 var astIdent *ast.Ident // will be nil if ident is removed
389 if pkgName.Imported().Name() != r.to {
390 // ImportSpec.Name needed
391 astIdent = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to}
392 }
393
394 // Make a copy of the ident that just has the name and path.
395 updated := &ast.ImportSpec{
396 Name: astIdent,
397 Path: spec.Path,
398 EndPos: spec.EndPos,
399 }
400
Alan Donovanb929f3b2022-06-01 15:20:56 -0400401 rng := span.NewRange(tokFile, spec.Pos(), spec.End())
Suzy Muellerf80f6712019-06-27 14:01:56 -0400402 spn, err := rng.Span()
403 if err != nil {
404 return nil, err
405 }
406
407 var buf bytes.Buffer
408 format.Node(&buf, r.fset, updated)
409 newText := buf.String()
410
Ian Cottrell85edb9e2019-08-19 19:28:08 -0400411 return &diff.TextEdit{
Suzy Muellerf80f6712019-06-27 14:01:56 -0400412 Span: spn,
413 NewText: newText,
414 }, nil
415}