diff --git a/extensions/ql-vscode/src/databases.ts b/extensions/ql-vscode/src/databases.ts index e6651b8e0..1624d43cd 100644 --- a/extensions/ql-vscode/src/databases.ts +++ b/extensions/ql-vscode/src/databases.ts @@ -26,6 +26,7 @@ import { QueryRunner } from "./queryRunner"; import { pathsEqual } from "./pure/files"; import { redactableError } from "./pure/errors"; import { isCodespacesTemplate } from "./config"; +import { QlPackGenerator, QueryLanguage } from "./qlpack-generator"; /** * databases.ts @@ -655,9 +656,26 @@ export class DatabaseManager extends DisposableObject { return; } - await showBinaryChoiceDialog( - `We've noticed you don't have a QL pack downloaded to analyze this database. Can we set up a ${databaseItem.language} query pack for you`, + const answer = await showBinaryChoiceDialog( + `We've noticed you don't have a QL pack downloaded to analyze this database. Can we set up a query pack for you?`, ); + + if (!answer) { + return; + } + + try { + const qlPackGenerator = new QlPackGenerator( + folderName, + databaseItem.language as QueryLanguage, + this.cli, + ); + await qlPackGenerator.generate(); + } catch (e: unknown) { + void this.logger.log( + `Could not create skeleton QL pack: ${getErrorMessage(e)}`, + ); + } } private async reregisterDatabases( diff --git a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases.test.ts index 068be7385..f3f13d451 100644 --- a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases.test.ts @@ -23,6 +23,7 @@ import { testDisposeHandler } from "../test-dispose-handler"; import { QueryRunner } from "../../../src/queryRunner"; import * as helpers from "../../../src/helpers"; import { Setting } from "../../../src/config"; +import { QlPackGenerator } from "../../../src/qlpack-generator"; describe("databases", () => { const MOCK_DB_OPTIONS: FullDatabaseOptions = { @@ -37,6 +38,7 @@ describe("databases", () => { let registerSpy: jest.Mock, []>; let deregisterSpy: jest.Mock, []>; let resolveDatabaseSpy: jest.Mock, []>; + let packAddSpy: jest.Mock; let logSpy: jest.Mock; let showBinaryChoiceDialogSpy: jest.SpiedFunction< @@ -52,6 +54,7 @@ describe("databases", () => { registerSpy = jest.fn(() => Promise.resolve(undefined)); deregisterSpy = jest.fn(() => Promise.resolve(undefined)); resolveDatabaseSpy = jest.fn(() => Promise.resolve({} as DbInfo)); + packAddSpy = jest.fn(); logSpy = jest.fn(() => { /* */ }); @@ -79,6 +82,7 @@ describe("databases", () => { } as unknown as QueryRunner, { resolveDatabase: resolveDatabaseSpy, + packAdd: packAddSpy, } as unknown as CodeQLCliServer, { log: logSpy, @@ -589,20 +593,66 @@ describe("databases", () => { describe("createSkeletonPacks", () => { let mockDbItem: DatabaseItemImpl; + let packfolderName: string; + let qlPackYamlFilePath: string; + let exampleQlFilePath: string; + let language: string; + + beforeEach(() => { + language = "ruby"; + + const options: FullDatabaseOptions = { + dateAdded: 123, + ignoreSourceArchive: false, + language, + }; + mockDbItem = createMockDB(options); + + packfolderName = `codeql-custom-queries-${mockDbItem.language}`; + qlPackYamlFilePath = join(packfolderName, "qlpack.yml"); + exampleQlFilePath = join(packfolderName, "example.ql"); + }); + + afterEach(async () => { + try { + fs.rmdirSync(packfolderName, { recursive: true }); + } catch (e) { + // ignore + } + }); describe("when the language is set", () => { it("should offer the user to set up a skeleton QL pack", async () => { - const options: FullDatabaseOptions = { - dateAdded: 123, - ignoreSourceArchive: false, - language: "ruby", - }; - mockDbItem = createMockDB(options); - await (databaseManager as any).createSkeletonPacks(mockDbItem); expect(showBinaryChoiceDialogSpy).toBeCalledTimes(1); }); + + it("should return early if the user refuses help", async () => { + showBinaryChoiceDialogSpy = jest + .spyOn(helpers, "showBinaryChoiceDialog") + .mockResolvedValue(false); + + const generateSpy = jest.spyOn(QlPackGenerator.prototype, "generate"); + + await (databaseManager as any).createSkeletonPacks(mockDbItem); + + expect(generateSpy).not.toBeCalled(); + }); + + it("should create the skeleton QL pack for the user", async () => { + expect(fs.existsSync(packfolderName)).toBe(false); + expect(fs.existsSync(qlPackYamlFilePath)).toBe(false); + expect(fs.existsSync(exampleQlFilePath)).toBe(false); + + await (databaseManager as any).createSkeletonPacks(mockDbItem); + + expect(fs.existsSync(packfolderName)).toBe(true); + expect(fs.existsSync(qlPackYamlFilePath)).toBe(true); + expect(fs.existsSync(exampleQlFilePath)).toBe(true); + + expect(packAddSpy).toHaveBeenCalledWith(packfolderName, language); + }); }); describe("when the language is not set", () => {