eslint-plugin-nextfriday 4.0.0 → 4.1.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,15 @@
1
1
  # eslint-plugin-nextfriday
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#124](https://github.com/next-friday/eslint-plugin-nextfriday/pull/124) [`be12809`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/be128098e2af102a153c3d01546a2921518f4b96) Thanks [@joetakara](https://github.com/joetakara)! - add two JSX rules targeting unnecessary wrapper noise (the "Divitis" anti-pattern):
8
+ - `no-ghost-wrapper` flags bare `<div>` / `<span>` elements that carry no meaningful attributes. Self-closing variants are checked the same way. The `key` prop alone does not silence the rule, since `key` carries no structural intent. Any other attribute — `className`, `style`, `id`, `ref`, `role`, `aria-*`, `data-*`, `tabIndex`, event handlers, or spread attributes — is considered meaningful and lets the element pass.
9
+ - `no-redundant-fragment` flags Fragments (`<>...</>`, `<Fragment>`, `<React.Fragment>`) that wrap zero or one child. JSX text consisting only of whitespace is not counted. Long-form Fragments carrying a `key` attribute are exempt, since `key` is the canonical reason long-form Fragment exists (the shorthand `<>...</>` cannot accept attributes).
10
+
11
+ Both rules are report-only — no autofix is provided so authors retain control over which structural alternative (Fragment, semantic element, unwrapping the children) best fits the surrounding code. Both are added to the JSX rule tier and ship in the `react`, `react/recommended`, `nextjs`, and `nextjs/recommended` presets at `warn` and `error` severity respectively.
12
+
3
13
  ## 4.0.0
4
14
 
5
15
  ### Major Changes
package/README.md CHANGED
@@ -467,6 +467,8 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
467
467
  | [jsx-simple-props](docs/rules/JSX_SIMPLE_PROPS.md) | Enforce simple prop values (strings, variables, callbacks, ReactNode) | ❌ |
468
468
  | [jsx-sort-props](docs/rules/JSX_SORT_PROPS.md) | Enforce JSX props are sorted by value type | ✅ |
469
469
  | [jsx-spread-props-last](docs/rules/JSX_SPREAD_PROPS_LAST.md) | Enforce JSX spread attributes appear after all other props | ❌ |
470
+ | [no-ghost-wrapper](docs/rules/NO_GHOST_WRAPPER.md) | Disallow bare `<div>`/`<span>` with no meaningful attributes | ❌ |
471
+ | [no-redundant-fragment](docs/rules/NO_REDUNDANT_FRAGMENT.md) | Disallow Fragments wrapping zero or one child | ❌ |
470
472
  | [prefer-jsx-template-literals](docs/rules/PREFER_JSX_TEMPLATE_LITERALS.md) | Enforce template literals instead of mixing text and JSX expressions | ✅ |
471
473
  | [prefer-props-with-children](docs/rules/PREFER_PROPS_WITH_CHILDREN.md) | Prefer PropsWithChildren over manually declaring children: ReactNode | ❌ |
472
474
  | [react-props-destructure](docs/rules/REACT_PROPS_DESTRUCTURE.md) | Enforce destructuring props inside React component body | ❌ |
@@ -481,10 +483,10 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
481
483
  | -------------------- | -------- | ---------- | --------- | ----------- |
482
484
  | `base` | warn | 40 | 0 | 40 |
483
485
  | `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 |
486
+ | `react` | warn | 40 | 19 | 59 |
487
+ | `react/recommended` | error | 40 | 19 | 59 |
488
+ | `nextjs` | warn | 40 | 19 | 59 |
489
+ | `nextjs/recommended` | error | 40 | 19 | 59 |
488
490
 
489
491
  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.
490
492
 
@@ -533,7 +535,7 @@ Included in `base`, `base/recommended`, and all other presets:
533
535
  - `nextfriday/sort-type-alphabetically`
534
536
  - `nextfriday/sort-type-required-first`
535
537
 
536
- ### JSX Rules (17 rules)
538
+ ### JSX Rules (19 rules)
537
539
 
538
540
  Additionally included in `react`, `react/recommended`, `nextjs`, `nextjs/recommended`:
539
541
 
@@ -549,6 +551,8 @@ Additionally included in `react`, `react/recommended`, `nextjs`, `nextjs/recomme
549
551
  - `nextfriday/jsx-simple-props`
550
552
  - `nextfriday/jsx-sort-props`
551
553
  - `nextfriday/jsx-spread-props-last`
554
+ - `nextfriday/no-ghost-wrapper`
555
+ - `nextfriday/no-redundant-fragment`
552
556
  - `nextfriday/prefer-interface-for-component-props`
553
557
  - `nextfriday/prefer-interface-over-inline-types`
554
558
  - `nextfriday/prefer-jsx-template-literals`
