@activatus/code-standards 3.0.2 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -13
- package/docs/Guides/component-library-workflow.md +16 -16
- package/docs/Guides/migrating.md +18 -16
- package/docs/Guides/setup.md +9 -7
- package/docs/Standards/agent-skills.md +2 -2
- package/docs/Standards/agent-specs.md +3 -3
- package/docs/Standards/documentation.md +7 -7
- package/docs/Standards/eslint.md +321 -155
- package/docs/Standards/human-readable-names.md +15 -3
- package/docs/Standards/lint-errors.md +13 -12
- package/docs/Standards/readable-code.md +82 -43
- package/docs/Standards/stylelint.md +4 -4
- package/docs/Standards/styling.md +8 -8
- package/docs/Standards/tech-stack.md +1 -1
- package/docs/Structure/api-routes.md +3 -3
- package/docs/Structure/component-organization.md +19 -18
- package/docs/Structure/folder-structure.md +131 -12
- package/docs/Structure/naming-conventions.md +10 -8
- package/docs/design-system-components.md +3 -3
- package/docs/intro.md +20 -20
- package/docs/ontology.md +32 -31
- package/docs/templates.mdx +12 -12
- package/docs/version-history.md +77 -42
- package/package.json +19 -4
- package/static/templates/AGENTS.md +6 -5
- package/static/templates/docs/overview/writing-docs.md +1 -1
- package/static/templates/eslint/base/eslint.config.mjs +57 -33
- package/static/templates/eslint/nextjs/eslint.config.mjs +80 -32
- package/static/templates/eslint/react/eslint.config.mjs +76 -33
- package/static/templates/eslint/typescript/eslint.config.mjs +76 -33
package/docs/Standards/eslint.md
CHANGED
|
@@ -68,22 +68,70 @@ pnpm add -D eslint-config-next
|
|
|
68
68
|
|
|
69
69
|
## Usage
|
|
70
70
|
|
|
71
|
-
Create `eslint.config.mjs` in your project root.
|
|
71
|
+
Create `eslint.config.mjs` in your project root. Each config passes its own rules, so it lints clean the moment it is copied in. The code standards repo checks this with `pnpm check:eslint-templates` whenever a config changes.
|
|
72
72
|
|
|
73
73
|
### Base (JavaScript / Node.js)
|
|
74
74
|
|
|
75
75
|
```js title="eslint.config.mjs"
|
|
76
76
|
import js from "@eslint/js";
|
|
77
|
-
import
|
|
77
|
+
import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
|
|
78
78
|
import prettier from "eslint-config-prettier";
|
|
79
79
|
import { flatConfigs as importX } from "eslint-plugin-import-x";
|
|
80
80
|
import unicorn from "eslint-plugin-unicorn";
|
|
81
81
|
import globals from "globals";
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// Code patterns that are errors in every file.
|
|
84
|
+
const RESTRICTED_SYNTAX = [
|
|
85
|
+
{
|
|
86
|
+
selector: "ClassDeclaration",
|
|
87
|
+
message: "Use functions, not classes.",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
selector: "ClassExpression",
|
|
91
|
+
message: "Use functions, not classes.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
selector: "VariableDeclarator > FunctionExpression",
|
|
95
|
+
message: "Use an arrow function.",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
selector: "VariableDeclaration[kind='let']",
|
|
99
|
+
message:
|
|
100
|
+
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
// Functions take one parameter: a single object when they need more than one value.
|
|
105
|
+
const SINGLE_OBJECT_PARAMETER = [
|
|
106
|
+
{
|
|
107
|
+
selector: "FunctionDeclaration[params.length>1]",
|
|
108
|
+
message:
|
|
109
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
selector:
|
|
113
|
+
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
114
|
+
message:
|
|
115
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
// Deeper than this, return early instead.
|
|
120
|
+
const MAXIMUM_NESTING_DEPTH = 3;
|
|
121
|
+
|
|
122
|
+
const config = [
|
|
123
|
+
{
|
|
124
|
+
ignores: [
|
|
125
|
+
"dist/",
|
|
126
|
+
"build/",
|
|
127
|
+
"coverage/",
|
|
128
|
+
// The docs viewer is the standard template, with its own packages and build.
|
|
129
|
+
"docsSite/",
|
|
130
|
+
"**/vendor/**",
|
|
131
|
+
],
|
|
132
|
+
},
|
|
85
133
|
js.configs.recommended,
|
|
86
|
-
|
|
134
|
+
eslintCommentsRecommended,
|
|
87
135
|
importX.recommended,
|
|
88
136
|
unicorn.configs.recommended,
|
|
89
137
|
{
|
|
@@ -92,7 +140,7 @@ export default [
|
|
|
92
140
|
"@eslint-community/eslint-comments/require-description":
|
|
93
141
|
"error",
|
|
94
142
|
"func-style": ["error", "expression"],
|
|
95
|
-
"max-depth": ["error",
|
|
143
|
+
"max-depth": ["error", MAXIMUM_NESTING_DEPTH],
|
|
96
144
|
"max-lines": [
|
|
97
145
|
"error",
|
|
98
146
|
{ max: 300, skipBlankLines: true, skipComments: true },
|
|
@@ -112,34 +160,8 @@ export default [
|
|
|
112
160
|
],
|
|
113
161
|
"no-restricted-syntax": [
|
|
114
162
|
"error",
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
},
|
|
163
|
+
...RESTRICTED_SYNTAX,
|
|
164
|
+
...SINGLE_OBJECT_PARAMETER,
|
|
143
165
|
],
|
|
144
166
|
"import-x/no-cycle": "error",
|
|
145
167
|
"import-x/no-default-export": "error",
|
|
@@ -177,13 +199,15 @@ export default [
|
|
|
177
199
|
},
|
|
178
200
|
prettier,
|
|
179
201
|
];
|
|
202
|
+
|
|
203
|
+
export default config;
|
|
180
204
|
```
|
|
181
205
|
|
|
182
206
|
### TypeScript
|
|
183
207
|
|
|
184
208
|
```js title="eslint.config.mjs"
|
|
185
209
|
import js from "@eslint/js";
|
|
186
|
-
import
|
|
210
|
+
import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
|
|
187
211
|
import prettier from "eslint-config-prettier";
|
|
188
212
|
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
|
|
189
213
|
import { flatConfigs as importX } from "eslint-plugin-import-x";
|
|
@@ -191,10 +215,65 @@ import unicorn from "eslint-plugin-unicorn";
|
|
|
191
215
|
import globals from "globals";
|
|
192
216
|
import { configs as typescript } from "typescript-eslint";
|
|
193
217
|
|
|
194
|
-
|
|
195
|
-
|
|
218
|
+
// Code patterns that are errors in every file.
|
|
219
|
+
const RESTRICTED_SYNTAX = [
|
|
220
|
+
{
|
|
221
|
+
selector: "ClassDeclaration",
|
|
222
|
+
message: "Use functions, not classes.",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
selector: "ClassExpression",
|
|
226
|
+
message: "Use functions, not classes.",
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
selector: "VariableDeclarator > FunctionExpression",
|
|
230
|
+
message: "Use an arrow function.",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
selector: "VariableDeclaration[kind='let']",
|
|
234
|
+
message:
|
|
235
|
+
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
// Functions take one parameter: a single object when they need more than one value.
|
|
240
|
+
const SINGLE_OBJECT_PARAMETER = [
|
|
241
|
+
{
|
|
242
|
+
selector: "FunctionDeclaration[params.length>1]",
|
|
243
|
+
message:
|
|
244
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
selector:
|
|
248
|
+
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
249
|
+
message:
|
|
250
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
// Deeper than this, return early instead.
|
|
255
|
+
const MAXIMUM_NESTING_DEPTH = 3;
|
|
256
|
+
|
|
257
|
+
// Type a value where it is declared. Story files are the exception: see below.
|
|
258
|
+
const NO_SATISFIES = {
|
|
259
|
+
selector: "TSSatisfiesExpression",
|
|
260
|
+
message:
|
|
261
|
+
"Type a value where it is declared, not with satisfies afterwards.",
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const config = [
|
|
265
|
+
{
|
|
266
|
+
ignores: [
|
|
267
|
+
"dist/",
|
|
268
|
+
"build/",
|
|
269
|
+
"coverage/",
|
|
270
|
+
// The docs viewer is the standard template, with its own packages and build.
|
|
271
|
+
"docsSite/",
|
|
272
|
+
"**/vendor/**",
|
|
273
|
+
],
|
|
274
|
+
},
|
|
196
275
|
js.configs.recommended,
|
|
197
|
-
|
|
276
|
+
eslintCommentsRecommended,
|
|
198
277
|
importX.recommended,
|
|
199
278
|
importX.typescript,
|
|
200
279
|
unicorn.configs.recommended,
|
|
@@ -208,7 +287,7 @@ export default [
|
|
|
208
287
|
"@eslint-community/eslint-comments/require-description":
|
|
209
288
|
"error",
|
|
210
289
|
"func-style": ["error", "expression"],
|
|
211
|
-
"max-depth": ["error",
|
|
290
|
+
"max-depth": ["error", MAXIMUM_NESTING_DEPTH],
|
|
212
291
|
"max-lines": [
|
|
213
292
|
"error",
|
|
214
293
|
{ max: 300, skipBlankLines: true, skipComments: true },
|
|
@@ -228,34 +307,9 @@ export default [
|
|
|
228
307
|
],
|
|
229
308
|
"no-restricted-syntax": [
|
|
230
309
|
"error",
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
},
|
|
310
|
+
NO_SATISFIES,
|
|
311
|
+
...RESTRICTED_SYNTAX,
|
|
312
|
+
...SINGLE_OBJECT_PARAMETER,
|
|
259
313
|
],
|
|
260
314
|
"import-x/no-cycle": "error",
|
|
261
315
|
"import-x/no-default-export": "error",
|
|
@@ -334,8 +388,21 @@ export default [
|
|
|
334
388
|
files: ["**/*.config.{js,mjs,ts}"],
|
|
335
389
|
rules: { "unicorn/no-top-level-side-effects": "off" },
|
|
336
390
|
},
|
|
391
|
+
{
|
|
392
|
+
// Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
|
|
393
|
+
files: ["**/*.stories.{ts,tsx}"],
|
|
394
|
+
rules: {
|
|
395
|
+
"no-restricted-syntax": [
|
|
396
|
+
"error",
|
|
397
|
+
...RESTRICTED_SYNTAX,
|
|
398
|
+
...SINGLE_OBJECT_PARAMETER,
|
|
399
|
+
],
|
|
400
|
+
},
|
|
401
|
+
},
|
|
337
402
|
prettier,
|
|
338
403
|
];
|
|
404
|
+
|
|
405
|
+
export default config;
|
|
339
406
|
```
|
|
340
407
|
|
|
341
408
|
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.
|
|
@@ -346,7 +413,7 @@ Start from the [TypeScript](#typescript) config and make five changes:
|
|
|
346
413
|
|
|
347
414
|
```js title="eslint.config.mjs"
|
|
348
415
|
import js from "@eslint/js";
|
|
349
|
-
import
|
|
416
|
+
import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
|
|
350
417
|
import prettier from "eslint-config-prettier";
|
|
351
418
|
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
|
|
352
419
|
import { flatConfigs as importX } from "eslint-plugin-import-x";
|
|
@@ -356,10 +423,65 @@ import unicorn from "eslint-plugin-unicorn";
|
|
|
356
423
|
import globals from "globals";
|
|
357
424
|
import { configs as typescript } from "typescript-eslint";
|
|
358
425
|
|
|
359
|
-
|
|
360
|
-
|
|
426
|
+
// Code patterns that are errors in every file.
|
|
427
|
+
const RESTRICTED_SYNTAX = [
|
|
428
|
+
{
|
|
429
|
+
selector: "ClassDeclaration",
|
|
430
|
+
message: "Use functions, not classes.",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
selector: "ClassExpression",
|
|
434
|
+
message: "Use functions, not classes.",
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
selector: "VariableDeclarator > FunctionExpression",
|
|
438
|
+
message: "Use an arrow function.",
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
selector: "VariableDeclaration[kind='let']",
|
|
442
|
+
message:
|
|
443
|
+
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
444
|
+
},
|
|
445
|
+
];
|
|
446
|
+
|
|
447
|
+
// Functions take one parameter: a single object when they need more than one value.
|
|
448
|
+
const SINGLE_OBJECT_PARAMETER = [
|
|
449
|
+
{
|
|
450
|
+
selector: "FunctionDeclaration[params.length>1]",
|
|
451
|
+
message:
|
|
452
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
selector:
|
|
456
|
+
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
457
|
+
message:
|
|
458
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
459
|
+
},
|
|
460
|
+
];
|
|
461
|
+
|
|
462
|
+
// Deeper than this, return early instead.
|
|
463
|
+
const MAXIMUM_NESTING_DEPTH = 3;
|
|
464
|
+
|
|
465
|
+
// Type a value where it is declared. Story files are the exception: see below.
|
|
466
|
+
const NO_SATISFIES = {
|
|
467
|
+
selector: "TSSatisfiesExpression",
|
|
468
|
+
message:
|
|
469
|
+
"Type a value where it is declared, not with satisfies afterwards.",
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const config = [
|
|
473
|
+
{
|
|
474
|
+
ignores: [
|
|
475
|
+
"dist/",
|
|
476
|
+
"build/",
|
|
477
|
+
"coverage/",
|
|
478
|
+
// The docs viewer is the standard template, with its own packages and build.
|
|
479
|
+
"docsSite/",
|
|
480
|
+
"**/vendor/**",
|
|
481
|
+
],
|
|
482
|
+
},
|
|
361
483
|
js.configs.recommended,
|
|
362
|
-
|
|
484
|
+
eslintCommentsRecommended,
|
|
363
485
|
importX.recommended,
|
|
364
486
|
importX.typescript,
|
|
365
487
|
unicorn.configs.recommended,
|
|
@@ -381,7 +503,7 @@ export default [
|
|
|
381
503
|
"@eslint-community/eslint-comments/require-description":
|
|
382
504
|
"error",
|
|
383
505
|
"func-style": ["error", "expression"],
|
|
384
|
-
"max-depth": ["error",
|
|
506
|
+
"max-depth": ["error", MAXIMUM_NESTING_DEPTH],
|
|
385
507
|
"max-lines": [
|
|
386
508
|
"error",
|
|
387
509
|
{ max: 300, skipBlankLines: true, skipComments: true },
|
|
@@ -401,34 +523,9 @@ export default [
|
|
|
401
523
|
],
|
|
402
524
|
"no-restricted-syntax": [
|
|
403
525
|
"error",
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
},
|
|
526
|
+
NO_SATISFIES,
|
|
527
|
+
...RESTRICTED_SYNTAX,
|
|
528
|
+
...SINGLE_OBJECT_PARAMETER,
|
|
432
529
|
],
|
|
433
530
|
"import-x/no-cycle": "error",
|
|
434
531
|
"import-x/no-default-export": "error",
|
|
@@ -516,8 +613,21 @@ export default [
|
|
|
516
613
|
files: ["**/*.config.{js,mjs,ts}"],
|
|
517
614
|
rules: { "unicorn/no-top-level-side-effects": "off" },
|
|
518
615
|
},
|
|
616
|
+
{
|
|
617
|
+
// Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
|
|
618
|
+
files: ["**/*.stories.{ts,tsx}"],
|
|
619
|
+
rules: {
|
|
620
|
+
"no-restricted-syntax": [
|
|
621
|
+
"error",
|
|
622
|
+
...RESTRICTED_SYNTAX,
|
|
623
|
+
...SINGLE_OBJECT_PARAMETER,
|
|
624
|
+
],
|
|
625
|
+
},
|
|
626
|
+
},
|
|
519
627
|
prettier,
|
|
520
628
|
];
|
|
629
|
+
|
|
630
|
+
export default config;
|
|
521
631
|
```
|
|
522
632
|
|
|
523
633
|
:::caution[Pin the React version]
|
|
@@ -528,10 +638,10 @@ Set `settings.react.version` to the project's React version rather than `"detect
|
|
|
528
638
|
|
|
529
639
|
### Next.js
|
|
530
640
|
|
|
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)
|
|
641
|
+
`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). Route handlers in `app/**/route.ts` may take two parameters, because Next.js calls them as `(request, context)`:
|
|
532
642
|
|
|
533
643
|
```js title="eslint.config.mjs"
|
|
534
|
-
import
|
|
644
|
+
import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
|
|
535
645
|
import prettier from "eslint-config-prettier";
|
|
536
646
|
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
|
|
537
647
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
@@ -540,7 +650,54 @@ import { flatConfigs as importX } from "eslint-plugin-import-x";
|
|
|
540
650
|
import unicorn from "eslint-plugin-unicorn";
|
|
541
651
|
import globals from "globals";
|
|
542
652
|
|
|
543
|
-
|
|
653
|
+
// Code patterns that are errors in every file.
|
|
654
|
+
const RESTRICTED_SYNTAX = [
|
|
655
|
+
{
|
|
656
|
+
selector: "ClassDeclaration",
|
|
657
|
+
message: "Use functions, not classes.",
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
selector: "ClassExpression",
|
|
661
|
+
message: "Use functions, not classes.",
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
selector: "VariableDeclarator > FunctionExpression",
|
|
665
|
+
message: "Use an arrow function.",
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
selector: "VariableDeclaration[kind='let']",
|
|
669
|
+
message:
|
|
670
|
+
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
671
|
+
},
|
|
672
|
+
];
|
|
673
|
+
|
|
674
|
+
// Functions take one parameter: a single object when they need more than one value.
|
|
675
|
+
// Route handlers are the exception: see below.
|
|
676
|
+
const SINGLE_OBJECT_PARAMETER = [
|
|
677
|
+
{
|
|
678
|
+
selector: "FunctionDeclaration[params.length>1]",
|
|
679
|
+
message:
|
|
680
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
selector:
|
|
684
|
+
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
685
|
+
message:
|
|
686
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
687
|
+
},
|
|
688
|
+
];
|
|
689
|
+
|
|
690
|
+
// Deeper than this, return early instead.
|
|
691
|
+
const MAXIMUM_NESTING_DEPTH = 3;
|
|
692
|
+
|
|
693
|
+
// Type a value where it is declared. Story files are the exception: see below.
|
|
694
|
+
const NO_SATISFIES = {
|
|
695
|
+
selector: "TSSatisfiesExpression",
|
|
696
|
+
message:
|
|
697
|
+
"Type a value where it is declared, not with satisfies afterwards.",
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
const config = [
|
|
544
701
|
{
|
|
545
702
|
ignores: [
|
|
546
703
|
".next/",
|
|
@@ -548,12 +705,14 @@ export default [
|
|
|
548
705
|
"build/",
|
|
549
706
|
"coverage/",
|
|
550
707
|
"next-env.d.ts",
|
|
708
|
+
// The docs viewer is the standard template, with its own packages and build.
|
|
709
|
+
"docsSite/",
|
|
551
710
|
"**/vendor/**",
|
|
552
711
|
],
|
|
553
712
|
},
|
|
554
713
|
...nextVitals,
|
|
555
714
|
...nextTypescript,
|
|
556
|
-
|
|
715
|
+
eslintCommentsRecommended,
|
|
557
716
|
importX.recommended,
|
|
558
717
|
importX.typescript,
|
|
559
718
|
unicorn.configs.recommended,
|
|
@@ -571,7 +730,7 @@ export default [
|
|
|
571
730
|
"@eslint-community/eslint-comments/require-description":
|
|
572
731
|
"error",
|
|
573
732
|
"func-style": ["error", "expression"],
|
|
574
|
-
"max-depth": ["error",
|
|
733
|
+
"max-depth": ["error", MAXIMUM_NESTING_DEPTH],
|
|
575
734
|
"max-lines": [
|
|
576
735
|
"error",
|
|
577
736
|
{ max: 300, skipBlankLines: true, skipComments: true },
|
|
@@ -591,34 +750,9 @@ export default [
|
|
|
591
750
|
],
|
|
592
751
|
"no-restricted-syntax": [
|
|
593
752
|
"error",
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
},
|
|
753
|
+
NO_SATISFIES,
|
|
754
|
+
...RESTRICTED_SYNTAX,
|
|
755
|
+
...SINGLE_OBJECT_PARAMETER,
|
|
622
756
|
],
|
|
623
757
|
"import-x/no-cycle": "error",
|
|
624
758
|
"import-x/no-default-export": "error",
|
|
@@ -710,8 +844,32 @@ export default [
|
|
|
710
844
|
files: ["**/*.config.{js,mjs,ts}"],
|
|
711
845
|
rules: { "unicorn/no-top-level-side-effects": "off" },
|
|
712
846
|
},
|
|
847
|
+
{
|
|
848
|
+
// Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
|
|
849
|
+
files: ["**/*.stories.{ts,tsx}"],
|
|
850
|
+
rules: {
|
|
851
|
+
"no-restricted-syntax": [
|
|
852
|
+
"error",
|
|
853
|
+
...RESTRICTED_SYNTAX,
|
|
854
|
+
...SINGLE_OBJECT_PARAMETER,
|
|
855
|
+
],
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
// Next.js calls a route handler with two values, (request, context), so route.ts files may take two parameters.
|
|
860
|
+
files: ["app/**/route.ts"],
|
|
861
|
+
rules: {
|
|
862
|
+
"no-restricted-syntax": [
|
|
863
|
+
"error",
|
|
864
|
+
NO_SATISFIES,
|
|
865
|
+
...RESTRICTED_SYNTAX,
|
|
866
|
+
],
|
|
867
|
+
},
|
|
868
|
+
},
|
|
713
869
|
prettier,
|
|
714
870
|
];
|
|
871
|
+
|
|
872
|
+
export default config;
|
|
715
873
|
```
|
|
716
874
|
|
|
717
875
|
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.
|
|
@@ -727,7 +885,7 @@ Use the `globals` package rather than turning `no-undef` off or hand-writing a g
|
|
|
727
885
|
Add a block before `prettier` to add or override rules:
|
|
728
886
|
|
|
729
887
|
```js
|
|
730
|
-
|
|
888
|
+
const config = [
|
|
731
889
|
// ...
|
|
732
890
|
{
|
|
733
891
|
rules: {
|
|
@@ -737,6 +895,8 @@ export default [
|
|
|
737
895
|
},
|
|
738
896
|
prettier,
|
|
739
897
|
];
|
|
898
|
+
|
|
899
|
+
export default config;
|
|
740
900
|
```
|
|
741
901
|
|
|
742
902
|
Keep `prettier` last — it switches off every rule that would fight the formatter.
|
|
@@ -786,7 +946,7 @@ Third-party code that is copied into the repository lives in a `vendor/` folder,
|
|
|
786
946
|
|
|
787
947
|
`vendor/` is only for third-party code we **do not change** — code that is replaced wholesale when the package updates.
|
|
788
948
|
|
|
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).
|
|
949
|
+
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](/docs/Guides/component-library-workflow#adding-a-shadcn-component).
|
|
790
950
|
|
|
791
951
|
| Code | Ours? | Follows the standards? |
|
|
792
952
|
| --------------------------------------------------------- | ------- | ------------------------------------------ |
|
|
@@ -799,11 +959,11 @@ If a vendored package lives somewhere else and cannot be moved, add its path to
|
|
|
799
959
|
|
|
800
960
|
## Key rule decisions
|
|
801
961
|
|
|
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).
|
|
962
|
+
Most of these enforce the [Readable Code](/docs/Standards/readable-code) rules. For what each error means and how to fix it, see [Fixing Lint Errors](/docs/Standards/lint-errors).
|
|
803
963
|
|
|
804
964
|
### `func-style`, `prefer-arrow-callback`: arrow functions everywhere
|
|
805
965
|
|
|
806
|
-
Every function is an arrow function. See [Readable Code](/Standards/readable-code#arrow-functions-everywhere).
|
|
966
|
+
Every function is an arrow function. See [Readable Code](/docs/Standards/readable-code#arrow-functions-everywhere).
|
|
807
967
|
|
|
808
968
|
- `func-style: expression` rejects `function name() {}` declarations.
|
|
809
969
|
- `prefer-arrow-callback` rejects `function` callbacks.
|
|
@@ -813,58 +973,60 @@ Every function is an arrow function. See [Readable Code](/Standards/readable-cod
|
|
|
813
973
|
|
|
814
974
|
### `no-restricted-syntax`, `prefer-const`, `no-var`: always `const`
|
|
815
975
|
|
|
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).
|
|
976
|
+
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](/docs/Standards/readable-code#always-const).
|
|
817
977
|
|
|
818
978
|
### `@eslint-community/eslint-comments/require-description`
|
|
819
979
|
|
|
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).
|
|
980
|
+
Every `eslint-disable` comment must say why, after `--`. A rule is never switched off silently. See [the only allowed exceptions](/docs/Standards/lint-errors#the-only-allowed-exceptions).
|
|
821
981
|
|
|
822
982
|
### `max-lines: 300`
|
|
823
983
|
|
|
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).
|
|
984
|
+
No file is longer than 300 lines, not counting blank lines and comments. Test files are exempt. See [Readable Code](/docs/Standards/readable-code#no-file-longer-than-300-lines).
|
|
825
985
|
|
|
826
986
|
### `max-depth: 3`
|
|
827
987
|
|
|
828
|
-
Blocks nest at most three levels deep. Return early instead. See [Readable Code](/Standards/readable-code#return-early-nest-shallow).
|
|
988
|
+
Blocks nest at most three levels deep. Return early instead. The configs name it `MAXIMUM_NESTING_DEPTH`, because they follow their own `no-magic-numbers` rule. See [Readable Code](/docs/Standards/readable-code#return-early-nest-shallow).
|
|
829
989
|
|
|
830
990
|
### `no-nested-ternary`
|
|
831
991
|
|
|
832
|
-
A ternary inside a ternary is an error. See [Readable Code](/Standards/readable-code#no-nested-ternaries).
|
|
992
|
+
A ternary inside a ternary is an error. See [Readable Code](/docs/Standards/readable-code#no-nested-ternaries).
|
|
833
993
|
|
|
834
994
|
### `no-magic-numbers`
|
|
835
995
|
|
|
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).
|
|
996
|
+
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](/docs/Standards/readable-code#no-magic-numbers-or-strings).
|
|
837
997
|
|
|
838
998
|
### `no-restricted-syntax`: no classes, one parameter
|
|
839
999
|
|
|
840
1000
|
These are banned by syntax, along with `function` expressions (above):
|
|
841
1001
|
|
|
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).
|
|
1002
|
+
- **Class declarations and class expressions.** See [Readable Code](/docs/Standards/readable-code#functions-never-classes).
|
|
1003
|
+
- **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](/docs/Standards/readable-code#one-parameter-always-an-object-for-more).
|
|
1004
|
+
|
|
1005
|
+
The configs keep the one-parameter selectors in their own `SINGLE_OBJECT_PARAMETER` list, so a file that must take more than one parameter can switch off just those. The Next.js config does this for route handlers in `app/**/route.ts`, which Next.js calls as `(request, context)`.
|
|
844
1006
|
|
|
845
1007
|
### `import-x/no-cycle`: no circular imports
|
|
846
1008
|
|
|
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).
|
|
1009
|
+
A file may never import a file that imports it back, directly or through a chain of other files. See [Import safety](/docs/Structure/import-organization#import-safety).
|
|
848
1010
|
|
|
849
1011
|
### `no-use-before-define`
|
|
850
1012
|
|
|
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).
|
|
1013
|
+
A function or variable is defined above the code that uses it. TypeScript files use `@typescript-eslint/no-use-before-define`. See [Import safety](/docs/Structure/import-organization#import-safety).
|
|
852
1014
|
|
|
853
1015
|
### `import-x/no-default-export`
|
|
854
1016
|
|
|
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).
|
|
1017
|
+
Named exports only. Default exports are allowed where a tool requires them: config files, Storybook stories and Next.js route files. See [Readable Code](/docs/Standards/readable-code#named-exports-only).
|
|
856
1018
|
|
|
857
1019
|
### Import order is not linted
|
|
858
1020
|
|
|
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.
|
|
1021
|
+
There is deliberately no import-**order** rule (circular imports are a different matter — see above). Follow the [Import Organization](/docs/Structure/import-organization) pattern for consistency, but ESLint will not complain about it.
|
|
860
1022
|
|
|
861
1023
|
### `unicorn/filename-case: camelCase or PascalCase`
|
|
862
1024
|
|
|
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`).
|
|
1025
|
+
The plugin's default is kebab-case, which contradicts our [naming conventions](/docs/Structure/naming-conventions). Files are camelCase (`formatDate.ts`), and component files are PascalCase (`UserProfile.tsx`).
|
|
864
1026
|
|
|
865
1027
|
### `unicorn/name-replacements: error`
|
|
866
1028
|
|
|
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`.)
|
|
1029
|
+
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](/docs/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](/docs/Standards/human-readable-names). (Before `eslint-plugin-unicorn` 77 this rule was called `unicorn/prevent-abbreviations`.)
|
|
868
1030
|
|
|
869
1031
|
### `@typescript-eslint/no-explicit-any: error`
|
|
870
1032
|
|
|
@@ -874,9 +1036,13 @@ Names are full English words: `btn` must be `button`, `e` must be `error` or `ev
|
|
|
874
1036
|
|
|
875
1037
|
The `!` operator tells the compiler a value exists without checking. Check it instead.
|
|
876
1038
|
|
|
1039
|
+
### `no-restricted-syntax`: type first, never `satisfies`
|
|
1040
|
+
|
|
1041
|
+
A value gets its type where it is declared: `const config: ConfigType = { … }`. `satisfies` attaches the type afterwards, so it is an error in the TypeScript, React and Next.js configs, except in story files (`*.stories.{ts,tsx}`), which follow Storybook's docs. The rule lists live in `RESTRICTED_SYNTAX` and `NO_SATISFIES` at the top of each config, so story files use every other restriction without repeating it. See [Type first, never `satisfies`](/docs/Standards/readable-code#type-first-never-satisfies).
|
|
1042
|
+
|
|
877
1043
|
### `@typescript-eslint/consistent-type-assertions: never`
|
|
878
1044
|
|
|
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:
|
|
1045
|
+
`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](/docs/Standards/readable-code#no-any-no--no-unexplained-as) requires:
|
|
880
1046
|
|
|
881
1047
|
```ts
|
|
882
1048
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- the API returns untyped JSON; validated by parseUser below
|
|
@@ -885,7 +1051,7 @@ const user = response.data as UserType;
|
|
|
885
1051
|
|
|
886
1052
|
### `@typescript-eslint/naming-convention`: `Props` or `Type` suffix
|
|
887
1053
|
|
|
888
|
-
Every interface and type alias ends in `Props` or `Type`. See [Naming Conventions](/Structure/naming-conventions#types--interfaces).
|
|
1054
|
+
Every interface and type alias ends in `Props` or `Type`. See [Naming Conventions](/docs/Structure/naming-conventions#types--interfaces).
|
|
889
1055
|
|
|
890
1056
|
### `@typescript-eslint/consistent-type-imports: error`
|
|
891
1057
|
|
|
@@ -893,8 +1059,8 @@ Type-only imports use `import type { Foo }` or inline `import { type Foo }`. Thi
|
|
|
893
1059
|
|
|
894
1060
|
### `react/jsx-no-bind: error`
|
|
895
1061
|
|
|
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).
|
|
1062
|
+
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](/docs/Structure/component-organization#-writing-a-component).
|
|
897
1063
|
|
|
898
1064
|
### `react/no-multi-comp: error`
|
|
899
1065
|
|
|
900
|
-
One component per file. See [Readable Code](/Standards/readable-code#one-component-per-file).
|
|
1066
|
+
One component per file. See [Readable Code](/docs/Standards/readable-code#one-component-per-file).
|