eslint-plugin-code-style 1.13.0 → 1.14.1
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 +46 -0
- package/README.md +50 -5
- package/index.d.ts +1 -0
- package/index.js +547 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.14.1] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Enhanced
|
|
13
|
+
|
|
14
|
+
- **`function-naming-convention`** - Detect functions destructured from hooks without proper naming
|
|
15
|
+
- Flags: `const { logout } = useAuth()` (should be `logoutHandler`)
|
|
16
|
+
- Auto-fixes to: `const { logout: logoutHandler } = useAuth()`
|
|
17
|
+
- Renames all usages of the local variable
|
|
18
|
+
- Only flags clear action verbs (login, logout, toggle, increment, etc.)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [1.14.0] - 2026-02-05
|
|
23
|
+
|
|
24
|
+
**New Rule: useState Naming Convention**
|
|
25
|
+
|
|
26
|
+
**Version Range:** v1.13.0 → v1.14.0
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
**New Rules (1)**
|
|
31
|
+
- `use-state-naming-convention` - Enforce boolean useState variables to start with valid prefixes 🔧
|
|
32
|
+
- Boolean state must start with: `is`, `has`, `with`, `without` (configurable)
|
|
33
|
+
- Auto-fixes both state variable and setter function names, plus all usages
|
|
34
|
+
- Detects boolean literals (`useState(false)`) and type annotations (`useState<boolean>()`)
|
|
35
|
+
- Options: `booleanPrefixes`, `extendBooleanPrefixes`, `allowPastVerbBoolean`, `allowContinuousVerbBoolean`
|
|
36
|
+
|
|
37
|
+
### Enhanced
|
|
38
|
+
|
|
39
|
+
- **`folder-component-suffix`** - Add auto-fix to rename component and all references in the file
|
|
40
|
+
- **`function-naming-convention`** - Detect useCallback-wrapped functions in custom hooks
|
|
41
|
+
- **`prop-naming-convention`** - Auto-fix now renames both type annotation AND destructured parameter with all usages
|
|
42
|
+
|
|
43
|
+
### Stats
|
|
44
|
+
|
|
45
|
+
- Total Rules: 76 (was 75)
|
|
46
|
+
- Auto-fixable: 67 rules 🔧
|
|
47
|
+
- Configurable: 17 rules ⚙️
|
|
48
|
+
- Report-only: 9 rules
|
|
49
|
+
|
|
50
|
+
**Full Changelog:** [v1.13.0...v1.14.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.13.0...v1.14.0)
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
10
54
|
## [1.13.0] - 2026-02-05
|
|
11
55
|
|
|
12
56
|
**New Rule: Prop Naming Convention & Auto-Fix Enhancements**
|
|
@@ -1622,6 +1666,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1622
1666
|
|
|
1623
1667
|
---
|
|
1624
1668
|
|
|
1669
|
+
[1.14.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.14.0...v1.14.1
|
|
1670
|
+
[1.14.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.13.0...v1.14.0
|
|
1625
1671
|
[1.13.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.12.1...v1.13.0
|
|
1626
1672
|
[1.12.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.12.0...v1.12.1
|
|
1627
1673
|
[1.12.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.11.9...v1.12.0
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
**A powerful ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects.**
|
|
21
21
|
|
|
22
|
-
*
|
|
22
|
+
*76 rules (67 auto-fixable, 17 configurable) to keep your codebase clean and consistent*
|
|
23
23
|
|
|
24
24
|
</div>
|
|
25
25
|
|
|
@@ -36,7 +36,7 @@ This plugin provides **75 custom rules** (66 auto-fixable, 17 configurable) for
|
|
|
36
36
|
- **Works alongside existing tools** — Complements ESLint's built-in rules and packages like eslint-plugin-react, eslint-plugin-import, etc
|
|
37
37
|
- **Self-sufficient rules** — Each rule handles complete formatting independently
|
|
38
38
|
- **Consistency at scale** — Reduces code-style differences between team members by enforcing uniform formatting across your projects
|
|
39
|
-
- **Highly automated** —
|
|
39
|
+
- **Highly automated** — 67 of 76 rules support auto-fix with `eslint --fix`
|
|
40
40
|
|
|
41
41
|
When combined with ESLint's native rules and other popular plugins, this package helps create a complete code style solution that keeps your codebase clean and consistent.
|
|
42
42
|
|
|
@@ -97,7 +97,7 @@ We provide **ready-to-use ESLint flat configuration files** that combine `eslint
|
|
|
97
97
|
<td width="50%">
|
|
98
98
|
|
|
99
99
|
### 🔧 Auto-Fixable Rules
|
|
100
|
-
**
|
|
100
|
+
**67 rules** support automatic fixing with `eslint --fix`. **17 rules** have configurable options. 9 rules are report-only (require manual changes).
|
|
101
101
|
|
|
102
102
|
</td>
|
|
103
103
|
<td width="50%">
|
|
@@ -214,6 +214,7 @@ rules: {
|
|
|
214
214
|
"code-style/function-params-per-line": "error",
|
|
215
215
|
"code-style/hook-callback-format": "error",
|
|
216
216
|
"code-style/hook-deps-per-line": "error",
|
|
217
|
+
"code-style/use-state-naming-convention": "error",
|
|
217
218
|
"code-style/if-else-spacing": "error",
|
|
218
219
|
"code-style/if-statement-format": "error",
|
|
219
220
|
"code-style/import-format": "error",
|
|
@@ -265,7 +266,7 @@ rules: {
|
|
|
265
266
|
|
|
266
267
|
## 📖 Rules Categories
|
|
267
268
|
|
|
268
|
-
> **
|
|
269
|
+
> **76 rules total** — 67 with auto-fix 🔧, 17 configurable ⚙️, 9 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
|
|
269
270
|
>
|
|
270
271
|
> **Legend:** 🔧 Auto-fixable with `eslint --fix` • ⚙️ Customizable options
|
|
271
272
|
|
|
@@ -316,6 +317,7 @@ rules: {
|
|
|
316
317
|
| **Hook Rules** | |
|
|
317
318
|
| `hook-callback-format` | React hooks: callback on new line, deps array on separate line, proper indentation 🔧 |
|
|
318
319
|
| `hook-deps-per-line` | Collapse deps ≤ threshold to one line; expand larger arrays with each dep on own line (default: >2) 🔧 ⚙️ |
|
|
320
|
+
| `use-state-naming-convention` | Boolean useState variables must start with is/has/with/without prefix 🔧 ⚙️ |
|
|
319
321
|
| **Import/Export Rules** | |
|
|
320
322
|
| `absolute-imports-only` | Use alias imports from index files only (not deep paths), no relative imports (default: `@/`) ⚙️ |
|
|
321
323
|
| `export-format` | `export {` on same line; collapse ≤ threshold to one line; expand larger with each specifier on own line (default: ≤3) 🔧 ⚙️ |
|
|
@@ -1761,6 +1763,49 @@ useEffect(() => {}, [
|
|
|
1761
1763
|
|
|
1762
1764
|
<br />
|
|
1763
1765
|
|
|
1766
|
+
### `use-state-naming-convention`
|
|
1767
|
+
|
|
1768
|
+
**What it does:** Enforces boolean useState variables to start with valid prefixes (is, has, with, without).
|
|
1769
|
+
|
|
1770
|
+
**Why use it:** Consistent boolean state naming makes code more predictable and self-documenting. When you see `isLoading`, you immediately know it's a boolean state.
|
|
1771
|
+
|
|
1772
|
+
```typescript
|
|
1773
|
+
// ✅ Good — boolean state with proper prefix
|
|
1774
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1775
|
+
const [hasError, setHasError] = useState<boolean>(false);
|
|
1776
|
+
const [isAuthenticated, setIsAuthenticated] = useState(true);
|
|
1777
|
+
const [withBorder, setWithBorder] = useState(false);
|
|
1778
|
+
|
|
1779
|
+
// ❌ Bad — boolean state without prefix
|
|
1780
|
+
const [loading, setLoading] = useState(false);
|
|
1781
|
+
const [authenticated, setAuthenticated] = useState<boolean>(true);
|
|
1782
|
+
const [error, setError] = useState<boolean>(false);
|
|
1783
|
+
```
|
|
1784
|
+
|
|
1785
|
+
**Customization Options:**
|
|
1786
|
+
|
|
1787
|
+
| Option | Type | Default | Description |
|
|
1788
|
+
|--------|------|---------|-------------|
|
|
1789
|
+
| `booleanPrefixes` | `string[]` | `["is", "has", "with", "without"]` | Replace default prefixes entirely |
|
|
1790
|
+
| `extendBooleanPrefixes` | `string[]` | `[]` | Add additional prefixes to defaults |
|
|
1791
|
+
| `allowPastVerbBoolean` | `boolean` | `false` | Allow past verb names without prefix (disabled, selected) |
|
|
1792
|
+
| `allowContinuousVerbBoolean` | `boolean` | `false` | Allow continuous verb names without prefix (loading, saving) |
|
|
1793
|
+
|
|
1794
|
+
```javascript
|
|
1795
|
+
// Example: Allow "loading" and "disabled" without prefix
|
|
1796
|
+
"code-style/use-state-naming-convention": ["error", {
|
|
1797
|
+
allowPastVerbBoolean: true,
|
|
1798
|
+
allowContinuousVerbBoolean: true
|
|
1799
|
+
}]
|
|
1800
|
+
|
|
1801
|
+
// Example: Add "should" prefix
|
|
1802
|
+
"code-style/use-state-naming-convention": ["error", {
|
|
1803
|
+
extendBooleanPrefixes: ["should"]
|
|
1804
|
+
}]
|
|
1805
|
+
```
|
|
1806
|
+
|
|
1807
|
+
<br />
|
|
1808
|
+
|
|
1764
1809
|
## 📥 Import/Export Rules
|
|
1765
1810
|
|
|
1766
1811
|
### `absolute-imports-only`
|
|
@@ -3849,7 +3894,7 @@ const UseAuth = () => {}; // hooks should be camelCase
|
|
|
3849
3894
|
|
|
3850
3895
|
## 🔧 Auto-fixing
|
|
3851
3896
|
|
|
3852
|
-
|
|
3897
|
+
67 of 76 rules support auto-fixing. Run ESLint with the `--fix` flag:
|
|
3853
3898
|
|
|
3854
3899
|
```bash
|
|
3855
3900
|
# Fix all files in src directory
|
package/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export type RuleNames =
|
|
|
33
33
|
| "code-style/function-params-per-line"
|
|
34
34
|
| "code-style/hook-callback-format"
|
|
35
35
|
| "code-style/hook-deps-per-line"
|
|
36
|
+
| "code-style/use-state-naming-convention"
|
|
36
37
|
| "code-style/if-else-spacing"
|
|
37
38
|
| "code-style/if-statement-format"
|
|
38
39
|
| "code-style/import-format"
|
package/index.js
CHANGED
|
@@ -2115,6 +2115,7 @@ const functionNamingConvention = {
|
|
|
2115
2115
|
"build", "make", "generate", "compute", "calculate", "process", "execute", "run",
|
|
2116
2116
|
"evaluate", "analyze", "measure", "benchmark", "profile", "optimize",
|
|
2117
2117
|
"count", "sum", "avg", "min", "max", "clamp", "round", "floor", "ceil", "abs",
|
|
2118
|
+
"increment", "decrement", "multiply", "divide", "mod", "negate",
|
|
2118
2119
|
// Invocation
|
|
2119
2120
|
"apply", "call", "invoke", "trigger", "fire", "dispatch", "emit", "raise", "signal",
|
|
2120
2121
|
// Auth
|
|
@@ -2216,6 +2217,23 @@ const functionNamingConvention = {
|
|
|
2216
2217
|
if (node.parent && node.parent.type === "VariableDeclarator" && node.parent.id) {
|
|
2217
2218
|
name = node.parent.id.name;
|
|
2218
2219
|
identifierNode = node.parent.id;
|
|
2220
|
+
} else if (node.parent && node.parent.type === "CallExpression") {
|
|
2221
|
+
// Check for useCallback wrapped functions: const login = useCallback(() => {...}, [])
|
|
2222
|
+
// Note: Skip useMemo - it returns computed values, not action functions
|
|
2223
|
+
const callExpr = node.parent;
|
|
2224
|
+
const callee = callExpr.callee;
|
|
2225
|
+
|
|
2226
|
+
// Only check useCallback (not useMemo, useEffect, etc.)
|
|
2227
|
+
if (callee && callee.type === "Identifier" && callee.name === "useCallback") {
|
|
2228
|
+
// Check if the function is the first argument to useCallback
|
|
2229
|
+
if (callExpr.arguments && callExpr.arguments[0] === node) {
|
|
2230
|
+
// Check if the CallExpression is the init of a VariableDeclarator
|
|
2231
|
+
if (callExpr.parent && callExpr.parent.type === "VariableDeclarator" && callExpr.parent.id) {
|
|
2232
|
+
name = callExpr.parent.id.name;
|
|
2233
|
+
identifierNode = callExpr.parent.id;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2219
2237
|
}
|
|
2220
2238
|
}
|
|
2221
2239
|
|
|
@@ -2321,15 +2339,14 @@ const functionNamingConvention = {
|
|
|
2321
2339
|
if (!hasVerbPrefix && !hasHandlerSuffix) {
|
|
2322
2340
|
context.report({
|
|
2323
2341
|
message: `Function "${name}" should start with a verb (get, set, fetch, etc.) AND end with "Handler" (e.g., getDataHandler, clickHandler)`,
|
|
2324
|
-
node:
|
|
2342
|
+
node: identifierNode,
|
|
2325
2343
|
});
|
|
2326
2344
|
} else if (!hasVerbPrefix) {
|
|
2327
2345
|
context.report({
|
|
2328
2346
|
message: `Function "${name}" should start with a verb (get, set, fetch, handle, click, submit, etc.)`,
|
|
2329
|
-
node:
|
|
2347
|
+
node: identifierNode,
|
|
2330
2348
|
});
|
|
2331
2349
|
} else if (!hasHandlerSuffix) {
|
|
2332
|
-
const identifierNode = node.id || node.parent.id;
|
|
2333
2350
|
const newName = `${name}Handler`;
|
|
2334
2351
|
|
|
2335
2352
|
context.report({
|
|
@@ -2483,11 +2500,139 @@ const functionNamingConvention = {
|
|
|
2483
2500
|
}
|
|
2484
2501
|
};
|
|
2485
2502
|
|
|
2503
|
+
// Check functions destructured from custom hooks: const { logout } = useAuth()
|
|
2504
|
+
// Valid: onLogout, logoutAction, logoutHandler
|
|
2505
|
+
// Invalid: logout (should be logoutHandler)
|
|
2506
|
+
const checkDestructuredHookFunctionHandler = (node) => {
|
|
2507
|
+
// Check if this is destructuring from a hook call: const { ... } = useXxx()
|
|
2508
|
+
if (!node.init || node.init.type !== "CallExpression") return;
|
|
2509
|
+
if (!node.id || node.id.type !== "ObjectPattern") return;
|
|
2510
|
+
|
|
2511
|
+
const callee = node.init.callee;
|
|
2512
|
+
|
|
2513
|
+
// Check if it's a hook call (starts with "use")
|
|
2514
|
+
if (!callee || callee.type !== "Identifier" || !hookRegex.test(callee.name)) return;
|
|
2515
|
+
|
|
2516
|
+
// Action verbs that are clearly function names (not noun-like)
|
|
2517
|
+
// These are verbs that when used alone as a name, clearly indicate an action/function
|
|
2518
|
+
const actionVerbs = [
|
|
2519
|
+
// Auth actions
|
|
2520
|
+
"login", "logout", "authenticate", "authorize", "signup", "signout", "signin",
|
|
2521
|
+
// CRUD actions (when standalone, not as prefix)
|
|
2522
|
+
"submit", "reset", "clear", "refresh", "reload", "retry",
|
|
2523
|
+
// Toggle/state actions
|
|
2524
|
+
"toggle", "enable", "disable", "activate", "deactivate",
|
|
2525
|
+
"show", "hide", "open", "close", "expand", "collapse",
|
|
2526
|
+
// Navigation
|
|
2527
|
+
"navigate", "redirect", "goBack", "goForward",
|
|
2528
|
+
// Increment/decrement
|
|
2529
|
+
"increment", "decrement", "increase", "decrease",
|
|
2530
|
+
// Other common hook function names
|
|
2531
|
+
"connect", "disconnect", "subscribe", "unsubscribe",
|
|
2532
|
+
"start", "stop", "pause", "resume", "cancel", "abort",
|
|
2533
|
+
"select", "deselect", "check", "uncheck",
|
|
2534
|
+
"add", "remove", "insert", "delete", "update",
|
|
2535
|
+
];
|
|
2536
|
+
|
|
2537
|
+
// Check each destructured property
|
|
2538
|
+
node.id.properties.forEach((prop) => {
|
|
2539
|
+
if (prop.type !== "Property" || prop.key.type !== "Identifier") return;
|
|
2540
|
+
|
|
2541
|
+
const name = prop.key.name;
|
|
2542
|
+
|
|
2543
|
+
// Get the local variable name (value node for non-shorthand, key for shorthand)
|
|
2544
|
+
const valueNode = prop.value || prop.key;
|
|
2545
|
+
const localName = valueNode.type === "Identifier" ? valueNode.name : name;
|
|
2546
|
+
|
|
2547
|
+
// Skip if local name starts with "on" (like onLogout, onClick)
|
|
2548
|
+
if (/^on[A-Z]/.test(localName)) return;
|
|
2549
|
+
|
|
2550
|
+
// Skip if local name ends with "Action" (like logoutAction)
|
|
2551
|
+
if (/Action$/.test(localName)) return;
|
|
2552
|
+
|
|
2553
|
+
// Skip if local name ends with "Handler" (like logoutHandler)
|
|
2554
|
+
if (handlerRegex.test(localName)) return;
|
|
2555
|
+
|
|
2556
|
+
// Skip boolean-like names (is, has, can, should, etc.)
|
|
2557
|
+
const booleanPrefixes = ["is", "has", "can", "should", "will", "did", "was", "were", "does"];
|
|
2558
|
+
|
|
2559
|
+
if (booleanPrefixes.some((prefix) => localName.startsWith(prefix) && localName.length > prefix.length && /[A-Z]/.test(localName[prefix.length]))) return;
|
|
2560
|
+
|
|
2561
|
+
// Only flag if the name is exactly an action verb or starts with one followed by uppercase
|
|
2562
|
+
const isActionVerb = actionVerbs.some((verb) =>
|
|
2563
|
+
localName === verb || (localName.startsWith(verb) && localName.length > verb.length && /[A-Z]/.test(localName[verb.length])));
|
|
2564
|
+
|
|
2565
|
+
if (!isActionVerb) return;
|
|
2566
|
+
|
|
2567
|
+
// This is a function name without proper prefix/suffix
|
|
2568
|
+
// Add Handler suffix: logout -> logoutHandler
|
|
2569
|
+
const suggestedName = localName + "Handler";
|
|
2570
|
+
|
|
2571
|
+
context.report({
|
|
2572
|
+
fix(fixer) {
|
|
2573
|
+
const fixes = [];
|
|
2574
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
2575
|
+
|
|
2576
|
+
if (prop.shorthand) {
|
|
2577
|
+
// Shorthand: { logout } -> { logout: logoutHandler }
|
|
2578
|
+
// Replace the entire property with key: newValue format
|
|
2579
|
+
fixes.push(fixer.replaceText(prop, `${name}: ${suggestedName}`));
|
|
2580
|
+
} else {
|
|
2581
|
+
// Non-shorthand: { logout: myVar } -> { logout: myVarHandler }
|
|
2582
|
+
// Just replace the value identifier
|
|
2583
|
+
fixes.push(fixer.replaceText(valueNode, suggestedName));
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
// Find and rename all usages of the local variable
|
|
2587
|
+
const scope = sourceCode.getScope
|
|
2588
|
+
? sourceCode.getScope(node)
|
|
2589
|
+
: context.getScope();
|
|
2590
|
+
|
|
2591
|
+
// Helper to find variable in scope chain
|
|
2592
|
+
const findVariableHandler = (s, varName) => {
|
|
2593
|
+
const v = s.variables.find((variable) => variable.name === varName);
|
|
2594
|
+
|
|
2595
|
+
if (v) return v;
|
|
2596
|
+
if (s.upper) return findVariableHandler(s.upper, varName);
|
|
2597
|
+
|
|
2598
|
+
return null;
|
|
2599
|
+
};
|
|
2600
|
+
|
|
2601
|
+
const variable = findVariableHandler(scope, localName);
|
|
2602
|
+
|
|
2603
|
+
if (variable) {
|
|
2604
|
+
const fixedRanges = new Set();
|
|
2605
|
+
|
|
2606
|
+
// Add the definition range to avoid double-fixing
|
|
2607
|
+
if (valueNode.range) {
|
|
2608
|
+
fixedRanges.add(`${valueNode.range[0]}-${valueNode.range[1]}`);
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// Fix all references
|
|
2612
|
+
variable.references.forEach((ref) => {
|
|
2613
|
+
const rangeKey = `${ref.identifier.range[0]}-${ref.identifier.range[1]}`;
|
|
2614
|
+
|
|
2615
|
+
if (!fixedRanges.has(rangeKey)) {
|
|
2616
|
+
fixedRanges.add(rangeKey);
|
|
2617
|
+
fixes.push(fixer.replaceText(ref.identifier, suggestedName));
|
|
2618
|
+
}
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
return fixes;
|
|
2623
|
+
},
|
|
2624
|
+
message: `Function "${localName}" destructured from hook should end with "Handler" suffix. Use "${suggestedName}" instead`,
|
|
2625
|
+
node: valueNode,
|
|
2626
|
+
});
|
|
2627
|
+
});
|
|
2628
|
+
};
|
|
2629
|
+
|
|
2486
2630
|
return {
|
|
2487
2631
|
ArrowFunctionExpression: checkFunctionHandler,
|
|
2488
2632
|
FunctionDeclaration: checkFunctionHandler,
|
|
2489
2633
|
FunctionExpression: checkFunctionHandler,
|
|
2490
2634
|
MethodDefinition: checkMethodHandler,
|
|
2635
|
+
VariableDeclarator: checkDestructuredHookFunctionHandler,
|
|
2491
2636
|
};
|
|
2492
2637
|
},
|
|
2493
2638
|
meta: {
|
|
@@ -3312,6 +3457,249 @@ const hookDepsPerLine = {
|
|
|
3312
3457
|
},
|
|
3313
3458
|
};
|
|
3314
3459
|
|
|
3460
|
+
/**
|
|
3461
|
+
* ───────────────────────────────────────────────────────────────
|
|
3462
|
+
* Rule: useState Naming Convention
|
|
3463
|
+
* ───────────────────────────────────────────────────────────────
|
|
3464
|
+
*
|
|
3465
|
+
* Description:
|
|
3466
|
+
* When useState holds a boolean value, the state variable name
|
|
3467
|
+
* should start with a valid boolean prefix (is, has, with, without).
|
|
3468
|
+
*
|
|
3469
|
+
* ✓ Good:
|
|
3470
|
+
* const [isLoading, setIsLoading] = useState(false);
|
|
3471
|
+
* const [hasError, setHasError] = useState(false);
|
|
3472
|
+
* const [isAuthenticated, setIsAuthenticated] = useState<boolean>(() => checkAuth());
|
|
3473
|
+
*
|
|
3474
|
+
* ✗ Bad:
|
|
3475
|
+
* const [loading, setLoading] = useState(false);
|
|
3476
|
+
* const [authenticated, setAuthenticated] = useState<boolean>(true);
|
|
3477
|
+
* const [error, setError] = useState<boolean>(false);
|
|
3478
|
+
*/
|
|
3479
|
+
const useStateNamingConvention = {
|
|
3480
|
+
create(context) {
|
|
3481
|
+
const options = context.options[0] || {};
|
|
3482
|
+
|
|
3483
|
+
// Boolean prefixes handling (same pattern as prop-naming-convention)
|
|
3484
|
+
const defaultBooleanPrefixes = ["is", "has", "with", "without"];
|
|
3485
|
+
const booleanPrefixes = options.booleanPrefixes || [
|
|
3486
|
+
...defaultBooleanPrefixes,
|
|
3487
|
+
...(options.extendBooleanPrefixes || []),
|
|
3488
|
+
];
|
|
3489
|
+
|
|
3490
|
+
const allowPastVerbBoolean = options.allowPastVerbBoolean || false;
|
|
3491
|
+
const allowContinuousVerbBoolean = options.allowContinuousVerbBoolean || false;
|
|
3492
|
+
|
|
3493
|
+
// Pattern to check if name starts with valid boolean prefix followed by capital letter
|
|
3494
|
+
const booleanPrefixPattern = new RegExp(`^(${booleanPrefixes.join("|")})[A-Z]`);
|
|
3495
|
+
|
|
3496
|
+
// Pattern for past verb booleans (ends with -ed: disabled, selected, checked, etc.)
|
|
3497
|
+
const pastVerbPattern = /^[a-z]+ed$/;
|
|
3498
|
+
|
|
3499
|
+
// Pattern for continuous verb booleans (ends with -ing: loading, saving, etc.)
|
|
3500
|
+
const continuousVerbPattern = /^[a-z]+ing$/;
|
|
3501
|
+
|
|
3502
|
+
// Words that suggest "has" prefix instead of "is"
|
|
3503
|
+
const hasKeywords = [
|
|
3504
|
+
"children", "content", "data", "error", "errors", "items",
|
|
3505
|
+
"permission", "permissions", "value", "values",
|
|
3506
|
+
];
|
|
3507
|
+
|
|
3508
|
+
// Convert name to appropriate boolean prefix
|
|
3509
|
+
const toBooleanNameHandler = (name) => {
|
|
3510
|
+
const lowerName = name.toLowerCase();
|
|
3511
|
+
const prefix = hasKeywords.some((k) => lowerName.includes(k)) ? "has" : "is";
|
|
3512
|
+
|
|
3513
|
+
return prefix + name[0].toUpperCase() + name.slice(1);
|
|
3514
|
+
};
|
|
3515
|
+
|
|
3516
|
+
// Convert setter name based on new state name
|
|
3517
|
+
const toSetterNameHandler = (stateName) => "set" + stateName[0].toUpperCase() + stateName.slice(1);
|
|
3518
|
+
|
|
3519
|
+
// Check if name is a valid boolean state name
|
|
3520
|
+
const isValidBooleanNameHandler = (name) => {
|
|
3521
|
+
// Starts with valid prefix
|
|
3522
|
+
if (booleanPrefixPattern.test(name)) return true;
|
|
3523
|
+
|
|
3524
|
+
// Allow past verb booleans if option is enabled (disabled, selected, checked, etc.)
|
|
3525
|
+
if (allowPastVerbBoolean && pastVerbPattern.test(name)) return true;
|
|
3526
|
+
|
|
3527
|
+
// Allow continuous verb booleans if option is enabled (loading, saving, etc.)
|
|
3528
|
+
if (allowContinuousVerbBoolean && continuousVerbPattern.test(name)) return true;
|
|
3529
|
+
|
|
3530
|
+
return false;
|
|
3531
|
+
};
|
|
3532
|
+
|
|
3533
|
+
// Check if the useState initial value indicates boolean
|
|
3534
|
+
const isBooleanValueHandler = (arg) => {
|
|
3535
|
+
if (!arg) return false;
|
|
3536
|
+
|
|
3537
|
+
// Direct boolean literal: useState(false) or useState(true)
|
|
3538
|
+
if (arg.type === "Literal" && typeof arg.value === "boolean") return true;
|
|
3539
|
+
|
|
3540
|
+
// Arrow function returning boolean: useState(() => checkAuth())
|
|
3541
|
+
// We can't reliably determine return type, so skip these unless typed
|
|
3542
|
+
return false;
|
|
3543
|
+
};
|
|
3544
|
+
|
|
3545
|
+
// Check if the useState has boolean type annotation
|
|
3546
|
+
const hasBooleanTypeAnnotationHandler = (node) => {
|
|
3547
|
+
// useState<boolean>(...) or useState<boolean | null>(...)
|
|
3548
|
+
if (node.typeParameters && node.typeParameters.params && node.typeParameters.params.length > 0) {
|
|
3549
|
+
const typeParam = node.typeParameters.params[0];
|
|
3550
|
+
|
|
3551
|
+
if (typeParam.type === "TSBooleanKeyword") return true;
|
|
3552
|
+
|
|
3553
|
+
// Check union types: boolean | null, boolean | undefined
|
|
3554
|
+
if (typeParam.type === "TSUnionType") {
|
|
3555
|
+
return typeParam.types.some((t) => t.type === "TSBooleanKeyword");
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
return false;
|
|
3560
|
+
};
|
|
3561
|
+
|
|
3562
|
+
return {
|
|
3563
|
+
CallExpression(node) {
|
|
3564
|
+
// Check if it's a useState call
|
|
3565
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "useState") return;
|
|
3566
|
+
|
|
3567
|
+
// Check if it's in a variable declaration with array destructuring
|
|
3568
|
+
if (!node.parent || node.parent.type !== "VariableDeclarator") return;
|
|
3569
|
+
if (!node.parent.id || node.parent.id.type !== "ArrayPattern") return;
|
|
3570
|
+
|
|
3571
|
+
const arrayPattern = node.parent.id;
|
|
3572
|
+
|
|
3573
|
+
// Must have at least the state variable (first element)
|
|
3574
|
+
if (!arrayPattern.elements || arrayPattern.elements.length < 1) return;
|
|
3575
|
+
|
|
3576
|
+
const stateElement = arrayPattern.elements[0];
|
|
3577
|
+
const setterElement = arrayPattern.elements[1];
|
|
3578
|
+
|
|
3579
|
+
if (!stateElement || stateElement.type !== "Identifier") return;
|
|
3580
|
+
|
|
3581
|
+
const stateName = stateElement.name;
|
|
3582
|
+
|
|
3583
|
+
// Check if this is a boolean useState
|
|
3584
|
+
const isBooleanState = (node.arguments && node.arguments.length > 0 && isBooleanValueHandler(node.arguments[0]))
|
|
3585
|
+
|| hasBooleanTypeAnnotationHandler(node);
|
|
3586
|
+
|
|
3587
|
+
if (!isBooleanState) return;
|
|
3588
|
+
|
|
3589
|
+
// Check if state name follows boolean naming convention
|
|
3590
|
+
if (isValidBooleanNameHandler(stateName)) return;
|
|
3591
|
+
|
|
3592
|
+
const suggestedStateName = toBooleanNameHandler(stateName);
|
|
3593
|
+
const suggestedSetterName = toSetterNameHandler(suggestedStateName);
|
|
3594
|
+
|
|
3595
|
+
context.report({
|
|
3596
|
+
fix(fixer) {
|
|
3597
|
+
const fixes = [];
|
|
3598
|
+
const scope = context.sourceCode
|
|
3599
|
+
? context.sourceCode.getScope(node)
|
|
3600
|
+
: context.getScope();
|
|
3601
|
+
|
|
3602
|
+
// Helper to find variable in scope
|
|
3603
|
+
const findVariableHandler = (s, name) => {
|
|
3604
|
+
const v = s.variables.find((variable) => variable.name === name);
|
|
3605
|
+
|
|
3606
|
+
if (v) return v;
|
|
3607
|
+
if (s.upper) return findVariableHandler(s.upper, name);
|
|
3608
|
+
|
|
3609
|
+
return null;
|
|
3610
|
+
};
|
|
3611
|
+
|
|
3612
|
+
// Fix state variable
|
|
3613
|
+
const stateVar = findVariableHandler(scope, stateName);
|
|
3614
|
+
const stateFixedRanges = new Set();
|
|
3615
|
+
|
|
3616
|
+
// Fix definition first
|
|
3617
|
+
const stateDefRangeKey = `${stateElement.range[0]}-${stateElement.range[1]}`;
|
|
3618
|
+
|
|
3619
|
+
stateFixedRanges.add(stateDefRangeKey);
|
|
3620
|
+
fixes.push(fixer.replaceText(stateElement, suggestedStateName));
|
|
3621
|
+
|
|
3622
|
+
// Fix all usages
|
|
3623
|
+
if (stateVar) {
|
|
3624
|
+
stateVar.references.forEach((ref) => {
|
|
3625
|
+
const rangeKey = `${ref.identifier.range[0]}-${ref.identifier.range[1]}`;
|
|
3626
|
+
|
|
3627
|
+
if (!stateFixedRanges.has(rangeKey)) {
|
|
3628
|
+
stateFixedRanges.add(rangeKey);
|
|
3629
|
+
fixes.push(fixer.replaceText(ref.identifier, suggestedStateName));
|
|
3630
|
+
}
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
// Fix setter if exists
|
|
3635
|
+
if (setterElement && setterElement.type === "Identifier") {
|
|
3636
|
+
const setterName = setterElement.name;
|
|
3637
|
+
const setterVar = findVariableHandler(scope, setterName);
|
|
3638
|
+
const setterFixedRanges = new Set();
|
|
3639
|
+
|
|
3640
|
+
// Fix definition first
|
|
3641
|
+
const setterDefRangeKey = `${setterElement.range[0]}-${setterElement.range[1]}`;
|
|
3642
|
+
|
|
3643
|
+
setterFixedRanges.add(setterDefRangeKey);
|
|
3644
|
+
fixes.push(fixer.replaceText(setterElement, suggestedSetterName));
|
|
3645
|
+
|
|
3646
|
+
// Fix all usages
|
|
3647
|
+
if (setterVar) {
|
|
3648
|
+
setterVar.references.forEach((ref) => {
|
|
3649
|
+
const rangeKey = `${ref.identifier.range[0]}-${ref.identifier.range[1]}`;
|
|
3650
|
+
|
|
3651
|
+
if (!setterFixedRanges.has(rangeKey)) {
|
|
3652
|
+
setterFixedRanges.add(rangeKey);
|
|
3653
|
+
fixes.push(fixer.replaceText(ref.identifier, suggestedSetterName));
|
|
3654
|
+
}
|
|
3655
|
+
});
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
return fixes;
|
|
3660
|
+
},
|
|
3661
|
+
message: `Boolean state "${stateName}" should start with a valid prefix (${booleanPrefixes.join(", ")}). Use "${suggestedStateName}" instead.`,
|
|
3662
|
+
node: stateElement,
|
|
3663
|
+
});
|
|
3664
|
+
},
|
|
3665
|
+
};
|
|
3666
|
+
},
|
|
3667
|
+
meta: {
|
|
3668
|
+
docs: { description: "Enforce boolean useState variables to start with is/has/with/without prefix" },
|
|
3669
|
+
fixable: "code",
|
|
3670
|
+
schema: [
|
|
3671
|
+
{
|
|
3672
|
+
additionalProperties: false,
|
|
3673
|
+
properties: {
|
|
3674
|
+
allowContinuousVerbBoolean: {
|
|
3675
|
+
default: false,
|
|
3676
|
+
description: "Allow continuous verb boolean state without prefix (e.g., loading, saving)",
|
|
3677
|
+
type: "boolean",
|
|
3678
|
+
},
|
|
3679
|
+
allowPastVerbBoolean: {
|
|
3680
|
+
default: false,
|
|
3681
|
+
description: "Allow past verb boolean state without prefix (e.g., disabled, selected)",
|
|
3682
|
+
type: "boolean",
|
|
3683
|
+
},
|
|
3684
|
+
booleanPrefixes: {
|
|
3685
|
+
description: "Replace default boolean prefixes entirely",
|
|
3686
|
+
items: { type: "string" },
|
|
3687
|
+
type: "array",
|
|
3688
|
+
},
|
|
3689
|
+
extendBooleanPrefixes: {
|
|
3690
|
+
default: [],
|
|
3691
|
+
description: "Add additional prefixes to the defaults (is, has, with, without)",
|
|
3692
|
+
items: { type: "string" },
|
|
3693
|
+
type: "array",
|
|
3694
|
+
},
|
|
3695
|
+
},
|
|
3696
|
+
type: "object",
|
|
3697
|
+
},
|
|
3698
|
+
],
|
|
3699
|
+
type: "suggestion",
|
|
3700
|
+
},
|
|
3701
|
+
};
|
|
3702
|
+
|
|
3315
3703
|
/**
|
|
3316
3704
|
* ───────────────────────────────────────────────────────────────
|
|
3317
3705
|
* Rule: If Statement Format
|
|
@@ -8775,6 +9163,111 @@ const propNamingConvention = {
|
|
|
8775
9163
|
return false;
|
|
8776
9164
|
};
|
|
8777
9165
|
|
|
9166
|
+
// Find the containing function for inline type annotations
|
|
9167
|
+
const findContainingFunctionHandler = (node) => {
|
|
9168
|
+
let current = node;
|
|
9169
|
+
|
|
9170
|
+
while (current) {
|
|
9171
|
+
if (current.type === "ArrowFunctionExpression" ||
|
|
9172
|
+
current.type === "FunctionExpression" ||
|
|
9173
|
+
current.type === "FunctionDeclaration") {
|
|
9174
|
+
return current;
|
|
9175
|
+
}
|
|
9176
|
+
|
|
9177
|
+
current = current.parent;
|
|
9178
|
+
}
|
|
9179
|
+
|
|
9180
|
+
return null;
|
|
9181
|
+
};
|
|
9182
|
+
|
|
9183
|
+
// Find matching property in ObjectPattern (destructured params)
|
|
9184
|
+
const findDestructuredPropertyHandler = (funcNode, propName) => {
|
|
9185
|
+
if (!funcNode || !funcNode.params || funcNode.params.length === 0) return null;
|
|
9186
|
+
|
|
9187
|
+
const firstParam = funcNode.params[0];
|
|
9188
|
+
|
|
9189
|
+
if (firstParam.type !== "ObjectPattern") return null;
|
|
9190
|
+
|
|
9191
|
+
// Find the property in the ObjectPattern
|
|
9192
|
+
for (const prop of firstParam.properties) {
|
|
9193
|
+
if (prop.type === "Property" && prop.key && prop.key.type === "Identifier") {
|
|
9194
|
+
if (prop.key.name === propName) {
|
|
9195
|
+
return prop;
|
|
9196
|
+
}
|
|
9197
|
+
}
|
|
9198
|
+
}
|
|
9199
|
+
|
|
9200
|
+
return null;
|
|
9201
|
+
};
|
|
9202
|
+
|
|
9203
|
+
// Create fix that renames type annotation, destructured prop, and all references
|
|
9204
|
+
const createRenamingFixHandler = (fixer, member, propName, suggestedName) => {
|
|
9205
|
+
const fixes = [fixer.replaceText(member.key, suggestedName)];
|
|
9206
|
+
|
|
9207
|
+
// Find the containing function
|
|
9208
|
+
const funcNode = findContainingFunctionHandler(member);
|
|
9209
|
+
|
|
9210
|
+
if (!funcNode) return fixes;
|
|
9211
|
+
|
|
9212
|
+
// Find the matching destructured property
|
|
9213
|
+
const destructuredProp = findDestructuredPropertyHandler(funcNode, propName);
|
|
9214
|
+
|
|
9215
|
+
if (!destructuredProp) return fixes;
|
|
9216
|
+
|
|
9217
|
+
// Get the value node (the actual variable being declared)
|
|
9218
|
+
const valueNode = destructuredProp.value || destructuredProp.key;
|
|
9219
|
+
|
|
9220
|
+
if (!valueNode || valueNode.type !== "Identifier") return fixes;
|
|
9221
|
+
|
|
9222
|
+
// If key and value are the same (shorthand: { copied }), we need to expand and rename
|
|
9223
|
+
// If different (renamed: { copied: isCopied }), just rename the value
|
|
9224
|
+
const isShorthand = destructuredProp.shorthand !== false &&
|
|
9225
|
+
destructuredProp.key === destructuredProp.value;
|
|
9226
|
+
|
|
9227
|
+
if (isShorthand) {
|
|
9228
|
+
// Shorthand syntax: { copied } -> { copied: isCopied }
|
|
9229
|
+
// We need to keep the key (for the type match) and add the renamed value
|
|
9230
|
+
fixes.push(fixer.replaceText(destructuredProp, `${propName}: ${suggestedName}`));
|
|
9231
|
+
} else {
|
|
9232
|
+
// Already renamed syntax or explicit: just update the value
|
|
9233
|
+
fixes.push(fixer.replaceText(valueNode, suggestedName));
|
|
9234
|
+
}
|
|
9235
|
+
|
|
9236
|
+
// Find all references to the variable and rename them
|
|
9237
|
+
const scope = context.sourceCode
|
|
9238
|
+
? context.sourceCode.getScope(funcNode)
|
|
9239
|
+
: context.getScope();
|
|
9240
|
+
|
|
9241
|
+
// Find the variable in the scope
|
|
9242
|
+
const findVariableHandler = (s, name) => {
|
|
9243
|
+
const v = s.variables.find((variable) => variable.name === name);
|
|
9244
|
+
|
|
9245
|
+
if (v) return v;
|
|
9246
|
+
if (s.upper) return findVariableHandler(s.upper, name);
|
|
9247
|
+
|
|
9248
|
+
return null;
|
|
9249
|
+
};
|
|
9250
|
+
|
|
9251
|
+
const variable = findVariableHandler(scope, propName);
|
|
9252
|
+
|
|
9253
|
+
if (variable) {
|
|
9254
|
+
const fixedRanges = new Set();
|
|
9255
|
+
|
|
9256
|
+
variable.references.forEach((ref) => {
|
|
9257
|
+
// Skip the definition itself
|
|
9258
|
+
if (ref.identifier === valueNode) return;
|
|
9259
|
+
// Skip if already fixed
|
|
9260
|
+
const rangeKey = `${ref.identifier.range[0]}-${ref.identifier.range[1]}`;
|
|
9261
|
+
|
|
9262
|
+
if (fixedRanges.has(rangeKey)) return;
|
|
9263
|
+
fixedRanges.add(rangeKey);
|
|
9264
|
+
fixes.push(fixer.replaceText(ref.identifier, suggestedName));
|
|
9265
|
+
});
|
|
9266
|
+
}
|
|
9267
|
+
|
|
9268
|
+
return fixes;
|
|
9269
|
+
};
|
|
9270
|
+
|
|
8778
9271
|
// Check a property signature (interface/type member) - recursive for nested types
|
|
8779
9272
|
const checkPropertySignatureHandler = (member) => {
|
|
8780
9273
|
if (member.type !== "TSPropertySignature") return;
|
|
@@ -8802,7 +9295,7 @@ const propNamingConvention = {
|
|
|
8802
9295
|
const suggestedName = toBooleanNameHandler(propName);
|
|
8803
9296
|
|
|
8804
9297
|
context.report({
|
|
8805
|
-
fix: (fixer) => fixer
|
|
9298
|
+
fix: (fixer) => createRenamingFixHandler(fixer, member, propName, suggestedName),
|
|
8806
9299
|
message: `Boolean prop "${propName}" should start with a valid prefix (${booleanPrefixes.join(", ")}). Use "${suggestedName}" instead.`,
|
|
8807
9300
|
node: member.key,
|
|
8808
9301
|
});
|
|
@@ -8817,7 +9310,7 @@ const propNamingConvention = {
|
|
|
8817
9310
|
const suggestedName = toCallbackNameHandler(propName);
|
|
8818
9311
|
|
|
8819
9312
|
context.report({
|
|
8820
|
-
fix: (fixer) => fixer
|
|
9313
|
+
fix: (fixer) => createRenamingFixHandler(fixer, member, propName, suggestedName),
|
|
8821
9314
|
message: `Callback prop "${propName}" should start with "${callbackPrefix}" prefix. Use "${suggestedName}" instead.`,
|
|
8822
9315
|
node: member.key,
|
|
8823
9316
|
});
|
|
@@ -18487,8 +18980,54 @@ const folderComponentSuffix = {
|
|
|
18487
18980
|
|
|
18488
18981
|
// Check if component name ends with the required suffix
|
|
18489
18982
|
if (!name.endsWith(suffix)) {
|
|
18983
|
+
const newName = `${name}${suffix}`;
|
|
18984
|
+
|
|
18490
18985
|
context.report({
|
|
18491
|
-
|
|
18986
|
+
fix(fixer) {
|
|
18987
|
+
const scope = context.sourceCode
|
|
18988
|
+
? context.sourceCode.getScope(node)
|
|
18989
|
+
: context.getScope();
|
|
18990
|
+
|
|
18991
|
+
// Find the variable in scope
|
|
18992
|
+
const findVariableHandler = (s, varName) => {
|
|
18993
|
+
const v = s.variables.find((variable) => variable.name === varName);
|
|
18994
|
+
|
|
18995
|
+
if (v) return v;
|
|
18996
|
+
if (s.upper) return findVariableHandler(s.upper, varName);
|
|
18997
|
+
|
|
18998
|
+
return null;
|
|
18999
|
+
};
|
|
19000
|
+
|
|
19001
|
+
const variable = findVariableHandler(scope, name);
|
|
19002
|
+
|
|
19003
|
+
if (!variable) return fixer.replaceText(identifierNode, newName);
|
|
19004
|
+
|
|
19005
|
+
const fixes = [];
|
|
19006
|
+
const fixedRanges = new Set();
|
|
19007
|
+
|
|
19008
|
+
// Fix definition
|
|
19009
|
+
variable.defs.forEach((def) => {
|
|
19010
|
+
const rangeKey = `${def.name.range[0]}-${def.name.range[1]}`;
|
|
19011
|
+
|
|
19012
|
+
if (!fixedRanges.has(rangeKey)) {
|
|
19013
|
+
fixedRanges.add(rangeKey);
|
|
19014
|
+
fixes.push(fixer.replaceText(def.name, newName));
|
|
19015
|
+
}
|
|
19016
|
+
});
|
|
19017
|
+
|
|
19018
|
+
// Fix all references
|
|
19019
|
+
variable.references.forEach((ref) => {
|
|
19020
|
+
const rangeKey = `${ref.identifier.range[0]}-${ref.identifier.range[1]}`;
|
|
19021
|
+
|
|
19022
|
+
if (!fixedRanges.has(rangeKey)) {
|
|
19023
|
+
fixedRanges.add(rangeKey);
|
|
19024
|
+
fixes.push(fixer.replaceText(ref.identifier, newName));
|
|
19025
|
+
}
|
|
19026
|
+
});
|
|
19027
|
+
|
|
19028
|
+
return fixes;
|
|
19029
|
+
},
|
|
19030
|
+
message: `Component "${name}" in "${folder}" folder must end with "${suffix}" suffix (e.g., "${newName}")`,
|
|
18492
19031
|
node: identifierNode,
|
|
18493
19032
|
});
|
|
18494
19033
|
}
|
|
@@ -18502,7 +19041,7 @@ const folderComponentSuffix = {
|
|
|
18502
19041
|
},
|
|
18503
19042
|
meta: {
|
|
18504
19043
|
docs: { description: "Enforce components in 'views' folder end with 'View' and components in 'pages' folder end with 'Page'" },
|
|
18505
|
-
fixable:
|
|
19044
|
+
fixable: "code",
|
|
18506
19045
|
schema: [],
|
|
18507
19046
|
type: "suggestion",
|
|
18508
19047
|
},
|
|
@@ -21694,6 +22233,7 @@ export default {
|
|
|
21694
22233
|
// Hook rules
|
|
21695
22234
|
"hook-callback-format": hookCallbackFormat,
|
|
21696
22235
|
"hook-deps-per-line": hookDepsPerLine,
|
|
22236
|
+
"use-state-naming-convention": useStateNamingConvention,
|
|
21697
22237
|
|
|
21698
22238
|
// Import/Export rules
|
|
21699
22239
|
"absolute-imports-only": absoluteImportsOnly,
|
package/package.json
CHANGED