Skip to content

Commit a03a1c2

Browse files
committed
gopls/internal: prompt with full package path in preparerename
As part of implementing package move refactoring (rename on a package that results in moving the package to a different directory), we need to allow the user to provide us with a full package path. First, we change PrepareRename to prompt the user with the full package path instead of just the package name. For example, PrepareRename on package golang would display "golang.org/x/tools/gopls/internal/golang" instead of "golang". For now, throw an error if the user attempts to rename a package to a different directory. Also update the marker test framework to handle directory renames. For golang/go#57171 Change-Id: If2f7d1cfcbb9ce142f619b5ccb04843bca5d40f5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/708655 Reviewed-by: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 4b2fd2a commit a03a1c2

File tree

9 files changed

+131
-14
lines changed

9 files changed

+131
-14
lines changed

gopls/doc/settings.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,16 @@ and package declaration in a newly created Go file.
269269

270270
Default: `true`.
271271

272+
<a id='packageMove'></a>
273+
### `packageMove bool`
274+
275+
**This setting is experimental and may be deleted.**
276+
277+
packageMove enables PrepareRename to send the full package path
278+
and allows users to move a package via renaming.
279+
280+
Default: `false`.
281+
272282
<a id='completion'></a>
273283
## Completion
274284

gopls/internal/doc/api.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,6 +2159,20 @@
21592159
"Hierarchy": "ui",
21602160
"DeprecationMessage": ""
21612161
},
2162+
{
2163+
"Name": "packageMove",
2164+
"Type": "bool",
2165+
"Doc": "packageMove enables PrepareRename to send the full package path\nand allows users to move a package via renaming.\n",
2166+
"EnumKeys": {
2167+
"ValueType": "",
2168+
"Keys": null
2169+
},
2170+
"EnumValues": null,
2171+
"Default": "false",
2172+
"Status": "experimental",
2173+
"Hierarchy": "ui",
2174+
"DeprecationMessage": ""
2175+
},
21622176
{
21632177
"Name": "local",
21642178
"Type": "string",

gopls/internal/golang/rename.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,14 @@ func prepareRenamePackageName(ctx context.Context, snapshot *cache.Snapshot, pgf
214214
if err != nil {
215215
return nil, err
216216
}
217+
218+
text := string(meta.Name)
219+
if snapshot.Options().PackageMove {
220+
text = string(meta.PkgPath)
221+
}
217222
return &PrepareItem{
218223
Range: rng,
219-
Text: string(meta.Name),
224+
Text: text,
220225
}, nil
221226
}
222227

@@ -420,10 +425,6 @@ func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp pro
420425
return edits, false, nil
421426
}
422427

