Skip to content

Commit 9ad7ba1

Browse files
authored
fix: prevent request failure notification spam for new files in lazy loader (#1202)
1 parent f9f9f3a commit 9ad7ba1

File tree

3 files changed

+113
-24
lines changed

3 files changed

+113
-24
lines changed

cmd/templ/lspcmd/proxy/server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,12 @@ var supportedCodeActions = map[string]bool{}
368368
func (p *Server) CodeAction(ctx context.Context, params *lsp.CodeActionParams) (result []lsp.CodeAction, err error) {
369369
p.Log.Info("client -> server: CodeAction", slog.Any("params", params))
370370
defer p.Log.Info("client -> server: CodeAction end")
371+
372+
if p.NoPreload && !p.templDocLazyLoader.HasLoaded(params.TextDocument) {
373+
p.Log.Error("lazy loader has not loaded document", slog.Any("params", params))
374+
return nil, nil
375+
}
376+
371377
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
372378
if !isTemplFile {
373379
return p.Target.CodeAction(ctx, params)

internal/lazyloader/templdoclazyloader.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ type TemplDocLazyLoader interface {
1919

2020
// Unload unloads a templ document and its dependencies.
2121
Unload(ctx context.Context, params *lsp.DidCloseTextDocumentParams) error
22+
23+
// HasLoaded reports whether a templ document and its dependencies have been loaded.
24+
HasLoaded(doc lsp.TextDocumentIdentifier) bool
2225
}
2326

2427
// templDocLazyLoader is a loader that uses the go/packages API to lazily load templ documents in the dependency graph.
2528
type templDocLazyLoader struct {
2629
loadedPkgs map[string]*packages.Package
2730
openDocHeaders map[string]docHeader
31+
docsPendingLoad map[string]struct{}
2832
pkgLoader pkgLoader
2933
pkgTraverser pkgTraverser
3034
docHeaderParser docHeaderParser
@@ -40,8 +44,9 @@ type NewParams struct {
4044
// New creates a new lazy loader using the provided arguments.
4145
func New(params NewParams) TemplDocLazyLoader {
4246
return &templDocLazyLoader{
43-
loadedPkgs: make(map[string]*packages.Package),
44-
openDocHeaders: make(map[string]docHeader),
47+
loadedPkgs: make(map[string]*packages.Package),
48+
openDocHeaders: make(map[string]docHeader),
49+
docsPendingLoad: make(map[string]struct{}),
4550
pkgLoader: &goPkgLoader{
4651
openDocSources: params.OpenDocSources,
4752
loadPackages: packages.Load,
@@ -66,6 +71,7 @@ func (l *templDocLazyLoader) Load(ctx context.Context, params *lsp.DidOpenTextDo
6671
pkg, err := l.pkgLoader.load(filename)
6772
if err != nil {
6873
if errors.Is(err, errNoPkgsLoaded) {
74+
l.docsPendingLoad[filename] = struct{}{}
6975
return l.docHandler.HandleDidOpen(ctx, params)
7076
}
7177
return fmt.Errorf("load package for file %q: %w", filename, err)
@@ -95,6 +101,7 @@ func (l *templDocLazyLoader) Sync(ctx context.Context, params *lsp.DidChangeText
95101
if err != nil {
96102
return fmt.Errorf("load package for file %q: %w", filename, err)
97103
}
104+
delete(l.docsPendingLoad, filename)
98105

99106
if _, ok := l.loadedPkgs[pkg.PkgPath]; !ok {
100107
if err := l.pkgTraverser.openTopologically(ctx, pkg); err != nil {
@@ -128,6 +135,7 @@ func (l *templDocLazyLoader) Unload(ctx context.Context, params *lsp.DidCloseTex
128135
pkg, err := l.pkgLoader.load(filename)
129136
if err != nil {
130137
if errors.Is(err, errNoPkgsLoaded) {
138+
delete(l.docsPendingLoad, filename)
131139
return l.docHandler.HandleDidClose(ctx, params)
132140
}
133141
return fmt.Errorf("load package for file %q: %w", filename, err)
@@ -142,3 +150,12 @@ func (l *templDocLazyLoader) Unload(ctx context.Context, params *lsp.DidCloseTex
142150

143151
return nil
144152
}
153+
154+
func (l *templDocLazyLoader) HasLoaded(doc lsp.TextDocumentIdentifier) bool {
155+
if _, ok := l.docsPendingLoad[doc.URI.Filename()]; ok {
156+
return false
157+
}
158+
159+
_, ok := l.openDocHeaders[doc.URI.Filename()]
160+
return ok
161+
}

internal/lazyloader/templdoclazyloader_test.go

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import (
1313

1414
func TestTemplDocLazyLoaderLoad(t *testing.T) {
1515
tests := []struct {
16-
name string
17-
loader *templDocLazyLoader
18-
params *lsp.DidOpenTextDocumentParams
19-
wantLoadedPkgs map[string]*packages.Package
20-
wantOpenDocHeaders map[string]docHeader
21-
wantErrContains string
16+
name string
17+
loader *templDocLazyLoader
18+
params *lsp.DidOpenTextDocumentParams
19+
wantLoadedPkgs map[string]*packages.Package
20+
wantOpenDocHeaders map[string]docHeader
21+
wantDocsPendingLoad map[string]struct{}
22+
wantErrContains string
2223
}{
2324
{
2425
name: "load package failed",
@@ -48,12 +49,16 @@ func TestTemplDocLazyLoaderLoad(t *testing.T) {
4849
openedDocs: map[string]int{},
4950
error: errors.New("mock error"),
5051
},
52+
docsPendingLoad: map[string]struct{}{},
5153
},
5254
params: &lsp.DidOpenTextDocumentParams{
5355
TextDocument: lsp.TextDocumentItem{
5456
URI: lsp.DocumentURI("file:///foo.go"),
5557
},
5658
},
59+
wantDocsPendingLoad: map[string]struct{}{
60+
"/foo.go": {},
61+
},
5762
wantErrContains: "mock error",
5863
},
5964
{
@@ -113,6 +118,7 @@ func TestTemplDocLazyLoaderLoad(t *testing.T) {
113118
if tt.wantErrContains != "" {
114119
assert.Error(t, err)
115120
assert.ErrorContains(t, err, tt.wantErrContains)
121+
assert.Equal(t, tt.wantDocsPendingLoad, tt.loader.docsPendingLoad)
116122
} else {
117123
assert.NoError(t, err)
118124
assert.Equal(t, tt.wantLoadedPkgs, tt.loader.loadedPkgs)
@@ -124,14 +130,15 @@ func TestTemplDocLazyLoaderLoad(t *testing.T) {
124130

125131
func TestTemplDocLazyLoaderSync(t *testing.T) {
126132
tests := []struct {
127-
name string
128-
loader *templDocLazyLoader
129-
params *lsp.DidChangeTextDocumentParams
130-
wantLoadedPkgs map[string]*packages.Package
131-
wantOpenDocHeaders map[string]docHeader
132-
wantOpenedPkgs []string
133-
wantClosedPkgs []string
134-
wantErrContains string
133+
name string
134+
loader *templDocLazyLoader
135+
params *lsp.DidChangeTextDocumentParams
136+
wantLoadedPkgs map[string]*packages.Package
137+
wantOpenDocHeaders map[string]docHeader
138+
wantOpenedPkgs []string
139+
wantClosedPkgs []string
140+
wantDocsPendingLoad map[string]struct{}
141+
wantErrContains string
135142
}{
136143
{
137144
name: "same header",
@@ -307,6 +314,9 @@ func TestTemplDocLazyLoaderSync(t *testing.T) {
307314
openErrors: map[string]error{},
308315
},
309316
loadedPkgs: map[string]*packages.Package{},
317+
docsPendingLoad: map[string]struct{}{
318+
"/foo.go": {},
319+
},
310320
},
311321
params: &lsp.DidChangeTextDocumentParams{
312322
TextDocument: lsp.VersionedTextDocumentIdentifier{
@@ -336,7 +346,8 @@ func TestTemplDocLazyLoaderSync(t *testing.T) {
336346
},
337347
},
338348
},
339-
wantOpenedPkgs: []string{"foo_pkg"},
349+
wantOpenedPkgs: []string{"foo_pkg"},
350+
wantDocsPendingLoad: map[string]struct{}{},
340351
},
341352
{
342353
name: "open topologically failed",
@@ -542,6 +553,7 @@ func TestTemplDocLazyLoaderSync(t *testing.T) {
542553
assert.NoError(t, err)
543554
assert.Equal(t, tt.wantLoadedPkgs, tt.loader.loadedPkgs)
544555
assert.Equal(t, tt.wantOpenDocHeaders, tt.loader.openDocHeaders)
556+
assert.Equal(t, tt.wantDocsPendingLoad, tt.loader.docsPendingLoad)
545557

546558
traverser, ok := tt.loader.pkgTraverser.(*mockPkgTraverser)
547559
require.True(t, ok)
@@ -554,12 +566,13 @@ func TestTemplDocLazyLoaderSync(t *testing.T) {
554566

555567
func TestTemplDocLazyLoaderUnload(t *testing.T) {
556568
tests := []struct {
557-
name string
558-
loader *templDocLazyLoader
559-
params *lsp.DidCloseTextDocumentParams
560-
wantLoadedPkgs map[string]*packages.Package
561-
wantOpenDocHeaders map[string]docHeader
562-
wantErrContains string
569+
name string
570+
loader *templDocLazyLoader
571+
params *lsp.DidCloseTextDocumentParams
572+
wantLoadedPkgs map[string]*packages.Package
573+
wantOpenDocHeaders map[string]docHeader
574+
wantDocsPendingLoad map[string]struct{}
575+
wantErrContains string
563576
}{
564577
{
565578
name: "load package failed",
@@ -589,13 +602,17 @@ func TestTemplDocLazyLoaderUnload(t *testing.T) {
589602
closedDocs: map[string]int{},
590603
error: errors.New("mock error"),
591604
},
605+
docsPendingLoad: map[string]struct{}{
606+
"/foo.go": {},
607+
},
592608
},
593609
params: &lsp.DidCloseTextDocumentParams{
594610
TextDocument: lsp.TextDocumentIdentifier{
595611
URI: lsp.DocumentURI("file:///foo.go"),
596612
},
597613
},
598-
wantErrContains: "mock error",
614+
wantDocsPendingLoad: map[string]struct{}{},
615+
wantErrContains: "mock error",
599616
},
600617
{
601618
name: "close topologically failed",
@@ -654,6 +671,7 @@ func TestTemplDocLazyLoaderUnload(t *testing.T) {
654671
if tt.wantErrContains != "" {
655672
assert.Error(t, err)
656673
assert.ErrorContains(t, err, tt.wantErrContains)
674+
assert.Equal(t, tt.wantDocsPendingLoad, tt.loader.docsPendingLoad)
657675
} else {
658676
assert.NoError(t, err)
659677
assert.Equal(t, tt.wantLoadedPkgs, tt.loader.loadedPkgs)
@@ -663,6 +681,54 @@ func TestTemplDocLazyLoaderUnload(t *testing.T) {
663681
}
664682
}
665683

684+
func TestTemplDocLazyLoaderHasLoaded(t *testing.T) {
685+
tests := []struct {
686+
name string
687+
loader *templDocLazyLoader
688+
doc lsp.TextDocumentIdentifier
689+
expected bool
690+
}{
691+
{
692+
name: "doc pending load",
693+
loader: &templDocLazyLoader{
694+
docsPendingLoad: map[string]struct{}{
695+
"/foo.go": {},
696+
},
697+
},
698+
doc: lsp.TextDocumentIdentifier{
699+
URI: lsp.DocumentURI("file:///foo.go"),
700+
},
701+
},
702+
{
703+
name: "doc loaded",
704+
loader: &templDocLazyLoader{
705+
openDocHeaders: map[string]docHeader{
706+
"/foo.go": &goDocHeader{pkgName: "foo_pkg"},
707+
},
708+
},
709+
doc: lsp.TextDocumentIdentifier{
710+
URI: lsp.DocumentURI("file:///foo.go"),
711+
},
712+
expected: true,
713+
},
714+
{
715+
name: "doc not loaded",
716+
loader: &templDocLazyLoader{
717+
openDocHeaders: map[string]docHeader{},
718+
},
719+
doc: lsp.TextDocumentIdentifier{
720+
URI: lsp.DocumentURI("file:///foo.go"),
721+
},
722+
},
723+
}
724+
725+
for _, tt := range tests {
726+
t.Run(tt.name, func(t *testing.T) {
727+
assert.Equal(t, tt.expected, tt.loader.HasLoaded(tt.doc))
728+
})
729+
}
730+
}
731+
666732
type mockPkgTraverser struct {
667733
openedPkgs []string
668734
closedPkgs []string

0 commit comments

Comments
 (0)