Skip to content

Commit

Permalink
gopls/internal/lsp: update replace directives in go.mod for package r…
Browse files Browse the repository at this point in the history
…enaming

Check active go.mod files in the workspace to see if any replace directives need to be fixed if package renaming affects the replaced locations in any go.mod files.

For golang/go#56184.

Change-Id: I98ea07a602c39168d13f42f1b7a5f6738e194ece
Reviewed-on: https://go-review.googlesource.com/c/tools/+/459416
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Dylan Le <dungtuanle@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
  • Loading branch information
Dung Le committed Dec 29, 2022
1 parent eac36cb commit 1a08d01
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 3 deletions.
88 changes: 88 additions & 0 deletions gopls/internal/lsp/source/rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import (
"go/token"
"go/types"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"

"golang.org/x/mod/modfile"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/safetoken"
Expand Down Expand Up @@ -214,6 +216,92 @@ func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position,
return nil, true, err
}

oldBase := filepath.Dir(span.URI.Filename(f.URI()))
newPkgDir := filepath.Join(filepath.Dir(oldBase), newName)

// TODO: should this operate on all go.mod files, irrespective of whether they are included in the workspace?
// Get all active mod files in the workspace
modFiles := s.ModFiles()
for _, m := range modFiles {
fh, err := s.GetFile(ctx, m)
if err != nil {
return nil, true, err
}
pm, err := s.ParseMod(ctx, fh)
if err != nil {
return nil, true, err
}

modFileDir := filepath.Dir(pm.URI.Filename())
affectedReplaces := []*modfile.Replace{}

// Check if any replace directives need to be fixed
for _, r := range pm.File.Replace {
if !strings.HasPrefix(r.New.Path, "/") && !strings.HasPrefix(r.New.Path, "./") && !strings.HasPrefix(r.New.Path, "../") {
continue
}

replacedPath := r.New.Path
if strings.HasPrefix(r.New.Path, "./") || strings.HasPrefix(r.New.Path, "../") {
replacedPath = filepath.Join(modFileDir, r.New.Path)
}

// TODO: Is there a risk of converting a '\' delimited replacement to a '/' delimited replacement?
if !strings.HasPrefix(filepath.ToSlash(replacedPath)+"/", filepath.ToSlash(oldBase)+"/") {
continue // not affected by the package renaming
}

affectedReplaces = append(affectedReplaces, r)
}

if len(affectedReplaces) == 0 {
continue
}
copied, err := modfile.Parse("", pm.Mapper.Content, nil)
if err != nil {
return nil, true, err
}

for _, r := range affectedReplaces {
replacedPath := r.New.Path
if strings.HasPrefix(r.New.Path, "./") || strings.HasPrefix(r.New.Path, "../") {
replacedPath = filepath.Join(modFileDir, r.New.Path)
}

suffix := strings.TrimPrefix(replacedPath, string(oldBase))

newReplacedPath, err := filepath.Rel(modFileDir, newPkgDir+suffix)
if err != nil {
return nil, true, err
}

newReplacedPath = filepath.ToSlash(newReplacedPath)

if !strings.HasPrefix(newReplacedPath, "/") && !strings.HasPrefix(newReplacedPath, "../") {
newReplacedPath = "./" + newReplacedPath
}

if err := copied.AddReplace(r.Old.Path, "", newReplacedPath, ""); err != nil {
return nil, true, err
}
}

copied.Cleanup()
newContent, err := copied.Format()
if err != nil {
return nil, true, err
}

// Calculate the edits to be made due to the change.
diff := s.View().Options().ComputeEdits(string(pm.Mapper.Content), string(newContent))
modFileEdits, err := ToProtocolEdits(pm.Mapper, diff)
if err != nil {
return nil, true, err
}

renamingEdits[pm.URI] = append(renamingEdits[pm.URI], modFileEdits...)
}

return renamingEdits, true, nil
}

Expand Down
30 changes: 27 additions & 3 deletions gopls/internal/regtest/misc/rename_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,16 @@ func main() {
}

func TestRenamePackage_NestedModule(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
testenv.NeedsGo1Point(t, 18)
const files = `
-- go.work --
go 1.18
use (
.
./foo/bar
./foo/baz
)
-- go.mod --
module mod.com
Expand All @@ -530,7 +538,10 @@ require (
mod.com/foo/bar v0.0.0
)
replace mod.com/foo/bar => ./foo/bar
replace (
mod.com/foo/bar => ./foo/bar
mod.com/foo/baz => ./foo/baz
)
-- foo/foo.go --
package foo
Expand All @@ -546,20 +557,30 @@ module mod.com/foo/bar
-- foo/bar/bar.go --
package bar
const Msg = "Hi"
const Msg = "Hi from package bar"
-- foo/baz/go.mod --
module mod.com/foo/baz
-- foo/baz/baz.go --
package baz
const Msg = "Hi from package baz"
-- main.go --
package main
import (
"fmt"
"mod.com/foo/bar"
"mod.com/foo/baz"
"mod.com/foo"
)
func main() {
foo.Bar()
fmt.Println(bar.Msg)
fmt.Println(baz.Msg)
}
`
Run(t, files, func(t *testing.T, env *Env) {
Expand All @@ -574,6 +595,9 @@ func main() {
env.RegexpSearch("main.go", "mod.com/foo/bar")
env.RegexpSearch("main.go", "mod.com/foox")
env.RegexpSearch("main.go", "foox.Bar()")

env.RegexpSearch("go.mod", "./foox/bar")
env.RegexpSearch("go.mod", "./foox/baz")
})
}

Expand Down

0 comments on commit 1a08d01

Please sign in to comment.