Skip to content

Commit aa385af

Browse files
committed
go/packages: add support for entire packages defined in overlays
This change adds support in go/packages for defining an entire package in an overlay. We also add corresponding tests for this in gopls, to confirm that it works as expected. Fixes golang/go#31467 Change-Id: Iead203ab2964a7ac4f571be97624b725ac5de7e0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/172409 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
1 parent 4ca4b55 commit aa385af

File tree

8 files changed

+158
-8
lines changed

8 files changed

+158
-8
lines changed

go/packages/golist.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,8 @@ extractQueries:
166166
containsCandidates = append(containsCandidates, modifiedPkgs...)
167167
containsCandidates = append(containsCandidates, needPkgs...)
168168
}
169-
170-
if len(needPkgs) > 0 {
171-
addNeededOverlayPackages(cfg, golistDriver, response, needPkgs)
172-
if err != nil {
173-
return nil, err
174-
}
169+
if err := addNeededOverlayPackages(cfg, golistDriver, response, needPkgs); err != nil {
170+
return nil, err
175171
}
176172
// Check candidate packages for containFiles.
177173
if len(containFiles) > 0 {
@@ -191,13 +187,21 @@ extractQueries:
191187
}
192188

193189
func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string) error {
190+
if len(pkgs) == 0 {
191+
return nil
192+
}
194193
dr, err := driver(cfg, pkgs...)
195194
if err != nil {
196195
return err
197196
}
198197
for _, pkg := range dr.Packages {
199198
response.addPackage(pkg)
200199
}
200+
_, needPkgs, err := processGolistOverlay(cfg, response.dr)
201+
if err != nil {
202+
return err
203+
}
204+
addNeededOverlayPackages(cfg, driver, response, needPkgs)
201205
return nil
202206
}
203207

go/packages/golist_overlay.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ outer:
4646
fileExists = true
4747
}
4848
}
49-
if dirContains {
49+
// The overlay could have included an entirely new package.
50+
isNewPackage := extractPackage(pkg, path, contents)
51+
if dirContains || isNewPackage {
5052
if !fileExists {
5153
pkg.GoFiles = append(pkg.GoFiles, path) // TODO(matloob): should the file just be added to GoFiles?
5254
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, path)
@@ -102,3 +104,30 @@ func extractImports(filename string, contents []byte) ([]string, error) {
102104
}
103105
return res, nil
104106
}
107+
108+
func extractPackage(pkg *Package, filename string, contents []byte) bool {
109+
// TODO(rstambler): Check the message of the actual error?
110+
// It differs between $GOPATH and module mode.
111+
if len(pkg.Errors) != 1 {
112+
return false
113+
}
114+
if pkg.Name != "" || pkg.ExportFile != "" {
115+
return false
116+
}
117+
if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 {
118+
return false
119+
}
120+
if len(pkg.Imports) > 0 {
121+
return false
122+
}
123+
f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset?
124+
if err != nil {
125+
return false
126+
}
127+
if filepath.Base(pkg.PkgPath) != f.Name.Name {
128+
return false
129+
}
130+
pkg.Name = f.Name.Name
131+
pkg.Errors = []Error{}
132+
return true
133+
}

go/packages/packages_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,87 @@ func testOverlay(t *testing.T, exporter packagestest.Exporter) {
921921
}
922922
}
923923

