From 5d652ac67d86b64a7ad1d6b8c0f596122b16fccc Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Fri, 3 Apr 2026 17:28:08 -0700 Subject: [PATCH] Fix incorrect parser errors from unloaded assemblies An attempt to fix #5381 where we believe the cause is that the didOpen()/didChange() notifications are being sent before PowerShell has totally finished loading. Since these run the PowerShell parser on the OmniSharp thread pool, they essentially race the analysis of the files by PSScriptAnalyzer on the PowerShell runspace pool. So when they beat it, the return errors. But when they're beaten by it, the PSSA analysis has caused the assemblies (and so custom attributes) to be loaded, no longer erroring. I posited we could gate the notifications instead of duplicating them like in #5402, and if the `Middleware` works as suspected by me (and Claude) this should fix it. Morever, we now also use a proper `Promise` instead of a while loop around a sleep to wait for the LSP server to be running. While all of this could just be an AI hallucination...it seems right. --- src/session.ts | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/session.ts b/src/session.ts index a6bfde9b94..4d54f76807 100644 --- a/src/session.ts +++ b/src/session.ts @@ -126,6 +126,11 @@ export class SessionManager implements Middleware { private versionDetails: IPowerShellVersionDetails | undefined; private traceLogLevelHandler?: vscode.Disposable; + // Promise-based gate resolved when the session reaches Running status. + // Used by waitUntilStarted() and the didOpen()/didChange() notifications. + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type + private started = Promise.withResolvers(); + constructor( private extensionContext: vscode.ExtensionContext, private sessionSettings: Settings, @@ -295,6 +300,7 @@ export class SessionManager implements Middleware { `Started PowerShell v${this.versionDetails.version}.`, ); this.setSessionRunningStatus(); // Yay, we made it! + this.started.resolve(); // Release didOpen()/didChange() notifications and waitUntilStarted() gate await this.writePidIfInDevMode(this.languageServerProcess); @@ -328,6 +334,8 @@ export class SessionManager implements Middleware { } this.languageClient = undefined; + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type + this.started = Promise.withResolvers(); // Stop and dispose the PowerShell process(es). this.debugSessionProcess?.dispose(); @@ -497,9 +505,27 @@ export class SessionManager implements Middleware { } public async waitUntilStarted(): Promise { - while (this.sessionStatus !== SessionStatus.Running) { - await utils.sleep(200); - } + await this.started.promise; + } + + // Middleware hooks to delay document sync notifications until the server + // is fully initialized. This prevents stale parser diagnostics (e.g. + // unresolved custom attribute types) that would otherwise appear because + // textDocument/didOpen is sent before the server's type resolution is ready. + public async didOpen( + document: vscode.TextDocument, + next: (document: vscode.TextDocument) => Promise, + ): Promise { + await this.started.promise; + return next(document); + } + + public async didChange( + event: vscode.TextDocumentChangeEvent, + next: (event: vscode.TextDocumentChangeEvent) => Promise, + ): Promise { + await this.started.promise; + return next(event); } // TODO: Is this used by the magic of "Middleware" in the client library?