@jameswyse/oxc-config 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -0
  3. package/REFERENCE.md +134 -0
  4. package/dist/format.d.ts +5 -0
  5. package/dist/format.js +47 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +97 -0
  8. package/dist/lint.d.ts +33 -0
  9. package/dist/lint.js +493 -0
  10. package/dist/rules/companion-has-owner.d.ts +1 -0
  11. package/dist/rules/companion-has-owner.js +74 -0
  12. package/dist/rules/electron-no-raw-ipc-renderer-exposure.d.ts +1 -0
  13. package/dist/rules/electron-no-raw-ipc-renderer-exposure.js +127 -0
  14. package/dist/rules/electron-no-remote-module.d.ts +1 -0
  15. package/dist/rules/electron-no-remote-module.js +112 -0
  16. package/dist/rules/electron-secure-web-preferences.d.ts +1 -0
  17. package/dist/rules/electron-secure-web-preferences.js +108 -0
  18. package/dist/rules/explicit-api-states.d.ts +1 -0
  19. package/dist/rules/explicit-api-states.js +143 -0
  20. package/dist/rules/import-boundaries.d.ts +1 -0
  21. package/dist/rules/import-boundaries.js +99 -0
  22. package/dist/rules/no-ambient-clock.d.ts +1 -0
  23. package/dist/rules/no-ambient-clock.js +35 -0
  24. package/dist/rules/no-ambient-random.d.ts +1 -0
  25. package/dist/rules/no-ambient-random.js +71 -0
  26. package/dist/rules/no-comments.d.ts +1 -0
  27. package/dist/rules/no-comments.js +64 -0
  28. package/dist/rules/no-deep-ternary.d.ts +1 -0
  29. package/dist/rules/no-deep-ternary.js +32 -0
  30. package/dist/rules/no-edit-proof-assertions.d.ts +1 -0
  31. package/dist/rules/no-edit-proof-assertions.js +120 -0
  32. package/dist/rules/no-env-access.d.ts +1 -0
  33. package/dist/rules/no-env-access.js +35 -0
  34. package/dist/rules/no-fixed-test-delay.d.ts +1 -0
  35. package/dist/rules/no-fixed-test-delay.js +130 -0
  36. package/dist/rules/no-forwarding-module.d.ts +1 -0
  37. package/dist/rules/no-forwarding-module.js +158 -0
  38. package/dist/rules/no-generic-file-suffix.d.ts +1 -0
  39. package/dist/rules/no-generic-file-suffix.js +74 -0
  40. package/dist/rules/no-pass-through-function.d.ts +1 -0
  41. package/dist/rules/no-pass-through-function.js +106 -0
  42. package/dist/rules/no-swallowed-errors.d.ts +1 -0
  43. package/dist/rules/no-swallowed-errors.js +168 -0
  44. package/dist/rules/no-test-timeout-overrides.d.ts +1 -0
  45. package/dist/rules/no-test-timeout-overrides.js +105 -0
  46. package/dist/rules/no-vacuous-test-assertions.d.ts +1 -0
  47. package/dist/rules/no-vacuous-test-assertions.js +71 -0
  48. package/dist/rules/server-action-guard.d.ts +1 -0
  49. package/dist/rules/server-action-guard.js +109 -0
  50. package/dist/rules/statement-spacing.d.ts +1 -0
  51. package/dist/rules/statement-spacing.js +29 -0
  52. package/dist/rules/type-guard-name.d.ts +1 -0
  53. package/dist/rules/type-guard-name.js +59 -0
  54. package/dist/shared/index.d.ts +26 -0
  55. package/dist/shared/index.js +260 -0
  56. package/dist/shared/rule-options.d.ts +2 -0
  57. package/dist/shared/rule-options.js +8 -0
  58. package/dist/vendor/anti-slop/effect/rules/no-manual-effect-error-tag.d.ts +1 -0
  59. package/dist/vendor/anti-slop/effect/rules/no-manual-effect-error-tag.js +35 -0
  60. package/dist/vendor/anti-slop/effect/rules/no-service-constructor-imports.d.ts +1 -0
  61. package/dist/vendor/anti-slop/effect/rules/no-service-constructor-imports.js +48 -0
  62. package/dist/vendor/anti-slop/effect/shared/tagged-values.d.ts +9 -0
  63. package/dist/vendor/anti-slop/effect/shared/tagged-values.js +65 -0
  64. package/dist/vendor/anti-slop/rules/no-array-filter-map.d.ts +1 -0
  65. package/dist/vendor/anti-slop/rules/no-array-filter-map.js +40 -0
  66. package/dist/vendor/anti-slop/rules/no-chained-type-assertions.d.ts +1 -0
  67. package/dist/vendor/anti-slop/rules/no-chained-type-assertions.js +58 -0
  68. package/dist/vendor/anti-slop/rules/no-conditional-empty-object-spread.d.ts +1 -0
  69. package/dist/vendor/anti-slop/rules/no-conditional-empty-object-spread.js +41 -0
  70. package/dist/vendor/anti-slop/rules/no-known-value-widening.d.ts +1 -0
  71. package/dist/vendor/anti-slop/rules/no-known-value-widening.js +333 -0
  72. package/dist/vendor/anti-slop/rules/no-module-mocking.d.ts +1 -0
  73. package/dist/vendor/anti-slop/rules/no-module-mocking.js +74 -0
  74. package/dist/vendor/anti-slop/rules/no-object-parameters.d.ts +1 -0
  75. package/dist/vendor/anti-slop/rules/no-object-parameters.js +59 -0
  76. package/dist/vendor/anti-slop/rules/no-reduce-accumulator-copy.d.ts +1 -0
  77. package/dist/vendor/anti-slop/rules/no-reduce-accumulator-copy.js +124 -0
  78. package/dist/vendor/anti-slop/rules/no-reflect-apply.d.ts +1 -0
  79. package/dist/vendor/anti-slop/rules/no-reflect-apply.js +26 -0
  80. package/dist/vendor/anti-slop/rules/no-reflect-get.d.ts +1 -0
  81. package/dist/vendor/anti-slop/rules/no-reflect-get.js +26 -0
  82. package/dist/vendor/anti-slop/rules/no-runtime-typeof.d.ts +1 -0
  83. package/dist/vendor/anti-slop/rules/no-runtime-typeof.js +63 -0
  84. package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.d.ts +1 -0
  85. package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.js +37 -0
  86. package/dist/vendor/anti-slop/rules/no-unknown-returns.d.ts +1 -0
  87. package/dist/vendor/anti-slop/rules/no-unknown-returns.js +61 -0
  88. package/dist/vendor/anti-slop/rules/no-unknown-type-aliases.d.ts +1 -0
  89. package/dist/vendor/anti-slop/rules/no-unknown-type-aliases.js +38 -0
  90. package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.d.ts +1 -0
  91. package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.js +143 -0
  92. package/dist/vendor/anti-slop/rules/no-widen-then-assert.d.ts +1 -0
  93. package/dist/vendor/anti-slop/rules/no-widen-then-assert.js +288 -0
  94. package/dist/vendor/anti-slop/shared/array-method.d.ts +10 -0
  95. package/dist/vendor/anti-slop/shared/array-method.js +95 -0
  96. package/dist/vendor/anti-slop/shared/dictionary-types.d.ts +23 -0
  97. package/dist/vendor/anti-slop/shared/dictionary-types.js +408 -0
  98. package/dist/vendor/anti-slop/shared/function-parameters.d.ts +6 -0
  99. package/dist/vendor/anti-slop/shared/function-parameters.js +41 -0
  100. package/dist/vendor/anti-slop/shared/lexical-type-parameters.d.ts +4 -0
  101. package/dist/vendor/anti-slop/shared/lexical-type-parameters.js +32 -0
  102. package/dist/vendor/anti-slop/shared/reflect-method.d.ts +3 -0
  103. package/dist/vendor/anti-slop/shared/reflect-method.js +24 -0
  104. package/dist/vendor/anti-slop/shared/scope.d.ts +3 -0
  105. package/dist/vendor/anti-slop/shared/scope.js +12 -0
  106. package/dist/vendor/anti-slop/shared/type-alias-resolution.d.ts +21 -0
  107. package/dist/vendor/anti-slop/shared/type-alias-resolution.js +147 -0
  108. package/dist/vendor/eslint-stylistic/padding-line-ast.d.ts +14 -0
  109. package/dist/vendor/eslint-stylistic/padding-line-ast.js +19 -0
  110. package/dist/vendor/eslint-stylistic/padding-line-between-statements.d.ts +91 -0
  111. package/dist/vendor/eslint-stylistic/padding-line-between-statements.js +493 -0
  112. package/package.json +89 -0
  113. package/src/vendor/anti-slop/LICENSE +21 -0
  114. package/src/vendor/eslint-stylistic/LICENSE +22 -0
  115. package/tsconfig.base.json +10 -0
