eslint-plugin-code-style 1.7.6 → 1.8.3

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
@@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.8.3] - 2026-02-03
11
+
12
+ ### Fixed
13
+
14
+ - **`class-naming-convention`** - Auto-fix now renames all references to the class (including instantiations like `new ClassName()` and type annotations), not just the class declaration
15
+
16
+ ---
17
+
18
+ ## [1.8.2] - 2026-02-03
19
+
20
+ ### Changed
21
+
22
+ - **`multiline-if-conditions`** - Remove configurable `maxNestingLevel` option; nesting level is now fixed at 2 to prevent overly complex conditions. Nested groups with >maxOperands are formatted multiline inline (not extracted). Extraction only occurs when nesting exceeds 2 levels.
23
+ - **`ternary-condition-multiline`** - Remove configurable `maxNestingLevel` option; nesting level is now fixed at 2 to prevent overly complex conditions. Nested groups with >maxOperands are formatted multiline inline (not extracted). Extraction only occurs when nesting exceeds 2 levels.
24
+ - **`opening-brackets-same-line`** - Skip ternary condition tests and detect intentional multiline format to prevent rule conflicts
25
+ - **`if-statement-format`** - Detect intentional multiline conditions to prevent collapsing formatted conditions
26
+
27
+ ### Documentation
28
+
29
+ - Update both rules to show multiline inline formatting examples instead of extraction
30
+ - Remove `maxNestingLevel` option from documentation (now fixed internally)
31
+
32
+ ---
33
+
34
+ ## [1.8.1] - 2026-02-03
35
+
36
+ ### Changed
37
+
38
+ - **`function-naming-convention`** - Add `handleXxx` → `xxxHandler` auto-fix (converts `handleClick` to `clickHandler` instead of `handleClickHandler`)
39
+
40
+ ---
41
+
42
+ ## [1.8.0] - 2026-02-03
43
+
44
+ ### Added
45
+
46
+ - **New Rule: `no-hardcoded-strings`** - Enforce importing strings from constants/strings modules instead of hardcoding them inline. Promotes maintainability, consistency, and easier internationalization.
47
+ - Detects hardcoded strings in JSX text content, attributes, and component logic
48
+ - Configurable `ignoreAttributes`, `extraIgnoreAttributes`, `ignorePatterns` options
49
+ - Automatically ignores technical strings (CSS units, URLs, paths, identifiers, etc.)
50
+ - Valid import paths: `@/constants`, `@/strings`, `@/@constants`, `@/@strings`, `@/data/constants`, `@/data/strings`
51
+
52
+ ### Changed
53
+
54
+ - **`absolute-imports-only`** - Add `strings`, `@constants`, `@strings` to default allowed folders
55
+ - **`module-index-exports`** - Add `strings`, `@constants`, `@strings` to default module folders
56
+
57
+ ### Stats
58
+
59
+ - Total Rules: 70 (was 69)
60
+ - Auto-fixable: 63 rules 🔧
61
+ - Report-only: 7 rules (was 6)
62
+
63
+ ---
64
+
10
65
  ## [1.7.6] - 2026-02-02
11
66
 
12
67
  ### Changed
@@ -1138,6 +1193,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1138
1193
 
1139
1194
  ---
1140
1195
 
