@@ -146,6 +146,9 @@ type LanguageServer struct {
146146 regoRouter * rego.RegoRouter
147147 testingCompiler * ast.Compiler
148148
149+ // initializationGate blocks workers until the initialized notification is received
150+ initializationGate chan struct {}
151+
149152 commandRequest chan types.ExecuteCommandParams
150153 lintWorkspaceJobs chan lintWorkspaceJob
151154 lintFileJobs chan lintFileJob
@@ -213,6 +216,7 @@ func NewLanguageServerMinimal(ctx context.Context, opts *LanguageServerOptions,
213216 regoStore : store ,
214217 log : opts .Logger ,
215218 featureFlags : * featureFlags ,
219+ initializationGate : make (chan struct {}),
216220 lintFileJobs : make (chan lintFileJob , 10 ),
217221 lintWorkspaceJobs : make (chan lintWorkspaceJob , 10 ),
218222 builtinsPositionJobs : make (chan lintFileJob , 10 ),
@@ -260,13 +264,13 @@ func (l *LanguageServer) Handle(ctx context.Context, _ *jsonrpc2.Conn, req *json
260264 case "initialize" :
261265 return handler .WithContextAndParams (ctx , req , l .handleInitialize )
262266 case "initialized" :
263- return l .handleInitialized ()
267+ return l .handleInitialized (ctx )
264268 case "textDocument/definition" :
265269 return handler .WithParams (req , l .handleTextDocumentDefinition )
266270 case "textDocument/diagnostic" :
267271 return l .handleTextDocumentDiagnostic ()
268272 case "textDocument/didOpen" :
269- return handler .WithContextAndParams ( ctx , req , l .handleTextDocumentDidOpen )
273+ return handler .WithParams ( req , l .handleTextDocumentDidOpen )
270274 case "textDocument/didClose" :
271275 return handler .WithParams (req , l .handleTextDocumentDidClose )
272276 case "textDocument/didSave" :
@@ -365,13 +369,15 @@ func (l *LanguageServer) StartDiagnosticsWorker(ctx context.Context) {
365369 continue
366370 }
367371
368- // Send test locations update after parse completes
369- if parseSuccess {
370- l .testLocationJobs <- lintFileJob {Reason : job .Reason , URI : job .URI }
371- } else {
372- // Parse failed, send empty test locations
373- if err := l .sendTestLocations (ctx , job .URI , []any {}); err != nil {
374- l .log .Message ("failed to send empty test locations after parse failure: %s" , err )
372+ // Send test locations update after parse completes (if client supports it)
373+ if l .client .SupportsOPATestProvider () {
374+ if parseSuccess {
375+ l .testLocationJobs <- lintFileJob {Reason : job .Reason , URI : job .URI }
376+ } else {
377+ // Parse failed, send empty test locations
378+ if err := l .sendTestLocations (ctx , job .URI , []any {}); err != nil {
379+ l .log .Message ("failed to send empty test locations after parse failure: %s" , err )
380+ }
375381 }
376382 }
377383
@@ -1516,12 +1522,11 @@ func (l *LanguageServer) handleTextDocumentDefinition(params types.DefinitionPar
15161522}
15171523
15181524func (l * LanguageServer ) handleTextDocumentDidOpen (
1519- ctx context.Context ,
15201525 params types.DidOpenTextDocumentParams ,
15211526) (any , error ) {
15221527 // then we have started the server, and not yet received a suitable root to use.
15231528 if l .workspaceRootURI == "" {
1524- err := l .updateRootURI (ctx ,
1529+ err := l .updateRootURI (
15251530 // get the URI of the file's immediate parent
15261531 l .fromPath (filepath .Dir (uri .ToPath (params .TextDocument .URI ))),
15271532 )
@@ -2034,7 +2039,7 @@ func (l *LanguageServer) handleInitialize(ctx context.Context, params types.Init
20342039 }
20352040
20362041 if params .RootURI != "" {
2037- err := l .updateRootURI (ctx , params .RootURI )
2042+ err := l .updateRootURI (params .RootURI )
20382043 if err != nil {
20392044 l .log .Message ("failed to set rootURI: %w" , err )
20402045 }
@@ -2044,7 +2049,7 @@ func (l *LanguageServer) handleInitialize(ctx context.Context, params types.Init
20442049 l .log .Message ("cannot operate with more than one workspace folder, using: %s" , (* params .WorkspaceFolders )[0 ].URI )
20452050 }
20462051
2047- err := l .updateRootURI (ctx , (* params .WorkspaceFolders )[0 ].URI )
2052+ err := l .updateRootURI ((* params .WorkspaceFolders )[0 ].URI )
20482053 if err != nil {
20492054 l .log .Message ("failed to set rootURI to workspace folder: %w" , err )
20502055 }
@@ -2053,7 +2058,7 @@ func (l *LanguageServer) handleInitialize(ctx context.Context, params types.Init
20532058 return initializeResult , nil
20542059}
20552060
2056- func (l * LanguageServer ) updateRootURI (ctx context. Context , rootURI string ) error {
2061+ func (l * LanguageServer ) updateRootURI (rootURI string ) error {
20572062 // rootURI not expected to have a trailing slash, remove if present for
20582063 // consistency
20592064 normalizedRootURI := strings .TrimSuffix (rootURI , string (os .PathSeparator ))
@@ -2105,19 +2110,6 @@ func (l *LanguageServer) updateRootURI(ctx context.Context, rootURI string) erro
21052110 l .log .Message ("no config file found for workspace" )
21062111 }
21072112
2108- _ , failed , err := l .loadWorkspaceContents (ctx , false )
2109- for _ , f := range failed {
2110- l .log .Message ("failed to load file %s: %s" , f .URI , f .Error )
2111- }
2112-
2113- if err != nil {
2114- l .log .Message ("failed to load workspace contents: %s" , err )
2115- }
2116-
2117- // 'OverwriteAggregates' is set to populate the cache's initial aggregate state.
2118- // Subsequent runs of lintWorkspaceJobs will not set this and use the cached state.
2119- l .lintWorkspaceJobs <- lintWorkspaceJob {Reason : "server initialize" , OverwriteAggregates : true }
2120-
21212113 return nil
21222114}
21232115
@@ -2161,13 +2153,15 @@ func (l *LanguageServer) loadWorkspaceContents(ctx context.Context, newOnly bool
21612153 return nil // continue processing other files
21622154 }
21632155
2164- if parseSuccess {
2165- l .testLocationJobs <- lintFileJob {Reason : "server initialized" , URI : fileURI }
2166- } else {
2167- // this is covering the case where the client starts and the state
2168- // is different (the client remembers test locations and state).
2169- if err := l .sendTestLocations (ctx , fileURI , []any {}); err != nil {
2170- l .log .Message ("failed to send empty test locations after parse failure: %s" , err )
2156+ if l .client .SupportsOPATestProvider () {
2157+ if parseSuccess {
2158+ l .testLocationJobs <- lintFileJob {Reason : "server initialized" , URI : fileURI }
2159+ } else {
2160+ // this is covering the case where the client starts and the state
2161+ // is different (the client remembers test locations and state).
2162+ if err := l .sendTestLocations (ctx , fileURI , []any {}); err != nil {
2163+ l .log .Message ("failed to send empty test locations after parse failure: %s" , err )
2164+ }
21712165 }
21722166 }
21732167
@@ -2187,12 +2181,33 @@ func (l *LanguageServer) loadWorkspaceContents(ctx context.Context, newOnly bool
21872181 return changedOrNewURIs , failed , nil
21882182}
21892183
2190- func (l * LanguageServer ) handleInitialized () (any , error ) {
2191- // if running without config, then we should send the diagnostic request now
2192- // otherwise it'll happen when the config is loaded
2193- if ! l .configWatcher .IsWatching () {
2194- l .lintWorkspaceJobs <- lintWorkspaceJob {Reason : "server initialized" }
2195- }
2184+ func (l * LanguageServer ) handleInitialized (ctx context.Context ) (any , error ) {
2185+ // Signal workers that initialization handshake is complete
2186+ close (l .initializationGate )
2187+
2188+ // Load workspace contents and start jobs asynchronously
2189+ // This allows us to respond to the client immediately while workspace
2190+ // loading happens in the background
2191+ go func () {
2192+ _ , failed , err := l .loadWorkspaceContents (ctx , false )
2193+ for _ , f := range failed {
2194+ l .log .Message ("failed to load file %s: %s" , f .URI , f .Error )
2195+ }
2196+
2197+ if err != nil {
2198+ l .log .Message ("failed to load workspace contents: %s" , err )
2199+ }
2200+
2201+ // 'OverwriteAggregates' is set to populate the cache's initial aggregate state.
2202+ // Subsequent runs of lintWorkspaceJobs will not set this and use the cached state.
2203+ l .lintWorkspaceJobs <- lintWorkspaceJob {Reason : "server initialize" , OverwriteAggregates : true }
2204+
2205+ // if running without config, then we should send the diagnostic request now
2206+ // otherwise it'll happen when the config is loaded
2207+ if ! l .configWatcher .IsWatching () {
2208+ l .lintWorkspaceJobs <- lintWorkspaceJob {Reason : "server initialized" }
2209+ }
2210+ }()
21962211
21972212 return emptyStruct , nil
21982213}
0 commit comments