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 16 "golang.org/x/tools/go/types/typeutil" 17 "golang.org/x/tools/internal/lsp/diff" 18 "golang.org/x/tools/internal/lsp/protocol" 19 "golang.org/x/tools/internal/span" 20 "golang.org/x/tools/internal/telemetry/trace" 21 "golang.org/x/tools/refactor/satisfy" 22 errors "golang.org/x/xerrors" 23) 24 25type renamer struct { 26 ctx context.Context 27 fset *token.FileSet 28 refs []*ReferenceInfo 29 objsToUpdate map[types.Object]bool 30 hadConflicts bool 31 errors string 32 from, to string 33 satisfyConstraints map[satisfy.Constraint]bool 34 packages map[*types.Package]Package // may include additional packages that are a rdep of pkg 35 msets typeutil.MethodSetCache 36 changeMethods bool 37} 38 39type PrepareItem struct { 40 Range protocol.Range 41 Text string 42} 43 44func PrepareRename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) (*PrepareItem, error) { 45 ctx, done := trace.StartSpan(ctx, "source.PrepareRename") 46 defer done() 47 48 qos, err := qualifiedObjsAtProtocolPos(ctx, s, f, pp) 49 if err != nil { 50 return nil, err 51 } 52 53 // Do not rename builtin identifiers. 54 if qos[0].obj.Parent() == types.Universe { 55 return nil, errors.Errorf("cannot rename builtin %q", qos[0].obj.Name()) 56 } 57 58 mr, err := posToMappedRange(s.View(), qos[0].sourcePkg, qos[0].node.Pos(), qos[0].node.End()) 59 if err != nil { 60 return nil, err 61 } 62 63 rng, err := mr.Range() 64 if err != nil { 65 return nil, err 66 } 67 68 if _, isImport := qos[0].node.(*ast.ImportSpec); isImport { 69 // We're not really renaming the import path. 70 rng.End = rng.Start 71 } 72 73 return &PrepareItem{ 74 Range: rng, 75 Text: qos[0].obj.Name(), 76 }, nil 77} 78 79// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package. 80func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, newName string) (map[span.URI][]protocol.TextEdit, error) { 81 ctx, done := trace.StartSpan(ctx, "source.Rename") 82 defer done() 83 84 qos, err := qualifiedObjsAtProtocolPos(ctx, s, f, pp) 85 if err != nil { 86 return nil, err 87 } 88 89 obj := qos[0].obj 90 pkg := qos[0].pkg 91 92 if obj.Name() == newName { 93 return nil, errors.Errorf("old and new names are the same: %s", newName) 94 } 95 if !isValidIdentifier(newName) { 96 return nil, errors.Errorf("invalid identifier to rename: %q", newName) 97 } 98 // Do not rename builtin identifiers. 99 if obj.Parent() == types.Universe { 100 return nil, errors.Errorf("cannot rename builtin %q", obj.Name()) 101 } 102 if pkg == nil || pkg.IsIllTyped() { 103 return nil, errors.Errorf("package for %s is ill typed", f.Identity().URI) 104 } 105 106 refs, err := References(ctx, s, f, pp, true) 107 if err != nil { 108 return nil, err 109 } 110 111 r := renamer{ 112 ctx: ctx, 113 fset: s.View().Session().Cache().FileSet(), 114 refs: refs, 115 objsToUpdate: make(map[types.Object]bool), 116 from: obj.Name(), 117 to: newName, 118 packages: make(map[*types.Package]Package), 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(uri) 144 if err != nil { 145 return nil, err 146 } 147 data, _, err := fh.Read(ctx) 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 for _, comment := range doc.List { 217 for _, locs := range docRegexp.FindAllStringIndex(comment.Text, -1) { 218 rng := span.NewRange(r.fset, comment.Pos()+token.Pos(locs[0]), comment.Pos()+token.Pos(locs[1])) 219 spn, err := rng.Span() 220 if err != nil { 221 return nil, err 222 } 223 result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{ 224 Span: spn, 225 NewText: r.to, 226 }) 227 } 228 } 229 } 230 231 return result, nil 232} 233 234// docComment returns the doc for an identifier. 235func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup { 236 _, nodes, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, id.Pos(), id.End()) 237 for _, node := range nodes { 238 switch decl := node.(type) { 239 case *ast.FuncDecl: 240 return decl.Doc 241 case *ast.Field: 242 return decl.Doc 243 case *ast.GenDecl: 244 return decl.Doc 245 // For {Type,Value}Spec, if the doc on the spec is absent, 246 // search for the enclosing GenDecl 247 case *ast.TypeSpec: 248 if decl.Doc != nil { 249 return decl.Doc 250 } 251 case *ast.ValueSpec: 252 if decl.Doc != nil { 253 return decl.Doc 254 } 255 case *ast.Ident: 256 default: 257 return nil 258 } 259 } 260 return nil 261} 262 263// updatePkgName returns the updates to rename a pkgName in the import spec 264func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) { 265 // Modify ImportSpec syntax to add or remove the Name as needed. 266 pkg := r.packages[pkgName.Pkg()] 267 _, path, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, pkgName.Pos(), pkgName.Pos()) 268 if len(path) < 2 { 269 return nil, errors.Errorf("no path enclosing interval for %s", pkgName.Name()) 270 } 271 spec, ok := path[1].(*ast.ImportSpec) 272 if !ok { 273 return nil, errors.Errorf("failed to update PkgName for %s", pkgName.Name()) 274 } 275 276 var astIdent *ast.Ident // will be nil if ident is removed 277 if pkgName.Imported().Name() != r.to { 278 // ImportSpec.Name needed 279 astIdent = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to} 280 } 281 282 // Make a copy of the ident that just has the name and path. 283 updated := &ast.ImportSpec{ 284 Name: astIdent, 285 Path: spec.Path, 286 EndPos: spec.EndPos, 287 } 288 289 rng := span.NewRange(r.fset, spec.Pos(), spec.End()) 290 spn, err := rng.Span() 291 if err != nil { 292 return nil, err 293 } 294 295 var buf bytes.Buffer 296 format.Node(&buf, r.fset, updated) 297 newText := buf.String() 298 299 return &diff.TextEdit{ 300 Span: spn, 301 NewText: newText, 302 }, nil 303} 304