@rslint/core 0.5.2 → 0.5.4-canary.1781059600
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/bin/rslint.cjs +21 -4
- package/dist/0~engine.js +406 -0
- package/dist/34.js +33 -0
- package/dist/browser.d.ts +52 -39
- package/dist/browser.js +42 -74
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +1051 -93
- package/dist/config-loader.d.ts +45 -14
- package/dist/config-loader.js +95 -59
- package/dist/eslint-plugin/612.js +43 -0
- package/dist/eslint-plugin/index.d.ts +892 -0
- package/dist/eslint-plugin/index.js +26692 -0
- package/dist/eslint-plugin/lint-worker.js +26225 -0
- package/dist/eslint-plugin/types.d.ts +23 -0
- package/dist/eslint-plugin/types.js +1 -0
- package/dist/index.d.ts +626 -19
- package/dist/index.js +598 -15
- package/dist/service.d.ts +360 -30
- package/dist/service.js +19 -34
- package/package.json +27 -11
- package/dist/browser.d.ts.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/config-loader.d.ts.map +0 -1
- package/dist/configs/import.d.ts +0 -6
- package/dist/configs/import.d.ts.map +0 -1
- package/dist/configs/import.js +0 -7
- package/dist/configs/index.d.ts +0 -16
- package/dist/configs/index.d.ts.map +0 -1
- package/dist/configs/index.js +0 -32
- package/dist/configs/javascript.d.ts +0 -6
- package/dist/configs/javascript.d.ts.map +0 -1
- package/dist/configs/javascript.js +0 -72
- package/dist/configs/jest.d.ts +0 -7
- package/dist/configs/jest.d.ts.map +0 -1
- package/dist/configs/jest.js +0 -35
- package/dist/configs/promise.d.ts +0 -6
- package/dist/configs/promise.d.ts.map +0 -1
- package/dist/configs/promise.js +0 -20
- package/dist/configs/react-hooks.d.ts +0 -6
- package/dist/configs/react-hooks.d.ts.map +0 -1
- package/dist/configs/react-hooks.js +0 -24
- package/dist/configs/react.d.ts +0 -6
- package/dist/configs/react.d.ts.map +0 -1
- package/dist/configs/react.js +0 -31
- package/dist/configs/typescript.d.ts +0 -8
- package/dist/configs/typescript.d.ts.map +0 -1
- package/dist/configs/typescript.js +0 -119
- package/dist/configs/unicorn.d.ts +0 -8
- package/dist/configs/unicorn.d.ts.map +0 -1
- package/dist/configs/unicorn.js +0 -161
- package/dist/define-config.d.ts +0 -109
- package/dist/define-config.d.ts.map +0 -1
- package/dist/define-config.js +0 -6
- package/dist/index.d.ts.map +0 -1
- package/dist/node.d.ts +0 -31
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js +0 -116
- package/dist/service.d.ts.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
- package/dist/types.d.ts +0 -342
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- package/dist/utils/args.d.ts +0 -19
- package/dist/utils/args.d.ts.map +0 -1
- package/dist/utils/args.js +0 -101
- package/dist/utils/config-discovery.d.ts +0 -47
- package/dist/utils/config-discovery.d.ts.map +0 -1
- package/dist/utils/config-discovery.js +0 -238
- package/dist/worker.d.ts +0 -2
- package/dist/worker.d.ts.map +0 -1
- 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 { }
|