oxlint-config-awesomeness 2.0.0 → 3.0.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 (4) hide show
  1. package/README.md +57 -5
  2. package/bin/init.js +3 -1
  3. package/index.js +473 -230
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Opinionated Oxlint config for software houses that want all their apps to feel the same.
4
4
 
5
- **430 rules** across **10 plugins**. Built for full-stack TypeScript monorepos with React, Next.js, Hono, Prisma, and more.
5
+ **450 rules** across **10 plugins**. Built for full-stack TypeScript monorepos with React, Next.js, Hono, Prisma, and more.
6
6
 
7
7
  ## Installation
8
8
 
@@ -38,6 +38,38 @@ export default defineConfig({
38
38
 
39
39
  Then run `pnpm oxlint` or `npx oxlint`.
40
40
 
41
+ ## Upgrading from 2.x
42
+
43
+ 3.0 adds **28 new restriction-level rules** that error on patterns AI-generated code routinely produces. Existing 2.x codebases may see new lint errors on upgrade — this is intentional; each rule catches a real failure mode.
44
+
45
+ **What's newly enforced:**
46
+
47
+ - **TypeScript escape hatches** — `no-explicit-any`, `no-non-null-assertion`, `no-non-null-asserted-nullish-coalescing`, `no-require-imports`, `no-var-requires`, `no-import-type-side-effects`, `use-unknown-in-catch-callback-variable`, `promise-function-async`, `no-dynamic-delete`, `no-invalid-void-type`, `no-empty-object-type`
48
+ - **React correctness** — `button-has-type` (defaults silently submit forms), `no-danger` (XSS vector), `no-unknown-property`; `react/jsx-no-useless-fragment` re-enabled via `pedantic`
49
+ - **Modern JS / Node hygiene** — `unicorn/prefer-node-protocol`, `unicorn/prefer-number-properties`, `unicorn/prefer-modern-math-apis`, `unicorn/no-document-cookie`, `unicorn/no-process-exit`, `unicorn/no-abusive-eslint-disable`, `node/handle-callback-err`, `node/no-new-require`, `node/no-path-concat`, `promise/catch-or-return`
50
+ - **Core ESLint** — `no-var`, `no-use-before-define`, `no-empty`
51
+ - **Import graph** — `import/no-cycle`
52
+ - **Accessibility** — `jsx-a11y/anchor-ambiguous-text`
53
+
54
+ **What changed in overrides:**
55
+
56
+ - New `bin/**` and `scripts/**` override turns off `no-console` and `unicorn/no-process-exit` for CLI entry points.
57
+ - Test override extended: the new strict rules that produce noise in tests (`no-require-imports`, `no-var-requires`, `promise-function-async`, `no-non-null-asserted-nullish-coalescing`, `import/no-cycle`, `no-empty`, `no-use-before-define`) are off inside tests.
58
+
59
+ **Fixing common failures:**
60
+
61
+ | Error | Fix |
62
+ |-------|-----|
63
+ | `: any` / `as any` | Use `unknown` + narrowing, or a real type. |
64
+ | `value!` | Narrow with a type guard or throw explicitly. |
65
+ | `<button onClick={...}>` | Add `type="button"` (or `"submit"`/`"reset"` as needed). |
66
+ | `import fs from 'fs'` | `import fs from 'node:fs'`. |
67
+ | `require(...)` in TS | Convert to ESM `import`. |
68
+ | `fn(): Promise<T>` | Add `async` to the function. |
69
+ | `catch((err) => err.message)` | `err` is `unknown` — narrow with `err instanceof Error`. |
70
+
71
+ Every rule has inline code documentation with bad/good examples in the [All Rules](#all-rules) section below.
72
+
41
73
  ## FAQ
42
74
 
43
75
  ### I installed the package but no `.oxlintrc.json` was created. What did I miss?
@@ -96,18 +128,38 @@ The config includes smart overrides so strict rules don't create noise in files
96
128
 
97
129
  | Files | Relaxed Rules |
98
130
  |-------|---------------|
99
- | `*.test.ts`, `*.spec.ts`, `__tests__/**` | `no-explicit-any`, all `no-unsafe-*`, `max-lines`, `max-statements`, `no-empty-function` |
131
+ | `*.test.ts`, `*.spec.ts`, `__tests__/**` | `no-explicit-any`, `no-non-null-assertion` (+ asserted-nullish variant), `no-require-imports`, `no-var-requires`, `promise-function-async`, all `no-unsafe-*`, `import/no-cycle`, `max-lines`, `max-lines-per-function`, `max-nested-callbacks`, `max-statements`, `no-empty`, `no-empty-function`, `no-use-before-define` |
100
132
  | `*.stories.tsx` | `no-console`, `no-multi-comp` |
101
133
  | `**/seed.ts`, `**/migrate.ts` | `no-console` |
134
+ | `**/bin/**`, `scripts/**` | `no-console`, `unicorn/no-process-exit` |
102
135
  | `*.config.ts`, `next.config.*`, etc. | `max-lines`, `no-anonymous-default-export` |
103
136
  | `**/e2e/**/fixtures/**` | `rules-of-hooks` |
104
137
  | `*.ts`, `*.tsx` (all TypeScript) | Rules handled natively by the TS compiler (`no-undef`, `no-redeclare`, etc.) |
105
138
 
106
139
  ## Cherry-Picked Restriction Rules
107
140
 
108
- Instead of enabling the entire `restriction` category (which includes rules like `no-bitwise`, `no-plusplus`, `capitalized-comments` that cause daily friction), this config cherry-picks the most valuable restriction rules:
141
+ Instead of enabling the entire `restriction` category (which includes rules like `no-bitwise`, `no-plusplus`, `capitalized-comments` that cause daily friction), this config cherry-picks the most valuable restriction rules, grouped by plugin:
142
+
143
+ **Core ESLint**
144
+ `curly`, `default-case`, `eqeqeq`, `grouped-accessor-pairs`, `max-classes-per-file`, `max-depth`, `max-lines`, `max-nested-callbacks`, `max-params`, `no-alert`, `no-caller`, `no-console`, `no-empty`, `no-eval`, `no-extend-native`, `no-implicit-coercion`, `no-new-func`, `no-new-wrappers`, `no-object-constructor`, `no-param-reassign`, `no-proto`, `no-return-assign`, `no-script-url`, `no-shadow`, `no-throw-literal`, `no-use-before-define`, `no-var`, `no-void`, `prefer-promise-reject-errors`, `prefer-template`
145
+
146
+ **TypeScript** — blocks escape hatches (`any`, `!`, `require`) that AI-generated code routinely produces:
147
+ `@typescript-eslint/no-dynamic-delete`, `no-empty-object-type`, `no-explicit-any`, `no-import-type-side-effects`, `no-invalid-void-type`, `no-non-null-asserted-nullish-coalescing`, `no-non-null-assertion`, `no-require-imports`, `no-var-requires`, `promise-function-async`, `use-unknown-in-catch-callback-variable`
148
+
149
+ **React**
150
+ `react/button-has-type`, `react/no-danger`, `react/no-unknown-property`
151
+
152
+ **Import graph**
153
+ `import/no-cycle`
154
+
155
+ **Unicorn** (modern JS + anti-escape-hatch)
156
+ `unicorn/no-abusive-eslint-disable`, `unicorn/no-document-cookie`, `unicorn/no-process-exit`, `unicorn/prefer-modern-math-apis`, `unicorn/prefer-node-protocol`, `unicorn/prefer-number-properties`
157
+
158
+ **Promise / Node**
159
+ `promise/catch-or-return`, `node/handle-callback-err`, `node/no-new-require`, `node/no-path-concat`
109
160
 
110
- `curly`, `default-case`, `eqeqeq`, `grouped-accessor-pairs`, `max-classes-per-file`, `max-depth`, `max-lines`, `max-nested-callbacks`, `max-params`, `no-alert`, `no-caller`, `no-console`, `no-eval`, `no-extend-native`, `no-implicit-coercion`, `no-new-func`, `no-new-wrappers`, `no-object-constructor`, `no-param-reassign`, `no-proto`, `no-return-assign`, `no-script-url`, `no-shadow`, `no-throw-literal`, `no-void`, `prefer-promise-reject-errors`, `prefer-template`
161
+ **Accessibility**
162
+ `jsx-a11y/anchor-ambiguous-text`
111
163
 
112
164
  ## Disabled by Intent
113
165
 
@@ -115,7 +167,7 @@ The five categories are enabled at `error`, but a small set of category-included
115
167
 
116
168
  | Category of fight | Rules off |
117
169
  |-------------------|-----------|
118
- | **React 17+ JSX transform / composition** | `react/react-in-jsx-scope`, `react/jsx-props-no-spreading`, `react/jsx-max-depth`, `react/no-array-index-key`, `react/jsx-no-useless-fragment`, `react/jsx-no-constructed-context-values` |
170
+ | **React 17+ JSX transform / composition** | `react/react-in-jsx-scope`, `react/jsx-props-no-spreading`, `react/jsx-max-depth`, `react/no-array-index-key`, `react/jsx-no-constructed-context-values` |
119
171
  | **Modern ESM (named exports, side-effect imports, namespace imports, `node:` protocol)** | `import/no-named-export`, `import/prefer-default-export`, `import/group-exports`, `import/exports-last`, `import/no-anonymous-default-export`, `import/no-nodejs-modules`, `import/no-unassigned-import`, `import/no-namespace`, `import/max-dependencies`, `import/first`, `import/consistent-type-specifier-style` |
120
172
  | **Pedantic style preferences** | `no-magic-numbers`, `no-ternary`, `no-inline-comments`, `capitalized-comments`, `arrow-body-style`, `func-style`, `func-names`, `init-declarations`, `no-inferrable-types`, `prefer-destructuring`, `no-negated-condition`, `no-continue`, `parameter-properties`, `max-statements`, `max-lines-per-function`, `id-length` |
121
173
  | **Unicorn overreach** | `unicorn/prefer-global-this`, `unicorn/no-useless-undefined`, `unicorn/no-nested-ternary`, `unicorn/explicit-length-check`, `unicorn/custom-error-definition`, `unicorn/no-zero-fractions`, `unicorn/escape-case`, `unicorn/no-array-callback-reference`, `unicorn/no-array-for-each`, `unicorn/no-array-reduce` |
package/bin/init.js CHANGED
@@ -34,7 +34,9 @@ if (command !== "init") {
34
34
  const target = resolve("oxlint.config.ts");
35
35
 
36
36
  if (existsSync(target) && !values.force) {
37
- process.stderr.write("oxlint.config.ts already exists. Re-run with --force.\n");
37
+ process.stderr.write(
38
+ "oxlint.config.ts already exists. Re-run with --force.\n",
39
+ );
38
40
  process.exit(1);
39
41
  }
40
42
 
package/index.js CHANGED
@@ -1,12 +1,20 @@
1
- import { defineConfig } from 'oxlint';
1
+ import { defineConfig } from "oxlint";
2
2
 
3
3
  export default defineConfig({
4
+ // Bulk-enable oxlint categories as errors.
5
+ // `restriction` is intentionally excluded — oxlint docs warn against enabling it as a whole
6
+ // since those rules forbid valid language features. We cherry-pick restriction rules below.
4
7
  categories: {
5
- correctness: 'error',
6
- pedantic: 'error',
7
- perf: 'error',
8
- style: 'error',
9
- suspicious: 'error',
8
+ // Outright bugs: unreachable code, constant conditions, invalid regex, etc.
9
+ correctness: "error",
10
+ // Style nitpicks that routinely prevent real bugs (strict booleans, unsafe any, etc.).
11
+ pedantic: "error",
12
+ // Patterns with measurable runtime cost.
13
+ perf: "error",
14
+ // Non-functional consistency — deterministic, no judgement calls.
15
+ style: "error",
16
+ // Code that's probably wrong but not provably so.
17
+ suspicious: "error",
10
18
  },
11
19
  env: {
12
20
  browser: true,
@@ -14,284 +22,519 @@ export default defineConfig({
14
22
  es2024: true,
15
23
  node: true,
16
24
  },
25
+ // JS-plugin bridge for ESLint plugins oxlint doesn't yet implement natively.
17
26
  jsPlugins: [
18
- 'eslint-plugin-no-only-tests',
19
- 'eslint-plugin-perfectionist',
20
- 'eslint-plugin-unused-imports',
21
- { name: 'react-hooks-js', specifier: 'eslint-plugin-react-hooks' },
27
+ "eslint-plugin-no-only-tests",
28
+ "eslint-plugin-perfectionist",
29
+ "eslint-plugin-unused-imports",
30
+ // React Compiler's own lint rules — stricter than classic react-hooks;
31
+ // enforce the purity/memoization/gating invariants the compiler needs.
32
+ { name: "react-hooks-js", specifier: "eslint-plugin-react-hooks" },
22
33
  ],
23
34
  overrides: [
24
- // TypeScript files — disable rules handled natively by the TS compiler
35
+ // TypeScript files — turn off rules the TS compiler already enforces via its type system.
36
+ // Running them again would be pure overhead and occasionally produce false positives.
25
37
  {
26
- files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
38
+ files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
27
39
  rules: {
28
- 'constructor-super': 'off',
29
- 'getter-return': 'off',
30
- 'no-class-assign': 'off',
31
- 'no-const-assign': 'off',
32
- 'no-dupe-class-members': 'off',
33
- 'no-dupe-keys': 'off',
34
- 'no-func-assign': 'off',
35
- 'no-import-assign': 'off',
36
- 'no-new-native-nonconstructor': 'off',
37
- 'no-obj-calls': 'off',
38
- 'no-redeclare': 'off',
39
- 'no-setter-return': 'off',
40
- 'no-this-before-super': 'off',
41
- 'no-undef': 'off',
42
- 'no-unreachable': 'off',
43
- 'no-unsafe-negation': 'off',
44
- 'no-with': 'off',
45
- 'prefer-rest-params': 'error',
46
- 'prefer-spread': 'error',
40
+ // TS enforces `super()` requirements via class hierarchy checks.
41
+ "constructor-super": "off",
42
+ // TS catches missing returns via the function's declared return type.
43
+ "getter-return": "off",
44
+ // Class bindings are const in TS — reassignment is a compile error.
45
+ "no-class-assign": "off",
46
+ // `const` bindings can't be reassigned — TS catches this.
47
+ "no-const-assign": "off",
48
+ // Duplicate class members produce a TS compile error.
49
+ "no-dupe-class-members": "off",
50
+ // Duplicate object keys are reported by TS.
51
+ "no-dupe-keys": "off",
52
+ // Function declarations are const-bound in TS.
53
+ "no-func-assign": "off",
54
+ // Imports are read-only bindings — TS catches reassignment.
55
+ "no-import-assign": "off",
56
+ // TS signatures prevent calling non-constructors with `new`.
57
+ "no-new-native-nonconstructor": "off",
58
+ // `Math()` / `JSON()` aren't callable per TS lib types.
59
+ "no-obj-calls": "off",
60
+ // Redeclaration is a TS compile error.
61
+ "no-redeclare": "off",
62
+ // TS enforces setter signatures must not return values.
63
+ "no-setter-return": "off",
64
+ // TS catches `this` before `super()` in derived constructors.
65
+ "no-this-before-super": "off",
66
+ // TS resolves all references — unresolved identifiers fail to compile.
67
+ "no-undef": "off",
68
+ // TS reports unreachable code as a diagnostic.
69
+ "no-unreachable": "off",
70
+ // TS narrows operators; `!a instanceof B` becomes a type error.
71
+ "no-unsafe-negation": "off",
72
+ // `with` statements aren't allowed in strict mode or TS at all.
73
+ "no-with": "off",
74
+ // Not type-checked by TS — prefer `...args` over `arguments`.
75
+ "prefer-rest-params": "error",
76
+ // Not type-checked by TS — prefer spread over `.apply()`.
77
+ "prefer-spread": "error",
47
78
  },
48
79
  },
49
- // Test files — relax strict rules that create noise in tests
80
+ // Test files — relax strict rules that generate noise in mocks, fixtures, describe blocks.
50
81
  {
51
82
  files: [
52
- '**/*.test.ts',
53
- '**/*.test.tsx',
54
- '**/*.spec.ts',
55
- '**/*.spec.tsx',
56
- '**/__tests__/**',
83
+ "**/*.test.ts",
84
+ "**/*.test.tsx",
85
+ "**/*.spec.ts",
86
+ "**/*.spec.tsx",
87
+ "**/__tests__/**",
57
88
  ],
58
89
  rules: {
59
- '@typescript-eslint/no-explicit-any': 'off',
60
- '@typescript-eslint/no-non-null-assertion': 'off',
61
- '@typescript-eslint/no-unsafe-argument': 'off',
62
- '@typescript-eslint/no-unsafe-assignment': 'off',
63
- '@typescript-eslint/no-unsafe-call': 'off',
64
- '@typescript-eslint/no-unsafe-member-access': 'off',
65
- '@typescript-eslint/no-unsafe-return': 'off',
66
- 'max-lines': 'off',
67
- 'max-lines-per-function': 'off',
68
- 'max-nested-callbacks': 'off',
69
- 'max-statements': 'off',
70
- 'no-empty-function': 'off',
90
+ // Mocks are commonly typed `any` for speed.
91
+ "@typescript-eslint/no-explicit-any": "off",
92
+ // Fixtures chain `!` + `??` for narrowing.
93
+ "@typescript-eslint/no-non-null-asserted-nullish-coalescing": "off",
94
+ // Fixtures narrow via `!` on known-populated test data.
95
+ "@typescript-eslint/no-non-null-assertion": "off",
96
+ // `jest.mock` / `vi.mock` factories use `require()`.
97
+ "@typescript-eslint/no-require-imports": "off",
98
+ // Mocks don't carry type info.
99
+ "@typescript-eslint/no-unsafe-argument": "off",
100
+ "@typescript-eslint/no-unsafe-assignment": "off",
101
+ "@typescript-eslint/no-unsafe-call": "off",
102
+ "@typescript-eslint/no-unsafe-member-access": "off",
103
+ "@typescript-eslint/no-unsafe-return": "off",
104
+ // Legacy `var x = require(...)` still appears in older test setups.
105
+ "@typescript-eslint/no-var-requires": "off",
106
+ // Mock implementations may return bare promises without `async`.
107
+ "@typescript-eslint/promise-function-async": "off",
108
+ // Test utilities can legitimately form small local cycles.
109
+ "import/no-cycle": "off",
110
+ // `describe`/`it` blocks legitimately exceed size limits.
111
+ "max-lines": "off",
112
+ "max-lines-per-function": "off",
113
+ "max-nested-callbacks": "off",
114
+ "max-statements": "off",
115
+ // Empty test stubs are valid placeholders.
116
+ "no-empty": "off",
117
+ // Empty mock implementations are common.
118
+ "no-empty-function": "off",
119
+ // Test helpers are often defined after the `describe` that uses them.
120
+ "no-use-before-define": "off",
71
121
  },
72
122
  },
73
- // Storybook stories — relax rules for component documentation
123
+ // Storybook stories — component documentation has its own idioms.
74
124
  {
75
- files: ['**/*.stories.ts', '**/*.stories.tsx'],
125
+ files: ["**/*.stories.ts", "**/*.stories.tsx"],
76
126
  rules: {
77
- 'no-console': 'off',
78
- 'react/no-multi-comp': 'off',
127
+ // `console.log` is a valid teaching tool in stories.
128
+ "no-console": "off",
129
+ // Stories legitimately export multiple component variants.
130
+ "react/no-multi-comp": "off",
79
131
  },
80
132
  },
81
- // Seed and migration files — allow console for CLI output
133
+ // Seed and migration scripts — one-shot CLI tools with log output.
82
134
  {
83
- files: ['**/seed.ts', '**/seed.js', '**/migrate.ts', '**/migrate.js'],
135
+ files: ["**/seed.ts", "**/seed.js", "**/migrate.ts", "**/migrate.js"],
84
136
  rules: {
85
- 'no-console': 'off',
137
+ // CLI tools print progress to stdout.
138
+ "no-console": "off",
86
139
  },
87
140
  },
88
- // Config files — relax rules for tooling configs
141
+ // CLI entry points — `process.exit` and console output are the whole point.
142
+ {
143
+ files: ["**/bin/**", "scripts/**"],
144
+ rules: {
145
+ "no-console": "off",
146
+ "unicorn/no-process-exit": "off",
147
+ },
148
+ },
149
+ // Config files — build tooling is allowed anonymous defaults and long files.
89
150
  {
90
151
  files: [
91
- '*.config.ts',
92
- '*.config.js',
93
- '*.config.mjs',
94
- '*.config.mts',
95
- '**/.storybook/**',
96
- 'vitest.config.*',
97
- 'playwright.config.*',
98
- 'tailwind.config.*',
99
- 'postcss.config.*',
100
- 'next.config.*',
152
+ "*.config.ts",
153
+ "*.config.js",
154
+ "*.config.mjs",
155
+ "*.config.mts",
156
+ "**/.storybook/**",
157
+ "vitest.config.*",
158
+ "playwright.config.*",
159
+ "tailwind.config.*",
160
+ "postcss.config.*",
161
+ "next.config.*",
101
162
  ],
102
163
  rules: {
103
- 'import-x/no-anonymous-default-export': 'off',
104
- 'max-lines': 'off',
164
+ // Tool configs are traditionally `export default {...}` with no name.
165
+ "import-x/no-anonymous-default-export": "off",
166
+ // Big configs (webpack, next) routinely exceed 400 lines.
167
+ "max-lines": "off",
105
168
  },
106
169
  },
107
- // E2E test fixtures — may not follow standard component rules
170
+ // Playwright/Cypress E2E fixtures — test harness code, not React components.
108
171
  {
109
- files: ['**/e2e/**/fixtures/**', '**/e2e/**/*.ts'],
172
+ files: ["**/e2e/**/fixtures/**", "**/e2e/**/*.ts"],
110
173
  rules: {
111
- 'react-hooks/rules-of-hooks': 'off',
174
+ // Playwright fixtures use hook-like names (`test.extend`) that trip the rule.
175
+ "react-hooks/rules-of-hooks": "off",
112
176
  },
113
177
  },
114
178
  ],
115
179
  plugins: [
116
- 'typescript',
117
- 'import',
118
- 'react',
119
- 'unicorn',
120
- 'jsx-a11y',
121
- 'promise',
122
- 'nextjs',
123
- 'oxc',
124
- 'node',
180
+ "typescript",
181
+ "import",
182
+ "react",
183
+ "unicorn",
184
+ "jsx-a11y",
185
+ "promise",
186
+ "nextjs",
187
+ "oxc",
188
+ "node",
125
189
  ],
126
190
  rules: {
127
191
  // TypeScript — custom options
128
- '@typescript-eslint/array-type': [
129
- 'error',
130
- {
131
- default: 'generic',
132
- },
133
- ],
134
- '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
192
+
193
+ // Prefer `Array<T>` over `T[]` — generic form scales to complex element types
194
+ // (`Array<() => void>` vs `(() => void)[]`) without extra parentheses.
195
+ "@typescript-eslint/array-type": ["error", { default: "generic" }],
196
+ // Force `type` over `interface` — `type` supports unions/intersections/mapped types
197
+ // and can't be accidentally merged via declaration merging.
198
+ "@typescript-eslint/consistent-type-definitions": ["error", "type"],
135
199
 
136
200
  // Core eslint — custom options
137
- 'eslint/no-duplicate-imports': ['error',
138
- {
139
- allowSeparateTypeImports: true,
140
- }
201
+
202
+ // Prevent duplicate import statements but still allow separating type-only imports
203
+ // from value imports (`import type { X } from 'x'; import { y } from 'x';`).
204
+ "eslint/no-duplicate-imports": [
205
+ "error",
206
+ { allowSeparateTypeImports: true },
141
207
  ],
142
- 'no-warning-comments': [
143
- 'error',
144
- {
145
- terms: ['@nocommit'],
146
- },
208
+ // Block `@nocommit` markers from reaching main — a hard stop for WIP code.
209
+ "no-warning-comments": ["error", { terms: ["@nocommit"] }],
210
+
211
+ // Restriction — core eslint (cherry-picked from the 90 rules in `restriction`)
212
+
213
+ // Always use braces — prevents the dangling-else bug and ambiguous single-line bodies.
214
+ curly: "error",
215
+ // Require a `default` case in switch — forces explicit handling of unknown values.
216
+ "default-case": "error",
217
+ // Ban `==` — type coercion is the single largest source of surprise in JS.
218
+ eqeqeq: "error",
219
+ // Keep get/set pairs adjacent — readers expect them together.
220
+ "grouped-accessor-pairs": "error",
221
+ // One class per file — keeps files focused and imports unambiguous.
222
+ "max-classes-per-file": ["error", { max: 1 }],
223
+ // Cap nesting at 4 — deeper usually means an extraction is overdue.
224
+ "max-depth": ["error", { max: 4 }],
225
+ // 400-line ceiling (comments + blanks excluded) — force splitting by concern.
226
+ "max-lines": [
227
+ "error",
228
+ { max: 400, skipBlankLines: true, skipComments: true },
147
229
  ],
230
+ // Deeply nested callbacks are a refactor smell — convert to async/await or extract.
231
+ "max-nested-callbacks": ["error", { max: 3 }],
232
+ // More than 4 positional params → pass an options object for readability.
233
+ "max-params": ["error", { max: 4 }],
234
+ // `alert()` is never production UI — use a toast/modal primitive.
235
+ "no-alert": "error",
236
+ // `.caller`/`.arguments.callee` are deprecated and strict-mode errors.
237
+ "no-caller": "error",
238
+ // Ship with a real logger, not `console.*` — overrides above allow CLIs/stories.
239
+ "no-console": "error",
240
+ // Empty blocks are either a forgotten TODO or a logic bug.
241
+ "no-empty": "error",
242
+ // Banned as a security + performance anti-pattern; defeats static analysis.
243
+ "no-eval": "error",
244
+ // Mutating `Array.prototype` etc. breaks every library that trusts the globals.
245
+ "no-extend-native": "error",
246
+ // Be explicit with conversions — `Number(x)` over `+x`, except `!!x` (idiomatic).
247
+ "no-implicit-coercion": ["error", { allow: ["!!"] }],
248
+ // Runtime code generation from strings — same security and performance issues as dynamic exec.
249
+ "no-new-func": "error",
250
+ // `new String/Number/Boolean(...)` creates boxed objects that fail `===`.
251
+ "no-new-wrappers": "error",
252
+ // Prefer `{}` literal over `new Object()`.
253
+ "no-object-constructor": "error",
254
+ // Params are inputs, not locals — reassigning them hides the original value.
255
+ "no-param-reassign": "error",
256
+ // `__proto__` is deprecated — use `Object.getPrototypeOf`.
257
+ "no-proto": "error",
258
+ // `return a = b` looks like a typo for `==`.
259
+ "no-return-assign": "error",
260
+ // `javascript:` URLs are XSS vectors.
261
+ "no-script-url": "error",
262
+ // Shadowing outer-scope names hides bugs during refactors.
263
+ "no-shadow": "error",
264
+ // Always throw `Error` objects — literals have no stack trace.
265
+ "no-throw-literal": "error",
266
+ // Catch TDZ bugs from using `let`/`const` before their declaration.
267
+ "no-use-before-define": "error",
268
+ // `var` has function scope — use `let`/`const` (block-scoped).
269
+ "no-var": "error",
270
+ // Allow `void promise` to explicitly ignore a floating promise — a real pattern.
271
+ "no-void": ["error", { allowAsStatement: true }],
272
+ // Reject promises with `Error` instances so stack traces survive.
273
+ "prefer-promise-reject-errors": "error",
274
+ // Template literals > string concatenation — fewer escape/coercion bugs.
275
+ "prefer-template": "error",
276
+
277
+ // Restriction — TypeScript
278
+ // Highest-leverage rules for AI-generated code: they block the escape hatches
279
+ // (`any`, `!`, `require`) that AI reaches for when it can't figure out the right type.
280
+
281
+ // `delete obj[dynamicKey]` is usually a type-lie — the key may not be optional.
282
+ "@typescript-eslint/no-dynamic-delete": "error",
283
+ // `{}` means "any non-nullish value" — almost never what people mean.
284
+ "@typescript-eslint/no-empty-object-type": "error",
285
+ // The #1 AI tell. Use `unknown` + narrowing or a real type.
286
+ "@typescript-eslint/no-explicit-any": "error",
287
+ // Ensure `import type` has no runtime side effects — keeps bundles clean.
288
+ "@typescript-eslint/no-import-type-side-effects": "error",
289
+ // `void` only makes sense as a return type or a generic constraint.
290
+ "@typescript-eslint/no-invalid-void-type": "error",
291
+ // `x! ?? fallback` is always a bug — the `!` already asserts non-null.
292
+ "@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error",
293
+ // Non-null `!` lies to the checker. Narrow properly or throw explicitly.
294
+ "@typescript-eslint/no-non-null-assertion": "error",
295
+ // Use ESM `import` — `require()` breaks tree-shaking and type inference.
296
+ "@typescript-eslint/no-require-imports": "error",
297
+ "@typescript-eslint/no-var-requires": "error",
298
+ // Promise-returning functions must be `async` — guarantees thrown errors become rejections.
299
+ "@typescript-eslint/promise-function-async": "error",
300
+ // `.catch((err) => ...)` — `err` should be `unknown`, not `any` (strict TS behavior).
301
+ "@typescript-eslint/use-unknown-in-catch-callback-variable": "error",
302
+
303
+ // Restriction — React
304
+
305
+ // `<button>` defaults to `type="submit"` — silently submits enclosing forms. Must be explicit.
306
+ "react/button-has-type": "error",
307
+ // Raw HTML insertion in JSX is an XSS vector — require a deliberate opt-out.
308
+ "react/no-danger": "error",
309
+ // Catches typos like `class=` or `tabindex=` in JSX.
310
+ "react/no-unknown-property": "error",
311
+
312
+ // Restriction — import graph
313
+
314
+ // Circular imports produce `undefined` exports at runtime — a real-world crash source.
315
+ "import/no-cycle": "error",
316
+
317
+ // Restriction — unicorn (modern JS + anti-escape-hatch)
318
+
319
+ // Blanket `// eslint-disable` without a rule name is the universal AI escape hatch.
320
+ "unicorn/no-abusive-eslint-disable": "error",
321
+ // Direct `document.cookie` access — use a cookie library for encoding/security.
322
+ "unicorn/no-document-cookie": "error",
323
+ // `process.exit()` skips `finally` blocks and async flushes. CLI override above handles CLIs.
324
+ "unicorn/no-process-exit": "error",
325
+ // `Math.trunc(x)` over `x | 0` — clearer intent, correct for numbers outside Int32 range.
326
+ "unicorn/prefer-modern-math-apis": "error",
327
+ // `import { fs } from 'node:fs'` — the `node:` prefix disambiguates from npm packages.
328
+ "unicorn/prefer-node-protocol": "error",
329
+ // `Number.isNaN(x)` over `isNaN(x)` — the global coerces strings before checking.
330
+ "unicorn/prefer-number-properties": "error",
331
+
332
+ // Restriction — promise / node
333
+
334
+ // Callback-style APIs: always handle the `err` argument.
335
+ "node/handle-callback-err": "error",
336
+ // `new require(...)` is meaningless — require is a function, not a constructor.
337
+ "node/no-new-require": "error",
338
+ // String-concatenating paths breaks on Windows — use `path.join`.
339
+ "node/no-path-concat": "error",
340
+ // Every promise chain must end with `.catch()` or `return` — unhandled rejections are silent bugs.
341
+ "promise/catch-or-return": "error",
148
342
 
149
-
150
-
151
- // Restriction — cherry-picked (not bulk-enabled)
152
- curly: 'error',
153
- 'default-case': 'error',
154
- eqeqeq: 'error',
155
- 'grouped-accessor-pairs': 'error',
156
- 'max-classes-per-file': ['error', { max: 1 }],
157
- 'max-depth': ['error', { max: 4 }],
158
- 'max-lines': ['error', { max: 400, skipBlankLines: true, skipComments: true }],
159
- 'max-nested-callbacks': ['error', { max: 3 }],
160
- 'max-params': ['error', { max: 4 }],
161
- 'no-alert': 'error',
162
- 'no-caller': 'error',
163
- 'no-console': 'error',
164
- 'no-eval': 'error',
165
- 'no-extend-native': 'error',
166
- 'no-implicit-coercion': ['error', { allow: ['!!'] }],
167
- 'no-new-func': 'error',
168
- 'no-new-wrappers': 'error',
169
- 'no-object-constructor': 'error',
170
- 'no-param-reassign': 'error',
171
- 'no-proto': 'error',
172
- 'no-return-assign': 'error',
173
- 'no-script-url': 'error',
174
- 'no-shadow': 'error',
175
- 'no-throw-literal': 'error',
176
- 'no-void': ['error', { allowAsStatement: true }],
177
- 'prefer-promise-reject-errors': 'error',
178
- 'prefer-template': 'error',
343
+ // Restriction — a11y
344
+
345
+ // "click here", "read more", "link" — ambiguous link text fails screen readers.
346
+ "jsx-a11y/anchor-ambiguous-text": "error",
179
347
 
180
348
  // Disabled — incompatible with React 17+ JSX transform and common composition patterns
181
- 'react/jsx-max-depth': 'off',
182
- 'react/jsx-no-constructed-context-values': 'off',
183
- 'react/jsx-no-useless-fragment': 'off',
184
- 'react/jsx-props-no-spreading': 'off',
185
- 'react/no-array-index-key': 'off',
186
- 'react/react-in-jsx-scope': 'off',
187
-
188
- // Disabled — modern ESM uses named exports; side-effect imports (CSS, polyfills, node: protocol) are legitimate; namespace imports are canonical for Sentry, Prisma, etc.
189
- 'import/consistent-type-specifier-style': 'off',
190
- 'import/exports-last': 'off',
191
- 'import/first': 'off',
192
- 'import/group-exports': 'off',
193
- 'import/max-dependencies': 'off',
194
- 'import/no-anonymous-default-export': 'off',
195
- 'import/no-named-export': 'off',
196
- 'import/no-namespace': 'off',
197
- 'import/no-nodejs-modules': 'off',
198
- 'import/no-unassigned-import': 'off',
199
- 'import/prefer-default-export': 'off',
349
+
350
+ // JSX depth is constrained by composition, not by a magic number.
351
+ "react/jsx-max-depth": "off",
352
+ // React Compiler (`react-hooks-js`) handles memoization — no need to pre-extract context values.
353
+ "react/jsx-no-constructed-context-values": "off",
354
+ // Prop spreading is a valid composition pattern with proper types.
355
+ "react/jsx-props-no-spreading": "off",
356
+ // Static lists have stable indices — false positives outweigh real catches.
357
+ "react/no-array-index-key": "off",
358
+ // React 17+ automatic JSX runtime — no import needed.
359
+ "react/react-in-jsx-scope": "off",
360
+
361
+ // Disabled — import rules that fight modern ESM / legitimate patterns
362
+
363
+ // Mixing `import type` styles in one project is fine.
364
+ "import/consistent-type-specifier-style": "off",
365
+ // "Exports at end" is style preference, not correctness.
366
+ "import/exports-last": "off",
367
+ // Side-effect imports (CSS, polyfills) must come first — conflicts with this rule.
368
+ "import/first": "off",
369
+ // Grouping exports isn't meaningful with named exports.
370
+ "import/group-exports": "off",
371
+ // Dependency count caps don't catch real problems.
372
+ "import/max-dependencies": "off",
373
+ // Anonymous default exports are normal in configs and utilities.
374
+ "import/no-anonymous-default-export": "off",
375
+ // Named exports are the modern ESM norm.
376
+ "import/no-named-export": "off",
377
+ // `import * as Sentry from '@sentry/node'` is the canonical API for Sentry/Prisma/etc.
378
+ "import/no-namespace": "off",
379
+ // Node built-ins (`node:fs`) are legitimate in Node code.
380
+ "import/no-nodejs-modules": "off",
381
+ // Side-effect imports for CSS and polyfills are required.
382
+ "import/no-unassigned-import": "off",
383
+ // Modern codebases prefer named exports; default exports are the exception.
384
+ "import/prefer-default-export": "off",
200
385
 
201
386
  // Disabled — pedantic style preferences that fight day-to-day clarity
202
- 'arrow-body-style': 'off',
203
- 'capitalized-comments': 'off',
204
- 'func-names': 'off',
205
- 'func-style': 'off',
206
- 'id-length': 'off',
207
- 'init-declarations': 'off',
208
- 'max-lines-per-function': 'off',
209
- 'max-statements': 'off',
210
- 'no-continue': 'off',
211
- 'no-inferrable-types': 'off',
212
- 'no-inline-comments': 'off',
213
- 'no-magic-numbers': 'off',
214
- 'no-negated-condition': 'off',
215
- 'no-ternary': 'off',
216
- 'parameter-properties': 'off',
217
- 'prefer-destructuring': 'off',
387
+
388
+ // Block bodies are sometimes clearer (breakpoints, multi-statement arrows).
389
+ "arrow-body-style": "off",
390
+ // Capitalization of comments is noise.
391
+ "capitalized-comments": "off",
392
+ // Anonymous arrows are canonical in modern code.
393
+ "func-names": "off",
394
+ // Mixing declarations and expressions is a project's call.
395
+ "func-style": "off",
396
+ // `i`, `x`, `n` are fine in small scopes.
397
+ "id-length": "off",
398
+ // `let x; ... x = compute();` is a real pattern.
399
+ "init-declarations": "off",
400
+ // Too blunt — well-factored functions can still be long.
401
+ "max-lines-per-function": "off",
402
+ // Too blunt — modern async code has many statements by design.
403
+ "max-statements": "off",
404
+ // `continue` is often clearer than extra nesting.
405
+ "no-continue": "off",
406
+ // Explicit types on simple assignments is a style choice, not an error.
407
+ "no-inferrable-types": "off",
408
+ // Inline `// ...` comments are sometimes the clearest option.
409
+ "no-inline-comments": "off",
410
+ // Too noisy — most numbers in UI code have obvious meaning.
411
+ "no-magic-numbers": "off",
412
+ // `if (!done)` is often clearer than `if (done === false)`.
413
+ "no-negated-condition": "off",
414
+ // Ternaries are fine in expressions.
415
+ "no-ternary": "off",
416
+ // TS parameter properties (`constructor(private x: T)`) are a valid shorthand.
417
+ "parameter-properties": "off",
418
+ // Destructuring isn't always clearer — `arr[0]` can be better than `const [first] = arr`.
419
+ "prefer-destructuring": "off",
218
420
 
219
421
  // Disabled — unicorn rules that overreach into legitimate patterns
220
- 'unicorn/custom-error-definition': 'off',
221
- 'unicorn/escape-case': 'off',
222
- 'unicorn/explicit-length-check': 'off',
223
- 'unicorn/no-array-callback-reference': 'off',
224
- 'unicorn/no-array-for-each': 'off',
225
- 'unicorn/no-array-reduce': 'off',
226
- 'unicorn/no-nested-ternary': 'off',
227
- 'unicorn/no-useless-undefined': 'off',
228
- 'unicorn/no-zero-fractions': 'off',
229
- 'unicorn/prefer-global-this': 'off',
230
-
231
- // Disabled — async/await is preferred but raw Promise constructors and callbacks remain idiomatic in many APIs (event emitters, streams, callback adapters)
232
- 'promise/avoid-new': 'off',
233
- 'promise/param-names': 'off',
234
- 'promise/prefer-await-to-callbacks': 'off',
235
-
236
- // Style overrides — handled by perfectionist/oxfmt
237
- 'sort-imports': 'off',
238
- 'sort-keys': 'off',
239
- 'sort-vars': 'off',
240
-
241
- // Unicorn — enforce kebab-case but allow Next.js bracket patterns
242
- // Next.js dynamic routes ([slug], [...catchAll]) and special files (_app, _document)
243
- 'unicorn/filename-case': [
244
- 'error',
245
- {
246
- case: 'kebabCase',
247
- ignore: [String.raw`\[.*\]`, '^_.*'],
248
- },
249
- ],
250
- 'unicorn/no-null': 'off',
251
422
 
252
- // Perfectionist — sorting
253
- 'perfectionist/sort-enums': [
254
- 'error',
423
+ // Over-prescribes a specific custom-error shape.
424
+ "unicorn/custom-error-definition": "off",
425
+ // Escape-case fights non-English strings and regex literals.
426
+ "unicorn/escape-case": "off",
427
+ // `if (arr.length)` is a valid idiom for "has elements".
428
+ "unicorn/explicit-length-check": "off",
429
+ // `.map(fn)` is the canonical callback pattern.
430
+ "unicorn/no-array-callback-reference": "off",
431
+ // `forEach` is fine for side effects.
432
+ "unicorn/no-array-for-each": "off",
433
+ // `reduce` is fine — the rule is ideological, not mechanical.
434
+ "unicorn/no-array-reduce": "off",
435
+ // Nested ternaries are sometimes the clearest expression.
436
+ "unicorn/no-nested-ternary": "off",
437
+ // `return undefined` is sometimes explicit and intentional.
438
+ "unicorn/no-useless-undefined": "off",
439
+ // `1.0` can signal "this is a floating-point value" in domain code.
440
+ "unicorn/no-zero-fractions": "off",
441
+ // `window` and `self` are fine in clearly-browser code.
442
+ "unicorn/prefer-global-this": "off",
443
+
444
+ // Disabled — raw Promise constructors and callbacks stay idiomatic for adapters
445
+
446
+ // `new Promise` is required when wrapping event emitters, streams, or callback APIs.
447
+ "promise/avoid-new": "off",
448
+ // `(resolve, reject)` is the idiomatic parameter naming.
449
+ "promise/param-names": "off",
450
+ // Event emitters and streams are callback-based and stay that way.
451
+ "promise/prefer-await-to-callbacks": "off",
452
+
453
+ // Style overrides — handled deterministically by perfectionist/oxfmt
454
+ "sort-imports": "off",
455
+ "sort-keys": "off",
456
+ "sort-vars": "off",
457
+
458
+ // Unicorn — filename-case
459
+
460
+ // kebab-case matches the CLAUDE.md convention, with exceptions for:
461
+ // - Next.js dynamic routes: `[slug].tsx`, `[...catchAll].tsx`
462
+ // - Next.js special files: `_app.tsx`, `_document.tsx`
463
+ "unicorn/filename-case": [
464
+ "error",
255
465
  {
256
- partitionByComment: true,
257
- sortByValue: 'always',
466
+ case: "kebabCase",
467
+ ignore: [String.raw`\[.*\]`, "^_.*"],
258
468
  },
259
469
  ],
260
- 'perfectionist/sort-heritage-clauses': 'error',
261
- 'perfectionist/sort-jsx-props': 'error',
262
- 'perfectionist/sort-object-types': 'error',
263
- 'perfectionist/sort-objects': [
264
- 'error',
265
- {
266
- partitionByComment: true,
267
- },
470
+ // `null` is a legitimate, distinct value — don't force `undefined` everywhere.
471
+ "unicorn/no-null": "off",
472
+
473
+ // Perfectionist — deterministic sorting keeps diffs stable and review-friendly
474
+
475
+ // Enums sorted by value make intent + ordering obvious; partitionByComment preserves groupings.
476
+ "perfectionist/sort-enums": [
477
+ "error",
478
+ { partitionByComment: true, sortByValue: "always" },
268
479
  ],
480
+ // `class Foo extends A, B` — alphabetical for stable diffs.
481
+ "perfectionist/sort-heritage-clauses": "error",
482
+ // JSX prop order is a huge diff-noise source — sort it.
483
+ "perfectionist/sort-jsx-props": "error",
484
+ // Type fields sorted alphabetically for scannability.
485
+ "perfectionist/sort-object-types": "error",
486
+ // Object keys alphabetical; partitionByComment preserves intentional grouping.
487
+ "perfectionist/sort-objects": ["error", { partitionByComment: true }],
488
+
489
+ // React Compiler (react-hooks-js) — enforce the invariants the compiler needs
490
+ // to safely auto-memoize your components.
269
491
 
270
- // React Compiler (react-hooks-js)
271
- 'react-hooks-js/component-hook-factories': 'error',
272
- 'react-hooks-js/config': 'error',
273
- 'react-hooks-js/error-boundaries': 'error',
274
- 'react-hooks-js/gating': 'error',
275
- 'react-hooks-js/globals': 'error',
276
- 'react-hooks-js/immutability': 'error',
277
- 'react-hooks-js/incompatible-library': 'error',
278
- 'react-hooks-js/preserve-manual-memoization': 'error',
279
- 'react-hooks-js/purity': 'error',
280
- 'react-hooks-js/refs': 'error',
281
- 'react-hooks-js/set-state-in-effect': 'error',
282
- 'react-hooks-js/set-state-in-render': 'error',
283
- 'react-hooks-js/static-components': 'error',
284
- 'react-hooks-js/unsupported-syntax': 'error',
285
- 'react-hooks-js/use-memo': 'error',
286
-
287
- // React hooks
288
- 'react-hooks/exhaustive-deps': 'error',
289
- 'react-hooks/rules-of-hooks': 'error',
290
-
291
- // No only tests
292
- 'no-only-tests/no-only-tests': 'error',
492
+ // Hooks can't be created inside components — they must be module-level.
493
+ "react-hooks-js/component-hook-factories": "error",
494
+ // Validates the plugin's own config shape.
495
+ "react-hooks-js/config": "error",
496
+ // Error boundaries have specific invariants the compiler respects.
497
+ "react-hooks-js/error-boundaries": "error",
498
+ // Feature-flag gating must be consistent so the compiler can reason about it.
499
+ "react-hooks-js/gating": "error",
500
+ // No global mutation during render — breaks compiler's purity assumptions.
501
+ "react-hooks-js/globals": "error",
502
+ // Don't mutate props or state — compiler assumes immutability.
503
+ "react-hooks-js/immutability": "error",
504
+ // Flags libraries the compiler can't safely optimize around.
505
+ "react-hooks-js/incompatible-library": "error",
506
+ // If you wrote `useMemo`/`useCallback`, keep them — the compiler preserves your intent.
507
+ "react-hooks-js/preserve-manual-memoization": "error",
508
+ // Render must be pure — no side effects, no I/O, no randomness.
509
+ "react-hooks-js/purity": "error",
510
+ // Refs belong in effects and handlers, not render.
511
+ "react-hooks-js/refs": "error",
512
+ // Setting state inside an effect must be carefully guarded.
513
+ "react-hooks-js/set-state-in-effect": "error",
514
+ // Never set state during render — infinite loop.
515
+ "react-hooks-js/set-state-in-render": "error",
516
+ // Prefer static component definitions over dynamic factories.
517
+ "react-hooks-js/static-components": "error",
518
+ // Syntax (decorators, specific private-field patterns) the compiler can't analyze.
519
+ "react-hooks-js/unsupported-syntax": "error",
520
+ // Enforces the modern `useMemo` shape the compiler expects.
521
+ "react-hooks-js/use-memo": "error",
522
+
523
+ // React hooks — the classic rules, still necessary even with the compiler
524
+
525
+ // Dependency array must include every value referenced in the effect.
526
+ "react-hooks/exhaustive-deps": "error",
527
+ // Hooks only at the top level, only from React functions — never in conditions/loops.
528
+ "react-hooks/rules-of-hooks": "error",
529
+
530
+ // Testing
531
+
532
+ // `.only()` in a test file skips every other test — must never land on main.
533
+ "no-only-tests/no-only-tests": "error",
293
534
 
294
535
  // Unused imports
295
- 'unused-imports/no-unused-imports': 'error',
536
+
537
+ // Dead imports bloat bundles and mislead readers — remove them.
538
+ "unused-imports/no-unused-imports": "error",
296
539
  },
297
540
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-config-awesomeness",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Oxlint config with sensible defaults.",
5
5
  "keywords": [
6
6
  "autofix",