Skip to content

Commit cd01b4b

Browse files
committed
gopls/internal/settings: add renameMovesSubpackages setting
Removes the packageMove setting - now we always prompt with the full package path during a package rename. The new renameMovesSubpackages setting, false by default, controls whether a package rename also results in moving its subpackages. Change-Id: I191f85cec09e244217fadf820aa7ee60a93063c7 Reviewed-on: https://go-review.googlesource.com/c/tools/+/721660 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Alan Donovan <adonovan@google.com>
1 parent 84fe542 commit cd01b4b

File tree

12 files changed

+140
-119
lines changed

12 files changed

+140
-119
lines changed

gopls/doc/features/transformation.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -340,18 +340,22 @@ Special cases:
340340
```
341341

342342
Using Rename to move a package:
343-
- When initiating a rename request on a package identifier, users can specify
344-
a new package name which will result in the package's contents moving to a new
345-
directory at the same directory level. Subpackages are not affected.
346-
- When the setting "packageMove" is set to true, users are prompted with
347-
the package path instead of the package name but can specify either an arbitrary
348-
package path or a package name. Subpackages are not affected.
343+
To rename a package, execute the Rename operation over the `p` in a
344+
`package p` declaration at the start of a file.
345+
You will be prompted to edit the package's path and choose its new location.
346+
The Rename operation will move all the package's files to the resulting directory,
347+
creating it if necessary.
348+
By default, subpackages will remain where they are. To include subpackages
349+
in the renaming, set [renameMovesSubpackages](../settings.md#renamemovessubpackages-bool) to true.
350+
Existing imports of the package will be updated to reflect its new path.
349351

350352
Package moves are rejected if they would break the build. For example:
351-
- Packages cannot move across a module boundary
352-
- Packages cannot be moved into existing packages; gopls does not support package merging
353-
- Packages cannot be moved to internal directories that would make them inaccessible to any of their current importers
353+
- Packages cannot move across a module boundary.
354+
- Packages cannot be moved into existing packages; gopls does not support package merging.
355+
- Packages cannot be moved to internal directories that would make them inaccessible to any of their current importers.
354356

357+
Renaming package main is not supported, because the main package has special meaning to the linker.
358+
Renaming x_test packages is currently not supported.
355359

356360
Some tips for best results:
357361

gopls/doc/settings.md

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

270270
Default: `true`.
271271

272-
<a id='packageMove'></a>
273-
### `packageMove bool`
272+
<a id='renameMovesSubpackages'></a>
273+
### `renameMovesSubpackages bool`
274274

275275
**This setting is experimental and may be deleted.**
276276

277-
packageMove enables PrepareRename to send the full package path
278-
and allows users to move a package via renaming.
277+
renameMovesSubpackages enables Rename operations on packages to
278+
move subdirectories of the target package.
279279

280280
Default: `false`.
281281

gopls/internal/doc/api.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,9 +2189,9 @@
21892189
"DeprecationMessage": ""
21902190
},
21912191
{
2192-
"Name": "packageMove",
2192+
"Name": "renameMovesSubpackages",
21932193
"Type": "bool",
2194-
"Doc": "packageMove enables PrepareRename to send the full package path\nand allows users to move a package via renaming.\n",
2194+
"Doc": "renameMovesSubpackages enables Rename operations on packages to\nmove subdirectories of the target package.\n",
21952195
"EnumKeys": {
21962196
"ValueType": "",
21972197
"Keys": null

gopls/internal/golang/rename.go

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ import (
5353
"go/types"
5454
"maps"
5555
"os"
56-
"path"
5756
"path/filepath"
5857
"regexp"
5958
"slices"
@@ -199,12 +198,14 @@ func prepareRenamePackageName(ctx context.Context, snapshot *cache.Snapshot, pgf
199198
if meta.Name == "main" {
200199
return nil, fmt.Errorf("can't rename package \"main\"")
201200
}
201+
// TODO(mkalil): support renaming x_test packages.
202202
if strings.HasSuffix(string(meta.Name), "_test") {
203203
return nil, fmt.Errorf("can't rename x_test packages")
204204
}
205205
if meta.Module == nil {
206206
return nil, fmt.Errorf("can't rename package: missing module information for package %q", meta.PkgPath)
207207
}
208+
// TODO(mkalil): why is renaming the root package of a module unsupported?
208209
if meta.Module.Path == string(meta.PkgPath) {
209210
return nil, fmt.Errorf("can't rename package: package path %q is the same as module path %q", meta.PkgPath, meta.Module.Path)
210211
}
@@ -215,20 +216,9 @@ func prepareRenamePackageName(ctx context.Context, snapshot *cache.Snapshot, pgf
215216
return nil, err
216217
}
217218

218-
pkgName := string(meta.Name)
219-
fullPath := string(meta.PkgPath)
220-
text := pkgName
221-
// Before displaying the full package path, verify that the PackageMove
222-
// setting is enabled and that the package name matches its directory
223-
// basename. Checking the value of meta.Module above ensures that the
224-
// current view is either a GoMod or a GoWork view, which are the only views
225-
// for which we should enable package move.
226-
if snapshot.Options().PackageMove && path.Base(fullPath) == pkgName {
227-
text = fullPath
228-
}
229219
return &PrepareItem{
230220
Range: rng,
231-
Text: text,
221+
Text: string(meta.PkgPath),
232222
}, nil
233223
}
234224

@@ -420,7 +410,7 @@ func editsToDocChanges(ctx context.Context, snapshot *cache.Snapshot, edits map[
420410

421411
// Rename returns a map of TextEdits for each file modified when renaming a
422412
// given identifier within a package.
423-
func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, newName string, renameSubpkgs bool) ([]protocol.DocumentChange, error) {
413+
func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, newName string) ([]protocol.DocumentChange, error) {
424414
ctx, done := event.Start(ctx, "golang.Rename")
425415
defer done()
426416

@@ -461,10 +451,11 @@ func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp pro
461451
newPkgPath PackagePath
462452
err error
463453
)
464-
if newPkgDir, newPkgName, newPkgPath, err = checkPackageRename(snapshot.Options(), pkg, f, newName); err != nil {
454+
moveSubpackages := snapshot.Options().RenameMovesSubpackages
455+
if newPkgDir, newPkgName, newPkgPath, err = checkPackageRename(pkg, f, newName, moveSubpackages); err != nil {
465456
return nil, err
466457
}
467-
editMap, err = renamePackage(ctx, snapshot, f, newPkgName, newPkgPath, newPkgDir, renameSubpkgs)
458+
editMap, err = renamePackage(ctx, snapshot, f, newPkgName, newPkgPath, newPkgDir, moveSubpackages)
468459
if err != nil {
469460
return nil, err
470461
}
@@ -524,7 +515,7 @@ func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp pro
524515
}
525516
if inPackageName {
526517
oldDir := f.URI().DirPath()
527-
if renameSubpkgs {
518+
if snapshot.Options().RenameMovesSubpackages {
528519
// Update the last component of the file's enclosing directory.
529520
changes = append(changes, protocol.DocumentChangeRename(
530521
protocol.URIFromPath(oldDir),
@@ -962,15 +953,15 @@ func renameExported(pkgs []*cache.Package, declPkgPath PackagePath, declObjPath
962953
//
963954
// f is the file originating the rename, and therefore f.URI().Dir() is the
964955
// current package directory. newName, newPath, and newDir describe the renaming.
965-
func renamePackage(ctx context.Context, s *cache.Snapshot, f file.Handle, newName PackageName, newPath PackagePath, newDir string, renameSubpkgs bool) (map[protocol.DocumentURI][]diff.Edit, error) {
956+
func renamePackage(ctx context.Context, s *cache.Snapshot, f file.Handle, newName PackageName, newPath PackagePath, newDir string, moveSubpackages bool) (map[protocol.DocumentURI][]diff.Edit, error) {
966957
// Rename the package decl and all imports.
967958
renamingEdits := make(map[protocol.DocumentURI][]diff.Edit)
968-
err := updatePackageDeclsAndImports(ctx, s, f, newName, newPath, renamingEdits, renameSubpkgs)
959+
err := updatePackageDeclsAndImports(ctx, s, f, newName, newPath, renamingEdits, moveSubpackages)
969960
if err != nil {
970961
return nil, err
971962
}
972963

973-
err = updateModFiles(ctx, s, f.URI().DirPath(), newDir, renamingEdits, renameSubpkgs)
964+
err = updateModFiles(ctx, s, f.URI().DirPath(), newDir, renamingEdits, moveSubpackages)
974965
if err != nil {
975966
return nil, err
976967
}
@@ -981,7 +972,7 @@ func renamePackage(ctx context.Context, s *cache.Snapshot, f file.Handle, newNam
981972
// Update any affected replace directives in go.mod files.
982973
// TODO(adonovan): should this operate on all go.mod files,
983974
// irrespective of whether they are included in the workspace?
984-
func updateModFiles(ctx context.Context, s *cache.Snapshot, oldDir string, newPkgDir string, renamingEdits map[protocol.DocumentURI][]diff.Edit, renameSubpkgs bool) error {
975+
func updateModFiles(ctx context.Context, s *cache.Snapshot, oldDir string, newPkgDir string, renamingEdits map[protocol.DocumentURI][]diff.Edit, moveSubpackages bool) error {
985976
modFiles := s.View().ModFiles()
986977
for _, m := range modFiles {
987978
fh, err := s.ReadFile(ctx, m)
@@ -1008,8 +999,8 @@ func updateModFiles(ctx context.Context, s *cache.Snapshot, oldDir string, newPk
1008999

10091000
// TODO: Is there a risk of converting a '\' delimited replacement to a '/' delimited replacement?
10101001

1011-
if renameSubpkgs && !strings.HasPrefix(filepath.ToSlash(replacedDir)+"/", filepath.ToSlash(oldDir)+"/") ||
1012-
!renameSubpkgs && !(filepath.ToSlash(replacedDir) == filepath.ToSlash(oldDir)) {
1002+
if moveSubpackages && !strings.HasPrefix(filepath.ToSlash(replacedDir)+"/", filepath.ToSlash(oldDir)+"/") ||
1003+
!moveSubpackages && !(filepath.ToSlash(replacedDir) == filepath.ToSlash(oldDir)) {
10131004
continue //not affected by the package renanming
10141005
}
10151006

@@ -1068,7 +1059,7 @@ func updateModFiles(ctx context.Context, s *cache.Snapshot, oldDir string, newPk
10681059
// It updates package clauses and import paths for the renamed package as well
10691060
// as any other packages affected by the directory renaming among all packages
10701061
// known to the snapshot.
1071-
func updatePackageDeclsAndImports(ctx context.Context, s *cache.Snapshot, f file.Handle, newName PackageName, newPkgPath PackagePath, renamingEdits map[protocol.DocumentURI][]diff.Edit, renameSubpkgs bool) error {
1062+
func updatePackageDeclsAndImports(ctx context.Context, s *cache.Snapshot, f file.Handle, newName PackageName, newPkgPath PackagePath, renamingEdits map[protocol.DocumentURI][]diff.Edit, moveSubpackages bool) error {
10721063
if strings.HasSuffix(string(newName), "_test") {
10731064
return fmt.Errorf("cannot rename to _test package")
10741065
}
@@ -1112,8 +1103,8 @@ func updatePackageDeclsAndImports(ctx context.Context, s *cache.Snapshot, f file
11121103
// Subtle: check this condition before checking for valid module info
11131104
// below, because we should not fail this operation if unrelated packages
11141105
// lack module info.
1115-
if renameSubpkgs && !pathutil.InDir(string(oldPkgPath), string(mp.PkgPath)) ||
1116-
!renameSubpkgs && mp.PkgPath != oldPkgPath {
1106+
if moveSubpackages && !pathutil.InDir(filepath.FromSlash(string(oldPkgPath)), filepath.FromSlash(string(mp.PkgPath))) ||
1107+
!moveSubpackages && mp.PkgPath != oldPkgPath {
11171108
continue // not affected by the package renaming
11181109
}
11191110

@@ -1138,7 +1129,6 @@ func updatePackageDeclsAndImports(ctx context.Context, s *cache.Snapshot, f file
11381129
return err
11391130
}
11401131
}
1141-
11421132
imp := ImportPath(newImportPath) // TODO(adonovan): what if newImportPath has vendor/ prefix?
11431133
if err := renameImports(ctx, s, mp, imp, pkgName, renamingEdits); err != nil {
11441134
return err

gopls/internal/golang/rename_check.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ import (
5151
"golang.org/x/tools/go/ast/inspector"
5252
"golang.org/x/tools/gopls/internal/cache"
5353
"golang.org/x/tools/gopls/internal/file"
54-
"golang.org/x/tools/gopls/internal/settings"
5554
"golang.org/x/tools/gopls/internal/util/safetoken"
5655
"golang.org/x/tools/internal/typeparams"
5756
"golang.org/x/tools/internal/typesinternal"
@@ -923,7 +922,7 @@ func isValidIdentifier(id string) bool {
923922
// resulting from renaming the current package to newName, which may be an
924923
// identifier or a package path. An error is returned if the renaming is
925924
// invalid.
926-
func checkPackageRename(opts *settings.Options, curPkg *cache.Package, f file.Handle, newName string) (newPkgDir string, newPkgName PackageName, newPkgPath PackagePath, err error) {
925+
func checkPackageRename(curPkg *cache.Package, f file.Handle, newName string, moveSubpackages bool) (newPkgDir string, newPkgName PackageName, newPkgPath PackagePath, err error) {
927926
// Path unchanged
928927
if newName == curPkg.String() || newName == string(curPkg.Metadata().Name) {
929928
return f.URI().DirPath(), curPkg.Metadata().Name, curPkg.Metadata().PkgPath, nil
@@ -949,21 +948,20 @@ func checkPackageRename(opts *settings.Options, curPkg *cache.Package, f file.Ha
949948
// path, in which case we should not allow renaming.
950949
root := filepath.Dir(f.URI().DirPath())
951950
newPkgDir = filepath.Join(root, newName)
952-
empty, err := dirIsEmpty(newPkgDir)
951+
exists, empty, err := dirIsEmpty(newPkgDir)
953952
if err != nil {
954953
return "", "", "", err
955954
}
956-
if !empty {
957-
return "", "", "", fmt.Errorf("invalid package identifier: %q is not empty", newName)
955+
// When rename moves subpackages, we merely change the directory name, which cannot
956+
// happen if the target directory already exists.
957+
if moveSubpackages && exists || !empty {
958+
return "", "", "", fmt.Errorf("invalid package identifier: %q is not empty", newPkgDir)
958959
}
959960
parentPkgPath := strings.TrimSuffix(string(curPkg.Metadata().PkgPath), string(curPkg.Metadata().Name)) // leaves a trailing slash
960961
newPkgPath = PackagePath(parentPkgPath + newName)
961962
newPkgName = PackageName(newName)
962963
return newPkgDir, newPkgName, newPkgPath, nil
963964
}
964-
if !opts.PackageMove {
965-
return "", "", "", fmt.Errorf("a full package path is specified but internal setting 'packageMove' is not enabled")
966-
}
967965
// Don't allow moving packages across module boundaries.
968966
curModPath := curPkg.Metadata().Module.Path
969967
if !strings.HasPrefix(newName+"/", curModPath+"/") {
@@ -980,17 +978,22 @@ func checkPackageRename(opts *settings.Options, curPkg *cache.Package, f file.Ha
980978
return "", "", "", fmt.Errorf("invalid package path %q", newName)
981979
}
982980
newPkgName = PackageName(filepath.Base(newPkgDir))
983-
empty, err := dirIsEmpty(newPkgDir)
981+
exists, empty, err := dirIsEmpty(newPkgDir)
984982
if err != nil {
985983
return "", "", "", err
986984
}
987-
if !empty {
988-
return "", "", "", fmt.Errorf("invalid package path: %q is not empty", newName)
985+
// When rename moves subpackages, we merely change the directory name, which cannot
986+
// happen if the target directory already exists.
987+
if moveSubpackages && exists || !empty {
988+
return "", "", "", fmt.Errorf("invalid package path: %q is not empty", newPkgDir)
989989
}
990990
// Verify that the new package name is a valid identifier.
991991
if !isValidIdentifier(string(newPkgName)) {
992992
return "", "", "", fmt.Errorf("invalid package name %q", newPkgName)
993993
}
994+
if f.URI().Dir().Base() != string(curPkg.Metadata().Name) {
995+
return "", "", "", fmt.Errorf("can't move package: package name %q does not match directory base name %q", string(curPkg.Metadata().Name), f.URI().Dir().Base())
996+
}
994997
return newPkgDir, newPkgName, PackagePath(newName), nil
995998
}
996999

@@ -1006,23 +1009,24 @@ func hasIgnoredGoFiles(pkg *cache.Package) bool {
10061009
return false
10071010
}
10081011

1009-
// dirIsEmpty returns true if the directory does not exist or if the entries in
1010-
// dir consist of only directories.
1011-
func dirIsEmpty(dir string) (bool, error) {
1012+
// dirIsEmpty returns two values: whether the directory exists and whether it is
1013+
// empty (contains only directory entries). If it does not exist, empty is
1014+
// always true.
1015+
func dirIsEmpty(dir string) (exists bool, empty bool, err error) {
10121016
files, err := os.ReadDir(dir)
10131017
if err != nil {
10141018
if errors.Is(err, fs.ErrNotExist) {
1015-
return true, nil
1019+
return false, true, nil
10161020
} else {
1017-
return false, err
1021+
return false, false, err
10181022
}
10191023
}
10201024
for _, f := range files {
10211025
if !f.IsDir() {
1022-
return false, nil
1026+
return true, false, nil
10231027
}
10241028
}
1025-
return true, nil
1029+
return true, true, nil
10261030
}
10271031

10281032
// isLocal reports whether obj is local to some function.

gopls/internal/mcp/rename_symbol.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (h *handler) renameSymbolHandler(ctx context.Context, req *mcp.CallToolRequ
3838
if err != nil {
3939
return nil, nil, err
4040
}
41-
changes, err := golang.Rename(ctx, snapshot, fh, loc.Range.Start, params.NewName, false)
41+
changes, err := golang.Rename(ctx, snapshot, fh, loc.Range.Start, params.NewName)
4242
if err != nil {
4343
return nil, nil, err
4444
}

gopls/internal/server/rename.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@ func (s *server) Rename(ctx context.Context, params *protocol.RenameParams) (*pr
3030
return nil, fmt.Errorf("cannot rename in file of type %s", kind)
3131
}
3232

33-
// TODO(mkalil): By default, we don't move subpackages on a package rename.
34-
// When dialog support is enabled, the user will be able to specify whether
35-
// they do want to move subpackages.
36-
changes, err := golang.Rename(ctx, snapshot, fh, params.Position, params.NewName, false)
33+
changes, err := golang.Rename(ctx, snapshot, fh, params.Position, params.NewName)
3734
if err != nil {
3835
return nil, err
3936
}

gopls/internal/settings/default.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ func DefaultOptions(overrides ...func(*Options)) *Options {
132132
CodeLensVendor: true,
133133
CodeLensRunGovulncheck: true,
134134
},
135-
NewGoFileHeader: true,
135+
NewGoFileHeader: true,
136+
RenameMovesSubpackages: false,
136137
},
137138
},
138139
InternalOptions: InternalOptions{

gopls/internal/settings/settings.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,9 @@ type UIOptions struct {
251251
// and package declaration in a newly created Go file.
252252
NewGoFileHeader bool
253253

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"`
254+
// RenameMovesSubpackages enables Rename operations on packages to
255+
// move subdirectories of the target package.
256+
RenameMovesSubpackages bool `status:"experimental"`
257257
}
258258

259259
// A CodeLensSource identifies an (algorithmic) source of code lenses.
@@ -1368,8 +1368,8 @@ func (o *Options) setOne(name string, value any) (applied []CounterPath, _ error
13681368
case "mcpTools":
13691369
return setBoolMap(&o.MCPTools, value)
13701370

1371-
case "packageMove":
1372-
return setBool(&o.PackageMove, value)
1371+
case "renameMovesSubpackages":
1372+
return setBool(&o.RenameMovesSubpackages, value)
13731373

13741374
// deprecated and renamed settings
13751375
//

0 commit comments

Comments
 (0)