From bcd9e2fd708d245cc3f83f1774be1a46d9923f74 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 24 Oct 2022 09:15:47 -0400 Subject: [PATCH 1/2] semanticToken request wait until validate finishes --- src/LanguageServer.spec.ts | 51 ++++++++++++++++++++++++++++++++++++++ src/LanguageServer.ts | 10 ++++++-- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/LanguageServer.spec.ts b/src/LanguageServer.spec.ts index f30cb6b6d..1a07075a7 100644 --- a/src/LanguageServer.spec.ts +++ b/src/LanguageServer.spec.ts @@ -16,6 +16,7 @@ import { expectZeroDiagnostics, trim } from './testHelpers.spec'; import { isBrsFile, isLiteralString } from './astUtils/reflection'; import { createVisitor, WalkMode } from './astUtils/visitors'; import { tempDir, rootDir } from './testHelpers.spec'; +import { URI } from 'vscode-uri'; const sinon = createSandbox(); @@ -1103,6 +1104,56 @@ describe('LanguageServer', () => { }); }); }); + + it('semantic tokens request waits until after validation has finished', async () => { + fsExtra.outputFileSync(s`${rootDir}/source/main.bs`, ` + sub main() + print \`hello world\` + end sub + `); + let spaceCount = 0; + const getContents = () => { + return ` + namespace sgnode + sub speak(message) + print message + end sub + + sub sayHello() + sgnode.speak("Hello")${' '.repeat(spaceCount++)} + end sub + end namespace + `; + }; + + const uri = URI.file(s`${rootDir}/source/sgnode.bs`).toString(); + + fsExtra.outputFileSync(s`${rootDir}/source/sgnode.bs`, getContents()); + server.run(); + await server['syncProjects'](); + expectZeroDiagnostics(server.projects[0].builder.program); + + fsExtra.outputFileSync(s`${rootDir}/source/sgnode.bs`, getContents()); + const changeWatchedFilesPromise = server['onDidChangeWatchedFiles']({ + changes: [{ + type: FileChangeType.Changed, + uri: uri + }] + }); + const document = { + getText: () => getContents(), + uri: uri + } as TextDocument; + + const semanticTokensPromise = server['onFullSemanticTokens']({ + textDocument: document + }); + await Promise.all([ + changeWatchedFilesPromise, + semanticTokensPromise + ]); + expectZeroDiagnostics(server.projects[0].builder.program); + }); }); export function getFileProtocolPath(fullPath: string) { diff --git a/src/LanguageServer.ts b/src/LanguageServer.ts index 09d43093f..e6559542a 100644 --- a/src/LanguageServer.ts +++ b/src/LanguageServer.ts @@ -1080,7 +1080,7 @@ export class LanguageServer { // validate all projects await this.validateAllThrottled(); } catch (e: any) { - this.sendCriticalFailure(`Critical error parsing / validating ${filePath}: ${e.message}`); + this.sendCriticalFailure(`Critical error parsing/validating ${filePath}: ${e.message}`); } } @@ -1206,7 +1206,13 @@ export class LanguageServer { @AddStackToErrorMessage private async onFullSemanticTokens(params: SemanticTokensParams) { await this.waitAllProjectFirstRuns(); - await this.keyedThrottler.onIdleOnce(util.uriToPath(params.textDocument.uri), true); + await Promise.all([ + //wait for the file to settle (in case there are multiple file changes in quick succession + this.keyedThrottler.onIdleOnce(util.uriToPath(params.textDocument.uri), true), + // wait for the validation to finish before providing semantic tokens. program.validate() populates and then caches AstNode.parent properties. + // If we don't wait, then fetching semantic tokens can cause some invalid cache + this.validateThrottler.onIdleOnce(false) + ]); const srcPath = util.uriToPath(params.textDocument.uri); for (const project of this.projects) { From 67199a28f4e71abdc4bc8bcfd3cf9a0260ed6a76 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 24 Oct 2022 09:23:33 -0400 Subject: [PATCH 2/2] Update src/LanguageServer.ts --- src/LanguageServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LanguageServer.ts b/src/LanguageServer.ts index e6559542a..e4d849f85 100644 --- a/src/LanguageServer.ts +++ b/src/LanguageServer.ts @@ -1207,7 +1207,7 @@ export class LanguageServer { private async onFullSemanticTokens(params: SemanticTokensParams) { await this.waitAllProjectFirstRuns(); await Promise.all([ - //wait for the file to settle (in case there are multiple file changes in quick succession + //wait for the file to settle (in case there are multiple file changes in quick succession) this.keyedThrottler.onIdleOnce(util.uriToPath(params.textDocument.uri), true), // wait for the validation to finish before providing semantic tokens. program.validate() populates and then caches AstNode.parent properties. // If we don't wait, then fetching semantic tokens can cause some invalid cache