pi-lens
Version:
Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo
203 lines (201 loc) • 6.84 kB
JavaScript
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
import {
createAvailabilityChecker,
resolveAvailableOrInstall
} from "./chunk-7FBF7NPR.js";
import {
ruffConfigArgs
} from "./chunk-HHYAELLJ.js";
import "./chunk-B3I2UUT4.js";
import "./chunk-DIULIWKU.js";
import "./chunk-VDBPVIKD.js";
import "./chunk-TGPXLY2J.js";
import "./chunk-QOZXJ4NA.js";
import "./chunk-PAK6IDME.js";
import "./chunk-GUOGVTCQ.js";
import "./chunk-AJVLWNFN.js";
import "./chunk-YGVYKNSN.js";
import "./chunk-LNAKEO63.js";
import "./chunk-Z3CCVDUB.js";
import "./chunk-2BEMIWEW.js";
import "./chunk-OQGFCZJ4.js";
import {
resolveToolCwd
} from "./chunk-RUOG5D2C.js";
import "./chunk-U3VGR4VU.js";
import "./chunk-NTUJEGDZ.js";
import "./chunk-EOKRQ3SA.js";
import "./chunk-WONERJTP.js";
import "./chunk-22AVIGXS.js";
import "./chunk-GVXA6CCE.js";
import "./chunk-6QIUX4EP.js";
import "./chunk-VHQLPIRJ.js";
import {
safeSpawnAsync
} from "./chunk-VN2AXLEX.js";
import "./chunk-BW5KFIQC.js";
import "./chunk-QYF5S6P6.js";
import {
isFileKind
} from "./chunk-5ZEJHV35.js";
import "./chunk-DQ2NZ7C4.js";
import "./chunk-VNMT7C64.js";
import "./chunk-2EECBNC5.js";
import "./chunk-N3YQJI6O.js";
import {
createSubsystemLogger
} from "./chunk-O6TQT6RI.js";
import "./chunk-5THXD6X5.js";
import "./chunk-UK3CMEAL.js";
import "./chunk-LJDODBBZ.js";
import "./chunk-ABBOA7UD.js";
import {
pathsEqual
} from "./chunk-Q5U6FMDI.js";
import "./chunk-NXL4FFQQ.js";
// dist/clients/ruff-client.js
import * as fs from "node:fs";
import * as path from "node:path";
var ruffAvailability = createAvailabilityChecker("ruff", ".exe");
var RuffClient = class {
ruffCommand = "ruff";
log;
constructor(verbose = false) {
this.log = verbose ? createSubsystemLogger("ruff") : () => {
};
}
/**
* Check if ruff CLI is available, auto-install if not.
*
* Re-entrancy safe: the shared availability seam deduplicates the complete
* probe/auto-install transaction per cwd and tool, so concurrent callers do
* not duplicate installation attempts.
*/
async ensureAvailable() {
const resolved = await resolveAvailableOrInstall(ruffAvailability, "ruff", process.cwd());
if (!resolved)
return false;
this.ruffCommand = resolved;
return true;
}
/**
* Check if a file is a Python file
*/
isPythonFile(filePath) {
return isFileKind(filePath, "python");
}
/**
* Async auto-fix variant for pipeline use (non-blocking spawn).
* `cwd` is the dispatch language root. It is the seam's starting point,
* not the answer: `resolveToolCwd` walks from the file to the nearest
* `pyproject.toml`/`ruff.toml`/`.ruff.toml` at or above it, capped inside
* `cwd` (and at `$HOME` when there is none), so a file in a nested package
* gets that package's config rather than the workspace-root one.
*/
async fixFileAsync(filePath, cwd) {
if (!await this.ensureAvailable()) {
return {
success: false,
changed: false,
fixed: 0,
error: "Ruff not available"
};
}
const absolutePath = path.resolve(filePath);
if (!fs.existsSync(absolutePath)) {
return {
success: false,
changed: false,
fixed: 0,
error: "File not found"
};
}
try {
const before = await fs.promises.readFile(absolutePath, "utf-8");
const ruffCwd = resolveToolCwd("runner", "ruff", absolutePath, {
...cwd !== void 0 && { cwd }
}).cwd;
const configArgs = ruffConfigArgs(ruffCwd);
const pre = await safeSpawnAsync(this.ruffCommand, [
"check",
"--output-format",
"json",
"--target-version",
"py310",
...configArgs,
absolutePath
], { timeout: 1e4, cwd: ruffCwd });
const beforeDiags = pre.stdout?.trim() ? this.parseOutput(pre.stdout, ruffCwd, absolutePath) : [];
const fixableCount = beforeDiags.filter((d) => d.fixable).length;
const fix = await safeSpawnAsync(this.ruffCommand, ["check", "--fix", ...configArgs, absolutePath], { timeout: 15e3, cwd: ruffCwd });
if (fix.error) {
return {
success: false,
changed: false,
fixed: 0,
error: fix.error.message
};
}
const after = await fs.promises.readFile(absolutePath, "utf-8");
const changed = before !== after;
if (changed) {
this.log(`Fixed ${fixableCount} issue(s) in ${path.basename(filePath)}`);
}
return { success: true, changed, fixed: fixableCount };
} catch (err) {
return { success: false, changed: false, fixed: 0, error: err.message };
}
}
// --- Internal ---
/**
* `cwd` is the directory ruff RAN in, and it is REQUIRED: a default would
* hide the wrong base at a call site that forgot it (#3284's D11).
*
* ruff's JSON `filename` is the `SourceFile`'s name verbatim — the JSON
* renderer reads `span.file().path(resolver)` (ruff 0.16.8
* `crates/ruff_db/src/diagnostic/render/json.rs:58,120`), which for a ruff
* (not `ty`) diagnostic is `file.name()` (`crates/ruff_db/src/diagnostic/
* mod.rs:1187-1193`), NOT the cwd-relative `relative_path` the text
* renderers use (`mod.rs:1195-1204`). That name is
* `SourceFileBuilder::new(path.to_string_lossy(), …)`
* (`crates/ruff_linter/src/linter.rs:1049`) over a path the resolver has
* already absolutized ("Normalize every path (e.g., convert from relative to
* absolute)", `crates/ruff_workspace/src/resolver.rs:484`). So ruff echoes
* the absolute path we hand it as argv: the BASE was already right here and
* the fold deletes a `path.resolve` with no base — which silently meant the
* EXTENSION's `process.cwd()`, not ruff's — rather than leaving it for a
* future relative invocation to re-enable.
*/
parseOutput(output, cwd, filterFile) {
if (!output.trim())
return [];
try {
const items = JSON.parse(output);
const diagnostics = [];
for (const item of items) {
if (filterFile && !pathsEqual(path.resolve(cwd, item.filename), filterFile))
continue;
diagnostics.push({
line: item.location.row - 1,
// ruff is 1-indexed
column: item.location.column - 1,
endLine: item.end_location.row - 1,
endColumn: item.end_location.column - 1,
severity: item.code?.startsWith("E") ? "error" : "warning",
message: item.message,
rule: item.code || "unknown",
file: item.filename,
fixable: item.fix !== null
});
}
return diagnostics;
} catch (err) {
void err;
this.log("Failed to parse ruff JSON output");
return [];
}
}
};
export {
RuffClient
};