@isentinel/eslint-config 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -2
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +8 -1
- package/dist/index.mjs +12 -3
- package/dist/lint-bootstrap.d.mts +1 -0
- package/dist/lint-bootstrap.mjs +98 -0
- package/dist/lint-cli.mjs +691 -129
- package/dist/oxlint.d.mts +7 -2
- package/dist/oxlint.mjs +22 -16
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -230,8 +230,8 @@ It resolves the `eslint` and `oxlint` binaries from your project's
|
|
|
230
230
|
|
|
231
231
|
By default (a local run, no `--type-aware`, no `--fix`) it runs **three**
|
|
232
232
|
children concurrently via
|
|
233
|
-
[`concurrently`](https://www.npmjs.com/package/concurrently)
|
|
234
|
-
output:
|
|
233
|
+
[`concurrently`](https://www.npmjs.com/package/concurrently)'s core (no shell),
|
|
234
|
+
grouping their output:
|
|
235
235
|
|
|
236
236
|
- **oxlint** — the full oxlint pass (including its type-aware rules).
|
|
237
237
|
- **fast pass** — ESLint with only the syntactic (non-type-aware) rules, over
|
|
@@ -253,6 +253,22 @@ Other behaviours:
|
|
|
253
253
|
re-lint (its own dirty count), rather than eagerly spinning up a worker per
|
|
254
254
|
CPU. The fast pass is cheap per file, so it packs far more files per worker
|
|
255
255
|
(see `FAST_FILES_PER_WORKER`) than the type-aware pass.
|
|
256
|
+
- Sizes the heap of every ESLint child that builds a TypeScript program (the
|
|
257
|
+
type-aware pass, the full pass and the fix child). Each worker holds its own
|
|
258
|
+
program, so the limit that matters is workers × heap. By default the runner
|
|
259
|
+
splits half the machine's memory (or its cgroup limit, when lower) across
|
|
260
|
+
those workers, and raises Node's heap limit when the share is larger. When the
|
|
261
|
+
budget cannot give each worker twice Node's default heap, it runs fewer
|
|
262
|
+
workers: every worker loads the program of nearly every project, so fewer,
|
|
263
|
+
larger workers need far less memory in total than more, smaller ones. The flag
|
|
264
|
+
goes in the child's `NODE_OPTIONS`, which every ESLint worker inherits; oxlint
|
|
265
|
+
and the fast pass never get it. `--print` shows the chosen workers, files per
|
|
266
|
+
worker and heap on stderr. If a child still runs out of memory, the runner
|
|
267
|
+
prints a hint that names `LINT_TYPED_HEAP_MB`. A size set in
|
|
268
|
+
`LINT_TYPED_HEAP_MB` also caps the workers at budget ÷ size. An explicit
|
|
269
|
+
`LINT_MAX_WORKERS` or `--concurrency` is never reduced. A
|
|
270
|
+
`--max-old-space-size` already in `NODE_OPTIONS` wins over all of this: the
|
|
271
|
+
runner adds nothing and keeps the worker count.
|
|
256
272
|
- Keeps a separate ESLint cache per pass so they never invalidate one another:
|
|
257
273
|
`.eslintcache-fast-<key>` (fast pass), `.eslintcache-typeaware-<key>`
|
|
258
274
|
(type-aware pass), and `.eslintcache-<key>` (the full config, used by CI,
|
|
@@ -341,6 +357,32 @@ determined it fails open and runs both engines as before. Explicit `--eslint` /
|
|
|
341
357
|
|
|
342
358
|
Any trailing positional arguments are treated as paths to lint (default `.`).
|
|
343
359
|
|
|
360
|
+
### Large file selections
|
|
361
|
+
|
|
362
|
+
A git hook that hands the runner thousands of files should not put them on a
|
|
363
|
+
command line: `cmd.exe` stops at 8,191 characters and `CreateProcessW` at
|
|
364
|
+
32,767, so tools end up splitting the list into batches that each start another
|
|
365
|
+
lint stack. Pass the list as data instead:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
git diff --cached --name-only -z --relative --diff-filter=ACMR | isentinel-lint --files0-from -
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`--files0-from <file|->` reads NUL-terminated UTF-8 paths from a file, or from
|
|
372
|
+
stdin for `-` (the `git -z` / `find -print0` shape). Relative entries resolve
|
|
373
|
+
against the working directory, exactly like positional paths (hence `--relative`
|
|
374
|
+
above when running from a sub-package). The paths go through the same planning,
|
|
375
|
+
caching and type-aware skip as positional ones, in one run. An empty list lints
|
|
376
|
+
nothing and exits 0 — it never falls back to `.`. A list that is not valid UTF-8
|
|
377
|
+
or holds an empty entry is refused, as is combining the flag with positional
|
|
378
|
+
paths.
|
|
379
|
+
|
|
380
|
+
The runner never puts paths on a command line itself either: every oxlint and
|
|
381
|
+
ESLint child is launched as `node <bootstrap> <engine bin>` with no shell, and
|
|
382
|
+
its full argument list is streamed to it over stdin. As a consequence ESLint's
|
|
383
|
+
own `--stdin` / `--stdin-filename` cannot be forwarded through `--eslint-args`
|
|
384
|
+
or `--`.
|
|
385
|
+
|
|
344
386
|
### Flags
|
|
345
387
|
|
|
346
388
|
| Flag | Description |
|
|
@@ -355,6 +397,7 @@ Any trailing positional arguments are treated as paths to lint (default `.`).
|
|
|
355
397
|
| `--concurrency <n\|off>` | Override the concurrency heuristic with a fixed worker count. |
|
|
356
398
|
| `--eslint-args "<args>"` | Extra arguments forwarded verbatim to ESLint. |
|
|
357
399
|
| `--oxlint-args "<args>"` | Extra arguments forwarded verbatim to oxlint. |
|
|
400
|
+
| `--files0-from <file\|->` | Read the paths to lint from a NUL-delimited UTF-8 list (`-` for stdin); an empty list exits 0. |
|
|
358
401
|
| `--print` | Print the composed commands without running them. |
|
|
359
402
|
| `-- <args>` | Forward args to the single selected tool (needs `--eslint`/`--oxlint`). |
|
|
360
403
|
| `-h`, `--help` | Show help. |
|
|
@@ -367,6 +410,7 @@ Any trailing positional arguments are treated as paths to lint (default `.`).
|
|
|
367
410
|
| `FILES_PER_WORKER` | Target files a single type-aware ESLint worker handles before adding another (default `350`). |
|
|
368
411
|
| `FAST_FILES_PER_WORKER` | Same, for the fast (syntactic) pass, which packs far more files per worker (default `800`). |
|
|
369
412
|
| `LINT_MAX_WORKERS` | Upper bound on ESLint workers (default a quarter of available CPUs). |
|
|
413
|
+
| `LINT_TYPED_HEAP_MB` | Heap per worker for the program-building ESLint children: `auto` (default), a size in MB, or `off` (Node's default). |
|
|
370
414
|
| `ESLINT_TYPE_AWARE` | `off` or `only`; the type-aware mode `--type-aware` sets for the ESLint child (`false` is a legacy alias for `off`). |
|
|
371
415
|
| `CI` | When set, the runner uses the single full pass with `--cache-strategy content` (see below). |
|
|
372
416
|
|
package/dist/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import { existsSync, readFileSync } from "fs";
|
|
10
10
|
import { execSync } from "node:child_process";
|
|
11
11
|
//#region package.json
|
|
12
|
-
var version = "6.
|
|
12
|
+
var version = "6.1.0";
|
|
13
13
|
var package_default = {
|
|
14
14
|
name: "@isentinel/eslint-config",
|
|
15
15
|
version,
|
package/dist/index.d.mts
CHANGED
|
@@ -28502,7 +28502,14 @@ declare function loadSmallRulesPlugin(): NonNullable<TypedFlatConfigItem["plugin
|
|
|
28502
28502
|
declare function smallRules(options?: OptionsComponentExtensions & OptionsFiles & OptionsIsInEditor & OptionsStylistic): Array<TypedFlatConfigItem>;
|
|
28503
28503
|
//#endregion
|
|
28504
28504
|
//#region src/eslint/configs/sonarjs.d.ts
|
|
28505
|
-
declare function sonarjs({ isInEditor, roblox }: OptionsHasRoblox & Required<OptionsIsInEditor>
|
|
28505
|
+
declare function sonarjs({ complementIgnores, isInEditor, roblox }: OptionsHasRoblox & Required<OptionsIsInEditor> & {
|
|
28506
|
+
/**
|
|
28507
|
+
* When set, re-apply the non-roblox rules to every source file except
|
|
28508
|
+
* these globs (the roblox scope), so the complement is linted as
|
|
28509
|
+
* standard-TS/Node land.
|
|
28510
|
+
*/
|
|
28511
|
+
complementIgnores?: Array<string>;
|
|
28512
|
+
}): Promise<Array<TypedFlatConfigItem>>;
|
|
28506
28513
|
//#endregion
|
|
28507
28514
|
//#region src/eslint/configs/sort.d.ts
|
|
28508
28515
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -4523,7 +4523,8 @@ function sonarjsTestRules({ jest = false, roblox = true } = {}) {
|
|
|
4523
4523
|
}
|
|
4524
4524
|
//#endregion
|
|
4525
4525
|
//#region src/eslint/configs/sonarjs.ts
|
|
4526
|
-
|
|
4526
|
+
init_globs();
|
|
4527
|
+
async function sonarjs({ complementIgnores, isInEditor, roblox }) {
|
|
4527
4528
|
return [{
|
|
4528
4529
|
name: "isentinel/sonarjs",
|
|
4529
4530
|
plugins: { sonar: await interopDefault(import("eslint-plugin-sonarjs")) },
|
|
@@ -4531,7 +4532,15 @@ async function sonarjs({ isInEditor, roblox }) {
|
|
|
4531
4532
|
isInEditor,
|
|
4532
4533
|
roblox
|
|
4533
4534
|
})
|
|
4534
|
-
}
|
|
4535
|
+
}, ...complementIgnores ? [{
|
|
4536
|
+
name: "isentinel/sonarjs/complement",
|
|
4537
|
+
files: [GLOB_SRC],
|
|
4538
|
+
ignores: complementIgnores,
|
|
4539
|
+
rules: sonarjsRules({
|
|
4540
|
+
isInEditor,
|
|
4541
|
+
roblox: false
|
|
4542
|
+
})
|
|
4543
|
+
}] : []];
|
|
4535
4544
|
}
|
|
4536
4545
|
//#endregion
|
|
4537
4546
|
//#region src/eslint/configs/sort.ts
|
|
@@ -5938,7 +5947,6 @@ function typescriptTypeAwareRules({ roblox = true } = {}) {
|
|
|
5938
5947
|
"ts/no-confusing-void-expression": "error",
|
|
5939
5948
|
"ts/no-deprecated": "error",
|
|
5940
5949
|
"ts/no-duplicate-type-constituents": "error",
|
|
5941
|
-
"ts/no-empty-object-type": "error",
|
|
5942
5950
|
"ts/no-floating-promises": ["error", {
|
|
5943
5951
|
checkThenables: roblox,
|
|
5944
5952
|
ignoreVoid: true
|
|
@@ -20314,6 +20322,7 @@ async function isentinel(options, ...userConfigs) {
|
|
|
20314
20322
|
roblox: enableRoblox,
|
|
20315
20323
|
stylistic: stylisticOptions
|
|
20316
20324
|
}), promise(), sonarjs({
|
|
20325
|
+
...needsComplementOverlay ? { complementIgnores: robloxScopedFiles } : {},
|
|
20317
20326
|
isInEditor,
|
|
20318
20327
|
roblox: enableRoblox
|
|
20319
20328
|
}), typescript({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
//#region src/lint-cli/bootstrap.ts
|
|
5
|
+
/**
|
|
6
|
+
* Engine bootstrap for the lint runner. Spawned as
|
|
7
|
+
* `node <this file> <engine bin>` with the engine's complete argument list as a
|
|
8
|
+
* JSON string array on stdin; it decodes that list, installs it as
|
|
9
|
+
* `process.argv` and then loads the engine's own bin in this same process.
|
|
10
|
+
*
|
|
11
|
+
* It exists to keep file lists off OS command lines: a hook selecting thousands
|
|
12
|
+
* of files would otherwise overflow `CreateProcessW` (32,767 chars) or cmd.exe
|
|
13
|
+
* (8,191) and have to be split into batches, each one starting another lint
|
|
14
|
+
* stack. The command line that launches this process has the same two
|
|
15
|
+
* arguments however many files the run lints.
|
|
16
|
+
*
|
|
17
|
+
* Kept free of imports beyond Node's own so it loads in milliseconds and
|
|
18
|
+
* nothing it pulls in can observe the placeholder `process.argv`.
|
|
19
|
+
*/
|
|
20
|
+
/** The exit code for a launch this bootstrap could not complete. */
|
|
21
|
+
const BOOTSTRAP_FAILURE = 2;
|
|
22
|
+
/**
|
|
23
|
+
* Read stdin to its end. Asynchronous rather than `readFileSync(0)`, which can
|
|
24
|
+
* fail with `EAGAIN` on a non-blocking pipe before the writer has finished.
|
|
25
|
+
*
|
|
26
|
+
* @returns The bytes stdin carried.
|
|
27
|
+
*/
|
|
28
|
+
async function readStdin() {
|
|
29
|
+
const chunks = [];
|
|
30
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), "utf8"));
|
|
31
|
+
return Buffer.concat(chunks);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether a decoded payload is an array of strings. Restated rather than
|
|
35
|
+
* imported from the package's guards, so this entry stays dependency-free.
|
|
36
|
+
*
|
|
37
|
+
* @param value - The decoded payload.
|
|
38
|
+
* @returns Whether every element is a string.
|
|
39
|
+
*/
|
|
40
|
+
function isStringArray(value) {
|
|
41
|
+
return Array.isArray(value) && value.every((element) => typeof element === "string");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Decode the engine argument list the runner streamed in.
|
|
45
|
+
*
|
|
46
|
+
* @param bytes - The raw stdin payload.
|
|
47
|
+
* @returns The arguments, verbatim.
|
|
48
|
+
*/
|
|
49
|
+
function decodeArguments(bytes) {
|
|
50
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
51
|
+
const parsed = JSON.parse(decoder.decode(bytes));
|
|
52
|
+
if (!isStringArray(parsed)) throw new TypeError("expected a JSON array of argument strings on stdin");
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read and validate everything the launch needs. Only this phase is the
|
|
57
|
+
* bootstrap's own: its failures are reported as such and exit 2.
|
|
58
|
+
*
|
|
59
|
+
* @returns The launch.
|
|
60
|
+
* @rejects {Error} When the usage, stdin or argument payload is invalid.
|
|
61
|
+
*/
|
|
62
|
+
async function prepare() {
|
|
63
|
+
const [bin, ...extra] = process.argv.slice(2);
|
|
64
|
+
if (bin === void 0 || bin.length === 0 || extra.length > 0) throw new Error("usage: node bootstrap <engine bin> (arguments as JSON on stdin)");
|
|
65
|
+
return {
|
|
66
|
+
args: decodeArguments(await readStdin()),
|
|
67
|
+
bin
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Prepare the launch, then become the engine.
|
|
72
|
+
*
|
|
73
|
+
* The engine import stays outside the catch around {@link prepare}: its
|
|
74
|
+
* failures are left to Node's default handling, so they surface (stack and
|
|
75
|
+
* exit code included) exactly as they would from a direct `node <bin>` launch.
|
|
76
|
+
*
|
|
77
|
+
* @rejects {Error} Whatever the engine's own module evaluation throws.
|
|
78
|
+
*/
|
|
79
|
+
async function main() {
|
|
80
|
+
let launch;
|
|
81
|
+
try {
|
|
82
|
+
launch = await prepare();
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
85
|
+
process.stderr.write(`isentinel-lint: could not start the engine: ${message}\n`);
|
|
86
|
+
process.exitCode = BOOTSTRAP_FAILURE;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
process.argv = [
|
|
90
|
+
process.execPath,
|
|
91
|
+
launch.bin,
|
|
92
|
+
...launch.args
|
|
93
|
+
];
|
|
94
|
+
await import(pathToFileURL(launch.bin).href);
|
|
95
|
+
}
|
|
96
|
+
main();
|
|
97
|
+
//#endregion
|
|
98
|
+
export {};
|