@@ -571,16 +575,6 @@ Additionally included in `react`, `react/recommended`, `nextjs`, `nextjs/recomme
571
575
  - **Clean code practices**: Prevents emoji usage, enforces parameter destructuring, and more
572
576
  - **Formatting rules**: Enforces consistent blank lines around multi-line blocks and return statements
573
577
 
574
- ## Agent Skill
575
-
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.
577
-
578
- ```bash
579
- npx skills add next-friday/eslint-plugin-nextfriday --skill eslint-plugin-nextfriday
580
- ```
581
-
582
- Once installed, AI assistants will automatically follow the naming, code style, type, JSX, import, and formatting patterns enforced by this plugin — reducing lint errors to zero.
583
-
584
578
  ## Need Help?
585
579
 
586
580
  If you encounter any issues or have questions:
@@ -0,0 +1,75 @@
1
+ # no-ghost-wrapper
2
+
3
+ Disallow bare `<div>` and `<span>` elements that have no meaningful attributes.
4
+
5
+ ## Rule Details
6
+
7
+ This rule flags `<div>` and `<span>` elements that carry zero meaningful attributes — the "ghost wrapper" anti-pattern (also called "Divitis"). These elements add depth to the DOM tree without contributing semantics, styling, behavior, accessibility hooks, or test hooks.
8
+
9
+ The `key` prop alone does not count as meaningful: `key` is a React-only signal for list reconciliation and carries no structural intent.
10
+
11
+ ### Why?
12
+
13
+ - **Semantic clarity**: A wrapper without attributes signals that the author has not decided whether the element is structural, presentational, or semantic.
14
+ - **DOM bloat**: Empty wrappers extend the DOM tree, increasing layout work and accessibility-tree noise.
15
+ - **Cognitive load**: Reviewers must guess whether a bare wrapper is intentional or a leftover from refactoring.
16
+ - **Better alternatives exist**: Fragments (`<>...</>`), semantic elements (`<section>`, `<article>`, `<header>`, `<nav>`), or simply unwrapping the children.
17
+
18
+ ## Examples
19
+
20
+ ### Incorrect
21
+
22
+ ```tsx
23
+ <div>x</div>
24
+ <span>text</span>
25
+ <div></div>
26
+ <div> </div>
27
+ <div />
28
+ <span />
29
+ <div key={item.id}>x</div>
30
+ <div>{children}</div>
31
+ ```
32
+
33
+ ### Correct
34
+
35
+ ```tsx
36
+ <div className="container">x</div>
37
+ <div data-testid="root">x</div>
38
+ <div role="button">x</div>
39
+ <div aria-label="close">x</div>
40
+ <div ref={ref}>x</div>
41
+ <div onClick={handleClick}>x</div>
42
+ <div id="anchor">x</div>
43
+ <div style={{ color: "red" }}>x</div>
44
+ <div {...props}>x</div>
45
+ <div tabIndex={0}>x</div>
46
+ <section>x</section>
47
+ <article>x</article>
48
+ <>x</>
49
+ ```
50
+
51
+ ## What This Rule Checks
52
+
53
+ The rule fires on a `<div>` or `<span>` opening element when, after filtering out a lone `key` attribute, it has zero remaining attributes (including spread attributes). Self-closing elements (`<div />`, `<span />`) are checked the same way.
54
+
55
+ Any of the following attributes count as meaningful and silence the rule:
56
+
57
+ - `className`
58
+ - `style`
59
+ - `id`
60
+ - `ref`
61
+ - `role`
62
+ - `aria-*`
63
+ - `data-*`
64
+ - `tabIndex`
65
+ - Event handlers (`onClick`, `onChange`, etc.)
66
+ - Spread attributes (`{...props}`)
67
+ - Any other JSX attribute except `key`
68
+
69
+ ## When Not To Use It
70
+
71
+ If your codebase intentionally uses bare `<div>` and `<span>` as structural placeholders, or if you rely on parent CSS selectors that target a fixed wrapper depth.
72
+
73
+ ## Related Rules
74
+
75
+ - [no-redundant-fragment](NO_REDUNDANT_FRAGMENT.md)
@@ -0,0 +1,56 @@
1
+ # no-redundant-fragment
2
+
3
+ Disallow Fragments that wrap zero or one child.
4
+
5
+ ## Rule Details
6
+
7
+ This rule flags Fragments (`<>...</>`, `<Fragment>...</Fragment>`, `<React.Fragment>...</React.Fragment>`) that wrap zero or one child. A Fragment is only justified when grouping multiple sibling nodes, or when a `key` prop is required during list iteration.
8
+
9
+ A Fragment with a single child is structurally equivalent to that child alone. An empty Fragment renders nothing and adds noise to the source.
10
+
11
+ ### Why?
12
+
13
+ - **Source clarity**: A Fragment around a single child is dead syntax — it tells the reader nothing.
14
+ - **Less noise in the AST**: Devtools, search, and codemods do not have to step over an extra layer.
15
+ - **Forces a decision**: Either the children are siblings (Fragment is the right tool) or they are not (drop it).
16
+
17
+ ## Examples
18
+
19
+ ### Incorrect
20
+
21
+ ```tsx
22
+ <></>
23
+ <>{x}</>
24
+ <><A /></>
25
+ <>text</>
26
+ <Fragment>x</Fragment>
27
+ <React.Fragment>{x}</React.Fragment>
28
+ <React.Fragment></React.Fragment>
29
+ ```
30
+
31
+ ### Correct
32
+
33
+ ```tsx
34
+ <>{a}{b}</>
35
+ <><A /><B /></>
36
+ <Fragment>{a}{b}</Fragment>
37
+ <React.Fragment>{a}{b}</React.Fragment>
38
+
39
+ // key is the legitimate reason to use long-form Fragment:
40
+ <React.Fragment key={item.id}>{item.label}{item.value}</React.Fragment>
41
+ <Fragment key={k}>{x}</Fragment>
42
+ ```
43
+
44
+ ## What This Rule Checks
45
+
46
+ A Fragment is flagged when its meaningful children count is `0` or `1`. JSX text nodes that contain only whitespace are not counted as children.
47
+
48
+ The rule does not fire on a long-form Fragment (`<Fragment>` or `<React.Fragment>`) that carries a `key` attribute — `key` is the canonical reason long-form Fragment exists, since the shorthand `<>...</>` cannot accept attributes.
49
+
50
+ ## When Not To Use It
51
+
52
+ If your codebase uses single-child Fragments to keep diffs stable around conditionally-rendered siblings, or if you rely on a build-time transform that depends on the Fragment wrapper.
53
+
54
+ ## Related Rules
55
+
56
+ - [no-ghost-wrapper](NO_GHOST_WRAPPER.md)