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