2019-06-11 13:09:43 -06:00
|
|
|
// 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.
|
|
|
|
|
2019-06-18 08:23:37 -06:00
|
|
|
package source
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2019-06-20 13:24:17 -06:00
|
|
|
"go/ast"
|
2019-06-18 08:23:37 -06:00
|
|
|
"go/token"
|
|
|
|
"go/types"
|
2019-06-20 13:24:17 -06:00
|
|
|
"regexp"
|
2019-06-18 08:23:37 -06:00
|
|
|
|
2019-06-11 13:09:43 -06:00
|
|
|
"golang.org/x/tools/go/types/typeutil"
|
2019-06-18 08:23:37 -06:00
|
|
|
"golang.org/x/tools/internal/span"
|
2019-06-11 13:09:43 -06:00
|
|
|
"golang.org/x/tools/refactor/satisfy"
|
2019-06-18 08:23:37 -06:00
|
|
|
)
|
|
|
|
|
2019-06-11 13:09:43 -06:00
|
|
|
type renamer struct {
|
2019-06-21 15:00:02 -06:00
|
|
|
ctx context.Context
|
2019-06-11 13:09:43 -06:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-06-18 08:23:37 -06:00
|
|
|
// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package.
|
2019-06-21 15:00:02 -06:00
|
|
|
func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.URI][]TextEdit, error) {
|
|
|
|
if i.Name == newName {
|
2019-06-18 08:23:37 -06:00
|
|
|
return nil, fmt.Errorf("old and new names are the same: %s", newName)
|
|
|
|
}
|
2019-06-21 15:00:02 -06:00
|
|
|
if !isValidIdentifier(i.Name) {
|
|
|
|
return nil, fmt.Errorf("invalid identifier to rename: %q", i.Name)
|
2019-06-11 13:09:43 -06:00
|
|
|
}
|
2019-06-18 08:23:37 -06:00
|
|
|
|
|
|
|
// Do not rename identifiers declared in another package.
|
2019-06-24 14:34:21 -06:00
|
|
|
if i.pkg == nil || i.pkg.IsIllTyped() {
|
2019-06-21 15:00:02 -06:00
|
|
|
return nil, fmt.Errorf("package for %s is ill typed", i.File.URI())
|
|
|
|
}
|
2019-06-24 14:34:21 -06:00
|
|
|
if i.pkg.GetTypes() != i.decl.obj.Pkg() {
|
2019-06-21 15:00:02 -06:00
|
|
|
return nil, fmt.Errorf("failed to rename because %q is declared in package %q", i.Name, i.decl.obj.Pkg().Name())
|
2019-06-18 08:23:37 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(suzmue): Support renaming of imported packages.
|
2019-06-21 15:00:02 -06:00
|
|
|
if _, ok := i.decl.obj.(*types.PkgName); ok {
|
|
|
|
return nil, fmt.Errorf("renaming imported package %s not supported", i.Name)
|
2019-06-18 08:23:37 -06:00
|
|
|
}
|
|
|
|
|
2019-06-21 15:00:02 -06:00
|
|
|
refs, err := i.References(ctx)
|
2019-06-18 08:23:37 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-11 13:09:43 -06:00
|
|
|
r := renamer{
|
2019-06-21 15:00:02 -06:00
|
|
|
fset: i.File.FileSet(),
|
2019-06-24 14:34:21 -06:00
|
|
|
pkg: i.pkg,
|
2019-06-11 13:09:43 -06:00
|
|
|
refs: refs,
|
|
|
|
objsToUpdate: make(map[types.Object]bool),
|
2019-06-21 15:00:02 -06:00
|
|
|
from: i.Name,
|
2019-06-11 13:09:43 -06:00
|
|
|
to: newName,
|
|
|
|
packages: make(map[*types.Package]Package),
|
|
|
|
}
|
2019-06-24 14:34:21 -06:00
|
|
|
r.packages[i.pkg.GetTypes()] = i.pkg
|
2019-06-11 13:09:43 -06:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2019-06-21 15:00:02 -06:00
|
|
|
return r.update(ctx)
|
2019-06-11 13:09:43 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Rename all references to the identifier.
|
2019-06-21 15:00:02 -06:00
|
|
|
func (r *renamer) update(ctx context.Context) (map[span.URI][]TextEdit, error) {
|
2019-06-11 13:09:43 -06:00
|
|
|
result := make(map[span.URI][]TextEdit)
|
|
|
|
|
2019-06-24 15:48:30 -06:00
|
|
|
docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-06-11 13:09:43 -06:00
|
|
|
for _, ref := range r.refs {
|
2019-06-18 08:23:37 -06:00
|
|
|
refSpan, err := ref.Range.Span()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
edit := TextEdit{
|
|
|
|
Span: refSpan,
|
2019-06-11 13:09:43 -06:00
|
|
|
NewText: r.to,
|
2019-06-18 08:23:37 -06:00
|
|
|
}
|
2019-06-11 13:09:43 -06:00
|
|
|
result[refSpan.URI()] = append(result[refSpan.URI()], edit)
|
2019-06-20 13:24:17 -06:00
|
|
|
|
2019-06-27 10:17:07 -06:00
|
|
|
if !ref.isDeclaration { // not a declaration
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
doc := r.docComment(r.pkg, ref.ident)
|
|
|
|
if doc == nil { // no doc comment
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform the rename in doc comments declared in the original package
|
|
|
|
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
|
2019-06-20 13:24:17 -06:00
|
|
|
}
|
2019-06-27 10:17:07 -06:00
|
|
|
result[refSpan.URI()] = append(result[refSpan.URI()], TextEdit{
|
|
|
|
Span: spn,
|
|
|
|
NewText: r.to,
|
|
|
|
})
|
2019-06-20 13:24:17 -06:00
|
|
|
}
|
|
|
|
}
|
2019-06-18 08:23:37 -06:00
|
|
|
}
|
|
|
|
|
2019-06-11 13:09:43 -06:00
|
|
|
return result, nil
|
2019-06-18 08:23:37 -06:00
|
|
|
}
|
2019-06-20 13:24:17 -06:00
|
|
|
|
|
|
|
// docComment returns the doc for an identifier.
|
|
|
|
func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
|
2019-06-21 15:00:02 -06:00
|
|
|
_, nodes, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, id.Pos(), id.End())
|
2019-06-20 13:24:17 -06:00
|
|
|
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
|
|
|
|
}
|