423-
if !isValidIdentifier(newName) {
424-
return nil, false, fmt.Errorf("invalid identifier to rename: %q", newName)
425-
}
426-
427428
// Cursor within package name declaration?
428429
_, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp)
429430
if err != nil {
@@ -433,8 +434,16 @@ func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp pro
433434
var editMap map[protocol.DocumentURI][]diff.Edit
434435
if inPackageName {
435436
countRenamePackage.Inc()
437+
if !isValidPackagePath(pkg.String(), newName) {
438+
return nil, false, fmt.Errorf("invalid package path: %q (package moves are not yet supported, see go.dev/issue/57171)", newName)
439+
}
440+
// Only the last element of the path is required as input for [renamePackageName].
441+
newName = path.Base(newName)
436442
editMap, err = renamePackageName(ctx, snapshot, f, PackageName(newName))
437443
} else {
444+
if !isValidIdentifier(newName) {
445+
return nil, false, fmt.Errorf("invalid identifier to rename: %q", newName)
446+
}
438447
editMap, err = renameOrdinary(ctx, snapshot, f.URI(), pp, newName)
439448
}
440449
if err != nil {
@@ -884,7 +893,6 @@ func renamePackageName(ctx context.Context, s *cache.Snapshot, f file.Handle, ne
884893
return nil, err
885894
}
886895

887-
// Update the last component of the file's enclosing directory.
888896
oldBase := f.URI().DirPath()
889897
newPkgDir := filepath.Join(filepath.Dir(oldBase), string(newName))
890898

gopls/internal/golang/rename_check.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,17 @@ func isValidIdentifier(id string) bool {
914914
return token.Lookup(id) == token.IDENT
915915
}
916916

917+
// isValidPackagePath reports whether newPath is a valid new path for the
918+
// package currently at oldPath. For now, we only support renames that
919+
// do not result in a package move.
920+
// TODO(mkalil): support package renames with arbitrary package paths, including
921+
// relative paths.
922+
func isValidPackagePath(oldPath, newPath string) bool {
923+
// We prompt with the full package path, but some users may delete this and
924+
// just enter a package identifier, which we should still support.
925+
return isValidIdentifier(newPath) || filepath.Dir(oldPath) == filepath.Dir(newPath)
926+
}
927+
917928
// isLocal reports whether obj is local to some function.
918929
// Precondition: not a struct field or interface method.
919930
func isLocal(obj types.Object) bool {

gopls/internal/server/rename.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package server
77
import (
88
"context"
99
"fmt"
10+
"path"
1011
"path/filepath"
1112

1213
"golang.org/x/tools/gopls/internal/file"
@@ -52,7 +53,7 @@ func (s *server) Rename(ctx context.Context, params *protocol.RenameParams) (*pr
5253
if isPkgRenaming {
5354
// Update the last component of the file's enclosing directory.
5455
oldDir := fh.URI().DirPath()
55-
newDir := filepath.Join(filepath.Dir(oldDir), params.NewName)
56+
newDir := filepath.Join(filepath.Dir(oldDir), path.Base(params.NewName))
5657
change := protocol.DocumentChangeRename(
5758
protocol.URIFromPath(oldDir),
5859
protocol.URIFromPath(newDir))

gopls/internal/settings/settings.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ type UIOptions struct {
250250
// NewGoFileHeader enables automatic insertion of the copyright comment
251251
// and package declaration in a newly created Go file.
252252
NewGoFileHeader bool
253+
254+
// PackageMove enables PrepareRename to send the full package path
255+
// and allows users to move a package via renaming.
256+
PackageMove bool `status:"experimental"`
253257
}
254258

255259
// A CodeLensSource identifies an (algorithmic) source of code lenses.
@@ -1361,6 +1365,9 @@ func (o *Options) setOne(name string, value any) (applied []CounterPath, _ error
13611365
case "mcpTools":
13621366
return setBoolMap(&o.MCPTools, value)
13631367

1368+
case "packageMove":
1369+
return setBool(&o.PackageMove, value)
1370+
13641371
// deprecated and renamed settings
13651372
//
13661373
// These should never be deleted: there is essentially no cost

gopls/internal/test/marker/marker_test.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2091,17 +2091,43 @@ func changedFiles(env *integration.Env, changes []protocol.DocumentChange) (map[
20912091

20922092
case change.RenameFile != nil:
20932093
old := change.RenameFile.OldURI
2094-
m, err := read(old)
2094+
new := change.RenameFile.NewURI
2095+
info, err := os.Stat(old.Path())
20952096
if err != nil {
2096-
return nil, err // missing
2097+
return nil, err
20972098
}
2098-
write(old, nil)
2099+
if info.IsDir() {
2100+
// Walk through all the files in the old directory and copy
2101+
// their content to the new directory.
2102+
// TODO(mkalil): This currently only handles renaming the file's
2103+
// innermost directory. Need to handle renames of outer directories
2104+
// directories when implementing package move refactoring.
2105+
for _, file := range env.ListFiles(old.Path()) {
2106+
oldFile := protocol.URIFromPath(path.Join(old.Path(), path.Base(file)))
2107+
m, err := read(oldFile)
2108+
if err != nil {
2109+
return nil, err // missing
2110+
}
2111+
write(oldFile, nil)
20992112

2100-
new := change.RenameFile.NewURI
2101-
if _, err := read(old); err == nil {
2102-
return nil, fmt.Errorf("RenameFile: destination %s exists", new)
2113+
newFile := protocol.URIFromPath(path.Join(new.Path(), path.Base(file)))
2114+
if _, err := read(oldFile); err == nil {
2115+
return nil, fmt.Errorf("RenameFile: destination %s exists", new)
2116+
}
2117+
write(newFile, m.Content)
2118+
}
2119+
} else {
2120+
m, err := read(old)
2121+
if err != nil {
2122+
return nil, err // missing
2123+
}
2124+
write(old, nil)
2125+
2126+
if _, err := read(old); err == nil {
2127+
return nil, fmt.Errorf("RenameFile: destination %s exists", new)
2128+
}
2129+
write(new, m.Content)
21032130
}
2104-
write(new, m.Content)
21052131

21062132
case change.CreateFile != nil:
21072133
uri := change.CreateFile.URI
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
This test verifies the behavior of renaming a package declaration.
2+
3+
-- settings.json --
4+
{
5+
"packageMove": true
6+
}
7+
8+
-- flags --
9+
-ignore_extra_diags
10+
11+
-- go.mod --
12+
module golang.org/lsptests/rename
13+
14+
go 1.20
15+
-- one/one.go --
16+
package one //@ rename("one", "golang.org/lsptests/rename/one", sameName), rename("one", "golang.org/lsptests/rename/two", newNameSameDir), renameerr("one", "golang.org/lsptests/otherdir/one", re"invalid package path")
17+
18+
-- @newNameSameDir/one/one.go --
19+
@@ -1,2 +0,0 @@
20+
-package one //@ rename("one", "golang.org/lsptests/rename/one", sameName), rename("one", "golang.org/lsptests/rename/two", newNameSameDir), renameerr("one", "golang.org/lsptests/otherdir/one", re"invalid package path")
21+
-
22+
-- @newNameSameDir/two/one.go --
23+
@@ -0,0 +1,2 @@
24+
+package two //@ rename("one", "golang.org/lsptests/rename/one", sameName), rename("one", "golang.org/lsptests/rename/two", newNameSameDir), renameerr("one", "golang.org/lsptests/otherdir/one", re"invalid package path")
25+
+
26+
-- @sameName/one/one.go --
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
This test verifies the behavior of textDocument/prepareRename when the experimental package move setting is enabled.
2+
3+
-- settings.json --
4+
{
5+
"packageMove": true
6+
}
7+
8+
-- go.mod --
9+
module golang.org/lsptests
10+
11+
go 1.20
12+
13+
-- b/b.go --
14+
package b //@ preparerename("b", "golang.org/lsptests/b")

0 commit comments

Comments
 (0)