.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / internal / lsp / source / rename.go
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
5 package source
6
7 import (
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
26 type 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
40 type PrepareItem struct {
41         Range protocol.Range
42         Text  string
43 }
44
45 // PrepareRename searches for a valid renaming at position pp.
46 //
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")
52         defer done()
53
54         qos, err := qualifiedObjsAtProtocolPos(ctx, snapshot, f, pp)
55         if err != nil {
56                 return nil, nil, err
57         }
58         node, obj, pkg := qos[0].node, qos[0].obj, qos[0].sourcePkg
59         if err := checkRenamable(obj); err != nil {
60                 return nil, err, err
61         }
62         mr, err := posToMappedRange(snapshot, pkg, node.Pos(), node.End())
63         if err != nil {
64                 return nil, nil, err
65         }
66         rng, err := mr.Range()
67         if err != nil {
68                 return nil, nil, err
69         }
70         if _, isImport := node.(*ast.ImportSpec); isImport {
71                 // We're not really renaming the import path.
72                 rng.End = rng.Start
73         }
74         return &PrepareItem{
75                 Range: rng,
76                 Text:  obj.Name(),
77         }, nil, nil
78 }
79
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")
84         }
85         if obj.Name() == "_" {
86                 return errors.New("can't rename \"_\"")
87         }
88         return nil
89 }
90
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")
95         defer done()
96
97         qos, err := qualifiedObjsAtProtocolPos(ctx, s, f, pp)
98         if err != nil {
99                 return nil, err
100         }
101
102         obj, pkg := qos[0].obj, qos[0].pkg
103
104         if err := checkRenamable(obj); err != nil {
105                 return nil, err
106         }
107         if obj.Name() == newName {
108                 return nil, errors.Errorf("old and new names are the same: %s", newName)
109         }
110         if !isValidIdentifier(newName) {
111                 return nil, errors.Errorf("invalid identifier to rename: %q", newName)
112         }
113         if pkg == nil || pkg.IsIllTyped() {
114                 return nil, errors.Errorf("package for %s is ill typed", f.URI())
115         }
116         refs, err := references(ctx, s, qos, true, false, true)
117         if err != nil {
118                 return nil, err
119         }
120         r := renamer{
121                 ctx:          ctx,
122                 fset:         s.FileSet(),
123                 refs:         refs,
124                 objsToUpdate: make(map[types.Object]bool),
125                 from:         obj.Name(),
126                 to:           newName,
127                 packages:     make(map[*types.Package]Package),
128         }
129
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
138                                 break
139                         }
140                 }
141         }
142         for _, from := range refs {
143                 r.packages[from.pkg.GetTypes()] = from.pkg
144         }
145
146         // Check that the renaming of the identifier is ok.
147         for _, ref := range refs {
148                 r.check(ref.obj)
149                 if r.hadConflicts { // one error is enough.
150                         break
151                 }
152         }
153         if r.hadConflicts {
154                 return nil, errors.Errorf(r.errors)
155         }
156
157         changes, err := r.update()
158         if err != nil {
159                 return nil, err
160         }
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)
166                 if err != nil {
167                         return nil, err
168                 }
169                 data, err := fh.Read()
170                 if err != nil {
171                         return nil, err
172                 }
173                 converter := span.NewContentConverter(uri.Filename(), data)
174                 m := &protocol.ColumnMapper{
175                         URI:       uri,
176                         Converter: converter,
177                         Content:   data,
178                 }
179                 // Sort the edits first.
180                 diff.SortTextEdits(edits)
181                 protocolEdits, err := ToProtocolEdits(m, edits)
182                 if err != nil {
183                         return nil, err
184                 }
185                 result[uri] = protocolEdits
186         }
187         return result, nil
188 }
189
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)
194
195         docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
196         if err != nil {
197                 return nil, err
198         }
199         for _, ref := range r.refs {
200                 refSpan, err := ref.spanRange.Span()
201                 if err != nil {
202                         return nil, err
203                 }
204                 if seen[refSpan] {
205                         continue
206                 }
207                 seen[refSpan] = true
208
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)
213                         if err != nil {
214                                 return nil, err
215                         }
216                         result[refSpan.URI()] = append(result[refSpan.URI()], *edit)
217                         continue
218                 }
219
220                 // Replace the identifier with r.to.
221                 edit := diff.TextEdit{
222                         Span:    refSpan,
223                         NewText: r.to,
224                 }
225
226                 result[refSpan.URI()] = append(result[refSpan.URI()], edit)
227
228                 if !ref.isDeclaration || ref.ident == nil { // uses do not have doc comments to update.
229                         continue
230                 }
231
232                 doc := r.docComment(ref.pkg, ref.ident)
233                 if doc == nil {
234                         continue
235                 }
236
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) {
242                                 continue
243                         }
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()
249                                 if i > 0 {
250                                         lineStart = tok.LineStart(commentLine + i)
251                                 }
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()
255                                         if err != nil {
256                                                 return nil, err
257                                         }
258                                         result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
259                                                 Span:    spn,
260                                                 NewText: r.to,
261                                         })
262                                 }
263                         }
264                 }
265         }
266
267         return result, nil
268 }
269
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) {
275                 case *ast.FuncDecl:
276                         return decl.Doc
277                 case *ast.Field:
278                         return decl.Doc
279                 case *ast.GenDecl:
280                         return decl.Doc
281                 // For {Type,Value}Spec, if the doc on the spec is absent,
282                 // search for the enclosing GenDecl
283                 case *ast.TypeSpec:
284                         if decl.Doc != nil {
285                                 return decl.Doc
286                         }
287                 case *ast.ValueSpec:
288                         if decl.Doc != nil {
289                                 return decl.Doc
290                         }
291                 case *ast.Ident:
292                 default:
293                         return nil
294                 }
295         }
296         return nil
297 }
298
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())
304         if len(path) < 2 {
305                 return nil, errors.Errorf("no path enclosing interval for %s", pkgName.Name())
306         }
307         spec, ok := path[1].(*ast.ImportSpec)
308         if !ok {
309                 return nil, errors.Errorf("failed to update PkgName for %s", pkgName.Name())
310         }
311
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}
316         }
317
318         // Make a copy of the ident that just has the name and path.
319         updated := &ast.ImportSpec{
320                 Name:   astIdent,
321                 Path:   spec.Path,
322                 EndPos: spec.EndPos,
323         }
324
325         rng := span.NewRange(r.fset, spec.Pos(), spec.End())
326         spn, err := rng.Span()
327         if err != nil {
328                 return nil, err
329         }
330
331         var buf bytes.Buffer
332         format.Node(&buf, r.fset, updated)
333         newText := buf.String()
334
335         return &diff.TextEdit{
336                 Span:    spn,
337                 NewText: newText,
338         }, nil
339 }