1
0
mirror of https://github.com/golang/go synced 2024-11-18 17:04:41 -07:00

go/packages: fix misbehavior when an overlay changes the package name

The new code checks for inconsistencies between the package name on disk
and the package name in overlays (as might happen when editing).

This Cl is meant to fix https://github.com/golang/go/issues/38328

Change-Id: If56b542775c216b4e0ed8ce91d6721c672bff792
Reviewed-on: https://go-review.googlesource.com/c/tools/+/228236
Run-TryBot: Peter Weinberger <pjw@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
pjw 2020-04-14 18:20:59 -04:00 committed by Peter Weinberger
parent 5744cfde56
commit ea51688515
2 changed files with 186 additions and 0 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt"
"go/parser"
"go/token"
"log"
"os"
"path/filepath"
"sort"
@ -22,10 +23,15 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif
needPkgsSet := make(map[string]bool)
modifiedPkgsSet := make(map[string]bool)
pkgOfDir := make(map[string][]*Package)
for _, pkg := range response.dr.Packages {
// This is an approximation of import path to id. This can be
// wrong for tests, vendored packages, and a number of other cases.
havePkgs[pkg.PkgPath] = pkg.ID
x := commonDir(pkg.GoFiles)
if x != "" {
pkgOfDir[x] = append(pkgOfDir[x], pkg)
}
}
// If no new imports are added, it is safe to avoid loading any needPkgs.
@ -64,6 +70,9 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif
// to the overlay.
continue
}
// if all the overlay files belong to a different package, change the package
// name to that package. Otherwise leave it alone; there will be an error message.
maybeFixPackageName(pkgName, pkgOfDir, dir)
nextPackage:
for _, p := range response.dr.Packages {
if pkgName != p.Name && p.ID != "command-line-arguments" {
@ -384,3 +393,46 @@ func extractPackageName(filename string, contents []byte) (string, bool) {
}
return f.Name.Name, true
}
func commonDir(a []string) string {
seen := make(map[string]bool)
x := append([]string{}, a...)
for _, f := range x {
seen[filepath.Dir(f)] = true
}
if len(seen) > 1 {
log.Fatalf("commonDir saw %v for %v", seen, x)
}
for k := range seen {
// len(seen) == 1
return k
}
return "" // no files
}
// It is possible that the files in the disk directory dir have a different package
// name from newName, which is deduced from the overlays. If they all have a different
// package name, and they all have the same package name, then that name becomes
// the package name.
// It returns true if it changes the package name, false otherwise.
func maybeFixPackageName(newName string, pkgOfDir map[string][]*Package, dir string) bool {
names := make(map[string]int)
for _, p := range pkgOfDir[dir] {
names[p.Name]++
}
if len(names) != 1 {
// some files are in different packages
return false
}
oldName := ""
for k := range names {
oldName = k
}
if newName == oldName {
return false
}
for _, p := range pkgOfDir[dir] {
p.Name = newName
}
return true
}

134
go/packages/overlay_test.go Normal file
View File

@ -0,0 +1,134 @@
package packages_test
import (
"log"
"path/filepath"
"testing"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/packages/packagestest"
)
const commonMode = packages.NeedName | packages.NeedFiles |
packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedSyntax
func TestOverlayChangesPackage(t *testing.T) {
log.SetFlags(log.Lshortfile)
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
Name: "fake",
Files: map[string]interface{}{
"a.go": "package foo\nfunc f(){}\n",
},
Overlay: map[string][]byte{
"a.go": []byte("package foox\nfunc f(){}\n"),
},
}})
defer exported.Cleanup()
exported.Config.Mode = packages.NeedName
initial, err := packages.Load(exported.Config,
filepath.Dir(exported.File("fake", "a.go")))
if err != nil {
t.Fatalf("failed to load: %v", err)
}
if len(initial) != 1 || initial[0].ID != "fake" || initial[0].Name != "foox" {
t.Fatalf("got %v, expected [fake]", initial)
}
if len(initial[0].Errors) != 0 {
t.Fatalf("got %v, expected no errors", initial[0].Errors)
}
log.SetFlags(0)
}
func TestOverlayChangesBothPackages(t *testing.T) {
log.SetFlags(log.Lshortfile)
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
Name: "fake",
Files: map[string]interface{}{
"a.go": "package foo\nfunc g(){}\n",
"a_test.go": "package foo\nfunc f(){}\n",
},
Overlay: map[string][]byte{
"a.go": []byte("package foox\nfunc g(){}\n"),
"a_test.go": []byte("package foox\nfunc f(){}\n"),
},
}})
defer exported.Cleanup()
exported.Config.Mode = commonMode
initial, err := packages.Load(exported.Config,
filepath.Dir(exported.File("fake", "a.go")))
if err != nil {
t.Fatalf("failed to load: %v", err)
}
if len(initial) != 3 {
t.Errorf("got %d packges, expected 3", len(initial))
}
want := []struct {
id, name string
count int
}{
{"fake", "foox", 1},
{"fake [fake.test]", "foox", 2},
{"fake.test", "main", 1},
}
for i := 0; i < 3; i++ {
if ok := checkPkg(t, initial[i], want[i].id, want[i].name, want[i].count); !ok {
t.Errorf("%d: got {%s %s %d}, expected %v", i, initial[i].ID,
initial[i].Name, len(initial[i].Syntax), want[i])
}
if len(initial[i].Errors) != 0 {
t.Errorf("%d: got %v, expected no errors", i, initial[i].Errors)
}
}
log.SetFlags(0)
}
func TestOverlayChangesTestPackage(t *testing.T) {
log.SetFlags(log.Lshortfile)
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
Name: "fake",
Files: map[string]interface{}{
"a_test.go": "package foo\nfunc f(){}\n",
},
Overlay: map[string][]byte{
"a_test.go": []byte("package foox\nfunc f(){}\n"),
},
}})
defer exported.Cleanup()
exported.Config.Mode = commonMode
initial, err := packages.Load(exported.Config,
filepath.Dir(exported.File("fake", "a_test.go")))
if err != nil {
t.Fatalf("failed to load: %v", err)
}
if len(initial) != 3 {
t.Errorf("got %d packges, expected 3", len(initial))
}
want := []struct {
id, name string
count int
}{
{"fake", "foo", 0},
{"fake [fake.test]", "foox", 1},
{"fake.test", "main", 1},
}
for i := 0; i < 3; i++ {
if ok := checkPkg(t, initial[i], want[i].id, want[i].name, want[i].count); !ok {
t.Errorf("got {%s %s %d}, expected %v", initial[i].ID,
initial[i].Name, len(initial[i].Syntax), want[i])
}
}
if len(initial[0].Errors) != 0 {
t.Fatalf("got %v, expected no errors", initial[0].Errors)
}
log.SetFlags(0)
}
func checkPkg(t *testing.T, p *packages.Package, id, name string, syntax int) bool {
t.Helper()
if p.ID == id && p.Name == name && len(p.Syntax) == syntax {
return true
}
return false
}