@rslint/core 0.5.3 → 0.6.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.
Files changed (74) hide show
  1. package/bin/rslint.cjs +21 -4
  2. package/dist/0~engine.js +406 -0
  3. package/dist/34.js +33 -0
  4. package/dist/browser.d.ts +52 -39
  5. package/dist/browser.js +42 -74
  6. package/dist/cli.d.ts +3 -2
  7. package/dist/cli.js +1051 -93
  8. package/dist/config-loader.d.ts +45 -14
  9. package/dist/config-loader.js +95 -59
  10. package/dist/eslint-plugin/612.js +43 -0
  11. package/dist/eslint-plugin/index.d.ts +892 -0
  12. package/dist/eslint-plugin/index.js +26692 -0
  13. package/dist/eslint-plugin/lint-worker.js +26225 -0
  14. package/dist/eslint-plugin/types.d.ts +23 -0
  15. package/dist/eslint-plugin/types.js +1 -0
  16. package/dist/index.d.ts +626 -19
  17. package/dist/index.js +598 -15
  18. package/dist/service.d.ts +360 -30
  19. package/dist/service.js +19 -34
  20. package/package.json +27 -11
  21. package/dist/browser.d.ts.map +0 -1
  22. package/dist/cli.d.ts.map +0 -1
  23. package/dist/config-loader.d.ts.map +0 -1
  24. package/dist/configs/import.d.ts +0 -6
  25. package/dist/configs/import.d.ts.map +0 -1
  26. package/dist/configs/import.js +0 -7
  27. package/dist/configs/index.d.ts +0 -47
  28. package/dist/configs/index.d.ts.map +0 -1
  29. package/dist/configs/index.js +0 -36
  30. package/dist/configs/javascript.d.ts +0 -6
  31. package/dist/configs/javascript.d.ts.map +0 -1
  32. package/dist/configs/javascript.js +0 -72
  33. package/dist/configs/jest.d.ts +0 -7
  34. package/dist/configs/jest.d.ts.map +0 -1
  35. package/dist/configs/jest.js +0 -35
  36. package/dist/configs/jsx-a11y.d.ts +0 -6
  37. package/dist/configs/jsx-a11y.d.ts.map +0 -1
  38. package/dist/configs/jsx-a11y.js +0 -135
  39. package/dist/configs/promise.d.ts +0 -6
  40. package/dist/configs/promise.d.ts.map +0 -1
  41. package/dist/configs/promise.js +0 -20
  42. package/dist/configs/react-hooks.d.ts +0 -6
  43. package/dist/configs/react-hooks.d.ts.map +0 -1
  44. package/dist/configs/react-hooks.js +0 -24
  45. package/dist/configs/react.d.ts +0 -6
  46. package/dist/configs/react.d.ts.map +0 -1
  47. package/dist/configs/react.js +0 -31
  48. package/dist/configs/typescript.d.ts +0 -9
  49. package/dist/configs/typescript.d.ts.map +0 -1
  50. package/dist/configs/typescript.js +0 -122
  51. package/dist/configs/unicorn.d.ts +0 -8
  52. package/dist/configs/unicorn.d.ts.map +0 -1
  53. package/dist/configs/unicorn.js +0 -161
  54. package/dist/define-config.d.ts +0 -110
  55. package/dist/define-config.d.ts.map +0 -1
  56. package/dist/define-config.js +0 -6
  57. package/dist/index.d.ts.map +0 -1
  58. package/dist/node.d.ts +0 -31
  59. package/dist/node.d.ts.map +0 -1
  60. package/dist/node.js +0 -116
  61. package/dist/service.d.ts.map +0 -1
  62. package/dist/tsconfig.build.tsbuildinfo +0 -1
  63. package/dist/types.d.ts +0 -342
  64. package/dist/types.d.ts.map +0 -1
  65. package/dist/types.js +0 -1
  66. package/dist/utils/args.d.ts +0 -19
  67. package/dist/utils/args.d.ts.map +0 -1
  68. package/dist/utils/args.js +0 -101
  69. package/dist/utils/config-discovery.d.ts +0 -47
  70. package/dist/utils/config-discovery.d.ts.map +0 -1
  71. package/dist/utils/config-discovery.js +0 -238
  72. package/dist/worker.d.ts +0 -2
  73. package/dist/worker.d.ts.map +0 -1
  74. package/dist/worker.js +0 -114
