Skip to content

Commit 92d58ea

Browse files
committed
internal/lsp/cache: register a file watcher for explicit GOWORK values
When the go.work file is set by the GOWORK environment variable, we must create an additional file watching pattern. Fixes golang/go#53631 Change-Id: I2d78c5a9ee8a71551d5274db7eb4e6c623d8db74 Reviewed-on: https://go-review.googlesource.com/c/tools/+/421501 gopls-CI: kokoro <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Suzy Mueller <[email protected]>
1 parent 98aef77 commit 92d58ea

File tree

5 files changed

+77
-49
lines changed

5 files changed

+77
-49
lines changed

gopls/internal/regtest/diagnostics/diagnostics_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,7 +1548,7 @@ func Hello() {
15481548
}
15491549
-- go.mod --
15501550
module mod.com
1551-
-- main.go --
1551+
-- cmd/main.go --
15521552
package main
15531553
15541554
import "mod.com/bob"
@@ -1558,11 +1558,12 @@ func main() {
15581558
}
15591559
`
15601560
Run(t, mod, func(t *testing.T, env *Env) {
1561+
env.Await(FileWatchMatching("bob"))
15611562
env.RemoveWorkspaceFile("bob")
15621563
env.Await(
1563-
env.DiagnosticAtRegexp("main.go", `"mod.com/bob"`),
1564+
env.DiagnosticAtRegexp("cmd/main.go", `"mod.com/bob"`),
15641565
EmptyDiagnostics("bob/bob.go"),
1565-
RegistrationMatching("didChangeWatchedFiles"),
1566+
NoFileWatchMatching("bob"),
15661567
)
15671568
})
15681569
}

gopls/internal/regtest/workspace/fromenv_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ use (
4141
WithOptions(
4242
EnvVars{"GOWORK": "$SANDBOX_WORKDIR/config/go.work"},
4343
).Run(t, files, func(t *testing.T, env *Env) {
44+
// When we have an explicit GOWORK set, we should get a file watch request.
45+
env.Await(FileWatchMatching(`config.go\.work`))
4446
// Even though work/b is not open, we should get its diagnostics as it is
4547
// included in the workspace.
4648
env.OpenFile("work/a/a.go")

internal/lsp/cache/snapshot.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,10 @@ func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]stru
887887
fmt.Sprintf("**/*.{%s}", extensions): {},
888888
}
889889

890+
if s.view.explicitGowork != "" {
891+
patterns[s.view.explicitGowork.Filename()] = struct{}{}
892+
}
893+
890894
// Add a pattern for each Go module in the workspace that is not within the view.
891895
dirs := s.workspace.dirs(ctx, s)
892896
for _, dir := range dirs {

internal/lsp/regtest/env.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ type State struct {
8585
showMessage []*protocol.ShowMessageParams
8686
showMessageRequest []*protocol.ShowMessageRequestParams
8787

88-
registrations []*protocol.RegistrationParams
89-
unregistrations []*protocol.UnregistrationParams
88+
registrations []*protocol.RegistrationParams
89+
registeredCapabilities map[string]protocol.Registration
90+
unregistrations []*protocol.UnregistrationParams
9091

9192
// outstandingWork is a map of token->work summary. All tokens are assumed to
9293
// be string, though the spec allows for numeric tokens as well. When work
@@ -226,6 +227,12 @@ func (a *Awaiter) onRegistration(_ context.Context, m *protocol.RegistrationPara
226227
defer a.mu.Unlock()
227228

228229
a.state.registrations = append(a.state.registrations, m)
230+
if a.state.registeredCapabilities == nil {
231+
a.state.registeredCapabilities = make(map[string]protocol.Registration)
232+
}
233+
for _, reg := range m.Registrations {
234+
a.state.registeredCapabilities[reg.Method] = reg
235+
}
229236
a.checkConditionsLocked()
230237
return nil
231238
}

internal/lsp/regtest/expectation.go

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -394,32 +394,66 @@ func NoLogMatching(typ protocol.MessageType, re string) LogExpectation {
394394
}
395395
}
396396

397-
// RegistrationExpectation is an expectation on the capability registrations
398-
// received by the editor from gopls.
399-
type RegistrationExpectation struct {
400-
check func([]*protocol.RegistrationParams) Verdict
401-
description string
397+
// FileWatchMatching expects that a file registration matches re.
398+
func FileWatchMatching(re string) SimpleExpectation {
399+
return SimpleExpectation{
400+
check: checkFileWatch(re, Met, Unmet),
401+
description: fmt.Sprintf("file watch matching %q", re),
402+
}
402403
}
403404

404-
// Check implements the Expectation interface.
405-
func (e RegistrationExpectation) Check(s State) Verdict {
406-
return e.check(s.registrations)
405+
// NoFileWatchMatching expects that no file registration matches re.
406+
func NoFileWatchMatching(re string) SimpleExpectation {
407+
return SimpleExpectation{
408+
check: checkFileWatch(re, Unmet, Met),
409+
description: fmt.Sprintf("no file watch matching %q", re),
410+
}
407411
}
408412

409-
// Description implements the Expectation interface.
410-
func (e RegistrationExpectation) Description() string {
411-
return e.description
413+
func checkFileWatch(re string, onMatch, onNoMatch Verdict) func(State) Verdict {
414+
rec := regexp.MustCompile(re)
415+
return func(s State) Verdict {
416+
r := s.registeredCapabilities["workspace/didChangeWatchedFiles"]
417+
watchers := jsonProperty(r.RegisterOptions, "watchers").([]interface{})
418+
for _, watcher := range watchers {
419+
pattern := jsonProperty(watcher, "globPattern").(string)
420+
if rec.MatchString(pattern) {
421+
return onMatch
422+
}
423+
}
424+
return onNoMatch
425+
}
426+
}
427+
428+
// jsonProperty extracts a value from a path of JSON property names, assuming
429+
// the default encoding/json unmarshaling to the empty interface (i.e.: that
430+
// JSON objects are unmarshalled as map[string]interface{})
431+
//
432+
// For example, if obj is unmarshalled from the following json:
433+
//
434+
// {
435+
// "foo": { "bar": 3 }
436+
// }
437+
//
438+
// Then jsonProperty(obj, "foo", "bar") will be 3.
439+
func jsonProperty(obj interface{}, path ...string) interface{} {
440+
if len(path) == 0 || obj == nil {
441+
return obj
442+
}
443+
m := obj.(map[string]interface{})
444+
return jsonProperty(m[path[0]], path[1:]...)
412445
}
413446

414447
// RegistrationMatching asserts that the client has received a capability
415448
// registration matching the given regexp.
416-
func RegistrationMatching(re string) RegistrationExpectation {
417-
rec, err := regexp.Compile(re)
418-
if err != nil {
419-
panic(err)
420-
}
421-
check := func(params []*protocol.RegistrationParams) Verdict {
422-
for _, p := range params {
449+
//
450+
// TODO(rfindley): remove this once TestWatchReplaceTargets has been revisited.
451+
//
452+
// Deprecated: use (No)FileWatchMatching
453+
func RegistrationMatching(re string) SimpleExpectation {
454+
rec := regexp.MustCompile(re)
455+
check := func(s State) Verdict {
456+
for _, p := range s.registrations {
423457
for _, r := range p.Registrations {
424458
if rec.Match([]byte(r.Method)) {
425459
return Met
@@ -428,38 +462,18 @@ func RegistrationMatching(re string) RegistrationExpectation {
428462
}
429463
return Unmet
430464
}
431-
return RegistrationExpectation{
465+
return SimpleExpectation{
432466
check: check,
433467
description: fmt.Sprintf("registration matching %q", re),
434468
}
435469
}
436470

437-
// UnregistrationExpectation is an expectation on the capability
438-
// unregistrations received by the editor from gopls.
439-
type UnregistrationExpectation struct {
440-
check func([]*protocol.UnregistrationParams) Verdict
441-
description string
442-
}
443-
444-
// Check implements the Expectation interface.
445-
func (e UnregistrationExpectation) Check(s State) Verdict {
446-
return e.check(s.unregistrations)
447-
}
448-
449-
// Description implements the Expectation interface.
450-
func (e UnregistrationExpectation) Description() string {
451-
return e.description
452-
}
453-
454471
// UnregistrationMatching asserts that the client has received an
455472
// unregistration whose ID matches the given regexp.
456-
func UnregistrationMatching(re string) UnregistrationExpectation {
457-
rec, err := regexp.Compile(re)
458-
if err != nil {
459-
panic(err)
460-
}
461-
check := func(params []*protocol.UnregistrationParams) Verdict {
462-
for _, p := range params {
473+
func UnregistrationMatching(re string) SimpleExpectation {
474+
rec := regexp.MustCompile(re)
475+
check := func(s State) Verdict {
476+
for _, p := range s.unregistrations {
463477
for _, r := range p.Unregisterations {
464478
if rec.Match([]byte(r.Method)) {
465479
return Met
@@ -468,7 +482,7 @@ func UnregistrationMatching(re string) UnregistrationExpectation {
468482
}
469483
return Unmet
470484
}
471-
return UnregistrationExpectation{
485+
return SimpleExpectation{
472486
check: check,
473487
description: fmt.Sprintf("unregistration matching %q", re),
474488
}

0 commit comments

Comments
 (0)