UNPKG

pi-lens

Version:

Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo

79 lines (78 loc) • 3.22 kB
import * as path from "node:path"; import * as fs from "node:fs"; import { safeSpawnAsync } from "../../safe-spawn.js"; import { PRIORITY } from "../priorities.js"; import { createCwdCachedProbe } from "./utils/runner-helpers.js"; // Per-cwd cached `mix credo --version` probe (#120). Before this, the probe // fired on every Elixir file save in projects with mix.exs. const probeCredo = createCwdCachedProbe(async (cwd) => { const r = await safeSpawnAsync("mix", ["credo", "--version"], { timeout: 10000, cwd, }); return !r.error && r.status === 0; }); // `mix credo <file>` is single-file-scoped today, so blanket attribution would // be correct — but map issue.filename (resolved against cwd) for robustness so // this stays correct if the invocation ever widens to a directory (#265 A4). export function parseCredoJson(raw, fallbackPath, cwd) { try { const output = JSON.parse(raw); return (output.issues ?? []).map((issue) => ({ id: `credo:${issue.check}:${issue.line_no}`, message: `[${issue.check}] ${issue.message}`, filePath: issue.filename && issue.filename.trim() ? path.isAbsolute(issue.filename) ? issue.filename : path.resolve(cwd, issue.filename) : fallbackPath, line: issue.line_no, column: issue.column ?? 1, severity: issue.priority <= 10 ? "error" : "warning", semantic: issue.priority <= 10 ? "blocking" : "warning", tool: "credo", rule: issue.check, fixable: false, })); } catch { return []; } } function hasMixExs(cwd) { return fs.existsSync(path.join(cwd, "mix.exs")); } const credoRunner = { id: "credo", appliesTo: ["elixir"], priority: PRIORITY.GENERAL_ANALYSIS, enabledByDefault: true, skipTestFiles: false, async run(ctx) { const cwd = ctx.cwd || process.cwd(); if (!hasMixExs(cwd)) { return { status: "skipped", diagnostics: [], semantic: "none" }; } // Credo ships as a mix dependency — cached per-cwd probe (see probeCredo). if (!(await probeCredo(cwd))) { return { status: "skipped", diagnostics: [], semantic: "none" }; } const absPath = path.resolve(cwd, ctx.filePath); const result = await safeSpawnAsync("mix", ["credo", "--format", "json", "--strict", absPath], { timeout: 30000, cwd }); // credo exits 1 when issues found, 0 when clean if (result.status === null || result.status > 1) { return { status: "skipped", diagnostics: [], semantic: "none" }; } const diagnostics = parseCredoJson(result.stdout ?? "", ctx.filePath, cwd); if (diagnostics.length === 0) { return { status: "succeeded", diagnostics: [], semantic: "none" }; } const hasBlocking = diagnostics.some((d) => d.semantic === "blocking"); return { status: hasBlocking ? "failed" : "succeeded", diagnostics, semantic: hasBlocking ? "blocking" : "warning", }; }, }; export default credoRunner;