From c0ed920f42254c38d36ab0656145367dbf6a5c88 Mon Sep 17 00:00:00 2001 From: Stephan Brandauer Date: Wed, 27 Nov 2024 13:09:37 +0000 Subject: [PATCH 1/3] fix spurious compilation errors by notifying language server of newly created packs --- .../local-databases/database-manager.ts | 3 ++ extensions/ql-vscode/src/extension.ts | 8 ++++-- .../src/local-queries/local-queries.ts | 3 ++ .../src/local-queries/qlpack-generator.ts | 28 +++++++++++++++++++ .../local-queries/skeleton-query-wizard.ts | 3 ++ 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/extensions/ql-vscode/src/databases/local-databases/database-manager.ts b/extensions/ql-vscode/src/databases/local-databases/database-manager.ts index f820625a5d8..4a5798fe4d0 100644 --- a/extensions/ql-vscode/src/databases/local-databases/database-manager.ts +++ b/extensions/ql-vscode/src/databases/local-databases/database-manager.ts @@ -44,6 +44,7 @@ import { telemetryListener } from "../../common/vscode/telemetry"; import type { LanguageContextStore } from "../../language-context-store"; import type { DatabaseOrigin } from "./database-origin"; import { ensureZippedSourceLocation } from "./database-contents"; +import type { LanguageClient } from "vscode-languageclient/node"; /** * The name of the key in the workspaceState dictionary in which we @@ -118,6 +119,7 @@ export class DatabaseManager extends DisposableObject { private readonly app: App, private readonly qs: QueryRunner, private readonly cli: CodeQLCliServer, + private readonly langClient: LanguageClient, private readonly languageContext: LanguageContextStore, public logger: Logger, ) { @@ -407,6 +409,7 @@ export class DatabaseManager extends DisposableObject { const qlPackGenerator = new QlPackGenerator( databaseItem.language, this.cli, + this.langClient, qlpackStoragePath, qlpackStoragePath, ); diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index fde7cbec42a..6c36a3b3226 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -792,12 +792,16 @@ async function activateWithInstalledDistribution( const languageSelectionPanel = new LanguageSelectionPanel(languageContext); ctx.subscriptions.push(languageSelectionPanel); + void extLogger.log("Initializing CodeQL language server."); + const languageClient = createLanguageClient(qlConfigurationListener); + void extLogger.log("Initializing database manager."); const dbm = new DatabaseManager( ctx, app, qs, cliServer, + languageClient, languageContext, extLogger, ); @@ -961,9 +965,6 @@ async function activateWithInstalledDistribution( ctx.subscriptions.push(tmpDirDisposal); - void extLogger.log("Initializing CodeQL language server."); - const languageClient = createLanguageClient(qlConfigurationListener); - const localQueries = new LocalQueries( app, qs, @@ -971,6 +972,7 @@ async function activateWithInstalledDistribution( dbm, databaseFetcher, cliServer, + languageClient, databaseUI, localQueryResultsView, queryStorageDir, diff --git a/extensions/ql-vscode/src/local-queries/local-queries.ts b/extensions/ql-vscode/src/local-queries/local-queries.ts index 2961586650b..1bcab430953 100644 --- a/extensions/ql-vscode/src/local-queries/local-queries.ts +++ b/extensions/ql-vscode/src/local-queries/local-queries.ts @@ -55,6 +55,7 @@ import { tryGetQueryLanguage } from "../common/query-language"; import type { LanguageContextStore } from "../language-context-store"; import type { ExtensionApp } from "../common/vscode/extension-app"; import type { DatabaseFetcher } from "../databases/database-fetcher"; +import type { LanguageClient } from "vscode-languageclient/node"; export enum QuickEvalType { None, @@ -72,6 +73,7 @@ export class LocalQueries extends DisposableObject { private readonly databaseManager: DatabaseManager, private readonly databaseFetcher: DatabaseFetcher, private readonly cliServer: CodeQLCliServer, + private readonly langClient: LanguageClient, private readonly databaseUI: DatabaseUI, private readonly localQueryResultsView: ResultsView, private readonly queryStorageDir: string, @@ -324,6 +326,7 @@ export class LocalQueries extends DisposableObject { const language = this.languageContextStore.selectedLanguage; const skeletonQueryWizard = new SkeletonQueryWizard( this.cliServer, + this.langClient, progress, this.app, this.databaseManager, diff --git a/extensions/ql-vscode/src/local-queries/qlpack-generator.ts b/extensions/ql-vscode/src/local-queries/qlpack-generator.ts index 41b4fbd2381..5c00306c1cf 100644 --- a/extensions/ql-vscode/src/local-queries/qlpack-generator.ts +++ b/extensions/ql-vscode/src/local-queries/qlpack-generator.ts @@ -6,6 +6,11 @@ import type { CodeQLCliServer } from "../codeql-cli/cli"; import type { QueryLanguage } from "../common/query-language"; import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders"; import { basename } from "../common/path"; +import { + DidChangeWatchedFilesNotification, + FileChangeType, +} from "vscode-languageclient"; +import type { LanguageClient } from "vscode-languageclient/node"; export class QlPackGenerator { private qlpackName: string | undefined; @@ -17,6 +22,7 @@ export class QlPackGenerator { constructor( private readonly queryLanguage: QueryLanguage, private readonly cliServer: CodeQLCliServer, + private readonly langClient: LanguageClient, private readonly storagePath: string, private readonly queryStoragePath: string, private readonly includeFolderNameInQlpackName: boolean = false, @@ -114,5 +120,27 @@ select f, "Hello, world!" private async createCodeqlPackLockYaml() { await this.cliServer.packAdd(this.folderUri.fsPath, this.queryLanguage); + // when the language pack has not been available locally before, the + // packAdd command will download it. This will trigger a pack change that + // the language server needs to be notified of: + await this.notifyPackChanged(this.folderUri.fsPath, this.langClient); + } + + private async notifyPackChanged( + packFileDir: string, + ideServer: LanguageClient, + ) { + const packFilePath = join(packFileDir, this.qlpackFileName); + await this.cliServer.logger.log( + `Notifying pack change for ${packFilePath}`, + ); + await ideServer.sendNotification(DidChangeWatchedFilesNotification.type, { + changes: [ + { + type: FileChangeType.Changed, + uri: Uri.file(packFilePath).toString(), + }, + ], + }); } } diff --git a/extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts b/extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts index 8d9390eab96..bb984e866b5 100644 --- a/extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts +++ b/extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts @@ -34,6 +34,7 @@ import type { QueryTreeViewItem } from "../queries-panel/query-tree-view-item"; import { containsPath, pathsEqual } from "../common/files"; import { getQlPackFilePath } from "../common/ql"; import { getQlPackLanguage } from "../common/qlpack-language"; +import type { LanguageClient } from "vscode-languageclient/node"; type QueryLanguagesToDatabaseMap = Record; @@ -56,6 +57,7 @@ export class SkeletonQueryWizard { constructor( private readonly cliServer: CodeQLCliServer, + private readonly langClient: LanguageClient, private readonly progress: ProgressCallback, private readonly app: App, private readonly databaseManager: DatabaseManager, @@ -443,6 +445,7 @@ export class SkeletonQueryWizard { return new QlPackGenerator( this.language, this.cliServer, + this.langClient, this.qlPackStoragePath, this.queryStoragePath, includeFolderNameInQlpackName, From ddf4df4663db2dc29f8de52af5ce6c77cce910ee Mon Sep 17 00:00:00 2001 From: Stephan Brandauer Date: Wed, 27 Nov 2024 13:40:53 +0000 Subject: [PATCH 2/3] remove unnecessary log line --- extensions/ql-vscode/src/local-queries/qlpack-generator.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/ql-vscode/src/local-queries/qlpack-generator.ts b/extensions/ql-vscode/src/local-queries/qlpack-generator.ts index 5c00306c1cf..3cd734cad12 100644 --- a/extensions/ql-vscode/src/local-queries/qlpack-generator.ts +++ b/extensions/ql-vscode/src/local-queries/qlpack-generator.ts @@ -131,9 +131,6 @@ select f, "Hello, world!" ideServer: LanguageClient, ) { const packFilePath = join(packFileDir, this.qlpackFileName); - await this.cliServer.logger.log( - `Notifying pack change for ${packFilePath}`, - ); await ideServer.sendNotification(DidChangeWatchedFilesNotification.type, { changes: [ { From 9c871134e1dc9b28be82e4974688d9bfef79041a Mon Sep 17 00:00:00 2001 From: Stephan Brandauer Date: Wed, 27 Nov 2024 13:54:57 +0000 Subject: [PATCH 3/3] update mocks --- .../local-queries/skeleton-query-wizard.test.ts | 16 ++++++++++++++++ .../local-queries/local-databases.test.ts | 2 ++ .../minimal-workspace/qlpack-generator.test.ts | 10 ++++++++++ 3 files changed, 28 insertions(+) diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/local-queries/skeleton-query-wizard.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/local-queries/skeleton-query-wizard.test.ts index 9076ec9bed4..1c771f2f3ac 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/local-queries/skeleton-query-wizard.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/local-queries/skeleton-query-wizard.test.ts @@ -36,9 +36,11 @@ import { createQueryTreeFolderItem, } from "../../../../src/queries-panel/query-tree-view-item"; import { dump } from "js-yaml"; +import type { LanguageClient } from "vscode-languageclient/node"; describe("SkeletonQueryWizard", () => { let mockCli: CodeQLCliServer; + let mockLanguageClient: LanguageClient; let mockApp: App; let wizard: SkeletonQueryWizard; let mockDatabaseManager: DatabaseManager; @@ -88,6 +90,11 @@ describe("SkeletonQueryWizard", () => { ]), resolveQlpacks: resolveQlpacksMock, }); + + mockLanguageClient = mockedObject({ + sendNotification: jest.fn().mockResolvedValue([]), + }); + mockApp = createMockApp(); mockDatabaseManager = mockedObject({ @@ -142,6 +149,7 @@ describe("SkeletonQueryWizard", () => { wizard = new SkeletonQueryWizard( mockCli, + mockLanguageClient, jest.fn(), mockApp, mockDatabaseManager, @@ -165,6 +173,7 @@ describe("SkeletonQueryWizard", () => { beforeEach(() => { wizard = new SkeletonQueryWizard( mockCli, + mockLanguageClient, jest.fn(), mockApp, mockDatabaseManager, @@ -313,6 +322,7 @@ describe("SkeletonQueryWizard", () => { wizard = new SkeletonQueryWizard( mockCli, + mockLanguageClient, jest.fn(), mockApp, mockDatabaseManagerWithItems, @@ -362,6 +372,7 @@ describe("SkeletonQueryWizard", () => { wizard = new SkeletonQueryWizard( mockCli, + mockLanguageClient, jest.fn(), mockApp, mockDatabaseManagerWithItems, @@ -466,6 +477,7 @@ describe("SkeletonQueryWizard", () => { wizard = new SkeletonQueryWizard( mockCli, + mockLanguageClient, jest.fn(), mockApp, mockDatabaseManager, @@ -687,6 +699,7 @@ describe("SkeletonQueryWizard", () => { wizard = new SkeletonQueryWizard( mockCli, + mockLanguageClient, jest.fn(), mockApp, mockDatabaseManager, @@ -716,6 +729,7 @@ describe("SkeletonQueryWizard", () => { wizard = new SkeletonQueryWizard( mockCli, + mockLanguageClient, jest.fn(), mockApp, mockDatabaseManager, @@ -749,6 +763,7 @@ describe("SkeletonQueryWizard", () => { wizard = new SkeletonQueryWizard( mockCli, + mockLanguageClient, jest.fn(), mockApp, mockDatabaseManager, @@ -792,6 +807,7 @@ describe("SkeletonQueryWizard", () => { wizard = new SkeletonQueryWizard( mockCli, + mockLanguageClient, jest.fn(), mockApp, mockDatabaseManager, diff --git a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-queries/local-databases.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-queries/local-databases.test.ts index b72be548844..df2ff5a1978 100644 --- a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-queries/local-databases.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-queries/local-databases.test.ts @@ -36,6 +36,7 @@ import { } from "../../../factories/databases/databases"; import { findSourceArchive } from "../../../../src/databases/local-databases/database-resolver"; import { LanguageContextStore } from "../../../../src/language-context-store"; +import type { LanguageClient } from "vscode-languageclient/node"; describe("local databases", () => { let databaseManager: DatabaseManager; @@ -109,6 +110,7 @@ describe("local databases", () => { resolveDatabase: resolveDatabaseSpy, packAdd: packAddSpy, }), + mockedObject({}), new LanguageContextStore(mockApp), mockedObject({ log: logSpy, diff --git a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/qlpack-generator.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/qlpack-generator.test.ts index f9a538250b3..961d2b48fdc 100644 --- a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/qlpack-generator.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/qlpack-generator.test.ts @@ -11,6 +11,7 @@ import { mockedObject } from "../utils/mocking.helpers"; import { ensureDir, readFile } from "fs-extra"; import { load } from "js-yaml"; import type { QlPackFile } from "../../../src/packaging/qlpack-file"; +import type { LanguageClient } from "vscode-languageclient/node"; describe("QlPackGenerator", () => { let packFolderPath: string; @@ -23,6 +24,7 @@ describe("QlPackGenerator", () => { typeof CodeQLCliServer.prototype.resolveQlpacks >; let mockCli: CodeQLCliServer; + let mockLangClient: LanguageClient; let dir: DirResult; beforeEach(async () => { @@ -45,9 +47,14 @@ describe("QlPackGenerator", () => { resolveQlpacks: resolveQlpacksSpy, }); + mockLangClient = mockedObject({ + sendNotification: jest.fn().mockResolvedValue([]), + }); + generator = new QlPackGenerator( language as QueryLanguage, mockCli, + mockLangClient, packFolderPath, packFolderPath, ); @@ -131,6 +138,7 @@ describe("QlPackGenerator", () => { generator = new QlPackGenerator( language as QueryLanguage, mockCli, + mockLangClient, packFolderPath, packFolderPath, true, @@ -165,6 +173,7 @@ describe("QlPackGenerator", () => { generator = new QlPackGenerator( language as QueryLanguage, mockCli, + mockLangClient, packFolderPath, packFolderPath, true, @@ -200,6 +209,7 @@ describe("QlPackGenerator", () => { generator = new QlPackGenerator( language as QueryLanguage, mockCli, + mockLangClient, packFolderPath, packFolderPath, true,