Skip to content

Commit 01cad11

Browse files
findleyrrinchsan
authored andcommitted
internal/lsp: use directoryFilters in import scanning
Based on dle8's CL 414454, wire in directoryFilters into the goimports ModuleResolver scan. A few changes/fixes/additions from that CL: - Fix a bug in filter validation that was causing -**/a not to validate. - Fix a bug in regex building where -a was not treated as an excluded prefix (it was matching 'a' anywhere). - Use absolute paths in the SkipPathInScan, so that we can evaluate directory filters relative to the workspace folder. - Add several regression tests. - Consolidate directoryFilters regression tests under a new directoryfilters_test.go file. - Add several TODOs. Fixes golang/go#46438 Change-Id: I55cd3d6410905216cc8cfbdf528f301d67c2bbac Reviewed-on: https://go-review.googlesource.com/c/tools/+/420959 Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Dylan Le <[email protected]>
1 parent 8669313 commit 01cad11

File tree

3 files changed

+267
-0
lines changed

3 files changed

+267
-0
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package workspace
6+
7+
import (
8+
"sort"
9+
"strings"
10+
"testing"
11+
12+
. "golang.org/x/tools/internal/lsp/regtest"
13+
)
14+
15+
// This file contains regression tests for the directoryFilters setting.
16+
//
17+
// TODO:
18+
// - consolidate some of these tests into a single test
19+
// - add more tests for changing directory filters
20+
21+
func TestDirectoryFilters(t *testing.T) {
22+
WithOptions(
23+
ProxyFiles(workspaceProxy),
24+
WorkspaceFolders("pkg"),
25+
Settings{
26+
"directoryFilters": []string{"-inner"},
27+
},
28+
).Run(t, workspaceModule, func(t *testing.T, env *Env) {
29+
syms := env.WorkspaceSymbol("Hi")
30+
sort.Slice(syms, func(i, j int) bool { return syms[i].ContainerName < syms[j].ContainerName })
31+
for _, s := range syms {
32+
if strings.Contains(s.ContainerName, "inner") {
33+
t.Errorf("WorkspaceSymbol: found symbol %q with container %q, want \"inner\" excluded", s.Name, s.ContainerName)
34+
}
35+
}
36+
})
37+
}
38+
39+
func TestDirectoryFiltersLoads(t *testing.T) {
40+
// exclude, and its error, should be excluded from the workspace.
41+
const files = `
42+
-- go.mod --
43+
module example.com
44+
45+
go 1.12
46+
-- exclude/exclude.go --
47+
package exclude
48+
49+
const _ = Nonexistant
50+
`
51+
52+
WithOptions(
53+
Settings{"directoryFilters": []string{"-exclude"}},
54+
).Run(t, files, func(t *testing.T, env *Env) {
55+
env.Await(NoDiagnostics("exclude/x.go"))
56+
})
57+
}
58+
59+
func TestDirectoryFiltersTransitiveDep(t *testing.T) {
60+
// Even though exclude is excluded from the workspace, it should
61+
// still be importable as a non-workspace package.
62+
const files = `
63+
-- go.mod --
64+
module example.com
65+
66+
go 1.12
67+
-- include/include.go --
68+
package include
69+
import "example.com/exclude"
70+
71+
const _ = exclude.X
72+
-- exclude/exclude.go --
73+
package exclude
74+
75+
const _ = Nonexistant // should be ignored, since this is a non-workspace package
76+
const X = 1
77+
`
78+
79+
WithOptions(
80+
Settings{"directoryFilters": []string{"-exclude"}},
81+
).Run(t, files, func(t *testing.T, env *Env) {
82+
env.Await(
83+
NoDiagnostics("exclude/exclude.go"), // filtered out
84+
NoDiagnostics("include/include.go"), // successfully builds
85+
)
86+
})
87+
}
88+
89+
func TestDirectoryFiltersWorkspaceModules(t *testing.T) {
90+
// Define a module include.com which should be in the workspace, plus a
91+
// module exclude.com which should be excluded and therefore come from
92+
// the proxy.
93+
const files = `
94+
-- include/go.mod --
95+
module include.com
96+
97+
go 1.12
98+
99+
require exclude.com v1.0.0
100+
101+
-- include/go.sum --
102+
exclude.com v1.0.0 h1:Q5QSfDXY5qyNCBeUiWovUGqcLCRZKoTs9XdBeVz+w1I=
103+
exclude.com v1.0.0/go.mod h1:hFox2uDlNB2s2Jfd9tHlQVfgqUiLVTmh6ZKat4cvnj4=
104+
105+
-- include/include.go --
106+
package include
107+
108+
import "exclude.com"
109+
110+
var _ = exclude.X // satisfied only by the workspace version
111+
-- exclude/go.mod --
112+
module exclude.com
113+
114+
go 1.12
115+
-- exclude/exclude.go --
116+
package exclude
117+
118+
const X = 1
119+
`
120+
const proxy = `
121+
-- [email protected]/go.mod --
122+
module exclude.com
123+
124+
go 1.12
125+
-- [email protected]/exclude.go --
126+
package exclude
127+
`
128+
WithOptions(
129+
Modes(Experimental),
130+
ProxyFiles(proxy),
131+
Settings{"directoryFilters": []string{"-exclude"}},
132+
).Run(t, files, func(t *testing.T, env *Env) {
133+
env.Await(env.DiagnosticAtRegexp("include/include.go", `exclude.(X)`))
134+
})
135+
}
136+
137+
// Test for golang/go#46438: support for '**' in directory filters.
138+
func TestDirectoryFilters_Wildcard(t *testing.T) {
139+
filters := []string{"-**/bye"}
140+
WithOptions(
141+
ProxyFiles(workspaceProxy),
142+
WorkspaceFolders("pkg"),
143+
Settings{
144+
"directoryFilters": filters,
145+
},
146+
).Run(t, workspaceModule, func(t *testing.T, env *Env) {
147+
syms := env.WorkspaceSymbol("Bye")
148+
sort.Slice(syms, func(i, j int) bool { return syms[i].ContainerName < syms[j].ContainerName })
149+
for _, s := range syms {
150+
if strings.Contains(s.ContainerName, "bye") {
151+
t.Errorf("WorkspaceSymbol: found symbol %q with container %q with filters %v", s.Name, s.ContainerName, filters)
152+
}
153+
}
154+
})
155+
}
156+
157+
// Test for golang/go#52993: wildcard directoryFilters should apply to
158+
// goimports scanning as well.
159+
func TestDirectoryFilters_ImportScanning(t *testing.T) {
160+
const files = `
161+
-- go.mod --
162+
module mod.test
163+
164+
go 1.12
165+
-- main.go --
166+
package main
167+
168+
func main() {
169+
bye.Goodbye()
170+
}
171+
-- p/bye/bye.go --
172+
package bye
173+
174+
func Goodbye() {}
175+
`
176+
177+
WithOptions(
178+
Settings{
179+
"directoryFilters": []string{"-**/bye"},
180+
},
181+
// This test breaks in 'Experimental' mode, because with
182+
// experimentalWorkspaceModule set we the goimports scan behaves
183+
// differently.
184+
//
185+
// Since this feature is going away (golang/go#52897), don't investigate.
186+
Modes(Default),
187+
).Run(t, files, func(t *testing.T, env *Env) {
188+
env.OpenFile("main.go")
189+
beforeSave := env.Editor.BufferText("main.go")
190+
env.OrganizeImports("main.go")
191+
got := env.Editor.BufferText("main.go")
192+
if got != beforeSave {
193+
t.Errorf("after organizeImports code action, got modified buffer:\n%s", got)
194+
}
195+
})
196+
}
197+
198+
// Test for golang/go#52993: non-wildcard directoryFilters should still be
199+
// applied relative to the workspace folder, not the module root.
200+
func TestDirectoryFilters_MultiRootImportScanning(t *testing.T) {
201+
const files = `
202+
-- go.work --
203+
go 1.18
204+
205+
use (
206+
a
207+
b
208+
)
209+
-- a/go.mod --
210+
module mod1.test
211+
212+
go 1.18
213+
-- a/main.go --
214+
package main
215+
216+
func main() {
217+
hi.Hi()
218+
}
219+
-- a/hi/hi.go --
220+
package hi
221+
222+
func Hi() {}
223+
-- b/go.mod --
224+
module mod2.test
225+
226+
go 1.18
227+
-- b/main.go --
228+
package main
229+
230+
func main() {
231+
hi.Hi()
232+
}
233+
-- b/hi/hi.go --
234+
package hi
235+
236+
func Hi() {}
237+
`
238+
239+
WithOptions(
240+
Settings{
241+
"directoryFilters": []string{"-hi"}, // this test fails with -**/hi
242+
},
243+
).Run(t, files, func(t *testing.T, env *Env) {
244+
env.OpenFile("a/main.go")
245+
beforeSave := env.Editor.BufferText("a/main.go")
246+
env.OrganizeImports("a/main.go")
247+
got := env.Editor.BufferText("a/main.go")
248+
if got == beforeSave {
249+
t.Errorf("after organizeImports code action, got identical buffer:\n%s", got)
250+
}
251+
})
252+
}

internal/imports/fix.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,11 @@ type ProcessEnv struct {
613613
ModFlag string
614614
ModFile string
615615

616+
// SkipPathInScan returns true if the path should be skipped from scans of
617+
// the RootCurrentModule root type. The function argument is a clean,
618+
// absolute path.
619+
SkipPathInScan func(string) bool
620+
616621
// Env overrides the OS environment, and can be used to specify
617622
// GOPROXY, GO111MODULE, etc. PATH cannot be set here, because
618623
// exec.Command will not honor it.

internal/imports/mod.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,16 @@ func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error
462462
// We assume cached directories are fully cached, including all their
463463
// children, and have not changed. We can skip them.
464464
skip := func(root gopathwalk.Root, dir string) bool {
465+
if r.env.SkipPathInScan != nil && root.Type == gopathwalk.RootCurrentModule {
466+
if root.Path == dir {
467+
return false
468+
}
469+
470+
if r.env.SkipPathInScan(filepath.Clean(dir)) {
471+
return true
472+
}
473+
}
474+
465475
info, ok := r.cacheLoad(dir)
466476
if !ok {
467477
return false

0 commit comments

Comments
 (0)