@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 +6 -4
- package/docs/Guides/migrating.md +3 -1
- package/docs/Guides/setup.md +1 -1
- package/docs/Standards/eslint.md +156 -54
- package/docs/Standards/readable-code.md +3 -1
- package/docs/Structure/folder-structure.md +23 -2
- package/docs/version-history.md +11 -2
- package/package.json +19 -4
- package/static/templates/AGENTS.md +1 -1
- package/static/templates/eslint/base/eslint.config.mjs +57 -33
- package/static/templates/eslint/nextjs/eslint.config.mjs +33 -5
- package/static/templates/eslint/react/eslint.config.mjs +29 -6
- package/static/templates/eslint/typescript/eslint.config.mjs +29 -6
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: `
|
|
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,
|
|
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.
|
package/docs/Guides/migrating.md
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/docs/Guides/setup.md
CHANGED
|
@@ -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
|
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";
|
|
@@ -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
|
-
|
|
234
|
-
{
|
|
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
|
-
|
|
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",
|
|
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": [
|
|
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
|
|
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
|
-
|
|
419
|
-
{
|
|
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
|
-
|
|
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",
|
|
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": [
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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": [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
386
|
+
- **Other constants**, such as numbers, are `SCREAMING_SNAKE_CASE`: see [Naming Conventions](/docs/Structure/naming-conventions#constants).
|
|
366
387
|
|
|
367
388
|
---
|
|
368
389
|
|
package/docs/version-history.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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": [
|
|
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
|
|
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
|
-
|
|
52
|
-
{
|
|
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
|
-
|
|
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",
|
|
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": [
|
|
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
|
|
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
|
-
|
|
50
|
-
{
|
|
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
|
-
|
|
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",
|
|
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": [
|
|
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;
|