Skip to content

Commit 6cda653

Browse files
committed
Introduce a QlPackGenerator class
This will receive a folder name and language. It will generate: - a `codeql-pack.yml` file - an `example.ql` file - a `codeql-pack.lock.yml` file It will also install dependencies listed in `codeql-pack.lock.yml` file. We were initially planning to call the `packInstall` command once we generate `codeql-pack.yml` in order to install dependencies. However, the `packAdd` command does this for us, as well as generating a lock file. Rather than trying to craft the lock file by hand, we're opting to use the cli command. NB: We're introducing a new `QueryLanguage` type which is identical to the `VariantAnalysisQueryLanguage`. In a subsequent PR we'll unify these two types.
1 parent 5a9d12e commit 6cda653

2 files changed

Lines changed: 138 additions & 0 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { mkdir, writeFile } from "fs-extra";
2+
import { dump } from "js-yaml";
3+
import { join } from "path";
4+
import { CodeQLCliServer } from "./cli";
5+
6+
export type QueryLanguage =
7+
| "csharp"
8+
| "cpp"
9+
| "go"
10+
| "java"
11+
| "javascript"
12+
| "python"
13+
| "ruby"
14+
| "swift";
15+
16+
export class QlPackGenerator {
17+
private readonly qlpackName: string;
18+
private readonly qlpackVersion: string;
19+
private readonly header: string;
20+
private readonly qlpackFileName: string;
21+
22+
constructor(
23+
private readonly folderName: string,
24+
private readonly queryLanguage: QueryLanguage,
25+
private readonly cliServer: CodeQLCliServer,
26+
) {
27+
this.qlpackName = `getting-started/codeql-extra-queries-${this.queryLanguage}`;
28+
this.qlpackVersion = "1.0.0";
29+
this.header = "# This is an automatically generated file.\n\n";
30+
31+
this.qlpackFileName = "qlpack.yml";
32+
}
33+
34+
public async generate() {
35+
await mkdir(this.folderName);
36+
37+
// create qlpack.yml
38+
await this.createQlPackYaml();
39+
40+
// create example.ql
41+
await this.createExampleQlFile();
42+
43+
// create codeql-pack.lock.yml
44+
await this.createCodeqlPackLockYaml();
45+
}
46+
47+
private async createQlPackYaml() {
48+
const qlPackFile = join(this.folderName, this.qlpackFileName);
49+
50+
const qlPackYml = {
51+
name: this.qlpackName,
52+
version: this.qlpackVersion,
53+
dependencies: {
54+
[`codeql/${this.queryLanguage}-all`]: "*",
55+
},
56+
};
57+
58+
await writeFile(qlPackFile, this.header + dump(qlPackYml), "utf8");
59+
}
60+
61+
private async createExampleQlFile() {
62+
const exampleQlFile = join(this.folderName, "example.ql");
63+
64+
const exampleQl = `
65+
/**
66+
* @name Empty block
67+
* @kind problem
68+
* @problem.severity warning
69+
* @id ${this.queryLanguage}/example/empty-block
70+
*/
71+
72+
import ${this.queryLanguage}
73+
74+
from BlockStmt b
75+
where b.getNumStmt() = 0
76+
select b, "This is an empty block."
77+
`.trim();
78+
79+
await writeFile(exampleQlFile, exampleQl, "utf8");
80+
}
81+
82+
private async createCodeqlPackLockYaml() {
83+
await this.cliServer.packAdd(this.folderName, this.queryLanguage);
84+
}
85+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { join } from "path";
2+
import { existsSync, rmdirSync } from "fs";
3+
import { QlPackGenerator, QueryLanguage } from "../../../src/qlpack-generator";
4+
import { CodeQLCliServer } from "../../../src/cli";
5+
6+
describe("QlPackGenerator", () => {
7+
let packfolderName: string;
8+
let qlPackYamlFilePath: string;
9+
let exampleQlFilePath: string;
10+
let language: string;
11+
let generator: QlPackGenerator;
12+
let packAddSpy: jest.SpyInstance;
13+
14+
beforeEach(async () => {
15+
language = "ruby";
16+
packfolderName = `test-ql-pack-${language}`;
17+
qlPackYamlFilePath = join(packfolderName, "qlpack.yml");
18+
exampleQlFilePath = join(packfolderName, "example.ql");
19+
20+
packAddSpy = jest.fn();
21+
const mockCli = {
22+
packAdd: packAddSpy,
23+
} as unknown as CodeQLCliServer;
24+
25+
generator = new QlPackGenerator(
26+
packfolderName,
27+
language as QueryLanguage,
28+
mockCli,
29+
);
30+
});
31+
32+
afterEach(async () => {
33+
try {
34+
rmdirSync(packfolderName, { recursive: true });
35+
} catch (e) {
36+
// ignore
37+
}
38+
});
39+
40+
it("should generate a QL pack", async () => {
41+
expect(existsSync(packfolderName)).toBe(false);
42+
expect(existsSync(qlPackYamlFilePath)).toBe(false);
43+
expect(existsSync(exampleQlFilePath)).toBe(false);
44+
45+
await generator.generate();
46+
47+
expect(existsSync(packfolderName)).toBe(true);
48+
expect(existsSync(qlPackYamlFilePath)).toBe(true);
49+
expect(existsSync(exampleQlFilePath)).toBe(true);
50+
51+
expect(packAddSpy).toHaveBeenCalledWith(packfolderName, language);
52+
});
53+
});

0 commit comments

Comments
 (0)