eslint-plugin-code-style 1.7.4 β†’ 1.8.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 CHANGED
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.8.1] - 2026-02-03
11
+
12
+ ### Changed
13
+
14
+ - **`function-naming-convention`** - Add `handleXxx` β†’ `xxxHandler` auto-fix (converts `handleClick` to `clickHandler` instead of `handleClickHandler`)
15
+
16
+ ---
17
+
18
+ ## [1.8.0] - 2026-02-03
19
+
20
+ ### Added
21
+
22
+ - **New Rule: `no-hardcoded-strings`** - Enforce importing strings from constants/strings modules instead of hardcoding them inline. Promotes maintainability, consistency, and easier internationalization.
23
+ - Detects hardcoded strings in JSX text content, attributes, and component logic
24
+ - Configurable `ignoreAttributes`, `extraIgnoreAttributes`, `ignorePatterns` options
25
+ - Automatically ignores technical strings (CSS units, URLs, paths, identifiers, etc.)
26
+ - Valid import paths: `@/constants`, `@/strings`, `@/@constants`, `@/@strings`, `@/data/constants`, `@/data/strings`
27
+
28
+ ### Changed
29
+
30
+ - **`absolute-imports-only`** - Add `strings`, `@constants`, `@strings` to default allowed folders
31
+ - **`module-index-exports`** - Add `strings`, `@constants`, `@strings` to default module folders
32
+
33
+ ### Stats
34
+
35
+ - Total Rules: 70 (was 69)
36
+ - Auto-fixable: 63 rules πŸ”§
37
+ - Report-only: 7 rules (was 6)
38
+
39
+ ---
40
+
41
+ ## [1.7.6] - 2026-02-02
42
+
43
+ ### Changed
44
+
45
+ - **`ternary-condition-multiline`** - Now depends only on operand count, not line length:
46
+ - ≀maxOperands (default: 3): Always collapse to single line regardless of line length
47
+ - \>maxOperands: Format multiline with each operand on its own line
48
+ - Removed `maxLineLength` option (no longer used)
49
+ - This aligns behavior with `multiline-if-conditions` rule
50
+
51
+ ---
52
+
53
+ ## [1.7.5] - 2026-02-02
54
+
55
+ ### Fixed
56
+
57
+ - **`ternary-condition-multiline`** - For ≀3 operands, always collapse to single line when `?` is on different line than condition end (enforces `condition ? value : value` format for simple ternaries)
58
+ - **`no-empty-lines-in-function-params`** - Add detection for empty lines in TSTypeLiteral (type annotation objects like `{ prop: Type }` in intersection types)
59
+
60
+ ---
61
+
10
62
  ## [1.7.4] - 2026-02-02
11
63
 
12
64
  ### Fixed
@@ -1117,6 +1169,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1117
1169
 
1118
1170
  ---
1119
1171
 
