pi-lens
Version:
Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo
121 lines (120 loc) • 4.94 kB
JavaScript
/**
* Biome check runner for dispatch system
*
* Dispatch mode is diagnostics-only.
* Autofix is handled earlier by the post-write pipeline to avoid
* mutating files mid-dispatch after LSP sync has already happened.
*/
import * as path from "node:path";
import { resolvePackagePath } from "../../package-root.js";
import { safeSpawnAsync } from "../../safe-spawn.js";
import { getAutofixCapability, getBiomeConfigPath, getJstsLintPolicyForCwd, } from "../../tool-policy.js";
import { PRIORITY } from "../priorities.js";
import { resolveToolCommandWithInstallFallback } from "./utils/runner-helpers.js";
function parseBiomeJson(raw, filePath) {
try {
const result = JSON.parse(raw);
const diagnostics = result.diagnostics || [];
const autofix = getAutofixCapability("biome");
return {
diagnostics: diagnostics.map((d) => ({
id: `biome:${d.category}:${d.location.start.line}`,
message: d.message,
filePath,
line: d.location.start.line,
column: d.location.start.column,
severity: d.severity === "error" ? "error" : "warning",
semantic: d.severity === "error" ? "blocking" : "warning",
tool: "biome",
rule: d.category,
fixable: d.tags?.includes("fixable") ?? false,
autoFixAvailable: (d.tags?.includes("fixable") ?? false) &&
(autofix?.safePipelineAutofix ?? false),
fixKind: d.tags?.includes("fixable") && autofix?.fixKind !== "none"
? autofix?.fixKind
: undefined,
})),
};
}
catch (err) {
return {
diagnostics: [],
parseError: err instanceof Error ? err.message : String(err),
};
}
}
const biomeCheckJsonRunner = {
id: "biome-check-json",
appliesTo: ["jsts"],
priority: PRIORITY.FORMAT_AND_LINT_PRIMARY,
enabledByDefault: true,
async run(ctx) {
const cwd = ctx.cwd || path.dirname(ctx.filePath);
const policy = getJstsLintPolicyForCwd(cwd);
// Defer to ESLint/oxlint if the project has explicitly configured one —
// biome runs as the default linter only when no alternative is present.
if (!policy.hasBiomeConfig && policy.hasExplicitNonBiomeLinter) {
return { status: "skipped", diagnostics: [], semantic: "none" };
}
const cmd = await resolveToolCommandWithInstallFallback(cwd, "biome");
if (!cmd) {
return { status: "skipped", diagnostics: [], semantic: "none" };
}
// Build config path — use user's if exists, else pi-lens config
const userConfigPath = getBiomeConfigPath(cwd);
const configArg = [
"--config-path=" +
(userConfigPath ??
resolvePackagePath(import.meta.url, "config/biome/core.jsonc")),
];
// Run biome lint (diagnostics only - format is handled separately)
const checkResult = await safeSpawnAsync(cmd, [
"lint",
"--reporter=json",
"--no-errors-on-unmatched",
...configArg,
ctx.filePath,
], { timeout: 30000, cwd });
// Handle spawn errors (e.g., binary not found)
if (checkResult.error) {
return { status: "skipped", diagnostics: [], semantic: "none" };
}
const parsed = checkResult.status === 0 || checkResult.status === 1
? parseBiomeJson(checkResult.stdout || "", ctx.filePath)
: { diagnostics: [] };
if (parsed.parseError) {
const raw = checkResult.stdout || checkResult.stderr || "";
const preview = raw.replace(/\s+/g, " ").slice(0, 160);
return {
status: "failed",
diagnostics: [
{
id: "biome:parse-error:1",
message: "Biome JSON parse failed: " + parsed.parseError + (preview ? " (output preview: " + preview + ")" : ""),
filePath: ctx.filePath,
line: 1,
column: 1,
severity: "warning",
semantic: "warning",
tool: "biome",
},
],
semantic: "warning",
};
}
const diagnostics = parsed.diagnostics;
let semantic = "none";
if (diagnostics.some((d) => d.semantic === "blocking")) {
semantic = "blocking";
}
else if (diagnostics.length > 0) {
semantic = "warning";
}
return {
status: semantic === "blocking" ? "failed" : "succeeded",
diagnostics,
semantic,
};
},
};
export default biomeCheckJsonRunner;