package/dist/lint.js ADDED
@@ -0,0 +1,493 @@
1
+ import { createRequire } from "node:module";
2
+ import { extname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const require = createRequire(import.meta.url);
5
+ const sourceFiles = ["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
6
+ const typeScriptFiles = ["**/*.{ts,tsx,mts,cts}"];
7
+ const testFiles = [
8
+ "**/*.{test,spec}.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
9
+ "**/__tests__/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
10
+ ];
11
+ export const defaultIgnores = [
12
+ "**/node_modules/**",
13
+ "**/coverage/**",
14
+ "**/dist/**",
15
+ "**/.next/**",
16
+ "**/.nuxt/**",
17
+ "**/.output/**",
18
+ "**/.turbo/**",
19
+ "**/playwright-report/**",
20
+ "**/test-results/**",
21
+ "**/.agent/**",
22
+ "**/.claude/**",
23
+ "**/.codex/**",
24
+ "**/.cursor/**",
25
+ "**/.gemini/**",
26
+ "**/.playwright-cli/**",
27
+ ];
28
+ const entrypointFiles = [
29
+ "**/*.{config,conf}.{js,mjs,cjs,ts,mts,cts}",
30
+ "**/{sanity.cli,codegen,graphql-codegen}.{js,mjs,cjs,ts,mts,cts}",
31
+ "**/scripts/**/*.{js,mjs,cjs,ts,mts,cts}",
32
+ ];
33
+ const defaultExportFiles = [
34
+ "**/*.d.{ts,mts,cts}",
35
+ "**/*.stories.{js,jsx,mjs,ts,tsx,mts}",
36
+ "**/.storybook/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
37
+ ];
38
+ const endToEndFiles = ["**/e2e/**", "**/*.e2e.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
39
+ const electronRendererFiles = ["src/renderer/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
40
+ const testRules = {
41
+ "wyse/no-fixed-test-delay": "error",
42
+ "wyse/no-vacuous-test-assertions": "error",
43
+ "wyse/no-edit-proof-assertions": "error",
44
+ "wyse/companion-has-owner": "warn",
45
+ };
46
+ const basePlugins = ["eslint", "unicorn", "oxc", "import", "promise", "typescript"];
47
+ function errors(prefix, names) {
48
+ return Object.fromEntries(names.map((name) => [`${prefix}/${name}`, "error"]));
49
+ }
50
+ const wysePlugin = {
51
+ name: "wyse",
52
+ specifier: fileURLToPath(new URL(`./index${extname(import.meta.url)}`, import.meta.url)),
53
+ };
54
+ function filesFor(value, defaults) {
55
+ return value === true ? defaults : value || [];
56
+ }
57
+ function ownedRule(rule, owners, excludeFiles) {
58
+ return [
59
+ { files: sourceFiles, excludeFiles, rules: { [rule]: "error" } },
60
+ { files: [...entrypointFiles, ...owners], rules: { [rule]: "off" } },
61
+ ];
62
+ }
63
+ function electronRenderer(electron) {
64
+ return electron === true || !electron.renderer ? electronRendererFiles : electron.renderer;
65
+ }
66
+ function reactScope(react, nextjs) {
67
+ if (react === true || nextjs === true) {
68
+ return sourceFiles;
69
+ }
70
+ const files = [...(react || []), ...(nextjs || [])];
71
+ return files.length > 0 ? [...new Set(files)] : false;
72
+ }
73
+ const baseErrors = errors("wyse", [
74
+ "no-array-filter-map",
75
+ "no-reduce-accumulator-copy",
76
+ "no-chained-type-assertions",
77
+ "no-conditional-empty-object-spread",
78
+ "no-known-value-widening",
79
+ "no-module-mocking",
80
+ "no-object-parameters",
81
+ "no-reflect-apply",
82
+ "no-reflect-get",
83
+ "no-shape-in-symbol-names",
84
+ "no-unknown-returns",
85
+ "no-unknown-type-aliases",
86
+ "no-unsafe-dictionary-type",
87
+ "no-widen-then-assert",
88
+ ]);
89
+ const typeAwareRules = {
90
+ ...errors("typescript", [
91
+ "await-thenable",
92
+ "no-misused-promises",
93
+ "no-unnecessary-condition",
94
+ "no-unnecessary-type-assertion",
95
+ "no-unsafe-argument",
96
+ "no-unsafe-assignment",
97
+ "no-unsafe-call",
98
+ "no-unsafe-member-access",
99
+ "no-unsafe-return",
100
+ "no-unsafe-type-assertion",
101
+ "only-throw-error",
102
+ "prefer-readonly",
103
+ "return-await",
104
+ "switch-exhaustiveness-check",
105
+ ]),
106
+ "typescript/no-floating-promises": [
107
+ "error",
108
+ {
109
+ allowForKnownSafeCalls: [
110
+ { from: "package", package: "node:test", name: ["test", "it", "describe", "suite"] },
111
+ ],
112
+ },
113
+ ],
114
+ };
115
+ const reactRules = errors("react", [
116
+ "capitalized-calls",
117
+ "error-boundaries",
118
+ "exhaustive-effect-dependencies",
119
+ "globals",
120
+ "hooks",
121
+ "immutability",
122
+ "incompatible-library",
123
+ "invariant",
124
+ "memo-dependencies",
125
+ "no-deriving-state-in-effects",
126
+ "preserve-manual-memoization",
127
+ "purity",
128
+ "refs",
129
+ "rule-suppression",
130
+ "set-state-in-effect",
131
+ "set-state-in-render",
132
+ "static-components",
133
+ "syntax",
134
+ "todo",
135
+ "unsupported-syntax",
136
+ "use-memo",
137
+ "void-use-memo",
138
+ "forward-ref-uses-ref",
139
+ "iframe-missing-sandbox",
140
+ "jsx-key",
141
+ "jsx-no-comment-textnodes",
142
+ "jsx-no-duplicate-props",
143
+ "jsx-no-script-url",
144
+ "jsx-no-undef",
145
+ "jsx-props-no-spread-multi",
146
+ "no-children-prop",
147
+ "no-danger-with-children",
148
+ "no-did-mount-set-state",
149
+ "no-did-update-set-state",
150
+ "no-direct-mutation-state",
151
+ "no-find-dom-node",
152
+ "no-is-mounted",
153
+ "no-namespace",
154
+ "no-render-return-value",
155
+ "no-string-refs",
156
+ "no-this-in-sfc",
157
+ "no-unsafe",
158
+ "no-unstable-nested-components",
159
+ "no-will-update-set-state",
160
+ "style-prop-object",
161
+ "void-dom-elements-no-children",
162
+ ]);
163
+ const accessibilityRules = errors("jsx-a11y", [
164
+ "alt-text",
165
+ "anchor-has-content",
166
+ "anchor-is-valid",
167
+ "aria-activedescendant-has-tabindex",
168
+ "aria-props",
169
+ "aria-proptypes",
170
+ "aria-role",
171
+ "aria-unsupported-elements",
172
+ "autocomplete-valid",
173
+ "click-events-have-key-events",
174
+ "control-has-associated-label",
175
+ "heading-has-content",
176
+ "html-has-lang",
177
+ "iframe-has-title",
178
+ "img-redundant-alt",
179
+ "interactive-supports-focus",
180
+ "label-has-associated-control",
181
+ "lang",
182
+ "media-has-caption",
183
+ "mouse-events-have-key-events",
184
+ "no-access-key",
185
+ "no-aria-hidden-on-focusable",
186
+ "no-autofocus",
187
+ "no-distracting-elements",
188
+ "no-interactive-element-to-noninteractive-role",
189
+ "no-noninteractive-element-interactions",
190
+ "no-noninteractive-element-to-interactive-role",
191
+ "no-redundant-roles",
192
+ "no-static-element-interactions",
193
+ "role-has-required-aria-props",
194
+ "role-supports-aria-props",
195
+ "scope",
196
+ "tabindex-no-positive",
197
+ ]);
198
+ const nextRules = errors("nextjs", [
199
+ "google-font-display",
200
+ "google-font-preconnect",
201
+ "inline-script-id",
202
+ "next-script-for-ga",
203
+ "no-assign-module-variable",
204
+ "no-async-client-component",
205
+ "no-before-interactive-script-outside-document",
206
+ "no-css-tags",
207
+ "no-document-import-in-page",
208
+ "no-duplicate-head",
209
+ "no-head-element",
210
+ "no-head-import-in-document",
211
+ "no-html-link-for-pages",
212
+ "no-img-element",
213
+ "no-page-custom-font",
214
+ "no-script-component-in-head",
215
+ "no-styled-jsx-in-document",
216
+ "no-sync-scripts",
217
+ "no-title-in-document-head",
218
+ "no-typos",
219
+ "no-unwanted-polyfillio",
220
+ ]);
221
+ const reactEffectRules = errors("react-you-might-not-need-an-effect", [
222
+ "no-adjust-state-on-prop-change",
223
+ "no-chain-state-updates",
224
+ "no-derived-state",
225
+ "no-event-handler",
226
+ "no-external-store-subscription",
227
+ "no-initialize-state",
228
+ "no-pass-data-to-parent",
229
+ "no-pass-live-state-to-parent",
230
+ "no-reset-all-state-on-prop-change",
231
+ ]);
232
+ const browserPerformanceRules = errors("e18e", [
233
+ "prefer-array-at",
234
+ "prefer-array-fill",
235
+ "prefer-array-from-map",
236
+ "prefer-array-some",
237
+ "prefer-date-now",
238
+ "prefer-includes",
239
+ "prefer-nullish-coalescing",
240
+ "prefer-object-has-own",
241
+ "prefer-regex-test",
242
+ "prefer-spread-syntax",
243
+ "prefer-static-regex",
244
+ "prefer-string-fromcharcode",
245
+ "prefer-timer-args",
246
+ ]);
247
+ const testRuleNames = [
248
+ "no-conditional-expect",
249
+ "no-disabled-tests",
250
+ "no-duplicate-hooks",
251
+ "no-focused-tests",
252
+ "no-identical-title",
253
+ "no-standalone-expect",
254
+ "valid-describe-callback",
255
+ "valid-expect",
256
+ "valid-expect-in-promise",
257
+ ];
258
+ export function createLintConfig({ typeAware = true, node = false, react = false, nextjs = false, vitest = false, jest = false, effect = false, tests = [], env, boundaries = [], serverActionGuards, clock, random, electron = false, } = {}) {
259
+ const nextRoots = filesFor(nextjs, ["."]).map((root) => {
260
+ const normalized = root.replace(/^\.\//, "").replace(/\/+$/, "");
261
+ return normalized === "." || normalized === "" ? "" : `${normalized}/`;
262
+ });
263
+ const nextFiles = nextRoots.flatMap((root) => sourceFiles.map((pattern) => `${root}${pattern}`));
264
+ const reactFiles = reactScope(react, nextFiles);
265
+ const allTestFiles = [
266
+ ...new Set([
267
+ ...testFiles,
268
+ ...tests,
269
+ ...filesFor(vitest, testFiles),
270
+ ...filesFor(jest, testFiles),
271
+ ]),
272
+ ];
273
+ const jsPlugins = [wysePlugin];
274
+ const typescriptRules = {
275
+ ...errors("typescript", [
276
+ "ban-ts-comment",
277
+ "consistent-type-imports",
278
+ "no-explicit-any",
279
+ "no-non-null-assertion",
280
+ "no-empty-object-type",
281
+ "no-unnecessary-type-constraint",
282
+ ]),
283
+ "wyse/type-guard-name": "error",
284
+ };
285
+ const overrides = [
286
+ {
287
+ files: entrypointFiles,
288
+ plugins: [...basePlugins, "node"],
289
+ env: { node: true },
290
+ rules: {
291
+ "import/no-default-export": "off",
292
+ "eslint/no-console": "off",
293
+ "wyse/no-forwarding-module": "off",
294
+ },
295
+ },
296
+ {
297
+ files: defaultExportFiles,
298
+ rules: { "import/no-default-export": "off" },
299
+ },
300
+ { files: allTestFiles, rules: testRules },
301
+ {
302
+ files: allTestFiles,
303
+ excludeFiles: endToEndFiles,
304
+ rules: { "wyse/no-test-timeout-overrides": "warn" },
305
+ },
306
+ ];
307
+ if (env !== undefined && env.length > 0) {
308
+ overrides.push(...ownedRule("wyse/no-env-access", env, []));
309
+ }
310
+ if (clock !== undefined && clock.length > 0) {
311
+ overrides.push(...ownedRule("wyse/no-ambient-clock", clock, allTestFiles));
312
+ }
313
+ if (random !== undefined && random.length > 0) {
314
+ overrides.push(...ownedRule("wyse/no-ambient-random", random, allTestFiles));
315
+ }
316
+ const importBoundaries = electron
317
+ ? [
318
+ ...boundaries,
319
+ {
320
+ files: electronRenderer(electron),
321
+ ignore: allTestFiles,
322
+ deny: ["electron", "electron/**", "@electron/**", "node:*", "node:*/**"],
323
+ message: "Renderer code reaches Electron and Node through the preload API.",
324
+ },
325
+ ]
326
+ : boundaries;
327
+ if (importBoundaries.length > 0) {
328
+ overrides.push({
329
+ files: sourceFiles,
330
+ rules: { "wyse/import-boundaries": ["error", { boundaries: importBoundaries }] },
331
+ });
332
+ }
333
+ if (electron) {
334
+ overrides.push({ files: sourceFiles, rules: { "wyse/electron-no-remote-module": "error" } }, {
335
+ files: sourceFiles,
336
+ excludeFiles: allTestFiles,
337
+ rules: {
338
+ "wyse/electron-secure-web-preferences": "error",
339
+ "wyse/electron-no-raw-ipc-renderer-exposure": "error",
340
+ },
341
+ });
342
+ }
343
+ if (serverActionGuards !== undefined && serverActionGuards.length > 0) {
344
+ overrides.push({
345
+ files: sourceFiles,
346
+ excludeFiles: allTestFiles,
347
+ rules: { "wyse/server-action-guard": ["error", { guards: serverActionGuards }] },
348
+ });
349
+ }
350
+ if (typeAware) {
351
+ overrides.unshift({ files: typeScriptFiles, rules: typeAwareRules });
352
+ }
353
+ if (node) {
354
+ overrides.push({
355
+ files: filesFor(node, sourceFiles),
356
+ plugins: [...basePlugins, "node"],
357
+ env: { node: true },
358
+ rules: {
359
+ "node/no-new-require": "error",
360
+ "node/no-path-concat": "error",
361
+ "node/no-exports-assign": "error",
362
+ },
363
+ });
364
+ }
365
+ if (reactFiles) {
366
+ jsPlugins.push({ name: "e18e", specifier: require.resolve("@e18e/eslint-plugin") }, {
367
+ name: "react-you-might-not-need-an-effect",
368
+ specifier: require.resolve("eslint-plugin-react-you-might-not-need-an-effect"),
369
+ });
370
+ overrides.push({
371
+ files: reactFiles,
372
+ plugins: [...basePlugins, "react", "jsx-a11y"],
373
+ env: { browser: true },
374
+ rules: {
375
+ ...reactRules,
376
+ ...accessibilityRules,
377
+ "react/exhaustive-deps": "error",
378
+ "react/no-array-index-key": "error",
379
+ "react/react-in-jsx-scope": "off",
380
+ "jsx-a11y/no-noninteractive-tabindex": ["error", { roles: ["region", "tabpanel"] }],
381
+ "jsx-a11y/prefer-tag-over-role": "off",
382
+ },
383
+ }, {
384
+ files: reactFiles,
385
+ excludeFiles: allTestFiles,
386
+ rules: { ...reactEffectRules, ...browserPerformanceRules },
387
+ });
388
+ }
389
+ if (nextFiles.length > 0) {
390
+ overrides.push({
391
+ files: nextFiles,
392
+ excludeFiles: allTestFiles,
393
+ plugins: [...basePlugins, "react", "jsx-a11y", "nextjs"],
394
+ rules: nextRules,
395
+ }, {
396
+ files: nextRoots.flatMap((root) => ["", "src/"].flatMap((source) => [
397
+ `${root}${source}app/**/{page,layout,template,loading,error,global-error,not-found,global-not-found,forbidden,unauthorized,default}.{js,jsx,ts,tsx}`,
398
+ `${root}${source}app/**/{sitemap,robots,manifest,icon,apple-icon,opengraph-image,twitter-image}.{js,jsx,ts,tsx}`,
399
+ `${root}${source}pages/**/*.{js,jsx,ts,tsx}`,
400
+ `${root}${source}{middleware,proxy}.{js,ts}`,
401
+ ])),
402
+ excludeFiles: allTestFiles,
403
+ rules: { "import/no-default-export": "off" },
404
+ });
405
+ }
406
+ const testRunners = [
407
+ ["vitest", vitest],
408
+ ["jest", jest],
409
+ ];
410
+ for (const [name, value] of testRunners) {
411
+ if (value) {
412
+ overrides.push({
413
+ files: filesFor(value, allTestFiles),
414
+ plugins: [...basePlugins, name],
415
+ env: { [name]: true },
416
+ rules: {
417
+ ...errors(name, testRuleNames),
418
+ ...testRules,
419
+ [`${name}/no-standalone-expect`]: [
420
+ "error",
421
+ { additionalTestBlockFunctions: ["it.effect", "it.live"] },
422
+ ],
423
+ },
424
+ });
425
+ }
426
+ }
427
+ if (effect) {
428
+ overrides.push({
429
+ files: filesFor(effect, typeScriptFiles),
430
+ rules: errors("wyse", ["no-manual-effect-error-tag", "no-service-constructor-imports"]),
431
+ });
432
+ }
433
+ return {
434
+ categories: { correctness: "error", suspicious: "error" },
435
+ options: { reportUnusedDisableDirectives: "error", typeAware },
436
+ plugins: basePlugins,
437
+ jsPlugins,
438
+ env: { es2022: true },
439
+ ignorePatterns: defaultIgnores,
440
+ rules: {
441
+ ...baseErrors,
442
+ ...typescriptRules,
443
+ "wyse/no-runtime-typeof": ["warn", { allowInTypeGuards: true }],
444
+ "wyse/no-comments": "error",
445
+ "wyse/statement-spacing": "error",
446
+ "wyse/no-generic-file-suffix": "error",
447
+ "wyse/no-swallowed-errors": "warn",
448
+ "wyse/explicit-api-states": "warn",
449
+ "wyse/no-pass-through-function": "warn",
450
+ "wyse/no-forwarding-module": "warn",
451
+ "import/no-cycle": "warn",
452
+ "oxc/no-barrel-file": "warn",
453
+ "eslint/curly": ["error", "all"],
454
+ "eslint/eqeqeq": ["error", "always"],
455
+ "eslint/no-console": "error",
456
+ "eslint/no-empty": ["error", { allowEmptyCatch: false }],
457
+ "eslint/no-implicit-coercion": "error",
458
+ "eslint/no-param-reassign": "error",
459
+ "eslint/no-unused-vars": [
460
+ "error",
461
+ {
462
+ args: "all",
463
+ argsIgnorePattern: "^_",
464
+ caughtErrors: "all",
465
+ caughtErrorsIgnorePattern: "^_",
466
+ destructuredArrayIgnorePattern: "^_",
467
+ varsIgnorePattern: "^_",
468
+ ignoreRestSiblings: true,
469
+ },
470
+ ],
471
+ "eslint/no-use-before-define": [
472
+ "error",
473
+ { functions: true, classes: true, variables: true, allowNamedExports: false },
474
+ ],
475
+ "eslint/no-underscore-dangle": "off",
476
+ "eslint/prefer-const": "error",
477
+ "import/first": "error",
478
+ "import/no-default-export": "error",
479
+ "import/no-duplicates": "error",
480
+ "import/no-self-import": "error",
481
+ "import/no-unassigned-import": [
482
+ "error",
483
+ { allow: ["**/*.css", "server-only", "client-only"] },
484
+ ],
485
+ "oxc/no-accumulating-spread": "error",
486
+ "unicorn/no-nested-ternary": "off",
487
+ "wyse/no-deep-ternary": "error",
488
+ "unicorn/require-module-specifiers": "error",
489
+ "unicorn/consistent-function-scoping": "off",
490
+ },
491
+ overrides,
492
+ };
493
+ }
@@ -0,0 +1 @@
1
+ export declare const companionHasOwner: import("@oxlint/plugins").Rule;
@@ -0,0 +1,74 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { basename, dirname } from "node:path";
3
+ import { defineRule } from "@oxlint/plugins";
4
+ import { projectPath } from "../shared/index.js";
5
+ const companionMarkers = new Set(["test", "spec", "bench"]);
6
+ const integrationSegments = new Set(["integration", "e2e"]);
7
+ const integrationDirectories = new Set(["tests", "test", "__tests__", "e2e", "__e2e__"]);
8
+ const directoryEntries = new Map();
9
+ function entries(directory) {
10
+ const cached = directoryEntries.get(directory);
11
+ if (cached !== undefined) {
12
+ return cached;
13
+ }
14
+ const read = readdirSync(directory, { withFileTypes: true });
15
+ directoryEntries.set(directory, read);
16
+ return read;
17
+ }
18
+ function segmentsOf(name) {
19
+ return name.split(".").slice(0, -1);
20
+ }
21
+ function isCompanion(segments) {
22
+ const marker = segments.at(-1);
23
+ return segments.length > 1 && marker !== undefined && companionMarkers.has(marker);
24
+ }
25
+ function ownerStems(directory) {
26
+ const stems = new Set([basename(directory)]);
27
+ for (const entry of entries(directory)) {
28
+ const segments = segmentsOf(entry.name);
29
+ if (entry.isDirectory()) {
30
+ stems.add(entry.name);
31
+ }
32
+ else if (entry.isFile() && segments.length > 0 && !isCompanion(segments)) {
33
+ stems.add(segments.join("."));
34
+ }
35
+ }
36
+ return stems;
37
+ }
38
+ function prefixes(segments) {
39
+ return segments.slice(0, -1).map((_, index) => segments.slice(0, index + 1).join("."));
40
+ }
41
+ function isIntegrationTest(context, segments) {
42
+ return (segments.some((segment) => integrationSegments.has(segment)) ||
43
+ projectPath(context.cwd, dirname(context.filename))
44
+ .split("/")
45
+ .some((directory) => integrationDirectories.has(directory)));
46
+ }
47
+ export const companionHasOwner = defineRule({
48
+ meta: {
49
+ type: "suggestion",
50
+ docs: {
51
+ description: "Start a test file's name with the basename of the sibling module it covers.",
52
+ },
53
+ schema: [],
54
+ messages: {
55
+ owner: "'{{name}}' has no sibling module named '{{owner}}'. Start the name with its owner's basename, such as `{{owner}}.case.test`, and rename it after moving or deleting the owner. Name a cross-module test `<behaviour>.integration.test` at the nearest common owner.",
56
+ },
57
+ },
58
+ create(context) {
59
+ const name = basename(context.filename);
60
+ const segments = segmentsOf(name);
61
+ if (!isCompanion(segments) || isIntegrationTest(context, segments)) {
62
+ return {};
63
+ }
64
+ const stems = ownerStems(dirname(context.filename));
65
+ if (prefixes(segments).some((stem) => stems.has(stem))) {
66
+ return {};
67
+ }
68
+ return {
69
+ Program(node) {
70
+ context.report({ node, messageId: "owner", data: { name, owner: segments[0] } });
71
+ },
72
+ };
73
+ },
74
+ });
@@ -0,0 +1 @@
1
+ export declare const electronNoRawIpcRendererExposure: import("@oxlint/plugins").Rule;