@activatus/code-standards 3.0.1 → 3.0.4
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 +10 -10
- package/docs/Guides/component-library-workflow.md +16 -16
- package/docs/Guides/migrating.md +15 -15
- package/docs/Guides/setup.md +12 -7
- package/docs/Standards/agent-skills.md +2 -2
- package/docs/Standards/agent-specs.md +3 -3
- package/docs/Standards/documentation.md +7 -7
- package/docs/Standards/eslint.md +169 -105
- package/docs/Standards/human-readable-names.md +15 -3
- package/docs/Standards/lint-errors.md +13 -12
- package/docs/Standards/readable-code.md +80 -43
- package/docs/Standards/stylelint.md +4 -4
- package/docs/Standards/styling.md +8 -8
- package/docs/Standards/tech-stack.md +1 -1
- package/docs/Structure/api-routes.md +3 -3
- package/docs/Structure/component-organization.md +19 -18
- package/docs/Structure/folder-structure.md +110 -12
- package/docs/Structure/naming-conventions.md +10 -8
- package/docs/design-system-components.md +3 -3
- package/docs/intro.md +20 -20
- package/docs/ontology.md +32 -31
- package/docs/templates.mdx +12 -12
- package/docs/version-history.md +76 -42
- package/package.json +1 -1
- package/static/templates/AGENTS.md +5 -4
- package/static/templates/docs/overview/writing-docs.md +1 -1
- package/static/templates/eslint/nextjs/eslint.config.mjs +48 -28
- package/static/templates/eslint/react/eslint.config.mjs +48 -28
- package/static/templates/eslint/typescript/eslint.config.mjs +48 -28
package/docs/Standards/eslint.md
CHANGED
|
@@ -191,6 +191,45 @@ import unicorn from "eslint-plugin-unicorn";
|
|
|
191
191
|
import globals from "globals";
|
|
192
192
|
import { configs as typescript } from "typescript-eslint";
|
|
193
193
|
|
|
194
|
+
// Code patterns that are errors in every file.
|
|
195
|
+
const RESTRICTED_SYNTAX = [
|
|
196
|
+
{
|
|
197
|
+
selector: "ClassDeclaration",
|
|
198
|
+
message: "Use functions, not classes.",
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
selector: "ClassExpression",
|
|
202
|
+
message: "Use functions, not classes.",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
selector: "VariableDeclarator > FunctionExpression",
|
|
206
|
+
message: "Use an arrow function.",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
selector: "VariableDeclaration[kind='let']",
|
|
210
|
+
message:
|
|
211
|
+
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
selector: "FunctionDeclaration[params.length>1]",
|
|
215
|
+
message:
|
|
216
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
selector:
|
|
220
|
+
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
221
|
+
message:
|
|
222
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
// Type a value where it is declared. Story files are the exception: see below.
|
|
227
|
+
const NO_SATISFIES = {
|
|
228
|
+
selector: "TSSatisfiesExpression",
|
|
229
|
+
message:
|
|
230
|
+
"Type a value where it is declared, not with satisfies afterwards.",
|
|
231
|
+
};
|
|
232
|
+
|
|
194
233
|
export default [
|
|
195
234
|
{ ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
|
|
196
235
|
js.configs.recommended,
|
|
@@ -228,34 +267,8 @@ export default [
|
|
|
228
267
|
],
|
|
229
268
|
"no-restricted-syntax": [
|
|
230
269
|
"error",
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
message: "Use functions, not classes.",
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
selector: "ClassExpression",
|
|
237
|
-
message: "Use functions, not classes.",
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
selector: "VariableDeclarator > FunctionExpression",
|
|
241
|
-
message: "Use an arrow function.",
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
selector: "VariableDeclaration[kind='let']",
|
|
245
|
-
message:
|
|
246
|
-
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
selector: "FunctionDeclaration[params.length>1]",
|
|
250
|
-
message:
|
|
251
|
-
"Use a single object parameter when a function needs more than one value.",
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
selector:
|
|
255
|
-
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
256
|
-
message:
|
|
257
|
-
"Use a single object parameter when a function needs more than one value.",
|
|
258
|
-
},
|
|
270
|
+
NO_SATISFIES,
|
|
271
|
+
...RESTRICTED_SYNTAX,
|
|
259
272
|
],
|
|
260
273
|
"import-x/no-cycle": "error",
|
|
261
274
|
"import-x/no-default-export": "error",
|
|
@@ -334,6 +347,13 @@ export default [
|
|
|
334
347
|
files: ["**/*.config.{js,mjs,ts}"],
|
|
335
348
|
rules: { "unicorn/no-top-level-side-effects": "off" },
|
|
336
349
|
},
|
|
350
|
+
{
|
|
351
|
+
// Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
|
|
352
|
+
files: ["**/*.stories.{ts,tsx}"],
|
|
353
|
+
rules: {
|
|
354
|
+
"no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
337
357
|
prettier,
|
|
338
358
|
];
|
|
339
359
|
```
|
|
@@ -356,6 +376,45 @@ import unicorn from "eslint-plugin-unicorn";
|
|
|
356
376
|
import globals from "globals";
|
|
357
377
|
import { configs as typescript } from "typescript-eslint";
|
|
358
378
|
|
|
379
|
+
// Code patterns that are errors in every file.
|
|
380
|
+
const RESTRICTED_SYNTAX = [
|
|
381
|
+
{
|
|
382
|
+
selector: "ClassDeclaration",
|
|
383
|
+
message: "Use functions, not classes.",
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
selector: "ClassExpression",
|
|
387
|
+
message: "Use functions, not classes.",
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
selector: "VariableDeclarator > FunctionExpression",
|
|
391
|
+
message: "Use an arrow function.",
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
selector: "VariableDeclaration[kind='let']",
|
|
395
|
+
message:
|
|
396
|
+
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
selector: "FunctionDeclaration[params.length>1]",
|
|
400
|
+
message:
|
|
401
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
selector:
|
|
405
|
+
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
406
|
+
message:
|
|
407
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
408
|
+
},
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
// Type a value where it is declared. Story files are the exception: see below.
|
|
412
|
+
const NO_SATISFIES = {
|
|
413
|
+
selector: "TSSatisfiesExpression",
|
|
414
|
+
message:
|
|
415
|
+
"Type a value where it is declared, not with satisfies afterwards.",
|
|
416
|
+
};
|
|
417
|
+
|
|
359
418
|
export default [
|
|
360
419
|
{ ignores: ["dist/", "build/", "coverage/", "**/vendor/**"] },
|
|
361
420
|
js.configs.recommended,
|
|
@@ -401,34 +460,8 @@ export default [
|
|
|
401
460
|
],
|
|
402
461
|
"no-restricted-syntax": [
|
|
403
462
|
"error",
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
message: "Use functions, not classes.",
|
|
407
|
-
},
|
|
408
|
-
{
|
|
409
|
-
selector: "ClassExpression",
|
|
410
|
-
message: "Use functions, not classes.",
|
|
411
|
-
},
|
|
412
|
-
{
|
|
413
|
-
selector: "VariableDeclarator > FunctionExpression",
|
|
414
|
-
message: "Use an arrow function.",
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
selector: "VariableDeclaration[kind='let']",
|
|
418
|
-
message:
|
|
419
|
-
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
420
|
-
},
|
|
421
|
-
{
|
|
422
|
-
selector: "FunctionDeclaration[params.length>1]",
|
|
423
|
-
message:
|
|
424
|
-
"Use a single object parameter when a function needs more than one value.",
|
|
425
|
-
},
|
|
426
|
-
{
|
|
427
|
-
selector:
|
|
428
|
-
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
429
|
-
message:
|
|
430
|
-
"Use a single object parameter when a function needs more than one value.",
|
|
431
|
-
},
|
|
463
|
+
NO_SATISFIES,
|
|
464
|
+
...RESTRICTED_SYNTAX,
|
|
432
465
|
],
|
|
433
466
|
"import-x/no-cycle": "error",
|
|
434
467
|
"import-x/no-default-export": "error",
|
|
@@ -516,6 +549,13 @@ export default [
|
|
|
516
549
|
files: ["**/*.config.{js,mjs,ts}"],
|
|
517
550
|
rules: { "unicorn/no-top-level-side-effects": "off" },
|
|
518
551
|
},
|
|
552
|
+
{
|
|
553
|
+
// Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
|
|
554
|
+
files: ["**/*.stories.{ts,tsx}"],
|
|
555
|
+
rules: {
|
|
556
|
+
"no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
|
|
557
|
+
},
|
|
558
|
+
},
|
|
519
559
|
prettier,
|
|
520
560
|
];
|
|
521
561
|
```
|
|
@@ -540,6 +580,45 @@ import { flatConfigs as importX } from "eslint-plugin-import-x";
|
|
|
540
580
|
import unicorn from "eslint-plugin-unicorn";
|
|
541
581
|
import globals from "globals";
|
|
542
582
|
|
|
583
|
+
// Code patterns that are errors in every file.
|
|
584
|
+
const RESTRICTED_SYNTAX = [
|
|
585
|
+
{
|
|
586
|
+
selector: "ClassDeclaration",
|
|
587
|
+
message: "Use functions, not classes.",
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
selector: "ClassExpression",
|
|
591
|
+
message: "Use functions, not classes.",
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
selector: "VariableDeclarator > FunctionExpression",
|
|
595
|
+
message: "Use an arrow function.",
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
selector: "VariableDeclaration[kind='let']",
|
|
599
|
+
message:
|
|
600
|
+
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
selector: "FunctionDeclaration[params.length>1]",
|
|
604
|
+
message:
|
|
605
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
selector:
|
|
609
|
+
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
610
|
+
message:
|
|
611
|
+
"Use a single object parameter when a function needs more than one value.",
|
|
612
|
+
},
|
|
613
|
+
];
|
|
614
|
+
|
|
615
|
+
// Type a value where it is declared. Story files are the exception: see below.
|
|
616
|
+
const NO_SATISFIES = {
|
|
617
|
+
selector: "TSSatisfiesExpression",
|
|
618
|
+
message:
|
|
619
|
+
"Type a value where it is declared, not with satisfies afterwards.",
|
|
620
|
+
};
|
|
621
|
+
|
|
543
622
|
export default [
|
|
544
623
|
{
|
|
545
624
|
ignores: [
|
|
@@ -591,34 +670,8 @@ export default [
|
|
|
591
670
|
],
|
|
592
671
|
"no-restricted-syntax": [
|
|
593
672
|
"error",
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
message: "Use functions, not classes.",
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
selector: "ClassExpression",
|
|
600
|
-
message: "Use functions, not classes.",
|
|
601
|
-
},
|
|
602
|
-
{
|
|
603
|
-
selector: "VariableDeclarator > FunctionExpression",
|
|
604
|
-
message: "Use an arrow function.",
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
selector: "VariableDeclaration[kind='let']",
|
|
608
|
-
message:
|
|
609
|
-
"Use const. If this value really must be reassigned, disable this rule on this line with a comment saying why.",
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
selector: "FunctionDeclaration[params.length>1]",
|
|
613
|
-
message:
|
|
614
|
-
"Use a single object parameter when a function needs more than one value.",
|
|
615
|
-
},
|
|
616
|
-
{
|
|
617
|
-
selector:
|
|
618
|
-
"VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression)[params.length>1]",
|
|
619
|
-
message:
|
|
620
|
-
"Use a single object parameter when a function needs more than one value.",
|
|
621
|
-
},
|
|
673
|
+
NO_SATISFIES,
|
|
674
|
+
...RESTRICTED_SYNTAX,
|
|
622
675
|
],
|
|
623
676
|
"import-x/no-cycle": "error",
|
|
624
677
|
"import-x/no-default-export": "error",
|
|
@@ -710,6 +763,13 @@ export default [
|
|
|
710
763
|
files: ["**/*.config.{js,mjs,ts}"],
|
|
711
764
|
rules: { "unicorn/no-top-level-side-effects": "off" },
|
|
712
765
|
},
|
|
766
|
+
{
|
|
767
|
+
// Storybook's own docs type stories with `satisfies Meta<typeof Component>`, so story files may use it.
|
|
768
|
+
files: ["**/*.stories.{ts,tsx}"],
|
|
769
|
+
rules: {
|
|
770
|
+
"no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
|
|
771
|
+
},
|
|
772
|
+
},
|
|
713
773
|
prettier,
|
|
714
774
|
];
|
|
715
775
|
```
|
|
@@ -786,7 +846,7 @@ Third-party code that is copied into the repository lives in a `vendor/` folder,
|
|
|
786
846
|
|
|
787
847
|
`vendor/` is only for third-party code we **do not change** — code that is replaced wholesale when the package updates.
|
|
788
848
|
|
|
789
|
-
Code that is copied into the project and then **owned and edited by us** is our code, and every standard applies to it. The main case is **shadcn/ui**: the CLI copies component source into the project, and from then on it is ours. It is converted to our standards and lives in `components/ui/`, never in `vendor/`. See [Adding a shadcn component](/Guides/component-library-workflow#adding-a-shadcn-component).
|
|
849
|
+
Code that is copied into the project and then **owned and edited by us** is our code, and every standard applies to it. The main case is **shadcn/ui**: the CLI copies component source into the project, and from then on it is ours. It is converted to our standards and lives in `components/ui/`, never in `vendor/`. See [Adding a shadcn component](/docs/Guides/component-library-workflow#adding-a-shadcn-component).
|
|
790
850
|
|
|
791
851
|
| Code | Ours? | Follows the standards? |
|
|
792
852
|
| --------------------------------------------------------- | ------- | ------------------------------------------ |
|
|
@@ -799,11 +859,11 @@ If a vendored package lives somewhere else and cannot be moved, add its path to
|
|
|
799
859
|
|
|
800
860
|
## Key rule decisions
|
|
801
861
|
|
|
802
|
-
Most of these enforce the [Readable Code](/Standards/readable-code) rules. For what each error means and how to fix it, see [Fixing Lint Errors](/Standards/lint-errors).
|
|
862
|
+
Most of these enforce the [Readable Code](/docs/Standards/readable-code) rules. For what each error means and how to fix it, see [Fixing Lint Errors](/docs/Standards/lint-errors).
|
|
803
863
|
|
|
804
864
|
### `func-style`, `prefer-arrow-callback`: arrow functions everywhere
|
|
805
865
|
|
|
806
|
-
Every function is an arrow function. See [Readable Code](/Standards/readable-code#arrow-functions-everywhere).
|
|
866
|
+
Every function is an arrow function. See [Readable Code](/docs/Standards/readable-code#arrow-functions-everywhere).
|
|
807
867
|
|
|
808
868
|
- `func-style: expression` rejects `function name() {}` declarations.
|
|
809
869
|
- `prefer-arrow-callback` rejects `function` callbacks.
|
|
@@ -813,58 +873,58 @@ Every function is an arrow function. See [Readable Code](/Standards/readable-cod
|
|
|
813
873
|
|
|
814
874
|
### `no-restricted-syntax`, `prefer-const`, `no-var`: always `const`
|
|
815
875
|
|
|
816
|
-
Variables are declared with `const`. `let` is an error unless the line carries a disable comment with a reason; `var` is never allowed. See [Readable Code](/Standards/readable-code#always-const).
|
|
876
|
+
Variables are declared with `const`. `let` is an error unless the line carries a disable comment with a reason; `var` is never allowed. See [Readable Code](/docs/Standards/readable-code#always-const).
|
|
817
877
|
|
|
818
878
|
### `@eslint-community/eslint-comments/require-description`
|
|
819
879
|
|
|
820
|
-
Every `eslint-disable` comment must say why, after `--`. A rule is never switched off silently. See [the only allowed exceptions](/Standards/lint-errors#the-only-allowed-exceptions).
|
|
880
|
+
Every `eslint-disable` comment must say why, after `--`. A rule is never switched off silently. See [the only allowed exceptions](/docs/Standards/lint-errors#the-only-allowed-exceptions).
|
|
821
881
|
|
|
822
882
|
### `max-lines: 300`
|
|
823
883
|
|
|
824
|
-
No file is longer than 300 lines, not counting blank lines and comments. Test files are exempt. See [Readable Code](/Standards/readable-code#no-file-longer-than-300-lines).
|
|
884
|
+
No file is longer than 300 lines, not counting blank lines and comments. Test files are exempt. See [Readable Code](/docs/Standards/readable-code#no-file-longer-than-300-lines).
|
|
825
885
|
|
|
826
886
|
### `max-depth: 3`
|
|
827
887
|
|
|
828
|
-
Blocks nest at most three levels deep. Return early instead. See [Readable Code](/Standards/readable-code#return-early-nest-shallow).
|
|
888
|
+
Blocks nest at most three levels deep. Return early instead. See [Readable Code](/docs/Standards/readable-code#return-early-nest-shallow).
|
|
829
889
|
|
|
830
890
|
### `no-nested-ternary`
|
|
831
891
|
|
|
832
|
-
A ternary inside a ternary is an error. See [Readable Code](/Standards/readable-code#no-nested-ternaries).
|
|
892
|
+
A ternary inside a ternary is an error. See [Readable Code](/docs/Standards/readable-code#no-nested-ternaries).
|
|
833
893
|
|
|
834
894
|
### `no-magic-numbers`
|
|
835
895
|
|
|
836
|
-
Numbers other than `-1`, `0` and `1` must be named constants. Test files are exempt. TypeScript files use `@typescript-eslint/no-magic-numbers`, which understands enums and numeric literal types. See [Readable Code](/Standards/readable-code#no-magic-numbers-or-strings).
|
|
896
|
+
Numbers other than `-1`, `0` and `1` must be named constants. Test files are exempt. TypeScript files use `@typescript-eslint/no-magic-numbers`, which understands enums and numeric literal types. See [Readable Code](/docs/Standards/readable-code#no-magic-numbers-or-strings).
|
|
837
897
|
|
|
838
898
|
### `no-restricted-syntax`: no classes, one parameter
|
|
839
899
|
|
|
840
900
|
These are banned by syntax, along with `function` expressions (above):
|
|
841
901
|
|
|
842
|
-
- **Class declarations and class expressions.** See [Readable Code](/Standards/readable-code#functions-never-classes).
|
|
843
|
-
- **Functions you define with more than one parameter.** Use a single object parameter. Inline callbacks whose signature is set by someone else — `array.reduce((total, item) => …)`, event handlers — are not affected. See [Readable Code](/Standards/readable-code#one-parameter-always-an-object-for-more).
|
|
902
|
+
- **Class declarations and class expressions.** See [Readable Code](/docs/Standards/readable-code#functions-never-classes).
|
|
903
|
+
- **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).
|
|
844
904
|
|
|
845
905
|
### `import-x/no-cycle`: no circular imports
|
|
846
906
|
|
|
847
|
-
A file may never import a file that imports it back, directly or through a chain of other files. See [Import safety](/Structure/import-organization#import-safety).
|
|
907
|
+
A file may never import a file that imports it back, directly or through a chain of other files. See [Import safety](/docs/Structure/import-organization#import-safety).
|
|
848
908
|
|
|
849
909
|
### `no-use-before-define`
|
|
850
910
|
|
|
851
|
-
A function or variable is defined above the code that uses it. TypeScript files use `@typescript-eslint/no-use-before-define`. See [Import safety](/Structure/import-organization#import-safety).
|
|
911
|
+
A function or variable is defined above the code that uses it. TypeScript files use `@typescript-eslint/no-use-before-define`. See [Import safety](/docs/Structure/import-organization#import-safety).
|
|
852
912
|
|
|
853
913
|
### `import-x/no-default-export`
|
|
854
914
|
|
|
855
|
-
Named exports only. Default exports are allowed where a tool requires them: config files, Storybook stories and Next.js route files. See [Readable Code](/Standards/readable-code#named-exports-only).
|
|
915
|
+
Named exports only. Default exports are allowed where a tool requires them: config files, Storybook stories and Next.js route files. See [Readable Code](/docs/Standards/readable-code#named-exports-only).
|
|
856
916
|
|
|
857
917
|
### Import order is not linted
|
|
858
918
|
|
|
859
|
-
There is deliberately no import-**order** rule (circular imports are a different matter — see above). Follow the [Import Organization](/Structure/import-organization) pattern for consistency, but ESLint will not complain about it.
|
|
919
|
+
There is deliberately no import-**order** rule (circular imports are a different matter — see above). Follow the [Import Organization](/docs/Structure/import-organization) pattern for consistency, but ESLint will not complain about it.
|
|
860
920
|
|
|
861
921
|
### `unicorn/filename-case: camelCase or PascalCase`
|
|
862
922
|
|
|
863
|
-
The plugin's default is kebab-case, which contradicts our [naming conventions](/Structure/naming-conventions). Files are camelCase (`formatDate.ts`), and component files are PascalCase (`UserProfile.tsx`).
|
|
923
|
+
The plugin's default is kebab-case, which contradicts our [naming conventions](/docs/Structure/naming-conventions). Files are camelCase (`formatDate.ts`), and component files are PascalCase (`UserProfile.tsx`).
|
|
864
924
|
|
|
865
925
|
### `unicorn/name-replacements: error`
|
|
866
926
|
|
|
867
|
-
Names are full English words: `btn` must be `button`, `e` must be `error` or `event`, `acc` must be `accumulator`. `docs`, `lib`, `props`, `ref`, `repo` and `utils` are allowed because they are on the [dictionary](/Standards/human-readable-names#dictionary) list. Only three names owned by React and Next.js are allowed exactly as they are — `ref`, `params` and `searchParams` — and our own names built from them are still flagged (`inputRef` must be `inputReference`). See [Human-Readable Names](/Standards/human-readable-names). (Before `eslint-plugin-unicorn` 77 this rule was called `unicorn/prevent-abbreviations`.)
|
|
927
|
+
Names are full English words: `btn` must be `button`, `e` must be `error` or `event`, `acc` must be `accumulator`. `docs`, `lib`, `props`, `ref`, `repo` and `utils` are allowed because they are on the [dictionary](/docs/Standards/human-readable-names#dictionary) list. Only three names owned by React and Next.js are allowed exactly as they are — `ref`, `params` and `searchParams` — and our own names built from them are still flagged (`inputRef` must be `inputReference`). See [Human-Readable Names](/docs/Standards/human-readable-names). (Before `eslint-plugin-unicorn` 77 this rule was called `unicorn/prevent-abbreviations`.)
|
|
868
928
|
|
|
869
929
|
### `@typescript-eslint/no-explicit-any: error`
|
|
870
930
|
|
|
@@ -874,9 +934,13 @@ Names are full English words: `btn` must be `button`, `e` must be `error` or `ev
|
|
|
874
934
|
|
|
875
935
|
The `!` operator tells the compiler a value exists without checking. Check it instead.
|
|
876
936
|
|
|
937
|
+
### `no-restricted-syntax`: type first, never `satisfies`
|
|
938
|
+
|
|
939
|
+
A value gets its type where it is declared: `const config: ConfigType = { … }`. `satisfies` attaches the type afterwards, so it is an error in the TypeScript, React and Next.js configs, except in story files (`*.stories.{ts,tsx}`), which follow Storybook's docs. The rule lists live in `RESTRICTED_SYNTAX` and `NO_SATISFIES` at the top of each config, so story files use every other restriction without repeating it. See [Type first, never `satisfies`](/docs/Standards/readable-code#type-first-never-satisfies).
|
|
940
|
+
|
|
877
941
|
### `@typescript-eslint/consistent-type-assertions: never`
|
|
878
942
|
|
|
879
|
-
`as` casts are errors (`as const` is still allowed). When a cast is genuinely needed, disable the rule on that line **with the reason** — which is exactly the comment the [standard](/Standards/readable-code#no-any-no--no-unexplained-as) requires:
|
|
943
|
+
`as` casts are errors (`as const` is still allowed). When a cast is genuinely needed, disable the rule on that line **with the reason** — which is exactly the comment the [standard](/docs/Standards/readable-code#no-any-no--no-unexplained-as) requires:
|
|
880
944
|
|
|
881
945
|
```ts
|
|
882
946
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- the API returns untyped JSON; validated by parseUser below
|
|
@@ -885,7 +949,7 @@ const user = response.data as UserType;
|
|
|
885
949
|
|
|
886
950
|
### `@typescript-eslint/naming-convention`: `Props` or `Type` suffix
|
|
887
951
|
|
|
888
|
-
Every interface and type alias ends in `Props` or `Type`. See [Naming Conventions](/Structure/naming-conventions#types--interfaces).
|
|
952
|
+
Every interface and type alias ends in `Props` or `Type`. See [Naming Conventions](/docs/Structure/naming-conventions#types--interfaces).
|
|
889
953
|
|
|
890
954
|
### `@typescript-eslint/consistent-type-imports: error`
|
|
891
955
|
|
|
@@ -893,8 +957,8 @@ Type-only imports use `import type { Foo }` or inline `import { type Foo }`. Thi
|
|
|
893
957
|
|
|
894
958
|
### `react/jsx-no-bind: error`
|
|
895
959
|
|
|
896
|
-
No inline functions in render. A function passed as a JSX prop must be wrapped in `useCallback`, so it is not recreated on every render. See [Writing a component](/Structure/component-organization#-writing-a-component).
|
|
960
|
+
No inline functions in render. A function passed as a JSX prop must be wrapped in `useCallback`, so it is not recreated on every render. See [Writing a component](/docs/Structure/component-organization#-writing-a-component).
|
|
897
961
|
|
|
898
962
|
### `react/no-multi-comp: error`
|
|
899
963
|
|
|
900
|
-
One component per file. See [Readable Code](/Standards/readable-code#one-component-per-file).
|
|
964
|
+
One component per file. See [Readable Code](/docs/Standards/readable-code#one-component-per-file).
|
|
@@ -5,7 +5,7 @@ sidebar_label: "Human-Readable Names"
|
|
|
5
5
|
|
|
6
6
|
# Human-Readable Names
|
|
7
7
|
|
|
8
|
-
**Every name we write is made of whole words a person can read out loud.** Variables, function parameters, props, types, constants, form fields, files, folders, routes, database columns, scripts and environment variables — everything. No shortened words, no single letters, no numbered names — not even for a loop counter or an event handler.
|
|
8
|
+
**Every name we write is made of whole words a person can read out loud.** Variables, function parameters, props, types, constants, form fields, files, folders, routes, database columns, scripts and environment variables — everything. No shortened words, no single letters, no numbered names — not even for a loop counter or an event handler. The one exception is [`r` and `s`](#r-and-s).
|
|
9
9
|
|
|
10
10
|
Anyone reading the code should never have to guess what a name stands for.
|
|
11
11
|
|
|
@@ -59,7 +59,7 @@ Everything that has a name:
|
|
|
59
59
|
| **Doc files** | `architecture/background-jobs.md` | `arch/bg.md`, `notes2.md` |
|
|
60
60
|
| **Error messages** | `"Enter an email address"` | `"Invalid input"`, `"Error 42"` |
|
|
61
61
|
|
|
62
|
-
The words come from the [Ontology](/ontology) wherever it has a term: reusable functions are in `utils/`, never `helpers/` or `utilities/`. A project's own ontology adds its product words: in Launchpad, a Draft Ticket is `draftTicket`, never `pendingTicket`. The casing comes from [Naming Conventions](/Structure/naming-conventions).
|
|
62
|
+
The words come from the [Ontology](/docs/ontology) wherever it has a term: reusable functions are in `utils/`, never `helpers/` or `utilities/`. A project's own ontology adds its product words: in Launchpad, a Draft Ticket is `draftTicket`, never `pendingTicket`. The casing comes from [Naming Conventions](/docs/Structure/naming-conventions).
|
|
63
63
|
|
|
64
64
|
---
|
|
65
65
|
|
|
@@ -151,10 +151,20 @@ When a short name feels like a dictionary word, don't just use it — add it her
|
|
|
151
151
|
|
|
152
152
|
1. **Ask:** is this the word people actually say, so the long form would only be harder to read? `docs`, `repo`: yes. `env`, `btn`, `msg`: no.
|
|
153
153
|
2. **Add a row** to the table above, before or alongside the code that uses it.
|
|
154
|
-
3. **If ESLint flags the word**, add it to `replacements` in the `unicorn/name-replacements` rule of the [ESLint config](/Standards/eslint), so the linter agrees: `replacements: { docs: false, lib: false, props: false, ref: false, repo: false, utils: false }`.
|
|
154
|
+
3. **If ESLint flags the word**, add it to `replacements` in the `unicorn/name-replacements` rule of the [ESLint config](/docs/Standards/eslint), so the linter agrees: `replacements: { docs: false, lib: false, props: false, ref: false, repo: false, utils: false }`.
|
|
155
155
|
|
|
156
156
|
The list is the record. If a word is not on it, it is not a dictionary word yet.
|
|
157
157
|
|
|
158
|
+
## `r` and `s`: the only single letters {#r-and-s}
|
|
159
|
+
|
|
160
|
+
**Two names are allowed to be a single letter:** `r`, the object of every route, and `s`, the object of every piece of static text. Both live in `lib/constants/`. See [Constants](/docs/Structure/folder-structure#constants).
|
|
161
|
+
|
|
162
|
+
They are used so often that the short form is easier to read: `href={r.contact}` and `{s.contact}` read cleanly in a component, where `routes.contact` and `strings.contact` crowd it.
|
|
163
|
+
|
|
164
|
+
- **Only these two.** No other single letter is allowed anywhere: not `e`, `i`, `x` or `T`, and not another constants object.
|
|
165
|
+
- **Only as those two objects.** `r` is never a request or a result, and `s` is never a string or a second.
|
|
166
|
+
- **Their keys are full words:** `r.linkedIn`, `s.pageNotFound`.
|
|
167
|
+
|
|
158
168
|
---
|
|
159
169
|
|
|
160
170
|
## Third-party names
|
|
@@ -272,3 +282,5 @@ users.map((u) => u.name);
|
|
|
272
282
|
| **Everything else** — folders, props, form fields, union values, database columns, routes, environment variables, test names | **Code review and AI agents.** The linter cannot read these, so every agent and reviewer checks them |
|
|
273
283
|
|
|
274
284
|
ESLint allows the [dictionary](#dictionary) words it would otherwise flag (`docs`, `lib`, `props`, `ref`, `repo`, `utils`), and exactly two third-party names as they are: `params` and `searchParams`. It still flags any of our own names built from an third-party name, such as `inputRef`.
|
|
285
|
+
|
|
286
|
+
ESLint does not flag `r` or `s`. Reviewers check that they are only ever the two constants objects.
|
|
@@ -29,12 +29,12 @@ This applies to everyone, **including AI agents**. An agent's work is not finish
|
|
|
29
29
|
|
|
30
30
|
## Imports
|
|
31
31
|
|
|
32
|
-
| Rule | What you will see | What it means | How to fix it
|
|
33
|
-
| -------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
34
|
-
| `import-x/no-cycle` | `Dependency cycle detected` | Two files import each other, directly or through other files. One will load before the other is ready | Move the part both files need into a **third file** that both import. If the cycle goes through an `index.ts`, import the file directly instead. See [Import safety](/Structure/import-organization#import-safety) |
|
|
35
|
-
| `import-x/no-unresolved` | `Unable to resolve path to module '…'` | The file being imported does not exist at that path | Check the spelling and the path. If the file was renamed or moved, update the import
|
|
36
|
-
| `import-x/no-default-export` | `Prefer named exports` | The file uses `export default` | Change to a named export: `export const formatCurrency = …`
|
|
37
|
-
| `@typescript-eslint/consistent-type-imports` | `All imports in the declaration are only used as types. Use import type` | A type is imported like a value | Change `import { UserType }` to `import type { UserType }`
|
|
32
|
+
| Rule | What you will see | What it means | How to fix it |
|
|
33
|
+
| -------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `import-x/no-cycle` | `Dependency cycle detected` | Two files import each other, directly or through other files. One will load before the other is ready | Move the part both files need into a **third file** that both import. If the cycle goes through an `index.ts`, import the file directly instead. See [Import safety](/docs/Structure/import-organization#import-safety) |
|
|
35
|
+
| `import-x/no-unresolved` | `Unable to resolve path to module '…'` | The file being imported does not exist at that path | Check the spelling and the path. If the file was renamed or moved, update the import |
|
|
36
|
+
| `import-x/no-default-export` | `Prefer named exports` | The file uses `export default` | Change to a named export: `export const formatCurrency = …` |
|
|
37
|
+
| `@typescript-eslint/consistent-type-imports` | `All imports in the declaration are only used as types. Use import type` | A type is imported like a value | Change `import { UserType }` to `import type { UserType }` |
|
|
38
38
|
|
|
39
39
|
## Order and variables
|
|
40
40
|
|
|
@@ -57,6 +57,7 @@ This applies to everyone, **including AI agents**. An agent's work is not finish
|
|
|
57
57
|
| `no-restricted-syntax` | `Use an arrow function` | `const name = function () {}` was used | Rewrite as `const name = () => {}` |
|
|
58
58
|
| `no-restricted-syntax` | `Use a single object parameter when a function needs more than one value` | A function has more than one parameter | Take one object instead: `({ name, emailAddress }: CreateUserProps) =>` |
|
|
59
59
|
| `no-restricted-syntax` | `Use functions, not classes` | A `class` was used | Rewrite it as functions |
|
|
60
|
+
| `no-restricted-syntax` | `Type a value where it is declared, not with satisfies afterwards` | `satisfies` was used outside a story file | Put the type on the declaration instead: `const config: ConfigType = { … }` |
|
|
60
61
|
| `max-depth` | `Blocks are nested too deeply (4). Maximum allowed is 3` | `if`s and loops are nested more than three deep | Return early for the error cases, or move the inner part into its own function |
|
|
61
62
|
| `no-nested-ternary` | `Do not nest ternary expressions` | A `? :` sits inside another `? :` | Use an `if`, or move it into its own function |
|
|
62
63
|
|
|
@@ -120,11 +121,11 @@ Nothing else is switched off. If another rule seems impossible to satisfy, the c
|
|
|
120
121
|
|
|
121
122
|
## Errors that stop ESLint running
|
|
122
123
|
|
|
123
|
-
| What you will see | What it means | How to fix it
|
|
124
|
-
| ------------------------------------------------- | ------------------------------------------------ |
|
|
125
|
-
| `typescript-eslint does not support TS 7.0` | The linter cannot run against TypeScript 7 yet | See the [TypeScript 7 note](/Standards/eslint#installation) on the ESLint page
|
|
126
|
-
| `contextOrFilename.getFilename is not a function` | The React plugin cannot detect the React version | Set `settings.react.version` to the project's React version instead of `"detect"`
|
|
127
|
-
| `Cannot find module '…'` | A lint package is not installed | Run the install commands on the [ESLint page](/Standards/eslint#installation)
|
|
124
|
+
| What you will see | What it means | How to fix it |
|
|
125
|
+
| ------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
|
126
|
+
| `typescript-eslint does not support TS 7.0` | The linter cannot run against TypeScript 7 yet | See the [TypeScript 7 note](/docs/Standards/eslint#installation) on the ESLint page |
|
|
127
|
+
| `contextOrFilename.getFilename is not a function` | The React plugin cannot detect the React version | Set `settings.react.version` to the project's React version instead of `"detect"` |
|
|
128
|
+
| `Cannot find module '…'` | A lint package is not installed | Run the install commands on the [ESLint page](/docs/Standards/eslint#installation) |
|
|
128
129
|
|
|
129
130
|
---
|
|
130
131
|
|
|
@@ -132,4 +133,4 @@ Nothing else is switched off. If another rule seems impossible to satisfy, the c
|
|
|
132
133
|
|
|
133
134
|
**Work is not finished until `pnpm lint` passes.** That goes for people and AI agents alike: an agent fixes every lint error before it says a task is done.
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
Most projects check it automatically on every change, with a CI job that runs `pnpm lint` (often with `pnpm test` and the build) or with a git hook. Which one, if any, is the project's own choice. See [Project workflow](/docs/Standards/agent-specs#project-workflow).
|