1196
+ [1.8.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.0...v1.8.1
1197
+ [1.8.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.6...v1.8.0
1141
1198
  [1.7.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.5...v1.7.6
1142
1199
  [1.7.5]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.4...v1.7.5
1143
1200
  [1.7.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.3...v1.7.4
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
- *69 rules (63 auto-fixable) to keep your codebase clean and consistent*
22
+ *70 rules (63 auto-fixable) to keep your codebase clean and consistent*
23
23
 
24
24
  </div>
25
25
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## 🎯 Why This Plugin?
29
29
 
30
- This plugin provides **69 custom rules** (63 auto-fixable) for code formatting. Built for **ESLint v9 flat configs**.
30
+ This plugin provides **70 custom rules** (63 auto-fixable) for code formatting. Built for **ESLint v9 flat configs**.
31
31
 
32
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.
33
33
 
@@ -36,7 +36,7 @@ This plugin provides **69 custom rules** (63 auto-fixable) for code formatting.
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** — 63 of 69 rules support auto-fix with `eslint --fix`
39
+ - **Highly automated** — 63 of 70 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
 
@@ -236,6 +236,7 @@ rules: {
236
236
  "code-style/no-empty-lines-in-jsx": "error",
237
237
  "code-style/no-empty-lines-in-objects": "error",
238
238
  "code-style/no-empty-lines-in-switch-cases": "error",
239
+ "code-style/no-hardcoded-strings": "error",
239
240
  "code-style/no-inline-type-definitions": "error",
240
241
  "code-style/object-property-per-line": "error",
241
242
  "code-style/object-property-value-brace": "error",
@@ -259,7 +260,7 @@ rules: {
259
260
 
260
261
  ## 📖 Rules Categories
261
262
 
262
- > **69 rules total** — 63 with auto-fix 🔧, 6 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
263
+ > **70 rules total** — 63 with auto-fix 🔧, 7 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
263
264
  >
264
265
  > **Legend:** 🔧 Auto-fixable with `eslint --fix` • ⚙️ Customizable options
265
266
 
@@ -299,7 +300,7 @@ rules: {
299
300
  | **Function Rules** | |
300
301
  | `function-call-spacing` | No space between function name and `(`: `fn()` not `fn ()` 🔧 |
301
302
  | `function-declaration-style` | Auto-fix for `func-style`: converts function declarations to arrow expressions 🔧 |
302
- | `function-naming-convention` | Functions use camelCase, start with verb (get/set/handle/is/has), handlers end with Handler 🔧 |
303
+ | `function-naming-convention` | Functions use camelCase, start with verb, end with Handler suffix; handleXxx → xxxHandler 🔧 |
303
304
  | `function-object-destructure` | Non-component functions: use typed params (not destructured), destructure in body; report dot notation access 🔧 |
304
305
  | `function-params-per-line` | When multiline, each param on own line with consistent indentation 🔧 |
305
306
  | `no-empty-lines-in-function-params` | No empty lines between parameters or after `(`/before `)` 🔧 |
@@ -348,6 +349,8 @@ rules: {
348
349
  | `typescript-definition-location` | Enforce TypeScript definitions (interfaces, types, enums) to be in designated folders ⚙️ |
349
350
  | **React Rules** | |
350
351
  | `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 🔧 |
352
+ | **String Rules** | |
353
+ | `no-hardcoded-strings` | Enforce importing strings from constants/strings modules instead of hardcoding them ⚙️ |
351
354
  | **Variable Rules** | |
352
355
  | `variable-naming-convention` | camelCase for all variables and constants, PascalCase for components, `use` prefix for hooks 🔧 |
353
356
 
@@ -1148,13 +1151,59 @@ if (isAuthenticated &&
1148
1151
 
1149
1152
  | Option | Type | Default | Description |
1150
1153
  |--------|------|---------|-------------|
1151
- | `maxOperands` | `integer` | `3` | Maximum operands to keep on single line |
1154
+ | `maxOperands` | `integer` | `3` | Maximum operands to keep on single line. Also applies to nested groups |
1152
1155
 
1153
1156
  ```javascript
1154
1157
  // Example: Allow up to 4 operands on single line
1155
1158
  "code-style/multiline-if-conditions": ["error", { maxOperands: 4 }]
1156
1159
  ```
1157
1160
 
1161
+ **Auto-formatting:** Nested groups with >maxOperands are formatted multiline inline:
1162
+
1163
+ ```javascript
1164
+ // ❌ Before (nested group has 4 operands)
1165
+ if ((a || b || c || d) && e) {}
1166
+
1167
+ // ✅ After auto-fix — formats nested group multiline
1168
+ if ((
1169
+ a
1170
+ || b
1171
+ || c
1172
+ || d
1173
+ ) && e) {}
1174
+ ```
1175
+
1176
+ **Double nesting:** Both levels expand when both exceed maxOperands:
1177
+
1178
+ ```javascript
1179
+ // ❌ Before (both parent and nested have 4 operands)
1180
+ if ((a || (c && d && a && b) || c || d) && e) {}
1181
+
1182
+ // ✅ After auto-fix — both levels formatted multiline
1183
+ if ((
1184
+ a
1185
+ || (
1186
+ c
1187
+ && d
1188
+ && a
1189
+ && b
1190
+ )
1191
+ || c
1192
+ || d
1193
+ ) && e) {}
1194
+ ```
1195
+
1196
+ **Extraction:** Groups exceeding nesting level 2 are extracted to variables:
1197
+
1198
+ ```javascript
1199
+ // ❌ Before (level 3 nesting)
1200
+ if ((a && (b || (c && d))) || e) {}
1201
+
1202
+ // ✅ After auto-fix — extracts deepest nested group
1203
+ const isCAndD = (c && d);
1204
+ if ((a && (b || isCAndD)) || e) {}
1205
+ ```
1206
+
1158
1207
  ---
1159
1208
 
1160
1209
  ### `no-empty-lines-in-switch-cases`
@@ -1214,6 +1263,9 @@ switch (status) {
1214
1263
  **What it does:** Formats ternary expressions based on condition operand count:
1215
1264
  - ≤maxOperands (default: 3): Always collapse to single line regardless of line length
1216
1265
  - \>maxOperands: Expand to multiline with each operand on its own line
1266
+ - Simple parenthesized nested ternaries (≤maxOperands) count as 1 operand and collapse
1267
+ - Complex nested ternaries (>maxOperands in their condition) are skipped for manual formatting
1268
+ - Nesting level is fixed at 2 to prevent overly complex conditions
1217
1269
 
1218
1270
  **Why use it:** Consistent formatting based on complexity, not line length. Simple conditions stay readable on one line; complex conditions get proper multiline formatting.
1219
1271
 
@@ -1221,13 +1273,16 @@ switch (status) {
1221
1273
 
1222
1274
  | Option | Type | Default | Description |
1223
1275
  |--------|------|---------|-------------|
1224
- | `maxOperands` | `integer` | `3` | Maximum condition operands to keep ternary on single line |
1276
+ | `maxOperands` | `integer` | `3` | Maximum condition operands to keep ternary on single line. Also applies to nested groups |
1225
1277
 
1226
1278
  ```javascript
1227
1279
  // ✅ Good — ≤3 operands always on single line
1228
1280
  const x = a && b && c ? "yes" : "no";
1229
1281
  const url = lang === "ar" ? `${apiEndpoints.exam.status}/${jobId}?lang=ar` : `${apiEndpoints.exam.status}/${jobId}`;
1230
1282
 
1283
+ // ✅ Good — parenthesized nested ternary counts as 1 operand
1284
+ const inputType = showToggle ? (showPassword ? "text" : "password") : type;
1285
+
1231
1286
  // ✅ Good — >3 operands formatted multiline
1232
1287
  const style = variant === "ghost"
1233
1288
  || variant === "ghost-danger"
@@ -1236,6 +1291,19 @@ const style = variant === "ghost"
1236
1291
  ? "transparent"
1237
1292
  : "solid";
1238
1293
 
1294
+ // ✅ Good — nested group with >3 operands formatted multiline inline
1295
+ const result = (
1296
+ a
1297
+ || (
1298
+ c
1299
+ && d
1300
+ && a
1301
+ && b
1302
+ )
1303
+ || c
1304
+ || d
1305
+ ) && e ? "yes" : "no";
1306
+
1239
1307
  // ❌ Bad — ≤3 operands split across lines
1240
1308
  const x = a && b && c
1241
1309
  ? "yes"
@@ -1245,6 +1313,19 @@ const x = a && b && c
1245
1313
  const style = variant === "ghost" || variant === "ghost-danger" || variant === "muted" || variant === "primary" ? "transparent" : "solid";
1246
1314
  ```
1247
1315
 
1316
+ **Auto-extraction:** Nested groups are auto-extracted to variables only when nesting depth exceeds 2 levels:
1317
+
1318
+ ```javascript
1319
+ // ❌ Before (level 3 nesting exceeds limit)
1320
+ const result = (a && (b || (c && d))) || e ? "yes" : "no";
1321
+
1322
+ // ✅ After auto-fix — extracts deepest nested group
1323
+ const isCAndD = (c && d);
1324
+ const result = (a && (b || isCAndD)) || e ? "yes" : "no";
1325
+ ```
1326
+
1327
+ **Note:** When nested groups exceed `maxOperands` but stay within the 2-level nesting limit, they are formatted multiline inline (not extracted).
1328
+
1248
1329
  <br />
1249
1330
 
1250
1331
  ## ⚡ Function Rules
@@ -1316,33 +1397,37 @@ function isAuthenticated(): boolean {
1316
1397
 
1317
1398
  **What it does:** Enforces naming conventions for functions:
1318
1399
  - **camelCase** required
1319
- - **Verb prefix** recommended (get, set, handle, is, has, can, should, etc.)
1320
- - **Event handlers** can use `handle` prefix or `Handler` suffix
1400
+ - **Verb prefix** required (get, set, fetch, is, has, can, should, click, submit, etc.)
1401
+ - **Handler suffix** required (all functions must end with `Handler`)
1402
+ - **Auto-fixes** `handleXxx` to `xxxHandler` (avoids redundant `handleClickHandler`)
1403
+ - **Auto-fixes** PascalCase to camelCase for verb-prefixed functions
1321
1404
 
1322
- **Why use it:** Function names should describe actions. Verb prefixes make the purpose immediately clear.
1405
+ **Why use it:** Function names should describe actions. Verb prefixes make the purpose immediately clear, and consistent Handler suffix makes event handlers easy to identify.
1323
1406
 
1324
1407
  ```javascript
1325
- // ✅ Good — clear verb prefixes
1326
- function getUserData() {}
1327
- function setUserName(name) {}
1328
- function handleClick() {}
1329
- function handleSubmit() {}
1330
- function isValidEmail(email) {}
1331
- function hasPermission(user) {}
1332
- function canAccess(resource) {}
1333
- function shouldUpdate(props) {}
1334
- const fetchUsers = async () => {};
1335
- const submitHandler = () => {};
1408
+ // ✅ Good — verb prefix + Handler suffix
1409
+ function getUserDataHandler() {}
1410
+ function setUserNameHandler(name) {}
1411
+ function clickHandler() {}
1412
+ function submitHandler() {}
1413
+ function isValidEmailHandler(email) {}
1414
+ function hasPermissionHandler(user) {}
1415
+ function canAccessHandler(resource) {}
1416
+ const fetchUsersHandler = async () => {};
1417
+
1418
+ // Bad (auto-fixed) handleXxx → xxxHandler
1419
+ function handleClick() {} // → clickHandler
1420
+ function handleSubmit() {} // → submitHandler
1421
+ function handleChange() {} // → changeHandler
1336
1422
 
1337
- // ❌ Bad — no verb, unclear purpose
1338
- function userData() {}
1339
- function userName(name) {}
1340
- function click() {}
1341
- function valid(email) {}
1423
+ // ❌ Bad (auto-fixed) missing Handler suffix
1424
+ function getUserData() {} // → getUserDataHandler
1425
+ function setUserName() {} // → setUserNameHandler
1426
+ function fetchUsers() {} // → fetchUsersHandler
1342
1427
 
1343
- // ❌ Bad — wrong case
1344
- function GetUserData() {}
1345
- function get_user_data() {}
1428
+ // ❌ Bad (auto-fixed) PascalCase to camelCase
1429
+ function GetUserData() {} // → getUserDataHandler
1430
+ function FetchStatus() {} // → fetchStatusHandler
1346
1431
  ```
1347
1432
 
1348
1433
  ---
@@ -3278,6 +3363,73 @@ const YetAnotherBad = ({ title }) => {
3278
3363
 
3279
3364
  <br />
3280
3365
 
3366
+ ## 📝 String Rules
3367
+
3368
+ ### `no-hardcoded-strings`
3369
+
3370
+ **What it does:** Enforces that user-facing strings should be imported from constants/strings modules rather than hardcoded inline. This promotes maintainability, consistency, and enables easier internationalization.
3371
+
3372
+ **Why use it:** Hardcoded strings scattered throughout your codebase are hard to maintain, translate, and keep consistent. Centralizing strings in constants makes them easy to find, update, and potentially translate.
3373
+
3374
+ **Options:**
3375
+
3376
+ | Option | Type | Default | Description |
3377
+ |--------|------|---------|-------------|
3378
+ | `ignoreAttributes` | `string[]` | See below | JSX attributes to ignore (replaces defaults) |
3379
+ | `extraIgnoreAttributes` | `string[]` | `[]` | Additional JSX attributes to ignore (extends defaults) |
3380
+ | `ignorePatterns` | `string[]` | `[]` | Regex patterns for strings to ignore |
3381
+
3382
+ **Default ignored attributes:** `className`, `id`, `type`, `name`, `href`, `src`, `alt`, `role`, `style`, `key`, `data-*`, `aria-*`, and many more HTML/SVG attributes.
3383
+
3384
+ **Default ignored patterns:** Empty strings, single characters, CSS units (`px`, `em`, `%`), colors, URLs, paths, file extensions, MIME types, UUIDs, dates, camelCase/snake_case identifiers, HTTP methods, and other technical strings.
3385
+
3386
+ ```javascript
3387
+ // ✅ Good — strings imported from constants
3388
+ import { BUTTON_LABEL, ERROR_MESSAGE, welcomeText } from "@/constants";
3389
+ import { FORM_LABELS } from "@/strings";
3390
+
3391
+ const Component = () => (
3392
+ <div>
3393
+ <button>{BUTTON_LABEL}</button>
3394
+ <span>{ERROR_MESSAGE}</span>
3395
+ <p>{welcomeText}</p>
3396
+ </div>
3397
+ );
3398
+
3399
+ const getMessage = () => ERROR_MESSAGE;
3400
+
3401
+ // ✅ Good — technical strings are allowed
3402
+ <input type="text" className="input-field" />
3403
+ <a href="/dashboard">Link</a>
3404
+ const url = `/api/users/${id}`;
3405
+ const size = "100px";
3406
+
3407
+ // ❌ Bad — hardcoded user-facing strings
3408
+ <button>Submit Form</button>
3409
+ <span>Something went wrong</span>
3410
+ const message = "Welcome to the application";
3411
+ return "User not found";
3412
+ ```
3413
+
3414
+ **Configuration example:**
3415
+
3416
+ ```javascript
3417
+ // Allow more attributes, add custom ignore patterns
3418
+ "code-style/no-hardcoded-strings": ["error", {
3419
+ extraIgnoreAttributes: ["tooltip", "placeholder"],
3420
+ ignorePatterns: ["^TODO:", "^FIXME:"]
3421
+ }]
3422
+ ```
3423
+
3424
+ **Valid import paths for constants:**
3425
+ - `@/constants` or `@/@constants`
3426
+ - `@/strings` or `@/@strings`
3427
+ - `@/data/constants` or `@/data/strings`
3428
+
3429
+ ---
3430
+
3431
+ <br />
3432
+
3281
3433
  ## 📝 Variable Rules
3282
3434
 
3283
3435
  ### `variable-naming-convention`
@@ -3311,7 +3463,7 @@ const UseAuth = () => {}; // hooks should be camelCase
3311
3463
 
3312
3464
  ## 🔧 Auto-fixing
3313
3465
 
3314
- 63 of 69 rules support auto-fixing. Run ESLint with the `--fix` flag:
3466
+ 63 of 70 rules support auto-fixing. Run ESLint with the `--fix` flag:
3315
3467
 
3316
3468
  ```bash
3317
3469
  # Fix all files in src directory
package/index.d.ts CHANGED
@@ -59,6 +59,7 @@ export type RuleNames =
59
59
  | "code-style/no-empty-lines-in-jsx"
60
60
  | "code-style/no-empty-lines-in-objects"
61
61
  | "code-style/no-empty-lines-in-switch-cases"
62
+ | "code-style/no-hardcoded-strings"
62
63
  | "code-style/object-property-per-line"
63
64
  | "code-style/object-property-value-brace"
64
65
  | "code-style/object-property-value-format"
@@ -150,6 +151,7 @@ interface PluginRules {
150
151
  "no-empty-lines-in-jsx": Rule.RuleModule;
151
152
  "no-empty-lines-in-objects": Rule.RuleModule;
152
153
  "no-empty-lines-in-switch-cases": Rule.RuleModule;
154
+ "no-hardcoded-strings": Rule.RuleModule;
153
155
  "object-property-per-line": Rule.RuleModule;
154
156
  "object-property-value-brace": Rule.RuleModule;
155
157
  "object-property-value-format": Rule.RuleModule;