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 (i *IdentifierInfo) PrepareRename(ctx context.Context) (*PrepareItem, error) { 45 ctx, done := trace.StartSpan(ctx, "source.PrepareRename") 46 defer done() 47 48 // TODO(rstambler): We should handle this in a better way. 49 // If the object declaration is nil, assume it is an import spec. 50 if i.Declaration.obj == nil { 51 // Find the corresponding package name for this import spec 52 // and rename that instead. 53 ident, err := i.getPkgName(ctx) 54 if err != nil { 55 return nil, err 56 } 57 i = ident 58 } 59 60 // Do not rename builtin identifiers. 61 if i.Declaration.obj.Parent() == types.Universe { 62 return nil, errors.Errorf("cannot rename builtin %q", i.Name) 63 } 64 rng, err := i.mappedRange.Range() 65 if err != nil { 66 return nil, err 67 } 68 return &PrepareItem{ 69 Range: rng, 70 Text: i.Name, 71 }, nil 72} 73 74// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package. 75func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.URI][]protocol.TextEdit, error) { 76 ctx, done := trace.StartSpan(ctx, "source.Rename") 77 defer done() 78 79 // TODO(rstambler): We should handle this in a better way. 80 // If the object declaration is nil, assume it is an import spec. 81 if i.Declaration.obj == nil { 82 // Find the corresponding package name for this import spec 83 // and rename that instead. 84 ident, err := i.getPkgName(ctx) 85 if err != nil { 86 return nil, err 87 } 88 return ident.Rename(ctx, newName) 89 } 90 if i.Name == newName { 91 return nil, errors.Errorf("old and new names are the same: %s", newName) 92 } 93 if !isValidIdentifier(newName) { 94 return nil, errors.Errorf("invalid identifier to rename: %q", i.Name) 95 } 96 // Do not rename builtin identifiers. 97 if i.Declaration.obj.Parent() == types.Universe { 98 return nil, errors.Errorf("cannot rename builtin %q", i.Name) 99 } 100 if i.pkg == nil || i.pkg.IsIllTyped() { 101 return nil, errors.Errorf("package for %s is ill typed", i.File.File().Identity().URI) 102 } 103 // Do not rename identifiers declared in another package. 104 if i.pkg.GetTypes() != i.Declaration.obj.Pkg() { 105 return nil, errors.Errorf("failed to rename because %q is declared in package %q", i.Name, i.Declaration.obj.Pkg().Name()) 106 } 107 108 refs, err := i.References(ctx) 109 if err != nil { 110 return nil, err 111 } 112 113 r := renamer{ 114 ctx: ctx, 115 fset: i.Snapshot.View().Session().Cache().FileSet(), 116 refs: refs, 117 objsToUpdate: make(map[types.Object]bool), 118 from: i.Name, 119 to: newName, 120 packages: make(map[*types.Package]Package), 121 } 122 for _, from := range refs { 123 r.packages[from.pkg.GetTypes()] = from.pkg 124 } 125 126 // Check that the renaming of the identifier is ok. 127 for _, ref := range refs { 128 r.check(ref.obj) 129 if r.hadConflicts { // one error is enough. 130 break 131 } 132 } 133 if r.hadConflicts { 134 return nil, errors.Errorf(r.errors) 135 } 136 137 changes, err := r.update() 138 if err != nil { 139 return nil, err 140 } 141 result := make(map[span.URI][]protocol.TextEdit) 142 for uri, edits := range changes { 143 // These edits should really be associated with FileHandles for maximal correctness. 144 // For now, this is good enough. 145 f, err := i.Snapshot.View().GetFile(ctx, uri) 146 if err != nil { 147 return nil, err 148 } 149 fh := i.Snapshot.Handle(ctx, f) 150 data, _, err := fh.Read(ctx) 151 if err != nil { 152 return nil, err 153 } 154 converter := span.NewContentConverter(uri.Filename(), data) 155 m := &protocol.ColumnMapper{ 156 URI: uri, 157 Converter: converter, 158 Content: data, 159 } 160 // Sort the edits first. 161 diff.SortTextEdits(edits) 162 protocolEdits, err := ToProtocolEdits(m, edits) 163 if err != nil { 164 return nil, err 165 } 166 result[uri] = protocolEdits 167 } 168 return result, nil 169} 170 171// getPkgName gets the pkg name associated with an identifer representing 172// the import path in an import spec. 173func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error) { 174 ph, err := i.pkg.File(i.URI()) 175 if err != nil { 176 return nil, err 177 } 178 file, _, _, err := ph.Cached() 179 if err != nil { 180 return nil, err 181 } 182 var namePos token.Pos 183 for _, spec := range file.Imports { 184 if spec.Path.Pos() == i.spanRange.Start { 185 namePos = spec.Pos() 186 break 187 } 188 } 189 if !namePos.IsValid() { 190 return nil, errors.Errorf("import spec not found for %q", i.Name) 191 } 192 // Look for the object defined at NamePos. 193 for _, obj := range i.pkg.GetTypesInfo().Defs { 194 pkgName, ok := obj.(*types.PkgName) 195 if ok && pkgName.Pos() == namePos { 196 return getPkgNameIdentifier(ctx, i, pkgName) 197 } 198 } 199 for _, obj := range i.pkg.GetTypesInfo().Implicits { 200 pkgName, ok := obj.(*types.PkgName) 201 if ok && pkgName.Pos() == namePos { 202 return getPkgNameIdentifier(ctx, i, pkgName) 203 } 204 } 205 return nil, errors.Errorf("no package name for %q", i.Name) 206} 207 208// getPkgNameIdentifier returns an IdentifierInfo representing pkgName. 209// pkgName must be in the same package and file as ident. 210func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *types.PkgName) (*IdentifierInfo, error) { 211 decl := Declaration{ 212 obj: pkgName, 213 wasImplicit: true, 214 } 215 var err error 216 if decl.mappedRange, err = objToMappedRange(ctx, ident.Snapshot.View(), ident.pkg, decl.obj); err != nil { 217 return nil, err 218 } 219 if decl.node, err = objToNode(ctx, ident.Snapshot.View(), ident.pkg, decl.obj); err != nil { 220 return nil, err 221 } 222 return &IdentifierInfo{ 223 Snapshot: ident.Snapshot, 224 Name: pkgName.Name(), 225 mappedRange: decl.mappedRange, 226 File: ident.File, 227 Declaration: decl, 228 pkg: ident.pkg, 229 wasEmbeddedField: false, 230 qf: ident.qf, 231 }, nil 232} 233 234// Rename all references to the identifier. 235func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) { 236 result := make(map[span.URI][]diff.TextEdit) 237 seen := make(map[span.Span]bool) 238 239 docRegexp, err := regexp.Compile(`\b` + r.from + `\b`) 240 if err != nil { 241 return nil, err 242 } 243 for _, ref := range r.refs { 244 refSpan, err := ref.spanRange.Span() 245 if err != nil { 246 return nil, err 247 } 248 if seen[refSpan] { 249 continue 250 } 251 seen[refSpan] = true 252 253 // Renaming a types.PkgName may result in the addition or removal of an identifier, 254 // so we deal with this separately. 255 if pkgName, ok := ref.obj.(*types.PkgName); ok && ref.isDeclaration { 256 edit, err := r.updatePkgName(pkgName) 257 if err != nil { 258 return nil, err 259 } 260 result[refSpan.URI()] = append(result[refSpan.URI()], *edit) 261 continue 262 } 263 264 // Replace the identifier with r.to. 265 edit := diff.TextEdit{ 266 Span: refSpan, 267 NewText: r.to, 268 } 269 270 result[refSpan.URI()] = append(result[refSpan.URI()], edit) 271 272 if !ref.isDeclaration || ref.ident == nil { // uses do not have doc comments to update. 273 continue 274 } 275 276 doc := r.docComment(ref.pkg, ref.ident) 277 if doc == nil { 278 continue 279 } 280 281 // Perform the rename in doc comments declared in the original package. 282 for _, comment := range doc.List { 283 for _, locs := range docRegexp.FindAllStringIndex(comment.Text, -1) { 284 rng := span.NewRange(r.fset, comment.Pos()+token.Pos(locs[0]), comment.Pos()+token.Pos(locs[1])) 285 spn, err := rng.Span() 286 if err != nil { 287 return nil, err 288 } 289 result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{ 290 Span: spn, 291 NewText: r.to, 292 }) 293 } 294 } 295 } 296 297 return result, nil 298} 299 300// docComment returns the doc for an identifier. 301func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup { 302 _, nodes, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, id.Pos(), id.End()) 303 for _, node := range nodes { 304 switch decl := node.(type) { 305 case *ast.FuncDecl: 306 return decl.Doc 307 case *ast.Field: 308 return decl.Doc 309 case *ast.GenDecl: 310 return decl.Doc 311 // For {Type,Value}Spec, if the doc on the spec is absent, 312 // search for the enclosing GenDecl 313 case *ast.TypeSpec: 314 if decl.Doc != nil { 315 return decl.Doc 316 } 317 case *ast.ValueSpec: 318 if decl.Doc != nil { 319 return decl.Doc 320 } 321 case *ast.Ident: 322 default: 323 return nil 324 } 325 } 326 return nil 327} 328 329// updatePkgName returns the updates to rename a pkgName in the import spec 330func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) { 331 // Modify ImportSpec syntax to add or remove the Name as needed. 332 pkg := r.packages[pkgName.Pkg()] 333 _, path, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, pkgName.Pos(), pkgName.Pos()) 334 if len(path) < 2 { 335 return nil, errors.Errorf("no path enclosing interval for %s", pkgName.Name()) 336 } 337 spec, ok := path[1].(*ast.ImportSpec) 338 if !ok { 339 return nil, errors.Errorf("failed to update PkgName for %s", pkgName.Name()) 340 } 341 342 var astIdent *ast.Ident // will be nil if ident is removed 343 if pkgName.Imported().Name() != r.to { 344 // ImportSpec.Name needed 345 astIdent = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to} 346 } 347 348 // Make a copy of the ident that just has the name and path. 349 updated := &ast.ImportSpec{ 350 Name: astIdent, 351 Path: spec.Path, 352 EndPos: spec.EndPos, 353 } 354 355 rng := span.NewRange(r.fset, spec.Pos(), spec.End()) 356 spn, err := rng.Span() 357 if err != nil { 358 return nil, err 359 } 360 361 var buf bytes.Buffer 362 format.Node(&buf, r.fset, updated) 363 newText := buf.String() 364 365 return &diff.TextEdit{ 366 Span: spn, 367 NewText: newText, 368 }, nil 369} 370