@activatus/code-standards 0.0.0-stage → 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 (180) hide show
  1. package/README.md +199 -2
  2. package/docs/Guides/_category_.json +4 -0
  3. package/docs/Guides/component-library-workflow.md +182 -0
  4. package/docs/Guides/migrating.md +161 -0
  5. package/docs/Guides/setup.md +158 -0
  6. package/docs/Standards/_category_.json +4 -0
  7. package/docs/Standards/agent-skills.md +125 -0
  8. package/docs/Standards/agent-specs.md +74 -0
  9. package/docs/Standards/direnv.mdx +249 -0
  10. package/docs/Standards/documentation.md +314 -0
  11. package/docs/Standards/editorconfig.md +74 -0
  12. package/docs/Standards/eslint.md +900 -0
  13. package/docs/Standards/human-readable-names.md +274 -0
  14. package/docs/Standards/lint-errors.md +171 -0
  15. package/docs/Standards/prettier.md +106 -0
  16. package/docs/Standards/readable-code.md +570 -0
  17. package/docs/Standards/stylelint.md +193 -0
  18. package/docs/Standards/styling.md +290 -0
  19. package/docs/Standards/tech-stack.md +158 -0
  20. package/docs/Structure/_category_.json +4 -0
  21. package/docs/Structure/api-routes.md +118 -0
  22. package/docs/Structure/component-organization.md +526 -0
  23. package/docs/Structure/folder-structure.md +366 -0
  24. package/docs/Structure/import-organization.md +158 -0
  25. package/docs/Structure/naming-conventions.md +334 -0
  26. package/docs/design-system-components.md +117 -0
  27. package/docs/intro.md +44 -0
  28. package/docs/ontology.md +127 -0
  29. package/docs/templates.mdx +396 -0
  30. package/docs/version-history.md +338 -0
  31. package/package.json +60 -3
  32. package/scripts/renameSymbol.mjs +188 -0
  33. package/static/templates/.editorconfig +25 -0
  34. package/static/templates/.envrc +25 -0
  35. package/static/templates/.nvmrc +1 -0
  36. package/static/templates/AGENTS.md +163 -0
  37. package/static/templates/decision-doc.md +19 -0
  38. package/static/templates/docs/ai/agent-harness.md +17 -0
  39. package/static/templates/docs/ai/agents.md +17 -0
  40. package/static/templates/docs/ai/architecture.md +17 -0
  41. package/static/templates/docs/ai/context.md +17 -0
  42. package/static/templates/docs/ai/model-routing.md +17 -0
  43. package/static/templates/docs/ai/models.md +17 -0
  44. package/static/templates/docs/ai/retrieval.md +17 -0
  45. package/static/templates/docs/ai/tools.md +17 -0
  46. package/static/templates/docs/architecture/authentication.md +17 -0
  47. package/static/templates/docs/architecture/backend.md +17 -0
  48. package/static/templates/docs/architecture/background-jobs.md +17 -0
  49. package/static/templates/docs/architecture/backups.md +17 -0
  50. package/static/templates/docs/architecture/caching.md +17 -0
  51. package/static/templates/docs/architecture/ci-cd.md +17 -0
  52. package/static/templates/docs/architecture/disaster-recovery.md +17 -0
  53. package/static/templates/docs/architecture/environments.md +17 -0
  54. package/static/templates/docs/architecture/frontend.md +17 -0
  55. package/static/templates/docs/architecture/hosting.md +17 -0
  56. package/static/templates/docs/architecture/observability.md +17 -0
  57. package/static/templates/docs/architecture/realtime.md +17 -0
  58. package/static/templates/docs/architecture/search.md +17 -0
  59. package/static/templates/docs/architecture/services.md +17 -0
  60. package/static/templates/docs/architecture/state-management.md +17 -0
  61. package/static/templates/docs/architecture/storage.md +17 -0
  62. package/static/templates/docs/architecture/system.md +17 -0
  63. package/static/templates/docs/business/buying-trigger.md +17 -0
  64. package/static/templates/docs/business/customer-value.md +17 -0
  65. package/static/templates/docs/business/customer.md +17 -0
  66. package/static/templates/docs/business/defensibility.md +17 -0
  67. package/static/templates/docs/business/growth.md +17 -0
  68. package/static/templates/docs/business/pricing.md +17 -0
  69. package/static/templates/docs/business/revenue-model.md +17 -0
  70. package/static/templates/docs/business/unit-economics.md +17 -0
  71. package/static/templates/docs/data/analytics.md +17 -0
  72. package/static/templates/docs/data/audit.md +17 -0
  73. package/static/templates/docs/data/database.md +17 -0
  74. package/static/templates/docs/data/indexing.md +17 -0
  75. package/static/templates/docs/data/migrations.md +17 -0
  76. package/static/templates/docs/data/model.md +17 -0
  77. package/static/templates/docs/data/retention.md +17 -0
  78. package/static/templates/docs/data/schema.md +17 -0
  79. package/static/templates/docs/deployment/approval.md +17 -0
  80. package/static/templates/docs/deployment/live-verification.md +17 -0
  81. package/static/templates/docs/deployment/process.md +17 -0
  82. package/static/templates/docs/deployment/rollback.md +17 -0
  83. package/static/templates/docs/design/design-system.md +17 -0
  84. package/static/templates/docs/design/errors.md +17 -0
  85. package/static/templates/docs/design/feedback.md +17 -0
  86. package/static/templates/docs/design/interactions.md +17 -0
  87. package/static/templates/docs/design/layout.md +17 -0
  88. package/static/templates/docs/design/navigation.md +17 -0
  89. package/static/templates/docs/design/principles.md +17 -0
  90. package/static/templates/docs/design/responsive.md +17 -0
  91. package/static/templates/docs/development/branching.md +17 -0
  92. package/static/templates/docs/development/code-quality.md +17 -0
  93. package/static/templates/docs/development/coding-standards.md +21 -0
  94. package/static/templates/docs/development/commits.md +17 -0
  95. package/static/templates/docs/development/merge-strategy.md +17 -0
  96. package/static/templates/docs/development/process.md +17 -0
  97. package/static/templates/docs/development/pull-requests.md +17 -0
  98. package/static/templates/docs/experience/accessibility.md +17 -0
  99. package/static/templates/docs/experience/aha-moment.md +17 -0
  100. package/static/templates/docs/experience/anti-experience.md +17 -0
  101. package/static/templates/docs/experience/friction.md +17 -0
  102. package/static/templates/docs/experience/outcomes.md +17 -0
  103. package/static/templates/docs/experience/principles.md +17 -0
  104. package/static/templates/docs/experience/retention.md +17 -0
  105. package/static/templates/docs/experience/sharing.md +17 -0
  106. package/static/templates/docs/overview/how-it-works.md +17 -0
  107. package/static/templates/docs/overview/what-is-it.md +17 -0
  108. package/static/templates/docs/overview/writing-docs.md +16 -0
  109. package/static/templates/docs/pitch/alternatives.md +17 -0
  110. package/static/templates/docs/pitch/assumptions.md +17 -0
  111. package/static/templates/docs/pitch/audience.md +17 -0
  112. package/static/templates/docs/pitch/concept.md +17 -0
  113. package/static/templates/docs/pitch/differentiators.md +17 -0
  114. package/static/templates/docs/pitch/outcomes.md +17 -0
  115. package/static/templates/docs/pitch/problem.md +17 -0
  116. package/static/templates/docs/pitch/product-market-fit.md +17 -0
  117. package/static/templates/docs/pitch/success-criteria.md +17 -0
  118. package/static/templates/docs/pitch/validation.md +17 -0
  119. package/static/templates/docs/pitch/value-proposition.md +17 -0
  120. package/static/templates/docs/pitch/vision.md +17 -0
  121. package/static/templates/docs/scope/core.md +17 -0
  122. package/static/templates/docs/scope/exclusions.md +17 -0
  123. package/static/templates/docs/scope/future.md +17 -0
  124. package/static/templates/docs/scope/launch.md +17 -0
  125. package/static/templates/docs/scope/post-launch.md +17 -0
  126. package/static/templates/docs/scope/priorities.md +17 -0
  127. package/static/templates/docs/scope/quality-bar.md +17 -0
  128. package/static/templates/docs/security/audit.md +17 -0
  129. package/static/templates/docs/security/data-access.md +17 -0
  130. package/static/templates/docs/security/logging.md +17 -0
  131. package/static/templates/docs/security/privacy.md +17 -0
  132. package/static/templates/docs/security/secrets.md +17 -0
  133. package/static/templates/docs/security/threat-model.md +17 -0
  134. package/static/templates/docs/testing/end-to-end.md +17 -0
  135. package/static/templates/docs/testing/integration.md +17 -0
  136. package/static/templates/docs/testing/regression.md +17 -0
  137. package/static/templates/docs/testing/security.md +17 -0
  138. package/static/templates/docs/testing/strategy.md +17 -0
  139. package/static/templates/docs/testing/test-data.md +17 -0
  140. package/static/templates/docs/testing/unit.md +17 -0
  141. package/static/templates/docs/uat/checks.md +17 -0
  142. package/static/templates/docs/uat/strategy.md +17 -0
  143. package/static/templates/docs/users/permissions.md +17 -0
  144. package/static/templates/docs/users/roles.md +17 -0
  145. package/static/templates/docs/users/user-types.md +17 -0
  146. package/static/templates/docs/workflows/assignment.md +17 -0
  147. package/static/templates/docs/workflows/bugfix.md +17 -0
  148. package/static/templates/docs/workflows/dependencies.md +17 -0
  149. package/static/templates/docs/workflows/deployment.md +17 -0
  150. package/static/templates/docs/workflows/development.md +17 -0
  151. package/static/templates/docs/workflows/planning.md +17 -0
  152. package/static/templates/docs/workflows/review.md +17 -0
  153. package/static/templates/docs/workflows/uat.md +17 -0
  154. package/static/templates/docsSite/docusaurus.config.mjs +144 -0
  155. package/static/templates/docsSite/gitignore.txt +3 -0
  156. package/static/templates/docsSite/package.json.txt +42 -0
  157. package/static/templates/docsSite/pnpm-workspace.yaml +2 -0
  158. package/static/templates/docsSite/sidebars.mjs +7 -0
  159. package/static/templates/docsSite/src/css/custom.css +7 -0
  160. package/static/templates/docsSite/src/pages/index.tsx +9 -0
  161. package/static/templates/docsSite/static/images/logo.svg +4 -0
  162. package/static/templates/docsSite/tsconfig.json +7 -0
  163. package/static/templates/eslint/base/eslint.config.mjs +104 -0
  164. package/static/templates/eslint/nextjs/eslint.config.mjs +181 -0
  165. package/static/templates/eslint/react/eslint.config.mjs +173 -0
  166. package/static/templates/eslint/typescript/eslint.config.mjs +154 -0
  167. package/static/templates/globals.css +16 -0
  168. package/static/templates/husky/pre-commit +1 -0
  169. package/static/templates/next.config.mjs +21 -0
  170. package/static/templates/prettier.config.mjs +26 -0
  171. package/static/templates/skills/codeReview.md +31 -0
  172. package/static/templates/skills/format.md +18 -0
  173. package/static/templates/skills/lint.md +21 -0
  174. package/static/templates/skills/lintFix.md +24 -0
  175. package/static/templates/skills/securityAudit.md +28 -0
  176. package/static/templates/skills/test.md +24 -0
  177. package/static/templates/skills/typescript.md +28 -0
  178. package/static/templates/skills/updateLayout.md +20 -0
  179. package/static/templates/stylelint/css/stylelint.config.mjs +40 -0
  180. package/static/templates/stylelint/scss/stylelint.config.mjs +13 -0
