Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / 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 func 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.
73 func 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.
169 func (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.
246 func (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
275 func (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 }