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) 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 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. 246func (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 275func (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} 315