diff --git a/package.json b/package.json index 7a3f72db68bd..e41e1a14288f 100644 --- a/package.json +++ b/package.json @@ -1486,7 +1486,7 @@ "lint:apps": "pnpm lint:swift", "lint:auth:no-pairing-store-group": "node scripts/check-no-pairing-store-group-auth.mjs", "lint:auth:pairing-account-scope": "node scripts/check-pairing-account-scope.mjs", - "lint:core": "node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json src ui packages", + "lint:core": "node scripts/run-oxlint-shards.mjs --only=core --split-core", "lint:docker-e2e": "node scripts/check-docker-e2e-boundaries.mjs", "lint:docs": "pnpm dlx --config.resolution-mode=highest markdownlint-cli2 --config config/markdownlint-cli2.jsonc", "lint:docs:fix": "pnpm dlx --config.resolution-mode=highest markdownlint-cli2 --config config/markdownlint-cli2.jsonc --fix", diff --git a/scripts/run-oxlint-shards.mjs b/scripts/run-oxlint-shards.mjs index a7c720a37210..57b1d93a8867 100644 --- a/scripts/run-oxlint-shards.mjs +++ b/scripts/run-oxlint-shards.mjs @@ -19,6 +19,7 @@ const CORE_SHARD = { name: "core", args: ["--tsconfig", "config/tsconfig/oxlint.core.json", "src", "ui", "packages"], }; +const CORE_SPLIT_TARGETS = ["src", "ui", "packages"]; const EXTENSIONS_SHARD = { name: "extensions", args: ["--tsconfig", EXTENSION_TS_CONFIG, EXTENSIONS_DIR], @@ -33,11 +34,20 @@ export function createOxlintShards({ env = process.env, platform = process.platform, readDir = fs.readdirSync, + splitCore = false, } = {}) { + const coreShards = splitCore ? createCoreOxlintShards() : [CORE_SHARD]; const extensionShards = platform === "win32" ? createWindowsExtensionShards({ cwd, env, readDir }) : [EXTENSIONS_SHARD]; - return [CORE_SHARD, ...extensionShards, SCRIPTS_SHARD]; + return [...coreShards, ...extensionShards, SCRIPTS_SHARD]; +} + +export function createCoreOxlintShards() { + return CORE_SPLIT_TARGETS.map((target) => ({ + name: `core:${target}`, + args: ["--tsconfig", "config/tsconfig/oxlint.core.json", target], + })); } export function createWindowsExtensionShards({ @@ -143,13 +153,14 @@ function listExtensionEntries({ cwd, readDir }) { export async function main(extraArgs = process.argv.slice(2), runtimeEnv = process.env) { const runner = path.resolve("scripts", "run-oxlint.mjs"); + const shardArgs = parseShardRunnerArgs(extraArgs); const env = resolveLocalHeavyCheckEnv(runtimeEnv); - const hasMetadataOnlyFlag = extraArgs.some((arg) => + const hasMetadataOnlyFlag = shardArgs.oxlintArgs.some((arg) => ["--help", "-h", "--version", "-V", "--rules", "--print-config", "--init"].includes(arg), ); const shouldAcquireParentLock = !hasMetadataOnlyFlag || - shouldAcquireLocalHeavyCheckLockForOxlint(extraArgs, { + shouldAcquireLocalHeavyCheckLockForOxlint(shardArgs.oxlintArgs, { cwd: process.cwd(), env, }); @@ -168,7 +179,9 @@ export async function main(extraArgs = process.argv.slice(2), runtimeEnv = proce cwd: process.cwd(), env, platform: process.platform, + splitCore: shardArgs.splitCore, }); + const selectedShards = filterOxlintShards(shards, shardArgs.only); try { const prepareResult = spawnSync( @@ -186,13 +199,24 @@ export async function main(extraArgs = process.argv.slice(2), runtimeEnv = proce if ((prepareResult.status ?? 1) !== 0) { process.exitCode = prepareResult.status ?? 1; } else { - const runSerial = shouldRunOxlintShardsSerial({ - env, - platform: process.platform, - }); + const runSerial = + shardArgs.splitCore || + shouldRunOxlintShardsSerial({ + env, + platform: process.platform, + }); const results = runSerial - ? await runShardsSerial({ entries: shards, env, extraArgs, runner }) - : await Promise.all(shards.map((shard) => runShard({ env, extraArgs, runner, shard }))); + ? await runShardsSerial({ + entries: selectedShards, + env, + extraArgs: shardArgs.oxlintArgs, + runner, + }) + : await Promise.all( + selectedShards.map((shard) => + runShard({ env, extraArgs: shardArgs.oxlintArgs, runner, shard }), + ), + ); process.exitCode = results.find((status) => status !== 0) ?? 0; } } finally { @@ -216,6 +240,46 @@ function resolveHostResources(hostResources) { }; } +export function parseShardRunnerArgs(args) { + const only = new Set(); + const oxlintArgs = []; + let splitCore = false; + + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (arg === "--split-core") { + splitCore = true; + continue; + } + if (arg === "--only") { + const value = args[index + 1]; + if (value) { + only.add(value); + index += 1; + } + continue; + } + if (arg.startsWith("--only=")) { + const value = arg.slice("--only=".length); + if (value) { + only.add(value); + } + continue; + } + oxlintArgs.push(arg); + } + + return { only, oxlintArgs, splitCore }; +} + +export function filterOxlintShards(shards, only) { + if (only.size === 0) { + return shards; + } + + return shards.filter((shard) => only.has(shard.name) || only.has(shard.name.split(":")[0])); +} + async function runShardsSerial({ entries, env, extraArgs, runner }) { const results = []; for (const shard of entries) { diff --git a/test/scripts/run-oxlint.test.ts b/test/scripts/run-oxlint.test.ts index 3937e684bd2d..dad6c94c1dae 100644 --- a/test/scripts/run-oxlint.test.ts +++ b/test/scripts/run-oxlint.test.ts @@ -2,6 +2,8 @@ import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; import { createOxlintShards, + filterOxlintShards, + parseShardRunnerArgs, createWindowsExtensionShards, resolveWindowsExtensionChunkSize, shouldRunOxlintShardsSerial, @@ -33,6 +35,9 @@ describe("run-oxlint", () => { expect(packageJson.scripts.check).toBe("node scripts/check.mjs"); expect(packageJson.scripts.lint).toBe("node scripts/run-oxlint-shards.mjs"); + expect(packageJson.scripts["lint:core"]).toBe( + "node scripts/run-oxlint-shards.mjs --only=core --split-core", + ); expect(packageJson.scripts.check).not.toContain( "node scripts/prepare-extension-package-boundary-artifacts.mjs", ); @@ -168,6 +173,42 @@ describe("run-oxlint", () => { ]); }); + it("splits core oxlint shards when requested", () => { + expect(createOxlintShards({ splitCore: true }).slice(0, 3)).toEqual([ + { + name: "core:src", + args: ["--tsconfig", "config/tsconfig/oxlint.core.json", "src"], + }, + { + name: "core:ui", + args: ["--tsconfig", "config/tsconfig/oxlint.core.json", "ui"], + }, + { + name: "core:packages", + args: ["--tsconfig", "config/tsconfig/oxlint.core.json", "packages"], + }, + ]); + }); + + it("parses shard runner flags without forwarding them to oxlint", () => { + const parsed = parseShardRunnerArgs([ + "--only=core", + "--split-core", + "--max-warnings", + "0", + ]); + + expect([...parsed.only]).toEqual(["core"]); + expect(parsed.splitCore).toBe(true); + expect(parsed.oxlintArgs).toEqual(["--max-warnings", "0"]); + }); + + it("filters split core shards by shard family", () => { + const shards = filterOxlintShards(createOxlintShards({ splitCore: true }), new Set(["core"])); + + expect(shards.map((shard) => shard.name)).toEqual(["core:src", "core:ui", "core:packages"]); + }); + it("falls back to the full extension shard when Windows extension dirs are unavailable", () => { const shards = createWindowsExtensionShards({ cwd: "/repo",