@@ -0,0 +1,900 @@
1
+ ---
2
+ sidebar_position: 1
3
+ sidebar_label: "ESLint"
4
+ ---
5
+
6
+ # ESLint Configuration
7
+
8
+ Every project lints with ESLint using a flat config (`eslint.config.mjs`) in the project root. Start from the config below that matches the project:
9
+
10
+ | Config | Use when |
11
+ | ---------------------------------------- | --------------------------- |
12
+ | [Base](#base-javascript--nodejs) | Plain JS / Node.js projects |
13
+ | [TypeScript](#typescript) | TypeScript projects |
14
+ | [React + TypeScript](#react--typescript) | React + TypeScript projects |
15
+ | [Next.js](#nextjs) | Next.js projects |
16
+
17
+ Each config builds on the one above it — React adds to TypeScript, which adds to Base.
18
+
19
+ ## Installation
20
+
21
+ :::warning[ESLint 10.4 or later]
22
+
23
+ **The config needs ESLint 10.4 or later.** `eslint-plugin-unicorn` 78, which provides the naming rules, requires it. A project on ESLint 9 must upgrade first:
24
+
25
+ ```bash
26
+ pnpm add -D eslint@latest @eslint/js@latest
27
+ ```
28
+
29
+ :::
30
+
31
+ :::note[eslint-plugin-react on ESLint 10]
32
+
33
+ `eslint-plugin-react` 7.37 officially supports ESLint only up to 9.7, and on ESLint 10 it crashes when it tries to detect the React version by itself. The React and Next.js configs below avoid this by setting the version: `settings: { react: { version: "19.0" } }`. **Keep that setting** and update the number when the project upgrades React. pnpm warns about the unmet peer dependency on install; that warning is expected.
34
+
35
+ :::
36
+
37
+ ```bash
38
+ pnpm add -D eslint @eslint/js globals \
39
+ @eslint-community/eslint-plugin-eslint-comments \
40
+ eslint-config-prettier \
41
+ eslint-plugin-import-x \
42
+ eslint-plugin-unicorn
43
+ ```
44
+
45
+ For TypeScript projects also install:
46
+
47
+ ```bash
48
+ pnpm add -D typescript-eslint eslint-import-resolver-typescript
49
+ ```
50
+
51
+ For React projects:
52
+
53
+ ```bash
54
+ pnpm add -D eslint-plugin-react eslint-plugin-react-hooks
55
+ ```
56
+
57
+ For Next.js projects:
58
+
59
+ ```bash
60
+ pnpm add -D eslint-config-next
61
+ ```
62
+
63
+ :::caution[TypeScript 7 and typescript-eslint]
64
+
65
+ `typescript-eslint` 8 does not run against TypeScript 7.0 — ESLint stops with `typescript-eslint does not support TS 7.0`. Until it does, linting needs the TypeScript 6 API available alongside TypeScript 7. See [running side by side with TypeScript 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0) and the [typescript-eslint tracking issue](https://github.com/typescript-eslint/typescript-eslint/issues/10940).
66
+
67
+ :::
68
+
69
+ ## Usage
70
+
71
+ Create `eslint.config.mjs` in your project root.
72
+
73
+ ### Base (JavaScript / Node.js)
74
+
75
+ ```js title="eslint.config.mjs"
76
+ import js from "@eslint/js";
77
+ import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
78
+ import prettier from "eslint-config-prettier";
79
+ import { flatConfigs as importX } from "eslint-plugin-import-x";
80
+ import unicorn from "eslint-plugin-unicorn";
81
+ import globals from "globals";
82
+
83
+ export default [
84
+ { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
85
+ js.configs.recommended,
86
+ comments.recommended,
87
+ importX.recommended,
88
+ unicorn.configs.recommended,
89
+ {
90
+ languageOptions: { globals: { ...globals.node } },
91
+ rules: {
92
+ "@eslint-community/eslint-comments/require-description":
93
+ "error",
94
+ "func-style": ["error", "expression"],
95
+ "max-depth": ["error", 3],
96
+ "max-lines": [
97
+ "error",
98
+ { max: 300, skipBlankLines: true, skipComments: true },
99
+ ],
100
+ "no-nested-ternary": "error",
101
+ "no-var": "error",
102
+ "prefer-arrow-callback": "error",
103
+ "prefer-const": "error",
104
+ "no-magic-numbers": [
105
+ "error",
106
+ {
107
+ ignore: [-1, 0, 1],
108
+ ignoreArrayIndexes: true,
109
+ ignoreDefaultValues: true,
110
+ enforceConst: true,
111
+ },
112
+ ],
113
+ "no-restricted-syntax": [
114
+ "error",
115
+ {
116
+ selector: "ClassDeclaration",
117
+ message: "Use functions, not classes.",
118
+ },
119
+ {
120
+ selector: "ClassExpression",
121
+ message: "Use functions, not classes.",
122
+ },
123
+ {
124
+ selector: "VariableDeclarator > FunctionExpression",
125
+ message: "Use an arrow function.",
126
+ },
127
+ {
128
+ selector: "VariableDeclaration[kind='let']",
129
+ message:
130
+ "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
131
+ },
132
+ {
133
+ selector: "FunctionDeclaration[params.length>1]",
134
+ message:
135
+ "Use a single object parameter when a function needs more than one value.",
136
+ },
137
+ {
138
+ selector:
139
+ "VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
140
+ message:
141
+ "Use a single object parameter when a function needs more than one value.",
142
+ },
143
+ ],
144
+ "import-x/no-cycle": "error",
145
+ "import-x/no-default-export": "error",
146
+ "no-use-before-define": "error",
147
+ "unicorn/default-export-style": "off",
148
+ "unicorn/filename-case": [
149
+ "error",
150
+ { cases: { camelCase: true, pascalCase: true } },
151
+ ],
152
+ "unicorn/name-replacements": [
153
+ "error",
154
+ {
155
+ // Dictionary words: see "Dictionary" in the Human-Readable Names standard.
156
+ replacements: {
157
+ docs: false,
158
+ lib: false,
159
+ props: false,
160
+ ref: false,
161
+ repo: false,
162
+ utils: false,
163
+ },
164
+ // Third-party names, allowed only exactly as they are.
165
+ allowList: { params: true, searchParams: true },
166
+ },
167
+ ],
168
+ },
169
+ },
170
+ {
171
+ files: ["**/*.test.{js,mjs}"],
172
+ rules: { "max-lines": "off", "no-magic-numbers": "off" },
173
+ },
174
+ {
175
+ files: ["**/*.config.{js,mjs}"],
176
+ rules: { "import-x/no-default-export": "off" },
177
+ },
178
+ prettier,
179
+ ];
180
+ ```
181
+
182
+ ### TypeScript
183
+
184
+ ```js title="eslint.config.mjs"
185
+ import js from "@eslint/js";
186
+ import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
187
+ import prettier from "eslint-config-prettier";
188
+ import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
189
+ import { flatConfigs as importX } from "eslint-plugin-import-x";
190
+ import unicorn from "eslint-plugin-unicorn";
191
+ import globals from "globals";
192
+ import { configs as typescript } from "typescript-eslint";
193
+
194
+ export default [
195
+ { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
196
+ js.configs.recommended,
197
+ comments.recommended,
198
+ importX.recommended,
199
+ importX.typescript,
200
+ unicorn.configs.recommended,
201
+ ...typescript.recommended,
202
+ {
203
+ languageOptions: { globals: { ...globals.node } },
204
+ settings: {
205
+ "import-x/resolver-next": [createTypeScriptImportResolver()],
206
+ },
207
+ rules: {
208
+ "@eslint-community/eslint-comments/require-description":
209
+ "error",
210
+ "func-style": ["error", "expression"],
211
+ "max-depth": ["error", 3],
212
+ "max-lines": [
213
+ "error",
214
+ { max: 300, skipBlankLines: true, skipComments: true },
215
+ ],
216
+ "no-nested-ternary": "error",
217
+ "no-var": "error",
218
+ "prefer-arrow-callback": "error",
219
+ "prefer-const": "error",
220
+ "no-magic-numbers": [
221
+ "error",
222
+ {
223
+ ignore: [-1, 0, 1],
224
+ ignoreArrayIndexes: true,
225
+ ignoreDefaultValues: true,
226
+ enforceConst: true,
227
+ },
228
+ ],
229
+ "no-restricted-syntax": [
230
+ "error",
231
+ {
232
+ selector: "ClassDeclaration",
233
+ message: "Use functions, not classes.",
234
+ },
235
+ {
236
+ selector: "ClassExpression",
237
+ message: "Use functions, not classes.",
238
+ },
239
+ {
240
+ selector: "VariableDeclarator > FunctionExpression",
241
+ message: "Use an arrow function.",
242
+ },
243
+ {
244
+ selector: "VariableDeclaration[kind='let']",
245
+ message:
246
+ "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
247
+ },
248
+ {
249
+ selector: "FunctionDeclaration[params.length>1]",
250
+ message:
251
+ "Use a single object parameter when a function needs more than one value.",
252
+ },
253
+ {
254
+ selector:
255
+ "VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
256
+ message:
257
+ "Use a single object parameter when a function needs more than one value.",
258
+ },
259
+ ],
260
+ "import-x/no-cycle": "error",
261
+ "import-x/no-default-export": "error",
262
+ "no-use-before-define": "error",
263
+ "unicorn/default-export-style": "off",
264
+ "unicorn/filename-case": [
265
+ "error",
266
+ { cases: { camelCase: true, pascalCase: true } },
267
+ ],
268
+ "unicorn/name-replacements": [
269
+ "error",
270
+ {
271
+ // Dictionary words: see "Dictionary" in the Human-Readable Names standard.
272
+ replacements: {
273
+ docs: false,
274
+ lib: false,
275
+ props: false,
276
+ ref: false,
277
+ repo: false,
278
+ utils: false,
279
+ },
280
+ // Third-party names, allowed only exactly as they are.
281
+ allowList: { params: true, searchParams: true },
282
+ },
283
+ ],
284
+ },
285
+ },
286
+ {
287
+ files: ["**/*.{ts,tsx}"],
288
+ rules: {
289
+ "no-undef": "off",
290
+ "no-magic-numbers": "off",
291
+ "no-use-before-define": "off",
292
+ "@typescript-eslint/consistent-type-assertions": [
293
+ "error",
294
+ { assertionStyle: "never" },
295
+ ],
296
+ "@typescript-eslint/consistent-type-imports": "error",
297
+ "@typescript-eslint/naming-convention": [
298
+ "error",
299
+ {
300
+ selector: ["typeAlias", "interface"],
301
+ format: ["PascalCase"],
302
+ custom: { regex: "(Props|Type)$", match: true },
303
+ },
304
+ ],
305
+ "@typescript-eslint/no-explicit-any": "error",
306
+ "@typescript-eslint/no-magic-numbers": [
307
+ "error",
308
+ {
309
+ ignore: [-1, 0, 1],
310
+ ignoreArrayIndexes: true,
311
+ ignoreDefaultValues: true,
312
+ enforceConst: true,
313
+ ignoreEnums: true,
314
+ ignoreNumericLiteralTypes: true,
315
+ },
316
+ ],
317
+ "@typescript-eslint/no-non-null-assertion": "error",
318
+ "@typescript-eslint/no-use-before-define": "error",
319
+ },
320
+ },
321
+ {
322
+ files: ["**/*.test.{ts,tsx}"],
323
+ rules: {
324
+ "max-lines": "off",
325
+ "no-magic-numbers": "off",
326
+ "@typescript-eslint/no-magic-numbers": "off",
327
+ },
328
+ },
329
+ {
330
+ files: ["**/*.config.{js,mjs,ts}", "**/*.stories.tsx"],
331
+ rules: { "import-x/no-default-export": "off" },
332
+ },
333
+ {
334
+ files: ["**/*.config.{js,mjs,ts}"],
335
+ rules: { "unicorn/no-top-level-side-effects": "off" },
336
+ },
337
+ prettier,
338
+ ];
339
+ ```
340
+
341
+ TypeScript files are checked by the compiler, so `no-undef` is off for them. The Node globals still earn their place: your `eslint.config.mjs`, build scripts and any remaining `.js` files are linted with `no-undef` on.
342
+
343
+ ### React + TypeScript
344
+
345
+ Start from the [TypeScript](#typescript) config and make five changes:
346
+
347
+ ```js title="eslint.config.mjs"
348
+ import js from "@eslint/js";
349
+ import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
350
+ import prettier from "eslint-config-prettier";
351
+ import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
352
+ import { flatConfigs as importX } from "eslint-plugin-import-x";
353
+ import react from "eslint-plugin-react";
354
+ import reactHooks from "eslint-plugin-react-hooks";
355
+ import unicorn from "eslint-plugin-unicorn";
356
+ import globals from "globals";
357
+ import { configs as typescript } from "typescript-eslint";
358
+
359
+ export default [
360
+ { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
361
+ js.configs.recommended,
362
+ comments.recommended,
363
+ importX.recommended,
364
+ importX.typescript,
365
+ unicorn.configs.recommended,
366
+ ...typescript.recommended,
367
+ react.configs.flat.recommended,
368
+ react.configs.flat["jsx-runtime"],
369
+ reactHooks.configs.flat.recommended,
370
+ {
371
+ languageOptions: {
372
+ globals: { ...globals.browser, ...globals.node },
373
+ },
374
+ settings: {
375
+ // Keep this. eslint-plugin-react crashes on ESLint 10 if it has to detect the version itself.
376
+ // Update the number when the project upgrades React.
377
+ react: { version: "19.0" },
378
+ "import-x/resolver-next": [createTypeScriptImportResolver()],
379
+ },
380
+ rules: {
381
+ "@eslint-community/eslint-comments/require-description":
382
+ "error",
383
+ "func-style": ["error", "expression"],
384
+ "max-depth": ["error", 3],
385
+ "max-lines": [
386
+ "error",
387
+ { max: 300, skipBlankLines: true, skipComments: true },
388
+ ],
389
+ "no-nested-ternary": "error",
390
+ "no-var": "error",
391
+ "prefer-arrow-callback": "error",
392
+ "prefer-const": "error",
393
+ "no-magic-numbers": [
394
+ "error",
395
+ {
396
+ ignore: [-1, 0, 1],
397
+ ignoreArrayIndexes: true,
398
+ ignoreDefaultValues: true,
399
+ enforceConst: true,
400
+ },
401
+ ],
402
+ "no-restricted-syntax": [
403
+ "error",
404
+ {
405
+ selector: "ClassDeclaration",
406
+ message: "Use functions, not classes.",
407
+ },
408
+ {
409
+ selector: "ClassExpression",
410
+ message: "Use functions, not classes.",
411
+ },
412
+ {
413
+ selector: "VariableDeclarator > FunctionExpression",
414
+ message: "Use an arrow function.",
415
+ },
416
+ {
417
+ selector: "VariableDeclaration[kind='let']",
418
+ message:
419
+ "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
420
+ },
421
+ {
422
+ selector: "FunctionDeclaration[params.length>1]",
423
+ message:
424
+ "Use a single object parameter when a function needs more than one value.",
425
+ },
426
+ {
427
+ selector:
428
+ "VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
429
+ message:
430
+ "Use a single object parameter when a function needs more than one value.",
431
+ },
432
+ ],
433
+ "import-x/no-cycle": "error",
434
+ "import-x/no-default-export": "error",
435
+ "no-use-before-define": "error",
436
+ "unicorn/default-export-style": "off",
437
+ "unicorn/filename-case": [
438
+ "error",
439
+ { cases: { camelCase: true, pascalCase: true } },
440
+ ],
441
+ "unicorn/name-replacements": [
442
+ "error",
443
+ {
444
+ // Dictionary words: see "Dictionary" in the Human-Readable Names standard.
445
+ replacements: {
446
+ docs: false,
447
+ lib: false,
448
+ props: false,
449
+ ref: false,
450
+ repo: false,
451
+ utils: false,
452
+ },
453
+ // Third-party names, allowed only exactly as they are.
454
+ allowList: { params: true, searchParams: true },
455
+ },
456
+ ],
457
+ "react/function-component-definition": [
458
+ "error",
459
+ {
460
+ namedComponents: "arrow-function",
461
+ unnamedComponents: "arrow-function",
462
+ },
463
+ ],
464
+ "react/jsx-no-bind": "error",
465
+ "react/no-multi-comp": "error",
466
+ },
467
+ },
468
+ {
469
+ files: ["**/*.{ts,tsx}"],
470
+ rules: {
471
+ "no-undef": "off",
472
+ "no-magic-numbers": "off",
473
+ "no-use-before-define": "off",
474
+ "@typescript-eslint/consistent-type-assertions": [
475
+ "error",
476
+ { assertionStyle: "never" },
477
+ ],
478
+ "@typescript-eslint/consistent-type-imports": "error",
479
+ "@typescript-eslint/naming-convention": [
480
+ "error",
481
+ {
482
+ selector: ["typeAlias", "interface"],
483
+ format: ["PascalCase"],
484
+ custom: { regex: "(Props|Type)$", match: true },
485
+ },
486
+ ],
487
+ "@typescript-eslint/no-explicit-any": "error",
488
+ "@typescript-eslint/no-magic-numbers": [
489
+ "error",
490
+ {
491
+ ignore: [-1, 0, 1],
492
+ ignoreArrayIndexes: true,
493
+ ignoreDefaultValues: true,
494
+ enforceConst: true,
495
+ ignoreEnums: true,
496
+ ignoreNumericLiteralTypes: true,
497
+ },
498
+ ],
499
+ "@typescript-eslint/no-non-null-assertion": "error",
500
+ "@typescript-eslint/no-use-before-define": "error",
501
+ },
502
+ },
503
+ {
504
+ files: ["**/*.test.{ts,tsx}"],
505
+ rules: {
506
+ "max-lines": "off",
507
+ "no-magic-numbers": "off",
508
+ "@typescript-eslint/no-magic-numbers": "off",
509
+ },
510
+ },
511
+ {
512
+ files: ["**/*.config.{js,mjs,ts}", "**/*.stories.tsx"],
513
+ rules: { "import-x/no-default-export": "off" },
514
+ },
515
+ {
516
+ files: ["**/*.config.{js,mjs,ts}"],
517
+ rules: { "unicorn/no-top-level-side-effects": "off" },
518
+ },
519
+ prettier,
520
+ ];
521
+ ```
522
+
523
+ :::caution[Pin the React version]
524
+
525
+ Set `settings.react.version` to the project's React version rather than `"detect"`. On ESLint 10, `eslint-plugin-react` 7 crashes while detecting the version (`contextOrFilename.getFilename is not a function`).
526
+
527
+ :::
528
+
529
+ ### Next.js
530
+
531
+ `eslint-config-next` already brings the React, React Hooks and TypeScript rules, so it replaces the `js` and `typescript-eslint` presets rather than adding to them. Default exports are allowed in the files where Next.js requires them (`page.tsx`, `layout.tsx` and so on):
532
+
533
+ ```js title="eslint.config.mjs"
534
+ import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
535
+ import prettier from "eslint-config-prettier";
536
+ import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
537
+ import nextVitals from "eslint-config-next/core-web-vitals";
538
+ import nextTypescript from "eslint-config-next/typescript";
539
+ import { flatConfigs as importX } from "eslint-plugin-import-x";
540
+ import unicorn from "eslint-plugin-unicorn";
541
+ import globals from "globals";
542
+
543
+ export default [
544
+ {
545
+ ignores: [
546
+ ".next/",
547
+ "out/",
548
+ "build/",
549
+ "coverage/",
550
+ "next-env.d.ts",
551
+ "**/vendor/**",
552
+ ],
553
+ },
554
+ ...nextVitals,
555
+ ...nextTypescript,
556
+ comments.recommended,
557
+ importX.recommended,
558
+ importX.typescript,
559
+ unicorn.configs.recommended,
560
+ {
561
+ languageOptions: {
562
+ globals: { ...globals.browser, ...globals.node },
563
+ },
564
+ settings: {
565
+ // Keep this. eslint-plugin-react crashes on ESLint 10 if it has to detect the version itself.
566
+ // Update the number when the project upgrades React.
567
+ react: { version: "19.0" },
568
+ "import-x/resolver-next": [createTypeScriptImportResolver()],
569
+ },
570
+ rules: {
571
+ "@eslint-community/eslint-comments/require-description":
572
+ "error",
573
+ "func-style": ["error", "expression"],
574
+ "max-depth": ["error", 3],
575
+ "max-lines": [
576
+ "error",
577
+ { max: 300, skipBlankLines: true, skipComments: true },
578
+ ],
579
+ "no-nested-ternary": "error",
580
+ "no-var": "error",
581
+ "prefer-arrow-callback": "error",
582
+ "prefer-const": "error",
583
+ "no-magic-numbers": [
584
+ "error",
585
+ {
586
+ ignore: [-1, 0, 1],
587
+ ignoreArrayIndexes: true,
588
+ ignoreDefaultValues: true,
589
+ enforceConst: true,
590
+ },
591
+ ],
592
+ "no-restricted-syntax": [
593
+ "error",
594
+ {
595
+ selector: "ClassDeclaration",
596
+ message: "Use functions, not classes.",
597
+ },
598
+ {
599
+ selector: "ClassExpression",
600
+ message: "Use functions, not classes.",
601
+ },
602
+ {
603
+ selector: "VariableDeclarator > FunctionExpression",
604
+ message: "Use an arrow function.",
605
+ },
606
+ {
607
+ selector: "VariableDeclaration[kind='let']",
608
+ message:
609
+ "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
610
+ },
611
+ {
612
+ selector: "FunctionDeclaration[params.length>1]",
613
+ message:
614
+ "Use a single object parameter when a function needs more than one value.",
615
+ },
616
+ {
617
+ selector:
618
+ "VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
619
+ message:
620
+ "Use a single object parameter when a function needs more than one value.",
621
+ },
622
+ ],
623
+ "import-x/no-cycle": "error",
624
+ "import-x/no-default-export": "error",
625
+ "no-use-before-define": "error",
626
+ "unicorn/default-export-style": "off",
627
+ "unicorn/filename-case": [
628
+ "error",
629
+ { cases: { camelCase: true, pascalCase: true } },
630
+ ],
631
+ "unicorn/name-replacements": [
632
+ "error",
633
+ {
634
+ // Dictionary words: see "Dictionary" in the Human-Readable Names standard.
635
+ replacements: {
636
+ docs: false,
637
+ lib: false,
638
+ props: false,
639
+ ref: false,
640
+ repo: false,
641
+ utils: false,
642
+ },
643
+ // Third-party names, allowed only exactly as they are.
644
+ allowList: { params: true, searchParams: true },
645
+ },
646
+ ],
647
+ "react/function-component-definition": [
648
+ "error",
649
+ {
650
+ namedComponents: "arrow-function",
651
+ unnamedComponents: "arrow-function",
652
+ },
653
+ ],
654
+ "react/jsx-no-bind": "error",
655
+ "react/no-multi-comp": "error",
656
+ },
657
+ },
658
+ {
659
+ files: ["**/*.{ts,tsx}"],
660
+ rules: {
661
+ "no-undef": "off",
662
+ "no-magic-numbers": "off",
663
+ "no-use-before-define": "off",
664
+ "@typescript-eslint/consistent-type-assertions": [
665
+ "error",
666
+ { assertionStyle: "never" },
667
+ ],
668
+ "@typescript-eslint/consistent-type-imports": "error",
669
+ "@typescript-eslint/naming-convention": [
670
+ "error",
671
+ {
672
+ selector: ["typeAlias", "interface"],
673
+ format: ["PascalCase"],
674
+ custom: { regex: "(Props|Type)$", match: true },
675
+ },
676
+ ],
677
+ "@typescript-eslint/no-explicit-any": "error",
678
+ "@typescript-eslint/no-magic-numbers": [
679
+ "error",
680
+ {
681
+ ignore: [-1, 0, 1],
682
+ ignoreArrayIndexes: true,
683
+ ignoreDefaultValues: true,
684
+ enforceConst: true,
685
+ ignoreEnums: true,
686
+ ignoreNumericLiteralTypes: true,
687
+ },
688
+ ],
689
+ "@typescript-eslint/no-non-null-assertion": "error",
690
+ "@typescript-eslint/no-use-before-define": "error",
691
+ },
692
+ },
693
+ {
694
+ files: ["**/*.test.{ts,tsx}"],
695
+ rules: {
696
+ "max-lines": "off",
697
+ "no-magic-numbers": "off",
698
+ "@typescript-eslint/no-magic-numbers": "off",
699
+ },
700
+ },
701
+ {
702
+ files: [
703
+ "**/*.config.{js,mjs,ts}",
704
+ "**/*.stories.tsx",
705
+ "app/**/{page,layout,template,default,loading,error,global-error,not-found}.tsx",
706
+ ],
707
+ rules: { "import-x/no-default-export": "off" },
708
+ },
709
+ {
710
+ files: ["**/*.config.{js,mjs,ts}"],
711
+ rules: { "unicorn/no-top-level-side-effects": "off" },
712
+ },
713
+ prettier,
714
+ ];
715
+ ```
716
+
717
+ Next.js genuinely runs in both environments — client components in the browser, route handlers, server components and `next.config.js` in Node — so both sets of globals apply.
718
+
719
+ :::tip[Declare globals, do not disable the rule]
720
+
721
+ Use the `globals` package rather than turning `no-undef` off or hand-writing a globals list. A hand-written list is a snapshot of what one project happened to use on one day, and the next runtime API added to Node is missing from it — which is exactly how a project ends up with `fetch` declared but `AbortController` not.
722
+
723
+ :::
724
+
725
+ ### Project-specific overrides
726
+
727
+ Add a block before `prettier` to add or override rules:
728
+
729
+ ```js
730
+ export default [
731
+ // ...
732
+ {
733
+ rules: {
734
+ // project-specific overrides
735
+ "no-console": "off",
736
+ },
737
+ },
738
+ prettier,
739
+ ];
740
+ ```
741
+
742
+ Keep `prettier` last — it switches off every rule that would fight the formatter.
743
+
744
+ Add scripts to `package.json`:
745
+
746
+ ```json
747
+ {
748
+ "scripts": {
749
+ "lint": "eslint .",
750
+ "lint:fix": "eslint . --fix"
751
+ }
752
+ }
753
+ ```
754
+
755
+ ## Plugins
756
+
757
+ | Plugin | Purpose |
758
+ | ------------------------------------------------- | --------------------------------------------------------------- |
759
+ | `@eslint/js` | ESLint core recommended rules |
760
+ | `@eslint-community/eslint-plugin-eslint-comments` | Requires every `eslint-disable` comment to give a reason |
761
+ | `eslint-plugin-import-x` | Import checks: unresolved paths, duplicates, no default exports |
762
+ | `eslint-plugin-unicorn` | Opinionated JS best-practice rules |
763
+ | `eslint-config-prettier` | Disables rules that conflict with Prettier |
764
+ | `typescript-eslint` | TypeScript-specific rules (TypeScript configs only) |
765
+ | `eslint-plugin-react` | React-specific rules (React configs only) |
766
+ | `eslint-plugin-react-hooks` | Hooks rules (React configs only) |
767
+
768
+ ## Third-party code is exempt
769
+
770
+ **The standards apply to code we write. They do not apply to packages we pull in.**
771
+
772
+ | Code | Linted? |
773
+ | ------------------------------------------------- | ------------------------------------ |
774
+ | Our own code | ✅ Yes — every rule applies |
775
+ | `node_modules/` | ❌ No — ESLint ignores it by default |
776
+ | `vendor/` (third-party code copied into the repo) | ❌ No — ignored by the config |
777
+ | Build output (`dist/`, `build/`, `.next/`) | ❌ No — ignored by the config |
778
+
779
+ Third-party code that is copied into the repository lives in a `vendor/` folder, at any level. The configs ignore `**/vendor/**`, so it is never linted.
780
+
781
+ - **Do not fix lint errors in third-party code.** It is not ours, and the fix would be lost on the next update.
782
+ - **Do not rename or restructure it** to match our naming or folder rules.
783
+ - **Do not add `eslint-disable` comments to it.** Put it in `vendor/` instead.
784
+
785
+ ### Copied-in code we maintain is ours
786
+
787
+ `vendor/` is only for third-party code we **do not change** — code that is replaced wholesale when the package updates.
788
+
789
+ Code that is copied into the project and then **owned and edited by us** is our code, and every standard applies to it. The main case is **shadcn/ui**: the CLI copies component source into the project, and from then on it is ours. It is converted to our standards and lives in `components/ui/`, never in `vendor/`. See [Adding a shadcn component](/Guides/component-library-workflow#adding-a-shadcn-component).
790
+
791
+ | Code | Ours? | Follows the standards? |
792
+ | --------------------------------------------------------- | ------- | ------------------------------------------ |
793
+ | A package installed with `pnpm add` | No | No |
794
+ | Third-party code copied in and left untouched (`vendor/`) | No | No |
795
+ | shadcn components added with the CLI | **Yes** | **Yes — convert them when they are added** |
796
+ | Code generated once and then edited by us | **Yes** | **Yes** |
797
+
798
+ If a vendored package lives somewhere else and cannot be moved, add its path to the `ignores` list at the top of the config.
799
+
800
+ ## Key rule decisions
801
+
802
+ Most of these enforce the [Readable Code](/Standards/readable-code) rules. For what each error means and how to fix it, see [Fixing Lint Errors](/Standards/lint-errors).
803
+
804
+ ### `func-style`, `prefer-arrow-callback`: arrow functions everywhere
805
+
806
+ Every function is an arrow function. See [Readable Code](/Standards/readable-code#arrow-functions-everywhere).
807
+
808
+ - `func-style: expression` rejects `function name() {}` declarations.
809
+ - `prefer-arrow-callback` rejects `function` callbacks.
810
+ - `no-restricted-syntax` rejects `const name = function () {}`.
811
+ - `react/function-component-definition` requires components to be arrow functions (React configs).
812
+ - `unicorn/default-export-style` is switched off. It demands `export default function`, which contradicts this rule in the Next.js files that need a default export.
813
+
814
+ ### `no-restricted-syntax`, `prefer-const`, `no-var`: always `const`
815
+
816
+ Variables are declared with `const`. `let` is an error unless the line carries a disable comment with a reason; `var` is never allowed. See [Readable Code](/Standards/readable-code#always-const).
817
+
818
+ ### `@eslint-community/eslint-comments/require-description`
819
+
820
+ Every `eslint-disable` comment must say why, after `--`. A rule is never switched off silently. See [the only allowed exceptions](/Standards/lint-errors#the-only-allowed-exceptions).
821
+
822
+ ### `max-lines: 300`
823
+
824
+ No file is longer than 300 lines, not counting blank lines and comments. Test files are exempt. See [Readable Code](/Standards/readable-code#no-file-longer-than-300-lines).
825
+
826
+ ### `max-depth: 3`
827
+
828
+ Blocks nest at most three levels deep. Return early instead. See [Readable Code](/Standards/readable-code#return-early-nest-shallow).
829
+
830
+ ### `no-nested-ternary`
831
+
832
+ A ternary inside a ternary is an error. See [Readable Code](/Standards/readable-code#no-nested-ternaries).
833
+
834
+ ### `no-magic-numbers`
835
+
836
+ Numbers other than `-1`, `0` and `1` must be named constants. Test files are exempt. TypeScript files use `@typescript-eslint/no-magic-numbers`, which understands enums and numeric literal types. See [Readable Code](/Standards/readable-code#no-magic-numbers-or-strings).
837
+
838
+ ### `no-restricted-syntax`: no classes, one parameter
839
+
840
+ These are banned by syntax, along with `function` expressions (above):
841
+
842
+ - **Class declarations and class expressions.** See [Readable Code](/Standards/readable-code#functions-never-classes).
843
+ - **Functions you define with more than one parameter.** Use a single object parameter. Inline callbacks whose signature is set by someone else — `array.reduce((total, item) => …)`, event handlers — are not affected. See [Readable Code](/Standards/readable-code#one-parameter-always-an-object-for-more).
844
+
845
+ ### `import-x/no-cycle`: no circular imports
846
+
847
+ A file may never import a file that imports it back, directly or through a chain of other files. See [Import safety](/Structure/import-organization#import-safety).
848
+
849
+ ### `no-use-before-define`
850
+
851
+ A function or variable is defined above the code that uses it. TypeScript files use `@typescript-eslint/no-use-before-define`. See [Import safety](/Structure/import-organization#import-safety).
852
+
853
+ ### `import-x/no-default-export`
854
+
855
+ Named exports only. Default exports are allowed where a tool requires them: config files, Storybook stories and Next.js route files. See [Readable Code](/Standards/readable-code#named-exports-only).
856
+
857
+ ### Import order is not linted
858
+
859
+ There is deliberately no import-**order** rule (circular imports are a different matter — see above). Follow the [Import Organization](/Structure/import-organization) pattern for consistency, but ESLint will not complain about it.
860
+
861
+ ### `unicorn/filename-case: camelCase or PascalCase`
862
+
863
+ The plugin's default is kebab-case, which contradicts our [naming conventions](/Structure/naming-conventions). Files are camelCase (`formatDate.ts`), and component files are PascalCase (`UserProfile.tsx`).
864
+
865
+ ### `unicorn/name-replacements: error`
866
+
867
+ Names are full English words: `btn` must be `button`, `e` must be `error` or `event`, `acc` must be `accumulator`. `docs`, `lib`, `props`, `ref`, `repo` and `utils` are allowed because they are on the [dictionary](/Standards/human-readable-names#dictionary) list. Only three names owned by React and Next.js are allowed exactly as they are — `ref`, `params` and `searchParams` — and our own names built from them are still flagged (`inputRef` must be `inputReference`). See [Human-Readable Names](/Standards/human-readable-names). (Before `eslint-plugin-unicorn` 77 this rule was called `unicorn/prevent-abbreviations`.)
868
+
869
+ ### `@typescript-eslint/no-explicit-any: error`
870
+
871
+ `any` defeats the type system. Use `unknown` and narrow with guards instead.
872
+
873
+ ### `@typescript-eslint/no-non-null-assertion: error`
874
+
875
+ The `!` operator tells the compiler a value exists without checking. Check it instead.
876
+
877
+ ### `@typescript-eslint/consistent-type-assertions: never`
878
+
879
+ `as` casts are errors (`as const` is still allowed). When a cast is genuinely needed, disable the rule on that line **with the reason** — which is exactly the comment the [standard](/Standards/readable-code#no-any-no--no-unexplained-as) requires:
880
+
881
+ ```ts
882
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- the API returns untyped JSON; validated by parseUser below
883
+ const user = response.data as UserType;
884
+ ```
885
+
886
+ ### `@typescript-eslint/naming-convention`: `Props` or `Type` suffix
887
+
888
+ Every interface and type alias ends in `Props` or `Type`. See [Naming Conventions](/Structure/naming-conventions#types--interfaces).
889
+
890
+ ### `@typescript-eslint/consistent-type-imports: error`
891
+
892
+ Type-only imports use `import type { Foo }` or inline `import { type Foo }`. This produces cleaner transpiled output and avoids accidental value imports.
893
+
894
+ ### `react/jsx-no-bind: error`
895
+
896
+ No inline functions in render. A function passed as a JSX prop must be wrapped in `useCallback`, so it is not recreated on every render. See [Writing a component](/Structure/component-organization#-writing-a-component).
897
+
898
+ ### `react/no-multi-comp: error`
899
+
900
+ One component per file. See [Readable Code](/Standards/readable-code#one-component-per-file).