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