1
0
mirror of https://github.com/golang/go synced 2024-11-19 03:44:40 -07:00
go/internal/lsp/source/rename.go

160 lines
4.3 KiB
Go
Raw Normal View History

// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
"regexp"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/refactor/satisfy"
)
type renamer struct {
fset *token.FileSet
pkg Package // the package containing the declaration of the ident
refs []*ReferenceInfo
objsToUpdate map[types.Object]bool
hadConflicts bool
errors string
from, to string
satisfyConstraints map[satisfy.Constraint]bool
packages map[*types.Package]Package // may include additional packages that are a rdep of pkg
msets typeutil.MethodSetCache
changeMethods bool
}
// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package.
func Rename(ctx context.Context, view View, f GoFile, pos token.Pos, newName string) (map[span.URI][]TextEdit, error) {
pkg := f.GetPackage(ctx)
if pkg == nil || pkg.IsIllTyped() {
return nil, fmt.Errorf("package for %s is ill typed", f.URI())
}
// Get the identifier to rename.
ident, err := Identifier(ctx, view, f, pos)
if err != nil {
return nil, err
}
if ident.Name == newName {
return nil, fmt.Errorf("old and new names are the same: %s", newName)
}
if !isValidIdentifier(ident.Name) {
return nil, fmt.Errorf("invalid identifier to rename: %q", ident.Name)
}
// Do not rename identifiers declared in another package.
if pkg.GetTypes() != ident.decl.obj.Pkg() {
return nil, fmt.Errorf("failed to rename because %q is declared in package %q", ident.Name, ident.decl.obj.Pkg().Name())
}
// TODO(suzmue): Support renaming of imported packages.
if _, ok := ident.decl.obj.(*types.PkgName); ok {
return nil, fmt.Errorf("renaming imported package %s not supported", ident.Name)
}
refs, err := ident.References(ctx)
if err != nil {
return nil, err
}
r := renamer{
fset: f.FileSet(),
pkg: pkg,
refs: refs,
objsToUpdate: make(map[types.Object]bool),
from: ident.Name,
to: newName,
packages: make(map[*types.Package]Package),
}
r.packages[pkg.GetTypes()] = pkg
// Check that the renaming of the identifier is ok.
for _, from := range refs {
r.check(from.obj)
}
if r.hadConflicts {
return nil, fmt.Errorf(r.errors)
}
return r.update(ctx, view)
}
// Rename all references to the identifier.
func (r *renamer) update(ctx context.Context, view View) (map[span.URI][]TextEdit, error) {
result := make(map[span.URI][]TextEdit)
docRegexp := regexp.MustCompile(`\b` + r.from + `\b`)
for _, ref := range r.refs {
refSpan, err := ref.Range.Span()
if err != nil {
return nil, err
}
edit := TextEdit{
Span: refSpan,
NewText: r.to,
}
result[refSpan.URI()] = append(result[refSpan.URI()], edit)
if ref.isDeclaration {
// Perform the rename in doc comments too (declared in the original package)
if doc := r.docComment(r.pkg, ref.ident); doc != nil {
for _, comment := range doc.List {
for _, locs := range docRegexp.FindAllStringIndex(comment.Text, -1) {
rng := span.NewRange(r.fset, comment.Pos()+token.Pos(locs[0]), comment.Pos()+token.Pos(locs[1]))
spn, err := rng.Span()
if err != nil {
return nil, err
}
result[refSpan.URI()] = append(result[refSpan.URI()], TextEdit{
Span: spn,
NewText: r.to,
})
}
comment.Text = docRegexp.ReplaceAllString(comment.Text, r.to)
}
}
}
}
return result, nil
}
// docComment returns the doc for an identifier.
func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
_, nodes, _ := pathEnclosingInterval(r.fset, pkg, id.Pos(), id.End())
for _, node := range nodes {
switch decl := node.(type) {
case *ast.FuncDecl:
return decl.Doc
case *ast.Field:
return decl.Doc
case *ast.GenDecl:
return decl.Doc
// For {Type,Value}Spec, if the doc on the spec is absent,
// search for the enclosing GenDecl
case *ast.TypeSpec:
if decl.Doc != nil {
return decl.Doc
}
case *ast.ValueSpec:
if decl.Doc != nil {
return decl.Doc
}
case *ast.Ident:
default:
return nil
}
}
return nil
}