eslint-plugin-nextfriday 1.18.0 → 1.20.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,21 @@
1
1
  # eslint-plugin-nextfriday
2
2
 
3
+ ## 1.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#85](https://github.com/next-friday/eslint-plugin-nextfriday/pull/85) [`280a864`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/280a864f31d32433856d6914f742861d3bb43ec9) Thanks [@joetakara](https://github.com/joetakara)! - feat(rules): add no-misleading-constant-case rule and improve enforce-constant-case
8
+ - Add `no-misleading-constant-case` rule that disallows SCREAMING_SNAKE_CASE for mutable bindings (let/var) and non-static values (function calls, objects, arrays, dynamic templates, computed expressions)
9
+ - Add `prefer-inline-type-export` rule that requires type/interface declarations to be exported inline
10
+ - Update `enforce-constant-case` to exempt booleans with standard prefixes (is, has, should, etc.) to resolve conflict with `boolean-naming-prefix`
11
+ - Update existing rules: enforce-constant-case, jsx-newline-between-elements, no-lazy-identifiers
12
+
13
+ ## 1.19.0
14
+
15
+ ### Minor Changes
16
+
17
+ - [#83](https://github.com/next-friday/eslint-plugin-nextfriday/pull/83) [`b7143d2`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/b7143d2f779ca1ca34f6562d09c1a6168b74c338) Thanks [@joetakara](https://github.com/joetakara)! - Add jsx-no-ternary-null rule to enforce logical AND over ternary with null/undefined in JSX
18
+
3
19
  ## 1.18.0
4
20
 
5
21
  ### Minor Changes
package/README.md CHANGED
@@ -117,6 +117,7 @@ export default [
117
117
  "nextfriday/no-nested-interface-declaration": "error",
118
118
  "nextfriday/prefer-named-param-types": "error",
119
119
  "nextfriday/prefer-inline-literal-union": "error",
120
+ "nextfriday/prefer-inline-type-export": "error",
120
121
  "nextfriday/prefer-interface-over-inline-types": "error",
121
122
  "nextfriday/sort-type-alphabetically": "error",
122
123
  "nextfriday/sort-type-required-first": "error",
@@ -126,6 +127,7 @@ export default [
126
127
  "nextfriday/jsx-no-inline-object-prop": "error",
127
128
  "nextfriday/jsx-no-newline-single-line-elements": "error",
128
129
  "nextfriday/jsx-no-non-component-function": "error",
130
+ "nextfriday/jsx-no-ternary-null": "error",
129
131
  "nextfriday/jsx-no-variable-in-callback": "error",
130
132
  "nextfriday/jsx-require-suspense": "error",
131
133
  "nextfriday/jsx-simple-props": "error",
@@ -148,12 +150,13 @@ export default [
148
150
 
149
151
  ### Variable Naming Rules
150
152
 
151
- | Rule | Description | Fixable |
152
- | ------------------------------------------------------------------ | --------------------------------------------------------------------- | ------- |
153
- | [no-single-char-variables](docs/rules/NO_SINGLE_CHAR_VARIABLES.md) | Disallow single character variable names (e.g., `d`, `u`, `l`) | ❌ |
154
- | [no-lazy-identifiers](docs/rules/NO_LAZY_IDENTIFIERS.md) | Disallow lazy identifiers like `xxx`, `asdf`, `qwerty` | ❌ |
155
- | [boolean-naming-prefix](docs/rules/BOOLEAN_NAMING_PREFIX.md) | Enforce boolean variables to have prefix (is, has, should, can, etc.) | ❌ |
156
- | [enforce-constant-case](docs/rules/ENFORCE_CONSTANT_CASE.md) | Enforce SCREAMING_SNAKE_CASE for constant primitive values | ❌ |
153
+ | Rule | Description | Fixable |
154
+ | ------------------------------------------------------------------------ | --------------------------------------------------------------------- | ------- |
155
+ | [no-single-char-variables](docs/rules/NO_SINGLE_CHAR_VARIABLES.md) | Disallow single character variable names (e.g., `d`, `u`, `l`) | ❌ |
156
+ | [no-lazy-identifiers](docs/rules/NO_LAZY_IDENTIFIERS.md) | Disallow lazy identifiers like `xxx`, `asdf`, `qwerty` | ❌ |
157
+ | [boolean-naming-prefix](docs/rules/BOOLEAN_NAMING_PREFIX.md) | Enforce boolean variables to have prefix (is, has, should, can, etc.) | ❌ |
158
+ | [enforce-constant-case](docs/rules/ENFORCE_CONSTANT_CASE.md) | Enforce SCREAMING_SNAKE_CASE for static constant primitive values | ❌ |
159
+ | [no-misleading-constant-case](docs/rules/NO_MISLEADING_CONSTANT_CASE.md) | Disallow SCREAMING_SNAKE_CASE for non-constant or non-static values | ❌ |
157
160
 
158
161
  ### File Naming Rules
159
162
 
@@ -205,6 +208,7 @@ export default [
205
208
  | [no-nested-interface-declaration](docs/rules/NO_NESTED_INTERFACE_DECLARATION.md) | Disallow inline object types in interface/type properties | ❌ |
206
209
  | [prefer-named-param-types](docs/rules/PREFER_NAMED_PARAM_TYPES.md) | Enforce named types for function parameters with object types | ❌ |
207
210
  | [prefer-inline-literal-union](docs/rules/PREFER_INLINE_LITERAL_UNION.md) | Enforce inlining literal union types for better IDE hover info | ✅ |
211
+ | [prefer-inline-type-export](docs/rules/PREFER_INLINE_TYPE_EXPORT.md) | Require type/interface exports inline at the declaration | ✅ |
208
212
  | [prefer-interface-over-inline-types](docs/rules/PREFER_INTERFACE_OVER_INLINE_TYPES.md) | Enforce interface declarations over inline types for React props | ❌ |
209
213
  | [sort-type-alphabetically](docs/rules/SORT_TYPE_ALPHABETICALLY.md) | Enforce A-Z sorting of properties within type groups | ✅ |
210
214
  | [sort-type-required-first](docs/rules/SORT_TYPE_REQUIRED_FIRST.md) | Enforce required properties before optional in types/interfaces | ✅ |
@@ -213,10 +217,11 @@ export default [
213
217
 
214
218
  | Rule | Description | Fixable |
215
219
  | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------- |
216
- | [jsx-newline-between-elements](docs/rules/JSX_NEWLINE_BETWEEN_ELEMENTS.md) | Require empty lines between sibling multi-line JSX elements | ✅ |
220
+ | [jsx-newline-between-elements](docs/rules/JSX_NEWLINE_BETWEEN_ELEMENTS.md) | Require empty lines between sibling multi-line JSX children | ✅ |
217
221
  | [jsx-no-inline-object-prop](docs/rules/JSX_NO_INLINE_OBJECT_PROP.md) | Disallow inline object literals in JSX props | ❌ |
218
222
  | [jsx-no-newline-single-line-elements](docs/rules/JSX_NO_NEWLINE_SINGLE_LINE_ELEMENTS.md) | Disallow empty lines between single-line sibling JSX elements | ✅ |
219
223
  | [jsx-no-non-component-function](docs/rules/JSX_NO_NON_COMPONENT_FUNCTION.md) | Disallow non-component functions at top level in .tsx/.jsx files | ❌ |
224
+ | [jsx-no-ternary-null](docs/rules/JSX_NO_TERNARY_NULL.md) | Enforce logical AND over ternary with null/undefined in JSX | ✅ |
220
225
  | [jsx-no-variable-in-callback](docs/rules/JSX_NO_VARIABLE_IN_CALLBACK.md) | Disallow variable declarations inside callback functions in JSX | ❌ |
221
226
  | [jsx-require-suspense](docs/rules/JSX_REQUIRE_SUSPENSE.md) | Require lazy-loaded components to be wrapped in Suspense | ❌ |
222
227
  | [jsx-simple-props](docs/rules/JSX_SIMPLE_PROPS.md) | Enforce simple prop values (strings, variables, callbacks, ReactNode) | ❌ |
@@ -238,14 +243,14 @@ export default [
238
243
 
239
244
  | Preset | Severity | Base Rules | JSX Rules | Next.js Rules | Total Rules |
240
245
  | -------------------- | -------- | ---------- | --------- | ------------- | ----------- |
241
- | `base` | warn | 36 | 0 | 0 | 36 |
242
- | `base/recommended` | error | 36 | 0 | 0 | 36 |
243
- | `react` | warn | 36 | 14 | 0 | 50 |
244
- | `react/recommended` | error | 36 | 14 | 0 | 50 |
245
- | `nextjs` | warn | 36 | 14 | 1 | 51 |
246
- | `nextjs/recommended` | error | 36 | 14 | 1 | 51 |
246
+ | `base` | warn | 37 | 0 | 0 | 37 |
247
+ | `base/recommended` | error | 37 | 0 | 0 | 37 |
248
+ | `react` | warn | 37 | 15 | 0 | 52 |
249
+ | `react/recommended` | error | 37 | 15 | 0 | 52 |
250
+ | `nextjs` | warn | 37 | 15 | 1 | 53 |
251
+ | `nextjs/recommended` | error | 37 | 15 | 1 | 53 |
247
252
 
248
- ### Base Configuration Rules (36 rules)
253
+ ### Base Configuration Rules (37 rules)
249
254
 
250
255
  Included in `base`, `base/recommended`, and all other presets:
251
256
 
@@ -278,6 +283,7 @@ Included in `base`, `base/recommended`, and all other presets:
278
283
  - `nextfriday/prefer-guard-clause`
279
284
  - `nextfriday/prefer-import-type`
280
285
  - `nextfriday/prefer-inline-literal-union`
286
+ - `nextfriday/prefer-inline-type-export`
281
287
  - `nextfriday/prefer-named-param-types`
282
288
  - `nextfriday/prefer-react-import-types`
283
289
  - `nextfriday/require-explicit-return-type`
@@ -286,7 +292,7 @@ Included in `base`, `base/recommended`, and all other presets:
286
292
  - `nextfriday/sort-type-alphabetically`
287
293
  - `nextfriday/sort-type-required-first`
288
294
 
289
- ### JSX Rules (14 rules)
295
+ ### JSX Rules (15 rules)
290
296
 
291
297
  Additionally included in `react`, `react/recommended`, `nextjs`, `nextjs/recommended`:
292
298
 
@@ -296,6 +302,7 @@ Additionally included in `react`, `react/recommended`, `nextjs`, `nextjs/recomme
296
302
  - `nextfriday/jsx-no-inline-object-prop`
297
303
  - `nextfriday/jsx-no-newline-single-line-elements`
298
304
  - `nextfriday/jsx-no-non-component-function`
305
+ - `nextfriday/jsx-no-ternary-null`
299
306
  - `nextfriday/jsx-no-variable-in-callback`
300
307
  - `nextfriday/jsx-pascal-case`
301
308
  - `nextfriday/jsx-require-suspense`
@@ -329,7 +336,7 @@ Additionally included in `nextjs`, `nextjs/recommended` only:
329
336
 
330
337
  ## Agent Skill
331
338
 
332
- This plugin ships with an [Agent Skill](https://github.com/anthropics/skills) that teaches AI coding assistants (Claude Code, Cursor, etc.) all 51 rules so they generate compliant code from the start.
339
+ This plugin ships with an [Agent Skill](https://github.com/anthropics/skills) that teaches AI coding assistants (Claude Code, Cursor, etc.) all 53 rules so they generate compliant code from the start.
333
340
 
334
341
  ```bash
335
342
  npx skills add next-friday/eslint-plugin-nextfriday --skill eslint-plugin-nextfriday
@@ -4,7 +4,7 @@ Enforce SCREAMING_SNAKE_CASE for constant primitive values.
4
4
 
5
5
  ## Rule Details
6
6
 
7
- This rule ensures that `const` declarations with primitive values (strings, numbers, booleans, template literals) use SCREAMING_SNAKE_CASE naming convention. Objects, arrays, and functions are not checked. Only `const` declarations are checked; `let` and `var` are ignored.
7
+ This rule ensures that `const` declarations with static primitive values (strings, numbers, booleans, static template literals) use SCREAMING_SNAKE_CASE naming convention. Template literals with expressions (e.g., `` `${variable}` ``) are considered dynamic and are not checked. Objects, arrays, and functions are not checked. Only `const` declarations are checked; `let` and `var` are ignored.
8
8
 
9
9
  ## Examples
10
10
 
@@ -13,7 +13,6 @@ This rule ensures that `const` declarations with primitive values (strings, numb
13
13
  ```ts
14
14
  const defaultCover = "/images/default.jpg";
15
15
  const pageLimit = 10;
16
- const isEnabled = true;
17
16
  const apiBaseUrl = "https://api.example.com";
18
17
  const template = `hello world`;
19
18
  ```
@@ -23,10 +22,18 @@ const template = `hello world`;
23
22
  ```ts
24
23
  const DEFAULT_COVER = "/images/default.jpg";
25
24
  const PAGE_LIMIT = 10;
26
- const IS_ENABLED = true;
27
25
  const API_BASE_URL = "https://api.example.com";
28
26
  const TEMPLATE = `hello world`;
29
27
 
28
+ // Booleans with standard prefixes (is, has, should, can, etc.) are exempt
29
+ const isEnabled = true;
30
+ const hasAccess = false;
31
+ const shouldRender = true;
32
+
33
+ // Template literals with expressions are dynamic, camelCase is fine
34
+ const pendingHref = `/branch/${branch.branchNumber}`;
35
+ const greeting = `Hello, ${user.name}!`;
36
+
30
37
  // Objects, arrays, and functions can use camelCase
31
38
  const config = { key: "value" };
32
39
  const items = [1, 2, 3];
@@ -1,10 +1,10 @@
1
1
  # jsx-newline-between-elements
2
2
 
3
- Require empty lines between sibling JSX elements when at least one spans multiple lines.
3
+ Require empty lines between sibling JSX elements and expression containers when at least one spans multiple lines.
4
4
 
5
5
  ## Rule Details
6
6
 
7
- This rule enforces empty lines between sibling JSX elements when either element spans multiple lines. This improves visual separation and readability of complex component structures. Single-line elements do not require empty lines between them.
7
+ This rule enforces empty lines between sibling JSX elements, fragments, and expression containers (e.g., `{condition && (...)}`) when either sibling spans multiple lines. This improves visual separation and readability of complex component structures. Single-line siblings do not require empty lines between them.
8
8
 
9
9
  ## Examples
10
10
 
@@ -70,6 +70,28 @@ function UserProfile() {
70
70
  }
71
71
  ```
72
72
 
73
+ Expression containers also need empty lines:
74
+
75
+ ```tsx
76
+ function StudentInfo({ studentId, name }) {
77
+ return (
78
+ <div>
79
+ {studentId && (
80
+ <div className="flex items-center gap-x-1">
81
+ <Text size="lg">{studentId}</Text>
82
+ </div>
83
+ )}
84
+
85
+ {name && (
86
+ <div className="flex items-center gap-x-1">
87
+ <Text size="lg">{name}</Text>
88
+ </div>
89
+ )}
90
+ </div>
91
+ );
92
+ }
93
+ ```
94
+
73
95
  Single-line elements do not require empty lines:
74
96
 
75
97
  ```jsx
@@ -0,0 +1,42 @@
1
+ # jsx-no-ternary-null
2
+
3
+ Enforce logical AND over ternary with null/undefined in JSX expressions.
4
+
5
+ > This rule is auto-fixable using `--fix`.
6
+
7
+ ## Rule Details
8
+
9
+ This rule flags ternary expressions inside JSX where one branch is `null` or `undefined`. These patterns are better expressed using the logical AND (`&&`) operator, which is more concise and idiomatic in React.
10
+
11
+ ### Why?
12
+
13
+ - **Readability**: `{condition && <Component />}` is cleaner than `{condition ? <Component /> : null}`
14
+ - **Consistency**: Encourages a single pattern for conditional rendering
15
+ - **Conciseness**: Removes unnecessary null/undefined branches
16
+
17
+ ## Examples
18
+
19
+ ### Incorrect
20
+
21
+ ```tsx
22
+ <div>{condition ? <span>Hello</span> : null}</div>
23
+ <div>{condition ? <Component /> : undefined}</div>
24
+ <div>{condition ? null : <span>Fallback</span>}</div>
25
+ ```
26
+
27
+ ### Correct
28
+
29
+ ```tsx
30
+ <div>{condition && <span>Hello</span>}</div>
31
+ <div>{condition && <Component />}</div>
32
+ <div>{!condition && <span>Fallback</span>}</div>
33
+ <div>{condition ? <A /> : <B />}</div>
34
+ ```
35
+
36
+ ## When Not To Use It
37
+
38
+ If your team prefers explicit ternary expressions for conditional rendering, even when one branch is null or undefined.
39
+
40
+ ## Related Rules
41
+
42
+ - [no-nested-ternary](JSX_NO_TERNARY_NULL.md)
@@ -60,10 +60,12 @@ The rule detects two types of lazy patterns:
60
60
 
61
61
  ### Repeated Characters (3+)
62
62
 
63
- Variables with 3 or more consecutive identical characters:
63
+ Variables with 3 or more consecutive identical characters (case-sensitive):
64
64
 
65
65
  - `xxx`, `aaa`, `zzz`, `qqqq`, `aaaa`
66
66
 
67
+ Compound names where repeated characters span word boundaries are not flagged (e.g., `ProfileProgressSkeleton` is allowed because `ss` and `S` are different cases).
68
+
67
69
  ### Keyboard Sequences (4+)
68
70
 
69
71
  Variables containing 4 or more consecutive keyboard row characters:
@@ -0,0 +1,60 @@
1
+ # no-misleading-constant-case
2
+
3
+ Disallow SCREAMING_SNAKE_CASE for non-constant or non-static values.
4
+
5
+ ## Rule Details
6
+
7
+ SCREAMING_SNAKE_CASE conventionally signals a compile-time constant with a static, immutable value. Using it for mutable bindings (`let`/`var`) or dynamic values (function calls, objects, arrays, computed expressions) is misleading because the name implies immutability that the value does not have.
8
+
9
+ ### Why?
10
+
11
+ When reading `const API_URL = getUrl()`, the SCREAMING_SNAKE_CASE name suggests a hardcoded value, but it is actually computed at runtime. This mismatch makes code harder to reason about. Reserving SCREAMING_SNAKE_CASE for true static primitives keeps the convention meaningful.
12
+
13
+ ## Examples
14
+
15
+ ### Incorrect
16
+
17
+ ```ts
18
+ // Mutable bindings should not use SCREAMING_SNAKE_CASE
19
+ let API_URL = "https://api.example.com";
20
+ var MAX_COUNT = 10;
21
+
22
+ // Dynamic or computed values should use camelCase
23
+ const API_URL = getUrl();
24
+ const USER_ID = await fetchId();
25
+ const PATHNAME = `/news/${slug}`;
26
+ const CONFIG = { key: "value" };
27
+ const ITEMS = [1, 2, 3];
28
+ const RESULT = a + b;
29
+ const ACTIVE_USERS = users.filter((u) => u.active);
30
+ ```
31
+
32
+ ### Correct
33
+
34
+ ```ts
35
+ // Static primitive constants use SCREAMING_SNAKE_CASE
36
+ const API_URL = "https://api.example.com";
37
+ const MAX_COUNT = 10;
38
+ const IS_PRODUCTION = true;
39
+ const TEMPLATE = `hello world`;
40
+
41
+ // Dynamic or computed values use camelCase
42
+ const apiUrl = getUrl();
43
+ const userId = await fetchId();
44
+ const pathname = `/news/${slug}`;
45
+ const config = { key: "value" };
46
+ const items = [1, 2, 3];
47
+ const result = a + b;
48
+
49
+ // Mutable bindings use camelCase
50
+ let count = 10;
51
+ var name = "foo";
52
+ ```
53
+
54
+ ## When Not To Use It
55
+
56
+ If your project does not follow the convention that SCREAMING_SNAKE_CASE is reserved for static primitive constants.
57
+
58
+ ## Related Rules
59
+
60
+ - [enforce-constant-case](ENFORCE_CONSTANT_CASE.md) - The complementary rule that enforces SCREAMING_SNAKE_CASE for static primitive constants
@@ -0,0 +1,64 @@
1
+ # prefer-inline-type-export
2
+
3
+ Require type and interface declarations to be exported inline rather than via a separate export statement.
4
+
5
+ > This rule is auto-fixable using `--fix`.
6
+
7
+ ## Rule Details
8
+
9
+ This rule enforces that `interface` and `type` declarations are exported directly at the declaration site using the `export` keyword, rather than being exported separately via `export type { ... }` or `export { ... }` at the bottom of the file.
10
+
11
+ ### Why?
12
+
13
+ Inline exports make it immediately clear at the declaration site that a type is part of the module's public API. Separate export statements create a disconnect between the declaration and its visibility, making it harder to understand the module's surface area at a glance.
14
+
15
+ ## Examples
16
+
17
+ ### Incorrect
18
+
19
+ ```ts
20
+ interface ButtonProps {
21
+ label: string;
22
+ disabled: boolean;
23
+ }
24
+
25
+ type Theme = "light" | "dark";
26
+
27
+ export type { ButtonProps, Theme };
28
+ ```
29
+
30
+ ```ts
31
+ interface Config {
32
+ port: number;
33
+ }
34
+
35
+ export { Config };
36
+ ```
37
+
38
+ ### Correct
39
+
40
+ ```ts
41
+ export interface ButtonProps {
42
+ label: string;
43
+ disabled: boolean;
44
+ }
45
+
46
+ export type Theme = "light" | "dark";
47
+ ```
48
+
49
+ ```ts
50
+ export interface Config {
51
+ port: number;
52
+ }
53
+ ```
54
+
55
+ ## Exceptions
56
+
57
+ This rule only applies to `interface` and `type` declarations defined in the same file. It does not flag:
58
+
59
+ - Re-exports from other modules (`export type { Foo } from './types'`)
60
+ - Separate exports of non-type declarations (variables, functions, classes)
61
+
62
+ ## When Not To Use It
63
+
64
+ If your project prefers grouping all exports at the bottom of the file for consistency.