eslint-plugin-nextfriday 3.2.0 → 4.0.0
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/CHANGELOG.md +22 -0
- package/README.md +33 -61
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +22 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/rules/ENFORCE_CONSTANT_CASE.md +28 -17
- package/docs/rules/NO_INLINE_NESTED_OBJECT.md +45 -27
- package/docs/rules/PREFER_GUARD_CLAUSE.md +2 -0
- package/docs/rules/PREFER_IMPORT_TYPE.md +5 -0
- package/docs/rules/PREFER_INTERFACE_FOR_COMPONENT_PROPS.md +53 -0
- package/docs/rules/PREFER_NAMED_PARAM_TYPES.md +5 -1
- package/docs/rules/PREFER_PROPS_WITH_CHILDREN.md +112 -0
- package/lib/index.cjs +667 -757
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +92 -200
- package/lib/index.d.ts +92 -200
- package/lib/index.js +634 -724
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/docs/rules/ENFORCE_CURLY_NEWLINE.md +0 -55
- package/docs/rules/FILE_KEBAB_CASE.md +0 -70
- package/docs/rules/JSX_PASCAL_CASE.md +0 -71
- package/docs/rules/NEXTJS_REQUIRE_PUBLIC_ENV.md +0 -44
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# eslint-plugin-nextfriday
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [#122](https://github.com/next-friday/eslint-plugin-nextfriday/pull/122) [`a9c5237`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/a9c52375dfe7d7064ead37c611c680e4c8d6be9f) Thanks [@joetakara](https://github.com/joetakara)! - Breaking changes
|
|
8
|
+
- Remove `enforce-curly-newline` rule. Use ESLint's built-in `curly: "all"` instead. Consumers should add `"curly": ["error", "all"]` to their ESLint config.
|
|
9
|
+
- Narrow `no-inline-nested-object` to truly nested structures only. Flat collections (objects of primitive properties, arrays of identifiers/member expressions/primitives) are now allowed inline because Prettier already controls their wrapping via `printWidth`. Eliminates the fix loop with Prettier on cases like `{ allow: [target.utils, target.types, target.constants] }`.
|
|
10
|
+
|
|
11
|
+
New rules
|
|
12
|
+
- Add `prefer-props-with-children`. Reports `children: ReactNode` declarations and recommends `PropsWithChildren<T>`. Included in `react` and `nextjs` presets.
|
|
13
|
+
- Add `prefer-interface-for-component-props`. Auto-fixes `type FooProps = {...}` to `interface FooProps {...}` in `.tsx` and `.jsx` files. Included in `react` and `nextjs` presets.
|
|
14
|
+
|
|
15
|
+
Fixes
|
|
16
|
+
- `prefer-import-type` skips imports with inline `type` markers, deferring to `no-inline-type-import`.
|
|
17
|
+
- `prefer-named-param-types` skips React component functions with single non-destructured `Identifier` param, deferring to `prefer-interface-over-inline-types`.
|
|
18
|
+
|
|
19
|
+
## 3.2.1
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [#120](https://github.com/next-friday/eslint-plugin-nextfriday/pull/120) [`6f56995`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/6f569958e538f23b699fa3ac1cb4743b87e4ab60) Thanks [@joetakara](https://github.com/joetakara)! - `enforce-constant-case` now only flags global `const` declarations bound to a magic number or magic text literal. Object literals, array literals, RegExp, template literals (static or dynamic), `as const` assertions, booleans, and any non-literal initializer are no longer checked. This unblocks Next.js App Router files where reserved exports like `metadata`, `viewport`, `dynamic`, `revalidate`, `runtime`, `fetchCache`, `dynamicParams`, `preferredRegion`, and `maxDuration` are framework-owned and must keep their exact names. The rule scope now matches the documented intent of the plugin's naming convention skill ("top-level constant primitive values") instead of every static-shaped initializer.
|
|
24
|
+
|
|
3
25
|
## 3.2.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ export default [nextfriday.configs["nextjs/recommended"]];
|
|
|
50
50
|
|
|
51
51
|
To use a preset and adjust individual rules, append a second config object after the preset. Later objects override earlier ones, so you can change severity, swap options, or add rules without re-declaring the entire preset.
|
|
52
52
|
|
|
53
|
-
For example,
|
|
53
|
+
For example, start with the `react/recommended` preset (which runs `nextfriday/enforce-camel-case` and `nextfriday/enforce-props-suffix` as errors) and add a rule override on top:
|
|
54
54
|
|
|
55
55
|
```js
|
|
56
56
|
import nextfriday from "eslint-plugin-nextfriday";
|
|
@@ -60,7 +60,6 @@ export default [
|
|
|
60
60
|
|
|
61
61
|
{
|
|
62
62
|
rules: {
|
|
63
|
-
"nextfriday/jsx-pascal-case": "error",
|
|
64
63
|
"nextfriday/enforce-props-suffix": "error",
|
|
65
64
|
"nextfriday/sort-imports": "warn",
|
|
66
65
|
},
|
|
@@ -68,7 +67,7 @@ export default [
|
|
|
68
67
|
];
|
|
69
68
|
```
|
|
70
69
|
|
|
71
|
-
The first object enables every rule in `react/recommended`. The second object reaffirms `
|
|
70
|
+
The first object enables every rule in `react/recommended`. The second object reaffirms `enforce-props-suffix` (already enforced — useful when you want it loud and explicit) and downgrades `sort-imports` from error to warning.
|
|
72
71
|
|
|
73
72
|
### Manual Configuration
|
|
74
73
|
|
|
@@ -108,10 +107,6 @@ export default [
|
|
|
108
107
|
"nextfriday/enforce-property-case": "error",
|
|
109
108
|
"nextfriday/no-misleading-constant-case": "error",
|
|
110
109
|
|
|
111
|
-
// File Naming
|
|
112
|
-
"nextfriday/file-kebab-case": "error",
|
|
113
|
-
"nextfriday/jsx-pascal-case": "error",
|
|
114
|
-
|
|
115
110
|
// Code Style
|
|
116
111
|
"nextfriday/no-emoji": "error",
|
|
117
112
|
"nextfriday/prefer-destructuring-params": "error",
|
|
@@ -130,7 +125,6 @@ export default [
|
|
|
130
125
|
"nextfriday/no-inline-nested-object": "error",
|
|
131
126
|
"nextfriday/no-inline-return-properties": "error",
|
|
132
127
|
"nextfriday/prefer-async-await": "error",
|
|
133
|
-
"nextfriday/enforce-curly-newline": "error",
|
|
134
128
|
"nextfriday/no-nested-ternary": "error",
|
|
135
129
|
"nextfriday/prefer-guard-clause": "error",
|
|
136
130
|
|
|
@@ -166,9 +160,6 @@ export default [
|
|
|
166
160
|
"nextfriday/react-props-destructure": "error",
|
|
167
161
|
"nextfriday/enforce-props-suffix": "error",
|
|
168
162
|
"nextfriday/enforce-readonly-component-props": "error",
|
|
169
|
-
|
|
170
|
-
// Next.js
|
|
171
|
-
"nextfriday/nextjs-require-public-env": "error",
|
|
172
163
|
},
|
|
173
164
|
},
|
|
174
165
|
];
|
|
@@ -388,11 +379,11 @@ Once a directory hits zero, lock it in (step 4). Once the warn-level count hits
|
|
|
388
379
|
|
|
389
380
|
When the warn-level preset surfaces hundreds of violations, fix them in this order — high-impact rules catch real bugs, while low-impact rules are style preferences that can wait.
|
|
390
381
|
|
|
391
|
-
| Tier | Examples
|
|
392
|
-
| ------------------------------------- |
|
|
393
|
-
| High — correctness and runtime safety | `no-direct-date`, `no-env-fallback`, `
|
|
394
|
-
| Medium — structure and naming | `boolean-naming-prefix`, `enforce-camel-case`, `enforce-constant-case`, `
|
|
395
|
-
| Low — formatting and ordering | `sort-imports`, `sort-exports`, `sort-type-alphabetically`, `jsx-sort-props`, `newline-before-return`, `newline-after-multiline-block`, `no-emoji`
|
|
382
|
+
| Tier | Examples | Why first |
|
|
383
|
+
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
384
|
+
| High — correctness and runtime safety | `no-direct-date`, `no-env-fallback`, `enforce-readonly-component-props`, `jsx-no-non-component-function`, `enforce-hook-naming`, `no-logic-in-params` | Each violation can mask a bug, leak config, or break React's rules of hooks. Fix before they ship. |
|
|
385
|
+
| Medium — structure and naming | `boolean-naming-prefix`, `enforce-camel-case`, `enforce-constant-case`, `enforce-props-suffix`, `prefer-import-type` | No runtime impact, but inconsistent naming compounds review and onboarding cost. Fix once the high tier is clean. |
|
|
386
|
+
| Low — formatting and ordering | `sort-imports`, `sort-exports`, `sort-type-alphabetically`, `jsx-sort-props`, `newline-before-return`, `newline-after-multiline-block`, `no-emoji` | Cosmetic. Most are auto-fixable, so a single `pnpm eslint --fix` pass typically clears the whole codebase. Save these for last. |
|
|
396
387
|
|
|
397
388
|
In practice: turn the high tier on as `"error"` first, leave the medium tier as `"warn"` while you migrate, and run the auto-fixers for the low tier in a single dedicated PR.
|
|
398
389
|
|
|
@@ -412,13 +403,6 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
|
|
|
412
403
|
| [enforce-property-case](docs/rules/ENFORCE_PROPERTY_CASE.md) | Enforce camelCase for unquoted object property keys | ❌ |
|
|
413
404
|
| [no-misleading-constant-case](docs/rules/NO_MISLEADING_CONSTANT_CASE.md) | Disallow SCREAMING_SNAKE_CASE in local scope and for dynamic values | ❌ |
|
|
414
405
|
|
|
415
|
-
### File Naming Rules
|
|
416
|
-
|
|
417
|
-
| Rule | Description | Fixable |
|
|
418
|
-
| ------------------------------------------------ | ---------------------------------------------------- | ------- |
|
|
419
|
-
| [file-kebab-case](docs/rules/FILE_KEBAB_CASE.md) | Enforce kebab-case filenames for .ts and .js files | ❌ |
|
|
420
|
-
| [jsx-pascal-case](docs/rules/JSX_PASCAL_CASE.md) | Enforce PascalCase filenames for .jsx and .tsx files | ❌ |
|
|
421
|
-
|
|
422
406
|
### Code Style Rules
|
|
423
407
|
|
|
424
408
|
| Rule | Description | Fixable |
|
|
@@ -441,7 +425,6 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
|
|
|
441
425
|
| [no-inline-nested-object](docs/rules/NO_INLINE_NESTED_OBJECT.md) | Require nested objects and arrays to span multiple lines | ✅ |
|
|
442
426
|
| [no-inline-return-properties](docs/rules/NO_INLINE_RETURN_PROPERTIES.md) | Require return object properties to use shorthand notation | ❌ |
|
|
443
427
|
| [prefer-async-await](docs/rules/PREFER_ASYNC_AWAIT.md) | Enforce async/await over .then() promise chains | ❌ |
|
|
444
|
-
| [enforce-curly-newline](docs/rules/ENFORCE_CURLY_NEWLINE.md) | Enforce curly braces for multi-line if, forbid for single-line | ✅ |
|
|
445
428
|
| [no-nested-ternary](docs/rules/NO_NESTED_TERNARY.md) | Disallow nested ternary expressions | ❌ |
|
|
446
429
|
| [prefer-guard-clause](docs/rules/PREFER_GUARD_CLAUSE.md) | Enforce guard clause pattern instead of nested if statements | ❌ |
|
|
447
430
|
|
|
@@ -458,16 +441,17 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
|
|
|
458
441
|
|
|
459
442
|
### Type Pattern Rules
|
|
460
443
|
|
|
461
|
-
| Rule
|
|
462
|
-
|
|
|
463
|
-
| [enforce-type-declaration-order](docs/rules/ENFORCE_TYPE_DECLARATION_ORDER.md)
|
|
464
|
-
| [no-nested-interface-declaration](docs/rules/NO_NESTED_INTERFACE_DECLARATION.md)
|
|
465
|
-
| [prefer-named-param-types](docs/rules/PREFER_NAMED_PARAM_TYPES.md)
|
|
466
|
-
| [prefer-inline-literal-union](docs/rules/PREFER_INLINE_LITERAL_UNION.md)
|
|
467
|
-
| [prefer-inline-type-export](docs/rules/PREFER_INLINE_TYPE_EXPORT.md)
|
|
468
|
-
| [prefer-interface-
|
|
469
|
-
| [
|
|
470
|
-
| [sort-type-
|
|
444
|
+
| Rule | Description | Fixable |
|
|
445
|
+
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | ------- |
|
|
446
|
+
| [enforce-type-declaration-order](docs/rules/ENFORCE_TYPE_DECLARATION_ORDER.md) | Enforce referenced types are declared after their consumer | ❌ |
|
|
447
|
+
| [no-nested-interface-declaration](docs/rules/NO_NESTED_INTERFACE_DECLARATION.md) | Disallow inline object types in interface/type properties | ❌ |
|
|
448
|
+
| [prefer-named-param-types](docs/rules/PREFER_NAMED_PARAM_TYPES.md) | Enforce named types for function parameters with object types | ❌ |
|
|
449
|
+
| [prefer-inline-literal-union](docs/rules/PREFER_INLINE_LITERAL_UNION.md) | Enforce inlining literal union types for better IDE hover info | ✅ |
|
|
450
|
+
| [prefer-inline-type-export](docs/rules/PREFER_INLINE_TYPE_EXPORT.md) | Require type/interface exports inline at the declaration | ✅ |
|
|
451
|
+
| [prefer-interface-for-component-props](docs/rules/PREFER_INTERFACE_FOR_COMPONENT_PROPS.md) | Enforce interface over type alias for component prop declarations | ✅ |
|
|
452
|
+
| [prefer-interface-over-inline-types](docs/rules/PREFER_INTERFACE_OVER_INLINE_TYPES.md) | Enforce interface declarations over inline types for React props | ❌ |
|
|
453
|
+
| [sort-type-alphabetically](docs/rules/SORT_TYPE_ALPHABETICALLY.md) | Enforce A-Z sorting of properties within type groups | ✅ |
|
|
454
|
+
| [sort-type-required-first](docs/rules/SORT_TYPE_REQUIRED_FIRST.md) | Enforce required properties before optional in types/interfaces | ✅ |
|
|
471
455
|
|
|
472
456
|
### React/JSX Rules
|
|
473
457
|
|
|
@@ -484,45 +468,38 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
|
|
|
484
468
|
| [jsx-sort-props](docs/rules/JSX_SORT_PROPS.md) | Enforce JSX props are sorted by value type | ✅ |
|
|
485
469
|
| [jsx-spread-props-last](docs/rules/JSX_SPREAD_PROPS_LAST.md) | Enforce JSX spread attributes appear after all other props | ❌ |
|
|
486
470
|
| [prefer-jsx-template-literals](docs/rules/PREFER_JSX_TEMPLATE_LITERALS.md) | Enforce template literals instead of mixing text and JSX expressions | ✅ |
|
|
471
|
+
| [prefer-props-with-children](docs/rules/PREFER_PROPS_WITH_CHILDREN.md) | Prefer PropsWithChildren over manually declaring children: ReactNode | ❌ |
|
|
487
472
|
| [react-props-destructure](docs/rules/REACT_PROPS_DESTRUCTURE.md) | Enforce destructuring props inside React component body | ❌ |
|
|
488
473
|
| [enforce-props-suffix](docs/rules/ENFORCE_PROPS_SUFFIX.md) | Enforce 'Props' suffix for interfaces and types in \*.tsx files | ❌ |
|
|
489
474
|
| [enforce-readonly-component-props](docs/rules/ENFORCE_READONLY_COMPONENT_PROPS.md) | Enforce Readonly wrapper for React component props | ✅ |
|
|
490
475
|
|
|
491
|
-
### Next.js Rules
|
|
492
|
-
|
|
493
|
-
| Rule | Description | Fixable |
|
|
494
|
-
| -------------------------------------------------------------------- | ------------------------------------------------------------- | ------- |
|
|
495
|
-
| [nextjs-require-public-env](docs/rules/NEXTJS_REQUIRE_PUBLIC_ENV.md) | Require NEXT*PUBLIC* prefix for env vars in client components | ❌ |
|
|
496
|
-
|
|
497
476
|
## Configurations
|
|
498
477
|
|
|
499
478
|
### Configuration Presets Overview
|
|
500
479
|
|
|
501
|
-
| Preset | Severity | Base Rules | JSX Rules |
|
|
502
|
-
| -------------------- | -------- | ---------- | --------- |
|
|
503
|
-
| `base` | warn |
|
|
504
|
-
| `base/recommended` | error |
|
|
505
|
-
| `react` | warn |
|
|
506
|
-
| `react/recommended` | error |
|
|
507
|
-
| `nextjs` | warn |
|
|
508
|
-
| `nextjs/recommended` | error |
|
|
480
|
+
| Preset | Severity | Base Rules | JSX Rules | Total Rules |
|
|
481
|
+
| -------------------- | -------- | ---------- | --------- | ----------- |
|
|
482
|
+
| `base` | warn | 40 | 0 | 40 |
|
|
483
|
+
| `base/recommended` | error | 40 | 0 | 40 |
|
|
484
|
+
| `react` | warn | 40 | 17 | 57 |
|
|
485
|
+
| `react/recommended` | error | 40 | 17 | 57 |
|
|
486
|
+
| `nextjs` | warn | 40 | 17 | 57 |
|
|
487
|
+
| `nextjs/recommended` | error | 40 | 17 | 57 |
|
|
509
488
|
|
|
510
|
-
The `nextjs` and `nextjs/recommended` presets
|
|
489
|
+
The `nextjs` and `nextjs/recommended` presets currently share the same rule set as `react` and `react/recommended`; they are kept as named aliases for ergonomics.
|
|
511
490
|
|
|
512
|
-
### Base Configuration Rules (
|
|
491
|
+
### Base Configuration Rules (40 rules)
|
|
513
492
|
|
|
514
493
|
Included in `base`, `base/recommended`, and all other presets:
|
|
515
494
|
|
|
516
495
|
- `nextfriday/boolean-naming-prefix`
|
|
517
496
|
- `nextfriday/enforce-camel-case`
|
|
518
497
|
- `nextfriday/enforce-constant-case`
|
|
519
|
-
- `nextfriday/enforce-curly-newline`
|
|
520
498
|
- `nextfriday/enforce-hook-naming`
|
|
521
499
|
- `nextfriday/enforce-property-case`
|
|
522
500
|
- `nextfriday/enforce-service-naming`
|
|
523
501
|
- `nextfriday/enforce-sorted-destructuring`
|
|
524
502
|
- `nextfriday/enforce-type-declaration-order`
|
|
525
|
-
- `nextfriday/file-kebab-case`
|
|
526
503
|
- `nextfriday/index-export-only`
|
|
527
504
|
- `nextfriday/newline-after-multiline-block`
|
|
528
505
|
- `nextfriday/newline-before-return`
|
|
@@ -556,7 +533,7 @@ Included in `base`, `base/recommended`, and all other presets:
|
|
|
556
533
|
- `nextfriday/sort-type-alphabetically`
|
|
557
534
|
- `nextfriday/sort-type-required-first`
|
|
558
535
|
|
|
559
|
-
### JSX Rules (
|
|
536
|
+
### JSX Rules (17 rules)
|
|
560
537
|
|
|
561
538
|
Additionally included in `react`, `react/recommended`, `nextjs`, `nextjs/recommended`:
|
|
562
539
|
|
|
@@ -568,21 +545,16 @@ Additionally included in `react`, `react/recommended`, `nextjs`, `nextjs/recomme
|
|
|
568
545
|
- `nextfriday/jsx-no-non-component-function`
|
|
569
546
|
- `nextfriday/jsx-no-ternary-null`
|
|
570
547
|
- `nextfriday/jsx-no-variable-in-callback`
|
|
571
|
-
- `nextfriday/jsx-pascal-case`
|
|
572
548
|
- `nextfriday/jsx-require-suspense`
|
|
573
549
|
- `nextfriday/jsx-simple-props`
|
|
574
550
|
- `nextfriday/jsx-sort-props`
|
|
575
551
|
- `nextfriday/jsx-spread-props-last`
|
|
552
|
+
- `nextfriday/prefer-interface-for-component-props`
|
|
576
553
|
- `nextfriday/prefer-interface-over-inline-types`
|
|
577
554
|
- `nextfriday/prefer-jsx-template-literals`
|
|
555
|
+
- `nextfriday/prefer-props-with-children`
|
|
578
556
|
- `nextfriday/react-props-destructure`
|
|
579
557
|
|
|
580
|
-
### Next.js Only Rules (1 rule)
|
|
581
|
-
|
|
582
|
-
Additionally included in `nextjs`, `nextjs/recommended` only:
|
|
583
|
-
|
|
584
|
-
- `nextfriday/nextjs-require-public-env`
|
|
585
|
-
|
|
586
558
|
### Severity Levels
|
|
587
559
|
|
|
588
560
|
- **`base` / `react` / `nextjs`**: All rules set to `"warn"`
|
|
@@ -601,7 +573,7 @@ Additionally included in `nextjs`, `nextjs/recommended` only:
|
|
|
601
573
|
|
|
602
574
|
## Agent Skill
|
|
603
575
|
|
|
604
|
-
This plugin ships with an [Agent Skill](https://github.com/anthropics/skills) that teaches AI coding assistants (Claude Code, Cursor, etc.) all
|
|
576
|
+
This plugin ships with an [Agent Skill](https://github.com/anthropics/skills) that teaches AI coding assistants (Claude Code, Cursor, etc.) all 57 rules so they generate compliant code from the start.
|
|
605
577
|
|
|
606
578
|
```bash
|
|
607
579
|
npx skills add next-friday/eslint-plugin-nextfriday --skill eslint-plugin-nextfriday
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Domain Docs
|
|
2
|
+
|
|
3
|
+
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
|
4
|
+
|
|
5
|
+
## Before exploring, read these
|
|
6
|
+
|
|
7
|
+
- **`CONTEXT.md`** at the repo root, or
|
|
8
|
+
- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
|
|
9
|
+
- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
|
|
10
|
+
|
|
11
|
+
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The producer skill (`/grill-with-docs`) creates them lazily when terms or decisions actually get resolved.
|
|
12
|
+
|
|
13
|
+
## File structure
|
|
14
|
+
|
|
15
|
+
Single-context repo (most repos):
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/
|
|
19
|
+
├── CONTEXT.md
|
|
20
|
+
├── docs/adr/
|
|
21
|
+
│ ├── 0001-event-sourced-orders.md
|
|
22
|
+
│ └── 0002-postgres-for-write-model.md
|
|
23
|
+
└── src/
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
/
|
|
30
|
+
├── CONTEXT-MAP.md
|
|
31
|
+
├── docs/adr/ ← system-wide decisions
|
|
32
|
+
└── src/
|
|
33
|
+
├── ordering/
|
|
34
|
+
│ ├── CONTEXT.md
|
|
35
|
+
│ └── docs/adr/ ← context-specific decisions
|
|
36
|
+
└── billing/
|
|
37
|
+
├── CONTEXT.md
|
|
38
|
+
└── docs/adr/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Use the glossary's vocabulary
|
|
42
|
+
|
|
43
|
+
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
|
|
44
|
+
|
|
45
|
+
If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/grill-with-docs`).
|
|
46
|
+
|
|
47
|
+
## Flag ADR conflicts
|
|
48
|
+
|
|
49
|
+
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
|
50
|
+
|
|
51
|
+
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Issue tracker: GitHub
|
|
2
|
+
|
|
3
|
+
Issues and PRDs for this repo live as GitHub issues. Use the `gh` CLI for all operations.
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
|
|
7
|
+
- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies.
|
|
8
|
+
- **Read an issue**: `gh issue view <number> --comments`, filtering comments by `jq` and also fetching labels.
|
|
9
|
+
- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters.
|
|
10
|
+
- **Comment on an issue**: `gh issue comment <number> --body "..."`
|
|
11
|
+
- **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
|
|
12
|
+
- **Close**: `gh issue close <number> --comment "..."`
|
|
13
|
+
|
|
14
|
+
Infer the repo from `git remote -v` — `gh` does this automatically when run inside a clone.
|
|
15
|
+
|
|
16
|
+
## When a skill says "publish to the issue tracker"
|
|
17
|
+
|
|
18
|
+
Create a GitHub issue.
|
|
19
|
+
|
|
20
|
+
## When a skill says "fetch the relevant ticket"
|
|
21
|
+
|
|
22
|
+
Run `gh issue view <number> --comments`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Triage Labels
|
|
2
|
+
|
|
3
|
+
The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker.
|
|
4
|
+
|
|
5
|
+
| Label in mattpocock/skills | Label in our tracker | Meaning |
|
|
6
|
+
| -------------------------- | -------------------- | ---------------------------------------- |
|
|
7
|
+
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
|
|
8
|
+
| `needs-info` | `needs-info` | Waiting on reporter for more information |
|
|
9
|
+
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
|
|
10
|
+
| `ready-for-human` | `ready-for-human` | Requires human implementation |
|
|
11
|
+
| `wontfix` | `wontfix` | Will not be actioned |
|
|
12
|
+
|
|
13
|
+
When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
|
|
14
|
+
|
|
15
|
+
Edit the right-hand column to match whatever vocabulary you actually use.
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
# enforce-constant-case
|
|
2
2
|
|
|
3
|
-
Enforce SCREAMING_SNAKE_CASE for global
|
|
3
|
+
Enforce SCREAMING_SNAKE_CASE for global magic-number and magic-text constants.
|
|
4
4
|
|
|
5
5
|
## Rule Details
|
|
6
6
|
|
|
7
|
-
This rule ensures that global-scope `const` declarations
|
|
7
|
+
This rule ensures that global-scope `const` declarations bound to a **magic number** or **magic text** literal use SCREAMING_SNAKE_CASE. The rule scope is intentionally narrow:
|
|
8
|
+
|
|
9
|
+
- A magic text constant is a string literal: `const API_URL = "https://api.example.com"`
|
|
10
|
+
- A magic number constant is a number literal (including a unary `-`/`+` over a numeric literal): `const PAGE_LIMIT = 10`, `const OFFSET = -1`
|
|
11
|
+
|
|
12
|
+
Anything else is **not** checked: booleans, RegExp, template literals (static or dynamic), arrays, objects, `as const` assertions, function calls, identifiers, member expressions, JSX. Use whatever name fits the value (`metadata`, `viewport`, `statusMap`, `phoneRegex`, `isEnabled`, etc.) — the rule will not flag it.
|
|
8
13
|
|
|
9
14
|
Only global scope (top-level of a file) is checked. Local scope constants inside functions are not checked by this rule.
|
|
10
15
|
|
|
11
16
|
**Config files are exempt.** Files matching `*.config.{ts,mjs,cjs,js}`, `*.rc.*`, `*.setup.*`, `*.spec.*`, `*.test.*`, `.eslintrc*`, `.babelrc*`, and `.prettierrc*` skip this rule entirely. This avoids conflicts with framework conventions that require specific identifier names — e.g. Next.js expects `nextConfig` (not `NEXT_CONFIG`) in `next.config.ts`, Vite expects `config`, Tailwind expects `config`, etc.
|
|
12
17
|
|
|
18
|
+
### Why magic numbers and magic text only?
|
|
19
|
+
|
|
20
|
+
Reserved framework export names commonly bind to objects (Next.js App Router exports `metadata`, `viewport`, `generateStaticParams`, `dynamic`, `revalidate`, `runtime`, `fetchCache`, `dynamicParams`, `preferredRegion`, `maxDuration`; React Server Components and others have similar patterns). Forcing SCREAMING_SNAKE_CASE on any static-shaped initializer would rename those exports and break framework integration. Restricting the rule to bare number and string literals keeps the convention where it adds value (avoiding magic constants scattered through code) without colliding with framework-owned names.
|
|
21
|
+
|
|
13
22
|
## Examples
|
|
14
23
|
|
|
15
24
|
### Incorrect
|
|
@@ -18,10 +27,8 @@ Only global scope (top-level of a file) is checked. Local scope constants inside
|
|
|
18
27
|
const defaultCover = "/images/default.jpg";
|
|
19
28
|
const pageLimit = 10;
|
|
20
29
|
const apiBaseUrl = "https://api.example.com";
|
|
21
|
-
const
|
|
22
|
-
const phoneRegex = /^[0-9]{10}$/;
|
|
30
|
+
const negativeOne = -1;
|
|
23
31
|
const default_theme = "dark";
|
|
24
|
-
export const categories = [{ id: "1" }] as const;
|
|
25
32
|
```
|
|
26
33
|
|
|
27
34
|
### Correct
|
|
@@ -30,35 +37,39 @@ export const categories = [{ id: "1" }] as const;
|
|
|
30
37
|
const DEFAULT_COVER = "/images/default.jpg";
|
|
31
38
|
const PAGE_LIMIT = 10;
|
|
32
39
|
const API_BASE_URL = "https://api.example.com";
|
|
33
|
-
const
|
|
34
|
-
const PHONE_REGEX = /^[0-9]{10}$/;
|
|
40
|
+
const NEGATIVE_ONE = -1;
|
|
35
41
|
const DEFAULT_THEME = "dark";
|
|
36
|
-
export const CATEGORIES = [{ id: "1" }] as const;
|
|
37
42
|
|
|
38
|
-
const SKELETON_ITEMS = [1, 2, 3, 4, 5];
|
|
39
|
-
const MAP_STYLE = { height: "320px", width: "100%" };
|
|
40
|
-
const STATUS_MAP = { ACTIVE: "active" } as const;
|
|
41
|
-
|
|
42
|
-
// Booleans with standard prefixes (is, has, should, etc.) are exempt
|
|
43
43
|
const isProduction = true;
|
|
44
44
|
const hasAccess = false;
|
|
45
|
+
const featureEnabled = true;
|
|
46
|
+
|
|
47
|
+
const phoneRegex = /^[0-9]{10}$/;
|
|
48
|
+
const template = `hello world`;
|
|
49
|
+
const skeletonItems = [1, 2, 3, 4, 5];
|
|
50
|
+
const mapStyle = { height: "320px", width: "100%" };
|
|
51
|
+
const statusMap = { ACTIVE: "active" } as const;
|
|
52
|
+
const categories = [{ id: "1" }] as const;
|
|
53
|
+
|
|
54
|
+
export const metadata: Metadata = { title: "404 - Page Not Found" };
|
|
55
|
+
export const viewport: Viewport = { themeColor: "#fff" };
|
|
56
|
+
export const generateStaticParams = async () => [];
|
|
45
57
|
|
|
46
|
-
// Template literals with expressions are dynamic, camelCase is fine
|
|
47
58
|
const pendingHref = `/branch/${branch.branchNumber}`;
|
|
48
59
|
|
|
49
|
-
// Functions and components are not checked
|
|
50
60
|
const handleClick = () => {};
|
|
51
61
|
const MyComponent = () => {};
|
|
52
62
|
|
|
53
|
-
// Local scope is not checked
|
|
54
63
|
function foo() {
|
|
55
64
|
const maxRetry = 3;
|
|
56
65
|
}
|
|
57
66
|
```
|
|
58
67
|
|
|
68
|
+
> Note: Next.js App Router has a few string-valued reserved exports — `dynamic = "force-dynamic"`, `runtime = "edge"`, `fetchCache = "default-cache"`, etc. — and one number-valued one (`revalidate = 60`, `maxDuration = 30`). These remain in scope for this rule because their initializers are bare literals. Disable `nextfriday/enforce-constant-case` for `app/**` and `pages/**` in your own flat config if you use those exports.
|
|
69
|
+
|
|
59
70
|
## Configuration
|
|
60
71
|
|
|
61
|
-
This rule has no options — only severity is configurable (`"error"`, `"warn"`, `"off"`). It pairs with [`no-misleading-constant-case`](./NO_MISLEADING_CONSTANT_CASE.md) so that
|
|
72
|
+
This rule has no options — only severity is configurable (`"error"`, `"warn"`, `"off"`). It pairs with [`no-misleading-constant-case`](./NO_MISLEADING_CONSTANT_CASE.md) so that magic-literal globals use `SCREAMING_SNAKE_CASE` while local scopes and dynamic values keep `camelCase`.
|
|
62
73
|
|
|
63
74
|
### Install
|
|
64
75
|
|
|
@@ -1,62 +1,80 @@
|
|
|
1
1
|
# no-inline-nested-object
|
|
2
2
|
|
|
3
|
-
Require nested objects
|
|
3
|
+
Require object or array values that contain further nested objects or arrays to span multiple lines.
|
|
4
4
|
|
|
5
5
|
## Rule Details
|
|
6
6
|
|
|
7
|
-
This rule enforces that when an object property's value is
|
|
7
|
+
This rule enforces that when an object property's value is itself an object or array **that contains further nested structures inside**, it must span multiple lines. Flat collections — objects of primitive properties or arrays of simple references — are allowed inline because Prettier already controls their wrapping via `printWidth`.
|
|
8
|
+
|
|
9
|
+
A "nested structure" is any object or array element/property whose value is another object or array. The rule deliberately ignores depth-1 collections so it does not fight Prettier on simple data and configuration tables.
|
|
10
|
+
|
|
11
|
+
### Why?
|
|
12
|
+
|
|
13
|
+
Truly nested structures are easy to misread when collapsed onto a single line, and adding or removing an inner element produces noisy diffs. Flat collections do not have the same problem and are best left to Prettier's line-length logic.
|
|
8
14
|
|
|
9
15
|
## Examples
|
|
10
16
|
|
|
11
17
|
### Incorrect
|
|
12
18
|
|
|
19
|
+
<!-- prettier-ignore -->
|
|
13
20
|
```ts
|
|
14
|
-
const
|
|
15
|
-
|
|
21
|
+
const obj = {
|
|
22
|
+
items: [{ a: 1 }, { b: 2 }],
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
<!-- prettier-ignore -->
|
|
27
|
+
```ts
|
|
28
|
+
const obj = {
|
|
29
|
+
matrix: [[1, 2], [3, 4]],
|
|
16
30
|
};
|
|
17
31
|
```
|
|
18
32
|
|
|
19
33
|
```ts
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
const obj = {
|
|
35
|
+
layer: { inner: { leaf: 1 } },
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const obj = {
|
|
41
|
+
wrap: { items: [1, 2, 3] },
|
|
23
42
|
};
|
|
24
43
|
```
|
|
25
44
|
|
|
26
45
|
### Correct
|
|
27
46
|
|
|
47
|
+
<!-- prettier-ignore -->
|
|
28
48
|
```ts
|
|
29
|
-
const
|
|
30
|
-
|
|
49
|
+
const obj = {
|
|
50
|
+
items: [
|
|
51
|
+
{ a: 1 },
|
|
52
|
+
{ b: 2 },
|
|
53
|
+
],
|
|
31
54
|
};
|
|
32
55
|
```
|
|
33
56
|
|
|
34
57
|
```ts
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
port: 5432,
|
|
39
|
-
name: "myapp",
|
|
58
|
+
const obj = {
|
|
59
|
+
layer: {
|
|
60
|
+
inner: { leaf: 1 },
|
|
40
61
|
},
|
|
41
62
|
};
|
|
42
63
|
```
|
|
43
64
|
|
|
65
|
+
Flat values stay inline:
|
|
66
|
+
|
|
44
67
|
```ts
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
posts: "/api/posts",
|
|
49
|
-
},
|
|
50
|
-
auth: {
|
|
51
|
-
login: "/auth/login",
|
|
52
|
-
logout: "/auth/logout",
|
|
53
|
-
},
|
|
68
|
+
const obj = {
|
|
69
|
+
config: { enabled: true, timeout: 5000 },
|
|
70
|
+
database: { host: "localhost", port: 5432, name: "myapp" },
|
|
54
71
|
};
|
|
55
72
|
```
|
|
56
73
|
|
|
57
74
|
```ts
|
|
58
|
-
const
|
|
59
|
-
|
|
75
|
+
const obj = {
|
|
76
|
+
options: ["primary", "foreground", "danger", "outline", "ghost", "link"],
|
|
77
|
+
allow: [target.utils, target.types, target.constants],
|
|
60
78
|
};
|
|
61
79
|
```
|
|
62
80
|
|
|
@@ -71,8 +89,8 @@ const initialState = {
|
|
|
71
89
|
|
|
72
90
|
## When Not To Use It
|
|
73
91
|
|
|
74
|
-
If
|
|
92
|
+
If your team prefers compact single-line nested structures regardless of depth, or if your project has different formatting preferences.
|
|
75
93
|
|
|
76
94
|
## Fixable
|
|
77
95
|
|
|
78
|
-
This rule is auto-fixable. Running ESLint with the `--fix` flag will
|
|
96
|
+
This rule is auto-fixable. Running ESLint with the `--fix` flag will expand inline collections that contain nested structures onto multiple lines.
|
|
@@ -36,12 +36,14 @@ function validate(user) {
|
|
|
36
36
|
function process(data) {
|
|
37
37
|
if (!data) return [];
|
|
38
38
|
if (!data.items) return [];
|
|
39
|
+
|
|
39
40
|
return data.items.map(toItem);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
function validate(user) {
|
|
43
44
|
if (!user) return null;
|
|
44
45
|
if (!user.isAdmin) return null;
|
|
46
|
+
|
|
45
47
|
return adminDashboard();
|
|
46
48
|
}
|
|
47
49
|
```
|
|
@@ -61,6 +61,10 @@ import { TSESTree } from "@typescript-eslint/utils";
|
|
|
61
61
|
import type { TSESTree } from "@typescript-eslint/utils";
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
## Exceptions
|
|
65
|
+
|
|
66
|
+
This rule defers to [`no-inline-type-import`](./NO_INLINE_TYPE_IMPORT.md) when an import already contains an inline `type` marker (for example, `import { type Foo } from "x"`). Reporting both rules on the same statement produces overlapping messages and conflicting fixes, so `prefer-import-type` skips the import in that case and lets `no-inline-type-import` produce the canonical fix.
|
|
67
|
+
|
|
64
68
|
## When Not To Use It
|
|
65
69
|
|
|
66
70
|
- If your project doesn't use TypeScript
|
|
@@ -69,4 +73,5 @@ import type { TSESTree } from "@typescript-eslint/utils";
|
|
|
69
73
|
|
|
70
74
|
## Related Rules
|
|
71
75
|
|
|
76
|
+
- [no-inline-type-import](./NO_INLINE_TYPE_IMPORT.md) - Owns the `import { type Foo }` case
|
|
72
77
|
- [@typescript-eslint/consistent-type-imports](https://typescript-eslint.io/rules/consistent-type-imports/) - Similar rule from typescript-eslint
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# prefer-interface-for-component-props
|
|
2
|
+
|
|
3
|
+
Enforce `interface` over `type` alias for component prop declarations in component files (`*.tsx`, `*.jsx`).
|
|
4
|
+
|
|
5
|
+
> This rule is auto-fixable using `--fix`.
|
|
6
|
+
|
|
7
|
+
## Rule Details
|
|
8
|
+
|
|
9
|
+
In component files, prop declarations are more idiomatic as `interface` than `type` aliases. Interfaces support declaration merging, are easier to extend, and produce clearer error messages from the TypeScript compiler. This rule converts `type` aliases that look like component prop types into `interface` declarations.
|
|
10
|
+
|
|
11
|
+
The rule fires only when all of the following are true:
|
|
12
|
+
|
|
13
|
+
- The file extension is `.tsx` or `.jsx`
|
|
14
|
+
- The declaration is a `type` alias whose name ends with `Props`
|
|
15
|
+
- The body of the type is an object literal (`{ ... }`)
|
|
16
|
+
|
|
17
|
+
Type aliases with intersection types (`type FooProps = Other & { x: number }`), union types (`type Variant = 'a' | 'b'`), or non-object bodies (`type Props = ReactNode`) are left alone — they cannot be expressed as a single interface without restructuring.
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Incorrect
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
type StorePopoverProps = {
|
|
25
|
+
trigger: ReactNode;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const StorePopover = (props: Readonly<StorePopoverProps>) => {
|
|
29
|
+
return <div>{props.trigger}</div>;
|
|
30
|
+
};
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Correct
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
interface StorePopoverProps {
|
|
37
|
+
trigger: ReactNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const StorePopover = (props: Readonly<StorePopoverProps>) => {
|
|
41
|
+
return <div>{props.trigger}</div>;
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## When Not To Use It
|
|
46
|
+
|
|
47
|
+
- If your codebase uses `type` aliases for component props by convention
|
|
48
|
+
- If you rely on type alias features that interfaces don't support (for example, intersections or unions in the same declaration)
|
|
49
|
+
|
|
50
|
+
## Related Rules
|
|
51
|
+
|
|
52
|
+
- [enforce-props-suffix](./ENFORCE_PROPS_SUFFIX.md) - Enforces the `Props` suffix that this rule keys off
|
|
53
|
+
- [prefer-interface-over-inline-types](./PREFER_INTERFACE_OVER_INLINE_TYPES.md) - Sister rule for inline types in component params
|