@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.
- package/README.md +322 -3
- package/bin/index.js +1 -1
- package/bin/lint.js +3 -0
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +719 -0
- package/dist/formatter-agents.d.mts +22 -0
- package/dist/formatter-agents.mjs +69 -0
- package/dist/index.d.mts +19142 -11553
- package/dist/index.mjs +5394 -2590
- package/dist/lint-cli.d.mts +1 -0
- package/dist/lint-cli.mjs +4827 -0
- package/dist/lint-ignored.d.mts +1 -0
- package/dist/lint-ignored.mjs +280 -0
- package/dist/oxlint.d.mts +32373 -0
- package/dist/oxlint.mjs +4711 -0
- package/package.json +74 -41
package/dist/oxlint.mjs
ADDED
|
@@ -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 };
|