test.govlab/quality/core/emitters/tool.emitter.test.ts
test.govlab/quality/core/emitters/tool.emitter.test.ts is a file in Codebase Testing. 190 lines of code and 0 definitions.
import type { EmitDescriptor, EmitFile, EmitTokens } from "@govlab/quality/types/emitter.types.ts";
import { beforeAll, describe, expect, it } from "vitest";
import { emitConfigs, emitToolConfig } from "@govlab/quality/core/emitters/tool.emitter.ts";
import { loadDescriptors, loadEmitTokens, loadFormatters } from "@govlab/quality/core/loaders/emitter.loader.ts";
import type { ToolResolution } from "@govlab/quality/types/plan.types.ts";
const TOOL = "golangci-lint";
const CONCEPT = "cyclomatic-complexity";
const GOLANGCI: EmitDescriptor = {
configTarget: ".golangci.yml",
format: "yaml",
idiom: "central",
ignoreContainer: "linters.disable",
preamble: { version: "2" },
selectContainer: "linters.enable",
settingsContainer: "linters.settings",
tool: TOOL,
};
const TOKENS: EmitTokens = Object.fromEntries([
[
TOOL,
Object.fromEntries<EmitTokens[string][string]>([
[CONCEPT, { knob: "gocyclo.min-complexity", ruleIds: ["cyclop", "gocyclo"] }],
["format-string", { knob: "enabled+severity", ruleIds: ["goprintffuncname"] }],
]),
],
]);
const V2 = 2;
const V5 = 5;
const V7 = 7;
const V10 = 10;
const V12 = 12;
const V80 = 80;
const res = function res(over: Partial<ToolResolution>): ToolResolution {
return { disabledSurfaces: [], enabledIntents: [], knobs: {}, ...over };
};
const knob = function knob(id: string, value: number): Record<string, unknown> {
return Object.fromEntries([[id, value]]);
};
const oneTool = function oneTool(tool: string, resolution: ToolResolution): Record<string, ToolResolution> {
return Object.fromEntries([[tool, resolution]]);
};
beforeAll(async () => {
await loadFormatters();
});
describe("emitToolConfig — central idiom (golangci)", () => {
it("places the resolved threshold at its native path and enables the concept's linters", () => {
const file = emitToolConfig({
descriptor: GOLANGCI,
resolution: res({ knobs: knob(CONCEPT, V10) }),
surfaceMap: new Map(),
tokens: TOKENS,
tool: TOOL,
});
expect(file.path).toBe(".golangci.yml");
expect(file.content).toBe(
[
'version: "2"',
"linters:",
" enable:",
" - cyclop",
" - gocyclo",
" settings:",
" gocyclo:",
" min-complexity: 10",
"",
].join("\n"),
);
});
it("enables the native rule ids for an enabled rule-intent concept", () => {
const file = emitToolConfig({
descriptor: GOLANGCI,
resolution: res({ enabledIntents: ["format-string"] }),
surfaceMap: new Map(),
tokens: TOKENS,
tool: TOOL,
});
expect(file.content).toContain("- goprintffuncname");
});
it("emits the tool's rule ids for an ownership-disabled surface to the ignore container", () => {
const file = emitToolConfig({
descriptor: GOLANGCI,
resolution: res({ disabledSurfaces: ["layout"] }),
surfaceMap: new Map([["layout", ["format-string"]]]),
tokens: TOKENS,
tool: TOOL,
});
expect(file.content).toBe(['version: "2"', "linters:", " disable:", " - goprintffuncname", ""].join("\n"));
});
});
describe("emitConfigs — real bundled descriptors + tokens", () => {
const descriptors = loadDescriptors();
const tokens = loadEmitTokens();
const emit = async (perTool: Record<string, ToolResolution>): Promise<EmitFile[]> =>
emitConfigs(perTool, { descriptorFile: descriptors, tokens });
const emitted = async (perTool: Record<string, ToolResolution>, path: string): Promise<EmitFile | undefined> =>
(await emit(perTool)).find((file) => file.path === path);
it("golangci (yaml central): resolved threshold + owning linter", async () => {
const resolution = res({ knobs: knob(CONCEPT, V12) });
const file = await emitted(oneTool(TOOL, resolution), ".golangci.yml");
expect(file?.content).toContain("min-complexity: 12");
expect(file?.content).toContain("- gocyclo");
});
it("ruff (toml central, self-pathed knob): setting + select code", async () => {
const file = await emitted({ ruff: res({ knobs: knob(CONCEPT, V10) }) }, "ruff.toml");
expect(file?.content).toContain("[lint.mccabe]");
expect(file?.content).toContain("max-complexity = 10");
expect(file?.content).toContain('"C901"');
});
it("clippy (toml central, top-level knob, no select)", async () => {
const file = await emitted({ clippy: res({ knobs: knob("long-function", V80) }) }, "clippy.toml");
expect(file?.content).toContain("too-many-lines-threshold = 80");
});
it("rubocop (yaml per-rule): a cop block with Enabled + the resolved knob", async () => {
const file = await emitted({ rubocop: res({ knobs: knob(CONCEPT, V7) }) }, ".rubocop.yml");
expect(file?.content).toContain("Metrics/CyclomaticComplexity:");
expect(file?.content).toContain("Enabled: true");
expect(file?.content).toContain("Max: 7");
});
it("perlcritic (ini per-rule, no enable key): a policy section with the resolved knob", async () => {
const file = await emitted({ perlcritic: res({ knobs: knob("too-many-params", V5) }) }, ".perlcriticrc");
expect(file?.content).toContain("[Subroutines::ProhibitManyArgs]");
expect(file?.content).toContain("max_arguments=5");
expect(file?.content).not.toContain("Enabled");
});
it("pylint (ini central, per-knob section): the knob under its real section", async () => {
const file = await emitted({ pylint: res({ knobs: knob("too-many-params", 1) }) }, ".pylintrc");
expect(file?.content).toContain("[DESIGN]");
expect(file?.content).toContain("max-args=1");
});
it("yamllint (rules-container): the knob attaches to the owner rule under the container", async () => {
const file = await emitted({ yamllint: res({ knobs: { indentation: 4 } }) }, ".yamllint");
expect(file?.content).toBe(["rules:", " indentation:", " spaces: 4", ""].join("\n"));
});
it("checkstyle (xml ruleset): Checker>TreeWalker>module + property, with the required DOCTYPE", async () => {
const file = await emitted({ checkstyle: res({ knobs: knob(CONCEPT, 1) }) }, "checkstyle.xml");
expect(file?.content).toContain("<!DOCTYPE module PUBLIC");
expect(file?.content).toContain('<module name="Checker">');
expect(file?.content).toContain('<module name="TreeWalker">');
expect(file?.content).toContain("CyclomaticComplexityCheck");
expect(file?.content).toContain('<property name="max" value="1"/>');
});
it("pmd (xml ruleset): category-qualified ref + wrapped properties + description", async () => {
const file = await emitted({ pmd: res({ knobs: knob(CONCEPT, 1) }) }, "pmd-ruleset.xml");
expect(file?.content).toContain('<ruleset name="govlab"');
expect(file?.content).toContain("<description>GovLab SSOT-emitted ruleset</description>");
expect(file?.content).toContain('<rule ref="category/java/design.xml/CyclomaticComplexity">');
expect(file?.content).toContain('<property name="methodReportLevel" value="1"/>');
});
it("phpmd (xml ruleset): codesize ref + phpmd xmlns", async () => {
const file = await emitted({ phpmd: res({ knobs: knob(CONCEPT, 1) }) }, "phpmd.xml");
expect(file?.content).toContain('xmlns="http://pmd.sf.net/ruleset/1.0.0"');
expect(file?.content).toContain('<rule ref="rulesets/codesize.xml/CyclomaticComplexity">');
expect(file?.content).toContain('<property name="reportLevel" value="1"/>');
});
it("prettier (js-record central): the resolved indentation lands on tabWidth", async () => {
const file = await emitted({ prettier: res({ knobs: { indentation: 4 } }) }, ".prettierrc");
expect(file?.content).toContain('"tabWidth": 4');
});
it("revive (toml rules-container, positional args): value wrapped as an arguments array", async () => {
const file = await emitted({ revive: res({ knobs: knob(CONCEPT, V2) }) }, "revive.toml");
expect(file?.content).toContain("[rule.cognitive-complexity]");
expect(file?.content).toContain("arguments = [2]");
});
it("swiftlint (yaml per-rule, compound knob split): threshold under the warning key", async () => {
const file = await emitted({ swiftlint: res({ knobs: knob(CONCEPT, V2) }) }, ".swiftlint.yml");
expect(file?.content).toContain("cyclomatic_complexity:");
expect(file?.content).toContain("warning: 2");
expect(file?.content).not.toContain("warning|error");
});
it("sqlfluff (ini central, colon knob-section): tab_space_size under [sqlfluff:indentation]", async () => {
const file = await emitted({ sqlfluff: res({ knobs: { indentation: 8 } }) }, ".sqlfluff");
expect(file?.content).toContain("[sqlfluff:indentation]");
expect(file?.content).toContain("tab_space_size=8");
});
it("skips a tool with no descriptor", async () => {
const files = await emit(oneTool("some-unmapped-tool", res({})));
expect(files.find((file) => file.path.includes("some-unmapped"))).toBeUndefined();
});
it("reports an unmapped tool through the onSkip callback instead of silently dropping it", async () => {
const skipped: string[] = [];
await emitConfigs(oneTool("some-unmapped-tool", res({})), {
descriptorFile: descriptors,
onSkip: (tool) => {
skipped.push(tool);
},
tokens,
});
expect(skipped).toEqual(["some-unmapped-tool"]);
});
});