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 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, enforce PascalCase for React components via the `react/recommended` preset (which already runs `nextfriday/jsx-pascal-case` and `nextfriday/enforce-camel-case` as errors), and add a rule override on top:
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 `jsx-pascal-case` (already enforced — useful when you want it loud and explicit), enables `enforce-props-suffix`, and downgrades `sort-imports` from error to warning.
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 | Why first |
392
- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
393
- | High — correctness and runtime safety | `no-direct-date`, `no-env-fallback`, `nextjs-require-public-env`, `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. |
394
- | Medium — structure and naming | `boolean-naming-prefix`, `enforce-camel-case`, `enforce-constant-case`, `file-kebab-case`, `jsx-pascal-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. |
395
- | 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. |
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 | Description | Fixable |
462
- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------- |
463
- | [enforce-type-declaration-order](docs/rules/ENFORCE_TYPE_DECLARATION_ORDER.md) | Enforce referenced types are declared after their consumer | ❌ |
464
- | [no-nested-interface-declaration](docs/rules/NO_NESTED_INTERFACE_DECLARATION.md) | Disallow inline object types in interface/type properties | ❌ |
465
- | [prefer-named-param-types](docs/rules/PREFER_NAMED_PARAM_TYPES.md) | Enforce named types for function parameters with object types | ❌ |
466
- | [prefer-inline-literal-union](docs/rules/PREFER_INLINE_LITERAL_UNION.md) | Enforce inlining literal union types for better IDE hover info | ✅ |
467
- | [prefer-inline-type-export](docs/rules/PREFER_INLINE_TYPE_EXPORT.md) | Require type/interface exports inline at the declaration | ✅ |
468
- | [prefer-interface-over-inline-types](docs/rules/PREFER_INTERFACE_OVER_INLINE_TYPES.md) | Enforce interface declarations over inline types for React props | |
469
- | [sort-type-alphabetically](docs/rules/SORT_TYPE_ALPHABETICALLY.md) | Enforce A-Z sorting of properties within type groups | |
470
- | [sort-type-required-first](docs/rules/SORT_TYPE_REQUIRED_FIRST.md) | Enforce required properties before optional in types/interfaces | ✅ |
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 | Next.js Rules | Total Rules |
502
- | -------------------- | -------- | ---------- | --------- | ------------- | ----------- |
503
- | `base` | warn | 42 | 0 | 0 | 42 |
504
- | `base/recommended` | error | 42 | 0 | 0 | 42 |
505
- | `react` | warn | 42 | 16 | 0 | 58 |
506
- | `react/recommended` | error | 42 | 16 | 0 | 58 |
507
- | `nextjs` | warn | 42 | 16 | 1 | 59 |
508
- | `nextjs/recommended` | error | 42 | 16 | 1 | 59 |
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 ship as an array of two flat-config objects: the rule set above, plus a routing override that disables `nextfriday/file-kebab-case` and `nextfriday/jsx-pascal-case` for files matching `app/**/*.{js,jsx,ts,tsx}`, `src/app/**/*.{js,jsx,ts,tsx}`, `pages/**/*.{js,jsx,ts,tsx}`, and `src/pages/**/*.{js,jsx,ts,tsx}`. Next.js owns the filenames in those directories (`page.tsx`, `layout.tsx`, `route.ts`, `middleware.ts`, etc.), so the plugin steps out of the way. ESLint 9+ flattens nested config arrays automatically, so spreading the preset works as expected.
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 (42 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 (16 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 56 rules so they generate compliant code from the start.
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 constant static values.
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 with static values use SCREAMING_SNAKE_CASE naming convention. Static values include: string/number/boolean literals, RegExp, static template literals, `as const` assertions, and objects/arrays containing only literal values.
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 template = `hello world`;
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 TEMPLATE = `hello world`;
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 static globals use `SCREAMING_SNAKE_CASE` while local scopes and dynamic values keep `camelCase`.
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 and arrays with multiple properties to span multiple lines.
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 another object or array with more than one element, it should span multiple lines rather than being written inline. Single-property nested objects are allowed inline. This improves readability and makes diffs cleaner when properties are added or removed.
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 config = {
15
- database: { host: "localhost", port: 5432, name: "myapp" },
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 routes = {
21
- api: { users: "/api/users", posts: "/api/posts" },
22
- auth: { login: "/auth/login", logout: "/auth/logout" },
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 config = {
30
- database: { host: "localhost" },
49
+ const obj = {
50
+ items: [
51
+ { a: 1 },
52
+ { b: 2 },
53
+ ],
31
54
  };
32
55
  ```
33
56
 
34
57
  ```ts
35
- const config = {
36
- database: {
37
- host: "localhost",
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 routes = {
46
- api: {
47
- users: "/api/users",
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 validationRules = {
59
- required: ["name", "email", "password"],
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 you prefer compact inline nested objects for all cases, or if your team has different formatting preferences.
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 automatically expand inline nested objects and arrays with multiple properties to multiple lines.
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