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:
85
extensions/ql-vscode/src/qlpack-generator.ts
Normal file
85
extensions/ql-vscode/src/qlpack-generator.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user