924+
func TestNewPackagesInOverlay(t *testing.T) { packagestest.TestAll(t, testNewPackagesInOverlay) }
925+
func testNewPackagesInOverlay(t *testing.T, exporter packagestest.Exporter) {
926+
exported := packagestest.Export(t, exporter, []packagestest.Module{{
927+
Name: "golang.org/fake",
928+
Files: map[string]interface{}{
929+
"a/a.go": `package a; import "golang.org/fake/b"; const A = "a" + b.B`,
930+
"b/b.go": `package b; import "golang.org/fake/c"; const B = "b" + c.C`,
931+
"c/c.go": `package c; const C = "c"`,
932+
"d/d.go": `package d; const D = "d"`,
933+
}}})
934+
defer exported.Cleanup()
935+
936+
dir := filepath.Dir(filepath.Dir(exported.File("golang.org/fake", "a/a.go")))
937+
938+
for i, test := range []struct {
939+
overlay map[string][]byte
940+
want string // expected value of e.E
941+
}{
942+
// Overlay with one file.
943+
{map[string][]byte{
944+
filepath.Join(dir, "e", "e.go"): []byte(`package e; import "golang.org/fake/a"; const E = "e" + a.A`)},
945+
`"eabc"`},
946+
// Overlay with multiple files in the same package.
947+
{map[string][]byte{
948+
filepath.Join(dir, "e", "e.go"): []byte(`package e; import "golang.org/fake/a"; const E = "e" + a.A + underscore`),
949+
filepath.Join(dir, "e", "e_util.go"): []byte(`package e; const underscore = "_"`),
950+
},
951+
`"eabc_"`},
952+
// Overlay with multiple files in different packages.
953+
{map[string][]byte{
954+
filepath.Join(dir, "e", "e.go"): []byte(`package e; import "golang.org/fake/f"; const E = "e" + f.F + underscore`),
955+
filepath.Join(dir, "e", "e_util.go"): []byte(`package e; const underscore = "_"`),
956+
filepath.Join(dir, "f", "f.go"): []byte(`package f; const F = "f"`),
957+
},
958+
`"ef_"`},
959+
{map[string][]byte{
960+
filepath.Join(dir, "e", "e.go"): []byte(`package e; import "golang.org/fake/f"; const E = "e" + f.F + underscore`),
961+
filepath.Join(dir, "e", "e_util.go"): []byte(`package e; const underscore = "_"`),
962+
filepath.Join(dir, "f", "f.go"): []byte(`package f; import "golang.org/fake/g"; const F = "f" + g.G`),
963+
filepath.Join(dir, "g", "g.go"): []byte(`package g; const G = "g"`),
964+
},
965+
`"efg_"`},
966+
{map[string][]byte{
967+
filepath.Join(dir, "e", "e.go"): []byte(`package e; import "golang.org/fake/f"; import "golang.org/fake/h"; const E = "e" + f.F + h.H + underscore`),
968+
filepath.Join(dir, "e", "e_util.go"): []byte(`package e; const underscore = "_"`),
969+
filepath.Join(dir, "f", "f.go"): []byte(`package f; import "golang.org/fake/g"; const F = "f" + g.G`),
970+
filepath.Join(dir, "g", "g.go"): []byte(`package g; const G = "g"`),
971+
filepath.Join(dir, "h", "h.go"): []byte(`package h; const H = "h"`),
972+
},
973+
`"efgh_"`},
974+
{map[string][]byte{
975+
filepath.Join(dir, "e", "e.go"): []byte(`package e; import "golang.org/fake/f"; const E = "e" + f.F + underscore`),
976+
filepath.Join(dir, "e", "e_util.go"): []byte(`package e; const underscore = "_"`),
977+
filepath.Join(dir, "f", "f.go"): []byte(`package f; import "golang.org/fake/g"; const F = "f" + g.G`),
978+
filepath.Join(dir, "g", "g.go"): []byte(`package g; import "golang.org/fake/h"; const G = "g" + h.H`),
979+
filepath.Join(dir, "h", "h.go"): []byte(`package h; const H = "h"`),
980+
},
981+
`"efgh_"`},
982+
} {
983+
exported.Config.Overlay = test.overlay
984+
exported.Config.Mode = packages.LoadAllSyntax
985+
initial, err := packages.Load(exported.Config, "golang.org/fake/e")
986+
if err != nil {
987+
t.Error(err)
988+
continue
989+
}
990+
991+
// Check value of e.E.
992+
e := initial[0]
993+
eE := constant(e, "E")
994+
if eE == nil {
995+
t.Errorf("%d. e.E: got nil", i)
996+
continue
997+
}
998+
got := eE.Val().String()
999+
if got != test.want {
1000+
t.Errorf("%d. e.E: got %s, want %s", i, got, test.want)
1001+
}
1002+
}
1003+
}
1004+
9241005
func TestLoadAllSyntaxImportErrors(t *testing.T) {
9251006
packagestest.TestAll(t, testLoadAllSyntaxImportErrors)
9261007
}

