core/adapters/tool.clippy.adapter.ts

core/adapters/tool.clippy.adapter.ts is a file in GovLab Quality. 62 lines of code and 17 definitions.

import type { AdvisoryContext, RunnerContext, ScanSpec, ToolCall } from "#types/tool.types";
import { CARGO_MANIFEST, SUCCESS_STATUSES } from "#configuration/constants/tool.constants";
import { NO_STDERR_DETAIL, installHint, toolErrorBecause } from "#configuration/strings/tool.strings";
import { enabledToolRules, nativeToolConfig } from "#core/resolvers/tool.resolver";
import { failedOnStatus, failedWhenEmpty } from "#core/classifiers/invocation.classifier";
import { fixCycle, passTool, residualOf } from "#core/adapters/tool.adapter";
import type { RunResult } from "#types/finding.types";
import { defineTool } from "#core/registries/tool.registry";
import { gatingAdvisory } from "#core/factories/tool.factory";
import { parseClippyOutput } from "#core/parsers/tool.clippy.parser";
import path from "node:path";
import { projectDirWithMarker } from "#core/resolvers/scope.resolver";
import { tailLines } from "#core/selectors/failure.selector";
import { toolFailure } from "#core/factories/finding.factory";
import { writeToolFile } from "#core/persistence/tool.persistence";

const TOOL = "clippy";
const BIN = "cargo";
const LANGUAGE = "rust";
const CONFIG_FILE = "clippy.toml";
const CAUSE = "a malformed config or cargo error";

export const clippyArgs = function clippyArgs(lints: readonly string[], withFix: boolean): string[] {
    const fixArgs = withFix ? ["--fix", "--allow-dirty", "--allow-no-vcs"] : [];
    const lintArgs = lints.flatMap((lint) => ["-W", lint]);
    return ["clippy", ...fixArgs, "--message-format", "json", ...(lintArgs.length > 0 ? ["--", ...lintArgs] : [])];
};

const clippySpec = function clippySpec(advisory: AdvisoryContext, failed: ScanSpec["failed"]): ScanSpec {
    return {
        failed,
        failure: (exit) =>
            toolFailure(
                advisory,
                toolErrorBecause(`${BIN} ${TOOL}`, exit.status, CAUSE, tailLines(exit.stderr, NO_STDERR_DETAIL)),
            ),
        parse: (result) => parseClippyOutput(result.stdout, advisory.ecosystem),
    };
};

export const runClippy = async function runClippy(context: RunnerContext): Promise<RunResult> {
    const confFile = writeToolFile(
        context.root,
        CONFIG_FILE,
        await nativeToolConfig(context.root, LANGUAGE, CONFIG_FILE),
    );
    const lints = await enabledToolRules(context.root, LANGUAGE, TOOL);
    const advisory = gatingAdvisory(context, TOOL, installHint(TOOL));
    const call = (withFix: boolean): ToolCall => ({
        args: clippyArgs(lints, withFix),
        bin: BIN,
        cwd: projectDirWithMarker(context.root, context.paths, CARGO_MANIFEST),
        env: { ...context.env, CLIPPY_CONF_DIR: path.dirname(confFile) },
    });
    const report = clippySpec(advisory, failedWhenEmpty(SUCCESS_STATUSES));
    const fix = clippySpec(advisory, failedOnStatus(SUCCESS_STATUSES));
    return fixCycle(
        passTool(advisory, call(false), report),
        (findings) => context.fix && findings.length > 0,
        (reported) => {
            const fixed = passTool(advisory, call(true), fix);
            return fixed.terminal ? fixed.result : residualOf(reported, passTool(advisory, call(false), report));
        },
    );
};

defineTool({ ecosystems: [LANGUAGE], run: runClippy, tool: TOOL });