@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.
@@ -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 comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
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
- export default [
84
- { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
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
- comments.recommended,
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", 3],
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
- 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
- },
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 comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
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
- export default [
195
- { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
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
- comments.recommended,
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", 3],
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
- 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
- },
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 comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
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
- export default [
360
- { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
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
- comments.recommended,
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", 3],
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
- 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
- },
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 comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
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
- export default [
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
- comments.recommended,
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", 3],
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
- 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
- },
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
- export default [
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).