go/packages/packagestest/expect.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ func (e *Exported) getNotes() error {
141141
dirs = append(dirs, filepath.Dir(filename))
142142
}
143143
}
144+
for filename := range e.Config.Overlay {
145+
dirs = append(dirs, filepath.Dir(filename))
146+
}
144147
pkgs, err := packages.Load(e.Config, dirs...)
145148
if err != nil {
146149
return fmt.Errorf("unable to load packages for directories %s: %v", dirs, err)

internal/lsp/cache/file.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,15 @@ func (f *File) read(ctx context.Context) {
112112
return
113113
}
114114
}
115+
// We might have the content saved in an overlay.
116+
if content, ok := f.view.Config.Overlay[f.filename]; ok {
117+
f.content = content
118+
return
119+
}
115120
// We don't know the content yet, so read it.
116121
content, err := ioutil.ReadFile(f.filename)
117122
if err != nil {
123+
f.view.Logger().Errorf(ctx, "unable to read file %s: %v", f.filename, err)
118124
return
119125
}
120126
f.content = content

internal/lsp/lsp_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"strings"
1919
"testing"
2020

21+
"golang.org/x/tools/go/packages"
2122
"golang.org/x/tools/go/packages/packagestest"
2223
"golang.org/x/tools/internal/lsp/cache"
2324
"golang.org/x/tools/internal/lsp/diff"
@@ -37,7 +38,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
3738

3839
// We hardcode the expected number of test cases to ensure that all tests
3940
// are being executed. If a test is added, this number must be changed.
40-
const expectedCompletionsCount = 64
41+
const expectedCompletionsCount = 65
4142
const expectedDiagnosticsCount = 16
4243
const expectedFormatCount = 4
4344
const expectedDefinitionsCount = 17
@@ -62,7 +63,10 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
6263
defer exported.Cleanup()
6364

6465
// Merge the exported.Config with the view.Config.
66+
addUnsavedFiles(t, exported.Config, exported)
67+
6568
cfg := *exported.Config
69+
6670
cfg.Fset = token.NewFileSet()
6771
cfg.Context = context.Background()
6872
cfg.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
@@ -174,6 +178,25 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
174178
})
175179
}
176180

181+
func addUnsavedFiles(t *testing.T, cfg *packages.Config, exported *packagestest.Exported) {
182+
if cfg.Overlay == nil {
183+
cfg.Overlay = make(map[string][]byte)
184+
}
185+
// For now, we hardcode a file that we know is in the testdata.
186+
// TODO(rstambler): Figure out a way to do this better.
187+
dir := filepath.Dir(filepath.Dir(exported.File("golang.org/x/tools/internal/lsp", filepath.Join("complit", "complit.go"))))
188+
cfg.Overlay[filepath.Join(dir, "nodisk", "nodisk.go")] = []byte(`package nodisk
189+
190+
import (
191+
"golang.org/x/tools/internal/lsp/foo"
192+
)
193+
194+
func _() {
195+
foo.Foo() //@complete("F", Foo, IntFoo, StructFoo)
196+
}
197+
`)
198+
}
199+
177200
type diagnostics map[span.URI][]protocol.Diagnostic
178201
type completionItems map[token.Pos]*protocol.CompletionItem
179202
type completions map[token.Position][]token.Pos

internal/lsp/testdata/nodisk/empty

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
an empty file so that the directory exists

internal/span/utf16.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ func FromUTF16Column(p Point, chr int, content []byte) (Point, error) {
5656
if chr <= 1 {
5757
return p, nil
5858
}
59+
if p.Offset() >= len(content) {
60+
return p, fmt.Errorf("offset (%v) greater than length of content (%v)", p.Offset(), len(content))
61+
}
5962
remains := content[p.Offset():]
6063
// scan forward the specified number of characters
6164
for count := 1; count < chr; count++ {

0 commit comments

Comments
 (0)