core/adapters/tool.prettier.adapter.ts

core/adapters/tool.prettier.adapter.ts is a file in GovLab Quality. 104 lines of code and 25 definitions.

import { isRecord, stringArrayField } from "#core/selectors/record.selector";
import { toolCacheDir, writeToolFile, writeToolJson } from "#core/persistence/tool.persistence";
import { POSITION } from "#configuration/constants/tool.constants";
import type { RunResult } from "#types/finding.types";
import type { RunnerContext } from "#types/tool.types";
import { createRequire } from "node:module";
import { defineTool } from "#core/registries/tool.registry";
import { emitPrettierConfig } from "#core/emitters/tool.prettier.emitter";
import { exclusionsFrom } from "#core/converters/exclusions.converter";
import { isInsideRoot } from "#core/predicates/location.predicate";
import { loadGovlabConfig } from "#core/loaders/config.loader";
import path from "node:path";
import { prettierFailed } from "#configuration/strings/tool.strings";
import process from "node:process";
import { sectionOf } from "#core/selectors/config.selector";
import { spawn } from "node:child_process";
import { superviseChild } from "#core/lifecycle/invocation.lifecycle";
import { toolFinding } from "#core/factories/finding.factory";
import { validConcepts } from "#core/selectors/concept.selector";
import { validateConcerns } from "#core/validators/concern.validator";
import { withMasterExclude } from "#core/selectors/exclusions.selector";

const SECTION = "prettier";
const FAILED_EXIT = 1;
const moduleRequire = createRequire(import.meta.url);

const prettierBin = function prettierBin(): string {
    return path.join(path.dirname(moduleRequire.resolve("prettier/package.json")), "bin", "prettier.cjs");
};

export const govlabPrettierConfig = async (consumerRoot: string = process.cwd()): Promise<Record<string, unknown>> => {
    const config = await loadGovlabConfig(consumerRoot);
    const section = sectionOf(config, SECTION);
    const concerns = validateConcerns(config.qualityMaster?.concerns ?? {}, validConcepts());
    return emitPrettierConfig({ base: isRecord(section["base"]) ? section["base"] : undefined, concerns });
};

export const govlabPrettierIgnore = async (consumerRoot: string = process.cwd()): Promise<string[]> => {
    const config = await loadGovlabConfig(consumerRoot);
    return withMasterExclude(config, stringArrayField(sectionOf(config, SECTION), "ignore"));
};

interface PrettierRun {
    readonly ignorePath: string;
    readonly paths: readonly string[];
}

const prettierRuns = async function prettierRuns(context: RunnerContext): Promise<readonly PrettierRun[]> {
    const dir = toolCacheDir(context.root, SECTION);
    const exclusions = exclusionsFrom(await govlabPrettierIgnore(context.root), dir, context.root);
    const inside = context.paths.filter((target) => isInsideRoot(context.root, target));
    const outside = context.paths.filter((target) => !isInsideRoot(context.root, target));
    return [
        { ignorePath: writeToolFile(context.root, path.join(SECTION, "ignore"), exclusions.join("\n")), paths: inside },
        { ignorePath: writeToolFile(context.root, path.join(SECTION, "outside.ignore"), ""), paths: outside },
    ].filter((run) => run.paths.length > 0);
};

const prettierArgs = function prettierArgs(context: RunnerContext, configPath: string, run: PrettierRun): string[] {
    return [
        prettierBin(),
        "--config",
        configPath,
        "--ignore-path",
        run.ignorePath,
        "--cache",
        "--cache-location",
        path.join(toolCacheDir(context.root, SECTION), "cache"),
        "--no-error-on-unmatched-pattern",
        "--log-level",
        "log",
        context.fix ? "--write" : "--check",
        ...run.paths,
    ];
};

const exitOf = async function exitOf(root: string, args: readonly string[]): Promise<number> {
    return new Promise<number>((resolve) => {
        const child = spawn(process.execPath, args, { cwd: root, stdio: "inherit" });
        superviseChild(child);
        child.on("error", () => {
            resolve(FAILED_EXIT);
        });
        child.on("close", (exit) => {
            resolve(exit ?? FAILED_EXIT);
        });
    });
};

export const runPrettier = async function runPrettier(context: RunnerContext): Promise<RunResult> {
    const configPath = await writeToolJson(
        context.root,
        path.join(SECTION, "config.json"),
        await govlabPrettierConfig(context.root),
    );
    const runs = await prettierRuns(context);
    const code = await runs.reduce<Promise<number>>(async (previous, run) => {
        const prior = await previous;
        const exit = await exitOf(context.root, prettierArgs(context, configPath, run));
        return prior === 0 ? exit : prior;
    }, Promise.resolve(0));
    const failure = toolFinding({
        column: POSITION,
        ecosystem: context.ecosystem,
        file: "",
        line: POSITION,
        message: prettierFailed(code),
        ruleId: "prettier/format",
        tool: SECTION,
    });
    return { findings: code === 0 ? [] : [failure], fixedCount: 0, output: "" };
};

defineTool({ ecosystems: ["javascript", "typescript", "css", "json"], run: runPrettier, tool: SECTION });