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.
This commit is contained in:
Elena Tanasoiu
2023-02-07 22:17:08 +00:00
parent 5a9d12ea60
commit 6cda6534d1
2 changed files with 138 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
import { mkdir, writeFile } from "fs-extra";
import { dump } from "js-yaml";
import { join } from "path";
import { CodeQLCliServer } from "./cli";
export type QueryLanguage =
| "csharp"
| "cpp"
| "go"
| "java"
| "javascript"
| "python"
| "ruby"
| "swift";
export class QlPackGenerator {
private readonly qlpackName: string;
private readonly qlpackVersion: string;
private readonly header: string;
private readonly qlpackFileName: string;
constructor(
private readonly folderName: string,
private readonly queryLanguage: QueryLanguage,
private readonly cliServer: CodeQLCliServer,
) {
this.qlpackName = `getting-started/codeql-extra-queries-${this.queryLanguage}`;
this.qlpackVersion = "1.0.0";
this.header = "# This is an automatically generated file.\n\n";
this.qlpackFileName = "qlpack.yml";
}
public async generate() {
await mkdir(this.folderName);
// create qlpack.yml
await this.createQlPackYaml();
// create example.ql
await this.createExampleQlFile();
// create codeql-pack.lock.yml
await this.createCodeqlPackLockYaml();
}
private async createQlPackYaml() {
const qlPackFile = join(this.folderName, this.qlpackFileName);
const qlPackYml = {
name: this.qlpackName,
version: this.qlpackVersion,
dependencies: {
[`codeql/${this.queryLanguage}-all`]: "*",
},
};
await writeFile(qlPackFile, this.header + dump(qlPackYml), "utf8");
}
private async createExampleQlFile() {
const exampleQlFile = join(this.folderName, "example.ql");
const exampleQl = `
/**
* @name Empty block
* @kind problem
* @problem.severity warning
* @id ${this.queryLanguage}/example/empty-block
*/
import ${this.queryLanguage}
from BlockStmt b
where b.getNumStmt() = 0
select b, "This is an empty block."
`.trim();
await writeFile(exampleQlFile, exampleQl, "utf8");
}
private async createCodeqlPackLockYaml() {
await this.cliServer.packAdd(this.folderName, this.queryLanguage);
}
}

View File

@@ -0,0 +1,53 @@
import { join } from "path";
import { existsSync, rmdirSync } from "fs";
import { QlPackGenerator, QueryLanguage } from "../../../src/qlpack-generator";
import { CodeQLCliServer } from "../../../src/cli";
describe("QlPackGenerator", () => {
let packfolderName: string;
let qlPackYamlFilePath: string;
let exampleQlFilePath: string;
let language: string;
let generator: QlPackGenerator;
let packAddSpy: jest.SpyInstance;
beforeEach(async () => {
language = "ruby";
packfolderName = `test-ql-pack-${language}`;
qlPackYamlFilePath = join(packfolderName, "qlpack.yml");
exampleQlFilePath = join(packfolderName, "example.ql");
packAddSpy = jest.fn();
const mockCli = {
packAdd: packAddSpy,
} as unknown as CodeQLCliServer;
generator = new QlPackGenerator(
packfolderName,
language as QueryLanguage,
mockCli,
);
});
afterEach(async () => {
try {
rmdirSync(packfolderName, { recursive: true });
} catch (e) {
// ignore
}
});
it("should generate a QL pack", async () => {
expect(existsSync(packfolderName)).toBe(false);
expect(existsSync(qlPackYamlFilePath)).toBe(false);
expect(existsSync(exampleQlFilePath)).toBe(false);
await generator.generate();
expect(existsSync(packfolderName)).toBe(true);
expect(existsSync(qlPackYamlFilePath)).toBe(true);
expect(existsSync(exampleQlFilePath)).toBe(true);
expect(packAddSpy).toHaveBeenCalledWith(packfolderName, language);
});
});