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 (
|
2019-06-27 12:01:56 -06:00
|
|
|
"bytes"
|
2019-06-18 08:23:37 -06:00
|
|
|
"context"
|
2019-12-04 11:45:53 -07:00
|
|
|
"fmt"
|
2019-06-20 13:24:17 -06:00
|
|
|
"go/ast"
|
2019-06-27 12:01:56 -06:00
|
|
|
"go/format"
|
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-08-19 17:28:08 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/diff"
|
2019-08-22 11:31:03 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
2019-06-18 08:23:37 -06:00
|
|
|
"golang.org/x/tools/internal/span"
|
2019-08-13 13:07:39 -06:00
|
|
|
"golang.org/x/tools/internal/telemetry/trace"
|
2019-06-11 13:09:43 -06:00
|
|
|
"golang.org/x/tools/refactor/satisfy"
|
2019-08-06 13:13:11 -06:00
|
|
|
errors "golang.org/x/xerrors"
|
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
|
|
|
|
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-08-22 11:31:03 -06:00
|
|
|
type PrepareItem struct {
|
2019-09-05 18:04:28 -06:00
|
|
|
Range protocol.Range
|
2019-08-22 11:31:03 -06:00
|
|
|
Text string
|
|
|
|
}
|
|
|
|
|
2019-11-12 15:58:37 -07:00
|
|
|
func (i *IdentifierInfo) PrepareRename(ctx context.Context) (*PrepareItem, error) {
|
2019-08-22 11:31:03 -06:00
|
|
|
ctx, done := trace.StartSpan(ctx, "source.PrepareRename")
|
|
|
|
defer done()
|
|
|
|
|
2019-09-05 18:04:28 -06:00
|
|
|
// TODO(rstambler): We should handle this in a better way.
|
2019-08-22 11:31:03 -06:00
|
|
|
// If the object declaration is nil, assume it is an import spec.
|
|
|
|
if i.Declaration.obj == nil {
|
|
|
|
// Find the corresponding package name for this import spec
|
|
|
|
// and rename that instead.
|
2019-09-05 18:04:28 -06:00
|
|
|
ident, err := i.getPkgName(ctx)
|
2019-08-22 11:31:03 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-11-19 12:51:46 -07:00
|
|
|
rng, err := ident.mappedRange.Range()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// We're not really renaming the import path.
|
|
|
|
rng.End = rng.Start
|
|
|
|
return &PrepareItem{
|
|
|
|
Range: rng,
|
|
|
|
Text: ident.Name,
|
|
|
|
}, nil
|
2019-08-22 11:31:03 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Do not rename builtin identifiers.
|
|
|
|
if i.Declaration.obj.Parent() == types.Universe {
|
|
|
|
return nil, errors.Errorf("cannot rename builtin %q", i.Name)
|
|
|
|
}
|
2019-09-05 18:04:28 -06:00
|
|
|
rng, err := i.mappedRange.Range()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-08-22 11:31:03 -06:00
|
|
|
return &PrepareItem{
|
2019-09-05 18:04:28 -06:00
|
|
|
Range: rng,
|
2019-08-22 11:31:03 -06:00
|
|
|
Text: i.Name,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
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-11-12 15:58:37 -07:00
|
|
|
func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.URI][]protocol.TextEdit, error) {
|
2019-06-26 20:46:12 -06:00
|
|
|
ctx, done := trace.StartSpan(ctx, "source.Rename")
|
|
|
|
defer done()
|
2019-07-11 19:05:55 -06:00
|
|
|
|
2019-09-05 18:04:28 -06:00
|
|
|
// TODO(rstambler): We should handle this in a better way.
|
2019-08-21 20:40:36 -06:00
|
|
|
// If the object declaration is nil, assume it is an import spec.
|
2019-08-26 22:26:45 -06:00
|
|
|
if i.Declaration.obj == nil {
|
2019-08-21 20:40:36 -06:00
|
|
|
// Find the corresponding package name for this import spec
|
|
|
|
// and rename that instead.
|
|
|
|
ident, err := i.getPkgName(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-11-12 15:58:37 -07:00
|
|
|
return ident.Rename(ctx, newName)
|
2019-08-21 20:40:36 -06:00
|
|
|
}
|
2019-06-21 15:00:02 -06:00
|
|
|
if i.Name == newName {
|
2019-08-06 13:13:11 -06:00
|
|
|
return nil, errors.Errorf("old and new names are the same: %s", newName)
|
2019-06-18 08:23:37 -06:00
|
|
|
}
|
2019-08-22 11:31:03 -06:00
|
|
|
if !isValidIdentifier(newName) {
|
2019-08-06 13:13:11 -06:00
|
|
|
return nil, errors.Errorf("invalid identifier to rename: %q", i.Name)
|
2019-06-11 13:09:43 -06:00
|
|
|
}
|
2019-07-08 17:41:40 -06:00
|
|
|
// Do not rename builtin identifiers.
|
2019-08-26 22:26:45 -06:00
|
|
|
if i.Declaration.obj.Parent() == types.Universe {
|
2019-08-06 13:13:11 -06:00
|
|
|
return nil, errors.Errorf("cannot rename builtin %q", i.Name)
|
2019-07-08 17:41:40 -06:00
|
|
|
}
|
2019-09-09 17:26:26 -06:00
|
|
|
if i.pkg == nil || i.pkg.IsIllTyped() {
|
2019-12-16 14:06:34 -07:00
|
|
|
return nil, errors.Errorf("package for %s is ill typed", i.URI())
|
2019-07-09 15:52:23 -06:00
|
|
|
}
|
2019-07-08 17:41:40 -06:00
|
|
|
// Do not rename identifiers declared in another package.
|
2019-09-09 17:26:26 -06:00
|
|
|
if i.pkg.GetTypes() != i.Declaration.obj.Pkg() {
|
2019-08-26 22:26:45 -06:00
|
|
|
return nil, errors.Errorf("failed to rename because %q is declared in package %q", i.Name, i.Declaration.obj.Pkg().Name())
|
2019-06-18 08:23:37 -06:00
|
|
|
}
|
|
|
|
|
2019-09-06 21:58:07 -06:00
|
|
|
refs, err := i.References(ctx)
|
2019-06-18 08:23:37 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-05 17:28:59 -07:00
|
|
|
// Make sure to add the declaration of the identifier.
|
|
|
|
refs = append(refs, i.DeclarationReferenceInfo())
|
|
|
|
|
2019-06-11 13:09:43 -06:00
|
|
|
r := renamer{
|
2019-06-27 12:09:03 -06:00
|
|
|
ctx: ctx,
|
2019-11-12 15:58:37 -07:00
|
|
|
fset: i.Snapshot.View().Session().Cache().FileSet(),
|
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-07-08 19:53:01 -06:00
|
|
|
for _, from := range refs {
|
2019-08-15 08:29:18 -06:00
|
|
|
r.packages[from.pkg.GetTypes()] = from.pkg
|
2019-07-08 19:53:01 -06:00
|
|
|
}
|
2019-06-11 13:09:43 -06:00
|
|
|
|
|
|
|
// Check that the renaming of the identifier is ok.
|
2019-08-15 08:29:18 -06:00
|
|
|
for _, ref := range refs {
|
|
|
|
r.check(ref.obj)
|
2019-08-16 10:23:59 -06:00
|
|
|
if r.hadConflicts { // one error is enough.
|
|
|
|
break
|
|
|
|
}
|
2019-06-11 13:09:43 -06:00
|
|
|
}
|
|
|
|
if r.hadConflicts {
|
2019-08-06 13:13:11 -06:00
|
|
|
return nil, errors.Errorf(r.errors)
|
2019-06-11 13:09:43 -06:00
|
|
|
}
|
|
|
|
|
2019-07-15 15:02:40 -06:00
|
|
|
changes, err := r.update()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-09-06 12:55:14 -06:00
|
|
|
result := make(map[span.URI][]protocol.TextEdit)
|
|
|
|
for uri, edits := range changes {
|
2019-09-16 16:17:51 -06:00
|
|
|
// These edits should really be associated with FileHandles for maximal correctness.
|
|
|
|
// For now, this is good enough.
|
2019-12-17 16:57:54 -07:00
|
|
|
fh, err := i.Snapshot.GetFile(ctx, uri)
|
2019-09-06 12:55:14 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-09-16 16:17:51 -06:00
|
|
|
data, _, err := fh.Read(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
converter := span.NewContentConverter(uri.Filename(), data)
|
|
|
|
m := &protocol.ColumnMapper{
|
|
|
|
URI: uri,
|
|
|
|
Converter: converter,
|
|
|
|
Content: data,
|
|
|
|
}
|
|
|
|
// Sort the edits first.
|
|
|
|
diff.SortTextEdits(edits)
|
2019-09-06 12:55:14 -06:00
|
|
|
protocolEdits, err := ToProtocolEdits(m, edits)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result[uri] = protocolEdits
|
2019-07-15 15:02:40 -06:00
|
|
|
}
|
2019-09-06 12:55:14 -06:00
|
|
|
return result, nil
|
2019-06-11 13:09:43 -06:00
|
|
|
}
|
|
|
|
|
2019-11-19 13:32:09 -07:00
|
|
|
// getPkgName gets the pkg name associated with an identifier representing
|
2019-08-21 20:40:36 -06:00
|
|
|
// the import path in an import spec.
|
|
|
|
func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error) {
|
2019-09-17 09:19:11 -06:00
|
|
|
ph, err := i.pkg.File(i.URI())
|
|
|
|
if err != nil {
|
2019-12-04 11:45:53 -07:00
|
|
|
return nil, fmt.Errorf("finding file for identifier %v: %v", i.Name, err)
|
2019-09-06 21:58:07 -06:00
|
|
|
}
|
2019-10-24 13:44:41 -06:00
|
|
|
file, _, _, err := ph.Cached()
|
2019-09-17 09:19:11 -06:00
|
|
|
if err != nil {
|
2019-08-22 11:31:03 -06:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var namePos token.Pos
|
|
|
|
for _, spec := range file.Imports {
|
|
|
|
if spec.Path.Pos() == i.spanRange.Start {
|
|
|
|
namePos = spec.Pos()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !namePos.IsValid() {
|
|
|
|
return nil, errors.Errorf("import spec not found for %q", i.Name)
|
|
|
|
}
|
|
|
|
// Look for the object defined at NamePos.
|
2019-09-09 17:26:26 -06:00
|
|
|
for _, obj := range i.pkg.GetTypesInfo().Defs {
|
2019-08-21 20:40:36 -06:00
|
|
|
pkgName, ok := obj.(*types.PkgName)
|
2019-08-22 11:31:03 -06:00
|
|
|
if ok && pkgName.Pos() == namePos {
|
2019-08-21 20:40:36 -06:00
|
|
|
return getPkgNameIdentifier(ctx, i, pkgName)
|
|
|
|
}
|
|
|
|
}
|
2019-09-09 17:26:26 -06:00
|
|
|
for _, obj := range i.pkg.GetTypesInfo().Implicits {
|
2019-08-21 20:40:36 -06:00
|
|
|
pkgName, ok := obj.(*types.PkgName)
|
2019-08-22 11:31:03 -06:00
|
|
|
if ok && pkgName.Pos() == namePos {
|
2019-08-21 20:40:36 -06:00
|
|
|
return getPkgNameIdentifier(ctx, i, pkgName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, errors.Errorf("no package name for %q", i.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getPkgNameIdentifier returns an IdentifierInfo representing pkgName.
|
|
|
|
// pkgName must be in the same package and file as ident.
|
|
|
|
func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *types.PkgName) (*IdentifierInfo, error) {
|
2019-08-26 22:26:45 -06:00
|
|
|
decl := Declaration{
|
2019-12-16 14:06:34 -07:00
|
|
|
obj: pkgName,
|
2019-08-21 20:40:36 -06:00
|
|
|
}
|
2019-09-09 17:26:26 -06:00
|
|
|
var err error
|
2019-11-22 12:49:12 -07:00
|
|
|
if decl.mappedRange, err = objToMappedRange(ident.Snapshot.View(), ident.pkg, decl.obj); err != nil {
|
2019-09-16 16:17:51 -06:00
|
|
|
return nil, err
|
|
|
|
}
|
2019-11-22 12:49:12 -07:00
|
|
|
if decl.node, err = objToNode(ident.Snapshot.View(), ident.pkg, decl.obj); err != nil {
|
2019-08-21 20:40:36 -06:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &IdentifierInfo{
|
2019-11-19 12:51:46 -07:00
|
|
|
Snapshot: ident.Snapshot,
|
|
|
|
Name: pkgName.Name(),
|
|
|
|
mappedRange: decl.mappedRange,
|
|
|
|
Declaration: decl,
|
|
|
|
pkg: ident.pkg,
|
|
|
|
qf: ident.qf,
|
2019-08-21 20:40:36 -06:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-06-11 13:09:43 -06:00
|
|
|
// Rename all references to the identifier.
|
2019-08-19 17:28:08 -06:00
|
|
|
func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
|
|
|
|
result := make(map[span.URI][]diff.TextEdit)
|
2019-07-08 19:53:01 -06:00
|
|
|
seen := make(map[span.Span]bool)
|
2019-06-11 13:09:43 -06:00
|
|
|
|
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-08-26 22:26:45 -06:00
|
|
|
refSpan, err := ref.spanRange.Span()
|
2019-06-18 08:23:37 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-08 19:53:01 -06:00
|
|
|
if seen[refSpan] {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
seen[refSpan] = true
|
2019-06-18 08:23:37 -06:00
|
|
|
|
2019-06-27 12:01:56 -06:00
|
|
|
// Renaming a types.PkgName may result in the addition or removal of an identifier,
|
|
|
|
// so we deal with this separately.
|
|
|
|
if pkgName, ok := ref.obj.(*types.PkgName); ok && ref.isDeclaration {
|
|
|
|
edit, err := r.updatePkgName(pkgName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result[refSpan.URI()] = append(result[refSpan.URI()], *edit)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace the identifier with r.to.
|
2019-08-19 17:28:08 -06:00
|
|
|
edit := diff.TextEdit{
|
2019-06-18 08:23:37 -06:00
|
|
|
Span: refSpan,
|
2019-06-11 13:09:43 -06:00
|
|
|
NewText: r.to,
|
2019-06-18 08:23:37 -06:00
|
|
|
}
|
2019-06-27 12:09:03 -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 12:01:56 -06:00
|
|
|
if !ref.isDeclaration || ref.ident == nil { // uses do not have doc comments to update.
|
2019-06-27 10:17:07 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-08-15 08:29:18 -06:00
|
|
|
doc := r.docComment(ref.pkg, ref.ident)
|
2019-06-27 12:01:56 -06:00
|
|
|
if doc == nil {
|
2019-06-27 10:17:07 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-06-27 12:01:56 -06:00
|
|
|
// Perform the rename in doc comments declared in the original package.
|
2019-06-27 10:17:07 -06:00
|
|
|
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-08-19 17:28:08 -06:00
|
|
|
result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
|
2019-06-27 10:17:07 -06:00
|
|
|
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
|
|
|
|
}
|
2019-06-27 12:01:56 -06:00
|
|
|
|
|
|
|
// updatePkgName returns the updates to rename a pkgName in the import spec
|
2019-08-19 17:28:08 -06:00
|
|
|
func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) {
|
2019-06-27 12:01:56 -06:00
|
|
|
// Modify ImportSpec syntax to add or remove the Name as needed.
|
|
|
|
pkg := r.packages[pkgName.Pkg()]
|
|
|
|
_, path, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, pkgName.Pos(), pkgName.Pos())
|
|
|
|
if len(path) < 2 {
|
2019-07-09 15:52:23 -06:00
|
|
|
return nil, errors.Errorf("no path enclosing interval for %s", pkgName.Name())
|
2019-06-27 12:01:56 -06:00
|
|
|
}
|
|
|
|
spec, ok := path[1].(*ast.ImportSpec)
|
|
|
|
if !ok {
|
2019-08-06 13:13:11 -06:00
|
|
|
return nil, errors.Errorf("failed to update PkgName for %s", pkgName.Name())
|
2019-06-27 12:01:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
var astIdent *ast.Ident // will be nil if ident is removed
|
|
|
|
if pkgName.Imported().Name() != r.to {
|
|
|
|
// ImportSpec.Name needed
|
|
|
|
astIdent = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a copy of the ident that just has the name and path.
|
|
|
|
updated := &ast.ImportSpec{
|
|
|
|
Name: astIdent,
|
|
|
|
Path: spec.Path,
|
|
|
|
EndPos: spec.EndPos,
|
|
|
|
}
|
|
|
|
|
|
|
|
rng := span.NewRange(r.fset, spec.Pos(), spec.End())
|
|
|
|
spn, err := rng.Span()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
format.Node(&buf, r.fset, updated)
|
|
|
|
newText := buf.String()
|
|
|
|
|
2019-08-19 17:28:08 -06:00
|
|
|
return &diff.TextEdit{
|
2019-06-27 12:01:56 -06:00
|
|
|
Span: spn,
|
|
|
|
NewText: newText,
|
|
|
|
}, nil
|
|
|
|
}
|