@activatus/code-standards 3.0.4 → 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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # 🦘 Activatus Code Standards
2
2
 
3
+ ## 👋 About Code Standards
4
+
3
5
  **One way of doing things, written down, so nobody has to guess.**
4
6
 
5
7
  This is the rulebook for how we write code at Activatus. It's a docs site, a pile of copy-paste config files, and a set of lint rules that'll dob you in before your reviewer has to.
@@ -30,7 +32,7 @@ pnpm add -D @activatus/code-standards
30
32
 
31
33
  Then tell your agent: **"Follow the Activatus Code Standards. Start with `node_modules/@activatus/code-standards/docs/Guides/setup.md`."** It prints a numbered list of the standards, you say which ones you don't want, and it sets up the rest. You never touch the terminal.
32
34
 
33
- The package version is the standards version: `2.0.0` is version 2.
35
+ The package version is the standards version: `3.0.0` is version 3.
34
36
 
35
37
  > 🔢 **Updating a project to newer standards?** Run `pnpm update @activatus/code-standards --latest`, then point the agent at the **[Version History](https://activatus-code-standards.vercel.app/docs/version-history)**: it reads only the versions newer than the project's, does exactly what each one says, and bumps the number. Cheaper than shouting the whole pub a round.
36
38
 
@@ -72,7 +74,7 @@ The live site is at **[activatus-code-standards.vercel.app](https://activatus-co
72
74
  | 📖 **Ontology** | The code words we use and what each one means |
73
75
  | 🔢 **[Version History](https://activatus-code-standards.vercel.app/docs/version-history)** | What changed in each version, and what a project has to do about it. Updating a project? Read only the versions newer than the one in its `AGENTS.md` |
74
76
  | 🧭 **Guides** | Setting up a new project, dragging an old one up to scratch, and how design becomes code |
75
- | 📏 **Standards** | Readable code, linting, formatting, styling, commits, the tech stack and AI agent rules |
77
+ | 📏 **Standards** | Readable code, linting, formatting, styling, the tech stack and AI agent rules |
76
78
  | 🏗️ **Structure** | Folders, components, naming, imports and API routes |
77
79
  | 🧩 **[Design System Components](https://activatus-code-standards.vercel.app/docs/design-system-components)** | Every standard component and its props, so each project builds them the same way |
78
80
 
@@ -131,7 +133,7 @@ The full detail's on the site. This is the version you'd tell someone over a sch
131
133
 
132
134
  ## 📋 Just want the files?
133
135
 
134
- Fair enough. Head to the **Templates** page on the site, or grab them straight from [`static/templates/`](static/templates):
136
+ Fair enough. Head to the **Templates** page on the site, or grab them straight from [`static/templates/`](https://github.com/activatus-life/code-standards/tree/main/static/templates):
135
137
 
136
138
  ```text
137
139
  static/templates/
@@ -177,7 +179,7 @@ Reckon a rule's wrong, or there's one missing? Good. Standards that never change
177
179
 
178
180
  1. 🌿 **Branch off `main`**, named after the ticket (`ACT-14-update-eslint-page`), or `type/short-description` if there's no ticket (`docs/update-eslint-page`).
179
181
  2. 📝 **Edit or add a Markdown file in `docs/`.** Name it in kebab-case (`naming-conventions.md`, not `namingConventions.md`), because the file name becomes the page's URL. Yes, we follow our own rules here.
180
- 3. 📋 **Changing a config?** Edit the file in `static/templates/` and the matching page in `docs/Standards/` so the two don't drift apart.
182
+ 3. 📋 **Changing a config?** Edit the file in `static/templates/` and the matching page in `docs/Standards/` so the two don't drift apart. For an ESLint config, run `pnpm check:eslint-templates`: it lints each config against itself and fails if the ESLint page shows an old copy. Add `--update-docs` to copy the configs onto the page.
181
183
  4. 🔢 **Add a version** to `docs/version-history.md`: what changed, and what a project has to do about it. Bump the major version in `package.json` to match (version 3 is `3.0.0`), and publish it to npm (npmjs.com) once it's merged. We use pnpm for everything, so that's `pnpm publish`, which lands in the same npm registry as `npm publish`. Projects read only the new versions, so if it's not in there, nobody hears about it.
182
184
  5. 🔨 **Run `pnpm build`.** It fails on broken links, so you'll know quick smart if you've stuffed one.
183
185
  6. 🚀 **Open a pull request** and say why the change makes life better.
@@ -54,7 +54,7 @@ Add the `.editorconfig` file. No code changes needed — your editor will handle
54
54
  Add the [standard config](/docs/Standards/eslint) for your project type, then start in warn-only mode to see what you're dealing with. Add a block before `prettier`:
55
55
 
56
56
  ```js
57
- export default [
57
+ const config = [
58
58
  // ...
59
59
  {
60
60
  rules: {
@@ -66,6 +66,8 @@ export default [
66
66
  },
67
67
  prettier,
68
68
  ];
69
+
70
+ export default config;
69
71
  ```
70
72
 
71
73
  Run `pnpm lint` and triage the output. Fix them one kind at a time (for example, all `any` removals together). Once clean, remove the overrides.
@@ -64,7 +64,7 @@ Tell me the numbers to skip or change, or say "all good".
64
64
  10. Node version set by .nvmrc and direnv
65
65
  11. Styling: Tailwind, flexbox first, colours only from the global palette
66
66
  12. Folder structure: folders at the project root; reusable helper functions in utils/ (no business rules, no outside calls)
67
- 12.1. Constants: r for every link, s for every piece of text, noData for local stand-in data, all in lib/constants/
67
+ 12.1. Constants: r for every link, s for every piece of text, noData for local stand-in data, shared numbers by topic, all in lib/constants/
68
68
  13. Components live where they are used: components/ui/, components/shared/, or beside their page
69
69
  14. Component files in a fixed order, with index.ts exports, and server and client code in separate files
70
70
  15. Design System Components: every project builds the standard components with the same props
@@ -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";
@@ -210,6 +234,10 @@ const RESTRICTED_SYNTAX = [
210
234
  message:
211
235
  "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
212
236
  },
237
+ ];
238
+
239
+ // Functions take one parameter: a single object when they need more than one value.
240
+ const SINGLE_OBJECT_PARAMETER = [
213
241
  {
214
242
  selector: "FunctionDeclaration[params.length>1]",
215
243
  message:
@@ -223,6 +251,9 @@ const RESTRICTED_SYNTAX = [
223
251
  },
224
252
  ];
225
253
 
254
+ // Deeper than this, return early instead.
255
+ const MAXIMUM_NESTING_DEPTH = 3;
256
+
226
257
  // Type a value where it is declared. Story files are the exception: see below.
227
258
  const NO_SATISFIES = {
228
259
  selector: "TSSatisfiesExpression",
@@ -230,10 +261,19 @@ const NO_SATISFIES = {
230
261
  "Type a value where it is declared, not with satisfies afterwards.",
231
262
  };
232
263
 
233
- export default [
234
- { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
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
+ },
235
275
  js.configs.recommended,
236
- comments.recommended,
276
+ eslintCommentsRecommended,
237
277
  importX.recommended,
238
278
  importX.typescript,
239
279
  unicorn.configs.recommended,
@@ -247,7 +287,7 @@ export default [
247
287
  "@eslint-community/eslint-comments/require-description":
248
288
  "error",
249
289
  "func-style": ["error", "expression"],
250
- "max-depth": ["error", 3],
290
+ "max-depth": ["error", MAXIMUM_NESTING_DEPTH],
251
291
  "max-lines": [
252
292
  "error",
253
293
  { max: 300, skipBlankLines: true, skipComments: true },
@@ -269,6 +309,7 @@ export default [
269
309
  "error",
270
310
  NO_SATISFIES,
271
311
  ...RESTRICTED_SYNTAX,
312
+ ...SINGLE_OBJECT_PARAMETER,
272
313
  ],
273
314
  "import-x/no-cycle": "error",
274
315
  "import-x/no-default-export": "error",
@@ -351,11 +392,17 @@ export default [
351
392
  // Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
352
393
  files: ["**/*.stories.{ts,tsx}"],
353
394
  rules: {
354
- "no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
395
+ "no-restricted-syntax": [
396
+ "error",
397
+ ...RESTRICTED_SYNTAX,
398
+ ...SINGLE_OBJECT_PARAMETER,
399
+ ],
355
400
  },
356
401
  },
357
402
  prettier,
358
403
  ];
404
+
405
+ export default config;
359
406
  ```
360
407
 
361
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.
@@ -366,7 +413,7 @@ Start from the [TypeScript](#typescript) config and make five changes:
366
413
 
367
414
  ```js title="eslint.config.mjs"
368
415
  import js from "@eslint/js";
369
- import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
416
+ import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
370
417
  import prettier from "eslint-config-prettier";
371
418
  import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
372
419
  import { flatConfigs as importX } from "eslint-plugin-import-x";
@@ -395,6 +442,10 @@ const RESTRICTED_SYNTAX = [
395
442
  message:
396
443
  "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
397
444
  },
445
+ ];
446
+
447
+ // Functions take one parameter: a single object when they need more than one value.
448
+ const SINGLE_OBJECT_PARAMETER = [
398
449
  {
399
450
  selector: "FunctionDeclaration[params.length>1]",
400
451
  message:
@@ -408,6 +459,9 @@ const RESTRICTED_SYNTAX = [
408
459
  },
409
460
  ];
410
461
 
462
+ // Deeper than this, return early instead.
463
+ const MAXIMUM_NESTING_DEPTH = 3;
464
+
411
465
  // Type a value where it is declared. Story files are the exception: see below.
412
466
  const NO_SATISFIES = {
413
467
  selector: "TSSatisfiesExpression",
@@ -415,10 +469,19 @@ const NO_SATISFIES = {
415
469
  "Type a value where it is declared, not with satisfies afterwards.",
416
470
  };
417
471
 
418
- export default [
419
- { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
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
+ },
420
483
  js.configs.recommended,
421
- comments.recommended,
484
+ eslintCommentsRecommended,
422
485
  importX.recommended,
423
486
  importX.typescript,
424
487
  unicorn.configs.recommended,
@@ -440,7 +503,7 @@ export default [
440
503
  "@eslint-community/eslint-comments/require-description":
441
504
  "error",
442
505
  "func-style": ["error", "expression"],
443
- "max-depth": ["error", 3],
506
+ "max-depth": ["error", MAXIMUM_NESTING_DEPTH],
444
507
  "max-lines": [
445
508
  "error",
446
509
  { max: 300, skipBlankLines: true, skipComments: true },
@@ -462,6 +525,7 @@ export default [
462
525
  "error",
463
526
  NO_SATISFIES,
464
527
  ...RESTRICTED_SYNTAX,
528
+ ...SINGLE_OBJECT_PARAMETER,
465
529
  ],
466
530
  "import-x/no-cycle": "error",
467
531
  "import-x/no-default-export": "error",
@@ -553,11 +617,17 @@ export default [
553
617
  // Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
554
618
  files: ["**/*.stories.{ts,tsx}"],
555
619
  rules: {
556
- "no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
620
+ "no-restricted-syntax": [
621
+ "error",
622
+ ...RESTRICTED_SYNTAX,
623
+ ...SINGLE_OBJECT_PARAMETER,
624
+ ],
557
625
  },
558
626
  },
559
627
  prettier,
560
628
  ];
629
+
630
+ export default config;
561
631
  ```
562
632
 
563
633
  :::caution[Pin the React version]
@@ -568,10 +638,10 @@ Set `settings.react.version` to the project's React version rather than `"detect
568
638
 
569
639
  ### Next.js
570
640
 
571
- `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)`:
572
642
 
573
643
  ```js title="eslint.config.mjs"
574
- import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
644
+ import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
575
645
  import prettier from "eslint-config-prettier";
576
646
  import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
577
647
  import nextVitals from "eslint-config-next/core-web-vitals";
@@ -599,6 +669,11 @@ const RESTRICTED_SYNTAX = [
599
669
  message:
600
670
  "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
601
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 = [
602
677
  {
603
678
  selector: "FunctionDeclaration[params.length>1]",
604
679
  message:
@@ -612,6 +687,9 @@ const RESTRICTED_SYNTAX = [
612
687
  },
613
688
  ];
614
689
 
690
+ // Deeper than this, return early instead.
691
+ const MAXIMUM_NESTING_DEPTH = 3;
692
+
615
693
  // Type a value where it is declared. Story files are the exception: see below.
616
694
  const NO_SATISFIES = {
617
695
  selector: "TSSatisfiesExpression",
@@ -619,7 +697,7 @@ const NO_SATISFIES = {
619
697
  "Type a value where it is declared, not with satisfies afterwards.",
620
698
  };
621
699
 
622
- export default [
700
+ const config = [
623
701
  {
624
702
  ignores: [
625
703
  ".next/",
@@ -627,12 +705,14 @@ export default [
627
705
  "build/",
628
706
  "coverage/",
629
707
  "next-env.d.ts",
708
+ // The docs viewer is the standard template, with its own packages and build.
709
+ "docsSite/",
630
710
  "**/vendor/**",
631
711
  ],
632
712
  },
633
713
  ...nextVitals,
634
714
  ...nextTypescript,
635
- comments.recommended,
715
+ eslintCommentsRecommended,
636
716
  importX.recommended,
637
717
  importX.typescript,
638
718
  unicorn.configs.recommended,
@@ -650,7 +730,7 @@ export default [
650
730
  "@eslint-community/eslint-comments/require-description":
651
731
  "error",
652
732
  "func-style": ["error", "expression"],
653
- "max-depth": ["error", 3],
733
+ "max-depth": ["error", MAXIMUM_NESTING_DEPTH],
654
734
  "max-lines": [
655
735
  "error",
656
736
  { max: 300, skipBlankLines: true, skipComments: true },
@@ -672,6 +752,7 @@ export default [
672
752
  "error",
673
753
  NO_SATISFIES,
674
754
  ...RESTRICTED_SYNTAX,
755
+ ...SINGLE_OBJECT_PARAMETER,
675
756
  ],
676
757
  "import-x/no-cycle": "error",
677
758
  "import-x/no-default-export": "error",
@@ -767,11 +848,28 @@ export default [
767
848
  // Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
768
849
  files: ["**/*.stories.{ts,tsx}"],
769
850
  rules: {
770
- "no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
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
+ ],
771
867
  },
772
868
  },
773
869
  prettier,
774
870
  ];
871
+
872
+ export default config;
775
873
  ```
776
874
 
777
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.
@@ -787,7 +885,7 @@ Use the `globals` package rather than turning `no-undef` off or hand-writing a g
787
885
  Add a block before `prettier` to add or override rules:
788
886
 
789
887
  ```js
790
- export default [
888
+ const config = [
791
889
  // ...
792
890
  {
793
891
  rules: {
@@ -797,6 +895,8 @@ export default [
797
895
  },
798
896
  prettier,
799
897
  ];
898
+
899
+ export default config;
800
900
  ```
801
901
 
802
902
  Keep `prettier` last — it switches off every rule that would fight the formatter.
@@ -885,7 +985,7 @@ No file is longer than 300 lines, not counting blank lines and comments. Test fi
885
985
 
886
986
  ### `max-depth: 3`
887
987
 
888
- Blocks nest at most three levels deep. Return early instead. See [Readable Code](/docs/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).
889
989
 
890
990
  ### `no-nested-ternary`
891
991
 
@@ -902,6 +1002,8 @@ These are banned by syntax, along with `function` expressions (above):
902
1002
  - **Class declarations and class expressions.** See [Readable Code](/docs/Standards/readable-code#functions-never-classes).
903
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).
904
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)`.
1006
+
905
1007
  ### `import-x/no-cycle`: no circular imports
906
1008
 
907
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).
@@ -309,7 +309,9 @@ if (order.status === "shipped") {
309
309
  }
310
310
  ```
311
311
 
312
- `-1`, `0` and `1` are allowed. See [Naming Conventions](/docs/Structure/naming-conventions#constants) for how to name constants.
312
+ **`-1`, `0` and `1` are allowed**, because they are part of how code works rather than a choice anyone makes: `0` is the start or nothing (`items[0]`, `length === 0`), `1` is one more or one less (`index + 1`), and `-1` is "not found" (`indexOf`). Any other number was chosen by someone, so its name says what it controls. Even `2` gets a name.
313
+
314
+ A number used in one file is named at the top of that file. One shared across the app goes in `lib/constants/`, in a file named after its topic. See [Numbers](/docs/Structure/folder-structure#numbers-named-and-kept-by-topic) for where they go, and [Naming Conventions](/docs/Structure/naming-conventions#constants) for how to name them.
313
315
 
314
316
  ---
315
317
 
@@ -272,7 +272,7 @@ Add the export to `utils/index.ts` whenever you add a function.
272
272
 
273
273
  ---
274
274
 
275
- ## 🔤 Constants: `r`, `s` and noData {#constants}
275
+ ## 🔤 Constants: `r`, `s`, noData and numbers {#constants}
276
276
 
277
277
  Every link, every piece of text on screen and every piece of stand-in data lives in `lib/constants/`, never written straight into a component:
278
278
 
@@ -281,6 +281,7 @@ lib/constants/
281
281
  ├── routes.ts # r: every link
282
282
  ├── strings.ts # s: every piece of static text
283
283
  ├── noData.ts # Stand-in data for local development
284
+ ├── durations.ts # Shared numbers, one file per topic
284
285
  └── index.ts # Exports all of them
285
286
  ```
286
287
 
@@ -356,13 +357,33 @@ const insights =
356
357
  - **Dummy values sit at the top of `noData.ts`** as their own constants, `DUMMY_HEADING` and the rest. They are never added to `s`.
357
358
  - **noData is for the whole site.** All of it lives in this one file.
358
359
 
360
+ ### Numbers: named, and kept by topic
361
+
362
+ **A number other than `-1`, `0` or `1` gets a name**, such as `MAXIMUM_RETRY_ATTEMPTS`. Those three mean "nothing", "one more" and "not found", so they read clearly as they are. Any other number was chosen by someone, and its name says what it controls. ESLint checks this with `no-magic-numbers`.
363
+
364
+ There is no `n` object. Text and links are one kind of thing each, but numbers are not: a retry limit, a cookie lifetime and a page size have nothing in common. Put each number where its meaning lives:
365
+
366
+ | Used by | Where it goes | Example |
367
+ | ------------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------- |
368
+ | One file | At the top of that file | `const MAXIMUM_NESTING_DEPTH = 3;` in `eslint.config.mjs` |
369
+ | More than one file | `lib/constants/`, in a file named after its topic | `lib/constants/durations.ts`, `lib/constants/limits.ts`, `lib/constants/httpStatus.ts` |
370
+
371
+ ```ts title="lib/constants/durations.ts"
372
+ export const COOKIE_DURATION_DAYS = 30;
373
+ export const SESSION_TIMEOUT_MILLISECONDS = 900_000;
374
+ ```
375
+
376
+ - **Start in the file that uses it**, and move it to `lib/constants/` when a second file needs it.
377
+ - **Never a `numbers.ts`** or any other file of unrelated values. Name the file after the topic.
378
+ - **Put the unit in the name**: `_MILLISECONDS`, `_DAYS`, `_PIXELS`.
379
+
359
380
  ### Rules
360
381
 
361
382
  - **Named exports only:** `export const r`, `export const s`. No default exports.
362
383
  - **Files outside `lib/constants/` import from `@/lib/constants`**, all in one import line.
363
384
  - **Files inside `lib/constants/` import each other directly**, `import { s } from "./strings";`, never through `index.ts`. See [Inside a folder, import files directly](/docs/Structure/import-organization#2-inside-a-folder-import-files-directly).
364
385
  - **`r` and `s` are the only single-letter names allowed anywhere.** See [Human-Readable Names](/docs/Standards/human-readable-names#r-and-s).
365
- - **Other constants** that hold a single value, such as `MAXIMUM_RETRY_ATTEMPTS`, follow [Naming Conventions](/docs/Structure/naming-conventions#constants).
386
+ - **Other constants**, such as numbers, are `SCREAMING_SNAKE_CASE`: see [Naming Conventions](/docs/Structure/naming-conventions#constants).
366
387
 
367
388
  ---
368
389
 
@@ -44,9 +44,18 @@ Anyone changing the standards adds a version, alongside the change:
44
44
 
45
45
  ## Version 3
46
46
 
47
- **Released:** 2026-10-17 · **Updates from:** version 2 · **Latest fix:** 3.0.4
47
+ **Released:** 2026-10-17 · **Updates from:** version 2 · **Latest fix:** 3.0.5
48
48
 
49
- Install it with `pnpm add -D @activatus/code-standards@3.0.4`. Do change 1 first: it says which parts of the project's `AGENTS.md` are now the project's own to write.
49
+ Install it with `pnpm add -D @activatus/code-standards@3.0.5`. Do change 1 first: it says which parts of the project's `AGENTS.md` are now the project's own to write.
50
+
51
+ **Fixes in 3.0.5.** The ESLint configs now pass their own rules. Copied in as they were, they gave three problems on the config file itself: `No magic number: 3` (`no-magic-numbers`), and in Next.js projects two warnings, `import/no-anonymous-default-export` and `import-x/no-named-as-default-member`. Copy the new [ESLint config](/docs/Standards/eslint) for the project's framework over `eslint.config.mjs`, keeping any project-specific block. Then run `pnpm lint`: the config file itself has no problems. If the project already fixed these three by hand, there is nothing to do.
52
+
53
+ - `max-depth` uses a named constant, `MAXIMUM_NESTING_DEPTH`.
54
+ - The array is assigned to `config` before `export default config`.
55
+ - The eslint-comments preset is imported by name: `import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";`.
56
+ - Numbers have a home: [Numbers](/docs/Structure/folder-structure#numbers-named-and-kept-by-topic). A number used in one file is named at the top of that file, and one shared across the app goes in `lib/constants/`, in a file named after its topic, such as `durations.ts`. There is no `n` object and no `numbers.ts`. If the project has a file of unrelated numbers, split it by topic. Copy the updated **No magic numbers** line from the [`AGENTS.md` template](/docs/templates#agents).
57
+ - `docsSite/` is ignored. It is the standard docs viewer, with its own packages and build, and without the ignore ESLint lints its built files. If the project already ignores it, nothing changes.
58
+ - The one-parameter selectors moved into their own `SINGLE_OBJECT_PARAMETER` list. **Next.js:** route handlers in `app/**/route.ts` may now take `(request, context)`. If the project switched the rule off for them some other way, such as an override block or `eslint-disable` comments, remove it.
50
59
 
51
60
  **Fixes in 3.0.4.** If the project already applied 3.0.3, do these:
52
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@activatus/code-standards",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "description": "The Activatus code standards: the rules as Markdown, and the config files to copy. Install it so an AI agent can read the standards from node_modules.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,7 @@
9
9
  "homepage": "https://activatus-code-standards.vercel.app",
10
10
  "files": [
11
11
  "docs",
12
- "scripts",
12
+ "scripts/renameSymbol.mjs",
13
13
  "static/templates",
14
14
  "README.md"
15
15
  ],
@@ -18,22 +18,36 @@
18
18
  },
19
19
  "devDependencies": {
20
20
  "@docusaurus/core": "3.10.2",
21
+ "@docusaurus/mdx-loader": "3.10.2",
21
22
  "@docusaurus/module-type-aliases": "3.10.2",
22
23
  "@docusaurus/preset-classic": "3.10.2",
23
24
  "@docusaurus/theme-mermaid": "3.10.2",
24
25
  "@docusaurus/tsconfig": "3.10.2",
25
26
  "@docusaurus/types": "3.10.2",
27
+ "@eslint-community/eslint-plugin-eslint-comments": "^4.8.1",
28
+ "@eslint/js": "^10.0.1",
26
29
  "@getcanary/docusaurus-theme-search-pagefind": "1.0.2",
27
30
  "@getcanary/web": "^1.0.0",
28
31
  "@mdx-js/react": "^3.0.0",
29
32
  "clsx": "^2.0.0",
33
+ "eslint": "^10.13.0",
34
+ "eslint-config-next": "^16.4.2",
35
+ "eslint-config-prettier": "^10.1.8",
36
+ "eslint-import-resolver-typescript": "^4.4.5",
37
+ "eslint-plugin-import-x": "^4.17.1",
38
+ "eslint-plugin-react": "^7.37.5",
39
+ "eslint-plugin-react-hooks": "^7.1.1",
40
+ "eslint-plugin-unicorn": "^78.0.0",
41
+ "globals": "^17.13.0",
42
+ "next": "^16.4.2",
30
43
  "pagefind": "^1.3.0",
31
44
  "prettier": "^3.9.10",
32
45
  "prism-react-renderer": "^2.3.0",
33
46
  "raw-loader": "^4.0.2",
34
47
  "react": "^18.3.1",
35
48
  "react-dom": "^18.3.1",
36
- "typescript": "~5.6.2"
49
+ "typescript": "~5.6.2",
50
+ "typescript-eslint": "^8.71.2"
37
51
  },
38
52
  "browserslist": {
39
53
  "production": [
@@ -58,6 +72,7 @@
58
72
  "clear": "docusaurus clear",
59
73
  "typecheck": "tsc",
60
74
  "format": "prettier --write .",
61
- "format:check": "prettier --check ."
75
+ "format:check": "prettier --check .",
76
+ "check:eslint-templates": "node scripts/checkEslintTemplates.mjs"
62
77
  }
63
78
  }
@@ -113,7 +113,7 @@ Follow these rules in every change:
113
113
  - **Functions, never classes.**
114
114
  - **One parameter per function.** Use a single object when more than one value is needed.
115
115
  - **Return early.** Never nest more than three levels deep.
116
- - **No magic numbers or strings.** Use named constants.
116
+ - **No magic numbers or strings.** Use named constants. Only `-1`, `0` and `1` stay bare. A number used in one file is named at the top of that file; one shared across the app goes in `lib/constants/`, in a file named after its topic (`durations.ts`, `limits.ts`), never a `numbers.ts`. Put the unit in the name: `SESSION_TIMEOUT_MILLISECONDS`.
117
117
  - **Links, text and stand-in data live in `lib/constants/`**, never written straight into a component. `r` holds every link (pages, outside sites, `mailto:`, `tel:`, `sms:`). `s` holds every piece of static text, and its keys become the translation keys when the site gets more languages. `noData.ts` holds stand-in data named `NO_DATA_` plus the thing it replaces (`NO_DATA_INSIGHTS`), used only in local development when the real source can't be reached, never in production. Dummy values sit at the top of `noData.ts`, never in `s`. Import them all in one line, `import { NO_DATA_INSIGHTS, r, s } from "@/lib/constants";`. `r` and `s` are the only single-letter names allowed anywhere. Full rule: https://activatus-code-standards.vercel.app/docs/Structure/folder-structure#constants
118
118
  - **No nested ternaries. No boolean flag parameters.**
119
119
  - **No `any`, no non-null `!`, no `as` cast without a comment saying why.**
@@ -1,14 +1,62 @@
1
1
  import js from "@eslint/js";
2
- import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
2
+ import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
3
3
  import prettier from "eslint-config-prettier";
4
4
  import { flatConfigs as importX } from "eslint-plugin-import-x";
5
5
  import unicorn from "eslint-plugin-unicorn";
6
6
  import globals from "globals";
7
7
 
8
- export default [
9
- { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
8
+ // Code patterns that are errors in every file.
9
+ const RESTRICTED_SYNTAX = [
10
+ {
11
+ selector: "ClassDeclaration",
12
+ message: "Use functions, not classes.",
13
+ },
14
+ {
15
+ selector: "ClassExpression",
16
+ message: "Use functions, not classes.",
17
+ },
18
+ {
19
+ selector: "VariableDeclarator > FunctionExpression",
20
+ message: "Use an arrow function.",
21
+ },
22
+ {
23
+ selector: "VariableDeclaration[kind='let']",
24
+ message:
25
+ "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
26
+ },
27
+ ];
28
+
29
+ // Functions take one parameter: a single object when they need more than one value.
30
+ const SINGLE_OBJECT_PARAMETER = [
31
+ {
32
+ selector: "FunctionDeclaration[params.length>1]",
33
+ message:
34
+ "Use a single object parameter when a function needs more than one value.",
35
+ },
36
+ {
37
+ selector:
38
+ "VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
39
+ message:
40
+ "Use a single object parameter when a function needs more than one value.",
41
+ },
42
+ ];
43
+
44
+ // Deeper than this, return early instead.
45
+ const MAXIMUM_NESTING_DEPTH = 3;
46
+
47
+ const config = [
48
+ {
49
+ ignores: [
50
+ "dist/",
51
+ "build/",
52
+ "coverage/",
53
+ // The docs viewer is the standard template, with its own packages and build.
54
+ "docsSite/",
55
+ "**/vendor/**",
56
+ ],
57
+ },
10
58
  js.configs.recommended,
11
- comments.recommended,
59
+ eslintCommentsRecommended,
12
60
  importX.recommended,
13
61
  unicorn.configs.recommended,
14
62
  {
@@ -17,7 +65,7 @@ export default [
17
65
  "@eslint-community/eslint-comments/require-description":
18
66
  "error",
19
67
  "func-style": ["error", "expression"],
20
- "max-depth": ["error", 3],
68
+ "max-depth": ["error", MAXIMUM_NESTING_DEPTH],
21
69
  "max-lines": [
22
70
  "error",
23
71
  { max: 300, skipBlankLines: true, skipComments: true },
@@ -37,34 +85,8 @@ export default [
37
85
  ],
38
86
  "no-restricted-syntax": [
39
87
  "error",
40
- {
41
- selector: "ClassDeclaration",
42
- message: "Use functions, not classes.",
43
- },
44
- {
45
- selector: "ClassExpression",
46
- message: "Use functions, not classes.",
47
- },
48
- {
49
- selector: "VariableDeclarator > FunctionExpression",
50
- message: "Use an arrow function.",
51
- },
52
- {
53
- selector: "VariableDeclaration[kind='let']",
54
- message:
55
- "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
56
- },
57
- {
58
- selector: "FunctionDeclaration[params.length>1]",
59
- message:
60
- "Use a single object parameter when a function needs more than one value.",
61
- },
62
- {
63
- selector:
64
- "VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
65
- message:
66
- "Use a single object parameter when a function needs more than one value.",
67
- },
88
+ ...RESTRICTED_SYNTAX,
89
+ ...SINGLE_OBJECT_PARAMETER,
68
90
  ],
69
91
  "import-x/no-cycle": "error",
70
92
  "import-x/no-default-export": "error",
@@ -102,3 +124,5 @@ export default [
102
124
  },
103
125
  prettier,
104
126
  ];
127
+
128
+ export default config;
@@ -1,4 +1,4 @@
1
- import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
1
+ import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
2
2
  import prettier from "eslint-config-prettier";
3
3
  import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
4
4
  import nextVitals from "eslint-config-next/core-web-vitals";
@@ -26,6 +26,11 @@ const RESTRICTED_SYNTAX = [
26
26
  message:
27
27
  "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
28
28
  },
29
+ ];
30
+
31
+ // Functions take one parameter: a single object when they need more than one value.
32
+ // Route handlers are the exception: see below.
33
+ const SINGLE_OBJECT_PARAMETER = [
29
34
  {
30
35
  selector: "FunctionDeclaration[params.length>1]",
31
36
  message:
@@ -39,6 +44,9 @@ const RESTRICTED_SYNTAX = [
39
44
  },
40
45
  ];
41
46
 
47
+ // Deeper than this, return early instead.
48
+ const MAXIMUM_NESTING_DEPTH = 3;
49
+
42
50
  // Type a value where it is declared. Story files are the exception: see below.
43
51
  const NO_SATISFIES = {
44
52
  selector: "TSSatisfiesExpression",
@@ -46,7 +54,7 @@ const NO_SATISFIES = {
46
54
  "Type a value where it is declared, not with satisfies afterwards.",
47
55
  };
48
56
 
49
- export default [
57
+ const config = [
50
58
  {
51
59
  ignores: [
52
60
  ".next/",
@@ -54,12 +62,14 @@ export default [
54
62
  "build/",
55
63
  "coverage/",
56
64
  "next-env.d.ts",
65
+ // The docs viewer is the standard template, with its own packages and build.
66
+ "docsSite/",
57
67
  "**/vendor/**",
58
68
  ],
59
69
  },
60
70
  ...nextVitals,
61
71
  ...nextTypescript,
62
- comments.recommended,
72
+ eslintCommentsRecommended,
63
73
  importX.recommended,
64
74
  importX.typescript,
65
75
  unicorn.configs.recommended,
@@ -77,7 +87,7 @@ export default [
77
87
  "@eslint-community/eslint-comments/require-description":
78
88
  "error",
79
89
  "func-style": ["error", "expression"],
80
- "max-depth": ["error", 3],
90
+ "max-depth": ["error", MAXIMUM_NESTING_DEPTH],
81
91
  "max-lines": [
82
92
  "error",
83
93
  { max: 300, skipBlankLines: true, skipComments: true },
@@ -99,6 +109,7 @@ export default [
99
109
  "error",
100
110
  NO_SATISFIES,
101
111
  ...RESTRICTED_SYNTAX,
112
+ ...SINGLE_OBJECT_PARAMETER,
102
113
  ],
103
114
  "import-x/no-cycle": "error",
104
115
  "import-x/no-default-export": "error",
@@ -194,8 +205,25 @@ export default [
194
205
  // Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
195
206
  files: ["**/*.stories.{ts,tsx}"],
196
207
  rules: {
197
- "no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
208
+ "no-restricted-syntax": [
209
+ "error",
210
+ ...RESTRICTED_SYNTAX,
211
+ ...SINGLE_OBJECT_PARAMETER,
212
+ ],
213
+ },
214
+ },
215
+ {
216
+ // Next.js calls a route handler with two values, (request, context), so route.ts files may take two parameters.
217
+ files: ["app/**/route.ts"],
218
+ rules: {
219
+ "no-restricted-syntax": [
220
+ "error",
221
+ NO_SATISFIES,
222
+ ...RESTRICTED_SYNTAX,
223
+ ],
198
224
  },
199
225
  },
200
226
  prettier,
201
227
  ];
228
+
229
+ export default config;
@@ -1,5 +1,5 @@
1
1
  import js from "@eslint/js";
2
- import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
2
+ import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
3
3
  import prettier from "eslint-config-prettier";
4
4
  import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
5
5
  import { flatConfigs as importX } from "eslint-plugin-import-x";
@@ -28,6 +28,10 @@ const RESTRICTED_SYNTAX = [
28
28
  message:
29
29
  "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
30
30
  },
31
+ ];
32
+
33
+ // Functions take one parameter: a single object when they need more than one value.
34
+ const SINGLE_OBJECT_PARAMETER = [
31
35
  {
32
36
  selector: "FunctionDeclaration[params.length>1]",
33
37
  message:
@@ -41,6 +45,9 @@ const RESTRICTED_SYNTAX = [
41
45
  },
42
46
  ];
43
47
 
48
+ // Deeper than this, return early instead.
49
+ const MAXIMUM_NESTING_DEPTH = 3;
50
+
44
51
  // Type a value where it is declared. Story files are the exception: see below.
45
52
  const NO_SATISFIES = {
46
53
  selector: "TSSatisfiesExpression",
@@ -48,10 +55,19 @@ const NO_SATISFIES = {
48
55
  "Type a value where it is declared, not with satisfies afterwards.",
49
56
  };
50
57
 
51
- export default [
52
- { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
58
+ const config = [
59
+ {
60
+ ignores: [
61
+ "dist/",
62
+ "build/",
63
+ "coverage/",
64
+ // The docs viewer is the standard template, with its own packages and build.
65
+ "docsSite/",
66
+ "**/vendor/**",
67
+ ],
68
+ },
53
69
  js.configs.recommended,
54
- comments.recommended,
70
+ eslintCommentsRecommended,
55
71
  importX.recommended,
56
72
  importX.typescript,
57
73
  unicorn.configs.recommended,
@@ -73,7 +89,7 @@ export default [
73
89
  "@eslint-community/eslint-comments/require-description":
74
90
  "error",
75
91
  "func-style": ["error", "expression"],
76
- "max-depth": ["error", 3],
92
+ "max-depth": ["error", MAXIMUM_NESTING_DEPTH],
77
93
  "max-lines": [
78
94
  "error",
79
95
  { max: 300, skipBlankLines: true, skipComments: true },
@@ -95,6 +111,7 @@ export default [
95
111
  "error",
96
112
  NO_SATISFIES,
97
113
  ...RESTRICTED_SYNTAX,
114
+ ...SINGLE_OBJECT_PARAMETER,
98
115
  ],
99
116
  "import-x/no-cycle": "error",
100
117
  "import-x/no-default-export": "error",
@@ -186,8 +203,14 @@ export default [
186
203
  // Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
187
204
  files: ["**/*.stories.{ts,tsx}"],
188
205
  rules: {
189
- "no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
206
+ "no-restricted-syntax": [
207
+ "error",
208
+ ...RESTRICTED_SYNTAX,
209
+ ...SINGLE_OBJECT_PARAMETER,
210
+ ],
190
211
  },
191
212
  },
192
213
  prettier,
193
214
  ];
215
+
216
+ export default config;
@@ -1,5 +1,5 @@
1
1
  import js from "@eslint/js";
2
- import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
2
+ import { recommended as eslintCommentsRecommended } from "@eslint-community/eslint-plugin-eslint-comments/configs";
3
3
  import prettier from "eslint-config-prettier";
4
4
  import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
5
5
  import { flatConfigs as importX } from "eslint-plugin-import-x";
@@ -26,6 +26,10 @@ const RESTRICTED_SYNTAX = [
26
26
  message:
27
27
  "Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
28
28
  },
29
+ ];
30
+
31
+ // Functions take one parameter: a single object when they need more than one value.
32
+ const SINGLE_OBJECT_PARAMETER = [
29
33
  {
30
34
  selector: "FunctionDeclaration[params.length>1]",
31
35
  message:
@@ -39,6 +43,9 @@ const RESTRICTED_SYNTAX = [
39
43
  },
40
44
  ];
41
45
 
46
+ // Deeper than this, return early instead.
47
+ const MAXIMUM_NESTING_DEPTH = 3;
48
+
42
49
  // Type a value where it is declared. Story files are the exception: see below.
43
50
  const NO_SATISFIES = {
44
51
  selector: "TSSatisfiesExpression",
@@ -46,10 +53,19 @@ const NO_SATISFIES = {
46
53
  "Type a value where it is declared, not with satisfies afterwards.",
47
54
  };
48
55
 
49
- export default [
50
- { ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
56
+ const config = [
57
+ {
58
+ ignores: [
59
+ "dist/",
60
+ "build/",
61
+ "coverage/",
62
+ // The docs viewer is the standard template, with its own packages and build.
63
+ "docsSite/",
64
+ "**/vendor/**",
65
+ ],
66
+ },
51
67
  js.configs.recommended,
52
- comments.recommended,
68
+ eslintCommentsRecommended,
53
69
  importX.recommended,
54
70
  importX.typescript,
55
71
  unicorn.configs.recommended,
@@ -63,7 +79,7 @@ export default [
63
79
  "@eslint-community/eslint-comments/require-description":
64
80
  "error",
65
81
  "func-style": ["error", "expression"],
66
- "max-depth": ["error", 3],
82
+ "max-depth": ["error", MAXIMUM_NESTING_DEPTH],
67
83
  "max-lines": [
68
84
  "error",
69
85
  { max: 300, skipBlankLines: true, skipComments: true },
@@ -85,6 +101,7 @@ export default [
85
101
  "error",
86
102
  NO_SATISFIES,
87
103
  ...RESTRICTED_SYNTAX,
104
+ ...SINGLE_OBJECT_PARAMETER,
88
105
  ],
89
106
  "import-x/no-cycle": "error",
90
107
  "import-x/no-default-export": "error",
@@ -167,8 +184,14 @@ export default [
167
184
  // Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
168
185
  files: ["**/*.stories.{ts,tsx}"],
169
186
  rules: {
170
- "no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
187
+ "no-restricted-syntax": [
188
+ "error",
189
+ ...RESTRICTED_SYNTAX,
190
+ ...SINGLE_OBJECT_PARAMETER,
191
+ ],
171
192
  },
172
193
  },
173
194
  prettier,
174
195
  ];
196
+
197
+ export default config;