@isentinel/eslint-config 6.0.0-beta.2 → 6.0.0-beta.21

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.
@@ -0,0 +1,4711 @@
1
+ import { createRequire } from "node:module";
2
+ import { isPackageExists } from "local-pkg";
3
+ import { defineConfig } from "oxlint";
4
+ import { getStaticJSONValue, parseJSON } from "jsonc-eslint-parser";
5
+ import fs, { readFileSync } from "node:fs";
6
+ import path from "node:path";
7
+ import process from "node:process";
8
+ import { getStaticYAMLValue, parseYAML } from "yaml-eslint-parser";
9
+ import { findUpSync } from "find-up-simple";
10
+ import { minVersion } from "semver";
11
+ import { pathToFileURL } from "node:url";
12
+ import globals from "globals";
13
+ //#region src/rules/oxlint-mapping.ts
14
+ /**
15
+ * Notable rule families that intentionally stay in ESLint in hybrid mode,
16
+ * with the reason why.
17
+ */
18
+ const staysInEslint = {
19
+ "e18e/* (optionally type-aware)": "Four e18e rules (prefer-array-at, prefer-array-to-reversed, prefer-array-to-sorted, prefer-spread-syntax) run without type information but detect more with it, so they stay in ESLint (see optionallyTypeAwareRules); the rest run in oxlint as a jsPlugin",
20
+ "eslint-comments/*": "Lints eslint-disable directives, which only exist in ESLint-linted code; oxlint-comments covers oxlint directives",
21
+ "flawless/naming-convention": "Type-aware custom rule; oxlint jsPlugins have no type information. The syntax-only flawless rules (arrow-return-style, max-lines-per-function, no-export-default-arrow, padding-after-expect-assertions, prefer-parameter-destructuring, the react jsx-shorthand-*, purity, no-unnecessary-use-*, prefer-destructuring-assignment) are mapped and run in oxlint. flawless/no-redundant-tsconfig-options, flawless/toml-* and flawless/yaml-* lint non-JS files and stay in ESLint",
22
+ "flawless/prefer-read-only-props": "Type-aware custom rule; oxlint jsPlugins have no type information",
23
+ "format-lua/*": "Oxlint cannot lint Lua files",
24
+ "jest/* (type-aware)": "Four type-aware jest rules stay in ESLint (see typeAwareJsPluginRules); the rest run in oxlint via eslint-plugin-jest as a jsPlugin (renamed jest-js, since oxlint reserves the native jest prefix), which honors settings.jest.globalPackage = @rbxts/jest-globals (the NATIVE oxlint jest plugin does not, https://github.com/oxc-project/oxc/issues/23290)",
25
+ "jsonc/yaml/toml/markdown/package-json/pnpm/sort-*": "Oxlint only lints JS/TS files; JSON, YAML, TOML and Markdown stay in ESLint",
26
+ "react/* (type-aware)": "The @eslint-react type-aware rules (no-implicit-children/key/ref, no-leaked-conditional-rendering, no-unused-props) need type information but do NOT declare requiresTypeChecking, so the metadata-based parity test cannot catch them; they are excluded manually and stay in ESLint",
27
+ "roblox/* (type-aware)": "Type-aware custom rules (lua-truthiness etc.); oxlint jsPlugins have no type information",
28
+ "sentinel/explicit-size-check": "Type-aware custom rule; oxlint jsPlugins have no type information",
29
+ "type-aware jsPlugin rules": "Rules whose meta.docs.requiresTypeChecking is true crash or silently no-op under oxlint's jsPlugin runtime (no type information): sonar/no-ignored-return, sonar/no-incompatible-assertion-types, sonar/no-redundant-optional, sonar/no-try-promise, sonar/prefer-immediate-return, unicorn/no-non-function-verb-prefix, eslint-plugin/no-property-in-node, jest/no-error-equal, jest/no-unnecessary-assertion, jest/unbound-method, jest/valid-expect-with-promise, ts/prefer-destructuring (also has no native oxlint port)",
30
+ "unicorn/no-unsafe-string-replacement": "False positives under oxlint's jsPlugin scope analysis (template-literal replacements are not resolved)",
31
+ "unicorn/no-useless-coercion": "Optionally type-aware: values whose type is only known through TypeScript (a `string`-typed parameter, say) are missed without parser services, so it stays in ESLint (see optionallyTypeAwareRules)"
32
+ };
33
+ const oxlintRuleMapping = {
34
+ "accessor-pairs": "native",
35
+ "array-callback-return": "native",
36
+ "block-scoped-var": "native",
37
+ "constructor-super": "native",
38
+ "curly": "native",
39
+ "default-case-last": "native",
40
+ "eqeqeq": "native",
41
+ "for-direction": "native",
42
+ "func-style": "native",
43
+ "logical-assignment-operators": "native",
44
+ "max-classes-per-file": "native",
45
+ "max-depth": "native",
46
+ "max-lines": "native",
47
+ "max-lines-per-function": "native",
48
+ "new-cap": "native",
49
+ "no-alert": "native",
50
+ "no-array-constructor": "native",
51
+ "no-async-promise-executor": "native",
52
+ "no-caller": "native",
53
+ "no-case-declarations": "native",
54
+ "no-class-assign": "native",
55
+ "no-compare-neg-zero": "native",
56
+ "no-cond-assign": "native",
57
+ "no-console": "native",
58
+ "no-const-assign": "native",
59
+ "no-constant-condition": "native",
60
+ "no-control-regex": "native",
61
+ "no-debugger": "native",
62
+ "no-delete-var": "native",
63
+ "no-dupe-class-members": "native",
64
+ "no-dupe-keys": "native",
65
+ "no-duplicate-case": "native",
66
+ "no-duplicate-imports": "native",
67
+ "no-else-return": "native",
68
+ "no-empty": "native",
69
+ "no-empty-character-class": "native",
70
+ "no-empty-function": "native",
71
+ "no-empty-pattern": "native",
72
+ "no-empty-static-block": "native",
73
+ "no-eval": "native",
74
+ "no-ex-assign": "native",
75
+ "no-extend-native": "native",
76
+ "no-extra-bind": "native",
77
+ "no-extra-boolean-cast": "native",
78
+ "no-fallthrough": "native",
79
+ "no-func-assign": "native",
80
+ "no-global-assign": "native",
81
+ "no-implied-eval": "native",
82
+ "no-import-assign": "native",
83
+ "no-inline-comments": "native",
84
+ "no-invalid-regexp": "native",
85
+ "no-irregular-whitespace": "native",
86
+ "no-iterator": "native",
87
+ "no-labels": "native",
88
+ "no-lone-blocks": "native",
89
+ "no-lonely-if": "native",
90
+ "no-loss-of-precision": "native",
91
+ "no-misleading-character-class": "native",
92
+ "no-multi-str": "native",
93
+ "no-new": "native",
94
+ "no-new-func": "native",
95
+ "no-new-native-nonconstructor": "native",
96
+ "no-new-wrappers": "native",
97
+ "no-obj-calls": "native",
98
+ "no-proto": "native",
99
+ "no-prototype-builtins": "native",
100
+ "no-redeclare": "native",
101
+ "no-regex-spaces": "native",
102
+ "no-restricted-globals": "native",
103
+ "no-restricted-properties": "native",
104
+ "no-return-assign": "native",
105
+ "no-self-assign": "native",
106
+ "no-self-compare": "native",
107
+ "no-sequences": "native",
108
+ "no-shadow": "native",
109
+ "no-shadow-restricted-names": "native",
110
+ "no-sparse-arrays": "native",
111
+ "no-template-curly-in-string": "native",
112
+ "no-this-before-super": "native",
113
+ "no-throw-literal": "native",
114
+ "no-undef": "native",
115
+ "no-unexpected-multiline": "native",
116
+ "no-unmodified-loop-condition": "native",
117
+ "no-unneeded-ternary": "native",
118
+ "no-unreachable": "native",
119
+ "no-unreachable-loop": "native",
120
+ "no-unsafe-finally": "native",
121
+ "no-unsafe-negation": "native",
122
+ "no-unsafe-optional-chaining": "native",
123
+ "no-unused-expressions": "native",
124
+ "no-unused-private-class-members": "native",
125
+ "no-use-before-define": "native",
126
+ "no-useless-backreference": "native",
127
+ "no-useless-call": "native",
128
+ "no-useless-catch": "native",
129
+ "no-useless-computed-key": "native",
130
+ "no-useless-constructor": "native",
131
+ "no-useless-rename": "native",
132
+ "no-useless-return": "native",
133
+ "no-var": "native",
134
+ "no-with": "native",
135
+ "object-shorthand": "native",
136
+ "prefer-arrow-callback": "native",
137
+ "prefer-const": "native",
138
+ "prefer-exponentiation-operator": "native",
139
+ "prefer-promise-reject-errors": "native",
140
+ "prefer-regex-literals": "native",
141
+ "prefer-rest-params": "native",
142
+ "prefer-spread": "native",
143
+ "prefer-template": "native",
144
+ "require-await": "native",
145
+ "symbol-description": "native",
146
+ "unicode-bom": "native",
147
+ "use-isnan": "native",
148
+ "valid-typeof": "native",
149
+ "vars-on-top": "native",
150
+ "yoda": "native",
151
+ "dot-notation": "js-plugin",
152
+ "id-length": "js-plugin",
153
+ "no-dupe-args": "js-plugin",
154
+ "no-loop-func": "js-plugin",
155
+ "no-octal": "js-plugin",
156
+ "no-octal-escape": "js-plugin",
157
+ "no-restricted-syntax": "js-plugin",
158
+ "no-undef-init": "js-plugin",
159
+ "one-var": "js-plugin",
160
+ "ts/default-param-last": "native",
161
+ "ts/no-empty-function": "native",
162
+ "ts/no-shadow": "native",
163
+ "ts/no-unused-expressions": "native",
164
+ "ts/no-unused-private-class-members": "native",
165
+ "ts/no-useless-constructor": "native",
166
+ "ts/array-type": "native",
167
+ "ts/ban-ts-comment": "native",
168
+ "ts/consistent-generic-constructors": "native",
169
+ "ts/consistent-indexed-object-style": "native",
170
+ "ts/consistent-type-assertions": "native",
171
+ "ts/consistent-type-definitions": "native",
172
+ "ts/consistent-type-imports": "native",
173
+ "ts/explicit-function-return-type": "native",
174
+ "ts/explicit-member-accessibility": "native",
175
+ "ts/no-confusing-non-null-assertion": "native",
176
+ "ts/no-duplicate-enum-values": "native",
177
+ "ts/no-empty-object-type": "native",
178
+ "ts/no-extra-non-null-assertion": "native",
179
+ "ts/no-extraneous-class": "native",
180
+ "ts/no-import-type-side-effects": "native",
181
+ "ts/no-inferrable-types": "native",
182
+ "ts/no-misused-new": "native",
183
+ "ts/no-non-null-asserted-nullish-coalescing": "native",
184
+ "ts/no-non-null-asserted-optional-chain": "native",
185
+ "ts/no-non-null-assertion": "native",
186
+ "ts/no-require-imports": "native",
187
+ "ts/no-this-alias": "native",
188
+ "ts/no-unnecessary-parameter-property-assignment": "native",
189
+ "ts/no-unnecessary-type-constraint": "native",
190
+ "ts/no-unsafe-declaration-merging": "native",
191
+ "ts/no-unsafe-function-type": "native",
192
+ "ts/no-wrapper-object-types": "native",
193
+ "ts/prefer-as-const": "native",
194
+ "ts/prefer-for-of": "native",
195
+ "ts/prefer-function-type": "native",
196
+ "ts/prefer-literal-enum-member": "native",
197
+ "ts/prefer-namespace-keyword": "native",
198
+ "ts/await-thenable": "tsgolint",
199
+ "ts/consistent-type-exports": "tsgolint",
200
+ "ts/dot-notation": "tsgolint",
201
+ "ts/no-array-delete": "tsgolint",
202
+ "ts/no-base-to-string": "tsgolint",
203
+ "ts/no-confusing-void-expression": "tsgolint",
204
+ "ts/no-deprecated": "tsgolint",
205
+ "ts/no-duplicate-type-constituents": "tsgolint",
206
+ "ts/no-floating-promises": "tsgolint",
207
+ "ts/no-for-in-array": "tsgolint",
208
+ "ts/no-implied-eval": "tsgolint",
209
+ "ts/no-meaningless-void-operator": "tsgolint",
210
+ "ts/no-misused-promises": "tsgolint",
211
+ "ts/no-misused-spread": "tsgolint",
212
+ "ts/no-mixed-enums": "tsgolint",
213
+ "ts/no-redundant-type-constituents": "tsgolint",
214
+ "ts/no-unnecessary-boolean-literal-compare": "tsgolint",
215
+ "ts/no-unnecessary-condition": "tsgolint",
216
+ "ts/no-unnecessary-qualifier": "tsgolint",
217
+ "ts/no-unnecessary-template-expression": "tsgolint",
218
+ "ts/no-unnecessary-type-arguments": "tsgolint",
219
+ "ts/no-unnecessary-type-assertion": "tsgolint",
220
+ "ts/no-unnecessary-type-conversion": "tsgolint",
221
+ "ts/no-unnecessary-type-parameters": "tsgolint",
222
+ "ts/no-unsafe-argument": "tsgolint",
223
+ "ts/no-unsafe-assignment": "tsgolint",
224
+ "ts/no-unsafe-call": "tsgolint",
225
+ "ts/no-unsafe-enum-comparison": "tsgolint",
226
+ "ts/no-unsafe-member-access": "tsgolint",
227
+ "ts/no-unsafe-return": "tsgolint",
228
+ "ts/no-unsafe-type-assertion": "tsgolint",
229
+ "ts/no-unsafe-unary-minus": "tsgolint",
230
+ "ts/no-useless-default-assignment": "tsgolint",
231
+ "ts/non-nullable-type-assertion-style": "tsgolint",
232
+ "ts/only-throw-error": "tsgolint",
233
+ "ts/prefer-find": "tsgolint",
234
+ "ts/prefer-includes": "tsgolint",
235
+ "ts/prefer-nullish-coalescing": "tsgolint",
236
+ "ts/prefer-optional-chain": "tsgolint",
237
+ "ts/prefer-promise-reject-errors": "tsgolint",
238
+ "ts/prefer-readonly": "tsgolint",
239
+ "ts/prefer-reduce-type-parameter": "tsgolint",
240
+ "ts/prefer-return-this-type": "tsgolint",
241
+ "ts/promise-function-async": "tsgolint",
242
+ "ts/related-getter-setter-pairs": "tsgolint",
243
+ "ts/require-array-sort-compare": "tsgolint",
244
+ "ts/require-await": "tsgolint",
245
+ "ts/restrict-plus-operands": "tsgolint",
246
+ "ts/restrict-template-expressions": "tsgolint",
247
+ "ts/return-await": "tsgolint",
248
+ "ts/strict-boolean-expressions": "tsgolint",
249
+ "ts/strict-void-return": "tsgolint",
250
+ "ts/switch-exhaustiveness-check": "tsgolint",
251
+ "ts/unbound-method": "tsgolint",
252
+ "ts/use-unknown-in-catch-callback-variable": "tsgolint",
253
+ "unicorn/catch-error-name": "native",
254
+ "unicorn/consistent-function-scoping": "native",
255
+ "unicorn/consistent-template-literal-escape": "native",
256
+ "unicorn/filename-case": "native",
257
+ "unicorn/no-array-fill-with-reference-type": "native",
258
+ "unicorn/no-await-expression-member": "native",
259
+ "unicorn/no-empty-file": "native",
260
+ "unicorn/no-for-each": "native",
261
+ "unicorn/no-immediate-mutation": "native",
262
+ "unicorn/no-lonely-if": "native",
263
+ "unicorn/no-negation-in-equality-check": "native",
264
+ "unicorn/no-object-as-default-parameter": "native",
265
+ "unicorn/no-single-promise-in-promise-methods": "native",
266
+ "unicorn/no-static-only-class": "native",
267
+ "unicorn/no-unreadable-array-destructuring": "native",
268
+ "unicorn/no-useless-collection-argument": "native",
269
+ "unicorn/no-useless-iterator-to-array": "native",
270
+ "unicorn/no-useless-promise-resolve-reject": "native",
271
+ "unicorn/no-useless-undefined": "native",
272
+ "unicorn/number-literal-case": "native",
273
+ "unicorn/prefer-array-flat-map": "native",
274
+ "unicorn/prefer-default-parameters": "native",
275
+ "unicorn/prefer-export-from": "native",
276
+ "unicorn/prefer-includes": "native",
277
+ "unicorn/prefer-logical-operator-over-ternary": "native",
278
+ "unicorn/prefer-optional-catch-binding": "native",
279
+ "unicorn/prefer-set-has": "native",
280
+ "unicorn/prefer-single-call": "native",
281
+ "unicorn/prefer-ternary": "native",
282
+ "unicorn/switch-case-braces": "native",
283
+ "unicorn/throw-new-error": "native",
284
+ "unicorn/consistent-compound-words": "js-plugin",
285
+ "unicorn/consistent-conditional-object-spread": "js-plugin",
286
+ "unicorn/consistent-destructuring": "js-plugin",
287
+ "unicorn/consistent-json-file-read": "js-plugin",
288
+ "unicorn/consistent-tuple-labels": "js-plugin",
289
+ "unicorn/isolated-functions": "js-plugin",
290
+ "unicorn/name-replacements": "js-plugin",
291
+ "unicorn/no-array-concat-in-loop": "js-plugin",
292
+ "unicorn/no-array-sort-for-min-max": "js-plugin",
293
+ "unicorn/no-async-promise-finally": "js-plugin",
294
+ "unicorn/no-boolean-sort-comparator": "js-plugin",
295
+ "unicorn/no-break-in-nested-loop": "js-plugin",
296
+ "unicorn/no-confusing-array-splice": "js-plugin",
297
+ "unicorn/no-constant-zero-expression": "js-plugin",
298
+ "unicorn/no-declarations-before-early-exit": "js-plugin",
299
+ "unicorn/no-double-comparison": "js-plugin",
300
+ "unicorn/no-duplicate-loops": "js-plugin",
301
+ "unicorn/no-duplicate-set-values": "js-plugin",
302
+ "unicorn/no-error-property-assignment": "js-plugin",
303
+ "unicorn/no-exports-in-scripts": "js-plugin",
304
+ "unicorn/no-for-loop": "js-plugin",
305
+ "unicorn/no-global-object-property-assignment": "js-plugin",
306
+ "unicorn/no-impossible-length-comparison": "js-plugin",
307
+ "unicorn/no-incorrect-template-string-interpolation": "js-plugin",
308
+ "unicorn/no-invalid-character-comparison": "js-plugin",
309
+ "unicorn/no-invalid-well-known-symbol-methods": "js-plugin",
310
+ "unicorn/no-keyword-prefix": "js-plugin",
311
+ "unicorn/no-loop-iterable-mutation": "js-plugin",
312
+ "unicorn/no-mismatched-map-key": "js-plugin",
313
+ "unicorn/no-misrefactored-assignment": "js-plugin",
314
+ "unicorn/no-negated-array-predicate": "js-plugin",
315
+ "unicorn/no-object-methods-with-collections": "js-plugin",
316
+ "unicorn/no-optional-chaining-on-undeclared-variable": "js-plugin",
317
+ "unicorn/no-redundant-comparison": "js-plugin",
318
+ "unicorn/no-return-array-push": "js-plugin",
319
+ "unicorn/no-subtraction-comparison": "js-plugin",
320
+ "unicorn/no-unnecessary-fetch-options": "js-plugin",
321
+ "unicorn/no-unnecessary-global-this": "js-plugin",
322
+ "unicorn/no-unnecessary-string-trim": "js-plugin",
323
+ "unicorn/no-unreadable-for-of-expression": "js-plugin",
324
+ "unicorn/no-unreadable-new-expression": "js-plugin",
325
+ "unicorn/no-unsafe-property-key": "js-plugin",
326
+ "unicorn/no-unused-properties": "js-plugin",
327
+ "unicorn/no-useless-compound-assignment": "js-plugin",
328
+ "unicorn/no-useless-concat": "js-plugin",
329
+ "unicorn/no-useless-delete-check": "js-plugin",
330
+ "unicorn/no-useless-logical-operand": "js-plugin",
331
+ "unicorn/no-useless-re-export": "js-plugin",
332
+ "unicorn/no-useless-recursion": "js-plugin",
333
+ "unicorn/no-xor-as-exponentiation": "js-plugin",
334
+ "unicorn/operator-assignment": "js-plugin",
335
+ "unicorn/prefer-abort-signal-any": "js-plugin",
336
+ "unicorn/prefer-array-from-map": "js-plugin",
337
+ "unicorn/prefer-array-from-range": "js-plugin",
338
+ "unicorn/prefer-array-iterable-methods": "js-plugin",
339
+ "unicorn/prefer-array-slice": "js-plugin",
340
+ "unicorn/prefer-block-statement-over-iife": "js-plugin",
341
+ "unicorn/prefer-continue": "js-plugin",
342
+ "unicorn/prefer-direct-iteration": "js-plugin",
343
+ "unicorn/prefer-else-if": "js-plugin",
344
+ "unicorn/prefer-flat-math-min-max": "js-plugin",
345
+ "unicorn/prefer-global-number-constants": "js-plugin",
346
+ "unicorn/prefer-group-by": "js-plugin",
347
+ "unicorn/prefer-has-check": "js-plugin",
348
+ "unicorn/prefer-hoisting-branch-code": "js-plugin",
349
+ "unicorn/prefer-identifier-import-export-specifiers": "js-plugin",
350
+ "unicorn/prefer-iterator-helpers": "js-plugin",
351
+ "unicorn/prefer-math-constants": "js-plugin",
352
+ "unicorn/prefer-simple-condition-first": "js-plugin",
353
+ "unicorn/prefer-simplified-conditions": "js-plugin",
354
+ "unicorn/prefer-split-limit": "js-plugin",
355
+ "unicorn/prefer-string-match-all": "js-plugin",
356
+ "unicorn/prefer-string-pad-start-end": "js-plugin",
357
+ "unicorn/prefer-string-repeat": "js-plugin",
358
+ "unicorn/prefer-switch": "js-plugin",
359
+ "unicorn/prefer-then-catch": "js-plugin",
360
+ "unicorn/prefer-unary-minus": "js-plugin",
361
+ "unicorn/require-passive-events": "js-plugin",
362
+ "import/first": "native",
363
+ "import/newline-after-import": "native",
364
+ "import/no-anonymous-default-export": "native",
365
+ "import/no-default-export": "native",
366
+ "import/no-mutable-exports": "native",
367
+ "import/no-named-default": "native",
368
+ "promise/always-return": "native",
369
+ "promise/catch-or-return": "native",
370
+ "promise/no-multiple-resolved": "native",
371
+ "promise/no-nesting": "native",
372
+ "promise/no-promise-in-callback": "native",
373
+ "promise/no-return-in-finally": "native",
374
+ "promise/no-return-wrap": "native",
375
+ "promise/param-names": "native",
376
+ "jsdoc/check-access": "native",
377
+ "jsdoc/check-property-names": "native",
378
+ "jsdoc/empty-tags": "native",
379
+ "jsdoc/implements-on-classes": "native",
380
+ "jsdoc/no-defaults": "native",
381
+ "jsdoc/require-param": "native",
382
+ "jsdoc/require-param-description": "native",
383
+ "jsdoc/require-param-name": "native",
384
+ "jsdoc/require-property": "native",
385
+ "jsdoc/require-property-description": "native",
386
+ "jsdoc/require-property-name": "native",
387
+ "jsdoc/require-returns": "native",
388
+ "jsdoc/require-returns-description": "native",
389
+ "jsdoc/check-alignment": "js-plugin",
390
+ "jsdoc/check-param-names": "js-plugin",
391
+ "jsdoc/check-types": "js-plugin",
392
+ "jsdoc/convert-to-jsdoc-comments": "js-plugin",
393
+ "jsdoc/informative-docs": "js-plugin",
394
+ "jsdoc/multiline-blocks": "js-plugin",
395
+ "jsdoc/no-blank-block-descriptions": "js-plugin",
396
+ "jsdoc/no-blank-blocks": "js-plugin",
397
+ "jsdoc/no-multi-asterisks": "js-plugin",
398
+ "jsdoc/no-types": "js-plugin",
399
+ "jsdoc/no-undefined-types": "js-plugin",
400
+ "jsdoc/normalize-see-links": "js-plugin",
401
+ "jsdoc/require-asterisk-prefix": "js-plugin",
402
+ "jsdoc/require-description": "js-plugin",
403
+ "jsdoc/require-description-complete-sentence": "js-plugin",
404
+ "jsdoc/require-hyphen-before-param-description": "js-plugin",
405
+ "jsdoc/require-rejects": "js-plugin",
406
+ "jsdoc/require-returns-check": "js-plugin",
407
+ "jsdoc/require-template": "js-plugin",
408
+ "jsdoc/require-yields-check": "js-plugin",
409
+ "node/handle-callback-err": "native",
410
+ "node/no-exports-assign": "native",
411
+ "node/no-new-require": "native",
412
+ "node/no-path-concat": "native",
413
+ "node/no-deprecated-api": "js-plugin",
414
+ "node/prefer-global/buffer": "js-plugin",
415
+ "node/prefer-global/process": "js-plugin",
416
+ "node/prefer-node-protocol": "js-plugin",
417
+ "node/process-exit-as-throw": "js-plugin",
418
+ "sonar/bool-param-default": "js-plugin",
419
+ "sonar/cognitive-complexity": "js-plugin",
420
+ "sonar/constructor-for-side-effects": "js-plugin",
421
+ "sonar/destructuring-assignment-syntax": "js-plugin",
422
+ "sonar/file-name-differ-from-class": "js-plugin",
423
+ "sonar/fixme-tag": "js-plugin",
424
+ "sonar/max-switch-cases": "js-plugin",
425
+ "sonar/misplaced-loop-counter": "js-plugin",
426
+ "sonar/no-collapsible-if": "js-plugin",
427
+ "sonar/no-dead-store": "js-plugin",
428
+ "sonar/no-default-utility-imports": "js-plugin",
429
+ "sonar/no-duplicate-string": "js-plugin",
430
+ "sonar/no-duplicated-branches": "js-plugin",
431
+ "sonar/no-element-overwrite": "js-plugin",
432
+ "sonar/no-empty-collection": "js-plugin",
433
+ "sonar/no-floating-point-equality": "js-plugin",
434
+ "sonar/no-identical-conditions": "js-plugin",
435
+ "sonar/no-identical-expressions": "js-plugin",
436
+ "sonar/no-identical-functions": "js-plugin",
437
+ "sonar/no-inverted-boolean-check": "js-plugin",
438
+ "sonar/no-mixed-completion-style": "js-plugin",
439
+ "sonar/no-nested-conditional": "js-plugin",
440
+ "sonar/no-nested-incdec": "js-plugin",
441
+ "sonar/no-nested-switch": "js-plugin",
442
+ "sonar/no-nested-template-literals": "js-plugin",
443
+ "sonar/no-parameter-reassignment": "js-plugin",
444
+ "sonar/no-redundant-boolean": "js-plugin",
445
+ "sonar/no-redundant-jump": "js-plugin",
446
+ "sonar/no-trivial-assertions": "js-plugin",
447
+ "sonar/no-unthrown-error": "js-plugin",
448
+ "sonar/no-unused-collection": "js-plugin",
449
+ "sonar/no-use-of-empty-return-value": "js-plugin",
450
+ "sonar/no-useless-catch": "js-plugin",
451
+ "sonar/no-useless-increment": "js-plugin",
452
+ "sonar/non-existent-operator": "js-plugin",
453
+ "sonar/prefer-object-literal": "js-plugin",
454
+ "sonar/prefer-promise-shorthand": "js-plugin",
455
+ "sonar/prefer-single-boolean-return": "js-plugin",
456
+ "sonar/prefer-while": "js-plugin",
457
+ "sonar/public-static-readonly": "js-plugin",
458
+ "sonar/super-linear-regex": "js-plugin",
459
+ "sonar/synchronous-suite-callback": "js-plugin",
460
+ "sonar/todo-tag": "js-plugin",
461
+ "sonar/use-type-alias": "js-plugin",
462
+ "perfectionist/sort-array-includes": "js-plugin",
463
+ "perfectionist/sort-classes": "js-plugin",
464
+ "perfectionist/sort-decorators": "js-plugin",
465
+ "perfectionist/sort-enums": "js-plugin",
466
+ "perfectionist/sort-exports": "js-plugin",
467
+ "perfectionist/sort-heritage-clauses": "js-plugin",
468
+ "perfectionist/sort-interfaces": "js-plugin",
469
+ "perfectionist/sort-intersection-types": "js-plugin",
470
+ "perfectionist/sort-jsx-props": "js-plugin",
471
+ "perfectionist/sort-maps": "js-plugin",
472
+ "perfectionist/sort-modules": "js-plugin",
473
+ "perfectionist/sort-named-imports": "js-plugin",
474
+ "perfectionist/sort-object-types": "js-plugin",
475
+ "perfectionist/sort-objects": "js-plugin",
476
+ "perfectionist/sort-sets": "js-plugin",
477
+ "perfectionist/sort-switch-case": "js-plugin",
478
+ "perfectionist/sort-union-types": "js-plugin",
479
+ "perfectionist/sort-variable-declarations": "js-plugin",
480
+ "@cspell/spellchecker": "js-plugin",
481
+ "antfu/import-dedupe": "js-plugin",
482
+ "antfu/no-import-dist": "js-plugin",
483
+ "antfu/no-import-node-modules-by-path": "js-plugin",
484
+ "antfu/no-top-level-await": "js-plugin",
485
+ "antfu/top-level-function": "js-plugin",
486
+ "better-max-params/better-max-params": "js-plugin",
487
+ "de-morgan/no-negated-conjunction": "js-plugin",
488
+ "de-morgan/no-negated-disjunction": "js-plugin",
489
+ "unused-imports/no-unused-imports": "js-plugin",
490
+ "unused-imports/no-unused-vars": "js-plugin",
491
+ "e18e/ban-dependencies": "js-plugin",
492
+ "e18e/no-spread-in-reduce": "js-plugin",
493
+ "e18e/prefer-array-fill": "js-plugin",
494
+ "e18e/prefer-array-from-map": "js-plugin",
495
+ "e18e/prefer-array-some": "js-plugin",
496
+ "e18e/prefer-array-to-spliced": "js-plugin",
497
+ "e18e/prefer-date-now": "js-plugin",
498
+ "e18e/prefer-get-or-insert": "js-plugin",
499
+ "e18e/prefer-includes": "js-plugin",
500
+ "e18e/prefer-includes-over-regex-test": "js-plugin",
501
+ "e18e/prefer-nullish-coalescing": "js-plugin",
502
+ "e18e/prefer-object-has-own": "js-plugin",
503
+ "e18e/prefer-regex-test": "js-plugin",
504
+ "e18e/prefer-static-collator": "js-plugin",
505
+ "e18e/prefer-static-regex": "js-plugin",
506
+ "e18e/prefer-string-fromcharcode": "js-plugin",
507
+ "e18e/prefer-timer-args": "js-plugin",
508
+ "e18e/prefer-url-canparse": "js-plugin",
509
+ "style/jsx-curly-brace-presence": "js-plugin",
510
+ "style/jsx-newline": "js-plugin",
511
+ "style/jsx-self-closing-comp": "js-plugin",
512
+ "style/lines-between-class-members": "js-plugin",
513
+ "style/multiline-comment-style": "js-plugin",
514
+ "style/object-property-newline": "js-plugin",
515
+ "style/padding-line-between-statements": "js-plugin",
516
+ "style/quotes": "js-plugin",
517
+ "style/spaced-comment": "js-plugin",
518
+ "comment-length/limit-multi-line-comments": "js-plugin",
519
+ "comment-length/limit-single-line-comments": "js-plugin",
520
+ "small-rules/array-type-generic": "js-plugin",
521
+ "small-rules/no-array-constructor-elements": "js-plugin",
522
+ "small-rules/no-array-size-assignment": "js-plugin",
523
+ "small-rules/no-async-constructor": "js-plugin",
524
+ "small-rules/no-commented-code": "js-plugin",
525
+ "small-rules/no-recursive": "js-plugin",
526
+ "small-rules/prefer-class-properties": "js-plugin",
527
+ "small-rules/prefer-early-return": "js-plugin",
528
+ "small-rules/prefer-module-scope-constants": "js-plugin",
529
+ "small-rules/prefer-singular-enums": "js-plugin",
530
+ "small-rules/react-hooks-strict-return": "js-plugin",
531
+ "small-rules/require-async-suffix": "js-plugin",
532
+ "small-rules/strict-component-boundaries": "js-plugin",
533
+ "flawless/arrow-return-style": "js-plugin",
534
+ "flawless/jsx-shorthand-boolean": "js-plugin",
535
+ "flawless/jsx-shorthand-fragment": "js-plugin",
536
+ "flawless/max-lines-per-function": "js-plugin",
537
+ "flawless/no-export-default-arrow": "js-plugin",
538
+ "flawless/no-unnecessary-use-callback": "js-plugin",
539
+ "flawless/no-unnecessary-use-memo": "js-plugin",
540
+ "flawless/padding-after-expect-assertions": "js-plugin",
541
+ "flawless/prefer-destructuring-assignment": "js-plugin",
542
+ "flawless/prefer-parameter-destructuring": "js-plugin",
543
+ "flawless/purity": "js-plugin",
544
+ "react/error-boundaries": "js-plugin",
545
+ "react/exhaustive-deps": "js-plugin",
546
+ "react/globals": "js-plugin",
547
+ "react/immutability": "js-plugin",
548
+ "react/no-access-state-in-setstate": "js-plugin",
549
+ "react/no-array-index-key": "js-plugin",
550
+ "react/no-children-count": "js-plugin",
551
+ "react/no-children-for-each": "js-plugin",
552
+ "react/no-children-map": "js-plugin",
553
+ "react/no-children-only": "js-plugin",
554
+ "react/no-children-to-array": "js-plugin",
555
+ "react/no-class-component": "js-plugin",
556
+ "react/no-clone-element": "js-plugin",
557
+ "react/no-component-will-mount": "js-plugin",
558
+ "react/no-component-will-receive-props": "js-plugin",
559
+ "react/no-component-will-update": "js-plugin",
560
+ "react/no-create-ref": "js-plugin",
561
+ "react/no-direct-mutation-state": "js-plugin",
562
+ "react/no-duplicate-key": "js-plugin",
563
+ "react/no-forward-ref": "js-plugin",
564
+ "react/no-missing-component-display-name": "js-plugin",
565
+ "react/no-missing-context-display-name": "js-plugin",
566
+ "react/no-missing-key": "js-plugin",
567
+ "react/no-misused-capture-owner-stack": "js-plugin",
568
+ "react/no-nested-component-definitions": "js-plugin",
569
+ "react/no-nested-lazy-component-declarations": "js-plugin",
570
+ "react/no-set-state-in-component-did-mount": "js-plugin",
571
+ "react/no-set-state-in-component-did-update": "js-plugin",
572
+ "react/no-set-state-in-component-will-update": "js-plugin",
573
+ "react/no-unnecessary-use-prefix": "js-plugin",
574
+ "react/no-unsafe-component-will-mount": "js-plugin",
575
+ "react/no-unsafe-component-will-receive-props": "js-plugin",
576
+ "react/no-unsafe-component-will-update": "js-plugin",
577
+ "react/no-unstable-context-value": "js-plugin",
578
+ "react/no-unstable-default-props": "js-plugin",
579
+ "react/no-unused-class-component-members": "js-plugin",
580
+ "react/no-unused-state": "js-plugin",
581
+ "react/no-use-context": "js-plugin",
582
+ "react/refs": "js-plugin",
583
+ "react/rules-of-hooks": "js-plugin",
584
+ "react/set-state-in-effect": "js-plugin",
585
+ "react/set-state-in-render": "js-plugin",
586
+ "react/static-components": "js-plugin",
587
+ "react/use-memo": "js-plugin",
588
+ "react/use-state": "js-plugin",
589
+ "react-jsx/no-children-prop": "js-plugin",
590
+ "react-jsx/no-children-prop-with-children": "js-plugin",
591
+ "react-jsx/no-comment-textnodes": "js-plugin",
592
+ "react-jsx/no-key-after-spread": "js-plugin",
593
+ "react-jsx/no-leaked-dollar": "js-plugin",
594
+ "react-jsx/no-leaked-semicolon": "js-plugin",
595
+ "react-jsx/no-useless-fragment": "js-plugin",
596
+ "react-naming-convention/context-name": "js-plugin",
597
+ "react-naming-convention/ref-name": "js-plugin",
598
+ "jest/consistent-test-it": "js-plugin",
599
+ "jest/expect-expect": "js-plugin",
600
+ "jest/max-expects": "js-plugin",
601
+ "jest/max-nested-describe": "js-plugin",
602
+ "jest/no-alias-methods": "js-plugin",
603
+ "jest/no-commented-out-tests": "js-plugin",
604
+ "jest/no-conditional-expect": "js-plugin",
605
+ "jest/no-conditional-in-test": "js-plugin",
606
+ "jest/no-disabled-tests": "js-plugin",
607
+ "jest/no-done-callback": "js-plugin",
608
+ "jest/no-duplicate-hooks": "js-plugin",
609
+ "jest/no-export": "js-plugin",
610
+ "jest/no-focused-tests": "js-plugin",
611
+ "jest/no-hooks": "js-plugin",
612
+ "jest/no-identical-title": "js-plugin",
613
+ "jest/no-standalone-expect": "js-plugin",
614
+ "jest/no-test-prefixes": "js-plugin",
615
+ "jest/no-test-return-statement": "js-plugin",
616
+ "jest/no-unneeded-async-expect-function": "js-plugin",
617
+ "jest/no-untyped-mock-factory": "js-plugin",
618
+ "jest/padding-around-all": "js-plugin",
619
+ "jest/prefer-called-with": "js-plugin",
620
+ "jest/prefer-comparison-matcher": "js-plugin",
621
+ "jest/prefer-each": "js-plugin",
622
+ "jest/prefer-ending-with-an-expect": "js-plugin",
623
+ "jest/prefer-equality-matcher": "js-plugin",
624
+ "jest/prefer-expect-assertions": "js-plugin",
625
+ "jest/prefer-hooks-in-order": "js-plugin",
626
+ "jest/prefer-lowercase-title": "js-plugin",
627
+ "jest/prefer-mock-promise-shorthand": "js-plugin",
628
+ "jest/prefer-mock-return-shorthand": "js-plugin",
629
+ "jest/prefer-spy-on": "js-plugin",
630
+ "jest/prefer-strict-equal": "js-plugin",
631
+ "jest/prefer-to-be": "js-plugin",
632
+ "jest/prefer-to-contain": "js-plugin",
633
+ "jest/prefer-to-have-been-called": "js-plugin",
634
+ "jest/prefer-to-have-been-called-times": "js-plugin",
635
+ "jest/prefer-to-have-length": "js-plugin",
636
+ "jest/prefer-todo": "js-plugin",
637
+ "jest/require-hook": "js-plugin",
638
+ "jest/require-to-throw-message": "js-plugin",
639
+ "jest/require-top-level-describe": "js-plugin",
640
+ "jest/valid-describe-callback": "js-plugin",
641
+ "jest/valid-expect": "js-plugin",
642
+ "jest/valid-expect-in-promise": "js-plugin",
643
+ "jest/valid-title": "js-plugin",
644
+ "jest-extended/prefer-to-be-array": "js-plugin",
645
+ "jest-extended/prefer-to-be-false": "js-plugin",
646
+ "jest-extended/prefer-to-be-object": "js-plugin",
647
+ "jest-extended/prefer-to-be-true": "js-plugin",
648
+ "jest-extended/prefer-to-have-been-called-once": "js-plugin",
649
+ // @vitest/eslint-plugin as a jsPlugin, renamed vitest-js.
650
+ "vitest/consistent-each-for": "native",
651
+ "vitest/consistent-test-filename": "native",
652
+ "vitest/consistent-test-it": "native",
653
+ "vitest/consistent-vitest-vi": "native",
654
+ "vitest/expect-expect": "native",
655
+ "vitest/hoisted-apis-on-top": "native",
656
+ "vitest/max-expects": "native",
657
+ "vitest/max-nested-describe": "native",
658
+ "vitest/no-alias-methods": "native",
659
+ "vitest/no-commented-out-tests": "native",
660
+ "vitest/no-conditional-expect": "native",
661
+ "vitest/no-conditional-in-test": "native",
662
+ "vitest/no-conditional-tests": "native",
663
+ "vitest/no-disabled-tests": "native",
664
+ "vitest/no-duplicate-hooks": "native",
665
+ "vitest/no-focused-tests": "native",
666
+ "vitest/no-hooks": "native",
667
+ "vitest/no-identical-title": "native",
668
+ "vitest/no-import-node-test": "native",
669
+ "vitest/no-interpolation-in-snapshots": "native",
670
+ "vitest/no-large-snapshots": "native",
671
+ "vitest/no-mocks-import": "native",
672
+ "vitest/no-standalone-expect": "native",
673
+ "vitest/no-test-prefixes": "native",
674
+ "vitest/no-test-return-statement": "native",
675
+ "vitest/no-unneeded-async-expect-function": "native",
676
+ "vitest/padding-around-all": "js-plugin",
677
+ "vitest/prefer-called-exactly-once-with": "native",
678
+ "vitest/prefer-called-once": "native",
679
+ "vitest/prefer-called-with": "native",
680
+ "vitest/prefer-comparison-matcher": "native",
681
+ "vitest/prefer-describe-function-title": "native",
682
+ "vitest/prefer-each": "native",
683
+ "vitest/prefer-equality-matcher": "native",
684
+ "vitest/prefer-expect-assertions": "native",
685
+ "vitest/prefer-expect-resolves": "native",
686
+ "vitest/prefer-expect-type-of": "native",
687
+ "vitest/prefer-hooks-in-order": "native",
688
+ "vitest/prefer-hooks-on-top": "native",
689
+ "vitest/prefer-import-in-mock": "native",
690
+ "vitest/prefer-importing-vitest-globals": "native",
691
+ "vitest/prefer-lowercase-title": "native",
692
+ "vitest/prefer-mock-promise-shorthand": "native",
693
+ "vitest/prefer-mock-return-shorthand": "native",
694
+ "vitest/prefer-snapshot-hint": "native",
695
+ "vitest/prefer-spy-on": "native",
696
+ "vitest/prefer-strict-boolean-matchers": "native",
697
+ "vitest/prefer-strict-equal": "native",
698
+ "vitest/prefer-to-be": "native",
699
+ "vitest/prefer-to-be-object": "native",
700
+ "vitest/prefer-to-contain": "native",
701
+ "vitest/prefer-to-have-been-called-times": "native",
702
+ "vitest/prefer-to-have-length": "native",
703
+ "vitest/prefer-todo": "native",
704
+ "vitest/prefer-vi-mocked": "js-plugin",
705
+ "vitest/require-awaited-expect-poll": "native",
706
+ "vitest/require-local-test-context-for-concurrent-snapshots": "native",
707
+ "vitest/require-mock-type-parameters": "native",
708
+ "vitest/require-to-throw-message": "native",
709
+ "vitest/require-top-level-describe": "native",
710
+ "vitest/valid-describe-callback": "native",
711
+ "vitest/valid-expect": "native",
712
+ "vitest/valid-expect-in-promise": "native",
713
+ "vitest/valid-title": "native",
714
+ "roblox/no-any": "js-plugin",
715
+ "roblox/no-enum-merging": "js-plugin",
716
+ "roblox/no-export-assignment-let": "js-plugin",
717
+ "roblox/no-for-in": "js-plugin",
718
+ "roblox/no-function-expression-name": "js-plugin",
719
+ "roblox/no-get-set": "js-plugin",
720
+ "roblox/no-implicit-self": "js-plugin",
721
+ "roblox/no-invalid-identifier": "js-plugin",
722
+ "roblox/no-namespace-merging": "js-plugin",
723
+ "roblox/no-null": "js-plugin",
724
+ "roblox/no-private-identifier": "js-plugin",
725
+ "roblox/no-unsupported-syntax": "js-plugin",
726
+ "roblox/no-user-defined-lua-tuple": "js-plugin",
727
+ "roblox/no-value-typeof": "js-plugin",
728
+ "roblox/prefer-get-players": "js-plugin",
729
+ "roblox/prefer-task-library": "js-plugin",
730
+ "sentinel/prefer-math-min-max": "js-plugin",
731
+ "eslint-plugin/consistent-output": "js-plugin",
732
+ "eslint-plugin/fixer-return": "js-plugin",
733
+ "eslint-plugin/no-deprecated-context-methods": "js-plugin",
734
+ "eslint-plugin/no-deprecated-report-api": "js-plugin",
735
+ "eslint-plugin/no-identical-tests": "js-plugin",
736
+ "eslint-plugin/no-matching-violation-suggest-message-ids": "js-plugin",
737
+ "eslint-plugin/no-meta-replaced-by": "js-plugin",
738
+ "eslint-plugin/no-meta-schema-default": "js-plugin",
739
+ "eslint-plugin/no-missing-message-ids": "js-plugin",
740
+ "eslint-plugin/no-missing-placeholders": "js-plugin",
741
+ "eslint-plugin/no-only-tests": "js-plugin",
742
+ "eslint-plugin/no-unused-message-ids": "js-plugin",
743
+ "eslint-plugin/no-unused-placeholders": "js-plugin",
744
+ "eslint-plugin/no-useless-token-range": "js-plugin",
745
+ "eslint-plugin/prefer-message-ids": "js-plugin",
746
+ "eslint-plugin/prefer-object-rule": "js-plugin",
747
+ "eslint-plugin/prefer-output-null": "js-plugin",
748
+ "eslint-plugin/prefer-placeholders": "js-plugin",
749
+ "eslint-plugin/prefer-replace-text": "js-plugin",
750
+ "eslint-plugin/report-message-format": "js-plugin",
751
+ "eslint-plugin/require-meta-default-options": "js-plugin",
752
+ "eslint-plugin/require-meta-docs-description": "js-plugin",
753
+ "eslint-plugin/require-meta-docs-recommended": "js-plugin",
754
+ "eslint-plugin/require-meta-fixable": "js-plugin",
755
+ "eslint-plugin/require-meta-has-suggestions": "js-plugin",
756
+ "eslint-plugin/require-meta-schema": "js-plugin",
757
+ "eslint-plugin/require-meta-schema-description": "js-plugin",
758
+ "eslint-plugin/require-meta-type": "js-plugin",
759
+ "eslint-plugin/require-test-case-name": "js-plugin",
760
+ "eslint-plugin/require-test-error-positions": "js-plugin",
761
+ "eslint-plugin/test-case-property-ordering": "js-plugin",
762
+ "eslint-plugin/test-case-shorthand-strings": "js-plugin",
763
+ "eslint-plugin/unique-test-case-names": "js-plugin",
764
+ "erasable-syntax-only/enums": "js-plugin",
765
+ "erasable-syntax-only/import-aliases": "js-plugin",
766
+ "erasable-syntax-only/namespaces": "js-plugin",
767
+ "erasable-syntax-only/parameter-properties": "js-plugin"
768
+ };
769
+ /**
770
+ * Type-aware ESLint rules (meta.docs.requiresTypeChecking) that our shared
771
+ * rule maps reference. Oxlint jsPlugins have no type information, so these
772
+ * rules crash (e.g. Eslint-plugin-jest throws) or silently no-op (e.g.
773
+ * Eslint-plugin-sonarjs skips) when run inside oxlint. The oxlint factory
774
+ * must never emit them; in hybrid mode they stay in ESLint.
775
+ *
776
+ * A test asserts (against the plugins' runtime metadata) that no rule
777
+ * emitted as a jsPlugin requires type checking.
778
+ */
779
+ const typeAwareJsPluginRules = /* @__PURE__ */ new Set([
780
+ "eslint-plugin/no-property-in-node",
781
+ "jest/no-error-equal",
782
+ "jest/no-unnecessary-assertion",
783
+ "jest/unbound-method",
784
+ "jest/valid-expect-with-promise",
785
+ "react/no-implicit-children",
786
+ "react/no-implicit-key",
787
+ "react/no-implicit-ref",
788
+ "react/no-leaked-conditional-rendering",
789
+ "react/no-unused-props",
790
+ "sonar/no-ignored-return",
791
+ "sonar/no-incompatible-assertion-types",
792
+ "sonar/no-redundant-optional",
793
+ "sonar/no-try-promise",
794
+ "sonar/prefer-immediate-return",
795
+ "ts/prefer-destructuring",
796
+ "unicorn/no-non-function-verb-prefix"
797
+ ]);
798
+ /**
799
+ * Rules that run without type information but produce better results with it:
800
+ * they widen their detection (or avoid false negatives) when the TypeScript
801
+ * parser services are available, and silently fall back to a syntax-only
802
+ * heuristic otherwise. Oxlint jsPlugins have no type information, so these
803
+ * rules would run in their degraded form there; they stay in ESLint, where the
804
+ * type-aware pass gives them a program.
805
+ *
806
+ * Unlike {@link typeAwareJsPluginRules} these do not declare
807
+ * `meta.docs.requiresTypeChecking`, so the metadata-based parity test cannot
808
+ * catch them; they are listed manually.
809
+ */
810
+ const optionallyTypeAwareRules = /* @__PURE__ */ new Set([
811
+ "e18e/prefer-array-at",
812
+ "e18e/prefer-array-to-reversed",
813
+ "e18e/prefer-array-to-sorted",
814
+ "e18e/prefer-spread-syntax",
815
+ "unicorn/no-useless-coercion"
816
+ ]);
817
+ /**
818
+ * ESLint rules covered on the oxlint side by a differently-named native oxc
819
+ * rule (rather than by translating the rule itself). They are treated as
820
+ * oxlint-covered, so hybrid mode drops them from ESLint and lets the oxc rule
821
+ * run instead; {@link translateRuleToOxlint} resolves them to the oxc rule so
822
+ * the coverage checks compare against the rule that actually runs. In
823
+ * ESLint-only mode oxlint does not run, so they stay enabled in ESLint.
824
+ *
825
+ * Their canonical ESLint versions must not also run inside oxlint as jsPlugins
826
+ * (they are in {@link excludedFromOxlint}), which would double-report.
827
+ */
828
+ const oxcCoveredRules = {
829
+ "sonar/no-all-duplicated-branches": "oxc/branches-sharing-code",
830
+ "unicorn/no-accidental-bitwise-operator": "oxc/bad-bitwise-operator"
831
+ };
832
+ /**
833
+ * Rules that must not run inside oxlint at all (even in standalone mode),
834
+ * either because they misbehave under oxlint's jsPlugin runtime or because a
835
+ * native oxc rule already covers the oxlint side. See {@link staysInEslint}
836
+ * and {@link oxcCoveredRules} for the reasons.
837
+ */
838
+ const excludedFromOxlint = /* @__PURE__ */ new Set([
839
+ ...Object.keys(oxcCoveredRules),
840
+ "unicorn/no-unsafe-string-replacement",
841
+ ...typeAwareJsPluginRules,
842
+ ...optionallyTypeAwareRules
843
+ ]);
844
+ /**
845
+ * TypeScript extension rules that map to oxlint's core (TypeScript-aware)
846
+ * implementations.
847
+ */
848
+ const TS_EXTENSION_TO_CORE = /* @__PURE__ */ new Set([
849
+ "default-param-last",
850
+ "no-empty-function",
851
+ "no-shadow",
852
+ "no-unused-expressions",
853
+ "no-unused-private-class-members",
854
+ "no-useless-constructor"
855
+ ]);
856
+ /** Unicorn rules renamed in eslint-plugin-unicorn v70 but not in oxlint. */
857
+ const UNICORN_NATIVE_RENAMES = { "no-for-each": "no-array-for-each" };
858
+ /**
859
+ * Prefix translation for rules that run via jsPlugins. Native oxlint plugin
860
+ * prefixes are reserved, so those jsPlugins use `-js` aliases.
861
+ */
862
+ const oxlintJsPluginPrefixRenames = {
863
+ "": "eslint-js",
864
+ "import": "import-js",
865
+ "jest": "jest-js",
866
+ "jsdoc": "jsdoc-js",
867
+ "node": "node-js",
868
+ "promise": "promise-js",
869
+ "react": "react-x",
870
+ "unicorn": "unicorn-js",
871
+ "vitest": "vitest-js"
872
+ };
873
+ /** JsPlugin package specifiers keyed by their oxlint-side prefix. */
874
+ const oxlintJsPlugins = {
875
+ "@cspell": "@cspell/eslint-plugin",
876
+ "antfu": "eslint-plugin-antfu",
877
+ "better-max-params": "eslint-plugin-better-max-params",
878
+ "comment-length": "eslint-plugin-comment-length",
879
+ "de-morgan": "eslint-plugin-de-morgan",
880
+ "e18e": "@e18e/eslint-plugin",
881
+ "erasable-syntax-only": "eslint-plugin-erasable-syntax-only",
882
+ "eslint-js": "oxlint-plugin-eslint",
883
+ "eslint-plugin": "eslint-plugin-eslint-plugin",
884
+ "flawless": "eslint-plugin-flawless",
885
+ "import-js": "eslint-plugin-import-lite",
886
+ "jest-extended": "eslint-plugin-jest-extended",
887
+ "jest-js": "eslint-plugin-jest",
888
+ "jsdoc-js": "eslint-plugin-jsdoc",
889
+ "node-js": "eslint-plugin-n",
890
+ "oxfmt": "eslint-plugin-oxfmt",
891
+ "oxlint-comments": "oxlint-plugin-oxlint-comments",
892
+ "perfectionist": "eslint-plugin-perfectionist",
893
+ "promise-js": "eslint-plugin-promise",
894
+ "react-jsx": "eslint-plugin-react-jsx",
895
+ "react-naming-convention": "eslint-plugin-react-naming-convention",
896
+ "react-x": "eslint-plugin-react-x",
897
+ "roblox": "eslint-plugin-roblox-ts",
898
+ "sentinel": "eslint-plugin-sentinel",
899
+ "small-rules": "@pobammer-ts/small-rules",
900
+ "sonar": "eslint-plugin-sonarjs",
901
+ "style": "@stylistic/eslint-plugin",
902
+ "ts": "@typescript-eslint/eslint-plugin",
903
+ "unicorn-js": "eslint-plugin-unicorn",
904
+ "unused-imports": "eslint-plugin-unused-imports",
905
+ "vitest-js": "@vitest/eslint-plugin"
906
+ };
907
+ /**
908
+ * Whether the given ESLint rule (canonical renamed name) is covered by oxlint
909
+ * in hybrid mode.
910
+ *
911
+ * @param rule - The canonical ESLint rule name.
912
+ * @returns Whether oxlint covers the rule.
913
+ */
914
+ function isOxlintCovered(rule) {
915
+ return rule in oxlintRuleMapping || rule in oxcCoveredRules;
916
+ }
917
+ /**
918
+ * Whether the rule runs via oxlint-tsgolint (`oxlint --type-aware`); such rules
919
+ * are noise without type information, so they are gated on `typeAware`.
920
+ *
921
+ * @param rule - The canonical ESLint rule name.
922
+ * @returns Whether the rule is executed by oxlint-tsgolint.
923
+ */
924
+ function isTsgolintRule(rule) {
925
+ return oxlintRuleMapping[rule] === "tsgolint";
926
+ }
927
+ /**
928
+ * Translate a canonical ESLint rule name into its oxlint configuration name.
929
+ *
930
+ * @param rule - The canonical ESLint rule name.
931
+ * @returns The rule name to use in an oxlint configuration.
932
+ */
933
+ function translateRuleToOxlint(rule) {
934
+ const oxcCovered = oxcCoveredRules[rule];
935
+ if (oxcCovered !== void 0) return oxcCovered;
936
+ const target = oxlintRuleMapping[rule];
937
+ const { name, prefix } = splitRuleName(rule);
938
+ if (target === "native") {
939
+ if (prefix === "") return name;
940
+ if (prefix === "ts") return TS_EXTENSION_TO_CORE.has(name) ? name : `typescript/${name}`;
941
+ if (prefix === "unicorn") return `unicorn/${UNICORN_NATIVE_RENAMES[name] ?? name}`;
942
+ return rule;
943
+ }
944
+ if (target === "tsgolint") return `typescript/${name}`;
945
+ const renamed = oxlintJsPluginPrefixRenames[prefix];
946
+ return renamed === void 0 ? rule : `${renamed}/${name}`;
947
+ }
948
+ /**
949
+ * Whether a rule is a TypeScript extension rule that oxlint implements as its
950
+ * TypeScript-aware core rule, so `ts/<name>` and the bare core `<name>`
951
+ * collapse onto a single native entry. The extension entry must win.
952
+ *
953
+ * @param rule - The canonical ESLint rule name.
954
+ * @returns Whether the rule collapses onto a shared native core rule.
955
+ */
956
+ function collapsesToTsCoreRule(rule) {
957
+ const { name, prefix } = splitRuleName(rule);
958
+ return prefix === "ts" && TS_EXTENSION_TO_CORE.has(name);
959
+ }
960
+ /**
961
+ * Whether a rule is the bare core counterpart of a collapsing TypeScript
962
+ * extension rule; its severity must yield to the extension entry.
963
+ *
964
+ * @param rule - The canonical ESLint rule name.
965
+ * @returns Whether the rule is such a core counterpart.
966
+ */
967
+ function isTsCoreCounterpartRule(rule) {
968
+ const { name, prefix } = splitRuleName(rule);
969
+ return prefix === "" && TS_EXTENSION_TO_CORE.has(name);
970
+ }
971
+ function splitRuleName(rule) {
972
+ const slashIndex = rule.indexOf("/");
973
+ if (slashIndex === -1) return {
974
+ name: rule,
975
+ prefix: ""
976
+ };
977
+ return {
978
+ name: rule.slice(slashIndex + 1),
979
+ prefix: rule.slice(0, slashIndex)
980
+ };
981
+ }
982
+ //#endregion
983
+ //#region src/globs.ts
984
+ const GLOB_ROOT = [
985
+ "*",
986
+ "packages/*/*",
987
+ "apps/*/*",
988
+ "libs/*/*",
989
+ "packages/*/*/*",
990
+ "apps/*/*/*",
991
+ "libs/*/*/*"
992
+ ];
993
+ const GLOB_SRC_EXT = "{,c,m}[jt]s{,x}";
994
+ const GLOB_SRC = "**/*.{,c,m}[jt]s{,x}";
995
+ const GLOB_JSX = "**/*.{,c,m}jsx";
996
+ const GLOB_TSX = "**/*.{,c,m}tsx";
997
+ const GLOB_DTS = "**/*.d.{,c,m}ts";
998
+ const GLOB_BIN = ["**/bin/**/*", `**/bin.${GLOB_SRC_EXT}`];
999
+ /**
1000
+ * Type tests (`expectTypeOf` / `assertType`), run by Vitest's `typecheck`
1001
+ * mode. They are test files, but assert nothing at runtime.
1002
+ */
1003
+ const GLOB_TYPE_TESTS = `**/*.{test,spec}-d.${GLOB_SRC_EXT}`;
1004
+ const GLOB_TESTS = [
1005
+ `**/__tests__/**/*.${GLOB_SRC_EXT}`,
1006
+ `**/*.spec.${GLOB_SRC_EXT}`,
1007
+ `**/*.test.${GLOB_SRC_EXT}`,
1008
+ GLOB_TYPE_TESTS,
1009
+ `**/*.bench.${GLOB_SRC_EXT}`,
1010
+ `**/*.benchmark.${GLOB_SRC_EXT}`
1011
+ ];
1012
+ const GLOB_BUILD_TOOLS = [
1013
+ "**/.husky/**/*",
1014
+ `**/config/${GLOB_SRC}`,
1015
+ `**/*config.${GLOB_SRC_EXT}`,
1016
+ `**/*config.*.${GLOB_SRC_EXT}`,
1017
+ `**/build/${GLOB_SRC}`,
1018
+ `**/tools/${GLOB_SRC}`,
1019
+ `**/setup/${GLOB_SRC}`,
1020
+ `**/dev/${GLOB_SRC}`,
1021
+ `**/tasks/${GLOB_SRC}`,
1022
+ "**/.github/scripts/**/*"
1023
+ ];
1024
+ const GLOB_EXCLUDE = [
1025
+ "**/.pnpm-store",
1026
+ "**/bun.lockb",
1027
+ "**/dist",
1028
+ "**/node_modules",
1029
+ "**/package-lock.json",
1030
+ "**/pnpm-lock.yaml",
1031
+ "**/yarn.lock",
1032
+ "**/skills-lock.json",
1033
+ "**/.cache",
1034
+ "**/.changeset",
1035
+ "**/.agents",
1036
+ "**/.claude/**/*",
1037
+ "!**/.claude/settings.json",
1038
+ "**/.gemini",
1039
+ "**/.kilocode",
1040
+ "**/.opencode",
1041
+ "**/.history",
1042
+ "**/.idea",
1043
+ "**/.next",
1044
+ "**/.nuxt",
1045
+ "**/.output",
1046
+ "**/.svelte-kit",
1047
+ "**/.temp",
1048
+ "**/.tmp",
1049
+ "**/.vercel",
1050
+ "**/.vite-inspect",
1051
+ "**/.vitepress/cache",
1052
+ "**/.worktree",
1053
+ "**/.yarn",
1054
+ "**/coverage",
1055
+ "**/out",
1056
+ "**/output",
1057
+ "**/temp",
1058
+ "**/tmp",
1059
+ "**/vite.config.*.timestamp-*",
1060
+ "**/eslint-suppressions.json",
1061
+ "**/*.min.*",
1062
+ "**/CHANGELOG*.md",
1063
+ "**/LICENSE*",
1064
+ "**/__snapshots__",
1065
+ "**/auto-import{,s}.d.ts",
1066
+ "**/components.d.ts",
1067
+ "**/roblox.yml"
1068
+ ];
1069
+ //#endregion
1070
+ //#region src/prettier-config.ts
1071
+ /**
1072
+ * Synchronous resolution of the effective Prettier-shaped formatting settings
1073
+ * (`printWidth`, `tabWidth`, `useTabs`, ...) shared by both factories.
1074
+ *
1075
+ * These settings do not only drive formatting: `printWidth` and `tabWidth`
1076
+ * feed rule options such as `flawless/arrow-return-style`'s `maxLen`,
1077
+ * `tabWidth` and `useOxfmt.printWidth`. The ESLint and oxlint factories must
1078
+ * therefore resolve them identically, or the same rule reaches opposite
1079
+ * conclusions in each engine and `--fix` ping-pongs the code between both
1080
+ * forms.
1081
+ *
1082
+ * Prettier's own `resolveConfig` is async-only since v3, and the oxlint
1083
+ * factory is synchronous, so resolution is reimplemented here against the
1084
+ * config formats that can be read synchronously. TypeScript config files
1085
+ * (`prettier.config.ts` and friends) need a loader and are skipped.
1086
+ */
1087
+ /** Preset defaults, overridden by any project-level configuration. */
1088
+ const PRETTIER_DEFAULTS = {
1089
+ arrowParens: "always",
1090
+ printWidth: 100,
1091
+ quoteProps: "consistent",
1092
+ semi: true,
1093
+ singleQuote: false,
1094
+ tabWidth: 4,
1095
+ trailingComma: "all",
1096
+ useTabs: true
1097
+ };
1098
+ /**
1099
+ * Config file names searched in each directory, in Prettier's own precedence
1100
+ * order. `package.json` is handled separately (it only counts when it carries
1101
+ * a `prettier` key).
1102
+ */
1103
+ const CONFIG_FILES = [
1104
+ ".prettierrc",
1105
+ ".prettierrc.json",
1106
+ ".prettierrc.jsonc",
1107
+ ".prettierrc.json5",
1108
+ ".prettierrc.yaml",
1109
+ ".prettierrc.yml",
1110
+ ".prettierrc.js",
1111
+ ".prettierrc.cjs",
1112
+ ".prettierrc.mjs",
1113
+ "prettier.config.js",
1114
+ "prettier.config.cjs",
1115
+ "prettier.config.mjs"
1116
+ ];
1117
+ /**
1118
+ * EditorConfig section matched against a representative source file rather
1119
+ * than `package.json`: the settings feed JS/TS rule options, so per-extension
1120
+ * sections must resolve the way they do for the files those rules lint.
1121
+ */
1122
+ const EDITORCONFIG_PROBE_FILE = "index.ts";
1123
+ const LINE_BREAK = /\r?\n/;
1124
+ const LEADING_SLASH = /^\//;
1125
+ const REGEXP_SPECIAL = /[$()*+.?[\\\]^{|}]/g;
1126
+ /**
1127
+ * Glob constructs expanded by {@link expandGlobToken}; anything else is
1128
+ * escaped.
1129
+ */
1130
+ const GLOB_TOKEN = /\*\*\/?|\{[^{}]*\}|[$()*+.?[\\\]^{|}]/g;
1131
+ /**
1132
+ * Resolve the project's Prettier configuration, EditorConfig included, without
1133
+ * awaiting.
1134
+ *
1135
+ * @param cwd - Directory to start the upward search from.
1136
+ * @returns The resolved options, or an empty object when none are found.
1137
+ */
1138
+ function resolveProjectPrettierConfig(cwd = process.cwd()) {
1139
+ return {
1140
+ ...resolveEditorConfigOptions(cwd),
1141
+ ...resolvePrettierRcOptions(cwd)
1142
+ };
1143
+ }
1144
+ /**
1145
+ * Resolve the effective formatting settings: preset defaults, overridden by
1146
+ * the project's EditorConfig and Prettier configuration, overridden by the
1147
+ * options passed to the factory.
1148
+ *
1149
+ * @param prettierOptions - Explicit `formatters.prettierOptions`.
1150
+ * @param cwd - Directory to resolve project configuration from.
1151
+ * @returns The merged settings.
1152
+ */
1153
+ function resolvePrettierSettings(prettierOptions = {}, cwd = process.cwd()) {
1154
+ return {
1155
+ ...PRETTIER_DEFAULTS,
1156
+ ...resolveProjectPrettierConfig(cwd),
1157
+ ...prettierOptions
1158
+ };
1159
+ }
1160
+ function readFileOrUndefined(filePath) {
1161
+ try {
1162
+ return fs.readFileSync(filePath, "utf-8");
1163
+ } catch {
1164
+ return;
1165
+ }
1166
+ }
1167
+ function parseJsonLike(contents) {
1168
+ try {
1169
+ return getStaticJSONValue(parseJSON(contents, { jsonSyntax: "json5" }));
1170
+ } catch {
1171
+ return;
1172
+ }
1173
+ }
1174
+ function isRecord$1(value) {
1175
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1176
+ }
1177
+ function readPackageJsonPrettierKey(filePath) {
1178
+ const contents = readFileOrUndefined(filePath);
1179
+ if (contents === void 0) return;
1180
+ const parsed = parseJsonLike(contents);
1181
+ const prettierKey = isRecord$1(parsed) ? parsed["prettier"] : void 0;
1182
+ return isRecord$1(prettierKey) ? prettierKey : void 0;
1183
+ }
1184
+ /**
1185
+ * Load a JS Prettier config. Node's `require` handles ESM entry points
1186
+ * synchronously on the versions this package supports, so both module systems
1187
+ * work; a config that cannot be loaded is treated as absent rather than fatal.
1188
+ *
1189
+ * @param filePath - Absolute path of the config module.
1190
+ * @returns The exported options, or `undefined`.
1191
+ */
1192
+ function requireConfigModule(filePath) {
1193
+ try {
1194
+ const loaded = createRequire(filePath)(filePath);
1195
+ const value = isRecord$1(loaded) && "default" in loaded ? loaded["default"] : loaded;
1196
+ return isRecord$1(value) ? value : void 0;
1197
+ } catch {
1198
+ return;
1199
+ }
1200
+ }
1201
+ function parseYamlLike(contents) {
1202
+ try {
1203
+ return getStaticYAMLValue(parseYAML(contents));
1204
+ } catch {
1205
+ return;
1206
+ }
1207
+ }
1208
+ function readConfigFile(filePath) {
1209
+ const extension = path.extname(filePath);
1210
+ if (extension === ".js" || extension === ".cjs" || extension === ".mjs") return fs.existsSync(filePath) ? requireConfigModule(filePath) : void 0;
1211
+ const contents = readFileOrUndefined(filePath);
1212
+ if (contents === void 0) return;
1213
+ const parsed = parseJsonLike(contents) ?? parseYamlLike(contents);
1214
+ return isRecord$1(parsed) ? parsed : void 0;
1215
+ }
1216
+ function ancestorDirectories(cwd) {
1217
+ const directories = [];
1218
+ let current = path.resolve(cwd);
1219
+ while (true) {
1220
+ directories.push(current);
1221
+ const parent = path.dirname(current);
1222
+ if (parent === current) return directories;
1223
+ current = parent;
1224
+ }
1225
+ }
1226
+ /**
1227
+ * Walk up from `cwd` looking for a Prettier configuration file.
1228
+ *
1229
+ * @param cwd - Directory to start the upward search from.
1230
+ * @returns The first configuration found, or an empty object.
1231
+ */
1232
+ function resolvePrettierRcOptions(cwd) {
1233
+ for (const directory of ancestorDirectories(cwd)) {
1234
+ const fromPackageJson = readPackageJsonPrettierKey(path.join(directory, "package.json"));
1235
+ if (fromPackageJson !== void 0) return fromPackageJson;
1236
+ for (const filename of CONFIG_FILES) {
1237
+ const config = readConfigFile(path.join(directory, filename));
1238
+ if (config !== void 0) return config;
1239
+ }
1240
+ }
1241
+ return {};
1242
+ }
1243
+ function editorConfigToPrettier(properties) {
1244
+ const options = {};
1245
+ const indentStyle = properties["indent_style"];
1246
+ if (indentStyle === "tab" || indentStyle === "space") options.useTabs = indentStyle === "tab";
1247
+ const indentSize = properties["indent_size"];
1248
+ const width = indentSize === void 0 || indentSize === "tab" ? properties["tab_width"] : indentSize;
1249
+ const tabWidth = Number.parseInt(width ?? "", 10);
1250
+ if (Number.isFinite(tabWidth)) options.tabWidth = tabWidth;
1251
+ const printWidth = Number.parseInt(properties["max_line_length"] ?? "", 10);
1252
+ if (Number.isFinite(printWidth)) options.printWidth = printWidth;
1253
+ const endOfLine = properties["end_of_line"];
1254
+ if (endOfLine === "cr" || endOfLine === "crlf" || endOfLine === "lf") options.endOfLine = endOfLine;
1255
+ return options;
1256
+ }
1257
+ function parseEditorConfig(contents) {
1258
+ const sections = [];
1259
+ let current;
1260
+ let isRoot = false;
1261
+ for (const rawLine of contents.split(LINE_BREAK)) {
1262
+ const line = rawLine.trim();
1263
+ if (line === "" || line.startsWith("#") || line.startsWith(";")) continue;
1264
+ if (line.startsWith("[") && line.endsWith("]")) {
1265
+ current = {
1266
+ pattern: line.slice(1, -1),
1267
+ properties: {}
1268
+ };
1269
+ sections.push(current);
1270
+ continue;
1271
+ }
1272
+ const separator = line.indexOf("=");
1273
+ if (separator === -1) continue;
1274
+ const key = line.slice(0, separator).trim().toLowerCase();
1275
+ const value = line.slice(separator + 1).trim();
1276
+ if (current === void 0) {
1277
+ isRoot ||= key === "root" && value.toLowerCase() === "true";
1278
+ continue;
1279
+ }
1280
+ current.properties[key] = value;
1281
+ }
1282
+ return {
1283
+ isRoot,
1284
+ sections
1285
+ };
1286
+ }
1287
+ function escapeRegExp(value) {
1288
+ return value.replaceAll(REGEXP_SPECIAL, (character) => `\\${character}`);
1289
+ }
1290
+ /**
1291
+ * Expand one glob token into its regular-expression equivalent.
1292
+ *
1293
+ * @param token - A token matched by {@link GLOB_TOKEN}.
1294
+ * @returns The regular-expression source for the token.
1295
+ */
1296
+ function expandGlobToken(token) {
1297
+ if (token.startsWith("**")) return "(?:.*)";
1298
+ if (token === "*") return "[^/]*";
1299
+ if (token === "?") return "[^/]";
1300
+ if (token.startsWith("{")) return `(?:${token.slice(1, -1).split(",").map((alternative) => escapeRegExp(alternative)).join("|")})`;
1301
+ return `\\${token}`;
1302
+ }
1303
+ /**
1304
+ * Match an EditorConfig section pattern against a path relative to the
1305
+ * `.editorconfig` file. Supports the `*`, `**`, `?` and `{a,b}` constructs;
1306
+ * numeric ranges and character classes are rare enough in formatting sections
1307
+ * to fall through as non-matches.
1308
+ *
1309
+ * @param pattern - The section pattern.
1310
+ * @param relativePath - Path relative to the `.editorconfig` directory.
1311
+ * @returns Whether the section applies.
1312
+ */
1313
+ function matchesEditorConfigPattern(pattern, relativePath) {
1314
+ const normalized = pattern.includes("/") ? pattern.replace(LEADING_SLASH, "") : `**/${pattern}`;
1315
+ const subject = relativePath.split(path.sep).join("/");
1316
+ const source = normalized.replaceAll(GLOB_TOKEN, (token) => expandGlobToken(token));
1317
+ return new RegExp(`^${source}$`).test(subject);
1318
+ }
1319
+ /**
1320
+ * Resolve EditorConfig settings for a representative source file and translate
1321
+ * them into their Prettier equivalents.
1322
+ *
1323
+ * @param cwd - Directory to start the upward search from.
1324
+ * @returns The translated options.
1325
+ */
1326
+ function resolveEditorConfigOptions(cwd) {
1327
+ const properties = {};
1328
+ for (const directory of ancestorDirectories(cwd).toReversed()) {
1329
+ const contents = readFileOrUndefined(path.join(directory, ".editorconfig"));
1330
+ if (contents === void 0) continue;
1331
+ const { isRoot, sections } = parseEditorConfig(contents);
1332
+ const relative = path.relative(directory, path.join(cwd, EDITORCONFIG_PROBE_FILE));
1333
+ const forProbe = sections.filter(({ pattern }) => matchesEditorConfigPattern(pattern, relative)).reduce((accumulator, section) => Object.assign(accumulator, section.properties), {});
1334
+ if (isRoot) for (const key of Object.keys(properties)) delete properties[key];
1335
+ Object.assign(properties, forProbe);
1336
+ }
1337
+ return editorConfigToPrettier(properties);
1338
+ }
1339
+ //#endregion
1340
+ //#region src/rules/oxfmt.ts
1341
+ const UNSUPPORTED_PRETTIER_KEYS = /* @__PURE__ */ new Set([
1342
+ "experimentalOperatorPosition",
1343
+ "experimentalTernaries",
1344
+ "jsdocPreferCodeFences",
1345
+ "jsdocPrintWidth",
1346
+ "parser",
1347
+ "plugins",
1348
+ "tsdoc"
1349
+ ]);
1350
+ const defaultSortImports = {
1351
+ customGroups: [{
1352
+ elementNamePattern: ["react"],
1353
+ groupName: "react"
1354
+ }, {
1355
+ elementNamePattern: ["@*/**"],
1356
+ groupName: "scoped"
1357
+ }],
1358
+ groups: [
1359
+ "react",
1360
+ "scoped",
1361
+ [
1362
+ "type-builtin",
1363
+ "type-external",
1364
+ "builtin",
1365
+ "external"
1366
+ ],
1367
+ [
1368
+ "type-internal",
1369
+ "internal",
1370
+ "type-parent",
1371
+ "type-sibling",
1372
+ "type-index",
1373
+ "parent",
1374
+ "sibling",
1375
+ "index"
1376
+ ],
1377
+ "unknown"
1378
+ ],
1379
+ newlinesBetween: true
1380
+ };
1381
+ /**
1382
+ * Migrate Prettier options to oxfmt options, dropping unsupported keys.
1383
+ *
1384
+ * @param prettierOptions - The Prettier options to migrate.
1385
+ * @returns The migrated oxfmt options.
1386
+ */
1387
+ function migratePrettierOptions(prettierOptions) {
1388
+ const oxfmtOptions = {};
1389
+ for (const [key, value] of Object.entries(prettierOptions)) {
1390
+ if (UNSUPPORTED_PRETTIER_KEYS.has(key)) continue;
1391
+ if (key === "endOfLine" && value === "auto") continue;
1392
+ oxfmtOptions[key] = value;
1393
+ }
1394
+ return oxfmtOptions;
1395
+ }
1396
+ /**
1397
+ * Build the effective oxfmt options shared between the ESLint and oxlint
1398
+ * factories.
1399
+ *
1400
+ * @param options - The option sources, in increasing precedence.
1401
+ * @returns The effective oxfmt options.
1402
+ */
1403
+ function buildOxfmtOptions({ oxfmtConfigOptions = {}, oxfmtOptions, prettierOptions = {} }) {
1404
+ return {
1405
+ sortImports: defaultSortImports,
1406
+ sortPackageJson: false,
1407
+ ...migratePrettierOptions(prettierOptions),
1408
+ ...oxfmtConfigOptions,
1409
+ ...oxfmtOptions
1410
+ };
1411
+ }
1412
+ //#endregion
1413
+ //#region src/guards.ts
1414
+ /**
1415
+ * Internal runtime type guards. Prefer these over `as` assertions so values
1416
+ * crossing untyped boundaries (`JSON.parse`, dynamic `import`, plugin objects)
1417
+ * are validated at runtime rather than asserted away.
1418
+ */
1419
+ /**
1420
+ * Whether a value is a non-null, non-array object usable as a string-keyed
1421
+ * record. Narrows `unknown` without an assertion.
1422
+ *
1423
+ * @param value - The value to test.
1424
+ * @returns Whether the value is a plain object.
1425
+ */
1426
+ function isRecord(value) {
1427
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1428
+ }
1429
+ createRequire(import.meta.url);
1430
+ /**
1431
+ * Whether the environment signals a CI run. Treats an unset, empty, `"false"`
1432
+ * or `"0"` `CI` value as not-CI, so a deliberately falsy `CI=false` is honoured
1433
+ * rather than read as truthy.
1434
+ *
1435
+ * @param environment - The environment variables to inspect.
1436
+ * @returns Whether CI is active.
1437
+ */
1438
+ function isCi(environment = process.env) {
1439
+ const value = environment["CI"];
1440
+ return value !== void 0 && value !== "" && value !== "false" && value !== "0";
1441
+ }
1442
+ /**
1443
+ * Finds the Node major version the linted project targets.
1444
+ *
1445
+ * The shared `settings.n.version` / `settings.node.version` values win, so the
1446
+ * setting `eslint-plugin-n` already reads doubles as the override for every
1447
+ * version-gated rule in the preset. Failing that, this walks up from `cwd` for
1448
+ * the nearest `package.json` that declares a version — leaf manifests in a
1449
+ * workspace frequently omit the field and leave it to the root, so a manifest
1450
+ * without it is skipped rather than treated as an answer.
1451
+ *
1452
+ * A range that cannot be parsed resolves to `undefined`, which leaves
1453
+ * version-gated rules off rather than enabling them for a runtime that cannot
1454
+ * support them.
1455
+ *
1456
+ * @param settings - Shared ESLint/oxlint settings to read the override from.
1457
+ * @param cwd - Directory to start searching from.
1458
+ * @returns The targeted Node major, or `undefined` when nothing declares one.
1459
+ */
1460
+ function resolveNodeMajor(settings, cwd = process.cwd()) {
1461
+ const configured = readSettingsNodeVersion(settings);
1462
+ if (configured !== void 0) return parseNodeMajor(configured);
1463
+ let searchFrom = cwd;
1464
+ while (true) {
1465
+ const manifestPath = findUpSync("package.json", { cwd: searchFrom });
1466
+ if (manifestPath === void 0) return;
1467
+ let manifest = {};
1468
+ try {
1469
+ const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
1470
+ if (isRecord(parsed)) manifest = parsed;
1471
+ } catch {}
1472
+ const range = readNodeRange(manifest);
1473
+ if (range !== void 0) return parseNodeMajor(range);
1474
+ const parent = path.dirname(path.dirname(manifestPath));
1475
+ if (parent === searchFrom) return;
1476
+ searchFrom = parent;
1477
+ }
1478
+ }
1479
+ function resolveSubOptions(options, key) {
1480
+ const optionValue = options[key];
1481
+ return optionValue === true || optionValue === false || optionValue === void 0 ? {} : optionValue;
1482
+ }
1483
+ function toSourceGlob(glob) {
1484
+ return glob.startsWith("!") ? `!${glob.slice(1)}.${GLOB_SRC_EXT}` : `${glob}.${GLOB_SRC_EXT}`;
1485
+ }
1486
+ function getOverrides(options, key) {
1487
+ const sub = resolveSubOptions(options, key);
1488
+ if (!isRecord(sub)) return {
1489
+ overrides: {},
1490
+ overridesTypeAware: {}
1491
+ };
1492
+ const shape = sub;
1493
+ return {
1494
+ files: shape.files,
1495
+ filesTypeAware: shape.filesTypeAware,
1496
+ ignoresTypeAware: shape.ignoresTypeAware,
1497
+ overrides: { ...shape.overrides },
1498
+ overridesTypeAware: { ...shape.overridesTypeAware }
1499
+ };
1500
+ }
1501
+ function isInGitHooksOrLintStaged(environment = process.env) {
1502
+ return [
1503
+ environment["GIT_HOOK"],
1504
+ environment["GIT_PARAMS"],
1505
+ environment["VSCODE_GIT_COMMAND"],
1506
+ environment["npm_lifecycle_script"]?.startsWith("lint-staged")
1507
+ ].some(Boolean);
1508
+ }
1509
+ /**
1510
+ * Agent probes, mirroring `unjs/std-env`'s `src/agents.ts` (@50dc652) so the
1511
+ * two stay diffable. An entry is either an environment variable that is truthy
1512
+ * only inside that agent, or a matcher for the agents that hide in a shared
1513
+ * variable. Entries run in std-env's order, one agent per line group.
1514
+ *
1515
+ * `CLAUDE_CODE_ENTRYPOINT` has no std-env counterpart; it predates this port
1516
+ * and stays for the Claude Code versions that set only it.
1517
+ */
1518
+ const AGENT_CHECKS = [
1519
+ "CLAUDECODE",
1520
+ "CLAUDE_CODE",
1521
+ "CLAUDE_CODE_ENTRYPOINT",
1522
+ "REPL_ID",
1523
+ "GEMINI_CLI",
1524
+ "CODEX_SANDBOX",
1525
+ "CODEX_THREAD_ID",
1526
+ "OPENCODE",
1527
+ environmentMatcher("PATH", /\.pi[/\\]agent/),
1528
+ "AUGMENT_AGENT",
1529
+ "GOOSE_PROVIDER",
1530
+ "JUNIE_DATA",
1531
+ "JUNIE_SHIM_PATH",
1532
+ environmentMatcher("EDITOR", /devin/),
1533
+ "CURSOR_AGENT",
1534
+ environmentMatcher("TERM_PROGRAM", /kiro/, { noTty: true })
1535
+ ];
1536
+ /**
1537
+ * Whether the process runs inside an AI coding agent session.
1538
+ *
1539
+ * `AI_AGENT` forces detection on, matching std-env's explicit override. Git
1540
+ * hook and lint-staged runs are excluded even under an agent: those are
1541
+ * human-initiated commits whose output a person reads.
1542
+ *
1543
+ * Not a pure function of `environment` — the kiro check also consults
1544
+ * `process.stdout.isTTY` (see {@link environmentMatcher}).
1545
+ *
1546
+ * @param environment - The environment variables to inspect.
1547
+ * @returns Whether an agent session was detected.
1548
+ */
1549
+ function isInAgentSession(environment = process.env) {
1550
+ if (isInGitHooksOrLintStaged(environment)) return false;
1551
+ if (environment["AI_AGENT"] !== void 0 && environment["AI_AGENT"] !== "") return true;
1552
+ return AGENT_CHECKS.some((check) => {
1553
+ return typeof check === "string" ? Boolean(environment[check]) : check(environment);
1554
+ });
1555
+ }
1556
+ function isInEditorEnvironment(environment = process.env) {
1557
+ const explicitValue = environment["ESLINT_IN_EDITOR"];
1558
+ if (explicitValue !== void 0) return explicitValue === "true" || explicitValue === "1";
1559
+ if (isCi(environment)) return false;
1560
+ if (isInGitHooksOrLintStaged(environment)) return false;
1561
+ return [
1562
+ environment["VSCODE_PID"],
1563
+ environment["VSCODE_CWD"],
1564
+ environment["JETBRAINS_IDE"],
1565
+ environment["VIM"],
1566
+ environment["NVIM"]
1567
+ ].some(Boolean);
1568
+ }
1569
+ /**
1570
+ * Merge custom glob patterns.
1571
+ *
1572
+ * - Patterns starting with "!" are used to remove matching patterns
1573
+ * - Other patterns are added to the result.
1574
+ *
1575
+ * @example
1576
+ *
1577
+ * ```ts
1578
+ * const result = mergeGlobs(GLOB_ROOT, ["places/**", "!apps/**"]);
1579
+ * // Returns: ["*", "packages/**", "libs/**", "places/**"]
1580
+ * ```
1581
+ *
1582
+ * @param globs - The default root glob patterns.
1583
+ * @param additionalPatterns - Custom root patterns to merge (optional).
1584
+ * @returns The merged array of glob patterns.
1585
+ */
1586
+ function mergeGlobs(globs, additionalPatterns) {
1587
+ if (!additionalPatterns || additionalPatterns.length === 0) return [...globs];
1588
+ let result = [...globs];
1589
+ for (const pattern of additionalPatterns) if (pattern.startsWith("!")) {
1590
+ const patternToRemove = pattern.slice(1);
1591
+ result = result.filter((item) => item !== patternToRemove);
1592
+ } else result.push(pattern);
1593
+ return result;
1594
+ }
1595
+ /**
1596
+ * Rename plugin prefixes in a rule object. Accepts a map of prefixes to rename.
1597
+ *
1598
+ * @example
1599
+ *
1600
+ * ```ts
1601
+ * import { renameRules } from "@antfu/eslint-config";
1602
+ *
1603
+ * export default [
1604
+ * {
1605
+ * rules: renameRules(
1606
+ * {
1607
+ * "@typescript-eslint/indent": "error",
1608
+ * },
1609
+ * { "@typescript-eslint": "ts" },
1610
+ * ),
1611
+ * },
1612
+ * ];
1613
+ * ```
1614
+ *
1615
+ * @param rules - The rules object to rename.
1616
+ * @param map - A map of prefixes to rename.
1617
+ * @returns The renamed rules object.
1618
+ */
1619
+ function renameRules(rules, map) {
1620
+ return Object.fromEntries(Object.entries(rules).map(([key, value]) => {
1621
+ for (const [from, to] of Object.entries(map)) if (key.startsWith(`${from}/`)) return [to + key.slice(from.length), value];
1622
+ return [key, value];
1623
+ }));
1624
+ }
1625
+ /**
1626
+ * Build a probe that regex-tests one environment variable, for the agents that
1627
+ * announce themselves inside a variable a human also sets.
1628
+ *
1629
+ * `noTty` marks a variable an interactive user shares with the agent (kiro's
1630
+ * `TERM_PROGRAM` is set in its terminal too): an attached stdout TTY means a
1631
+ * person is watching, so the probe declines.
1632
+ *
1633
+ * @param name - The environment variable to test.
1634
+ * @param pattern - The pattern the value must match.
1635
+ * @param options - Probe options.
1636
+ * @param options.noTty - Whether an attached stdout TTY disqualifies the match.
1637
+ * @returns The probe.
1638
+ */
1639
+ function environmentMatcher(name, pattern, { noTty = false } = {}) {
1640
+ return (environment) => {
1641
+ if (noTty && process.stdout.isTTY) return false;
1642
+ const value = environment[name];
1643
+ return value !== void 0 && pattern.test(value);
1644
+ };
1645
+ }
1646
+ const OXFMT_CONFIG_FILES = [".oxfmtrc.json", ".oxfmtrc.jsonc"];
1647
+ /**
1648
+ * Resolve oxfmt configuration from `.oxfmtrc.json` or `.oxfmtrc.jsonc`,
1649
+ * synchronously. Used by the (synchronous) oxlint factory.
1650
+ *
1651
+ * @returns The oxfmt configuration options, or an empty object if none found.
1652
+ */
1653
+ function resolveOxfmtConfigOptionsSync() {
1654
+ for (const filename of OXFMT_CONFIG_FILES) {
1655
+ const configPath = path.resolve(process.cwd(), filename);
1656
+ try {
1657
+ const content = fs.readFileSync(configPath, "utf-8");
1658
+ const parsed = JSON.parse(content);
1659
+ if (!isRecord(parsed)) continue;
1660
+ const { $schema: _, ...config } = parsed;
1661
+ return config;
1662
+ } catch {
1663
+ continue;
1664
+ }
1665
+ }
1666
+ return {};
1667
+ }
1668
+ /**
1669
+ * Override the severity of all rules in a rules object, preserving rule
1670
+ * options. Rules set to `"off"` are not affected.
1671
+ *
1672
+ * @param rules - The rules object to override.
1673
+ * @param severity - The target severity level.
1674
+ * @param excludeRules - Rules to exclude from the severity override.
1675
+ * @returns A new rules object with overridden severities.
1676
+ */
1677
+ function overrideRuleSeverity(rules, severity, excludeRules = /* @__PURE__ */ new Set()) {
1678
+ return Object.fromEntries(Object.entries(rules).map(([key, value]) => {
1679
+ if (value === "off" || value === 0 || excludeRules.has(key)) return [key, value];
1680
+ if (Array.isArray(value)) {
1681
+ const [currentSeverity, ...options] = value;
1682
+ if (currentSeverity === "off" || currentSeverity === 0) return [key, value];
1683
+ return [key, [severity, ...options]];
1684
+ }
1685
+ if (value === "error" || value === "warn" || value === 1 || value === 2) return [key, severity];
1686
+ return [key, value];
1687
+ }));
1688
+ }
1689
+ /**
1690
+ * Resolves the lowest Node major version a semver range can match.
1691
+ *
1692
+ * @param range - A semver range such as `>=24.12.0` or `^24 || ^26`.
1693
+ * @returns The lowest major version, or `undefined` for an invalid range.
1694
+ */
1695
+ function parseNodeMajor(range) {
1696
+ try {
1697
+ return minVersion(range)?.major;
1698
+ } catch {
1699
+ return;
1700
+ }
1701
+ }
1702
+ /**
1703
+ * Reads the Node version range from the shared settings, checking both keys
1704
+ * `eslint-plugin-n` accepts so a project configuring it for that plugin gets
1705
+ * the preset's version-gated rules lined up for free.
1706
+ *
1707
+ * @param settings - The shared settings object, if any.
1708
+ * @returns The configured range, or `undefined` when unset.
1709
+ */
1710
+ function readSettingsNodeVersion(settings) {
1711
+ for (const key of ["n", "node"]) {
1712
+ const setting = settings?.[key];
1713
+ const version = isRecord(setting) ? setting["version"] : void 0;
1714
+ if (typeof version === "string") return version;
1715
+ }
1716
+ }
1717
+ /**
1718
+ * Reads the declared Node version range from a manifest, preferring
1719
+ * `engines.node` and falling back to the `devEngines.runtime` entry named
1720
+ * `node`, matching how `eslint-plugin-n` resolves the same question.
1721
+ *
1722
+ * @param manifest - The parsed manifest.
1723
+ * @returns The declared range, or `undefined` when the manifest declares none.
1724
+ */
1725
+ function readNodeRange({ devEngines, engines }) {
1726
+ if (isRecord(engines) && typeof engines["node"] === "string") return engines["node"];
1727
+ const runtime = isRecord(devEngines) ? devEngines["runtime"] : void 0;
1728
+ const version = (Array.isArray(runtime) ? runtime : [runtime]).find((entry) => isRecord(entry) && entry["name"] === "node")?.["version"];
1729
+ return typeof version === "string" ? version : void 0;
1730
+ }
1731
+ //#endregion
1732
+ //#region src/rules/comments.ts
1733
+ /**
1734
+ * Comment style rules shared between the ESLint and oxlint factories.
1735
+ *
1736
+ * The directive-comment rules themselves differ per linter
1737
+ * (`eslint-comments/*` vs `oxlint-comments/*`) and live in the respective
1738
+ * config modules.
1739
+ *
1740
+ * @param options - Shared rule options.
1741
+ * @returns The rule map.
1742
+ */
1743
+ function commentsRules({ stylistic = true } = {}) {
1744
+ return { ...stylistic !== false ? {
1745
+ "no-inline-comments": "error",
1746
+ "style/multiline-comment-style": ["error", "separate-lines"]
1747
+ } : {} };
1748
+ }
1749
+ /**
1750
+ * Comment length rules shared between the ESLint and oxlint factories.
1751
+ *
1752
+ * @param options - Shared rule options.
1753
+ * @returns The rule map.
1754
+ */
1755
+ function commentLengthRules({ maxLength, multiLineMaxLength, semanticComments, tabSize }) {
1756
+ return {
1757
+ "comment-length/limit-multi-line-comments": ["error", {
1758
+ maxLength: multiLineMaxLength,
1759
+ mode: "compact-on-overflow",
1760
+ ...semanticComments ? { semanticComments } : {},
1761
+ tabSize
1762
+ }],
1763
+ "comment-length/limit-single-line-comments": ["error", {
1764
+ maxLength,
1765
+ ...semanticComments ? { semanticComments } : {},
1766
+ tabSize
1767
+ }]
1768
+ };
1769
+ }
1770
+ //#endregion
1771
+ //#region src/oxlint/utils.ts
1772
+ const NATIVE_PLUGINS = /* @__PURE__ */ new Set([
1773
+ "eslint",
1774
+ "import",
1775
+ "jest",
1776
+ "jsdoc",
1777
+ "jsx-a11y",
1778
+ "nextjs",
1779
+ "node",
1780
+ "oxc",
1781
+ "promise",
1782
+ "react",
1783
+ "react-perf",
1784
+ "typescript",
1785
+ "unicorn",
1786
+ "vitest",
1787
+ "vue"
1788
+ ]);
1789
+ /**
1790
+ * Whether a rule's plugin prefix names a plugin oxlint implements natively.
1791
+ *
1792
+ * @param prefix - The rule's plugin prefix.
1793
+ * @returns Whether the prefix is a native oxlint plugin.
1794
+ */
1795
+ function isNativePlugin(prefix) {
1796
+ return NATIVE_PLUGINS.has(prefix);
1797
+ }
1798
+ /**
1799
+ * Anchor for resolving a plugin from the consumer project, used when a plugin
1800
+ * is a peer dependency the consumer installs rather than one we depend on.
1801
+ */
1802
+ const consumerAnchor = pathToFileURL(`${process.cwd()}/`).href;
1803
+ const specifierCache = /* @__PURE__ */ new Map();
1804
+ /**
1805
+ * Resolve a jsPlugin specifier, throwing when the package is not installed.
1806
+ *
1807
+ * @param specifier - The plugin package specifier.
1808
+ * @returns The absolute `file://` specifier.
1809
+ * @throws {Error} When the package cannot be resolved.
1810
+ */
1811
+ function resolveJsPluginSpecifier(specifier) {
1812
+ const resolved = tryResolveJsPlugin(specifier);
1813
+ if (resolved === void 0) throw new Error(`[@isentinel/eslint-config] Cannot resolve oxlint jsPlugin "${specifier}". Install it in your project, or disable the rules that require it.`);
1814
+ return resolved;
1815
+ }
1816
+ /**
1817
+ * The dedupe key for a jsPlugin entry (a bare specifier, or a named entry).
1818
+ *
1819
+ * @param entry - A bare package specifier, or a `{ name, specifier }` entry.
1820
+ * @returns The plugin name used to deduplicate registrations.
1821
+ */
1822
+ function jsPluginKey(entry) {
1823
+ return typeof entry === "string" ? entry : entry.name;
1824
+ }
1825
+ /**
1826
+ * Anchor a slash-less override glob to the config directory.
1827
+ *
1828
+ * ESLint matches a flat-config `files` pattern without a `/` against
1829
+ * root-level entries only, while oxlint matches it gitignore-style at any
1830
+ * depth — a root-only relaxation such as `*` would silently apply to the
1831
+ * whole tree (#617). A leading `./` keeps oxlint's matching anchored without
1832
+ * changing what the pattern matches under ESLint semantics.
1833
+ *
1834
+ * @param glob - An override glob in ESLint `files` semantics.
1835
+ * @returns The glob, anchored when it has no path separator.
1836
+ */
1837
+ function anchorOxlintGlob(glob) {
1838
+ return glob.includes("/") ? glob : `./${glob}`;
1839
+ }
1840
+ /**
1841
+ * Drop every rule whose plugin prefix is not registered on the generated
1842
+ * config, mutating the overrides in place. Oxlint fails the whole config build
1843
+ * on a rule naming an unknown plugin, so entries left behind by a plugin that
1844
+ * native-only mode dropped have to go rather than sit inert.
1845
+ *
1846
+ * Keyed on what is actually registered, so a consumer's own jsPlugin keeps its
1847
+ * rules while a preset plugin that is no longer loaded loses them. Unprefixed
1848
+ * (core) rules are always kept.
1849
+ *
1850
+ * @param overrides - The merged overrides (mutated).
1851
+ * @param registeredPlugins - Every native plugin and jsPlugin name registered.
1852
+ */
1853
+ function stripUnregisteredPluginRules(overrides, registeredPlugins) {
1854
+ for (const override of overrides) {
1855
+ const { rules } = override;
1856
+ if (rules === void 0) continue;
1857
+ for (const rule of Object.keys(rules)) {
1858
+ const slashIndex = rule.indexOf("/");
1859
+ if (slashIndex !== -1 && !registeredPlugins.has(rule.slice(0, slashIndex))) delete rules[rule];
1860
+ }
1861
+ }
1862
+ }
1863
+ /**
1864
+ * Split a canonical (ESLint-named) rule map into oxlint-native rules and
1865
+ * jsPlugin rules, translating rule names via the oxlint rule mapping.
1866
+ *
1867
+ * Rules that are not part of the hybrid mapping (for example jest or react
1868
+ * rules, which only run in standalone mode) are treated as jsPlugin rules.
1869
+ * Disabled unmapped rules are skipped so that no jsPlugin is loaded solely for
1870
+ * an "off" entry, unless `keepUnmappedOff` is set (user options.rules must not
1871
+ * silently discard an explicit disable).
1872
+ *
1873
+ * @param rules - The canonical rule map.
1874
+ * @param keepUnmappedOff - Emit disabled unmapped rules instead of skipping
1875
+ * them (without registering a jsPlugin for them).
1876
+ * @returns The split rules with the plugins each side requires.
1877
+ */
1878
+ function splitOxlintRules(rules, keepUnmappedOff = false) {
1879
+ const nativeRules = {};
1880
+ const jsPluginRules = {};
1881
+ const nativePlugins = /* @__PURE__ */ new Set();
1882
+ const jsPluginPrefixes = /* @__PURE__ */ new Set();
1883
+ const tsCollapsed = /* @__PURE__ */ new Set();
1884
+ const entries = Object.entries(rules ?? {});
1885
+ for (const [rule, value] of entries) {
1886
+ if (value === void 0 || excludedFromOxlint.has(rule)) continue;
1887
+ const covered = isOxlintCovered(rule);
1888
+ const severity = Array.isArray(value) ? value[0] : value;
1889
+ const isOff = severity === "off" || severity === 0;
1890
+ const translated = translateRuleToOxlint(rule);
1891
+ const slashIndex = translated.indexOf("/");
1892
+ const prefix = slashIndex === -1 ? "eslint" : translated.slice(0, slashIndex);
1893
+ const nativePrefix = isNativePlugin(prefix) ? prefix : void 0;
1894
+ if (!covered && isOff && keepUnmappedOff) {
1895
+ if (nativePrefix !== void 0) {
1896
+ nativeRules[translated] = value;
1897
+ nativePlugins.add(nativePrefix);
1898
+ } else {
1899
+ const specifier = oxlintJsPlugins[prefix];
1900
+ if (specifier !== void 0 && canResolveJsPlugin(specifier)) {
1901
+ jsPluginRules[translated] = value;
1902
+ jsPluginPrefixes.add(prefix);
1903
+ }
1904
+ }
1905
+ continue;
1906
+ }
1907
+ if (!covered && isOff) continue;
1908
+ if ((covered ? mappedTarget(rule) : nativePrefix !== void 0 ? "native" : "js-plugin") === "js-plugin") {
1909
+ jsPluginRules[translated] = value;
1910
+ jsPluginPrefixes.add(prefix);
1911
+ continue;
1912
+ }
1913
+ if (collapsesToTsCoreRule(rule)) {
1914
+ tsCollapsed.add(translated);
1915
+ nativeRules[translated] = value;
1916
+ } else if (!isTsCoreCounterpartRule(rule) || !tsCollapsed.has(translated)) nativeRules[translated] = value;
1917
+ if (nativePrefix !== void 0) nativePlugins.add(nativePrefix);
1918
+ }
1919
+ const jsPlugins = [];
1920
+ for (const prefix of jsPluginPrefixes) {
1921
+ const specifier = oxlintJsPlugins[prefix];
1922
+ if (specifier === void 0) throw new Error(`[@isentinel/eslint-config] Unknown oxlint jsPlugin prefix: ${prefix}`);
1923
+ jsPlugins.push({
1924
+ name: prefix,
1925
+ specifier: resolveJsPluginSpecifier(specifier)
1926
+ });
1927
+ }
1928
+ return {
1929
+ jsPluginRules,
1930
+ jsPlugins,
1931
+ nativePlugins: [...nativePlugins],
1932
+ nativeRules
1933
+ };
1934
+ }
1935
+ /**
1936
+ * Create oxlint config fragments from a canonical rule map, one fragment for
1937
+ * native rules and one for jsPlugin rules.
1938
+ *
1939
+ * @param options - The fragment options.
1940
+ * @returns The generated config fragments.
1941
+ */
1942
+ function createOxlintConfigs({ name, excludeFiles, files, globals, keepUnmappedOff = false, rules, settings }) {
1943
+ const { jsPluginRules, jsPlugins, nativePlugins, nativeRules } = splitOxlintRules(rules, keepUnmappedOff);
1944
+ const fragments = [];
1945
+ if (Object.keys(nativeRules).length > 0) fragments.push({
1946
+ name,
1947
+ ...excludeFiles ? { excludeFiles } : {},
1948
+ files,
1949
+ ...globals ? { globals } : {},
1950
+ plugins: nativePlugins,
1951
+ rules: nativeRules,
1952
+ ...settings ? { settings } : {}
1953
+ });
1954
+ if (Object.keys(jsPluginRules).length > 0) fragments.push({
1955
+ name: `${name}/js-plugin`,
1956
+ ...excludeFiles ? { excludeFiles } : {},
1957
+ files,
1958
+ ...globals && fragments.length === 0 ? { globals } : {},
1959
+ jsPlugins,
1960
+ rules: jsPluginRules,
1961
+ ...settings && fragments.length === 0 ? { settings } : {}
1962
+ });
1963
+ return fragments;
1964
+ }
1965
+ /**
1966
+ * Resolve a specifier against an optional anchor, swallowing resolution errors.
1967
+ *
1968
+ * @param specifier - The plugin package specifier.
1969
+ * @param anchor - The URL to resolve against, or the current module by default.
1970
+ * @returns The resolved URL, or `undefined` when it does not resolve.
1971
+ */
1972
+ function resolveFrom(specifier, anchor) {
1973
+ try {
1974
+ return import.meta.resolve(specifier, anchor);
1975
+ } catch {
1976
+ return;
1977
+ }
1978
+ }
1979
+ /**
1980
+ * Resolve a jsPlugin package specifier to an absolute `file://` URL.
1981
+ *
1982
+ * Oxlint resolves bare `jsPlugins` specifiers relative to the consumer's config
1983
+ * file, which fails under pnpm's isolated node_modules: our plugin dependencies
1984
+ * only exist inside our own virtual store scope, never at the consumer's root.
1985
+ * Resolving here (from our package first, then the consumer) and emitting an
1986
+ * absolute specifier makes loading independent of the consumer's layout.
1987
+ *
1988
+ * @param specifier - The plugin package specifier.
1989
+ * @returns The resolved specifier, or `undefined` when it resolves nowhere.
1990
+ */
1991
+ function tryResolveJsPlugin(specifier) {
1992
+ const cached = specifierCache.get(specifier);
1993
+ if (cached !== void 0 || specifierCache.has(specifier)) return cached;
1994
+ const resolved = resolveFrom(specifier) ?? resolveFrom(specifier, consumerAnchor);
1995
+ specifierCache.set(specifier, resolved);
1996
+ return resolved;
1997
+ }
1998
+ /**
1999
+ * Whether a jsPlugin package can be resolved from either our package or the
2000
+ * consumer project.
2001
+ *
2002
+ * @param specifier - The plugin package specifier.
2003
+ * @returns Whether the plugin is resolvable.
2004
+ */
2005
+ function canResolveJsPlugin(specifier) {
2006
+ return tryResolveJsPlugin(specifier) !== void 0;
2007
+ }
2008
+ function mappedTarget(rule) {
2009
+ return oxlintRuleMapping[rule] === "js-plugin" ? "js-plugin" : "native";
2010
+ }
2011
+ //#endregion
2012
+ //#region src/oxlint/configs/comments.ts
2013
+ function oxlintComments({ prettierOptions = {}, stylistic = true } = {}) {
2014
+ return [
2015
+ {
2016
+ name: "isentinel/comments/directives",
2017
+ files: [GLOB_SRC],
2018
+ jsPlugins: [{
2019
+ name: "oxlint-comments",
2020
+ specifier: resolveJsPluginSpecifier("oxlint-plugin-oxlint-comments")
2021
+ }],
2022
+ rules: {
2023
+ "oxlint-comments/disable-enable-pair": ["error", { allowWholeFile: true }],
2024
+ "oxlint-comments/no-aggregating-enable": "error",
2025
+ "oxlint-comments/no-duplicate-disable": "error",
2026
+ "oxlint-comments/no-unlimited-disable": "error",
2027
+ "oxlint-comments/no-unused-enable": "error",
2028
+ "oxlint-comments/require-description": ["error", { ignore: ["oxlint-enable"] }]
2029
+ }
2030
+ },
2031
+ ...createOxlintConfigs({
2032
+ name: "isentinel/comments",
2033
+ files: [GLOB_SRC],
2034
+ rules: commentsRules({ stylistic })
2035
+ }),
2036
+ ...stylistic !== false ? createOxlintConfigs({
2037
+ name: "isentinel/comments/length",
2038
+ files: [GLOB_SRC],
2039
+ rules: commentLengthRules({
2040
+ maxLength: (Number(prettierOptions["jsdocPrintWidth"]) || 80) + 2,
2041
+ multiLineMaxLength: Number(prettierOptions["jsdocPrintWidth"]) || 80,
2042
+ semanticComments: ["oxlint-disable", "oxlint-enable"],
2043
+ tabSize: typeof prettierOptions["tabWidth"] === "number" ? prettierOptions["tabWidth"] : 4
2044
+ })
2045
+ }) : []
2046
+ ];
2047
+ }
2048
+ //#endregion
2049
+ //#region src/oxlint/configs/disables.ts
2050
+ function oxlintDisables({ root }) {
2051
+ return [
2052
+ ...createOxlintConfigs({
2053
+ name: "isentinel/disables/scripts",
2054
+ files: [`**/scripts/${GLOB_SRC}`],
2055
+ rules: {
2056
+ "antfu/no-top-level-await": "off",
2057
+ "no-console": "off",
2058
+ "small-rules/require-async-suffix": "off",
2059
+ "ts/explicit-function-return-type": "off"
2060
+ }
2061
+ }),
2062
+ ...createOxlintConfigs({
2063
+ name: "isentinel/disables/cli",
2064
+ files: [`**/cli/${GLOB_SRC}`, `**/cli.${GLOB_SRC_EXT}`],
2065
+ rules: {
2066
+ "antfu/no-top-level-await": "off",
2067
+ "no-console": "off"
2068
+ }
2069
+ }),
2070
+ ...createOxlintConfigs({
2071
+ name: "isentinel/disables/build-tools",
2072
+ files: GLOB_BUILD_TOOLS,
2073
+ rules: {
2074
+ "antfu/no-top-level-await": "off",
2075
+ "no-console": "off",
2076
+ "small-rules/require-async-suffix": "off",
2077
+ "ts/explicit-function-return-type": "off"
2078
+ }
2079
+ }),
2080
+ ...createOxlintConfigs({
2081
+ name: "isentinel/disables/bin",
2082
+ files: ["**/bin/**/*", `**/bin.${GLOB_SRC_EXT}`],
2083
+ rules: {
2084
+ "antfu/no-import-dist": "off",
2085
+ "antfu/no-import-node-modules-by-path": "off",
2086
+ "antfu/no-top-level-await": "off",
2087
+ "small-rules/require-async-suffix": "off"
2088
+ }
2089
+ }),
2090
+ ...createOxlintConfigs({
2091
+ name: "isentinel/disables/dts",
2092
+ files: [GLOB_DTS],
2093
+ keepUnmappedOff: true,
2094
+ rules: {
2095
+ "max-lines": "off",
2096
+ "no-duplicate-imports": "off",
2097
+ "no-restricted-syntax": "off",
2098
+ "oxc/no-barrel-file": "off",
2099
+ "roblox/no-namespace-merging": "off",
2100
+ "small-rules/prefer-class-properties": "off",
2101
+ "sonar/no-duplicate-string": "off",
2102
+ "unused-imports/no-unused-vars": "off"
2103
+ }
2104
+ }),
2105
+ {
2106
+ name: "isentinel/disables/dts/directives",
2107
+ files: [GLOB_DTS],
2108
+ jsPlugins: [{
2109
+ name: "oxlint-comments",
2110
+ specifier: resolveJsPluginSpecifier("oxlint-plugin-oxlint-comments")
2111
+ }],
2112
+ rules: { "oxlint-comments/no-unlimited-disable": "off" }
2113
+ },
2114
+ ...createOxlintConfigs({
2115
+ name: "isentinel/disables/test",
2116
+ files: [...GLOB_TESTS],
2117
+ rules: {
2118
+ "antfu/no-top-level-await": "off",
2119
+ "flawless/max-lines-per-function": "off",
2120
+ "max-lines": "off",
2121
+ "no-empty-function": "off",
2122
+ "no-unused-expressions": "off",
2123
+ "sonar/no-duplicate-string": "off",
2124
+ "ts/explicit-function-return-type": "off",
2125
+ "ts/no-empty-function": "off",
2126
+ "ts/no-extraneous-class": "off",
2127
+ "ts/no-non-null-assertion": "off",
2128
+ "ts/require-await": "off",
2129
+ "ts/unbound-method": "off",
2130
+ "unicorn/consistent-function-scoping": "off"
2131
+ }
2132
+ }),
2133
+ ...createOxlintConfigs({
2134
+ name: "isentinel/disables/cjs",
2135
+ files: ["**/*.js", "**/*.cjs"],
2136
+ rules: { "ts/no-require-imports": "off" }
2137
+ }),
2138
+ ...createOxlintConfigs({
2139
+ name: "isentinel/disables/root",
2140
+ files: [...root],
2141
+ rules: {
2142
+ "import/no-default-export": "off",
2143
+ "small-rules/require-async-suffix": "off",
2144
+ "sonar/file-name-differ-from-class": "off",
2145
+ "unicorn/filename-case": "off"
2146
+ }
2147
+ }),
2148
+ ...createOxlintConfigs({
2149
+ name: "isentinel/disables/jsx",
2150
+ files: [GLOB_JSX, GLOB_TSX],
2151
+ rules: { "flawless/max-lines-per-function": "off" }
2152
+ })
2153
+ ];
2154
+ }
2155
+ //#endregion
2156
+ //#region src/rules/e18e.ts
2157
+ /** `Map.prototype.getOrInsert` landed in V8 14.6, shipped in Node 26. */
2158
+ const NODE_GET_OR_INSERT = 26;
2159
+ /**
2160
+ * The e18e source rules shared between the ESLint and oxlint factories. Mirrors
2161
+ * the `modernization` and `performanceImprovements` presets of
2162
+ * `@e18e/eslint-plugin`; all rules are syntax-only (no type information).
2163
+ *
2164
+ * The `moduleReplacements` preset is not included here: its only rule,
2165
+ * `ban-dependencies`, has JSON-only visitors and so is applied separately
2166
+ * against `package.json` by the ESLint factory.
2167
+ *
2168
+ * @param options - Which preset groups to enable.
2169
+ * @returns The rule map.
2170
+ */
2171
+ function e18eRules({ modernization = true, nodeMajor, performanceImprovements = true } = {}) {
2172
+ return {
2173
+ ...modernization ? {
2174
+ "e18e/prefer-array-at": "error",
2175
+ "e18e/prefer-array-fill": "error",
2176
+ "e18e/prefer-array-to-reversed": "error",
2177
+ "e18e/prefer-array-to-sorted": "error",
2178
+ "e18e/prefer-array-to-spliced": "error",
2179
+ "e18e/prefer-includes": "error",
2180
+ "e18e/prefer-nullish-coalescing": "error",
2181
+ "e18e/prefer-object-has-own": "error",
2182
+ "e18e/prefer-spread-syntax": "error",
2183
+ "e18e/prefer-url-canparse": "error",
2184
+ ...nodeMajor !== void 0 && nodeMajor >= NODE_GET_OR_INSERT ? { "e18e/prefer-get-or-insert": "error" } : {}
2185
+ } : {},
2186
+ ...performanceImprovements ? {
2187
+ "e18e/no-spread-in-reduce": "error",
2188
+ "e18e/prefer-array-from-map": "error",
2189
+ "e18e/prefer-array-some": "error",
2190
+ "e18e/prefer-date-now": "error",
2191
+ "e18e/prefer-includes-over-regex-test": "error",
2192
+ "e18e/prefer-regex-test": "error",
2193
+ "e18e/prefer-static-collator": "error",
2194
+ "e18e/prefer-static-regex": "error",
2195
+ "e18e/prefer-string-fromcharcode": "error",
2196
+ "e18e/prefer-timer-args": "error"
2197
+ } : {}
2198
+ };
2199
+ }
2200
+ //#endregion
2201
+ //#region src/oxlint/configs/e18e.ts
2202
+ function oxlintE18e({ excludeFiles, files = [GLOB_SRC], modernization = true, nodeMajor = resolveNodeMajor(), overrides = {}, performanceImprovements = true } = {}) {
2203
+ return [...createOxlintConfigs({
2204
+ name: "isentinel/e18e",
2205
+ ...excludeFiles ? { excludeFiles } : {},
2206
+ files: files.flat(),
2207
+ rules: {
2208
+ ...e18eRules({
2209
+ modernization,
2210
+ nodeMajor,
2211
+ performanceImprovements
2212
+ }),
2213
+ ...overrides
2214
+ }
2215
+ }), ...createOxlintConfigs({
2216
+ name: "isentinel/e18e/disables/test",
2217
+ files: [...GLOB_TESTS],
2218
+ rules: { "e18e/prefer-static-regex": "off" }
2219
+ })];
2220
+ }
2221
+ //#endregion
2222
+ //#region src/rules/eslint-plugin.ts
2223
+ /**
2224
+ * ESLint-plugin-development rules shared between the ESLint and oxlint
2225
+ * factories.
2226
+ *
2227
+ * @returns The rule map.
2228
+ */
2229
+ function eslintPluginRules() {
2230
+ return {
2231
+ "eslint-plugin/consistent-output": "error",
2232
+ "eslint-plugin/fixer-return": "error",
2233
+ "eslint-plugin/meta-property-ordering": "off",
2234
+ "eslint-plugin/no-deprecated-context-methods": "error",
2235
+ "eslint-plugin/no-deprecated-report-api": "error",
2236
+ "eslint-plugin/no-identical-tests": "error",
2237
+ "eslint-plugin/no-matching-violation-suggest-message-ids": "error",
2238
+ "eslint-plugin/no-meta-replaced-by": "error",
2239
+ "eslint-plugin/no-meta-schema-default": "error",
2240
+ "eslint-plugin/no-missing-message-ids": "error",
2241
+ "eslint-plugin/no-missing-placeholders": "error",
2242
+ "eslint-plugin/no-only-tests": "error",
2243
+ "eslint-plugin/no-property-in-node": "error",
2244
+ "eslint-plugin/no-unused-message-ids": "error",
2245
+ "eslint-plugin/no-unused-placeholders": "error",
2246
+ "eslint-plugin/no-useless-token-range": "error",
2247
+ "eslint-plugin/prefer-message-ids": "error",
2248
+ "eslint-plugin/prefer-object-rule": "error",
2249
+ "eslint-plugin/prefer-output-null": "error",
2250
+ "eslint-plugin/prefer-placeholders": "error",
2251
+ "eslint-plugin/prefer-replace-text": "error",
2252
+ "eslint-plugin/report-message-format": "error",
2253
+ "eslint-plugin/require-meta-default-options": "error",
2254
+ "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow).*[^.!]$" }],
2255
+ "eslint-plugin/require-meta-docs-recommended": "error",
2256
+ "eslint-plugin/require-meta-docs-url": "off",
2257
+ "eslint-plugin/require-meta-fixable": "error",
2258
+ "eslint-plugin/require-meta-has-suggestions": "error",
2259
+ "eslint-plugin/require-meta-languages": "error",
2260
+ "eslint-plugin/require-meta-schema": "error",
2261
+ "eslint-plugin/require-meta-schema-description": "error",
2262
+ "eslint-plugin/require-meta-type": "error",
2263
+ "eslint-plugin/require-test-case-name": "error",
2264
+ "eslint-plugin/require-test-error-positions": "error",
2265
+ "eslint-plugin/test-case-property-ordering": "error",
2266
+ "eslint-plugin/test-case-shorthand-strings": "error",
2267
+ "eslint-plugin/unique-test-case-names": "error"
2268
+ };
2269
+ }
2270
+ //#endregion
2271
+ //#region src/oxlint/configs/eslint-plugin.ts
2272
+ function oxlintEslintPlugin(options = {}) {
2273
+ const { overrides = {} } = options;
2274
+ return createOxlintConfigs({
2275
+ name: "isentinel/eslint-plugin",
2276
+ files: options.files?.flat() ?? ["**/*.{,c,m}[jt]s{,x}"],
2277
+ rules: {
2278
+ ...eslintPluginRules(),
2279
+ ...overrides
2280
+ }
2281
+ });
2282
+ }
2283
+ //#endregion
2284
+ //#region src/rules/flawless.ts
2285
+ /**
2286
+ * Arrow return style rule on its own, for the Markdown code block override
2287
+ * where only the line width differs.
2288
+ *
2289
+ * @param options - Shared rule options.
2290
+ * @returns The rule map.
2291
+ */
2292
+ function arrowStyleRules({ maxLen, maxLength, printWidth, tabWidth } = {}) {
2293
+ return { "flawless/arrow-return-style": ["error", {
2294
+ jsxAlwaysUseExplicitReturn: true,
2295
+ maxLen: maxLength ?? maxLen ?? printWidth ?? 100,
2296
+ maxObjectProperties: 2,
2297
+ namedExportsAlwaysUseExplicitReturn: true,
2298
+ objectReturnStyle: "complex-explicit",
2299
+ ...tabWidth !== void 0 ? { tabWidth } : {},
2300
+ ...printWidth !== void 0 ? { useOxfmt: { printWidth } } : {}
2301
+ }] };
2302
+ }
2303
+ /**
2304
+ * Base (non-React) flawless rules shared between the ESLint and oxlint
2305
+ * factories. The React flawless rules live in the react rule map; the
2306
+ * type-aware `flawless/naming-convention`, the test-only
2307
+ * `flawless/padding-after-expect-assertions`, and the non-JS
2308
+ * `flawless/no-redundant-tsconfig-options`, `flawless/toml-*` and
2309
+ * `flawless/yaml-*` rules are configured by their own configs.
2310
+ *
2311
+ * @param options - Shared stylistic and arrow rule options.
2312
+ * @returns The rule map.
2313
+ */
2314
+ function flawlessRules({ stylistic = true, ...arrowOptions } = {}) {
2315
+ if (stylistic === false) return {};
2316
+ return {
2317
+ "flawless/max-lines-per-function": ["warn", {
2318
+ max: 30,
2319
+ skipBlankLines: true,
2320
+ skipComments: true
2321
+ }],
2322
+ "flawless/no-export-default-arrow": "error",
2323
+ "flawless/prefer-parameter-destructuring": "warn",
2324
+ ...arrowStyleRules(arrowOptions)
2325
+ };
2326
+ }
2327
+ //#endregion
2328
+ //#region src/oxlint/configs/flawless.ts
2329
+ function oxlintFlawless({ stylistic = true } = {}, prettierOptions = {}) {
2330
+ return createOxlintConfigs({
2331
+ name: "isentinel/flawless",
2332
+ files: [GLOB_SRC],
2333
+ rules: flawlessRules({
2334
+ maxLen: (typeof stylistic === "object" ? stylistic : {}).maxLen,
2335
+ printWidth: typeof prettierOptions["printWidth"] === "number" ? prettierOptions["printWidth"] : void 0,
2336
+ stylistic,
2337
+ tabWidth: typeof prettierOptions["tabWidth"] === "number" ? prettierOptions["tabWidth"] : void 0
2338
+ })
2339
+ });
2340
+ }
2341
+ //#endregion
2342
+ //#region src/oxlint/configs/gitignore.ts
2343
+ const LINE_BREAK_PATTERN = /\r?\n/;
2344
+ /**
2345
+ * Reads `.gitignore` and `.git/info/exclude` and returns their patterns as an
2346
+ * array for oxlint's top-level `ignorePatterns`.
2347
+ *
2348
+ * Oxlint does not natively support `.git/info/exclude`, so we parse it
2349
+ * ourselves.
2350
+ *
2351
+ * @returns Parsed ignore patterns from gitignore files.
2352
+ */
2353
+ function oxlintGitignore() {
2354
+ const cwd = process.cwd();
2355
+ const patterns = [];
2356
+ const gitignoreFile = findUpSync(".gitignore", { cwd });
2357
+ if (gitignoreFile !== void 0) patterns.push(...parseGitignoreFile(gitignoreFile));
2358
+ const gitDirectory = findUpSync(".git", {
2359
+ cwd,
2360
+ type: "directory"
2361
+ });
2362
+ if (gitDirectory !== void 0) {
2363
+ const excludeFile = path.join(gitDirectory, "info", "exclude");
2364
+ try {
2365
+ patterns.push(...parseGitignoreContent(readFileSync(excludeFile, "utf-8")));
2366
+ } catch {}
2367
+ }
2368
+ return patterns;
2369
+ }
2370
+ function parseGitignoreContent(content) {
2371
+ const patterns = [];
2372
+ for (const raw of content.split(LINE_BREAK_PATTERN)) {
2373
+ const line = raw.trim();
2374
+ if (line !== "" && !line.startsWith("#")) patterns.push(line);
2375
+ }
2376
+ return patterns;
2377
+ }
2378
+ function parseGitignoreFile(filePath) {
2379
+ return parseGitignoreContent(readFileSync(filePath, "utf-8"));
2380
+ }
2381
+ //#endregion
2382
+ //#region src/rules/imports.ts
2383
+ /**
2384
+ * Import rules shared between the ESLint and oxlint factories.
2385
+ *
2386
+ * @param options - Shared rule options.
2387
+ * @returns The rule map.
2388
+ */
2389
+ function importsRules({ stylistic = true } = {}) {
2390
+ return {
2391
+ "antfu/import-dedupe": "error",
2392
+ "antfu/no-import-dist": "error",
2393
+ "antfu/no-import-node-modules-by-path": "error",
2394
+ "import/first": "error",
2395
+ "import/no-mutable-exports": "error",
2396
+ "import/no-named-default": "error",
2397
+ ...stylistic !== false ? { "import/newline-after-import": ["error", {
2398
+ considerComments: true,
2399
+ count: 1
2400
+ }] } : {}
2401
+ };
2402
+ }
2403
+ //#endregion
2404
+ //#region src/oxlint/configs/imports.ts
2405
+ /**
2406
+ * Files that are CommonJS by convention, where `require`/`module.exports` is
2407
+ * the point rather than a mistake.
2408
+ */
2409
+ const GLOB_CJS = ["**/*.js", "**/*.cjs"];
2410
+ function oxlintImports({ excludeFiles, roblox = true, stylistic = true } = {}) {
2411
+ const exclude = excludeFiles ?? [];
2412
+ return [
2413
+ ...createOxlintConfigs({
2414
+ name: "isentinel/imports",
2415
+ ...excludeFiles ? { excludeFiles } : {},
2416
+ files: [GLOB_SRC],
2417
+ rules: importsRules({ stylistic })
2418
+ }),
2419
+ {
2420
+ name: "isentinel/imports/oxlint",
2421
+ ...excludeFiles ? { excludeFiles } : {},
2422
+ files: [GLOB_SRC],
2423
+ plugins: ["import"],
2424
+ rules: {
2425
+ "import/consistent-type-specifier-style": "error",
2426
+ "import/default": "error",
2427
+ "import/export": "error",
2428
+ "import/namespace": "error",
2429
+ "import/no-absolute-path": "error",
2430
+ "import/no-cycle": "error",
2431
+ "import/no-duplicates": "error",
2432
+ "import/no-empty-named-blocks": "error",
2433
+ "import/no-named-as-default": "error",
2434
+ "import/no-self-import": "error",
2435
+ ...roblox ? { "import/no-nodejs-modules": "error" } : {
2436
+ "import/extensions": ["error", "ignorePackages"],
2437
+ "import/no-nodejs-modules": "off"
2438
+ }
2439
+ }
2440
+ },
2441
+ {
2442
+ name: "isentinel/imports/oxlint/esm",
2443
+ excludeFiles: [...GLOB_CJS, ...exclude],
2444
+ files: [GLOB_SRC],
2445
+ plugins: ["import"],
2446
+ rules: { "import/no-commonjs": "error" }
2447
+ },
2448
+ {
2449
+ name: "isentinel/imports/oxlint/unassigned",
2450
+ excludeFiles: [...GLOB_BIN, ...exclude],
2451
+ files: [GLOB_SRC],
2452
+ plugins: ["import"],
2453
+ rules: { "import/no-unassigned-import": "error" }
2454
+ },
2455
+ {
2456
+ name: "isentinel/imports/oxlint/default-export",
2457
+ excludeFiles: [
2458
+ GLOB_DTS,
2459
+ ...GLOB_BUILD_TOOLS,
2460
+ ...exclude
2461
+ ],
2462
+ files: [GLOB_SRC],
2463
+ plugins: ["import"],
2464
+ rules: {
2465
+ "import/no-anonymous-default-export": "error",
2466
+ "import/no-default-export": "error"
2467
+ }
2468
+ }
2469
+ ];
2470
+ }
2471
+ //#endregion
2472
+ //#region src/rules/javascript.ts
2473
+ /**
2474
+ * Core JavaScript rules shared between the ESLint and oxlint factories.
2475
+ *
2476
+ * Rule names use the canonical (renamed) ESLint prefixes. The oxlint factory
2477
+ * translates names via the oxlint rule mapping.
2478
+ *
2479
+ * @param options - Shared rule options.
2480
+ * @returns The rule map.
2481
+ */
2482
+ function javascriptRules({ isInEditor = false, roblox = true, stylistic = true } = {}) {
2483
+ return {
2484
+ "accessor-pairs": ["error", {
2485
+ enforceForClassMembers: true,
2486
+ setWithoutGet: true
2487
+ }],
2488
+ "antfu/no-top-level-await": "error",
2489
+ "array-callback-return": ["error", { allowImplicit: true }],
2490
+ "better-max-params/better-max-params": ["error", { func: 4 }],
2491
+ "block-scoped-var": "error",
2492
+ "constructor-super": "error",
2493
+ "de-morgan/no-negated-conjunction": "error",
2494
+ "de-morgan/no-negated-disjunction": "error",
2495
+ "default-case-last": "error",
2496
+ "dot-notation": ["error", { allowKeywords: true }],
2497
+ "eqeqeq": "error",
2498
+ "for-direction": "error",
2499
+ "logical-assignment-operators": "error",
2500
+ "max-classes-per-file": "error",
2501
+ "max-depth": "error",
2502
+ "new-cap": ["error", {
2503
+ capIsNew: false,
2504
+ newIsCap: true,
2505
+ newIsCapExceptionPattern: "^mock",
2506
+ properties: true
2507
+ }],
2508
+ "no-alert": "error",
2509
+ "no-array-constructor": "error",
2510
+ "no-async-promise-executor": "error",
2511
+ "no-caller": "error",
2512
+ "no-case-declarations": "error",
2513
+ "no-class-assign": "error",
2514
+ "no-compare-neg-zero": "error",
2515
+ "no-cond-assign": ["error", "always"],
2516
+ "no-console": ["error", { allow: ["warn", "error"] }],
2517
+ "no-const-assign": "error",
2518
+ "no-constant-condition": ["error", { checkLoops: false }],
2519
+ "no-control-regex": "error",
2520
+ "no-debugger": "error",
2521
+ "no-delete-var": "error",
2522
+ "no-dupe-args": "error",
2523
+ "no-dupe-class-members": "error",
2524
+ "no-dupe-keys": "error",
2525
+ "no-duplicate-case": "error",
2526
+ "no-duplicate-imports": ["error", { allowSeparateTypeImports: true }],
2527
+ "no-else-return": ["error", { allowElseIf: false }],
2528
+ "no-empty": ["error", { allowEmptyCatch: true }],
2529
+ "no-empty-character-class": "error",
2530
+ "no-empty-function": "error",
2531
+ "no-empty-pattern": "error",
2532
+ "no-empty-static-block": "error",
2533
+ "no-eval": "error",
2534
+ "no-ex-assign": "error",
2535
+ "no-extend-native": "error",
2536
+ "no-extra-bind": "error",
2537
+ "no-extra-boolean-cast": "error",
2538
+ "no-fallthrough": "error",
2539
+ "no-func-assign": "error",
2540
+ "no-global-assign": "error",
2541
+ "no-implied-eval": "error",
2542
+ "no-import-assign": "error",
2543
+ "no-invalid-regexp": "error",
2544
+ "no-irregular-whitespace": "error",
2545
+ "no-iterator": "error",
2546
+ "no-labels": ["error", {
2547
+ allowLoop: false,
2548
+ allowSwitch: false
2549
+ }],
2550
+ "no-lonely-if": "error",
2551
+ "no-loop-func": "error",
2552
+ "no-loss-of-precision": "error",
2553
+ "no-misleading-character-class": "error",
2554
+ "no-new": "error",
2555
+ "no-new-func": "error",
2556
+ "no-new-native-nonconstructor": "error",
2557
+ "no-new-wrappers": "error",
2558
+ "no-obj-calls": "error",
2559
+ "no-octal": "error",
2560
+ "no-octal-escape": "error",
2561
+ "no-proto": "error",
2562
+ "no-prototype-builtins": "error",
2563
+ "no-redeclare": ["error", { builtinGlobals: false }],
2564
+ "no-regex-spaces": "error",
2565
+ "no-restricted-globals": [
2566
+ "error",
2567
+ {
2568
+ name: "global",
2569
+ message: "Use `globalThis` instead."
2570
+ },
2571
+ {
2572
+ name: "self",
2573
+ message: "Use `globalThis` instead."
2574
+ }
2575
+ ],
2576
+ "no-restricted-properties": [
2577
+ "error",
2578
+ {
2579
+ message: "Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.",
2580
+ property: "__proto__"
2581
+ },
2582
+ {
2583
+ message: "Use `Object.defineProperty` instead.",
2584
+ property: "__defineGetter__"
2585
+ },
2586
+ {
2587
+ message: "Use `Object.defineProperty` instead.",
2588
+ property: "__defineSetter__"
2589
+ },
2590
+ {
2591
+ message: "Use `Object.getOwnPropertyDescriptor` instead.",
2592
+ property: "__lookupGetter__"
2593
+ },
2594
+ {
2595
+ message: "Use `Object.getOwnPropertyDescriptor` instead.",
2596
+ property: "__lookupSetter__"
2597
+ }
2598
+ ],
2599
+ "no-restricted-syntax": [
2600
+ "error",
2601
+ "TSEnumDeclaration[const=true]",
2602
+ "TSExportAssignment"
2603
+ ],
2604
+ "no-return-assign": ["error", "always"],
2605
+ "no-self-assign": ["error", { props: true }],
2606
+ "no-self-compare": "error",
2607
+ "no-sequences": "error",
2608
+ "no-shadow-restricted-names": "error",
2609
+ "no-sparse-arrays": "error",
2610
+ "no-template-curly-in-string": "error",
2611
+ "no-this-before-super": "error",
2612
+ "no-throw-literal": "error",
2613
+ "no-undef": "error",
2614
+ "no-undef-init": "error",
2615
+ "no-unexpected-multiline": "error",
2616
+ "no-unmodified-loop-condition": "error",
2617
+ "no-unneeded-ternary": ["error", { defaultAssignment: false }],
2618
+ "no-unreachable": "error",
2619
+ "no-unreachable-loop": "error",
2620
+ "no-unsafe-finally": "error",
2621
+ "no-unsafe-negation": "error",
2622
+ "no-unused-expressions": ["error", {
2623
+ allowShortCircuit: true,
2624
+ allowTaggedTemplates: true,
2625
+ allowTernary: true
2626
+ }],
2627
+ "no-unused-vars": "off",
2628
+ "no-use-before-define": ["error", {
2629
+ classes: false,
2630
+ functions: false,
2631
+ variables: true
2632
+ }],
2633
+ "no-useless-backreference": "error",
2634
+ "no-useless-call": "error",
2635
+ "no-useless-catch": "error",
2636
+ "no-useless-computed-key": "error",
2637
+ "no-useless-constructor": "error",
2638
+ "no-useless-rename": "error",
2639
+ "no-useless-return": "error",
2640
+ "no-var": "error",
2641
+ "no-with": "error",
2642
+ "prefer-arrow-callback": ["error", {
2643
+ allowNamedFunctions: false,
2644
+ allowUnboundThis: true
2645
+ }],
2646
+ "prefer-const": [isInEditor ? "warn" : "error", {
2647
+ destructuring: "all",
2648
+ ignoreReadBeforeAssign: true
2649
+ }],
2650
+ "prefer-exponentiation-operator": "error",
2651
+ "prefer-promise-reject-errors": "error",
2652
+ "prefer-regex-literals": roblox ? "off" : ["error", { disallowRedundantWrapping: true }],
2653
+ "prefer-rest-params": "error",
2654
+ "prefer-spread": "error",
2655
+ "prefer-template": "error",
2656
+ "symbol-description": "error",
2657
+ "unicode-bom": ["error", "never"],
2658
+ "unused-imports/no-unused-imports": isInEditor ? "warn" : "error",
2659
+ "unused-imports/no-unused-vars": ["error", {
2660
+ args: "all",
2661
+ argsIgnorePattern: "^_+",
2662
+ caughtErrors: "all",
2663
+ caughtErrorsIgnorePattern: "^_+",
2664
+ destructuredArrayIgnorePattern: "^_+",
2665
+ ignoreRestSiblings: true,
2666
+ reportUsedIgnorePattern: true,
2667
+ vars: "all",
2668
+ varsIgnorePattern: "^_+"
2669
+ }],
2670
+ "use-isnan": ["error", {
2671
+ enforceForIndexOf: true,
2672
+ enforceForSwitchCase: true
2673
+ }],
2674
+ "valid-typeof": ["error", { requireStringLiterals: true }],
2675
+ "vars-on-top": "error",
2676
+ ...stylistic !== false ? {
2677
+ "id-length": ["error", {
2678
+ exceptions: [
2679
+ "_",
2680
+ "x",
2681
+ "y",
2682
+ "z",
2683
+ "a",
2684
+ "b",
2685
+ "e"
2686
+ ],
2687
+ max: 30,
2688
+ min: 2,
2689
+ properties: "never"
2690
+ }],
2691
+ "max-lines": ["warn", {
2692
+ max: 300,
2693
+ skipBlankLines: true,
2694
+ skipComments: true
2695
+ }],
2696
+ "no-lone-blocks": "error",
2697
+ "no-multi-str": "error",
2698
+ "object-shorthand": [
2699
+ "error",
2700
+ "always",
2701
+ {
2702
+ avoidQuotes: true,
2703
+ ignoreConstructors: false
2704
+ }
2705
+ ],
2706
+ "one-var": ["error", { initialized: "never" }],
2707
+ "yoda": ["error", "never"],
2708
+ ...!roblox ? { "func-style": ["error", "declaration"] } : {}
2709
+ } : {}
2710
+ };
2711
+ }
2712
+ //#endregion
2713
+ //#region src/oxlint/configs/javascript.ts
2714
+ function oxlintJavascript(options = {}) {
2715
+ const { excludeFiles, isInEditor = false, overrides = {}, roblox = true, stylistic = true } = options;
2716
+ const files = options.files?.flat() ?? ["**/*.{,c,m}[jt]s{,x}"];
2717
+ return createOxlintConfigs({
2718
+ name: "isentinel/javascript",
2719
+ ...excludeFiles ? { excludeFiles } : {},
2720
+ files,
2721
+ globals: {
2722
+ ...toGlobals(globals.browser),
2723
+ ...toGlobals(globals.es2021),
2724
+ ...toGlobals(globals.node),
2725
+ document: "readonly",
2726
+ navigator: "readonly",
2727
+ window: "readonly"
2728
+ },
2729
+ rules: {
2730
+ ...javascriptRules({
2731
+ isInEditor,
2732
+ roblox,
2733
+ stylistic
2734
+ }),
2735
+ ...overrides
2736
+ }
2737
+ });
2738
+ }
2739
+ function toGlobals(source, override) {
2740
+ const result = {};
2741
+ for (const [key, value] of Object.entries(source)) result[key] = override ?? (value ? "writable" : "readonly");
2742
+ return result;
2743
+ }
2744
+ //#endregion
2745
+ //#region src/rules/jsdoc.ts
2746
+ /**
2747
+ * JSDoc rules shared between the ESLint and oxlint factories.
2748
+ *
2749
+ * @param options - Shared rule options.
2750
+ * @returns The rule map.
2751
+ */
2752
+ function jsdocRules({ full = false, stylistic = true, type = "game" } = {}) {
2753
+ return {
2754
+ "jsdoc/check-access": "warn",
2755
+ "jsdoc/check-param-names": ["warn", { checkDestructured: false }],
2756
+ "jsdoc/check-property-names": "warn",
2757
+ "jsdoc/check-types": "warn",
2758
+ "jsdoc/empty-tags": "warn",
2759
+ "jsdoc/implements-on-classes": "warn",
2760
+ "jsdoc/informative-docs": "warn",
2761
+ "jsdoc/no-defaults": "warn",
2762
+ "jsdoc/no-types": "warn",
2763
+ "jsdoc/no-undefined-types": "error",
2764
+ "jsdoc/require-description": ["warn", { exemptedBy: [
2765
+ "hidden",
2766
+ "ignore",
2767
+ "inheritdoc",
2768
+ "client",
2769
+ "server",
2770
+ "see",
2771
+ "metadata"
2772
+ ] }],
2773
+ "jsdoc/require-description-complete-sentence": "warn",
2774
+ "jsdoc/require-param-description": "warn",
2775
+ "jsdoc/require-param-name": "warn",
2776
+ "jsdoc/require-property": "warn",
2777
+ "jsdoc/require-property-description": "warn",
2778
+ "jsdoc/require-property-name": "warn",
2779
+ "jsdoc/require-rejects": "error",
2780
+ "jsdoc/require-returns-check": "warn",
2781
+ "jsdoc/require-returns-description": "warn",
2782
+ "jsdoc/require-yields-check": "warn",
2783
+ "jsdoc/sort-tags": "off",
2784
+ ...type === "package" || full ? {
2785
+ "jsdoc/require-param": ["warn", {
2786
+ checkDestructured: false,
2787
+ exemptedBy: ["ignore"]
2788
+ }],
2789
+ "jsdoc/require-returns": ["warn", { exemptedBy: ["hidden"] }],
2790
+ "jsdoc/require-template": "warn"
2791
+ } : {},
2792
+ ...stylistic !== false ? {
2793
+ "jsdoc/check-alignment": "warn",
2794
+ "jsdoc/convert-to-jsdoc-comments": ["warn", { allowedPrefixes: [
2795
+ "@ts-",
2796
+ "istanbul ",
2797
+ "c8 ",
2798
+ "v8 ",
2799
+ "eslint",
2800
+ "jshint",
2801
+ "jslint",
2802
+ "globals",
2803
+ "exported",
2804
+ "jscs",
2805
+ "oxlint-",
2806
+ "prettier-"
2807
+ ] }],
2808
+ "jsdoc/multiline-blocks": "warn",
2809
+ "jsdoc/no-blank-block-descriptions": "warn",
2810
+ "jsdoc/no-blank-blocks": "warn",
2811
+ "jsdoc/no-multi-asterisks": "warn",
2812
+ "jsdoc/normalize-see-links": "warn",
2813
+ "jsdoc/require-asterisk-prefix": "warn",
2814
+ "jsdoc/require-hyphen-before-param-description": "warn"
2815
+ } : {}
2816
+ };
2817
+ }
2818
+ //#endregion
2819
+ //#region src/oxlint/configs/jsdoc.ts
2820
+ function oxlintJsdoc(options = {}) {
2821
+ return createOxlintConfigs({
2822
+ name: "isentinel/jsdoc",
2823
+ files: [GLOB_SRC],
2824
+ rules: jsdocRules(options)
2825
+ });
2826
+ }
2827
+ //#endregion
2828
+ //#region src/rules/node.ts
2829
+ /**
2830
+ * Node.js rules shared between the ESLint and oxlint factories.
2831
+ *
2832
+ * @returns The rule map.
2833
+ */
2834
+ function nodeRules() {
2835
+ return {
2836
+ "node/handle-callback-err": ["error", "^(err|error)$"],
2837
+ "node/no-deprecated-api": "error",
2838
+ "node/no-exports-assign": "error",
2839
+ "node/no-new-require": "error",
2840
+ "node/no-path-concat": "error",
2841
+ "node/prefer-global/buffer": ["error", "never"],
2842
+ "node/prefer-global/process": ["error", "never"],
2843
+ "node/prefer-node-protocol": "error",
2844
+ "node/process-exit-as-throw": "error"
2845
+ };
2846
+ }
2847
+ //#endregion
2848
+ //#region src/oxlint/configs/node.ts
2849
+ function oxlintNode(options = {}) {
2850
+ const files = options.files?.flat() ?? ["**/*.{,c,m}[jt]s{,x}"];
2851
+ return createOxlintConfigs({
2852
+ name: "isentinel/node",
2853
+ ...options.excludeFiles ? { excludeFiles: options.excludeFiles } : {},
2854
+ files,
2855
+ rules: nodeRules()
2856
+ });
2857
+ }
2858
+ //#endregion
2859
+ //#region src/oxlint/configs/oxc.ts
2860
+ /**
2861
+ * Oxlint's native `oxc/*` rules: correctness, performance and suspicious-pattern
2862
+ * checks with no ESLint equivalent, so they only run under oxlint.
2863
+ *
2864
+ * The rules are emitted as a raw fragment (`plugins: ["oxc"]` with literal
2865
+ * `oxc/*` names) rather than through the shared `createOxlintConfigs` helper,
2866
+ * since the canonical-name translation layer only knows rules that exist on the
2867
+ * ESLint side.
2868
+ *
2869
+ * @param options - Shared rule options.
2870
+ * @returns The oxc config fragment.
2871
+ */
2872
+ function oxlintOxc(options = {}) {
2873
+ const { excludeFiles, roblox = true } = options;
2874
+ const files = options.files?.flat() ?? ["**/*.{,c,m}[jt]s{,x}"];
2875
+ return [{
2876
+ name: "isentinel/oxc",
2877
+ ...excludeFiles ? { excludeFiles } : {},
2878
+ files,
2879
+ plugins: ["oxc"],
2880
+ rules: {
2881
+ "oxc/approx-constant": "error",
2882
+ "oxc/bad-array-method-on-arguments": "error",
2883
+ "oxc/bad-char-at-comparison": "error",
2884
+ "oxc/bad-comparison-sequence": "error",
2885
+ "oxc/bad-min-max-func": "error",
2886
+ "oxc/bad-object-literal-comparison": "error",
2887
+ "oxc/bad-replace-all-arg": "error",
2888
+ "oxc/branches-sharing-code": "error",
2889
+ "oxc/const-comparisons": "error",
2890
+ "oxc/double-comparisons": "error",
2891
+ "oxc/erasing-op": "error",
2892
+ "oxc/misrefactored-assign-op": "error",
2893
+ "oxc/missing-throw": "error",
2894
+ "oxc/no-accumulating-spread": "error",
2895
+ "oxc/no-barrel-file": "error",
2896
+ "oxc/no-map-spread": "error",
2897
+ "oxc/no-this-in-exported-function": "error",
2898
+ "oxc/number-arg-out-of-range": "error",
2899
+ "oxc/only-used-in-recursion": "error",
2900
+ "oxc/uninvoked-array-callback": "error",
2901
+ ...!roblox ? {
2902
+ "oxc/bad-bitwise-operator": "error",
2903
+ "oxc/no-const-enum": "error"
2904
+ } : {}
2905
+ }
2906
+ }];
2907
+ }
2908
+ //#endregion
2909
+ //#region src/eslint/plugin-renaming.ts
2910
+ const defaultPluginRenaming = {
2911
+ "@eslint-react": "react",
2912
+ "@eslint-react/hooks-extra": "react-hooks-extra",
2913
+ "@eslint-react/naming-convention": "react-naming-convention",
2914
+ "@isentinel/eslint-plugin-comment-length": "comment-length",
2915
+ "@stylistic": "style",
2916
+ "@typescript-eslint": "ts",
2917
+ "n": "node",
2918
+ "yml": "yaml"
2919
+ };
2920
+ //#endregion
2921
+ //#region src/oxlint/configs/oxfmt.ts
2922
+ const require$1 = createRequire(import.meta.url);
2923
+ function oxlintOxfmt(options) {
2924
+ const { componentExts: componentExtensions = [], files: oxfmtFiles, oxfmtOptions = {} } = options ?? {};
2925
+ const configPrettier = require$1("eslint-config-prettier/flat");
2926
+ const configPrettierRules = isRecord(configPrettier) && isRecord(configPrettier["rules"]) ? configPrettier["rules"] : {};
2927
+ const rulesToIgnore = /* @__PURE__ */ new Set(["curly", "style/quotes"]);
2928
+ const canonicalDisables = {};
2929
+ const prettierRuleNames = Object.keys(renameRules(configPrettierRules, defaultPluginRenaming));
2930
+ for (const key of prettierRuleNames) if (!rulesToIgnore.has(key)) canonicalDisables[key] = "off";
2931
+ const { jsPluginRules, jsPlugins, nativeRules } = splitOxlintRules(canonicalDisables);
2932
+ const stylisticPlugin = require$1("@stylistic/eslint-plugin");
2933
+ const stylisticRules = isRecord(stylisticPlugin) && isRecord(stylisticPlugin["rules"]) ? stylisticPlugin["rules"] : {};
2934
+ for (const [rule, value] of Object.entries(canonicalDisables)) {
2935
+ if (!rule.startsWith("style/") || jsPluginRules[rule] !== void 0) continue;
2936
+ if (stylisticRules[rule.slice(6)] !== void 0) jsPluginRules[rule] = value;
2937
+ }
2938
+ if (!jsPlugins.some((plugin) => typeof plugin !== "string" && plugin.name === "style")) jsPlugins.push({
2939
+ name: "style",
2940
+ specifier: resolveJsPluginSpecifier("@stylistic/eslint-plugin")
2941
+ });
2942
+ const oxfmtPlugin = {
2943
+ name: "oxfmt",
2944
+ specifier: resolveJsPluginSpecifier("eslint-plugin-oxfmt")
2945
+ };
2946
+ const jsFiles = oxfmtFiles?.flat() ?? ["**/*.{,c,m}js", "**/*.{,c,m}jsx"];
2947
+ const tsFiles = oxfmtFiles?.flat() ?? [
2948
+ "**/*.{,c,m}ts",
2949
+ "**/*.{,c,m}tsx",
2950
+ ...componentExtensions.map((extension) => `**/*.${extension}`)
2951
+ ];
2952
+ function createFragments(name, files) {
2953
+ return [{
2954
+ name,
2955
+ files,
2956
+ rules: {
2957
+ ...nativeRules,
2958
+ "arrow-body-style": "off",
2959
+ "prefer-arrow-callback": "off"
2960
+ }
2961
+ }, {
2962
+ name: `${name}/js-plugin`,
2963
+ files,
2964
+ jsPlugins: [...jsPlugins, oxfmtPlugin],
2965
+ rules: {
2966
+ ...jsPluginRules,
2967
+ "oxfmt/oxfmt": ["error", oxfmtOptions]
2968
+ }
2969
+ }];
2970
+ }
2971
+ return [...createFragments("isentinel/oxfmt/javascript", jsFiles), ...createFragments("isentinel/oxfmt/typescript", tsFiles)];
2972
+ }
2973
+ //#endregion
2974
+ //#region src/rules/perfectionist.ts
2975
+ const constructorGroup = {
2976
+ elementNamePattern: "constructor",
2977
+ groupName: "custom-constructor"
2978
+ };
2979
+ /** Shared perfectionist plugin settings for both factories. */
2980
+ const perfectionistSettings = { perfectionist: {
2981
+ order: "asc",
2982
+ partitionByComment: "^Part:\\s*(.*)$",
2983
+ type: "natural"
2984
+ } };
2985
+ /**
2986
+ * Perfectionist rules shared between the ESLint and oxlint factories.
2987
+ *
2988
+ * @param config - Shared rule options.
2989
+ * @returns The rule map.
2990
+ */
2991
+ function perfectionistRules(config) {
2992
+ const { customClassGroups = [], sortObjects, type = "game" } = config ?? {};
2993
+ const customGroups = [
2994
+ ...Array.from(customClassGroups, (customGroup) => {
2995
+ return {
2996
+ elementNamePattern: customGroup,
2997
+ groupName: customGroup
2998
+ };
2999
+ }),
3000
+ constructorGroup,
3001
+ createUnsortedMethod("private"),
3002
+ createUnsortedMethod("protected"),
3003
+ createUnsortedMethod("public")
3004
+ ];
3005
+ function createUnsortedMethod(methodType) {
3006
+ return {
3007
+ groupName: methodType,
3008
+ modifiers: [methodType],
3009
+ newlinesInside: 1,
3010
+ selector: "method",
3011
+ type: type === "game" ? "unsorted" : "natural"
3012
+ };
3013
+ }
3014
+ const sortedObjectConfig = sortObjects ?? {
3015
+ customGroups: [
3016
+ {
3017
+ elementNamePattern: "^id$",
3018
+ groupName: "id"
3019
+ },
3020
+ {
3021
+ elementNamePattern: "^key$",
3022
+ groupName: "key"
3023
+ },
3024
+ {
3025
+ elementNamePattern: "^name$",
3026
+ groupName: "name"
3027
+ }
3028
+ ],
3029
+ groups: [
3030
+ "id",
3031
+ "key",
3032
+ "name",
3033
+ "unknown"
3034
+ ]
3035
+ };
3036
+ return {
3037
+ "perfectionist/sort-array-includes": ["error"],
3038
+ "perfectionist/sort-classes": ["warn", {
3039
+ customGroups,
3040
+ fallbackSort: {
3041
+ order: "asc",
3042
+ type: "natural"
3043
+ },
3044
+ groups: [
3045
+ "private-static-readonly-property",
3046
+ "private-readonly-property",
3047
+ "private-static-property",
3048
+ "private-property",
3049
+ "protected-static-readonly-property",
3050
+ "protected-readonly-property",
3051
+ "protected-static-property",
3052
+ "protected-property",
3053
+ "public-static-readonly-property",
3054
+ "public-readonly-property",
3055
+ "public-static-property",
3056
+ "public-property",
3057
+ "custom-constructor",
3058
+ ...customClassGroups.reduce((accumulator, item) => {
3059
+ accumulator.push(item);
3060
+ return accumulator;
3061
+ }, []),
3062
+ "public",
3063
+ "protected",
3064
+ "private",
3065
+ "unknown"
3066
+ ],
3067
+ newlinesBetween: 1,
3068
+ useExperimentalDependencyDetection: true
3069
+ }],
3070
+ "perfectionist/sort-decorators": ["error"],
3071
+ "perfectionist/sort-enums": ["error", { sortByValue: "always" }],
3072
+ "perfectionist/sort-exports": ["error"],
3073
+ "perfectionist/sort-heritage-clauses": ["error", {
3074
+ customGroups: customClassGroups.map((item) => {
3075
+ return {
3076
+ elementNamePattern: `^${capitalizeFirstLetter(item)}$`,
3077
+ groupName: item
3078
+ };
3079
+ }),
3080
+ groups: [...customClassGroups, "unknown"]
3081
+ }],
3082
+ "perfectionist/sort-interfaces": ["error", { ...sortedObjectConfig }],
3083
+ "perfectionist/sort-intersection-types": ["error"],
3084
+ "perfectionist/sort-maps": ["error"],
3085
+ "perfectionist/sort-named-imports": ["error"],
3086
+ "perfectionist/sort-object-types": ["error"],
3087
+ "perfectionist/sort-objects": ["error", { ...sortedObjectConfig }],
3088
+ "perfectionist/sort-sets": ["error"],
3089
+ "perfectionist/sort-switch-case": ["error"],
3090
+ "perfectionist/sort-union-types": ["error"],
3091
+ "perfectionist/sort-variable-declarations": ["error"],
3092
+ ...type === "package" ? { "perfectionist/sort-modules": ["error", { type: "usage" }] } : {}
3093
+ };
3094
+ }
3095
+ /**
3096
+ * JSX-specific perfectionist rules shared between the ESLint and oxlint
3097
+ * factories.
3098
+ *
3099
+ * @param config - Shared rule options.
3100
+ * @returns The rule map.
3101
+ */
3102
+ function perfectionistJsxRules(config) {
3103
+ const { sortObjects } = config ?? {};
3104
+ const sortedObjectJsxConfig = sortObjects ?? {
3105
+ customGroups: [
3106
+ {
3107
+ elementNamePattern: "^id$",
3108
+ groupName: "id"
3109
+ },
3110
+ {
3111
+ elementNamePattern: "^key$",
3112
+ groupName: "key"
3113
+ },
3114
+ {
3115
+ elementNamePattern: "^name$",
3116
+ groupName: "name"
3117
+ },
3118
+ {
3119
+ elementNamePattern: ["\b(on[A-Z][a-zA-Z]*)\b"],
3120
+ groupName: "callbacks"
3121
+ },
3122
+ {
3123
+ elementNamePattern: ["^children$", "^ref$"],
3124
+ groupName: "react"
3125
+ }
3126
+ ],
3127
+ groups: [
3128
+ "id",
3129
+ "key",
3130
+ "name",
3131
+ "unknown",
3132
+ "react",
3133
+ "callbacks"
3134
+ ]
3135
+ };
3136
+ return {
3137
+ "perfectionist/sort-interfaces": ["error", { ...sortedObjectJsxConfig }],
3138
+ "perfectionist/sort-jsx-props": ["error", {
3139
+ customGroups: [{
3140
+ elementNamePattern: "^(?:key|ref)$",
3141
+ groupName: "reserved"
3142
+ }, {
3143
+ elementNamePattern: "^on.+",
3144
+ groupName: "callback"
3145
+ }],
3146
+ groups: [
3147
+ "reserved",
3148
+ "shorthand-prop",
3149
+ "unknown",
3150
+ "callback"
3151
+ ]
3152
+ }],
3153
+ "perfectionist/sort-objects": ["error", { ...sortedObjectJsxConfig }]
3154
+ };
3155
+ }
3156
+ function capitalizeFirstLetter(value) {
3157
+ return value.charAt(0).toUpperCase() + value.slice(1);
3158
+ }
3159
+ //#endregion
3160
+ //#region src/oxlint/configs/perfectionist.ts
3161
+ function oxlintPerfectionist(config) {
3162
+ return [...createOxlintConfigs({
3163
+ name: "isentinel/perfectionist",
3164
+ files: [GLOB_SRC],
3165
+ rules: perfectionistRules(config),
3166
+ settings: perfectionistSettings
3167
+ }), ...createOxlintConfigs({
3168
+ name: "isentinel/perfectionist/jsx",
3169
+ files: [GLOB_JSX, GLOB_TSX],
3170
+ rules: perfectionistJsxRules(config)
3171
+ })];
3172
+ }
3173
+ //#endregion
3174
+ //#region src/rules/promise.ts
3175
+ /**
3176
+ * Promise rules shared between the ESLint and oxlint factories.
3177
+ *
3178
+ * @returns The rule map.
3179
+ */
3180
+ function promiseRules() {
3181
+ return {
3182
+ "promise/always-return": ["error", { ignoreLastCallback: true }],
3183
+ "promise/avoid-new": "off",
3184
+ "promise/catch-or-return": ["error", {
3185
+ allowFinally: true,
3186
+ allowThen: true
3187
+ }],
3188
+ "promise/no-callback-in-promise": "off",
3189
+ "promise/no-multiple-resolved": "error",
3190
+ "promise/no-native": "off",
3191
+ "promise/no-nesting": "warn",
3192
+ "promise/no-new-statics": "off",
3193
+ "promise/no-promise-in-callback": "warn",
3194
+ "promise/no-return-in-finally": "warn",
3195
+ "promise/no-return-wrap": "error",
3196
+ "promise/param-names": "warn",
3197
+ "promise/prefer-await-to-callbacks": "off",
3198
+ "promise/prefer-await-to-then": "off"
3199
+ };
3200
+ }
3201
+ //#endregion
3202
+ //#region src/oxlint/configs/promise.ts
3203
+ function oxlintPromise() {
3204
+ return createOxlintConfigs({
3205
+ name: "isentinel/promise",
3206
+ files: [GLOB_SRC],
3207
+ rules: promiseRules()
3208
+ });
3209
+ }
3210
+ //#endregion
3211
+ //#region src/rules/react.ts
3212
+ /**
3213
+ * React rules shared between the ESLint and oxlint factories.
3214
+ *
3215
+ * The type-aware react rules stay in the ESLint config (oxlint jsPlugins have
3216
+ * no type information).
3217
+ *
3218
+ * @param options - Shared rule options.
3219
+ * @returns The rule map.
3220
+ */
3221
+ function reactRules({ filenameCase = "kebabCase", reactCompiler = true, stylistic = true } = {}) {
3222
+ return {
3223
+ "flawless/no-unnecessary-use-callback": "error",
3224
+ "flawless/no-unnecessary-use-memo": "error",
3225
+ "flawless/purity": "error",
3226
+ "small-rules/react-hooks-strict-return": "error",
3227
+ ...reactCompiler ? {
3228
+ "react/globals": "error",
3229
+ "react/refs": "error"
3230
+ } : {},
3231
+ "react-jsx/no-children-prop": "warn",
3232
+ "react-jsx/no-children-prop-with-children": "error",
3233
+ "react-jsx/no-comment-textnodes": "off",
3234
+ "react-jsx/no-key-after-spread": "error",
3235
+ "react-jsx/no-leaked-dollar": "warn",
3236
+ "react-jsx/no-leaked-semicolon": "warn",
3237
+ "react/error-boundaries": "error",
3238
+ "react/exhaustive-deps": "error",
3239
+ "react/immutability": "error",
3240
+ "react/no-access-state-in-setstate": "error",
3241
+ "react/no-array-index-key": "warn",
3242
+ "react/no-children-count": "warn",
3243
+ "react/no-children-for-each": "warn",
3244
+ "react/no-children-map": "warn",
3245
+ "react/no-children-only": "warn",
3246
+ "react/no-children-to-array": "warn",
3247
+ "react/no-class-component": "error",
3248
+ "react/no-clone-element": "warn",
3249
+ "react/no-component-will-mount": "error",
3250
+ "react/no-component-will-receive-props": "error",
3251
+ "react/no-component-will-update": "error",
3252
+ "react/no-create-ref": "error",
3253
+ "react/no-direct-mutation-state": "error",
3254
+ "react/no-duplicate-key": "error",
3255
+ "react/no-forward-ref": "off",
3256
+ "react/no-missing-component-display-name": "error",
3257
+ "react/no-missing-context-display-name": "error",
3258
+ "react/no-missing-key": "error",
3259
+ "react/no-misused-capture-owner-stack": "off",
3260
+ "react/no-nested-component-definitions": "warn",
3261
+ "react/no-nested-lazy-component-declarations": "warn",
3262
+ "react/no-set-state-in-component-did-mount": "warn",
3263
+ "react/no-set-state-in-component-did-update": "warn",
3264
+ "react/no-set-state-in-component-will-update": "warn",
3265
+ "react/no-unnecessary-use-prefix": "error",
3266
+ "react/no-unsafe-component-will-mount": "error",
3267
+ "react/no-unsafe-component-will-receive-props": "error",
3268
+ "react/no-unsafe-component-will-update": "error",
3269
+ "react/no-unstable-context-value": "error",
3270
+ "react/no-unstable-default-props": ["error", { safeDefaultProps: [
3271
+ "Axes",
3272
+ "BrickColor",
3273
+ "CatalogSearchParams",
3274
+ "CFrame",
3275
+ "Color3",
3276
+ "ColorSequence",
3277
+ "CFrame",
3278
+ "Content",
3279
+ "DateTime",
3280
+ "DockWidgetPluginGuiInfo",
3281
+ "Enum",
3282
+ "Faces",
3283
+ "FloatCurveKey",
3284
+ "Font",
3285
+ "NumberRange",
3286
+ "NumberSequence",
3287
+ "NumberSequenceKeypoint",
3288
+ "OverlapParams",
3289
+ "Path2DControlPoint",
3290
+ "PathWaypoint",
3291
+ "PhysicalProperties",
3292
+ "Ray",
3293
+ "RaycastParams",
3294
+ "Rect",
3295
+ "Region3",
3296
+ "Region3int16",
3297
+ "RotationCurveKey",
3298
+ "SecurityCapabilities",
3299
+ "TweenInfo",
3300
+ "UDim",
3301
+ "UDim2",
3302
+ "ValueCurveKey",
3303
+ "Vector2",
3304
+ "Vector3",
3305
+ "Vector3int16"
3306
+ ] }],
3307
+ "react/no-unused-class-component-members": "off",
3308
+ "react/no-unused-state": "error",
3309
+ "react/no-use-context": "off",
3310
+ "react/rules-of-hooks": "error",
3311
+ "react/set-state-in-effect": "error",
3312
+ "react/set-state-in-render": "error",
3313
+ "react/static-components": "error",
3314
+ "react/use-memo": "error",
3315
+ "react/use-state": ["error", {
3316
+ enforceAssignment: false,
3317
+ enforceSetterName: false
3318
+ }],
3319
+ ...stylistic !== false ? {
3320
+ "flawless/jsx-shorthand-boolean": "warn",
3321
+ "flawless/jsx-shorthand-fragment": "warn",
3322
+ "flawless/prefer-destructuring-assignment": "error",
3323
+ "flawless/react-namespace": "error",
3324
+ "one-var": "off",
3325
+ "react-jsx/no-useless-fragment": "warn",
3326
+ "react-naming-convention/context-name": "error",
3327
+ "react-naming-convention/ref-name": "error",
3328
+ "react/use-state": "error",
3329
+ "style/jsx-curly-brace-presence": ["error", {
3330
+ children: "never",
3331
+ propElementValues: "always",
3332
+ props: "never"
3333
+ }],
3334
+ "style/jsx-newline": "error",
3335
+ "style/jsx-self-closing-comp": "error",
3336
+ "unicorn/filename-case": ["error", {
3337
+ case: filenameCase,
3338
+ ignore: ["^[A-Z0-9]+.md$"],
3339
+ multipleFileExtensions: true
3340
+ }]
3341
+ } : {}
3342
+ };
3343
+ }
3344
+ //#endregion
3345
+ //#region src/oxlint/configs/react.ts
3346
+ function oxlintReact(options = {}) {
3347
+ const { filenameCase = "kebabCase", importSource, overrides = {}, reactCompiler = true, stylistic = true } = options;
3348
+ return createOxlintConfigs({
3349
+ name: "isentinel/react",
3350
+ files: options.files?.flat() ?? ["**/*.{,c,m}jsx", "**/*.{,c,m}tsx"],
3351
+ rules: {
3352
+ ...reactRules({
3353
+ filenameCase,
3354
+ reactCompiler,
3355
+ stylistic
3356
+ }),
3357
+ ...overrides
3358
+ },
3359
+ settings: { "react-x": {
3360
+ importSource: importSource ?? "@rbxts",
3361
+ version: "17.0.2"
3362
+ } }
3363
+ });
3364
+ }
3365
+ //#endregion
3366
+ //#region src/rules/roblox.ts
3367
+ /**
3368
+ * Roblox rules shared between the ESLint and oxlint factories.
3369
+ *
3370
+ * These do not require type information; the type-aware roblox rules stay in
3371
+ * the ESLint config (oxlint jsPlugins have no type information).
3372
+ *
3373
+ * @param options - Shared rule options.
3374
+ * @returns The rule map.
3375
+ */
3376
+ function robloxRules({ stylistic = true } = {}) {
3377
+ return {
3378
+ "roblox/no-any": "error",
3379
+ "roblox/no-enum-merging": "error",
3380
+ "roblox/no-export-assignment-let": "error",
3381
+ "roblox/no-for-in": "error",
3382
+ "roblox/no-function-expression-name": "error",
3383
+ "roblox/no-get-set": "error",
3384
+ "roblox/no-implicit-self": "error",
3385
+ "roblox/no-invalid-identifier": "error",
3386
+ "roblox/no-namespace-merging": "error",
3387
+ "roblox/no-null": "error",
3388
+ "roblox/no-private-identifier": "error",
3389
+ "roblox/no-unsupported-syntax": "error",
3390
+ "roblox/no-user-defined-lua-tuple": "error",
3391
+ "roblox/no-value-typeof": "error",
3392
+ "roblox/prefer-get-players": "error",
3393
+ "roblox/prefer-task-library": "error",
3394
+ "small-rules/no-array-size-assignment": "error",
3395
+ "small-rules/no-recursive": "error",
3396
+ ...stylistic !== false ? {
3397
+ "sentinel/prefer-math-min-max": "error",
3398
+ "small-rules/array-type-generic": "error",
3399
+ "small-rules/no-array-constructor-elements": "error"
3400
+ } : {}
3401
+ };
3402
+ }
3403
+ //#endregion
3404
+ //#region src/oxlint/configs/roblox.ts
3405
+ function oxlintRoblox(options = {}) {
3406
+ const { componentExts: componentExtensions = [], overrides = {}, stylistic = true } = options;
3407
+ return createOxlintConfigs({
3408
+ name: "isentinel/roblox",
3409
+ files: options.files?.flat() ?? [
3410
+ "**/*/*.{,c,m}ts",
3411
+ "**/*/*.{,c,m}tsx",
3412
+ ...componentExtensions.map((extension) => `**/*/*.${extension}`)
3413
+ ],
3414
+ rules: {
3415
+ ...robloxRules({ stylistic }),
3416
+ ...overrides
3417
+ }
3418
+ });
3419
+ }
3420
+ //#endregion
3421
+ //#region src/rules/small-rules.ts
3422
+ /**
3423
+ * Small-rules rules shared between the ESLint and oxlint factories.
3424
+ *
3425
+ * These do not require type information. The type-aware
3426
+ * `flawless/prefer-read-only-props` replaces the former read-only-props rule
3427
+ * and is configured by the flawless config.
3428
+ *
3429
+ * @param options - Shared rule options.
3430
+ * @returns The rule map.
3431
+ */
3432
+ function smallRulesRules({ isInEditor = false, stylistic = true } = {}) {
3433
+ return {
3434
+ "small-rules/no-async-constructor": "error",
3435
+ "small-rules/no-commented-code": isInEditor ? "off" : "error",
3436
+ "small-rules/prefer-class-properties": "error",
3437
+ "small-rules/prefer-early-return": ["error", { maximumStatements: 1 }],
3438
+ "small-rules/strict-component-boundaries": "error",
3439
+ ...stylistic !== false ? {
3440
+ "small-rules/prefer-module-scope-constants": "error",
3441
+ "small-rules/prefer-singular-enums": "error",
3442
+ "small-rules/require-async-suffix": "error"
3443
+ } : {}
3444
+ };
3445
+ }
3446
+ //#endregion
3447
+ //#region src/oxlint/configs/small-rules.ts
3448
+ function oxlintSmallRules(options = {}) {
3449
+ const { componentExts: componentExtensions = [], isInEditor = false, stylistic = true } = options;
3450
+ return createOxlintConfigs({
3451
+ name: "isentinel/small-rules",
3452
+ files: options.files?.flat() ?? [
3453
+ "**/*.{,c,m}ts",
3454
+ "**/*.{,c,m}tsx",
3455
+ ...componentExtensions.map((extension) => `**/*.${extension}`)
3456
+ ],
3457
+ rules: smallRulesRules({
3458
+ isInEditor,
3459
+ stylistic
3460
+ })
3461
+ });
3462
+ }
3463
+ //#endregion
3464
+ //#region src/rules/sonarjs.ts
3465
+ /**
3466
+ * SonarJS rules shared between the ESLint and oxlint factories.
3467
+ *
3468
+ * @param options - Shared rule options.
3469
+ * @returns The rule map.
3470
+ */
3471
+ function sonarjsRules({ isInEditor, roblox = true }) {
3472
+ return {
3473
+ "sonar/bool-param-default": "error",
3474
+ "sonar/cognitive-complexity": "warn",
3475
+ "sonar/constructor-for-side-effects": "error",
3476
+ "sonar/destructuring-assignment-syntax": "error",
3477
+ "sonar/elseif-without-else": "off",
3478
+ "sonar/file-name-differ-from-class": "error",
3479
+ "sonar/fixme-tag": isInEditor ? "warn" : "off",
3480
+ "sonar/max-switch-cases": "error",
3481
+ "sonar/misplaced-loop-counter": "error",
3482
+ "sonar/no-all-duplicated-branches": "error",
3483
+ "sonar/no-collapsible-if": "error",
3484
+ "sonar/no-commented-code": "off",
3485
+ "sonar/no-dead-store": "error",
3486
+ "sonar/no-duplicate-string": ["error", { ignoreStrings: "Not implemented" }],
3487
+ "sonar/no-duplicated-branches": "error",
3488
+ "sonar/no-element-overwrite": "error",
3489
+ "sonar/no-empty-collection": "error",
3490
+ "sonar/no-floating-point-equality": "error",
3491
+ "sonar/no-gratuitous-expressions": "off",
3492
+ "sonar/no-identical-conditions": "error",
3493
+ "sonar/no-identical-expressions": "error",
3494
+ "sonar/no-identical-functions": "error",
3495
+ "sonar/no-ignored-return": "error",
3496
+ "sonar/no-inverted-boolean-check": "error",
3497
+ "sonar/no-nested-conditional": "error",
3498
+ "sonar/no-nested-incdec": "error",
3499
+ "sonar/no-nested-switch": "error",
3500
+ "sonar/no-nested-template-literals": "error",
3501
+ "sonar/no-parameter-reassignment": "error",
3502
+ "sonar/no-redundant-boolean": "error",
3503
+ "sonar/no-redundant-jump": "error",
3504
+ "sonar/no-redundant-optional": "error",
3505
+ "sonar/no-try-promise": "error",
3506
+ "sonar/no-unthrown-error": "error",
3507
+ "sonar/no-unused-collection": "error",
3508
+ "sonar/no-use-of-empty-return-value": "error",
3509
+ "sonar/no-useless-catch": "error",
3510
+ "sonar/no-useless-increment": "error",
3511
+ "sonar/non-existent-operator": "error",
3512
+ "sonar/prefer-immediate-return": "error",
3513
+ "sonar/prefer-object-literal": "error",
3514
+ "sonar/prefer-promise-shorthand": "error",
3515
+ "sonar/prefer-single-boolean-return": "error",
3516
+ "sonar/prefer-while": "error",
3517
+ "sonar/public-static-readonly": "error",
3518
+ "sonar/todo-tag": isInEditor ? "warn" : "off",
3519
+ "sonar/use-type-alias": "error",
3520
+ ...!roblox ? {
3521
+ "sonar/no-default-utility-imports": "error",
3522
+ "sonar/super-linear-regex": "error"
3523
+ } : {}
3524
+ };
3525
+ }
3526
+ /**
3527
+ * SonarJS rules that only apply to test files.
3528
+ *
3529
+ * `no-mixed-completion-style` and `synchronous-suite-callback` are jest-only:
3530
+ * both exclude vitest by design, since vitest awaits the suite callback and
3531
+ * does not support the `done` callback, so neither reports in a vitest file.
3532
+ *
3533
+ * `super-linear-regex` is turned off for test files (mirroring its `!roblox`
3534
+ * gating in {@link sonarjsRules}): test files carry adversarial/edge-case
3535
+ * patterns and are not attack surface, so its ReDoS warning is noise there.
3536
+ *
3537
+ * @param options - Which test framework the rules are being enabled for.
3538
+ * @returns The rule map.
3539
+ */
3540
+ function sonarjsTestRules({ jest = false, roblox = true } = {}) {
3541
+ return {
3542
+ "sonar/no-incompatible-assertion-types": "error",
3543
+ "sonar/no-trivial-assertions": "error",
3544
+ ...jest ? {
3545
+ "sonar/no-mixed-completion-style": "error",
3546
+ "sonar/synchronous-suite-callback": "error"
3547
+ } : {},
3548
+ ...!roblox ? { "sonar/super-linear-regex": "off" } : {}
3549
+ };
3550
+ }
3551
+ //#endregion
3552
+ //#region src/oxlint/configs/sonarjs.ts
3553
+ function oxlintSonarjs(options) {
3554
+ return createOxlintConfigs({
3555
+ name: "isentinel/sonarjs",
3556
+ files: [GLOB_SRC],
3557
+ rules: sonarjsRules(options)
3558
+ });
3559
+ }
3560
+ //#endregion
3561
+ //#region src/rules/spelling.ts
3562
+ const require = createRequire(import.meta.url);
3563
+ /**
3564
+ * CSpell rules shared between the ESLint and oxlint factories.
3565
+ *
3566
+ * @param options - Shared rule options.
3567
+ * @returns The rule map.
3568
+ */
3569
+ function spellingRules({ inEditor, isInEditor = false, language = "en-US" } = {}) {
3570
+ const urlRobloxDictionary = pathToFileURL(require.resolve("@isentinel/dict-roblox"));
3571
+ const urlRoblox = new URL("dict/roblox.txt", urlRobloxDictionary);
3572
+ const urlRbxtsDictionary = pathToFileURL(require.resolve("@isentinel/dict-rbxts"));
3573
+ const urlRbxts = new URL("dict/rbxts.txt", urlRbxtsDictionary);
3574
+ return { "@cspell/spellchecker": [(inEditor === false ? isInEditor : true) ? "warn" : "off", {
3575
+ autoFix: false,
3576
+ checkComments: true,
3577
+ cspell: {
3578
+ dictionaries: ["roblox", "rbxts"],
3579
+ dictionaryDefinitions: [{
3580
+ name: "roblox",
3581
+ path: urlRoblox.href
3582
+ }, {
3583
+ name: "rbxts",
3584
+ path: urlRbxts.href
3585
+ }],
3586
+ language,
3587
+ words: ["isentinel", "uninvoked"]
3588
+ },
3589
+ generateSuggestions: isInEditor,
3590
+ numSuggestions: isInEditor ? 8 : 0
3591
+ }] };
3592
+ }
3593
+ //#endregion
3594
+ //#region src/oxlint/configs/spelling.ts
3595
+ function oxlintSpelling(options = {}) {
3596
+ const { componentExts: componentExtensions = [] } = options;
3597
+ return createOxlintConfigs({
3598
+ name: "isentinel/spelling",
3599
+ files: options.files?.flat() ?? ["**/*.{,c,m}[jt]s{,x}", ...componentExtensions.map((extension) => `**/*.${extension}`)],
3600
+ rules: spellingRules(options)
3601
+ });
3602
+ }
3603
+ //#endregion
3604
+ //#region src/rules/stylistic.ts
3605
+ /**
3606
+ * Stylistic rules shared between the ESLint and oxlint factories.
3607
+ *
3608
+ * These complement the `@stylistic` customize preset (ESLint side) and are
3609
+ * the stylistic rules that survive the formatter disables.
3610
+ *
3611
+ * @param options - Resolved stylistic options.
3612
+ * @returns The rule map.
3613
+ */
3614
+ function stylisticRules({ jsx = true, quotes = "double" } = {}) {
3615
+ return {
3616
+ "antfu/consistent-list-newline": "off",
3617
+ "antfu/if-newline": "off",
3618
+ "antfu/top-level-function": "error",
3619
+ "curly": ["error", "all"],
3620
+ ...jsx ? { "style/jsx-curly-brace-presence": ["error", { propElementValues: "always" }] } : {},
3621
+ "style/lines-between-class-members": ["error", { enforce: [{
3622
+ blankLine: "always",
3623
+ next: "method",
3624
+ prev: "method"
3625
+ }] }],
3626
+ "style/object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }],
3627
+ "style/padding-line-between-statements": [
3628
+ "error",
3629
+ {
3630
+ blankLine: "always",
3631
+ next: "*",
3632
+ prev: [
3633
+ "block",
3634
+ "block-like",
3635
+ "class",
3636
+ "export",
3637
+ "import"
3638
+ ]
3639
+ },
3640
+ {
3641
+ blankLine: "never",
3642
+ next: "*",
3643
+ prev: ["case"]
3644
+ },
3645
+ {
3646
+ blankLine: "any",
3647
+ next: ["export", "import"],
3648
+ prev: ["export", "import"]
3649
+ },
3650
+ {
3651
+ blankLine: "any",
3652
+ next: "*",
3653
+ prev: ["do"]
3654
+ }
3655
+ ],
3656
+ "style/quotes": [
3657
+ "error",
3658
+ quotes,
3659
+ {
3660
+ allowTemplateLiterals: "never",
3661
+ avoidEscape: true
3662
+ }
3663
+ ],
3664
+ "style/spaced-comment": [
3665
+ "error",
3666
+ "always",
3667
+ { markers: ["!native", "!optimize"] }
3668
+ ]
3669
+ };
3670
+ }
3671
+ //#endregion
3672
+ //#region src/oxlint/configs/stylistic.ts
3673
+ function oxlintStylistic(options = {}) {
3674
+ return createOxlintConfigs({
3675
+ name: "isentinel/stylistic",
3676
+ files: [GLOB_SRC],
3677
+ rules: stylisticRules(options)
3678
+ });
3679
+ }
3680
+ //#endregion
3681
+ //#region src/rules/test.ts
3682
+ /**
3683
+ * Rules that make no sense in a type test (`*.test-d.ts`): these files assert
3684
+ * with `expectTypeOf`/`assertType` at compile time, run no expectations, and
3685
+ * name their blocks after the types under test.
3686
+ *
3687
+ * @param prefix - The test-plugin prefix in use (`jest` or `vitest`).
3688
+ * @returns The rule map.
3689
+ */
3690
+ function typeTestRules(prefix) {
3691
+ return {
3692
+ [`${prefix}/expect-expect`]: "off",
3693
+ [`${prefix}/prefer-expect-assertions`]: "off",
3694
+ [`${prefix}/prefer-lowercase-title`]: "off"
3695
+ };
3696
+ }
3697
+ /**
3698
+ * Jest-extended rules shared between the ESLint and oxlint factories.
3699
+ *
3700
+ * @returns The rule map.
3701
+ */
3702
+ function jestExtendedRules() {
3703
+ return {
3704
+ "jest-extended/prefer-to-be-array": "error",
3705
+ "jest-extended/prefer-to-be-false": "error",
3706
+ "jest-extended/prefer-to-be-object": "error",
3707
+ "jest-extended/prefer-to-be-true": "error",
3708
+ "jest-extended/prefer-to-have-been-called-once": "error"
3709
+ };
3710
+ }
3711
+ /**
3712
+ * Jest rules shared between the ESLint and oxlint factories.
3713
+ *
3714
+ * Both factories run `eslint-plugin-jest` as a jsPlugin: the native oxlint jest
3715
+ * plugin does not support `settings.jest.globalPackage` (oxc-project/oxc#23290)
3716
+ * and we use `@rbxts/jest-globals`. The four type-aware jest rules stay in
3717
+ * ESLint (jsPlugins have no type information).
3718
+ *
3719
+ * @param options - Shared rule options.
3720
+ * @returns The rule map.
3721
+ */
3722
+ function jestRules({ extended = false, isInEditor = false, roblox: isRoblox = true, stylistic = true } = {}) {
3723
+ return {
3724
+ "jest/consistent-test-it": "error",
3725
+ "jest/expect-expect": "warn",
3726
+ "jest/max-expects": "error",
3727
+ "jest/max-nested-describe": ["error", { max: 4 }],
3728
+ "jest/no-alias-methods": "error",
3729
+ "jest/no-commented-out-tests": "warn",
3730
+ "jest/no-conditional-expect": "error",
3731
+ "jest/no-conditional-in-test": ["error", { allowOptionalChaining: false }],
3732
+ "jest/no-disabled-tests": isInEditor ? "off" : "error",
3733
+ "jest/no-done-callback": "error",
3734
+ "jest/no-duplicate-hooks": "error",
3735
+ "jest/no-error-equal": isRoblox ? "off" : "error",
3736
+ "jest/no-export": "error",
3737
+ "jest/no-focused-tests": isInEditor ? "off" : "error",
3738
+ "jest/no-hooks": "error",
3739
+ "jest/no-identical-title": "error",
3740
+ "jest/no-standalone-expect": "error",
3741
+ "jest/no-test-prefixes": "error",
3742
+ "jest/no-test-return-statement": "error",
3743
+ "jest/no-unnecessary-assertion": "error",
3744
+ "jest/no-unneeded-async-expect-function": "error",
3745
+ "jest/no-untyped-mock-factory": "error",
3746
+ "jest/prefer-called-with": "warn",
3747
+ "jest/prefer-comparison-matcher": "warn",
3748
+ "jest/prefer-each": "warn",
3749
+ "jest/prefer-ending-with-an-expect": ["warn", { assertFunctionNames: [
3750
+ "expect",
3751
+ "expectTypeOf",
3752
+ "assertType"
3753
+ ] }],
3754
+ "jest/prefer-equality-matcher": "warn",
3755
+ "jest/prefer-expect-assertions": "error",
3756
+ "jest/prefer-hooks-in-order": "warn",
3757
+ "jest/prefer-lowercase-title": "warn",
3758
+ "jest/prefer-mock-promise-shorthand": "error",
3759
+ "jest/prefer-mock-return-shorthand": "error",
3760
+ "jest/prefer-spy-on": "warn",
3761
+ "jest/prefer-strict-equal": "error",
3762
+ "jest/prefer-to-be": "error",
3763
+ "jest/prefer-to-contain": "error",
3764
+ "jest/prefer-to-have-been-called": "error",
3765
+ "jest/prefer-to-have-been-called-times": "error",
3766
+ "jest/prefer-to-have-length": "error",
3767
+ "jest/prefer-todo": "warn",
3768
+ "jest/require-hook": "error",
3769
+ "jest/require-to-throw-message": "warn",
3770
+ "jest/require-top-level-describe": "error",
3771
+ "jest/unbound-method": "error",
3772
+ "jest/valid-describe-callback": "error",
3773
+ "jest/valid-expect": isRoblox ? "off" : "error",
3774
+ "jest/valid-expect-in-promise": "error",
3775
+ "jest/valid-expect-with-promise": "error",
3776
+ "jest/valid-title": ["error", {
3777
+ ignoreTypeOfDescribeName: true,
3778
+ mustMatch: { it: ["^should", "Test title must start with \"should\""] }
3779
+ }],
3780
+ ...extended ? jestExtendedRules() : {},
3781
+ ...stylistic !== false ? {
3782
+ "flawless/padding-after-expect-assertions": "warn",
3783
+ "jest/padding-around-all": "warn"
3784
+ } : {}
3785
+ };
3786
+ }
3787
+ /**
3788
+ * Vitest rules shared between the ESLint and oxlint factories.
3789
+ *
3790
+ * The ESLint factory runs `@vitest/eslint-plugin` (an optional peer). The
3791
+ * oxlint factory runs the family as native rules, except `padding-around-all`
3792
+ * and `prefer-vi-mocked` (no native port) which run via the plugin as a
3793
+ * jsPlugin.
3794
+ *
3795
+ * @param options - Shared rule options.
3796
+ * @returns The rule map.
3797
+ */
3798
+ function vitestRules({ extended = false, isInEditor = false, stylistic = true } = {}) {
3799
+ return {
3800
+ "vitest/consistent-each-for": ["error", {
3801
+ describe: "for",
3802
+ it: "for",
3803
+ suite: "for",
3804
+ test: "for"
3805
+ }],
3806
+ "vitest/consistent-test-filename": ["error", { pattern: ".*\\.spec\\.[tj]sx?$" }],
3807
+ "vitest/consistent-test-it": ["error", {
3808
+ fn: "it",
3809
+ withinDescribe: "it"
3810
+ }],
3811
+ "vitest/consistent-vitest-vi": "error",
3812
+ "vitest/expect-expect": "warn",
3813
+ "vitest/hoisted-apis-on-top": "error",
3814
+ "vitest/max-expects": "error",
3815
+ "vitest/max-nested-describe": ["error", { max: 4 }],
3816
+ "vitest/no-alias-methods": "error",
3817
+ "vitest/no-commented-out-tests": "warn",
3818
+ "vitest/no-conditional-expect": "error",
3819
+ "vitest/no-conditional-in-test": "error",
3820
+ "vitest/no-conditional-tests": "error",
3821
+ "vitest/no-disabled-tests": isInEditor ? "off" : "error",
3822
+ "vitest/no-duplicate-hooks": "error",
3823
+ "vitest/no-focused-tests": isInEditor ? "off" : "error",
3824
+ "vitest/no-hooks": "error",
3825
+ "vitest/no-identical-title": "error",
3826
+ "vitest/no-import-node-test": "error",
3827
+ "vitest/no-interpolation-in-snapshots": "error",
3828
+ "vitest/no-large-snapshots": "warn",
3829
+ "vitest/no-mocks-import": "error",
3830
+ "vitest/no-standalone-expect": "error",
3831
+ "vitest/no-test-prefixes": "error",
3832
+ "vitest/no-test-return-statement": "error",
3833
+ "vitest/no-unneeded-async-expect-function": "error",
3834
+ "vitest/prefer-called-exactly-once-with": "warn",
3835
+ "vitest/prefer-called-once": "error",
3836
+ "vitest/prefer-called-with": "error",
3837
+ "vitest/prefer-comparison-matcher": "warn",
3838
+ "vitest/prefer-describe-function-title": "warn",
3839
+ "vitest/prefer-each": "warn",
3840
+ "vitest/prefer-equality-matcher": "warn",
3841
+ "vitest/prefer-expect-assertions": "warn",
3842
+ "vitest/prefer-expect-resolves": "error",
3843
+ "vitest/prefer-expect-type-of": "error",
3844
+ "vitest/prefer-hooks-in-order": "error",
3845
+ "vitest/prefer-hooks-on-top": "error",
3846
+ "vitest/prefer-import-in-mock": "error",
3847
+ "vitest/prefer-importing-vitest-globals": "error",
3848
+ "vitest/prefer-lowercase-title": "error",
3849
+ "vitest/prefer-mock-promise-shorthand": "error",
3850
+ "vitest/prefer-mock-return-shorthand": "error",
3851
+ "vitest/prefer-snapshot-hint": "warn",
3852
+ "vitest/prefer-spy-on": "warn",
3853
+ "vitest/prefer-strict-boolean-matchers": "error",
3854
+ "vitest/prefer-strict-equal": "error",
3855
+ "vitest/prefer-to-be": "error",
3856
+ "vitest/prefer-to-be-object": "error",
3857
+ "vitest/prefer-to-contain": "error",
3858
+ "vitest/prefer-to-have-been-called-times": "error",
3859
+ "vitest/prefer-to-have-length": "error",
3860
+ "vitest/prefer-todo": "warn",
3861
+ "vitest/prefer-vi-mocked": "error",
3862
+ "vitest/require-awaited-expect-poll": "error",
3863
+ "vitest/require-local-test-context-for-concurrent-snapshots": "error",
3864
+ "vitest/require-mock-type-parameters": "error",
3865
+ "vitest/require-to-throw-message": "warn",
3866
+ "vitest/require-top-level-describe": "error",
3867
+ "vitest/valid-describe-callback": "error",
3868
+ "vitest/valid-expect": "error",
3869
+ "vitest/valid-expect-in-promise": "error",
3870
+ "vitest/valid-title": ["error", {
3871
+ ignoreTypeOfDescribeName: true,
3872
+ mustMatch: { it: ["^should", "Test title must start with \"should\""] }
3873
+ }],
3874
+ ...extended ? jestExtendedRules() : {},
3875
+ ...stylistic !== false ? {
3876
+ "flawless/padding-after-expect-assertions": "warn",
3877
+ "vitest/padding-around-all": "warn"
3878
+ } : {}
3879
+ };
3880
+ }
3881
+ //#endregion
3882
+ //#region src/oxlint/configs/test.ts
3883
+ /**
3884
+ * Test rules for standalone oxlint.
3885
+ *
3886
+ * Jest runs through `eslint-plugin-jest` as a jsPlugin: the native oxlint
3887
+ * jest plugin does not support `settings.jest.globalPackage`
3888
+ * (https://github.com/oxc-project/oxc/issues/23290). Vitest runs as native
3889
+ * oxlint rules, except `padding-around-all` and `prefer-vi-mocked` (no
3890
+ * native port) which run via `@vitest/eslint-plugin` as a jsPlugin. The
3891
+ * four type-aware jest rules are filtered out because oxlint has no type
3892
+ * information for them.
3893
+ *
3894
+ * @param options - The test rule options.
3895
+ * @returns The generated config fragments.
3896
+ */
3897
+ function oxlintTest({ files = GLOB_TESTS, isInEditor = false, jest = false, overrides = {}, roblox: isRoblox = true, stylistic = true, type = "game", vitest = false } = {}) {
3898
+ const vitestOptions = typeof vitest === "object" ? vitest : {};
3899
+ const vitestEnabled = vitest === true || typeof vitest === "object";
3900
+ const jestOptions = typeof jest === "object" ? jest : {};
3901
+ const jestEnabled = jest === true || typeof jest === "object";
3902
+ const enableJest = jestEnabled || !vitestEnabled && (type === "game" || isRoblox);
3903
+ const enableVitest = vitestEnabled || !jestEnabled && type === "package" && !isRoblox;
3904
+ const configs = [];
3905
+ if (enableJest) configs.push(...createOxlintConfigs({
3906
+ name: "isentinel/test/jest",
3907
+ files: jestOptions.files?.flat() ?? files.flat(),
3908
+ rules: {
3909
+ ...jestRules({
3910
+ extended: jestOptions.extended === true,
3911
+ isInEditor,
3912
+ roblox: isRoblox,
3913
+ stylistic
3914
+ }),
3915
+ ...sonarjsTestRules({
3916
+ jest: true,
3917
+ roblox: isRoblox
3918
+ }),
3919
+ ...overrides,
3920
+ ...jestOptions.overrides
3921
+ },
3922
+ settings: { jest: {
3923
+ globalPackage: "@rbxts/jest-globals",
3924
+ version: 27
3925
+ } }
3926
+ }), ...createOxlintConfigs({
3927
+ name: "isentinel/test/jest/type-tests",
3928
+ files: [GLOB_TYPE_TESTS],
3929
+ keepUnmappedOff: true,
3930
+ rules: typeTestRules("jest")
3931
+ }));
3932
+ if (enableVitest) configs.push(...createOxlintConfigs({
3933
+ name: "isentinel/test/vitest",
3934
+ files: vitestOptions.files?.flat() ?? files.flat(),
3935
+ rules: {
3936
+ ...vitestRules({
3937
+ extended: vitestOptions.extended === true,
3938
+ isInEditor,
3939
+ stylistic
3940
+ }),
3941
+ ...sonarjsTestRules({ roblox: isRoblox }),
3942
+ ...overrides,
3943
+ ...vitestOptions.overrides
3944
+ },
3945
+ settings: { vitest: { typecheck: vitestOptions.typecheck ?? false } }
3946
+ }), ...createOxlintConfigs({
3947
+ name: "isentinel/test/vitest/type-tests",
3948
+ files: [GLOB_TYPE_TESTS],
3949
+ keepUnmappedOff: true,
3950
+ rules: typeTestRules("vitest")
3951
+ }));
3952
+ return configs;
3953
+ }
3954
+ //#endregion
3955
+ //#region src/rules/typescript.ts
3956
+ /**
3957
+ * TypeScript rules shared between the ESLint and oxlint factories.
3958
+ *
3959
+ * These do not require type information. Rule names use the canonical
3960
+ * (renamed) ESLint prefixes; the oxlint factory translates them via the
3961
+ * oxlint rule mapping.
3962
+ *
3963
+ * @param options - Shared rule options.
3964
+ * @returns The rule map.
3965
+ */
3966
+ function typescriptRules({ stylistic = true } = {}) {
3967
+ return {
3968
+ "no-dupe-class-members": "off",
3969
+ "no-empty-function": "off",
3970
+ "no-loss-of-precision": "off",
3971
+ "no-redeclare": "off",
3972
+ "no-restricted-syntax": ["error", "[declare=true]"],
3973
+ "no-shadow": "off",
3974
+ "no-throw-literal": "off",
3975
+ "no-unused-expressions": "off",
3976
+ "no-unused-private-class-members": "off",
3977
+ "no-use-before-define": "off",
3978
+ "no-useless-constructor": "off",
3979
+ "prefer-destructuring": "off",
3980
+ "ts/adjacent-overload-signatures": "off",
3981
+ "ts/ban-ts-comment": ["error", { "ts-ignore": "allow-with-description" }],
3982
+ "ts/default-param-last": "error",
3983
+ "ts/explicit-function-return-type": ["error", { allowExpressions: true }],
3984
+ "ts/explicit-member-accessibility": ["error", { overrides: { constructors: "no-public" } }],
3985
+ "ts/method-signature-style": "off",
3986
+ "ts/no-array-constructor": "off",
3987
+ "ts/no-confusing-non-null-assertion": "error",
3988
+ "ts/no-dupe-class-members": "off",
3989
+ "ts/no-dynamic-delete": "off",
3990
+ "ts/no-empty-function": "error",
3991
+ "ts/no-empty-object-type": ["error", { allowInterfaces: "always" }],
3992
+ "ts/no-explicit-any": "off",
3993
+ "ts/no-extraneous-class": "error",
3994
+ "ts/no-for-in-array": "off",
3995
+ "ts/no-import-type-side-effects": "error",
3996
+ "ts/no-inferrable-types": "error",
3997
+ "ts/no-invalid-void-type": "off",
3998
+ "ts/no-namespace": "off",
3999
+ "ts/no-non-null-assertion": "error",
4000
+ "ts/no-redeclare": "off",
4001
+ "ts/no-require-imports": "error",
4002
+ "ts/no-shadow": "error",
4003
+ "ts/no-unused-expressions": "error",
4004
+ "ts/no-unused-private-class-members": "error",
4005
+ "ts/no-unused-vars": "off",
4006
+ "ts/no-use-before-define": "off",
4007
+ "ts/no-useless-constructor": "error",
4008
+ "ts/no-wrapper-object-types": "error",
4009
+ "ts/prefer-for-of": "error",
4010
+ "ts/prefer-function-type": "error",
4011
+ "ts/prefer-literal-enum-member": ["error", { allowBitwiseExpressions: true }],
4012
+ "ts/triple-slash-reference": "off",
4013
+ "ts/unified-signatures": "off",
4014
+ ...stylistic !== false ? {
4015
+ "ts/array-type": ["error", {
4016
+ default: "generic",
4017
+ readonly: "generic"
4018
+ }],
4019
+ "ts/consistent-generic-constructors": ["error", "constructor"],
4020
+ "ts/consistent-indexed-object-style": ["error", "record"],
4021
+ "ts/consistent-type-definitions": ["error", "interface"],
4022
+ "ts/consistent-type-imports": ["error", {
4023
+ disallowTypeAnnotations: false,
4024
+ prefer: "type-imports"
4025
+ }]
4026
+ } : {}
4027
+ };
4028
+ }
4029
+ /**
4030
+ * Type-aware TypeScript rules shared between the ESLint and oxlint factories.
4031
+ *
4032
+ * In hybrid mode these are executed by oxlint-tsgolint (`oxlint
4033
+ * --type-aware`).
4034
+ *
4035
+ * @param options - Shared rule options.
4036
+ * @returns The rule map.
4037
+ */
4038
+ function typescriptTypeAwareRules({ roblox = true } = {}) {
4039
+ return {
4040
+ "dot-notation": "off",
4041
+ "no-implied-eval": "off",
4042
+ "no-unsafe-optional-chaining": "error",
4043
+ "prefer-promise-reject-errors": "off",
4044
+ "require-await": "off",
4045
+ "ts/await-thenable": "error",
4046
+ "ts/consistent-type-assertions": ["error", {
4047
+ assertionStyle: "as",
4048
+ objectLiteralTypeAssertions: "allow"
4049
+ }],
4050
+ "ts/consistent-type-exports": "error",
4051
+ "ts/dot-notation": ["error", { allowKeywords: true }],
4052
+ "ts/no-array-delete": "error",
4053
+ "ts/no-base-to-string": "error",
4054
+ "ts/no-confusing-void-expression": "error",
4055
+ "ts/no-deprecated": "error",
4056
+ "ts/no-duplicate-type-constituents": "error",
4057
+ "ts/no-empty-object-type": "error",
4058
+ "ts/no-floating-promises": ["error", {
4059
+ checkThenables: roblox,
4060
+ ignoreVoid: true
4061
+ }],
4062
+ "ts/no-for-in-array": "error",
4063
+ "ts/no-implied-eval": "error",
4064
+ "ts/no-meaningless-void-operator": "error",
4065
+ "ts/no-misused-promises": "error",
4066
+ "ts/no-misused-spread": "error",
4067
+ "ts/no-mixed-enums": "error",
4068
+ "ts/no-redundant-type-constituents": "error",
4069
+ "ts/no-unnecessary-boolean-literal-compare": "error",
4070
+ "ts/no-unnecessary-condition": ["error", { allowConstantLoopConditions: true }],
4071
+ "ts/no-unnecessary-parameter-property-assignment": "error",
4072
+ "ts/no-unnecessary-qualifier": "error",
4073
+ "ts/no-unnecessary-template-expression": "error",
4074
+ "ts/no-unnecessary-type-arguments": "error",
4075
+ "ts/no-unnecessary-type-assertion": "error",
4076
+ "ts/no-unnecessary-type-constraint": "error",
4077
+ "ts/no-unnecessary-type-conversion": "error",
4078
+ "ts/no-unnecessary-type-parameters": "error",
4079
+ "ts/no-unsafe-argument": "error",
4080
+ "ts/no-unsafe-assignment": "error",
4081
+ "ts/no-unsafe-call": "error",
4082
+ "ts/no-unsafe-enum-comparison": "error",
4083
+ "ts/no-unsafe-member-access": "error",
4084
+ "ts/no-unsafe-return": "error",
4085
+ "ts/no-unsafe-type-assertion": "error",
4086
+ "ts/no-unsafe-unary-minus": "error",
4087
+ "ts/no-useless-default-assignment": "error",
4088
+ "ts/non-nullable-type-assertion-style": "error",
4089
+ "ts/only-throw-error": ["error", { allow: [{
4090
+ name: "Error",
4091
+ from: "package",
4092
+ package: "@rbxts/luau-polyfill"
4093
+ }] }],
4094
+ "ts/prefer-destructuring": ["error", {
4095
+ array: false,
4096
+ object: true
4097
+ }],
4098
+ "ts/prefer-find": "error",
4099
+ "ts/prefer-includes": "error",
4100
+ "ts/prefer-nullish-coalescing": "error",
4101
+ "ts/prefer-optional-chain": "error",
4102
+ "ts/prefer-promise-reject-errors": "error",
4103
+ "ts/prefer-readonly": "error",
4104
+ "ts/prefer-reduce-type-parameter": "error",
4105
+ "ts/prefer-return-this-type": "error",
4106
+ "ts/promise-function-async": "error",
4107
+ "ts/require-await": "error",
4108
+ "ts/restrict-plus-operands": "error",
4109
+ "ts/restrict-template-expressions": ["error", {
4110
+ allowBoolean: true,
4111
+ allowNumber: true
4112
+ }],
4113
+ "ts/return-await": "error",
4114
+ "ts/strict-boolean-expressions": "error",
4115
+ "ts/strict-void-return": "error",
4116
+ "ts/switch-exhaustiveness-check": "error",
4117
+ "ts/unbound-method": "error",
4118
+ "ts/use-unknown-in-catch-callback-variable": "error",
4119
+ ...roblox ? {} : {
4120
+ "ts/related-getter-setter-pairs": "error",
4121
+ "ts/require-array-sort-compare": "error"
4122
+ }
4123
+ };
4124
+ }
4125
+ /**
4126
+ * Static port of the `@typescript-eslint` `eslint-recommended` overrides:
4127
+ * core rules that are redundant (or wrong) on TypeScript files because the
4128
+ * compiler already checks them — most importantly `no-undef`, which would
4129
+ * otherwise flag every ambient global (Roblox/Lua globals, roblox-ts macros).
4130
+ *
4131
+ * The ESLint factory spreads the preset dynamically from the plugin; the
4132
+ * oxlint factory applies this static copy to its TypeScript files.
4133
+ *
4134
+ * @returns The rule map.
4135
+ */
4136
+ function typescriptRecommendedOverrides() {
4137
+ return {
4138
+ "constructor-super": "off",
4139
+ "getter-return": "off",
4140
+ "no-array-constructor": "off",
4141
+ "no-class-assign": "off",
4142
+ "no-const-assign": "off",
4143
+ "no-dupe-args": "off",
4144
+ "no-dupe-class-members": "off",
4145
+ "no-dupe-keys": "off",
4146
+ "no-func-assign": "off",
4147
+ "no-import-assign": "off",
4148
+ "no-new-native-nonconstructor": "off",
4149
+ "no-obj-calls": "off",
4150
+ "no-redeclare": "off",
4151
+ "no-setter-return": "off",
4152
+ "no-this-before-super": "off",
4153
+ "no-undef": "off",
4154
+ "no-unreachable": "off",
4155
+ "no-unsafe-negation": "off",
4156
+ "no-var": "error",
4157
+ "no-with": "off",
4158
+ "prefer-const": "error",
4159
+ "prefer-rest-params": "error",
4160
+ "prefer-spread": "error"
4161
+ };
4162
+ }
4163
+ /**
4164
+ * Rules enabled by the `@typescript-eslint` `strict` preset that are not
4165
+ * already configured explicitly in {@link typescriptRules}.
4166
+ *
4167
+ * The ESLint factory spreads the preset dynamically from the plugin; the
4168
+ * oxlint factory enables these explicitly so hybrid mode keeps full coverage.
4169
+ *
4170
+ * @returns The rule map.
4171
+ */
4172
+ function typescriptStrictPresetRules() {
4173
+ return {
4174
+ "ts/no-duplicate-enum-values": "error",
4175
+ "ts/no-extra-non-null-assertion": "error",
4176
+ "ts/no-misused-new": "error",
4177
+ "ts/no-non-null-asserted-nullish-coalescing": "error",
4178
+ "ts/no-non-null-asserted-optional-chain": "error",
4179
+ "ts/no-this-alias": "error",
4180
+ "ts/no-unsafe-declaration-merging": "error",
4181
+ "ts/no-unsafe-function-type": "error",
4182
+ "ts/prefer-as-const": "error",
4183
+ "ts/prefer-namespace-keyword": "error"
4184
+ };
4185
+ }
4186
+ /**
4187
+ * Erasable-syntax-only rules shared between the ESLint and oxlint factories.
4188
+ *
4189
+ * @returns The rule map.
4190
+ */
4191
+ function erasableSyntaxOnlyRules() {
4192
+ return {
4193
+ "erasable-syntax-only/enums": "error",
4194
+ "erasable-syntax-only/import-aliases": "error",
4195
+ "erasable-syntax-only/namespaces": "error",
4196
+ "erasable-syntax-only/parameter-properties": "error"
4197
+ };
4198
+ }
4199
+ //#endregion
4200
+ //#region src/oxlint/configs/typescript.ts
4201
+ function oxlintTypescript(options = {}) {
4202
+ const { componentExts: componentExtensions = [], erasableOnly = false, excludeFiles, overrides = {}, roblox = true, stylistic = true, typeAware = true } = options;
4203
+ const files = options.files?.flat() ?? [
4204
+ "**/*.{,c,m}ts",
4205
+ "**/*.{,c,m}tsx",
4206
+ ...componentExtensions.map((extension) => `**/*.${extension}`)
4207
+ ];
4208
+ const typeAwareRules = typescriptTypeAwareRules({ roblox }) ?? {};
4209
+ const gatedTypeAwareRules = typeAware ? typeAwareRules : Object.fromEntries(Object.entries(typeAwareRules).filter(([rule]) => !isTsgolintRule(rule)));
4210
+ return createOxlintConfigs({
4211
+ name: "isentinel/typescript",
4212
+ ...excludeFiles ? { excludeFiles } : {},
4213
+ files,
4214
+ rules: {
4215
+ ...typescriptRecommendedOverrides(),
4216
+ ...typescriptStrictPresetRules(),
4217
+ ...typescriptRules({ stylistic }),
4218
+ ...gatedTypeAwareRules,
4219
+ ...erasableOnly ? erasableSyntaxOnlyRules() : {},
4220
+ ...overrides
4221
+ }
4222
+ });
4223
+ }
4224
+ //#endregion
4225
+ //#region src/rules/unicorn.ts
4226
+ const abbreviations = {
4227
+ args: false,
4228
+ ctx: false,
4229
+ dist: { distance: true },
4230
+ e: false,
4231
+ err: false,
4232
+ fn: {
4233
+ func: true,
4234
+ function: false
4235
+ },
4236
+ func: false,
4237
+ inst: { instance: true },
4238
+ jsdoc: false,
4239
+ nums: { numbers: true },
4240
+ pos: { position: true },
4241
+ props: false,
4242
+ ref: false,
4243
+ refs: false,
4244
+ str: false,
4245
+ util: false,
4246
+ utils: false
4247
+ };
4248
+ /**
4249
+ * Resolve the `unicorn/name-replacements` replacement map.
4250
+ *
4251
+ * @param options - Shared rule options.
4252
+ * @returns The merged replacements map.
4253
+ */
4254
+ function unicornNameReplacements({ nameReplacements, roblox = true } = {}) {
4255
+ return {
4256
+ ...abbreviations,
4257
+ ...roblox ? { buf: false } : {},
4258
+ ...nameReplacements
4259
+ };
4260
+ }
4261
+ /**
4262
+ * Unicorn rules shared between the ESLint and oxlint factories.
4263
+ *
4264
+ * @param options - Shared rule options.
4265
+ * @returns The rule map.
4266
+ */
4267
+ function unicornRules(options = {}) {
4268
+ const { roblox = true, stylistic = true } = options;
4269
+ return {
4270
+ "unicorn/catch-error-name": ["error", { name: "err" }],
4271
+ "unicorn/consistent-conditional-object-spread": ["error", "ternary"],
4272
+ "unicorn/consistent-destructuring": "error",
4273
+ "unicorn/consistent-function-scoping": ["error", { checkArrowFunctions: false }],
4274
+ "unicorn/consistent-json-file-read": "error",
4275
+ "unicorn/consistent-template-literal-escape": "error",
4276
+ "unicorn/consistent-tuple-labels": "error",
4277
+ "unicorn/error-message": "off",
4278
+ "unicorn/filename-case": ["error", {
4279
+ case: "kebabCase",
4280
+ ignore: ["^[A-Z0-9]+.md$"],
4281
+ multipleFileExtensions: true
4282
+ }],
4283
+ "unicorn/isolated-functions": "error",
4284
+ "unicorn/name-replacements": ["error", {
4285
+ checkFilenames: true,
4286
+ replacements: unicornNameReplacements(options)
4287
+ }],
4288
+ "unicorn/no-array-sort-for-min-max": "error",
4289
+ "unicorn/no-async-promise-finally": "error",
4290
+ "unicorn/no-await-expression-member": "error",
4291
+ "unicorn/no-break-in-nested-loop": "error",
4292
+ "unicorn/no-constant-zero-expression": "error",
4293
+ "unicorn/no-declarations-before-early-exit": "error",
4294
+ "unicorn/no-double-comparison": "error",
4295
+ "unicorn/no-duplicate-loops": "error",
4296
+ "unicorn/no-duplicate-set-values": "error",
4297
+ "unicorn/no-empty-file": "error",
4298
+ "unicorn/no-exports-in-scripts": "error",
4299
+ "unicorn/no-for-each": "error",
4300
+ "unicorn/no-for-loop": "error",
4301
+ "unicorn/no-immediate-mutation": "error",
4302
+ "unicorn/no-incorrect-template-string-interpolation": "error",
4303
+ "unicorn/no-invalid-well-known-symbol-methods": "error",
4304
+ "unicorn/no-keyword-prefix": "error",
4305
+ "unicorn/no-lonely-if": "error",
4306
+ "unicorn/no-loop-iterable-mutation": "error",
4307
+ "unicorn/no-mismatched-map-key": "error",
4308
+ "unicorn/no-misrefactored-assignment": "error",
4309
+ "unicorn/no-negated-array-predicate": "error",
4310
+ "unicorn/no-negated-condition": "off",
4311
+ "unicorn/no-negation-in-equality-check": "error",
4312
+ "unicorn/no-non-function-verb-prefix": "error",
4313
+ "unicorn/no-object-as-default-parameter": "error",
4314
+ "unicorn/no-optional-chaining-on-undeclared-variable": "error",
4315
+ "unicorn/no-redundant-comparison": "error",
4316
+ "unicorn/no-return-array-push": "error",
4317
+ "unicorn/no-single-promise-in-promise-methods": "error",
4318
+ "unicorn/no-static-only-class": "error",
4319
+ "unicorn/no-subtraction-comparison": "error",
4320
+ "unicorn/no-unreadable-array-destructuring": "error",
4321
+ "unicorn/no-unreadable-for-of-expression": "error",
4322
+ "unicorn/no-unreadable-new-expression": "error",
4323
+ "unicorn/no-unsafe-property-key": "error",
4324
+ "unicorn/no-unused-properties": "error",
4325
+ "unicorn/no-useless-collection-argument": "error",
4326
+ "unicorn/no-useless-compound-assignment": "error",
4327
+ "unicorn/no-useless-concat": "error",
4328
+ "unicorn/no-useless-delete-check": "error",
4329
+ "unicorn/no-useless-logical-operand": "error",
4330
+ "unicorn/no-useless-promise-resolve-reject": "error",
4331
+ "unicorn/no-useless-re-export": "error",
4332
+ "unicorn/no-useless-recursion": "error",
4333
+ "unicorn/no-useless-spread": "off",
4334
+ "unicorn/no-useless-undefined": ["error", { checkArguments: false }],
4335
+ "unicorn/number-literal-case": "error",
4336
+ "unicorn/operator-assignment": "error",
4337
+ "unicorn/prefer-continue": "error",
4338
+ "unicorn/prefer-default-parameters": "error",
4339
+ "unicorn/prefer-direct-iteration": "error",
4340
+ "unicorn/prefer-else-if": "error",
4341
+ "unicorn/prefer-export-from": "error",
4342
+ "unicorn/prefer-has-check": "error",
4343
+ "unicorn/prefer-hoisting-branch-code": "error",
4344
+ "unicorn/prefer-identifier-import-export-specifiers": "error",
4345
+ "unicorn/prefer-includes": "error",
4346
+ "unicorn/prefer-logical-operator-over-ternary": "error",
4347
+ "unicorn/prefer-math-min-max": "off",
4348
+ "unicorn/prefer-optional-catch-binding": "error",
4349
+ "unicorn/prefer-set-has": "error",
4350
+ "unicorn/prefer-simple-condition-first": "error",
4351
+ "unicorn/prefer-simplified-conditions": "error",
4352
+ "unicorn/prefer-single-call": "error",
4353
+ "unicorn/prefer-switch": "error",
4354
+ "unicorn/prefer-ternary": ["error", "only-single-line"],
4355
+ "unicorn/prefer-then-catch": "error",
4356
+ "unicorn/prefer-unary-minus": "error",
4357
+ ...stylistic !== false ? {
4358
+ "unicorn/consistent-compound-words": "error",
4359
+ "unicorn/prefer-block-statement-over-iife": "error",
4360
+ "unicorn/switch-case-braces": "error"
4361
+ } : {},
4362
+ ...!roblox ? {
4363
+ "unicorn/no-accidental-bitwise-operator": "error",
4364
+ "unicorn/no-array-concat-in-loop": "error",
4365
+ "unicorn/no-array-fill-with-reference-type": "error",
4366
+ "unicorn/no-boolean-sort-comparator": "error",
4367
+ "unicorn/no-confusing-array-splice": "error",
4368
+ "unicorn/no-error-property-assignment": "error",
4369
+ "unicorn/no-global-object-property-assignment": "error",
4370
+ "unicorn/no-impossible-length-comparison": "error",
4371
+ "unicorn/no-invalid-character-comparison": "error",
4372
+ "unicorn/no-object-methods-with-collections": "error",
4373
+ "unicorn/no-unnecessary-fetch-options": "error",
4374
+ "unicorn/no-unnecessary-global-this": "error",
4375
+ "unicorn/no-unnecessary-string-trim": "error",
4376
+ "unicorn/no-unsafe-string-replacement": "error",
4377
+ "unicorn/no-useless-coercion": "error",
4378
+ "unicorn/no-useless-iterator-to-array": "error",
4379
+ "unicorn/no-xor-as-exponentiation": "error",
4380
+ "unicorn/prefer-abort-signal-any": "error",
4381
+ "unicorn/prefer-array-flat-map": "error",
4382
+ "unicorn/prefer-array-from-map": "error",
4383
+ "unicorn/prefer-array-from-range": "error",
4384
+ "unicorn/prefer-array-iterable-methods": "error",
4385
+ "unicorn/prefer-array-slice": "error",
4386
+ "unicorn/prefer-flat-math-min-max": "error",
4387
+ "unicorn/prefer-global-number-constants": "error",
4388
+ "unicorn/prefer-group-by": "error",
4389
+ "unicorn/prefer-iterator-helpers": "error",
4390
+ "unicorn/prefer-math-constants": "error",
4391
+ "unicorn/prefer-split-limit": "error",
4392
+ "unicorn/prefer-string-match-all": "error",
4393
+ "unicorn/prefer-string-pad-start-end": "error",
4394
+ "unicorn/prefer-string-repeat": "error",
4395
+ "unicorn/require-passive-events": "error",
4396
+ "unicorn/throw-new-error": "error"
4397
+ } : {}
4398
+ };
4399
+ }
4400
+ /**
4401
+ * Root-level unicorn rules shared between the ESLint and oxlint factories.
4402
+ *
4403
+ * @param options - Shared rule options.
4404
+ * @returns The rule map.
4405
+ */
4406
+ function unicornRootRules(options = {}) {
4407
+ return { "unicorn/name-replacements": ["error", {
4408
+ checkFilenames: false,
4409
+ replacements: unicornNameReplacements(options)
4410
+ }] };
4411
+ }
4412
+ //#endregion
4413
+ //#region src/oxlint/configs/unicorn.ts
4414
+ function oxlintUnicorn({ excludeFiles, nameReplacements, roblox = true, root: customRootGlobs, stylistic = true } = {}) {
4415
+ const rootGlobs = mergeGlobs(GLOB_ROOT.map(toSourceGlob), customRootGlobs?.map(toSourceGlob));
4416
+ return [...createOxlintConfigs({
4417
+ name: "isentinel/unicorn",
4418
+ ...excludeFiles ? { excludeFiles } : {},
4419
+ files: [GLOB_SRC],
4420
+ rules: unicornRules({
4421
+ nameReplacements,
4422
+ roblox,
4423
+ stylistic
4424
+ })
4425
+ }), ...createOxlintConfigs({
4426
+ name: "isentinel/unicorn/root",
4427
+ ...excludeFiles ? { excludeFiles } : {},
4428
+ files: rootGlobs,
4429
+ rules: unicornRootRules({
4430
+ nameReplacements,
4431
+ roblox
4432
+ })
4433
+ })];
4434
+ }
4435
+ //#endregion
4436
+ //#region src/oxlint/factory.ts
4437
+ /**
4438
+ * The preset enables its rules explicitly, so every category is disabled to
4439
+ * stop oxlint's own defaults (notably `correctness: "warn"`) from firing on top
4440
+ * of the curated set.
4441
+ */
4442
+ const DEFAULT_CATEGORIES = {
4443
+ correctness: "off",
4444
+ nursery: "off",
4445
+ pedantic: "off",
4446
+ perf: "off",
4447
+ restriction: "off",
4448
+ style: "off",
4449
+ suspicious: "off"
4450
+ };
4451
+ /**
4452
+ * JsPlugins the preset keeps even under `jsPlugins: false`.
4453
+ *
4454
+ * `oxlint-comments` lints the `oxlint-disable` directives that native rules
4455
+ * still need, and the ESLint side cannot take it over: its rules use oxlint's
4456
+ * `createOnce` API, which ESLint rejects. Its rules visit `Program` once per
4457
+ * file, so the cost is negligible next to the jsPlugin rule set that native-only
4458
+ * mode exists to avoid.
4459
+ */
4460
+ const NATIVE_ONLY_JS_PLUGINS = /* @__PURE__ */ new Set(["oxlint-comments"]);
4461
+ /**
4462
+ * Implementation signature; the public overload above carries the
4463
+ * redundant-override validation generics.
4464
+ *
4465
+ * @param factoryOptions - The options for generating the oxlint configuration.
4466
+ * @param userConfigs - Additional oxlint config fragments (merged as
4467
+ * overrides).
4468
+ * @returns The oxlint configuration.
4469
+ */
4470
+ function isentinel(factoryOptions, ...userConfigs) {
4471
+ const options = factoryOptions ?? { name: "isentinel" };
4472
+ const { categories, componentExts: componentExtensions = [], e18e: enableE18e = true, env, eslintPlugin: enableEslintPlugin = false, formatters, gitignore: enableGitignore = true, globals, ignores, jsdoc: enableJsdoc = true, jsPlugins: userJsPlugins, jsx: enableJsx = true, options: linterOptions, oxc: enableOxc = true, react: enableReact = false, root: customRootGlobs, rules = {}, spellCheck: enableSpellCheck, test: enableTest = false } = options;
4473
+ const rootGlobs = mergeGlobs(GLOB_ROOT, customRootGlobs);
4474
+ const enableRoblox = options.roblox !== false;
4475
+ const nativeOnly = userJsPlugins === false;
4476
+ const robloxScopedFiles = typeof options.roblox === "object" && options.roblox.files !== void 0 ? options.roblox.files.flat() : void 0;
4477
+ const hasComplement = !enableRoblox || robloxScopedFiles !== void 0;
4478
+ const needsComplementOverlay = enableRoblox && robloxScopedFiles !== void 0;
4479
+ const typeAware = linterOptions?.typeAware ?? isPackageExists("oxlint-tsgolint");
4480
+ const inAgentSession = options.isAgent ?? isInAgentSession();
4481
+ let { defaultSeverity, isInEditor } = options;
4482
+ if (defaultSeverity === void 0 && inAgentSession) defaultSeverity = "error";
4483
+ isInEditor ??= isInEditorEnvironment();
4484
+ const projectType = (() => {
4485
+ if (options.type === "app") return "game";
4486
+ if (options.type !== void 0) return options.type;
4487
+ return enableRoblox ? "game" : "package";
4488
+ })();
4489
+ const stylisticOptions = (() => {
4490
+ if (options.stylistic === false) return false;
4491
+ if (typeof options.stylistic === "object") return options.stylistic;
4492
+ return {};
4493
+ })();
4494
+ if (stylisticOptions !== false && !("jsx" in stylisticOptions)) stylisticOptions.jsx = enableJsx;
4495
+ const formatterOptions = typeof formatters === "object" ? formatters : {};
4496
+ const prettierSettings = resolvePrettierSettings(formatterOptions.prettierOptions);
4497
+ const configs = [
4498
+ oxlintJavascript({
4499
+ ...getOverrides(options, "javascript"),
4500
+ isInEditor,
4501
+ roblox: enableRoblox,
4502
+ stylistic: stylisticOptions
4503
+ }),
4504
+ oxlintImports({
4505
+ roblox: enableRoblox,
4506
+ stylistic: stylisticOptions
4507
+ }),
4508
+ oxlintPromise(),
4509
+ oxlintSonarjs({
4510
+ isInEditor,
4511
+ roblox: enableRoblox
4512
+ }),
4513
+ oxlintTypescript({
4514
+ ...resolveSubOptions(options, "typescript"),
4515
+ ...getOverrides(options, "typescript"),
4516
+ componentExts: componentExtensions,
4517
+ roblox: enableRoblox,
4518
+ stylistic: stylisticOptions,
4519
+ typeAware
4520
+ }),
4521
+ oxlintUnicorn({
4522
+ ...resolveSubOptions(options, "unicorn"),
4523
+ roblox: enableRoblox,
4524
+ root: customRootGlobs,
4525
+ stylistic: stylisticOptions
4526
+ })
4527
+ ];
4528
+ if (enableJsdoc !== false) configs.push(oxlintJsdoc({
4529
+ ...resolveSubOptions(options, "jsdoc"),
4530
+ stylistic: stylisticOptions,
4531
+ type: projectType
4532
+ }));
4533
+ if (enableRoblox) configs.push(oxlintRoblox({
4534
+ ...getOverrides(options, "roblox"),
4535
+ componentExts: componentExtensions,
4536
+ stylistic: stylisticOptions
4537
+ }));
4538
+ if (needsComplementOverlay) configs.push(oxlintTypescript({
4539
+ ...resolveSubOptions(options, "typescript"),
4540
+ ...getOverrides(options, "typescript"),
4541
+ componentExts: componentExtensions,
4542
+ excludeFiles: robloxScopedFiles,
4543
+ roblox: false,
4544
+ stylistic: stylisticOptions,
4545
+ typeAware
4546
+ }), oxlintJavascript({
4547
+ ...getOverrides(options, "javascript"),
4548
+ excludeFiles: robloxScopedFiles,
4549
+ isInEditor,
4550
+ roblox: false,
4551
+ stylistic: stylisticOptions
4552
+ }), oxlintUnicorn({
4553
+ ...resolveSubOptions(options, "unicorn"),
4554
+ excludeFiles: robloxScopedFiles,
4555
+ roblox: false,
4556
+ stylistic: stylisticOptions
4557
+ }), oxlintImports({
4558
+ excludeFiles: robloxScopedFiles,
4559
+ roblox: false,
4560
+ stylistic: stylisticOptions
4561
+ }));
4562
+ if (enableE18e !== false && hasComplement) configs.push(oxlintE18e({
4563
+ excludeFiles: robloxScopedFiles,
4564
+ isInEditor,
4565
+ nodeMajor: resolveNodeMajor(options.settings),
4566
+ type: projectType,
4567
+ ...enableE18e === true ? {} : enableE18e
4568
+ }));
4569
+ if (hasComplement) configs.push(oxlintNode({ excludeFiles: robloxScopedFiles }));
4570
+ if (enableTest !== false) {
4571
+ const testOptions = typeof enableTest === "object" ? enableTest : {};
4572
+ configs.push(oxlintTest({
4573
+ ...getOverrides(options, "test"),
4574
+ isInEditor,
4575
+ roblox: enableRoblox,
4576
+ stylistic: stylisticOptions,
4577
+ type: projectType,
4578
+ ...testOptions
4579
+ }));
4580
+ }
4581
+ if (stylisticOptions !== false) configs.push(oxlintPerfectionist({
4582
+ ...resolveSubOptions(options, "perfectionist"),
4583
+ type: projectType
4584
+ }), oxlintStylistic(stylisticOptions));
4585
+ if (enableReact !== false) {
4586
+ const reactOptions = typeof enableReact === "object" ? enableReact : {};
4587
+ configs.push(oxlintReact({
4588
+ stylistic: stylisticOptions,
4589
+ ...reactOptions
4590
+ }));
4591
+ }
4592
+ if (enableSpellCheck !== false) configs.push(oxlintSpelling({
4593
+ ...resolveSubOptions(options, "spellCheck"),
4594
+ componentExts: componentExtensions,
4595
+ isInEditor
4596
+ }));
4597
+ if (enableEslintPlugin !== false) configs.push(oxlintEslintPlugin({ ...getOverrides(options, "eslintPlugin") }));
4598
+ if (enableOxc) {
4599
+ configs.push(oxlintOxc({ roblox: enableRoblox }));
4600
+ if (needsComplementOverlay) configs.push(oxlintOxc({
4601
+ excludeFiles: robloxScopedFiles,
4602
+ roblox: false
4603
+ }));
4604
+ }
4605
+ configs.push(oxlintSmallRules({
4606
+ componentExts: componentExtensions,
4607
+ isInEditor,
4608
+ stylistic: stylisticOptions
4609
+ }), oxlintFlawless({ stylistic: stylisticOptions }, prettierSettings), oxlintComments({
4610
+ prettierOptions: prettierSettings,
4611
+ stylistic: stylisticOptions
4612
+ }), oxlintDisables({ root: rootGlobs }));
4613
+ if (stylisticOptions !== false && formatters !== false) {
4614
+ const oxfmtConfigOptions = resolveOxfmtConfigOptionsSync();
4615
+ configs.push(oxlintOxfmt({
4616
+ componentExts: componentExtensions,
4617
+ oxfmtOptions: buildOxfmtOptions({
4618
+ oxfmtConfigOptions,
4619
+ oxfmtOptions: formatterOptions.oxfmtOptions,
4620
+ prettierOptions: prettierSettings
4621
+ })
4622
+ }));
4623
+ }
4624
+ const jsPlugins = /* @__PURE__ */ new Map();
4625
+ const nativePlugins = /* @__PURE__ */ new Set();
4626
+ const overrides = [];
4627
+ const mergedSettings = {};
4628
+ const gitignorePatterns = enableGitignore !== false ? oxlintGitignore() : [];
4629
+ const ignorePatterns = typeof ignores === "function" ? ignores([...GLOB_EXCLUDE, ...gitignorePatterns]) : [
4630
+ ...GLOB_EXCLUDE,
4631
+ ...gitignorePatterns,
4632
+ ...ignores ?? []
4633
+ ];
4634
+ /**
4635
+ * Merge a fragment into the accumulated overrides.
4636
+ *
4637
+ * @param config - The fragment to merge.
4638
+ */
4639
+ function mergeFragment(config) {
4640
+ const fragmentJsPlugins = config.jsPlugins ?? [];
4641
+ const fragmentPlugins = config.plugins ?? [];
4642
+ for (const plugin of fragmentPlugins) nativePlugins.add(plugin);
4643
+ for (const jsPlugin of fragmentJsPlugins) jsPlugins.set(jsPluginKey(jsPlugin), jsPlugin);
4644
+ if (config.settings) Object.assign(mergedSettings, config.settings);
4645
+ const { name: _name, plugins: _plugins, settings: _settings, ...override } = config;
4646
+ override.files = override.files.map(anchorOxlintGlob);
4647
+ if (override.excludeFiles !== void 0) override.excludeFiles = override.excludeFiles.map(anchorOxlintGlob);
4648
+ overrides.push(override);
4649
+ }
4650
+ for (const fragment of configs.flat()) {
4651
+ if (nativeOnly && droppedByNativeOnly(fragment)) {
4652
+ if (fragment.settings) Object.assign(mergedSettings, fragment.settings);
4653
+ continue;
4654
+ }
4655
+ mergeFragment(fragment);
4656
+ }
4657
+ if (Object.keys(rules).length > 0) {
4658
+ const optionsFragments = createOxlintConfigs({
4659
+ name: "isentinel/options-rules",
4660
+ files: [GLOB_SRC],
4661
+ keepUnmappedOff: true,
4662
+ rules
4663
+ });
4664
+ for (const fragment of optionsFragments) {
4665
+ const { jsPlugins: _fragmentJsPlugins, ...withoutJsPlugins } = fragment;
4666
+ mergeFragment(nativeOnly ? withoutJsPlugins : fragment);
4667
+ }
4668
+ }
4669
+ if (globals && Object.keys(globals).length > 0) mergeFragment({
4670
+ name: "isentinel/options-globals",
4671
+ files: [GLOB_SRC],
4672
+ globals
4673
+ });
4674
+ for (const userConfig of userConfigs) mergeFragment(userConfig);
4675
+ if (Array.isArray(userJsPlugins)) for (const jsPlugin of userJsPlugins) jsPlugins.set(jsPluginKey(jsPlugin), jsPlugin);
4676
+ if (nativeOnly) stripUnregisteredPluginRules(overrides, /* @__PURE__ */ new Set([...nativePlugins, ...jsPlugins.keys()]));
4677
+ if (defaultSeverity) {
4678
+ const severityExcludeRules = /* @__PURE__ */ new Set(["sonar/todo-tag"]);
4679
+ for (const override of overrides) if (override.rules) override.rules = overrideRuleSeverity(override.rules, defaultSeverity, severityExcludeRules);
4680
+ }
4681
+ return defineConfig({
4682
+ categories: {
4683
+ ...DEFAULT_CATEGORIES,
4684
+ ...categories
4685
+ },
4686
+ ...env ? { env } : {},
4687
+ ...globals ? { globals } : {},
4688
+ ignorePatterns,
4689
+ jsPlugins: [...jsPlugins.values()],
4690
+ options: {
4691
+ typeAware,
4692
+ ...linterOptions
4693
+ },
4694
+ overrides,
4695
+ plugins: [...nativePlugins],
4696
+ settings: mergedSettings
4697
+ });
4698
+ }
4699
+ /**
4700
+ * Whether native-only mode drops a preset fragment: it needs a jsPlugin, and at
4701
+ * least one of them is not kept in that mode.
4702
+ *
4703
+ * @param fragment - The preset fragment.
4704
+ * @returns Whether the fragment is dropped.
4705
+ */
4706
+ function droppedByNativeOnly(fragment) {
4707
+ const fragmentJsPlugins = fragment.jsPlugins ?? [];
4708
+ return fragmentJsPlugins.length > 0 && fragmentJsPlugins.some((entry) => !NATIVE_ONLY_JS_PLUGINS.has(jsPluginKey(entry)));
4709
+ }
4710
+ //#endregion
4711
+ export { isentinel as default, isentinel, isOxlintCovered, oxlintJsPlugins, oxlintRuleMapping, resolveJsPluginSpecifier, staysInEslint, translateRuleToOxlint };