Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/biome-native-binary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"diagnostics-webpack-plugin": patch
---

Run Biome's binary directly rather than through its Node launcher, which started a Node process of its own on every run only to start the binary.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,10 @@ new DiagnosticsPlugin({ checks: ["biome"] });
Like the oxlint check this runs a binary and reads the JSON it answers with,
which Biome calls an experimental reporter — a release of its own may move the
shape, and it is the only reporter carrying the severities this check needs.
The binary is the one published for the platform beside `@biomejs/biome`, or
what `BIOME_BINARY` names, as Biome's own launcher finds it — run directly
rather than through that launcher, which is a Node process of its own on every
run for nothing but to start it.

Biome's project and types rules — `noImportCycles`, `noFloatingPromises` —
answer for a file from the files it imports, and which rules those are is not
Expand Down
75 changes: 74 additions & 1 deletion src/checks/biome.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,75 @@ function formatDiagnostic(filename, diagnostic) {
return `${filename}${at} ${diagnostic.severity} ${diagnostic.message}${rule}`;
}

/** @type {Map<string, string | undefined>} */
const natives = new Map();

// Where each platform's build of Biome is published, as Biome's own launcher
// names them.
/** @type {Record<string, Record<string, string>>} */
const PLATFORMS = {
win32: {
x64: "@biomejs/cli-win32-x64/biome.exe",
arm64: "@biomejs/cli-win32-arm64/biome.exe",
},
darwin: {
x64: "@biomejs/cli-darwin-x64/biome",
arm64: "@biomejs/cli-darwin-arm64/biome",
},
linux: {
x64: "@biomejs/cli-linux-x64/biome",
arm64: "@biomejs/cli-linux-arm64/biome",
},
"linux-musl": {
x64: "@biomejs/cli-linux-x64-musl/biome",
arm64: "@biomejs/cli-linux-arm64-musl/biome",
},
};

/**
* @param {string} script the launcher
* @returns {string | undefined} the binary published for this platform beside it
*/
function lookUpNative(script) {
// Node built against glibc says which one; on musl it says nothing.
const { header } = /** @type {EXPECTED_ANY} */ (process.report.getReport());
const platform =
process.platform === "linux" && !header.glibcVersionRuntime
? "linux-musl"
: process.platform;
const published = PLATFORMS[platform] && PLATFORMS[platform][process.arch];

if (!published) return undefined;

try {
// Resolved from the launcher, whose optional dependency the build is.
return createRequire(script).resolve(published);
} catch {
return undefined;
}
}

/**
* The binary Biome's launcher would run, found as the launcher finds it: what
* `BIOME_BINARY` names, or the build published for this platform beside the
* package. The launcher is a Node process of its own on every run, for nothing
* but to start this one.
* @param {string} script the launcher
* @returns {string | undefined} the binary, or nothing where none is found
*/
function findNative(script) {
if (process.env.BIOME_BINARY) return process.env.BIOME_BINARY;

// Asked by every compilation, and the answer is the machine's.
if (natives.has(script)) return natives.get(script);

const found = lookUpNative(script);

natives.set(script, found);

return found;
}

/**
* @param {CheckContext} context check context
* @returns {Promise<CheckInstance>} biome check
Expand All @@ -76,6 +145,7 @@ async function create({ options }) {
String(options.biomePath || "@biomejs/biome"),
"biome",
);
const native = findNative(script);
const cwd = String(options.context);
const flags = [
String(options.command || "lint"),
Expand All @@ -96,9 +166,10 @@ async function create({ options }) {

for (let i = 0; i < files.length; i += FILES_PER_RUN) {
const answer = await runJson(
script,
native || script,
[...flags, ...files.slice(i, i + FILES_PER_RUN)],
cwd,
Boolean(native),
);

found.push(...(answer.diagnostics || []));
Expand Down Expand Up @@ -169,3 +240,5 @@ export default {
// of those rules can write `readsAcrossFiles: false`.
readsAcrossFiles: () => true,
};

export { findNative };
9 changes: 5 additions & 4 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,17 @@ function findBinary(specifier, binary) {
* Runs a tool and reads the JSON it answers with on stdout. A tool that found
* something answers non-zero, which is not a failure to run; an empty answer
* with one is.
* @param {string} script the entry to run
* @param {string} script the entry to run, a script Node runs or a binary
* @param {string[]} args what to run it with
* @param {string} cwd where to run it
* @param {boolean=} native whether the entry is a binary run on its own
* @returns {Promise<EXPECTED_ANY>} what it answered
*/
function runJson(script, args, cwd) {
function runJson(script, args, cwd, native = false) {
return new Promise((promiseResolve, reject) => {
execFile(
process.execPath,
[script, ...args],
native ? script : process.execPath,
native ? args : [script, ...args],
{ cwd, maxBuffer: OUTPUT_LIMIT },
(error, stdout) => {
if (!stdout && error) {
Expand Down
50 changes: 50 additions & 0 deletions test/biome/native.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import assert from "node:assert/strict";
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import { basename, join } from "node:path";
import { afterEach, describe, it } from "node:test";

import { findNative } from "../../src/checks/biome.js";

const require = createRequire(import.meta.url);
const launcher = join(
require.resolve("@biomejs/biome/package.json"),
"..",
"bin",
"biome",
);

describe("biome binary", () => {
afterEach(() => {
delete process.env.BIOME_BINARY;
});

it("should run the build published for this platform rather than its launcher", () => {
const found = /** @type {string} */ (findNative(launcher));

assert.ok(existsSync(found), found);
assert.match(basename(found), /^biome(?:\.exe)?$/u);
assert.match(found, /@biomejs[\\/]cli-/u);
});

it("should run what BIOME_BINARY names, as the launcher does", () => {
process.env.BIOME_BINARY = "/opt/biome";

assert.strictEqual(findNative(launcher), "/opt/biome");
});

it("should leave a launcher with no build beside it to run itself", () => {
const alone = mkdtempSync(join(tmpdir(), "biome-alone-"));

try {
const script = join(alone, "biome");

writeFileSync(script, "");

assert.strictEqual(findNative(script), undefined);
} finally {
rmSync(alone, { recursive: true, force: true });
}
});
});
9 changes: 9 additions & 0 deletions types/checks/biome.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,12 @@ export type Diagnostic = {
* @returns {Promise<CheckInstance>} biome check
*/
declare function create({ options }: CheckContext): Promise<CheckInstance>;
/**
* The binary Biome's launcher would run, found as the launcher finds it: what
* `BIOME_BINARY` names, or the build published for this platform beside the
* package. The launcher is a Node process of its own on every run, for nothing
* but to start this one.
* @param {string} script the launcher
* @returns {string | undefined} the binary, or nothing where none is found
*/
export function findNative(script: string): string | undefined;
4 changes: 3 additions & 1 deletion types/cli.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,17 @@ export function groupByFile<T>(
* Runs a tool and reads the JSON it answers with on stdout. A tool that found
* something answers non-zero, which is not a failure to run; an empty answer
* with one is.
* @param {string} script the entry to run
* @param {string} script the entry to run, a script Node runs or a binary
* @param {string[]} args what to run it with
* @param {string} cwd where to run it
* @param {boolean=} native whether the entry is a binary run on its own
* @returns {Promise<EXPECTED_ANY>} what it answered
*/
export function runJson(
script: string,
args: string[],
cwd: string,
native?: boolean | undefined,
): Promise<EXPECTED_ANY>;
/**
* Splits a file's diagnostics by the severity the tool gave them, so that
Expand Down
Loading