lintTexts

tcmts.cli.lint.lintTexts · #bjhsynsr · term · runs on the server

uses node:fs

Lint texts (each with the extension it needs, .ts or .tsx) with one oxlint run: the issues of each.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { configFor } from "#codebase/tcmts/cli/lint";
import type { LintIssue, LintTraits, OxlintDiagnostic } from "#codebase/tcmts/cli/lint";
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";

export function lintTexts(
  cb: Codebase,
  texts: { text: string; ext: string }[],
  traits: LintTraits,
): LintIssue[][] {
  if (!texts.length) return [];
  const { file: config, bin } = configFor(cb, traits);
  const dir = fs.mkdtempSync(path.join(cb.lay.tcmDir, "lint", "run-"));
  try {
    const files = texts.map((t, i) => {
      const f = path.join(dir, `${i}${t.ext}`);
      fs.writeFileSync(f, t.text);
      return f;
    });
    const r = spawnSync(
      process.execPath,
      [bin, "--format=json", "-c", config, "--no-ignore", ...files],
      { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 },
    );
    let parsed: { diagnostics?: OxlintDiagnostic[] };
    try {
      parsed = JSON.parse(r.stdout) as { diagnostics?: OxlintDiagnostic[] };
    } catch {
      throw new Error(`oxlint failed: ${r.stderr || r.stdout}`);
    }
    const byFile = new Map(files.map((f) => [path.basename(f), [] as LintIssue[]]));
    for (const d of parsed.diagnostics ?? []) {
      const span = d.labels?.[0]?.span;
      byFile.get(path.basename(d.filename))?.push({
        rule: d.code,
        message: d.message,
        severity: d.severity === "error" ? "error" : "warning",
        ...(d.help ? { help: d.help } : {}),
        ...(d.url ? { url: d.url } : {}),
        start: span?.offset ?? 0,
        end: (span?.offset ?? 0) + (span?.length ?? 0),
      });
    }
    return files.map((f) => byFile.get(path.basename(f)) ?? []);
  } finally {
    fs.rmSync(dir, { recursive: true, force: true });
  }
}

uses

used by