1172
+ [1.8.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.0...v1.8.1
1173
+ [1.8.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.6...v1.8.0
1174
+ [1.7.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.5...v1.7.6
1175
+ [1.7.5]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.4...v1.7.5
1120
1176
  [1.7.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.3...v1.7.4
1121
1177
  [1.7.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.2...v1.7.3
1122
1178
  [1.7.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.1...v1.7.2
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
 
@@ -295,11 +296,11 @@ rules: {
295
296
  | `if-statement-format` | `{` on same line as `if`/`else if`, `else` on same line as `}`, proper spacing πŸ”§ |
296
297
  | `multiline-if-conditions` | Conditions exceeding threshold get one operand per line with proper indentation (default: >3) πŸ”§ βš™οΈ |
297
298
  | `no-empty-lines-in-switch-cases` | No empty line after `case X:` before code, no empty lines between cases πŸ”§ |
298
- | `ternary-condition-multiline` | Collapse simple ternaries to single line; expand complex conditions (>3 operands) to multiline πŸ”§ βš™οΈ |
299
+ | `ternary-condition-multiline` | ≀maxOperands always single line; >maxOperands multiline (based on operand count, not line length) πŸ”§ βš™οΈ |
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
 
@@ -1211,24 +1214,24 @@ switch (status) {
1211
1214
 
1212
1215
  ### `ternary-condition-multiline`
1213
1216
 
1214
- **What it does:** Enforces consistent ternary formatting:
1215
- - Simple ternaries (≀3 operands in condition) collapse to single line if they fit
1216
- - Complex ternaries (>3 operands) expand to multiline with each operand on its own line
1217
+ **What it does:** Formats ternary expressions based on condition operand count:
1218
+ - ≀maxOperands (default: 3): Always collapse to single line regardless of line length
1219
+ - \>maxOperands: Expand to multiline with each operand on its own line
1217
1220
 
1218
- **Why use it:** Long ternary conditions on a single line are hard to read. Breaking complex conditions into multiple lines makes them scannable.
1221
+ **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
1222
 
1220
1223
  **Options:**
1221
1224
 
1222
1225
  | Option | Type | Default | Description |
1223
1226
  |--------|------|---------|-------------|
1224
- | `maxOperands` | `integer` | `3` | Maximum operands to keep on single line |
1225
- | `maxLineLength` | `integer` | `120` | Maximum line length for single-line ternaries |
1227
+ | `maxOperands` | `integer` | `3` | Maximum condition operands to keep ternary on single line |
1226
1228
 
1227
1229
  ```javascript
1228
- // βœ… Good β€” simple condition on single line
1230
+ // βœ… Good β€” ≀3 operands always on single line
1229
1231
  const x = a && b && c ? "yes" : "no";
1232
+ const url = lang === "ar" ? `${apiEndpoints.exam.status}/${jobId}?lang=ar` : `${apiEndpoints.exam.status}/${jobId}`;
1230
1233
 
1231
- // βœ… Good β€” complex condition multiline (>3 operands)
1234
+ // βœ… Good β€” >3 operands formatted multiline
1232
1235
  const style = variant === "ghost"
1233
1236
  || variant === "ghost-danger"
1234
1237
  || variant === "muted"
@@ -1236,7 +1239,12 @@ const style = variant === "ghost"
1236
1239
  ? "transparent"
1237
1240
  : "solid";
1238
1241
 
1239
- // ❌ Bad β€” complex condition crammed on one line
1242
+ // ❌ Bad β€” ≀3 operands split across lines
1243
+ const x = a && b && c
1244
+ ? "yes"
1245
+ : "no";
1246
+
1247
+ // ❌ Bad β€” >3 operands crammed on one line
1240
1248
  const style = variant === "ghost" || variant === "ghost-danger" || variant === "muted" || variant === "primary" ? "transparent" : "solid";
1241
1249
  ```
1242
1250
 
@@ -1311,33 +1319,37 @@ function isAuthenticated(): boolean {
1311
1319
 
1312
1320
  **What it does:** Enforces naming conventions for functions:
1313
1321
  - **camelCase** required
1314
- - **Verb prefix** recommended (get, set, handle, is, has, can, should, etc.)
1315
- - **Event handlers** can use `handle` prefix or `Handler` suffix
1322
+ - **Verb prefix** required (get, set, fetch, is, has, can, should, click, submit, etc.)
1323
+ - **Handler suffix** required (all functions must end with `Handler`)
1324
+ - **Auto-fixes** `handleXxx` to `xxxHandler` (avoids redundant `handleClickHandler`)
1325
+ - **Auto-fixes** PascalCase to camelCase for verb-prefixed functions
1316
1326
 
1317
- **Why use it:** Function names should describe actions. Verb prefixes make the purpose immediately clear.
1327
+ **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.
1318
1328
 
1319
1329
  ```javascript
1320
- // βœ… Good β€” clear verb prefixes
1321
- function getUserData() {}
1322
- function setUserName(name) {}
1323
- function handleClick() {}
1324
- function handleSubmit() {}
1325
- function isValidEmail(email) {}
1326
- function hasPermission(user) {}
1327
- function canAccess(resource) {}
1328
- function shouldUpdate(props) {}
1329
- const fetchUsers = async () => {};
1330
- const submitHandler = () => {};
1330
+ // βœ… Good β€” verb prefix + Handler suffix
1331
+ function getUserDataHandler() {}
1332
+ function setUserNameHandler(name) {}
1333
+ function clickHandler() {}
1334
+ function submitHandler() {}
1335
+ function isValidEmailHandler(email) {}
1336
+ function hasPermissionHandler(user) {}
1337
+ function canAccessHandler(resource) {}
1338
+ const fetchUsersHandler = async () => {};
1339
+
1340
+ // ❌ Bad (auto-fixed) β€” handleXxx β†’ xxxHandler
1341
+ function handleClick() {} // β†’ clickHandler
1342
+ function handleSubmit() {} // β†’ submitHandler
1343
+ function handleChange() {} // β†’ changeHandler
1331
1344
 
1332
- // ❌ Bad β€” no verb, unclear purpose
1333
- function userData() {}
1334
- function userName(name) {}
1335
- function click() {}
1336
- function valid(email) {}
1345
+ // ❌ Bad (auto-fixed) β€” missing Handler suffix
1346
+ function getUserData() {} // β†’ getUserDataHandler
1347
+ function setUserName() {} // β†’ setUserNameHandler
1348
+ function fetchUsers() {} // β†’ fetchUsersHandler
1337
1349
 
1338
- // ❌ Bad β€” wrong case
1339
- function GetUserData() {}
1340
- function get_user_data() {}
1350
+ // ❌ Bad (auto-fixed) β€” PascalCase to camelCase
1351
+ function GetUserData() {} // β†’ getUserDataHandler
1352
+ function FetchStatus() {} // β†’ fetchStatusHandler
1341
1353
  ```
1342
1354
 
1343
1355
  ---
@@ -3273,6 +3285,73 @@ const YetAnotherBad = ({ title }) => {
3273
3285
 
3274
3286
  <br />
3275
3287
 
3288
+ ## πŸ“ String Rules
3289
+
3290
+ ### `no-hardcoded-strings`
3291
+
3292
+ **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.
3293
+
3294
+ **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.
3295
+
3296
+ **Options:**
3297
+
3298
+ | Option | Type | Default | Description |
3299
+ |--------|------|---------|-------------|
3300
+ | `ignoreAttributes` | `string[]` | See below | JSX attributes to ignore (replaces defaults) |
3301
+ | `extraIgnoreAttributes` | `string[]` | `[]` | Additional JSX attributes to ignore (extends defaults) |
3302
+ | `ignorePatterns` | `string[]` | `[]` | Regex patterns for strings to ignore |
3303
+
3304
+ **Default ignored attributes:** `className`, `id`, `type`, `name`, `href`, `src`, `alt`, `role`, `style`, `key`, `data-*`, `aria-*`, and many more HTML/SVG attributes.
3305
+
3306
+ **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.
3307
+
3308
+ ```javascript
3309
+ // βœ… Good β€” strings imported from constants
3310
+ import { BUTTON_LABEL, ERROR_MESSAGE, welcomeText } from "@/constants";
3311
+ import { FORM_LABELS } from "@/strings";
3312
+
3313
+ const Component = () => (
3314
+ <div>
3315
+ <button>{BUTTON_LABEL}</button>
3316
+ <span>{ERROR_MESSAGE}</span>
3317
+ <p>{welcomeText}</p>
3318
+ </div>
3319
+ );
3320
+
3321
+ const getMessage = () => ERROR_MESSAGE;
3322
+
3323
+ // βœ… Good β€” technical strings are allowed
3324
+ <input type="text" className="input-field" />
3325
+ <a href="/dashboard">Link</a>
3326
+ const url = `/api/users/${id}`;
3327
+ const size = "100px";
3328
+
3329
+ // ❌ Bad β€” hardcoded user-facing strings
3330
+ <button>Submit Form</button>
3331
+ <span>Something went wrong</span>
3332
+ const message = "Welcome to the application";
3333
+ return "User not found";
3334
+ ```
3335
+
3336
+ **Configuration example:**
3337
+
3338
+ ```javascript
3339
+ // Allow more attributes, add custom ignore patterns
3340
+ "code-style/no-hardcoded-strings": ["error", {
3341
+ extraIgnoreAttributes: ["tooltip", "placeholder"],
3342
+ ignorePatterns: ["^TODO:", "^FIXME:"]
3343
+ }]
3344
+ ```
3345
+
3346
+ **Valid import paths for constants:**
3347
+ - `@/constants` or `@/@constants`
3348
+ - `@/strings` or `@/@strings`
3349
+ - `@/data/constants` or `@/data/strings`
3350
+
3351
+ ---
3352
+
3353
+ <br />
3354
+
3276
3355
  ## πŸ“ Variable Rules
3277
3356
 
3278
3357
  ### `variable-naming-convention`
@@ -3306,7 +3385,7 @@ const UseAuth = () => {}; // hooks should be camelCase
3306
3385
 
3307
3386
  ## πŸ”§ Auto-fixing
3308
3387
 
3309
- 63 of 69 rules support auto-fixing. Run ESLint with the `--fix` flag:
3388
+ 63 of 70 rules support auto-fixing. Run ESLint with the `--fix` flag:
3310
3389
 
3311
3390
  ```bash
3312
3391
  # 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;