core/producers/tool.clippy.producer.ts
core/producers/tool.clippy.producer.ts is a file in GovLab Quality. 179 lines of code and 47 definitions.
import type { CatalogProduct, CatalogRule, KnobEntry } from "#types/catalog.types";
import { commandOutput, commandVersion } from "#core/adapters/invocation.adapter";
import { intAt, trailingIdentifier } from "#core/parsers/knob.parser";
import { INTEGER_TYPE } from "#configuration/constants/knob.constants";
import { byRuleId } from "#core/comparators/rule.comparator";
import { defineProducer } from "#core/registries/producer.registry";
import { isThreshold } from "#core/predicates/knob.predicate";
import { remoteText } from "#core/adapters/remote.adapter";
import { tallyBy } from "#core/aggregators/catalog.aggregator";
const CLIPPY = "clippy";
const RUSTC = "rustc";
const DRIVER = "clippy-driver";
const CLIPPY_PREFIX = "clippy::";
const CLIPPY_DOCS = "https://rust-lang.github.io/rust-clippy/master/#";
const CONF_SOURCE = "https://raw.githubusercontent.com/rust-lang/rust-clippy/master/clippy_config/src/conf.rs";
const LINTS_OPEN = "#[lints(";
const LINTS_CLOSE = ")]";
const FIELD_WINDOW = 200;
const MIN_COLS = 2;
interface CheckLine {
level: string;
meaning: string | null;
name: string;
}
const skipSpaces = function skipSpaces(text: string, start: number): number {
let i = start;
while (i < text.length && text[i] === " ") {
i += 1;
}
return i;
};
const columnEnd = function columnEnd(text: string, start: number): number {
let i = start;
while (i < text.length && !(text[i] === " " && text[i + 1] === " ")) {
i += 1;
}
return i;
};
const splitColumns = function splitColumns(text: string): string[] {
const cols: string[] = [];
let i = skipSpaces(text, 0);
while (i < text.length) {
const end = columnEnd(text, i);
cols.push(text.slice(i, end).trimEnd());
i = skipSpaces(text, end);
}
return cols;
};
const isTableRow = function isTableRow(text: string): boolean {
return text.length > 0 && !text.startsWith("name") && !text.startsWith("----");
};
const parseCheckLine = function parseCheckLine(text: string): CheckLine | null {
const cols = isTableRow(text) ? splitColumns(text) : [];
if (cols.length < MIN_COLS) {
return null;
}
const meaning = cols.slice(MIN_COLS).join(" ");
return { level: cols[1] ?? "", meaning: meaning === "" ? null : meaning, name: cols[0] ?? "" };
};
const toUnderscore = function toUnderscore(name: string): string {
return name.split("-").join("_");
};
const sectionLines = function sectionLines(lines: readonly string[], start: string, end: string): string[] {
const from = lines.findIndex((line) => line.trim() === start);
const to = end === "" ? lines.length : lines.findIndex((line) => line.trim() === end);
return to === -1 ? [] : lines.slice(from + 1, to).map((line) => line.trim());
};
const groupMembers = function groupMembers(group: string, rest: readonly string[]): [string, string][] {
if (rest.length === 0 || group === "clippy::all") {
return [];
}
const short = group.startsWith(CLIPPY_PREFIX) ? group.slice(CLIPPY_PREFIX.length) : group;
return rest
.join(" ")
.split(",")
.map((member) => toUnderscore(member.trim()))
.filter((id) => id.length > 0)
.map((id): [string, string] => [id, short]);
};
const groupOf = function groupOf(lines: readonly string[]): Map<string, string> {
return new Map(
lines
.filter(isTableRow)
.map(splitColumns)
.flatMap(([group = "", ...rest]) => groupMembers(group, rest)),
);
};
const build = function build(
checks: readonly CheckLine[],
tool: string,
context: { groups: Map<string, string>; version: string },
): CatalogRule[] {
return checks
.map((check): CatalogRule => {
const id = toUnderscore(check.name);
const short = id.startsWith(CLIPPY_PREFIX) ? id.slice(CLIPPY_PREFIX.length) : id;
return {
canonical: null,
category: tool === CLIPPY ? (context.groups.get(id) ?? CLIPPY) : RUSTC,
defaultLevel: check.level,
description: check.meaning,
ecosystem: "rust",
name: short,
ruleId: id,
tool,
toolVersion: context.version,
url: tool === CLIPPY ? `${CLIPPY_DOCS}/${short}` : null,
};
})
.toSorted(byRuleId);
};
const productOf = function productOf(tool: string, rules: CatalogRule[], version: string): CatalogProduct {
return {
rules,
source: tool,
summary: {
byCategory: tallyBy(rules, "category"),
byLevel: tallyBy(rules, "defaultLevel"),
tool,
toolVersion: version,
total: rules.length,
},
};
};
const produce = async function produce(): Promise<CatalogProduct[]> {
const version = commandVersion(DRIVER, ["--version"]);
const lines = commandOutput(DRIVER, ["-Whelp"])
.split("\n")
.map((line) => (line.endsWith("\r") ? line.slice(0, -1) : line));
const checksOf = (start: string, end: string): CheckLine[] =>
sectionLines(lines, start, end).flatMap((line) => parseCheckLine(line) ?? []);
const context = { groups: groupOf(sectionLines(lines, "Lint groups loaded by this crate:", "")), version };
const clippy = build(
checksOf("Lint checks loaded by this crate:", "Lint groups loaded by this crate:"),
CLIPPY,
context,
);
const rustc = build(checksOf("Lint checks provided by rustc:", "Lint groups provided by rustc:"), RUSTC, context);
return [productOf(CLIPPY, clippy, version), productOf(RUSTC, rustc, version)];
};
const lintEntries = function lintEntries(text: string, open: number, close: number): KnobEntry[] {
const colon = text.indexOf(":", close);
const eq = text.indexOf("=", colon);
const def = colon !== -1 && eq !== -1 && eq - close <= FIELD_WINDOW ? intAt(text, eq + 1) : null;
if (def === null) {
return [];
}
const field = trailingIdentifier(text.slice(close + LINTS_CLOSE.length, colon));
return text
.slice(open + LINTS_OPEN.length, close)
.split(",")
.map((lint) => lint.trim())
.filter(Boolean)
.map((lint): KnobEntry => [
CLIPPY,
`${CLIPPY_PREFIX}${lint}`,
{
default: def,
knob: field === "" ? "threshold" : field,
threshold: isThreshold(field),
type: INTEGER_TYPE,
},
]);
};
const knobs = async function knobs(): Promise<KnobEntry[]> {
const text = (await remoteText(CONF_SOURCE)) ?? "";
const entries: KnobEntry[] = [];
let open = text.indexOf(LINTS_OPEN);
while (open >= 0) {
const close = text.indexOf(LINTS_CLOSE, open);
if (close === -1) {
return entries;
}
entries.push(...lintEntries(text, open, close));
open = text.indexOf(LINTS_OPEN, close + LINTS_CLOSE.length);
}
return entries;
};
defineProducer({ knobs, name: CLIPPY, produce, refresh: "manual" });