eslint-plugin-code-style 1.1.10 → 1.2.6

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.
Files changed (5) hide show
  1. package/AGENTS.md +27 -29
  2. package/README.md +581 -38
  3. package/index.d.ts +16 -0
  4. package/index.js +8491 -3271
  5. package/package.json +2 -2
package/README.md CHANGED
@@ -7,9 +7,10 @@
7
7
  [![License](https://img.shields.io/npm/l/eslint-plugin-code-style?style=for-the-badge&color=blue)](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/blob/main/LICENSE)
8
8
 
9
9
  [![ESLint](https://img.shields.io/badge/ESLint-%3E%3D9.0.0-4B32C3?style=for-the-badge&logo=eslint&logoColor=white)](https://eslint.org/)
10
- [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18.0.0-339933?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org/)
10
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D20.0.0-339933?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org/)
11
11
  [![React](https://img.shields.io/badge/React-JSX%20Support-61DAFB?style=for-the-badge&logo=react&logoColor=black)](https://react.dev/)
12
- [![TypeScript](https://img.shields.io/badge/TypeScript-Supported-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
12
+ [![TypeScript](https://img.shields.io/badge/TypeScript-%3E%3D5.0.0-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
13
+ [![Tailwind CSS](https://img.shields.io/badge/Tailwind%20CSS-%3E%3D4.0.0-06B6D4?style=for-the-badge&logo=tailwindcss&logoColor=white)](https://tailwindcss.com/)
13
14
 
14
15
  [![GitHub stars](https://img.shields.io/github/stars/Mohamed-Elhawary/eslint-plugin-code-style?style=for-the-badge&logo=github&color=yellow)](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/stargazers)
15
16
  [![GitHub issues](https://img.shields.io/github/issues/Mohamed-Elhawary/eslint-plugin-code-style?style=for-the-badge&logo=github)](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/issues)
@@ -18,7 +19,7 @@
18
19
 
19
20
  **A powerful ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects.**
20
21
 
21
- *51 auto-fixable rules to keep your codebase clean and consistent*
22
+ *60 auto-fixable rules to keep your codebase clean and consistent*
22
23
 
23
24
  </div>
24
25
 
@@ -26,7 +27,7 @@
26
27
 
27
28
  ## 🎯 Why This Plugin?
28
29
 
29
- This plugin provides **51 custom auto-fixable rules** for code formatting. Built for **ESLint v9 flat configs**.
30
+ This plugin provides **60 custom auto-fixable rules** for code formatting. Built for **ESLint v9 flat configs**.
30
31
 
31
32
  > **Note:** ESLint [deprecated 79 formatting rules](https://eslint.org/blog/2023/10/deprecating-formatting-rules/) in v8.53.0. Our recommended configs use `@stylistic/eslint-plugin` as the replacement for these deprecated rules.
32
33
 
@@ -35,7 +36,7 @@ This plugin provides **51 custom auto-fixable rules** for code formatting. Built
35
36
  - **Works alongside existing tools** — Complements ESLint's built-in rules and packages like eslint-plugin-react, eslint-plugin-import, etc
36
37
  - **Self-sufficient rules** — Each rule handles complete formatting independently
37
38
  - **Consistency at scale** — Reduces code-style differences between team members by enforcing uniform formatting across your projects
38
- - **Fully automated** — All 47 rules support auto-fix, eliminating manual style reviews
39
+ - **Fully automated** — All 60 rules support auto-fix, eliminating manual style reviews
39
40
 
40
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.
41
42
 
@@ -59,17 +60,19 @@ We provide **ready-to-use ESLint flat configuration files** that combine `eslint
59
60
 
60
61
  ### 💡 Why Use These Configs?
61
62
 
62
- - **Complete Coverage** — Combines ESLint built-in rules, third-party plugins, and all 51 code-style rules
63
+ - **Complete Coverage** — Combines ESLint built-in rules, third-party plugins, and all 60 code-style rules
63
64
  - **Ready-to-Use** — Copy the config file and start linting immediately
64
65
  - **Battle-Tested** — These configurations have been refined through real-world usage
65
66
  - **Fully Documented** — Each config includes detailed instructions and explanations
66
67
 
67
68
  ### 📋 Available Configurations
68
69
 
69
- | Configuration | Description | Link |
70
- |---------------|-------------|------|
70
+ | Configuration | Description | Status |
71
+ |---------------|-------------|--------|
71
72
  | **React** | React.js projects (JavaScript, JSX) | [View Config](./recommended-configs/react/) |
72
73
  | **React + TS + Tailwind** | React + TypeScript + Tailwind CSS | [View Config](./recommended-configs/react-ts-tw/) |
74
+ | **React + TypeScript** | React + TypeScript projects | Coming Soon |
75
+ | **React + Tailwind** | React + Tailwind CSS projects | Coming Soon |
73
76
 
74
77
  ### ⚡ Quick Start with Recommended Config
75
78
 
@@ -94,7 +97,7 @@ We provide **ready-to-use ESLint flat configuration files** that combine `eslint
94
97
  <td width="50%">
95
98
 
96
99
  ### 🔧 Auto-Fixable Rules
97
- All **51 rules** support automatic fixing with `eslint --fix`. No manual code changes needed.
100
+ All **60 rules** support automatic fixing with `eslint --fix`. No manual code changes needed.
98
101
 
99
102
  </td>
100
103
  <td width="50%">
@@ -140,7 +143,7 @@ yarn add eslint-plugin-code-style -D
140
143
  | Dependency | Version |
141
144
  |------------|---------|
142
145
  | **ESLint** | `>= 9.0.0` |
143
- | **Node.js** | `>= 18.0.0` |
146
+ | **Node.js** | `>= 20.0.0` |
144
147
 
145
148
  <br />
146
149
 
@@ -178,28 +181,35 @@ eslint src/ --fix
178
181
 
179
182
  ```javascript
180
183
  rules: {
184
+ "code-style/absolute-imports-only": "error",
181
185
  "code-style/array-items-per-line": "error",
182
186
  "code-style/array-objects-on-new-lines": "error",
183
187
  "code-style/arrow-function-block-body": "error",
184
188
  "code-style/arrow-function-simple-jsx": "error",
185
189
  "code-style/arrow-function-simplify": "error",
186
- "code-style/curried-arrow-same-line": "error",
187
190
  "code-style/assignment-value-same-line": "error",
188
191
  "code-style/block-statement-newlines": "error",
192
+ "code-style/classname-dynamic-at-end": "error",
193
+ "code-style/classname-multiline": "error",
194
+ "code-style/classname-no-extra-spaces": "error",
189
195
  "code-style/comment-format": "error",
196
+ "code-style/component-props-destructure": "error",
197
+ "code-style/component-props-inline-type": "error",
198
+ "code-style/curried-arrow-same-line": "error",
199
+ "code-style/enum-format": "error",
200
+ "code-style/export-format": "error",
201
+ "code-style/function-arguments-format": "error",
190
202
  "code-style/function-call-spacing": "error",
191
203
  "code-style/function-naming-convention": "error",
204
+ "code-style/function-object-destructure": "error",
192
205
  "code-style/function-params-per-line": "error",
193
206
  "code-style/hook-callback-format": "error",
194
207
  "code-style/hook-deps-per-line": "error",
195
208
  "code-style/if-statement-format": "error",
196
- "code-style/multiline-if-conditions": "error",
197
- "code-style/absolute-imports-only": "error",
198
- "code-style/export-format": "error",
199
209
  "code-style/import-format": "error",
200
210
  "code-style/import-source-spacing": "error",
201
211
  "code-style/index-export-style": "error",
202
- "code-style/module-index-exports": "error",
212
+ "code-style/interface-format": "error",
203
213
  "code-style/jsx-children-on-new-line": "error",
204
214
  "code-style/jsx-closing-bracket-spacing": "error",
205
215
  "code-style/jsx-element-child-new-line": "error",
@@ -210,7 +220,8 @@ rules: {
210
220
  "code-style/jsx-string-value-trim": "error",
211
221
  "code-style/jsx-ternary-format": "error",
212
222
  "code-style/member-expression-bracket-spacing": "error",
213
- "code-style/function-arguments-format": "error",
223
+ "code-style/module-index-exports": "error",
224
+ "code-style/multiline-if-conditions": "error",
214
225
  "code-style/nested-call-closing-brackets": "error",
215
226
  "code-style/no-empty-lines-in-function-calls": "error",
216
227
  "code-style/no-empty-lines-in-function-params": "error",
@@ -221,14 +232,14 @@ rules: {
221
232
  "code-style/object-property-value-brace": "error",
222
233
  "code-style/object-property-value-format": "error",
223
234
  "code-style/opening-brackets-same-line": "error",
235
+ "code-style/react-code-order": "error",
224
236
  "code-style/simple-call-single-line": "error",
225
237
  "code-style/single-argument-on-one-line": "error",
226
238
  "code-style/string-property-spacing": "error",
227
- "code-style/variable-naming-convention": "error",
228
- "code-style/enum-format": "error",
229
- "code-style/interface-format": "error",
239
+ "code-style/type-annotation-spacing": "error",
230
240
  "code-style/type-format": "error",
231
241
  "code-style/typescript-definition-location": "error",
242
+ "code-style/variable-naming-convention": "error",
232
243
  }
233
244
  ```
234
245
 
@@ -238,7 +249,7 @@ rules: {
238
249
 
239
250
  ## 📖 Rules Summary
240
251
 
241
- > All **51 rules** are auto-fixable. See detailed examples for each rule in the [Rules Reference](#-rules-reference) section below.
252
+ > All **60 rules** are auto-fixable. See detailed examples for each rule in the [Rules Reference](#-rules-reference) section below.
242
253
  >
243
254
  > Rules marked with ⚙️ support customization options (e.g., extending default folder lists).
244
255
 
@@ -257,10 +268,13 @@ rules: {
257
268
  | `nested-call-closing-brackets` | Chain closing brackets on same line: `}));` not scattered across lines |
258
269
  | `no-empty-lines-in-function-calls` | No empty lines between arguments or after `(`/before `)` |
259
270
  | `opening-brackets-same-line` | Opening `{`, `[`, or `(` on same line as function call, not on new line |
260
- | `simple-call-single-line` | Collapse simple `fn(() => call())` patterns to single line |
271
+ | `simple-call-single-line` | Collapse simple arrow function calls to single line (including callbacks with params and optional chaining) |
261
272
  | `single-argument-on-one-line` | Single simple argument stays on one line: `fn(x)` not expanded |
262
273
  | **Comment Rules** | |
263
274
  | `comment-format` | Space after `//`, space inside `/* */`, convert single-line blocks to `//`, no blank lines between file-top comments |
275
+ | **Component Rules** | |
276
+ | `component-props-destructure` | Component props must be destructured `({ prop })` not received as `(props)` |
277
+ | `component-props-inline-type` | Inline type annotation `} : {` with matching props, proper spacing, commas, no interface reference |
264
278
  | **Control Flow Rules** | |
265
279
  | `block-statement-newlines` | Newline after `{` and before `}` in if/for/while/function blocks |
266
280
  | `if-statement-format` | `{` on same line as `if`/`else if`, `else` on same line as `}`, proper spacing |
@@ -269,6 +283,7 @@ rules: {
269
283
  | **Function Rules** | |
270
284
  | `function-call-spacing` | No space between function name and `(`: `fn()` not `fn ()` |
271
285
  | `function-naming-convention` | Functions use camelCase, start with verb (get/set/handle/is/has), handlers end with Handler |
286
+ | `function-object-destructure` | Non-component functions: use typed params (not destructured), destructure in body; report dot notation access |
272
287
  | `function-params-per-line` | When multiline, each param on own line with consistent indentation |
273
288
  | `no-empty-lines-in-function-params` | No empty lines between parameters or after `(`/before `)` |
274
289
  | **Hook Rules** | |
@@ -282,6 +297,9 @@ rules: {
282
297
  | `index-export-style` | Index files: no blank lines, enforce shorthand or import-export style; Regular files: require blank lines between exports (default: shorthand) ⚙️ |
283
298
  | `module-index-exports` | Index files must export all folder contents (files and subfolders) ⚙️ |
284
299
  | **JSX Rules** | |
300
+ | `classname-dynamic-at-end` | Dynamic expressions (`${className}`) must be at the end of class strings (JSX and variables) |
301
+ | `classname-multiline` | Long className strings broken into multiple lines, one class per line; uses `"..."` in JSX (no expressions) or `` `...` `` (with expressions/variables) ⚙️ |
302
+ | `classname-no-extra-spaces` | No extra/leading/trailing spaces in class strings (JSX, variables, and objects) |
285
303
  | `jsx-children-on-new-line` | Multiple JSX children: each on own line with proper indentation |
286
304
  | `jsx-closing-bracket-spacing` | No space before `>` or `/>` in JSX tags |
287
305
  | `jsx-element-child-new-line` | Nested JSX elements on new lines; text/expression children can stay inline |
@@ -304,10 +322,13 @@ rules: {
304
322
  | **TypeScript Rules** | |
305
323
  | `enum-format` | Enforce enum naming (PascalCase + Enum suffix), UPPER_CASE members, no empty lines, and trailing commas |
306
324
  | `interface-format` | Enforce interface naming (PascalCase + Interface suffix), camelCase properties, no empty lines, and trailing commas |
325
+ | `type-annotation-spacing` | Enforce consistent spacing in type annotations: no space before colon/generic/array brackets, one space after colon |
307
326
  | `type-format` | Enforce type naming (PascalCase + Type suffix), camelCase properties, no empty lines, and trailing commas |
308
327
  | `typescript-definition-location` | Enforce TypeScript definitions (interfaces, types, enums) to be in designated folders ⚙️ |
328
+ | **React Rules** | |
329
+ | `react-code-order` | Enforce consistent ordering in components and hooks: props destructure → refs → state → redux → router → context → custom hooks → derived → memo → callback → handlers → effects → return |
309
330
  | **Variable Rules** | |
310
- | `variable-naming-convention` | camelCase for variables, UPPER_CASE for constants, PascalCase for components, `use` prefix for hooks |
331
+ | `variable-naming-convention` | camelCase for all variables and constants, PascalCase for components, `use` prefix for hooks |
311
332
 
312
333
  <br />
313
334
 
@@ -707,17 +728,18 @@ items.map(
707
728
 
708
729
  ### `simple-call-single-line`
709
730
 
710
- **What it does:** Collapses simple function calls with an arrow function containing a single call expression onto one line.
731
+ **What it does:** Collapses simple function calls with an arrow function onto one line when the result fits within 120 characters. Handles:
732
+ - Zero-param callbacks: `lazy(() => import("./Page"))`
733
+ - Callbacks with params and simple expression bodies: `.find((f) => f.code === x)`
734
+ - Optional chaining: `.find(...)?.symbol`
711
735
 
712
- **Why use it:** Common patterns like `lazy(() => import(...))` don't need multiline formatting. Single line is cleaner.
736
+ **Why use it:** Common patterns like `lazy(() => import(...))` and `.find((item) => item.id === id)` don't need multiline formatting. Single line is cleaner.
713
737
 
714
738
  ```javascript
715
739
  // ✅ Good — simple patterns on one line
716
740
  const Page = lazy(() => import("./Page"));
717
- const Modal = lazy(() => import("./Modal"));
718
741
  setTimeout(() => callback(), 100);
719
- requestAnimationFrame(() => render());
720
- items.filter(() => isValid(item));
742
+ const symbol = items.find(({ code }) => code === currency)?.symbol;
721
743
 
722
744
  // ✅ Good — complex callbacks stay multiline
723
745
  const Page = lazy(() => {
@@ -730,10 +752,11 @@ const Page = lazy(
730
752
  () => import("./Page"),
731
753
  );
732
754
 
733
- setTimeout(
734
- () => callback(),
735
- 100,
736
- );
755
+ const symbol = items.find(({ code }) =>
756
+ code === currency)?.symbol;
757
+
758
+ const symbol = items.find(({ code }) => code === currency)?.
759
+ symbol;
737
760
  ```
738
761
 
739
762
  ---
@@ -1091,6 +1114,57 @@ function get_user_data() {}
1091
1114
 
1092
1115
  ---
1093
1116
 
1117
+ ### `function-object-destructure`
1118
+
1119
+ **What it does:** Enforces that non-component functions should not destructure parameters in the function signature. Instead, use a typed parameter and destructure at the top of the function body. Also reports when parameters are accessed via dot notation (suggesting destructuring).
1120
+
1121
+ **Why use it:** Keeping function signatures clean and short improves readability. Destructuring in the body makes it clear what properties are being used. For React components, this rule does NOT apply — components should destructure props in the signature.
1122
+
1123
+ ```typescript
1124
+ // ✅ Good — typed param with destructuring in body
1125
+ const createUserHandler = async (data: CreateUserParamsInterface) => {
1126
+ const { age, email, isActive, name } = data;
1127
+
1128
+ // Use age, email, isActive, name...
1129
+ };
1130
+
1131
+ const updateUserHandler = (params: UpdateParamsInterface) => {
1132
+ const { id, updates } = params;
1133
+
1134
+ // Use id, updates...
1135
+ };
1136
+
1137
+ // ✅ Good — React components CAN destructure in signature
1138
+ const UserCard = ({
1139
+ name,
1140
+ email,
1141
+ } : {
1142
+ name: string,
1143
+ email: string,
1144
+ }) => (
1145
+ <div>{name} - {email}</div>
1146
+ );
1147
+
1148
+ // ❌ Bad — non-component function destructures in signature
1149
+ const createUserHandler = async ({
1150
+ age,
1151
+ email,
1152
+ isActive,
1153
+ name,
1154
+ }: CreateUserParamsInterface) => {
1155
+ // ...
1156
+ };
1157
+
1158
+ // ❌ Bad — accessing param via dot notation (should destructure)
1159
+ const processDataHandler = (data: DataInterface) => {
1160
+ console.log(data.id); // Bad: use destructuring
1161
+ console.log(data.name); // Bad: use destructuring
1162
+ return data.value * 2; // Bad: use destructuring
1163
+ };
1164
+ ```
1165
+
1166
+ ---
1167
+
1094
1168
  ### `function-params-per-line`
1095
1169
 
1096
1170
  **What it does:** When function parameters span multiple lines, ensures each parameter is on its own line with consistent indentation.
@@ -1299,7 +1373,7 @@ import { fetchUsers } from "@/apis/users/fetchUsers";
1299
1373
  ```
1300
1374
 
1301
1375
  **Default Allowed Folders:**
1302
- `actions`, `apis`, `assets`, `atoms`, `components`, `constants`, `contexts`, `data`, `enums`, `hooks`, `interfaces`, `layouts`, `middlewares`, `providers`, `reducers`, `redux`, `requests`, `routes`, `schemas`, `services`, `store`, `styles`, `theme`, `thunks`, `types`, `utils`, `views`
1376
+ `actions`, `apis`, `assets`, `atoms`, `components`, `config`, `configs`, `constants`, `contexts`, `data`, `enums`, `helpers`, `hooks`, `interfaces`, `layouts`, `lib`, `middlewares`, `providers`, `reducers`, `redux`, `requests`, `routes`, `schemas`, `services`, `store`, `styles`, `theme`, `thunks`, `types`, `ui`, `utils`, `utilities`, `views`
1303
1377
 
1304
1378
  **Customization Options:**
1305
1379
 
@@ -1543,7 +1617,7 @@ import { Button } from "@/components/Button/Button"; // Avoid this!
1543
1617
  ```
1544
1618
 
1545
1619
  **Default Module Folders:**
1546
- `actions`, `apis`, `assets`, `atoms`, `components`, `constants`, `contexts`, `data`, `enums`, `hooks`, `interfaces`, `layouts`, `middlewares`, `providers`, `reducers`, `redux`, `requests`, `routes`, `schemas`, `services`, `store`, `styles`, `theme`, `thunks`, `types`, `utils`, `views`
1620
+ `actions`, `apis`, `assets`, `atoms`, `components`, `config`, `configs`, `constants`, `contexts`, `data`, `enums`, `helpers`, `hooks`, `interfaces`, `layouts`, `lib`, `middlewares`, `providers`, `reducers`, `redux`, `requests`, `routes`, `schemas`, `services`, `store`, `styles`, `theme`, `thunks`, `types`, `ui`, `utils`, `utilities`, `views`
1547
1621
 
1548
1622
  **Default Ignore Patterns:**
1549
1623
  `index.js`, `index.jsx`, `index.ts`, `index.tsx`, `.DS_Store`, `__tests__`, `__mocks__`, `*.test.js`, `*.test.jsx`, `*.test.ts`, `*.test.tsx`, `*.spec.js`, `*.spec.jsx`, `*.spec.ts`, `*.spec.tsx`
@@ -1572,6 +1646,132 @@ import { Button } from "@/components/Button/Button"; // Avoid this!
1572
1646
 
1573
1647
  ## ⚛️ JSX Rules
1574
1648
 
1649
+ ### `classname-dynamic-at-end`
1650
+
1651
+ **What it does:** Enforces that dynamic expressions in className template literals are placed at the end, after all static class names. Also applies to variables with names containing "class" or "Class".
1652
+
1653
+ **Why use it:** When using Tailwind CSS with `tailwindcss/classnames-order`, static classes are automatically sorted. However, dynamic expressions like `${className}` or `${styles.button}` can break the visual order if placed in the middle. This rule ensures dynamic parts come last for consistent, readable class strings.
1654
+
1655
+ ```javascript
1656
+ // ✅ Good — dynamic expressions at the end (JSX)
1657
+ <div className={`flex items-center gap-4 ${className}`} />
1658
+
1659
+ // ✅ Good — dynamic expressions at the end (variable)
1660
+ const buttonClasses = `flex items-center ${className} ${styles.button}`;
1661
+
1662
+ // ❌ Bad — dynamic expression at the beginning
1663
+ <div className={`${className} flex items-center gap-4`} />
1664
+
1665
+ // ❌ Bad — dynamic expression in the middle (variable)
1666
+ const buttonClasses = `flex ${className} items-center gap-4`;
1667
+ ```
1668
+
1669
+ ---
1670
+
1671
+ ### `classname-multiline`
1672
+
1673
+ **What it does:** Enforces that long className strings are broken into multiple lines, with each class on its own line. Triggers when either the class count exceeds `maxClassCount` (default: 3) or the string length exceeds `maxLength` (default: 80). Also enforces:
1674
+ - JSX `className` with no dynamic expressions uses `"..."` string literal format
1675
+ - JSX `className` with dynamic expressions uses `` {`...`} `` template literal format
1676
+ - Variables/object properties use `` `...` `` template literal for multiline (JS requires it)
1677
+ - No empty lines between classes or before/after the quotes
1678
+ - Under-threshold multiline classes are collapsed back to a single line
1679
+
1680
+ Applies to JSX `className` attributes, variables with class-related names, and object properties within class-related objects.
1681
+
1682
+ **Why use it:** Long single-line class strings are hard to read and review. Breaking them into one class per line makes diffs cleaner and classes easier to scan. Using string literals when no expressions are needed keeps the code simpler.
1683
+
1684
+ ```javascript
1685
+ // ✅ Good — JSX with no expressions uses "..." format
1686
+ <div
1687
+ className="
1688
+ flex
1689
+ items-center
1690
+ justify-center
1691
+ rounded-lg
1692
+ p-4
1693
+ "
1694
+ />
1695
+
1696
+ // ✅ Good — JSX with expressions uses {`...`} format
1697
+ <div
1698
+ className={`
1699
+ flex
1700
+ items-center
1701
+ justify-center
1702
+ ${className}
1703
+ `}
1704
+ />
1705
+
1706
+ // ✅ Good — variable multiline uses template literal
1707
+ const buttonClasses = `
1708
+ flex
1709
+ items-center
1710
+ justify-center
1711
+ ${className}
1712
+ `;
1713
+
1714
+ // ✅ Good — object property multiline uses template literal
1715
+ const variantClasses = {
1716
+ danger: `
1717
+ flex
1718
+ items-center
1719
+ justify-center
1720
+ bg-red-500
1721
+ `,
1722
+ };
1723
+
1724
+ // ✅ Good — short class strings stay on one line
1725
+ <div className="flex items-center" />
1726
+
1727
+ // ❌ Bad — too many classes on one line
1728
+ <div className="flex items-center justify-center rounded-lg p-4 font-bold" />
1729
+
1730
+ // ❌ Bad — using template literal in JSX when no expressions
1731
+ <div className={`
1732
+ flex
1733
+ items-center
1734
+ justify-center
1735
+ rounded-lg
1736
+ `} />
1737
+
1738
+ // ❌ Bad — empty lines between classes
1739
+ <div className="
1740
+ flex
1741
+
1742
+ items-center
1743
+ justify-center
1744
+ " />
1745
+ ```
1746
+
1747
+ ---
1748
+
1749
+ ### `classname-no-extra-spaces`
1750
+
1751
+ **What it does:** Removes multiple consecutive spaces and leading/trailing spaces inside className values. Applies to:
1752
+ - JSX `className` attributes (string literals and template literals)
1753
+ - Variables with names containing "class" (e.g., `buttonClasses`, `variantClasses`)
1754
+ - Object properties within class-related objects
1755
+
1756
+ **Why use it:** Extra spaces between class names are usually unintentional and can cause issues. This rule normalizes spacing and removes unnecessary whitespace.
1757
+
1758
+ ```javascript
1759
+ // ✅ Good — single space between classes
1760
+ <div className="flex items-center gap-4 rounded-lg" />
1761
+ const buttonClasses = `flex items-center ${className}`;
1762
+ const variantClasses = { primary: "bg-blue-500 text-white" };
1763
+
1764
+ // ❌ Bad — multiple consecutive spaces
1765
+ <div className="flex items-center gap-4" />
1766
+ const buttonClasses = `flex items-center`;
1767
+ const variantClasses = { primary: "bg-blue-500 text-white" };
1768
+
1769
+ // ❌ Bad — leading/trailing spaces in template literal
1770
+ const buttonClasses = ` flex items-center ${className} `;
1771
+ ```
1772
+
1773
+ ---
1774
+
1575
1775
  ### `jsx-children-on-new-line`
1576
1776
 
1577
1777
  **What it does:** When a JSX element has multiple children, ensures each child is on its own line with proper indentation.
@@ -2195,6 +2395,139 @@ const item = data[ index ];
2195
2395
 
2196
2396
  <br />
2197
2397
 
2398
+ ## 🧩 Component Rules
2399
+
2400
+ ### `component-props-destructure`
2401
+
2402
+ **What it does:** Enforces that React component props must be destructured in the function parameter, not received as a single `props` object.
2403
+
2404
+ **Why use it:** Destructured props make it immediately clear what props a component uses. It improves readability and helps catch unused props.
2405
+
2406
+ ```typescript
2407
+ // ✅ Good — props are destructured
2408
+ export const Button = ({ label, onClick, variant = "primary" }) => (
2409
+ <button onClick={onClick} type="button">
2410
+ {label}
2411
+ </button>
2412
+ );
2413
+
2414
+ export const Card = ({
2415
+ children,
2416
+ className = "",
2417
+ title,
2418
+ } : {
2419
+ children: ReactNode,
2420
+ className?: string,
2421
+ title: string,
2422
+ }) => (
2423
+ <div className={className}>
2424
+ <h2>{title}</h2>
2425
+ {children}
2426
+ </div>
2427
+ );
2428
+
2429
+ // ❌ Bad — props received as single object
2430
+ export const Button = (props) => (
2431
+ <button onClick={props.onClick} type="button">
2432
+ {props.label}
2433
+ </button>
2434
+ );
2435
+
2436
+ export const Card = (props: CardPropsInterface) => (
2437
+ <div className={props.className}>
2438
+ <h2>{props.title}</h2>
2439
+ {props.children}
2440
+ </div>
2441
+ );
2442
+ ```
2443
+
2444
+ ---
2445
+
2446
+ ### `component-props-inline-type`
2447
+
2448
+ **What it does:** Enforces that React component props must use inline type annotation instead of referencing an interface or type alias. Also enforces:
2449
+ - Exactly one space before and after colon: `} : {`
2450
+ - Props in type must match exactly with destructured props (no missing or extra)
2451
+ - Each prop type on its own line when there are multiple props
2452
+ - First prop type must be on new line after `{` when multiple props
2453
+ - No empty lines after opening brace or before closing brace
2454
+ - No space before `?` in optional properties (`prop?: type` not `prop ?: type`)
2455
+ - Trailing commas (not semicolons) for each prop type
2456
+ - No empty lines between prop types
2457
+
2458
+ **Why use it:** Inline types keep the prop definitions colocated with the component, making it easier to understand and modify the component without jumping to separate interface definitions. Enforcing prop matching ensures type safety and prevents unused type properties.
2459
+
2460
+ ```typescript
2461
+ // ✅ Good — inline type annotation with matching props
2462
+ export const Button = ({ label } : { label: string }) => (
2463
+ <button type="button">{label}</button>
2464
+ );
2465
+
2466
+ export const Card = ({
2467
+ className = "",
2468
+ description,
2469
+ title,
2470
+ } : {
2471
+ className?: string,
2472
+ description?: string,
2473
+ title: string,
2474
+ }) => (
2475
+ <div className={className}>
2476
+ <h1>{title}</h1>
2477
+ {description && <p>{description}</p>}
2478
+ </div>
2479
+ );
2480
+
2481
+ // ❌ Bad — interface reference instead of inline type
2482
+ interface ButtonPropsInterface {
2483
+ label: string,
2484
+ }
2485
+ export const Button = ({ label }: ButtonPropsInterface) => (
2486
+ <button type="button">{label}</button>
2487
+ );
2488
+
2489
+ // ❌ Bad — missing space before and after colon
2490
+ export const Button = ({ label }:{ label: string }) => (
2491
+ <button type="button">{label}</button>
2492
+ );
2493
+
2494
+ // ❌ Bad — props don't match (extra 'flag' in type, missing in destructured)
2495
+ export const Card = ({
2496
+ title,
2497
+ } : {
2498
+ flag: boolean,
2499
+ title: string,
2500
+ }) => (
2501
+ <div>{title}</div>
2502
+ );
2503
+
2504
+ // ❌ Bad — semicolons instead of commas
2505
+ export const Card = ({ title } : { title: string; }) => (
2506
+ <div>{title}</div>
2507
+ );
2508
+
2509
+ // ❌ Bad — first prop on same line as opening brace
2510
+ export const Card = ({
2511
+ title,
2512
+ } : { title: string,
2513
+ className?: string,
2514
+ }) => (
2515
+ <div>{title}</div>
2516
+ );
2517
+
2518
+ // ❌ Bad — space before ? in optional property
2519
+ export const Card = ({ title } : { title ?: string }) => (
2520
+ <div>{title}</div>
2521
+ );
2522
+
2523
+ // ❌ Bad — props on same line when multiple
2524
+ export const Card = ({ a, b } : { a: string, b: string }) => (
2525
+ <div>{a}{b}</div>
2526
+ );
2527
+ ```
2528
+
2529
+ <br />
2530
+
2198
2531
  ## 🔷 TypeScript Rules
2199
2532
 
2200
2533
  ### `enum-format`
@@ -2321,6 +2654,45 @@ export type ConfigType = {
2321
2654
 
2322
2655
  ---
2323
2656
 
2657
+ ### `type-annotation-spacing`
2658
+
2659
+ **What it does:** Enforces consistent spacing in TypeScript type annotations:
2660
+ - No space before the colon in type annotations: `name: string` not `name : string`
2661
+ - One space after the colon: `name: string` not `name:string`
2662
+ - No space before generic type parameters: `Array<T>` not `Array <T>`
2663
+ - No space before array brackets: `string[]` not `string []`
2664
+
2665
+ **Why use it:** Consistent type annotation spacing follows TypeScript conventions and improves code readability.
2666
+
2667
+ ```typescript
2668
+ // ✅ Good — proper spacing
2669
+ const name: string = "John";
2670
+ const items: string[] = [];
2671
+ const data: Array<number> = [];
2672
+ const handler = (value: string): boolean => true;
2673
+
2674
+ function getData<T>(id: string): Promise<T> {
2675
+ return fetch(id);
2676
+ }
2677
+
2678
+ // ❌ Bad — space before colon
2679
+ const name : string = "John";
2680
+ const handler = (value : string) : boolean => true;
2681
+
2682
+ // ❌ Bad — no space after colon
2683
+ const name:string = "John";
2684
+ const handler = (value:string):boolean => true;
2685
+
2686
+ // ❌ Bad — space before generic
2687
+ const data: Array <number> = [];
2688
+ function getData <T>(id: string): Promise <T> {}
2689
+
2690
+ // ❌ Bad — space before array brackets
2691
+ const items: string [] = [];
2692
+ ```
2693
+
2694
+ ---
2695
+
2324
2696
  ### `typescript-definition-location`
2325
2697
 
2326
2698
  **What it does:** Enforces that TypeScript definitions are placed in their designated folders:
@@ -2368,13 +2740,184 @@ export enum StatusEnum { // Should be in enums folder, not types
2368
2740
 
2369
2741
  <br />
2370
2742
 
2743
+ ## ⚛️ React Rules
2744
+
2745
+ ### `react-code-order`
2746
+
2747
+ **What it does:** Enforces a consistent ordering of code blocks within React components and custom hooks. The order follows a logical dependency chain where declarations appear before their usage.
2748
+
2749
+ **Order (top to bottom):**
2750
+ 1. Props/params destructure (in function signature: `({ prop1, prop2 })`)
2751
+ 2. Props/params destructure in body (`const { x } = propValue` where propValue is a prop)
2752
+ 3. `useRef` declarations
2753
+ 4. `useState` declarations
2754
+ 5. `useReducer` declarations
2755
+ 6. `useSelector` / `useDispatch` (Redux hooks)
2756
+ 7. Router hooks (`useNavigate`, `useLocation`, `useParams`, `useSearchParams`)
2757
+ 8. Context hooks (`useContext`, `useToast`, etc.)
2758
+ 9. Custom hooks (`use*` pattern)
2759
+ 10. Derived state / variables (computed from hooks above, e.g., `const isSearching = term.length > 0`)
2760
+ 11. `useMemo` declarations
2761
+ 12. `useCallback` declarations
2762
+ 13. Handler functions (`const handleX = () => {}`)
2763
+ 14. `useEffect` / `useLayoutEffect`
2764
+ 15. Return statement
2765
+
2766
+ **Why use it:** A consistent code structure makes components and hooks predictable and easier to navigate. Placing hooks before derived values ensures dependencies are defined before use. Effects come last because they typically depend on everything else.
2767
+
2768
+ ```typescript
2769
+ // ✅ Good — Component follows the correct order
2770
+ const UserDashboard = ({ title }) => {
2771
+ // 1. useRef
2772
+ const inputRef = useRef(null);
2773
+
2774
+ // 2. useState
2775
+ const [count, setCount] = useState(0);
2776
+ const [isLoading, setIsLoading] = useState(false);
2777
+
2778
+ // 3. Redux hooks
2779
+ const dispatch = useDispatch();
2780
+ const user = useSelector((state) => state.user);
2781
+
2782
+ // 4. Router hooks
2783
+ const navigate = useNavigate();
2784
+ const { id } = useParams();
2785
+
2786
+ // 5. Custom hooks
2787
+ const { data, loading } = useFetchData(id);
2788
+
2789
+ // 6. Derived state
2790
+ const isReady = !loading && data !== null;
2791
+ const displayName = user?.name ?? "Guest";
2792
+
2793
+ // 7. useMemo
2794
+ const filteredItems = useMemo(
2795
+ () => data?.filter((item) => item.active),
2796
+ [data],
2797
+ );
2798
+
2799
+ // 8. useCallback
2800
+ const handleSubmit = useCallback(
2801
+ () => {
2802
+ dispatch(submitAction());
2803
+ },
2804
+ [dispatch],
2805
+ );
2806
+
2807
+ // 9. Handler functions
2808
+ const resetHandler = () => {
2809
+ setCount(0);
2810
+ setIsLoading(false);
2811
+ };
2812
+
2813
+ // 10. useEffect
2814
+ useEffect(
2815
+ () => {
2816
+ inputRef.current?.focus();
2817
+ },
2818
+ [],
2819
+ );
2820
+
2821
+ // 11. Return
2822
+ return (
2823
+ <div>
2824
+ <h1>{title}</h1>
2825
+ <span>{displayName}</span>
2826
+ </div>
2827
+ );
2828
+ };
2829
+
2830
+ // ✅ Good — Custom hook follows the correct order
2831
+ const useCreateAccount = () => {
2832
+ // 1. useState
2833
+ const [loading, setLoading] = useState(false);
2834
+ const [created, setCreated] = useState(false);
2835
+
2836
+ // 2. Redux hooks
2837
+ const dispatch = useDispatch();
2838
+
2839
+ // 3. Context hooks
2840
+ const { toast } = useToast();
2841
+
2842
+ // 4. Handler functions
2843
+ const createAccountHandler = async (data: AccountData) => {
2844
+ setLoading(true);
2845
+ try {
2846
+ await api.createAccount(data);
2847
+ setCreated(true);
2848
+ } catch (error) {
2849
+ toast({ description: "Failed to create account" });
2850
+ } finally {
2851
+ setLoading(false);
2852
+ }
2853
+ };
2854
+
2855
+ // 5. useEffect
2856
+ useEffect(
2857
+ () => {
2858
+ if (created) {
2859
+ setTimeout(() => setCreated(false), 50);
2860
+ }
2861
+ },
2862
+ [created],
2863
+ );
2864
+
2865
+ // 6. Return
2866
+ return { createAccountHandler, created, loading };
2867
+ };
2868
+
2869
+ // ❌ Bad — useEffect before useState
2870
+ const BadComponent = ({ title }) => {
2871
+ useEffect(() => {
2872
+ console.log("mounted");
2873
+ }, []);
2874
+
2875
+ const [count, setCount] = useState(0);
2876
+
2877
+ return <div>{title}</div>;
2878
+ };
2879
+
2880
+ // ❌ Bad — context hook before useState in custom hook
2881
+ const useBadHook = () => {
2882
+ const { toast } = useToast(); // Should come after useState
2883
+ const [loading, setLoading] = useState(false);
2884
+ return { loading };
2885
+ };
2886
+
2887
+ // ❌ Bad — handler before hooks
2888
+ const AnotherBadComponent = ({ title }) => {
2889
+ const handleClick = () => {
2890
+ console.log("clicked");
2891
+ };
2892
+
2893
+ const dispatch = useDispatch();
2894
+ const [count, setCount] = useState(0);
2895
+
2896
+ return <div onClick={handleClick}>{title}</div>;
2897
+ };
2898
+
2899
+ // ❌ Bad — derived state after handler
2900
+ const YetAnotherBad = ({ title }) => {
2901
+ const [items, setItems] = useState([]);
2902
+
2903
+ const handleAdd = () => {
2904
+ setItems([...items, "new"]);
2905
+ };
2906
+
2907
+ const itemCount = items.length; // Should come before handleAdd
2908
+
2909
+ return <div>{itemCount}</div>;
2910
+ };
2911
+ ```
2912
+
2913
+ <br />
2914
+
2371
2915
  ## 📝 Variable Rules
2372
2916
 
2373
2917
  ### `variable-naming-convention`
2374
2918
 
2375
2919
  **What it does:** Enforces naming conventions for variables:
2376
- - **camelCase** for regular variables and functions
2377
- - **UPPER_CASE** for constants (primitive values)
2920
+ - **camelCase** for all variables and constants
2378
2921
  - **PascalCase** for React components and classes
2379
2922
  - **camelCase with `use` prefix** for hooks
2380
2923
 
@@ -2384,14 +2927,14 @@ export enum StatusEnum { // Should be in enums folder, not types
2384
2927
  // ✅ Good — correct conventions
2385
2928
  const userName = "John"; // camelCase for variables
2386
2929
  const itemCount = 42; // camelCase for variables
2387
- const MAX_RETRIES = 3; // UPPER_CASE for constants
2388
- const API_BASE_URL = "/api"; // UPPER_CASE for constants
2930
+ const maxRetries = 3; // camelCase for constants
2931
+ const apiBaseUrl = "/api"; // camelCase for constants
2389
2932
  const UserProfile = () => <div />; // PascalCase for components
2390
2933
  const useAuth = () => {}; // camelCase with use prefix for hooks
2391
2934
 
2392
2935
  // ❌ Bad — wrong conventions
2393
2936
  const user_name = "John"; // snake_case
2394
- const maxretries = 3; // should be UPPER_CASE
2937
+ const MAX_RETRIES = 3; // should be camelCase
2395
2938
  const userProfile = () => <div />; // should be PascalCase
2396
2939
  const UseAuth = () => {}; // hooks should be camelCase
2397
2940
  ```