github.com/v2fly/tools@v0.100.0/internal/lsp/source/rename.go (about) 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 "github.com/v2fly/tools/go/types/typeutil" 18 "github.com/v2fly/tools/internal/event" 19 "github.com/v2fly/tools/internal/lsp/diff" 20 "github.com/v2fly/tools/internal/lsp/protocol" 21 "github.com/v2fly/tools/internal/span" 22 "github.com/v2fly/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 }