@@ -0,0 +1,892 @@
1
+ /**
2
+ * Project the runner's `LintFileResult[]` into the exact wire shape Go
3
+ * decodes. Both host paths use this so Go receives a byte-stable set of
4
+ * fields.
5
+ */
6
+ export declare function buildPluginLintResult(results: LintFileResult[]): EslintPluginLintResult;
7
+
8
+ /**
9
+ * Build per-file {@link LintTask}s from an EslintPluginLintRequest. Each
10
+ * task carries the file's `configKey` verbatim; the worker uses it to
11
+ * pick the right `LoadedPlugins` from its per-config map.
12
+ *
13
+ * If a file's `configKey` is empty OR missing from `configDirSet`, we
14
+ * still emit a task (with the empty/unknown key) and let the worker
15
+ * report the failure via `parseError` — keeping wire-format consistency.
16
+ */
17
+ export declare function buildPluginLintTasks(input: EslintPluginLintRequest, options: BuildPluginLintTasksOptions): LintTask[];
18
+
19
+ export declare interface BuildPluginLintTasksOptions {
20
+ /**
21
+ * Set of `configDirectory` strings the worker pool was initialized
22
+ * with. Used purely to detect "unknown configKey on the wire" — the
23
+ * host's invariant is that every file's `configKey` was previously
24
+ * declared in `WorkerPoolOptions.configs[]`. A miss is an internal
25
+ * bug surfaced through {@link onUnknownConfigKey}.
26
+ */
27
+ configDirSet: ReadonlySet<string>;
28
+ /**
29
+ * Invoked once per file whose `configKey` is not in `configDirSet`.
30
+ * The helper otherwise forwards `configKey` verbatim — the worker
31
+ * reports the failure via `parseError`.
32
+ */
33
+ onUnknownConfigKey?: (filePath: string, configKey: string) => void;
34
+ }
35
+
36
+ declare interface Comment {
37
+ type: 'Line' | 'Block' | 'Shebang';
38
+ value: string;
39
+ range: [number, number];
40
+ loc: SourceLocation;
41
+ }
42
+
43
+ /**
44
+ * Types shared between the eslint-plugin host and its lint workers.
45
+ *
46
+ * Wire-format / IPC frame types (the Go↔Node frame contract) live in
47
+ * `src/ipc/protocol.ts` — the single source the CLI host consumes.
48
+ */
49
+ /**
50
+ * Per-config descriptor handed to the worker pool. Each worker imports
51
+ * every descriptor's `configPath` once at init, then routes per-file
52
+ * lint tasks via `configKey === configDirectory` to the right plugin
53
+ * instances. The `configDirectory` here MUST match the value Go writes
54
+ * into `EslintPluginLintFile.ConfigKey` byte-for-byte; the worker uses
55
+ * it as a Map key for per-file dispatch.
56
+ */
57
+ export declare interface ConfigDescriptor {
58
+ /** Absolute filesystem path of the rslint config file (`rslint.config.{js,mjs,ts,mts}`). */
59
+ configPath: string;
60
+ /** Absolute filesystem path of the directory holding the config file.
61
+ * Matches the `ConfigKey` Go emits per file during plugin-lint dispatch. */
62
+ configDirectory: string;
63
+ }
64
+
65
+ /**
66
+ * Build a {@link PluginLintHost} over a freshly-initialized WorkerPool.
67
+ *
68
+ * `configs` empty ⇒ the pool spawns no workers (no-op fast path); a
69
+ * `lint` call then returns empty per-file results. Init rejects if a
70
+ * referenced plugin fails to import — the caller decides how loud to be
71
+ * (CLI fails the run; LSP logs and serves empty).
72
+ */
73
+ export declare function createPluginLintHost(configs: ConfigDescriptor[], onLog?: WorkerPoolOptions['onLog'], singleThreaded?: boolean): Promise<PluginLintHost>;
74
+
75
+ /** A single diagnostic emitted by a rule call to `context.report`. */
76
+ export declare interface Diagnostic {
77
+ ruleName: string;
78
+ messageId?: string;
79
+ message: string;
80
+ /**
81
+ * Position offsets into the source text. INSIDE the runner these
82
+ * are UTF-16 code-unit indices (matching native JS string
83
+ * indexing). On the IPC boundary in `ecma-language-plugin.ts` they
84
+ * are converted to UTF-8 BYTE offsets before shipping to Go — Go's
85
+ * `scanner.GetECMALineAndUTF16CharacterOfPosition` takes bytes.
86
+ * Callers that drain diagnostics via the public lintFile path
87
+ * therefore observe byte offsets; the in-process runner code paths
88
+ * still work in UTF-16 units.
89
+ */
90
+ startPos: number;
91
+ endPos: number;
92
+ fixes?: Fix[];
93
+ suggestions?: SuggestionDescriptor[];
94
+ }
95
+
96
+ /**
97
+ * Wire-format lint request as it leaves Go and arrives at the host.
98
+ *
99
+ * Mirrors Go's `internal/linter.EslintPluginLintRequest`. Field types
100
+ * are permissive (`unknown` for opaque pass-through fields) so the host
101
+ * doesn't need to re-validate Go's serialization.
102
+ */
103
+ export declare interface EslintPluginLintRequest {
104
+ files: ReadonlyArray<{
105
+ path: string;
106
+ /**
107
+ * Optional file content override. The CLI host leaves it absent —
108
+ * the worker reads from disk via `readFileSync` (and re-reads
109
+ * post-fix content across `--fix` passes). The LSP host sends it so
110
+ * an unsaved editor buffer's overlay text is linted instead of the
111
+ * stale on-disk copy. Also used by in-process test harnesses.
112
+ */
113
+ text?: string;
114
+ /**
115
+ * Per-file `languageOptions`, computed by Go via `GetConfigForFile`
116
+ * (flat-config files-glob match + deep merge). Opaque here; the
117
+ * worker reads `sourceType`/`globals`/`parserOptions.ecmaFeatures`.
118
+ */
119
+ languageOptions?: unknown;
120
+ settings?: Record<string, unknown>;
121
+ /**
122
+ * The owning config's directory in the SAME form the host used as
123
+ * its `ConfigDescriptor.configDirectory` (CLI: fs path; LSP: URI).
124
+ * The worker uses it to pick the right `LoadedPlugins`. Empty when
125
+ * no JS config governs the file.
126
+ */
127
+ configKey?: string;
128
+ }>;
129
+ rules?: Record<string, {
130
+ options?: readonly unknown[];
131
+ }>;
132
+ /** Collect autofixes (driven by Go's `--fix`). */
133
+ fix?: boolean;
134
+ suggestionsMode?: 'off' | 'eager';
135
+ }
136
+
137
+ /**
138
+ * Wire-format lint result, returned to Go. Mirrors Go's
139
+ * `internal/linter.EslintPluginFileResult`. Pure projection — drops the
140
+ * runner-internal aggregate fields (`fixes`, `suggestionsCount`) that
141
+ * Go doesn't decode.
142
+ */
143
+ export declare interface EslintPluginLintResult {
144
+ results: Array<{
145
+ filePath: string;
146
+ diagnostics: unknown[];
147
+ parseError?: string;
148
+ cancelled?: boolean;
149
+ ruleErrors?: Array<{
150
+ rule: string;
151
+ message: string;
152
+ }>;
153
+ }>;
154
+ }
155
+
156
+ /** Compact node shape — what plugin code reads through `context.sourceCode`. */
157
+ export declare interface ESTreeNode {
158
+ type: string;
159
+ range: [number, number];
160
+ loc: SourceLocation;
161
+ parent?: ESTreeNode;
162
+ [key: string]: unknown;
163
+ }
164
+
165
+ /**
166
+ * The `fixer` object passed to ESLint plugin code via `descriptor.fix(fixer)`
167
+ * (and `descriptor.suggest[i].fix(fixer)`). Plugins call methods on this
168
+ * object to express their intended edit; each method returns a `Fix`
169
+ * record that the runner ships back to Go for application.
170
+ *
171
+ * The full ESLint fixer surface is 8 methods. Most plugins use the
172
+ * insert/remove variants, not just `replaceText`, so leaving any out
173
+ * breaks real plugins.
174
+ */
175
+ /**
176
+ * A single fix edit. Mirrors ESLint's `Fix` interface: byte range +
177
+ * replacement text. Empty text + non-empty range = remove. Empty range
178
+ * (start === end) + non-empty text = insert.
179
+ */
180
+ declare interface Fix {
181
+ /**
182
+ * Offset range `[start, end)` into the source text.
183
+ *
184
+ * INSIDE the runner these are UTF-16 code-unit indices (matching
185
+ * native JS string indexing). On the IPC boundary they get
186
+ * converted to UTF-8 BYTE offsets — Go's TextRange consumes bytes.
187
+ * The conversion lives in `ecma-language-plugin.ts` next to the
188
+ * diagnostic drain; do not double-convert from rule code.
189
+ */
190
+ range: [number, number];
191
+ /** Replacement text (may be empty for remove). */
192
+ text: string;
193
+ }
194
+
195
+ /** Minimal node shape — the fixer only needs `range`. */
196
+ declare interface HasRange {
197
+ range: [number, number];
198
+ }
199
+
200
+ /**
201
+ * The forwarded subset of ESLint flat-config `languageOptions` exposed
202
+ * to plugin rules as `context.languageOptions`. Plugin rules read it
203
+ * to branch on `languageOptions.parserOptions.ecmaFeatures.jsx`,
204
+ * `parserOptions.sourceType`, or `languageOptions.globals`.
205
+ *
206
+ * Field set matches what the wire payload from Go carries in each file's
207
+ * `languageOptions` (computed by Go via `GetConfigForFile`); out-of-band
208
+ * fields the runner doesn't reproduce (custom `parser`,
209
+ * `parserOptions.project`, `parserOptions.tsconfigRootDir`) are
210
+ * intentionally absent. This is the contract the linter package and the
211
+ * plugin runtime share — any change here must update both
212
+ * `ecma-language-plugin.ts:LintFileRequest` and Go's
213
+ * `EslintPluginLintFile.LanguageOptions`.
214
+ */
215
+ declare interface LanguageOptions {
216
+ /**
217
+ * ECMAScript version target — top-level in ESLint v10. Plugin rules
218
+ * read this as `ctx.languageOptions.ecmaVersion`. The v8-era
219
+ * `parserOptions.ecmaVersion` nesting is intentionally NOT mirrored
220
+ * here: rslint targets v10 cleanly, and we don't expose this field
221
+ * to user config, so there is no legacy v8 surface to preserve.
222
+ */
223
+ ecmaVersion?: number | 'latest';
224
+ /** Module / script / commonjs — top-level in ESLint v10. Same rationale as `ecmaVersion`. */
225
+ sourceType?: 'module' | 'script' | 'commonjs';
226
+ globals?: Record<string, 'readonly' | 'writable' | 'off'>;
227
+ /**
228
+ * Parser-specific extras. Only `ecmaFeatures` lives here in v10
229
+ * (rules that gate on `jsx` / `globalReturn` / `impliedStrict` read
230
+ * via `ctx.languageOptions.parserOptions.ecmaFeatures.*`). Custom
231
+ * `parser` instances aren't supported by the runner, so we don't
232
+ * include that field.
233
+ */
234
+ parserOptions?: {
235
+ ecmaFeatures?: {
236
+ jsx?: boolean;
237
+ globalReturn?: boolean;
238
+ impliedStrict?: boolean;
239
+ };
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Lint a single file. Stateless — `loadedPlugins` is owned by the
245
+ * Worker; this function does not mutate it.
246
+ */
247
+ export declare function lintFile(req: LintFileRequest, loadedPlugins: LoadedPlugins): LintFileResult;
248
+
249
+ /** Per-file lint request input (called by Worker dispatcher per task).
250
+ *
251
+ * The Worker reads source text from disk via `fs.readFileSync(filePath)`
252
+ * by default — text is intentionally NOT carried over IPC. This drops
253
+ * the structuredClone cost of shipping every file's contents across
254
+ * the worker_threads boundary (~60 MB on a 5000-file repo).
255
+ *
256
+ * Multi-pass --fix coherence is preserved because cmd/rslint's
257
+ * applyFixPass writes fixes to disk BEFORE re-dispatching the next
258
+ * lint pass — the worker reads the post-fix contents.
259
+ *
260
+ * `text` here is an in-process override for unit tests that want to
261
+ * exercise `lintFile` against an in-memory source. The wire shape
262
+ * (engine.ts → worker postMessage) NEVER carries text.
263
+ */
264
+ export declare interface LintFileRequest {
265
+ filePath: string;
266
+ /** In-process override for tests. The IPC wire shape never carries this. */
267
+ text?: string;
268
+ /**
269
+ * Forwarded subset of user `languageOptions`. Only the fields the
270
+ * runner actually consumes are typed here; the rest of the user's
271
+ * `languageOptions` (parser custom hooks, plugin-specific extensions)
272
+ * is intentionally dropped at the IPC boundary because the worker
273
+ * doesn't reproduce ESLint's full language plugin pipeline.
274
+ *
275
+ * Per ESLint v10's flat-config spec
276
+ * (https://eslint.org/docs/v10.x/use/configure/language-options),
277
+ * `ecmaVersion` / `sourceType` / `globals` are TOP-LEVEL properties
278
+ * of `languageOptions`. `parserOptions` is reserved for parser-
279
+ * specific extras (`ecmaFeatures`, `allowReserved`). The v8-era
280
+ * positions (`parserOptions.ecmaVersion` / `parserOptions.sourceType`)
281
+ * are NOT accepted — rslint targets v10 cleanly.
282
+ */
283
+ languageOptions?: {
284
+ ecmaVersion?: number | 'latest';
285
+ sourceType?: 'module' | 'script' | 'commonjs';
286
+ globals?: Record<string, 'readonly' | 'writable' | 'off'>;
287
+ parserOptions?: {
288
+ ecmaFeatures?: {
289
+ jsx?: boolean;
290
+ globalReturn?: boolean;
291
+ impliedStrict?: boolean;
292
+ };
293
+ };
294
+ };
295
+ /** Merged flat-config `settings` for plugin consumption (e.g. `react.version`). */
296
+ settings?: Record<string, unknown>;
297
+ /** Map of fully-qualified rule name → config. Already enabled-filtered. */
298
+ rules: Record<string, RuleConfig>;
299
+ /**
300
+ * Whether to materialise plugin `descriptor.fix(fixer)` into the
301
+ * diagnostic's `fixes` payload. The runner never APPLIES fixes —
302
+ * application is the caller's job (CLI fix-loop, LSP code-action /
303
+ * fixAll). CLI sets this whenever `--fix` is on; LSP always sets it so
304
+ * Quick Fix / source.fixAll see plugin-rule fixes the same way they
305
+ * see native-rule ones.
306
+ */
307
+ collectFixes: boolean;
308
+ suggestionsMode: SuggestionsMode;
309
+ /** Optional Int32Array(SharedArrayBuffer) cancel flag, length-1, for per-node Atomics polling. */
310
+ cancelFlag?: Int32Array;
311
+ /**
312
+ * Identity of the rslint config that owns THIS file — the
313
+ * `configDirectory` Go writes into `EslintPluginLintFile.ConfigKey`. The
314
+ * worker uses this to pick the right `LoadedPlugins` from its
315
+ * per-config map (`Map<configDirectory, LoadedPlugins>`).
316
+ *
317
+ * The `lintFile` pipeline never reads this field directly — the
318
+ * worker has already selected `LoadedPlugins` by the time `lintFile`
319
+ * runs. It exists on the request only so the worker dispatcher can
320
+ * route the task before delegating.
321
+ */
322
+ configKey?: string;
323
+ }
324
+
325
+ /** Per-file lint response — one record per LintFileRequest. */
326
+ export declare interface LintFileResult {
327
+ filePath: string;
328
+ diagnostics: Diagnostic[];
329
+ /** Aggregated fixes (sum of diagnostic.fixes for ApplyRuleFixes). For convenience; same data as diagnostics[].fixes. */
330
+ fixes: Fix[];
331
+ /** Aggregated suggestions, same convenience as fixes. */
332
+ suggestionsCount: number;
333
+ /** True iff the visit was cancelled mid-flight (cancelFlag observed). */
334
+ cancelled: boolean;
335
+ /** Set when the native parser failed; diagnostics is empty in this case. */
336
+ parseError?: string;
337
+ /** Per-rule errors caught during create() / listener execution. */
338
+ ruleErrors?: Array<{
339
+ rule: string;
340
+ message: string;
341
+ }>;
342
+ }
343
+
344
+ /** A single task as the pool sees it; equivalent to LintFileRequest minus cancelFlag (pool injects). */
345
+ export declare type LintTask = Omit<LintFileRequest, 'cancelFlag'>;
346
+
347
+ /**
348
+ * Loaded plugin shape: only the fields the runner consumes. Plugins may
349
+ * carry far more (configs, processors, etc.) — we keep references intact
350
+ * for downstream needs but type only what we use.
351
+ */
352
+ declare interface LoadedPlugin {
353
+ prefix: string;
354
+ /** The unwrapped plugin module, ready for `plugin.rules['ruleName']`. */
355
+ plugin: {
356
+ meta?: {
357
+ name?: string;
358
+ version?: string;
359
+ };
360
+ name?: string;
361
+ rules?: Record<string, unknown>;
362
+ configs?: Record<string, unknown>;
363
+ [key: string]: unknown;
364
+ };
365
+ }
366
+
367
+ /**
368
+ * Loaded plugin set for a single rslint config. `rules` is keyed by
369
+ * `<prefix>/<ruleName>` and is the only lookup `lintFile` consults
370
+ * — the worker has already picked the right `LoadedPlugins` for this
371
+ * file via its `configKey` map before calling `lintFile`, so there's
372
+ * no cross-config prefix collision to worry about.
373
+ */
374
+ export declare interface LoadedPlugins {
375
+ plugins: LoadedPlugin[];
376
+ rules: Map<string, unknown>;
377
+ }
378
+
379
+ /**
380
+ * Load every plugin in `entries` and return them keyed by prefix, with a
381
+ * Import the user's rslint config file directly and extract plugin
382
+ * instances from its object-form `plugins` map(s). Each worker calls
383
+ * this through {@link loadPluginsFromConfigs}
384
+ * once per assigned config at init.
385
+ *
386
+ * Each worker independently imports the config (and transitively its
387
+ * plugins), naturally anchoring `node_modules` walks at the config's
388
+ * own location — which is what makes monorepo setups Just Work: a
389
+ * sub-package config gets its sub-package's node_modules, not the
390
+ * root's.
391
+ *
392
+ * @throws PluginLoaderError on config-import failure or a Node version
393
+ * too old to satisfy {@link MIN_NODE_MAJOR}.
394
+ */
395
+ export declare function loadPluginsFromConfigFile(configFilePath: string): Promise<LoadedPlugins>;
396
+
397
+ /**
398
+ * Import every config in `configs` and return a map keyed by each
399
+ * config's directory. This is the worker-side entry point for the new
400
+ * config-loads-in-worker flow: each worker calls this once at init,
401
+ * caches the result for its entire lifetime, and per-file lint tasks
402
+ * pick the right `LoadedPlugins` via `request.configKey === configDirectory`.
403
+ *
404
+ * Fail-fast on the first config import failure — same contract as the
405
+ * old `loadPlugins(entries, baseUrl)` path. Surfacing a partial success
406
+ * silently degrades lint quality across the workspace (files under the
407
+ * failing config get no plugin rules), so we prefer a clean, loud
408
+ * failure that the user can fix and retry.
409
+ */
410
+ export declare function loadPluginsFromConfigs(configs: readonly ConfigDescriptor[]): Promise<Map<string, LoadedPlugins>>;
411
+
412
+ /**
413
+ * Normalize native parser output to ESLint-shape ESTree in a single DFS:
414
+ * 1. add `range` + `loc` to every node (parser offsets are UTF-16 code units);
415
+ * 2. add `parent` references so rules can walk up the tree;
416
+ * 3. apply the TS-specific shape fixes scope-manager / TS plugins expect.
417
+ *
418
+ * Mutates the AST in place; the parser returns a fresh tree per parse.
419
+ */
420
+ declare interface LocPosition {
421
+ line: number;
422
+ column: number;
423
+ }
424
+
425
+ export declare interface PluginLintHost {
426
+ /**
427
+ * Run one reverse batch: build per-file tasks, lint, project results. An
428
+ * optional AbortSignal cancels the dispatched worker tasks — the LSP path
429
+ * wires it to a superseding keystroke / document close so the worker stops
430
+ * instead of running to completion.
431
+ */
432
+ lint(req: EslintPluginLintRequest, signal?: AbortSignal): Promise<EslintPluginLintResult>;
433
+ /** Drain in-flight tasks and terminate the worker pool. Idempotent. */
434
+ shutdown(): Promise<void>;
435
+ }
436
+
437
+ declare interface ReportDescriptor {
438
+ node?: ESTreeNode;
439
+ loc?: ReportLoc;
440
+ message?: string;
441
+ messageId?: string;
442
+ data?: Record<string, string | number>;
443
+ fix?: (fixer: RuleFixer) => Fix | Fix[] | null | undefined | Iterable<Fix>;
444
+ suggest?: SuggestionInput[];
445
+ }
446
+
447
+ /**
448
+ * Modern descriptor form: `context.report({ node | loc, message |
449
+ * messageId, data?, fix?, suggest? })`. This is the recommended form.
450
+ *
451
+ * For the legacy positional form ESLint still accepts —
452
+ * `report(node, message, data?, fix?)` or
453
+ * `report(node, loc, message, data?, fix?)` — see
454
+ * `normalizeReportArgs` below. ESLint's
455
+ * `lib/linter/file-report.js`'s `normalizeMultiArgReportCall` is our
456
+ * reference implementation.
457
+ */
458
+ /**
459
+ * `loc` shape accepted on a report descriptor. Wider than the AST's
460
+ * own {@link SourceLocation} on purpose: ESLint's `context.report({ loc })`
461
+ * permits any of:
462
+ *
463
+ * - `{ line, column }` — a single position (treated as zero-width)
464
+ * - `{ start, end }` — a full range
465
+ * - `{ start }` — partial range; `end` defaults to `start`
466
+ *
467
+ * The third form is what real plugins write when reporting at a single
468
+ * point but using the `{start: ...}` shape they already build for ranges
469
+ * elsewhere. ESLint v9's `lib/linter/file-report.js` accepts it; rslint
470
+ * matches.
471
+ */
472
+ declare type ReportLoc = LocPosition | {
473
+ start: LocPosition;
474
+ end?: LocPosition;
475
+ };
476
+
477
+ /** Per-rule configuration as it reaches the Worker. Already filtered to enabled rules; severity is reattached Go-side. */
478
+ export declare interface RuleConfig {
479
+ /** Rule options — typically [optionsObject] or [], pre-schema-defaults. */
480
+ options: readonly unknown[];
481
+ /**
482
+ * `rule.meta` is opaque to the runner except for:
483
+ * - schema → applyOptionDefaults
484
+ * - messages → context.report messageId lookup
485
+ * - fixable → currently informational only; collectFixes gating is
486
+ * done at the lintBatch level (internal/linter)
487
+ */
488
+ meta?: {
489
+ schema?: unknown;
490
+ messages?: Record<string, string>;
491
+ fixable?: 'code' | 'whitespace';
492
+ hasSuggestions?: boolean;
493
+ };
494
+ }
495
+
496
+ /**
497
+ * The shape of the context object passed to `rule.create(ctx)`. ESLint's
498
+ * actual type has many more fields; we expose what real plugins read.
499
+ *
500
+ * Surface tracks **ESLint v10** exactly — empirically pinned against
501
+ * `eslint@10.x` so any v10 plugin reads the same property set whether
502
+ * it runs under ESLint or rslint.
503
+ */
504
+ export declare interface RuleContext {
505
+ id: string;
506
+ options: readonly unknown[];
507
+ settings: Record<string, unknown>;
508
+ /**
509
+ * The full forwarded `languageOptions` object. Always present (never
510
+ * undefined) — when the user's config didn't set anything, nested
511
+ * fields stay undefined but the wrapper itself is here so a rule's
512
+ * `ctx.languageOptions.globals` access doesn't crash.
513
+ */
514
+ languageOptions: LanguageOptions;
515
+ filename: string;
516
+ /**
517
+ * Physical disk path of the file. For non-processor files this equals
518
+ * `filename`. Distinct from `filename` only when ESLint applies a
519
+ * processor that yields virtual sub-files; the runner doesn't run
520
+ * processors today, so they're equal.
521
+ */
522
+ physicalFilename: string;
523
+ cwd: string;
524
+ sourceCode: SourceCode;
525
+ report(descriptor: ReportDescriptor): void;
526
+ /** @internal */ _drainDiagnostics(): Diagnostic[];
527
+ }
528
+
529
+ /**
530
+ * The fixer object plugins call into. Eight methods, one record per call.
531
+ * Stateless — each method returns a fresh Fix; the caller (RuleContext's
532
+ * `report`) collects them into the diagnostic.
533
+ */
534
+ declare interface RuleFixer {
535
+ replaceText(node: HasRange, text: string): Fix;
536
+ replaceTextRange(range: [number, number], text: string): Fix;
537
+ insertTextBefore(node: HasRange, text: string): Fix;
538
+ insertTextBeforeRange(range: [number, number], text: string): Fix;
539
+ insertTextAfter(node: HasRange, text: string): Fix;
540
+ insertTextAfterRange(range: [number, number], text: string): Fix;
541
+ remove(node: HasRange): Fix;
542
+ removeRange(range: [number, number]): Fix;
543
+ }
544
+
545
+ declare interface SourceCode {
546
+ text: string;
547
+ ast: ESTreeNode;
548
+ lines: string[];
549
+ hasBOM: boolean;
550
+ scopeManager: unknown;
551
+ /**
552
+ * ESTree visitor keys map (`{ NodeType: [...childKeys] }`). v10
553
+ * exposes this so rules can do their own traversal without pulling
554
+ * `eslint-visitor-keys`. rslint ships the standard ESTree key set;
555
+ * the runner doesn't yet support custom parsers, so this is static.
556
+ */
557
+ visitorKeys: Record<string, readonly string[]>;
558
+ /**
559
+ * Parser-supplied services. Empty `{}` in plain JS (matches v10).
560
+ * Real ESLint would populate this with the TS parser's `program`,
561
+ * `esTreeNodeToTSNodeMap`, etc.; the runner doesn't proxy ts-go
562
+ * type info through, so plugin rules that need TS types should
563
+ * guard via `if (!services.program) return {}`.
564
+ */
565
+ parserServices: Record<string, unknown>;
566
+ getText(node?: ESTreeNode, beforeCount?: number, afterCount?: number): string;
567
+ getLines(): string[];
568
+ getLocFromIndex(index: number): LocPosition;
569
+ getIndexFromLoc(loc: LocPosition): number;
570
+ getRange(node: ESTreeNode): [number, number];
571
+ getLoc(node: ESTreeNode): SourceLocation;
572
+ getAncestors(node: ESTreeNode): ESTreeNode[];
573
+ getNodeByRangeIndex(index: number): ESTreeNode | null;
574
+ getTokenBefore(node: ESTreeNode, opts?: TokenSkipOpts): Token | null;
575
+ getTokenAfter(node: ESTreeNode, opts?: TokenSkipOpts): Token | null;
576
+ getFirstToken(node: ESTreeNode, opts?: TokenSkipOpts): Token | null;
577
+ getLastToken(node: ESTreeNode, opts?: TokenSkipOpts): Token | null;
578
+ getTokens(node: ESTreeNode, opts?: TokenFilterOpts, afterCount?: number): Token[];
579
+ getTokensBetween(left: ESTreeNode, right: ESTreeNode, opts?: TokenFilterOpts): Token[];
580
+ getFirstTokenBetween(left: ESTreeNode, right: ESTreeNode, opts?: TokenSkipOpts): Token | null;
581
+ getFirstTokensBetween(left: ESTreeNode, right: ESTreeNode, opts?: TokenCountOpts): Token[];
582
+ getLastTokenBetween(left: ESTreeNode, right: ESTreeNode, opts?: TokenSkipOpts): Token | null;
583
+ getLastTokensBetween(left: ESTreeNode, right: ESTreeNode, opts?: TokenCountOpts): Token[];
584
+ getFirstTokens(node: ESTreeNode, opts?: TokenCountOpts): Token[];
585
+ getLastTokens(node: ESTreeNode, opts?: TokenCountOpts): Token[];
586
+ getTokensBefore(node: ESTreeNode, opts?: TokenCountOpts): Token[];
587
+ getTokensAfter(node: ESTreeNode, opts?: TokenCountOpts): Token[];
588
+ getTokenByRangeStart(start: number, opts?: {
589
+ includeComments?: boolean;
590
+ }): Token | null;
591
+ getCommentsBefore(node: ESTreeNode): Comment[];
592
+ getCommentsAfter(node: ESTreeNode): Comment[];
593
+ getCommentsInside(node: ESTreeNode): Comment[];
594
+ getAllComments(): Comment[];
595
+ /**
596
+ * All code tokens AND comments merged into one stream sorted by `range[0]`
597
+ * — ESLint's `SourceCode#tokensAndComments`. Stylistic whitespace rules
598
+ * (`comma-spacing`, `no-multi-spaces`, `indent`, `indent-binary-ops`,
599
+ * `space-in-parens`, ...) read this array directly.
600
+ */
601
+ readonly tokensAndComments: readonly Token[];
602
+ commentsExistBetween(left: ESTreeNode, right: ESTreeNode): boolean;
603
+ isSpaceBetween(left: ESTreeNode, right: ESTreeNode): boolean;
604
+ getScope(node?: ESTreeNode): unknown;
605
+ getDeclaredVariables(node: ESTreeNode): unknown[];
606
+ markVariableAsUsed(name: string, node?: ESTreeNode): boolean;
607
+ /**
608
+ * ESLint v9 `sourceCode.isGlobalReference(node)`. Returns true iff
609
+ * `node` is an Identifier referencing a variable that lives in the
610
+ * global scope AND has no in-source definition. Widely used by
611
+ * community rules (unicorn, ESLint built-ins). See impl for the
612
+ * exact semantics.
613
+ */
614
+ isGlobalReference(node: ESTreeNode): boolean;
615
+ getInlineConfigNodes(): Comment[];
616
+ /**
617
+ * ESLint v10's `sourceCode.getDisableDirectives()`. Returns the
618
+ * parsed `eslint-disable*` / `eslint-enable` directives in the file
619
+ * plus any parse problems (e.g. multi-line `eslint-disable-line`).
620
+ * Plugin rules like `unicorn/no-abusive-eslint-disable` consume it
621
+ * to report on directive shape.
622
+ */
623
+ getDisableDirectives(): {
624
+ problems: Array<{
625
+ ruleId: null;
626
+ message: string;
627
+ loc: {
628
+ start: LocPosition;
629
+ end: LocPosition;
630
+ };
631
+ }>;
632
+ directives: Array<{
633
+ type: 'disable' | 'enable' | 'disable-next-line' | 'disable-line';
634
+ node: Comment;
635
+ value: string;
636
+ justification: string;
637
+ }>;
638
+ };
639
+ }
640
+
641
+ declare interface SourceLocation {
642
+ start: LocPosition;
643
+ end: LocPosition;
644
+ }
645
+
646
+ /**
647
+ * Suggestion descriptor as returned to Go. `fixes` is null in `'off'`
648
+ * mode (we record the descriptor but didn't run `fix(fixer)`); a
649
+ * populated array in `'eager'` mode.
650
+ */
651
+ export declare interface SuggestionDescriptor {
652
+ messageId?: string;
653
+ desc?: string;
654
+ fixes: Fix[] | null;
655
+ }
656
+
657
+ declare interface SuggestionInput {
658
+ messageId?: string;
659
+ desc?: string;
660
+ data?: Record<string, string | number>;
661
+ fix: (fixer: RuleFixer) => Fix | Fix[] | null | undefined | Iterable<Fix>;
662
+ }
663
+
664
+ /** Mode controlling whether suggestion `fix(fixer)` is invoked at report time. */
665
+ export declare type SuggestionsMode = 'off' | 'eager';
666
+
667
+ declare interface Token {
668
+ type: TokenType;
669
+ value: string;
670
+ range: [number, number];
671
+ loc: SourceLocation;
672
+ /**
673
+ * Only on `RegularExpression` tokens — espree's shape. Plugins (`eslint-plugin-regexp`,
674
+ * core `no-invalid-regexp` / `prefer-regex-literals`) read `token.regex.{pattern,flags}`.
675
+ */
676
+ regex?: {
677
+ pattern: string;
678
+ flags: string;
679
+ };
680
+ }
681
+
682
+ /**
683
+ * Plural-token API options. Mirrors ESLint:
684
+ * - `number` → maximum number of tokens to return
685
+ * - `(t: Token) => boolean` → per-token predicate
686
+ * - object → `{ count?, filter?, includeComments? }`
687
+ *
688
+ * `includeComments: true` interleaves comments with code tokens in
689
+ * source order (the same order a single-pass lexer would emit them).
690
+ * Plugins use this to inspect formatting (e.g. whether a comment
691
+ * appears between two tokens).
692
+ */
693
+ declare type TokenCountOpts = number | ((t: Token) => boolean) | {
694
+ count?: number;
695
+ filter?: (t: Token) => boolean;
696
+ includeComments?: boolean;
697
+ };
698
+
699
+ declare type TokenFilterOpts = {
700
+ filter?: (t: Token) => boolean;
701
+ includeComments?: boolean;
702
+ } | ((t: Token) => boolean) | number;
703
+
704
+ /**
705
+ * Singular-token API options. Mirrors ESLint:
706
+ * - `number` → `skip` count (return the (skip+1)-th matching token)
707
+ * - `(t: Token) => boolean` → per-token predicate
708
+ * - object → `{ skip?, filter?, includeComments? }`
709
+ *
710
+ * The `skip` semantics caught us in the audit: `getFirstToken(node, 2)`
711
+ * does NOT mean "first 2 tokens" — it means "skip the first 2 matches
712
+ * and return the 3rd". Plugin code that uses bare numbers here is
713
+ * almost always doing skip; treating the number as anything else
714
+ * silently picks the wrong token. (Verified against ESLint:
715
+ * `getFirstToken('const x = 1;', 2)` returns `=`.)
716
+ */
717
+ declare type TokenSkipOpts = number | ((t: Token) => boolean) | {
718
+ skip?: number;
719
+ filter?: (t: Token) => boolean;
720
+ includeComments?: boolean;
721
+ };
722
+
723
+ declare type TokenType = 'Identifier' | 'PrivateIdentifier' | 'Keyword' | 'Punctuator' | 'String' | 'Numeric' | 'RegularExpression' | 'Template' | 'Boolean' | 'Null' | 'JSXIdentifier' | 'JSXText';
724
+
725
+ export declare class WorkerPool {
726
+ private readonly opts;
727
+ private readonly cancelPool;
728
+ private workers;
729
+ private nextTaskId;
730
+ private closed;
731
+ /** Pool-level backlog. `kickQueue` moves entries to idle workers
732
+ * one at a time. Each worker carries at most ONE inflight task —
733
+ * the cap exists so per-task timeouts only measure actual
734
+ * execution time, not "stuck behind 30 other tasks on the worker's
735
+ * postMessage queue" wait time. Otherwise a 100-file batch on 8
736
+ * workers makes the last ~12 tasks per worker race a 30 s timer
737
+ * before the worker even reaches them, terminating the worker and
738
+ * marking everything else inflight on that worker as `worker_crashed`. */
739
+ private pendingQueue;
740
+ /**
741
+ * In-flight respawn promises. A crashed worker's `'exit'` handler
742
+ * kicks off `spawnWorker(id)` asynchronously; `shutdown()` awaits
743
+ * these so it doesn't return while a freshly-spawned replacement
744
+ * thread is still booting (which would leave an orphan worker alive
745
+ * past `await pool.shutdown()`). Each promise resolves only after
746
+ * the replacement is either adopted or — when `closed` raced ahead
747
+ * — fully terminated.
748
+ */
749
+ private readonly respawns;
750
+ constructor(opts: WorkerPoolOptions);
751
+ /**
752
+ * Spawn workers and wait until all report 'ready'. Rejects on the
753
+ * first worker init failure — the entire pool is unusable if any
754
+ * plugin fails to load (the user's config references rules from a
755
+ * plugin that didn't import, so every subsequent lintBatch would
756
+ * surface "rule not found").
757
+ *
758
+ * `workerCount=0` is a no-op fast path used when there are zero
759
+ * ESLint plugin entries: the Go side never reaches the dispatcher
760
+ * (no rule has IsEslintPluginRule=true), so a real worker pool is
761
+ * wasted overhead. We still go through the IPC handshake — this lets
762
+ * the CLI keep ONE code path regardless of whether plugins are
763
+ * configured.
764
+ */
765
+ init(): Promise<void>;
766
+ /**
767
+ * Dispatch tasks round-robin to workers; resolve as a per-task result array.
768
+ *
769
+ * @param onTaskDispatched optional callback invoked synchronously with
770
+ * each task's internal taskId after the task has been fully tracked
771
+ * (cancelSlot acquired, `inflight` populated) but BEFORE the task
772
+ * is posted to the worker. Two use cases:
773
+ *
774
+ * 1. **ID bookkeeping** — callers that need a list of dispatched
775
+ * ids (e.g. the VS Code extension host mapping LSP
776
+ * `$/cancelRequest` reqId → taskId) collect them here.
777
+ *
778
+ * 2. **Cancel-before-start** — callers that have observed a
779
+ * cancel signal mid-dispatch can call `cancelTask(taskId)` from
780
+ * inside this callback. Because `inflight` is already populated,
781
+ * the lookup succeeds and the SAB cancel flag is set BEFORE
782
+ * postMessage delivers the task. The worker sees flag=1 on its
783
+ * first poll and bails immediately without running any rule.
784
+ *
785
+ * The callback runs before any await, so the caller's bookkeeping
786
+ * is guaranteed populated before any task can complete.
787
+ */
788
+ lintBatch(tasks: LintTask[], onTaskDispatched?: (taskId: number) => void): Promise<LintFileResult[]>;
789
+ /**
790
+ * Move queued tasks onto idle workers. Each worker takes AT MOST ONE
791
+ * task — the per-worker concurrency cap is what makes the per-task
792
+ * timeout meaningful (it measures real execution time, not backlog
793
+ * wait + execution). Called from:
794
+ *
795
+ * - `lintBatch` after enqueue
796
+ * - the `result` message handler (`attachOngoingHandlers`) after a
797
+ * task completes
798
+ * - the `exit` handler after a respawn, so the replacement worker
799
+ * can pick up the backlog
800
+ *
801
+ * Idempotent + cheap: bails on the first non-idle worker for each
802
+ * loop pass and on an empty queue.
803
+ *
804
+ * Pre-cancelled entries (set by `cancelTask` while still queued)
805
+ * are resolved here without ever being posted to a worker.
806
+ */
807
+ private kickQueue;
808
+ /** Cancel a task by taskId. Best-effort.
809
+ *
810
+ * - In-flight (already on a worker): set the SAB cancel flag —
811
+ * worker bails at the next per-node visit.
812
+ * - Queued (not yet dispatched): set `cancelled = true` so
813
+ * `kickQueue` resolves it as cancelled and never posts to a
814
+ * worker.
815
+ * - Otherwise (already completed / never existed): no-op, returns
816
+ * false.
817
+ *
818
+ * RACE NOTE: a `true` return does NOT guarantee the task's result is
819
+ * suppressed. Cancellation is cooperative (polled per-node in
820
+ * `listener-merge`), so a worker that has already finished its
821
+ * traversal but whose result is still in flight will deliver that
822
+ * result with `cancelled: false` and complete diagnostics — the flag
823
+ * is no longer read. The returned result is itself correct/complete;
824
+ * callers that need to drop a cancelled task's output must key off
825
+ * each result's own `cancelled` field, not this method's return. */
826
+ cancelTask(taskId: number): boolean;
827
+ /** Graceful shutdown — message all workers, wait for exit. */
828
+ shutdown(): Promise<void>;
829
+ /**
830
+ * Spawn one worker and wait for its 'ready' (or 'init-error') message.
831
+ * On init error or init-timeout, throws — caller (init) reports up.
832
+ */
833
+ private spawnWorker;
834
+ /**
835
+ * Wire post-init handlers: result routing, log-forwarding, and crash recovery.
836
+ */
837
+ private attachOngoingHandlers;
838
+ /**
839
+ * Last-chance drain for the "every worker exhausted its respawn
840
+ * cap" terminal state. Iterates `pendingQueue`, releases each
841
+ * cancel-slot, and resolves with `parseError: 'pool_degraded'` so
842
+ * the host distinguishes this case from a normal `shutdown` /
843
+ * `worker_crashed` per-task failure. Idempotent: if `pendingQueue`
844
+ * is already empty, the loop is a no-op.
845
+ */
846
+ private drainQueueIfAllSlotsDegraded;
847
+ /**
848
+ * Post one queued task to a specific (idle) worker slot. Caller is
849
+ * `kickQueue`, which has already verified the slot is ready +
850
+ * idle. taskId / cancelSlot were allocated at `lintBatch` enqueue
851
+ * time, so cancellation works against queued AND inflight tasks.
852
+ *
853
+ * The per-task timeout starts HERE — when the worker actually
854
+ * takes the task off the queue — not at enqueue time. That's the
855
+ * whole point of the queue model: the previous design ran every
856
+ * task's timer from `lintBatch` time, so the last task on each
857
+ * worker (sitting in the worker's postMessage backlog) raced a
858
+ * 30 s deadline before the worker even reached it.
859
+ */
860
+ private dispatchToWorker;
861
+ }
862
+
863
+ export declare interface WorkerPoolOptions {
864
+ /**
865
+ * Each user rslint config file passed directly to the worker; the
866
+ * worker imports each one once at init via
867
+ * `loadPluginsFromConfigs`, caches the resulting `LoadedPlugins` per
868
+ * `configDirectory`, and per-file lint tasks pick the right one via
869
+ * the task's `configKey`.
870
+ *
871
+ * Empty array means "no plugin work" — the pool spawns no workers
872
+ * (`workerCount=0` fast path) and `lintBatch` short-circuits to
873
+ * empty per-file results.
874
+ */
875
+ configs: ConfigDescriptor[];
876
+ /** Worker count. 1 honors --singleThreaded; default min(cpus, 8). */
877
+ workerCount?: number;
878
+ /** Per-task soft deadline (ms). Default 30_000. */
879
+ taskTimeoutMs?: number;
880
+ /** Worker init timeout (ms). Default 60_000. */
881
+ workerInitTimeoutMs?: number;
882
+ /** Max worker respawns per crashed worker before giving up. Default 3. */
883
+ retryCap?: number;
884
+ /** Hook called when a runner-side log is received (`console.*` from plugins / pool diagnostics). */
885
+ onLog?: (rec: {
886
+ level: string;
887
+ source: 'plugin' | 'runner';
888
+ text: string;
889
+ }) => void;
890
+ }
891
+
892
+ export { }