Skip to content

Commit a8f36ee

Browse files
committed
Generate a QL pack when you add a new database, if one is missing
1 parent 6cda653 commit a8f36ee

2 files changed

Lines changed: 77 additions & 9 deletions

File tree

extensions/ql-vscode/src/databases.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { QueryRunner } from "./queryRunner";
2626
import { pathsEqual } from "./pure/files";
2727
import { redactableError } from "./pure/errors";
2828
import { isCodespacesTemplate } from "./config";
29+
import { QlPackGenerator, QueryLanguage } from "./qlpack-generator";
2930

3031
/**
3132
* databases.ts
@@ -655,9 +656,26 @@ export class DatabaseManager extends DisposableObject {
655656
return;
656657
}
657658

658-
await showBinaryChoiceDialog(
659-
`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`,
659+
const answer = await showBinaryChoiceDialog(
660+
`We've noticed you don't have a QL pack downloaded to analyze this database. Can we set up a query pack for you?`,
660661
);
662+
663+
if (!answer) {
664+
return;
665+
}
666+
667+
try {
668+
const qlPackGenerator = new QlPackGenerator(
669+
folderName,
670+
databaseItem.language as QueryLanguage,
671+
this.cli,
672+
);
673+
await qlPackGenerator.generate();
674+
} catch (e: unknown) {
675+
void this.logger.log(
676+
`Could not create skeleton QL pack: ${getErrorMessage(e)}`,
677+
);
678+
}
661679
}
662680

663681
private async reregisterDatabases(

extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases.test.ts

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { testDisposeHandler } from "../test-dispose-handler";
2323
import { QueryRunner } from "../../../src/queryRunner";
2424
import * as helpers from "../../../src/helpers";
2525
import { Setting } from "../../../src/config";
26+
import { QlPackGenerator } from "../../../src/qlpack-generator";
2627

2728
describe("databases", () => {
2829
const MOCK_DB_OPTIONS: FullDatabaseOptions = {
@@ -37,6 +38,7 @@ describe("databases", () => {
3738
let registerSpy: jest.Mock<Promise<void>, []>;
3839
let deregisterSpy: jest.Mock<Promise<void>, []>;
3940
let resolveDatabaseSpy: jest.Mock<Promise<DbInfo>, []>;
41+
let packAddSpy: jest.Mock<any, []>;
4042
let logSpy: jest.Mock<any, []>;
4143

4244
let showBinaryChoiceDialogSpy: jest.SpiedFunction<
@@ -52,6 +54,7 @@ describe("databases", () => {
5254
registerSpy = jest.fn(() => Promise.resolve(undefined));
5355
deregisterSpy = jest.fn(() => Promise.resolve(undefined));
5456
resolveDatabaseSpy = jest.fn(() => Promise.resolve({} as DbInfo));
57+
packAddSpy = jest.fn();
5558
logSpy = jest.fn(() => {
5659
/* */
5760
});
@@ -79,6 +82,7 @@ describe("databases", () => {
7982
} as unknown as QueryRunner,
8083
{
8184
resolveDatabase: resolveDatabaseSpy,
85+
packAdd: packAddSpy,
8286
} as unknown as CodeQLCliServer,
8387
{
8488
log: logSpy,
@@ -589,20 +593,66 @@ describe("databases", () => {
589593

590594
describe("createSkeletonPacks", () => {
591595
let mockDbItem: DatabaseItemImpl;
596+
let packfolderName: string;
597+
let qlPackYamlFilePath: string;
598+
let exampleQlFilePath: string;
599+
let language: string;
600+
601+
beforeEach(() => {
602+
language = "ruby";
603+
604+
const options: FullDatabaseOptions = {
605+
dateAdded: 123,
606+
ignoreSourceArchive: false,
607+
language,
608+
};
609+
mockDbItem = createMockDB(options);
610+
611+
packfolderName = `codeql-custom-queries-${mockDbItem.language}`;
612+
qlPackYamlFilePath = join(packfolderName, "qlpack.yml");
613+
exampleQlFilePath = join(packfolderName, "example.ql");
614+
});
615+
616+
afterEach(async () => {
617+
try {
618+
fs.rmdirSync(packfolderName, { recursive: true });
619+
} catch (e) {
620+
// ignore
621+
}
622+
});
592623

593624
describe("when the language is set", () => {
594625
it("should offer the user to set up a skeleton QL pack", async () => {
595-
const options: FullDatabaseOptions = {
596-
dateAdded: 123,
597-
ignoreSourceArchive: false,
598-
language: "ruby",
599-
};
600-
mockDbItem = createMockDB(options);
601-
602626
await (databaseManager as any).createSkeletonPacks(mockDbItem);
603627

604628
expect(showBinaryChoiceDialogSpy).toBeCalledTimes(1);
605629
});
630+
631+
it("should return early if the user refuses help", async () => {
632+
showBinaryChoiceDialogSpy = jest
633+
.spyOn(helpers, "showBinaryChoiceDialog")
634+
.mockResolvedValue(false);
635+
636+
const generateSpy = jest.spyOn(QlPackGenerator.prototype, "generate");
637+
638+
await (databaseManager as any).createSkeletonPacks(mockDbItem);
639+
640+
expect(generateSpy).not.toBeCalled();
641+
});
642+
643+
it("should create the skeleton QL pack for the user", async () => {
644+
expect(fs.existsSync(packfolderName)).toBe(false);
645+
expect(fs.existsSync(qlPackYamlFilePath)).toBe(false);
646+
expect(fs.existsSync(exampleQlFilePath)).toBe(false);
647+
648+
await (databaseManager as any).createSkeletonPacks(mockDbItem);
649+
650+
expect(fs.existsSync(packfolderName)).toBe(true);
651+
expect(fs.existsSync(qlPackYamlFilePath)).toBe(true);
652+
expect(fs.existsSync(exampleQlFilePath)).toBe(true);
653+
654+
expect(packAddSpy).toHaveBeenCalledWith(packfolderName, language);
655+
});
606656
});
607657

608658
describe("when the language is not set", () => {

0 commit comments

Comments
 (0)