eslint-plugin-code-style 1.8.4 → 1.9.2

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,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.9.2] - 2026-02-03
11
+
12
+ ### Enhanced
13
+
14
+ - **`no-hardcoded-strings`** - Now detects special strings that should be enums:
15
+ - HTTP status codes (2xx, 4xx, 5xx like "200", "404", "500")
16
+ - HTTP methods ("GET", "POST", "PUT", "DELETE", etc.)
17
+ - Role/permission names ("admin", "user", "moderator", etc.)
18
+ - Environment names ("production", "development", "staging", etc.)
19
+ - Log levels ("debug", "info", "warn", "error", etc.)
20
+ - Status strings ("active", "pending", "approved", "rejected", etc.)
21
+ - Priority levels ("high", "medium", "low", "critical", etc.)
22
+ - All above → import from `@/enums` or `@/data`
23
+ - Regular strings → import from `@/data or @/strings or @/constants or @/@constants or @/@strings`
24
+
25
+ ### Fixed
26
+
27
+ - **`no-hardcoded-strings`** - Fixed bug where `isInConstantsObjectHandler` incorrectly matched camelCase variable names due to case-insensitive regex flag
28
+
29
+ ---
30
+
31
+ ## [1.9.1] - 2026-02-03
32
+
33
+ ### Enhanced
34
+
35
+ - **`no-hardcoded-strings`** - Initial special string detection:
36
+ - HTTP status codes (4xx, 5xx)
37
+ - Role/permission names
38
+
39
+ ---
40
+
41
+ ## [1.9.0] - 2026-02-03
42
+
43
+ **New Rule: class-method-definition-format + Enhanced Spacing Rules**
44
+
45
+ **Version Range:** v1.8.1 → v1.9.0
46
+
47
+ ### Added
48
+
49
+ **New Rules (1)**
50
+ - **`class-method-definition-format`** - Enforce consistent spacing in class and method definitions 🔧
51
+ - Space before opening brace `{` in class declarations
52
+ - No space between method name and opening parenthesis `(`
53
+ - Space before opening brace `{` in method definitions
54
+ - Opening brace must be on same line as class/method signature
55
+
56
+ ### Enhanced
57
+
58
+ - **`function-call-spacing`** - Now also handles:
59
+ - `new` expressions (`new Class ()` → `new Class()`)
60
+ - Generic type calls (`get <Type>()` → `get<Type>()`)
61
+ - **`member-expression-bracket-spacing`** - Now also handles:
62
+ - TypeScript indexed access types (`Type ["prop"]` → `Type["prop"]`)
63
+
64
+ ### Stats
65
+
66
+ - Total Rules: 71 (was 70)
67
+ - Auto-fixable: 64 rules 🔧 (was 63)
68
+ - Report-only: 7 rules
69
+
70
+ **Full Changelog:** [v1.8.1...v1.9.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.1...v1.9.0)
71
+
72
+ ---
73
+
10
74
  ## [1.8.4] - 2026-02-03
11
75
 
12
76
  ### Documentation
@@ -54,7 +118,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
54
118
 
55
119
  **New Rule: no-hardcoded-strings**
56
120
 
57
- **Version Range:** v1.6.1 → v1.8.0
121
+ **Version Range:** v1.7.1 → v1.8.0
58
122
 
59
123
  ### Added
60
124
 
@@ -76,7 +140,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
76
140
  - Auto-fixable: 63 rules 🔧
77
141
  - Report-only: 7 rules (was 6)
78
142
 
79
- **Full Changelog:** [v1.6.1...v1.8.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.1...v1.8.0)
143
+ **Full Changelog:** [v1.7.1...v1.8.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.1...v1.8.0)
80
144
 
81
145
  ---
82
146
 
@@ -1211,6 +1275,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1211
1275
 
1212
1276
  ---
1213
1277
 
1278
+ [1.9.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.9.1...v1.9.2
1279
+ [1.9.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.9.0...v1.9.1
1280
+ [1.9.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.4...v1.9.0
1214
1281
  [1.8.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.3...v1.8.4
1215
1282
  [1.8.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.2...v1.8.3
1216
1283
  [1.8.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.1...v1.8.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
- *70 rules (63 auto-fixable) to keep your codebase clean and consistent*
22
+ *71 rules (64 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 **70 custom rules** (63 auto-fixable) for code formatting. Built for **ESLint v9 flat configs**.
30
+ This plugin provides **71 custom rules** (64 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 **70 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 70 rules support auto-fix with `eslint --fix`
39
+ - **Highly automated** — 64 of 71 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
- **63 rules** support automatic fixing with `eslint --fix`. 6 rules are report-only (require manual changes).
100
+ **64 rules** support automatic fixing with `eslint --fix`. 6 rules are report-only (require manual changes).
101
101
 
102
102
  </td>
103
103
  <td width="50%">
@@ -190,6 +190,7 @@ rules: {
190
190
  "code-style/arrow-function-simplify": "error",
191
191
  "code-style/assignment-value-same-line": "error",
192
192
  "code-style/block-statement-newlines": "error",
193
+ "code-style/class-method-definition-format": "error",
193
194
  "code-style/class-naming-convention": "error",
194
195
  "code-style/classname-dynamic-at-end": "error",
195
196
  "code-style/classname-multiline": "error",
@@ -260,7 +261,7 @@ rules: {
260
261
 
261
262
  ## 📖 Rules Categories
262
263
 
263
- > **70 rules total** — 63 with auto-fix 🔧, 7 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
264
+ > **71 rules total** — 64 with auto-fix 🔧, 7 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
264
265
  >
265
266
  > **Legend:** 🔧 Auto-fixable with `eslint --fix` • ⚙️ Customizable options
266
267
 
@@ -288,6 +289,7 @@ rules: {
288
289
  | `component-props-destructure` | Component props must be destructured `({ prop })` not received as `(props)` 🔧 |
289
290
  | `component-props-inline-type` | Inline type annotation `} : {` with matching props, proper spacing, commas, no interface reference 🔧 |
290
291
  | **Class Rules** | |
292
+ | `class-method-definition-format` | Consistent spacing in class/method definitions: space before `{`, no space before `(` 🔧 |
291
293
  | `class-naming-convention` | Class declarations must end with "Class" suffix (e.g., `ApiServiceClass`) 🔧 |
292
294
  | **Control Flow Rules** | |
293
295
  | `block-statement-newlines` | Newline after `{` and before `}` in if/for/while/function blocks 🔧 |
@@ -893,6 +895,60 @@ dispatch(
893
895
 
894
896
  ## 🏛️ Class Rules
895
897
 
898
+ ### `class-method-definition-format`
899
+
900
+ **What it does:** Enforces consistent spacing in class and method definitions:
901
+ - Space before opening brace `{` in class declarations
902
+ - No space between method name and opening parenthesis `(`
903
+ - Space before opening brace `{` in method definitions
904
+ - Opening brace must be on same line as class/method signature
905
+
906
+ **Why use it:** Consistent formatting makes code more readable and prevents common spacing inconsistencies in class definitions.
907
+
908
+ ```javascript
909
+ // ✅ Good — proper spacing in class and methods
910
+ class ApiServiceClass {
911
+ getDataHandler(): string {
912
+ return "data";
913
+ }
914
+
915
+ async fetchUserHandler(id: string): Promise<User> {
916
+ return await this.fetch(id);
917
+ }
918
+ }
919
+
920
+ // ❌ Bad — missing space before { in class
921
+ class ApiServiceClass{
922
+ getDataHandler(): string {
923
+ return "data";
924
+ }
925
+ }
926
+
927
+ // ❌ Bad — space between method name and (
928
+ class ApiServiceClass {
929
+ getDataHandler (): string {
930
+ return "data";
931
+ }
932
+ }
933
+
934
+ // ❌ Bad — missing space before { in method
935
+ class ApiServiceClass {
936
+ getDataHandler(): string{
937
+ return "data";
938
+ }
939
+ }
940
+
941
+ // ❌ Bad — opening brace on different line
942
+ class ApiServiceClass {
943
+ getDataHandler(): string
944
+ {
945
+ return "data";
946
+ }
947
+ }
948
+ ```
949
+
950
+ ---
951
+
896
952
  ### `class-naming-convention`
897
953
 
898
954
  **What it does:** Enforces that class declarations must end with "Class" suffix. This distinguishes class definitions from other PascalCase names like React components or type definitions.
@@ -3367,10 +3423,19 @@ const YetAnotherBad = ({ title }) => {
3367
3423
 
3368
3424
  ### `no-hardcoded-strings`
3369
3425
 
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.
3426
+ **What it does:** Enforces that user-facing strings should be imported from constants/strings/data modules rather than hardcoded inline. This promotes maintainability, consistency, and enables easier internationalization.
3371
3427
 
3372
3428
  **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
3429
 
3430
+ **Special detection (should be imported from `@/enums` or `@/data`):**
3431
+ - **HTTP status codes** — 2xx, 4xx, 5xx like "200", "404", "500"
3432
+ - **HTTP methods** — "GET", "POST", "PUT", "DELETE", "PATCH", etc.
3433
+ - **Role/permission names** — "admin", "user", "moderator", "editor", etc.
3434
+ - **Environment names** — "production", "development", "staging", "test", etc.
3435
+ - **Log levels** — "debug", "info", "warn", "error", "fatal", etc.
3436
+ - **Status strings** — "active", "pending", "approved", "rejected", "completed", etc.
3437
+ - **Priority levels** — "high", "medium", "low", "critical", "urgent", etc.
3438
+
3374
3439
  **Options:**
3375
3440
 
3376
3441
  | Option | Type | Default | Description |
@@ -3387,8 +3452,9 @@ const YetAnotherBad = ({ title }) => {
3387
3452
  // ✅ Good — strings imported from constants
3388
3453
  import { BUTTON_LABEL, ERROR_MESSAGE, welcomeText } from "@/constants";
3389
3454
  import { FORM_LABELS } from "@/strings";
3455
+ import { HttpStatus, UserRole } from "@/enums";
3390
3456
 
3391
- const Component = () => (
3457
+ const ComponentHandler = () => (
3392
3458
  <div>
3393
3459
  <button>{BUTTON_LABEL}</button>
3394
3460
  <span>{ERROR_MESSAGE}</span>
@@ -3396,7 +3462,11 @@ const Component = () => (
3396
3462
  </div>
3397
3463
  );
3398
3464
 
3399
- const getMessage = () => ERROR_MESSAGE;
3465
+ const getMessageHandler = () => ERROR_MESSAGE;
3466
+
3467
+ // ✅ Good — using enums for status codes and roles
3468
+ if (status === HttpStatus.NOT_FOUND) { ... }
3469
+ if (role === UserRole.ADMIN) { ... }
3400
3470
 
3401
3471
  // ✅ Good — technical strings are allowed
3402
3472
  <input type="text" className="input-field" />
@@ -3409,6 +3479,10 @@ const size = "100px";
3409
3479
  <span>Something went wrong</span>
3410
3480
  const message = "Welcome to the application";
3411
3481
  return "User not found";
3482
+
3483
+ // ❌ Bad — hardcoded status codes and roles
3484
+ if (status === "404") { ... }
3485
+ if (role === "admin") { ... }
3412
3486
  ```
3413
3487
 
3414
3488
  **Configuration example:**
@@ -3421,10 +3495,14 @@ return "User not found";
3421
3495
  }]
3422
3496
  ```
3423
3497
 
3424
- **Valid import paths for constants:**
3425
- - `@/constants` or `@/@constants`
3498
+ **Valid import paths for strings:**
3499
+ - `@/data`
3426
3500
  - `@/strings` or `@/@strings`
3427
- - `@/data/constants` or `@/data/strings`
3501
+ - `@/constants` or `@/@constants`
3502
+
3503
+ **Valid import paths for enums (status codes, roles):**
3504
+ - `@/enums`
3505
+ - `@/data`
3428
3506
 
3429
3507
  ---
3430
3508
 
@@ -3463,7 +3541,7 @@ const UseAuth = () => {}; // hooks should be camelCase
3463
3541
 
3464
3542
  ## 🔧 Auto-fixing
3465
3543
 
3466
- 63 of 70 rules support auto-fixing. Run ESLint with the `--fix` flag:
3544
+ 64 of 71 rules support auto-fixing. Run ESLint with the `--fix` flag:
3467
3545
 
3468
3546
  ```bash
3469
3547
  # Fix all files in src directory
package/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export type RuleNames =
13
13
  | "code-style/arrow-function-simplify"
14
14
  | "code-style/assignment-value-same-line"
15
15
  | "code-style/block-statement-newlines"
16
+ | "code-style/class-method-definition-format"
16
17
  | "code-style/class-naming-convention"
17
18
  | "code-style/comment-format"
18
19
  | "code-style/component-props-destructure"
@@ -105,6 +106,7 @@ interface PluginRules {
105
106
  "arrow-function-simplify": Rule.RuleModule;
106
107
  "assignment-value-same-line": Rule.RuleModule;
107
108
  "block-statement-newlines": Rule.RuleModule;
109
+ "class-method-definition-format": Rule.RuleModule;
108
110
  "class-naming-convention": Rule.RuleModule;
109
111
  "comment-format": Rule.RuleModule;
110
112
  "component-props-destructure": Rule.RuleModule;
package/index.js CHANGED
@@ -1850,31 +1850,80 @@ const functionCallSpacing = {
1850
1850
  create(context) {
1851
1851
  const sourceCode = context.sourceCode || context.getSourceCode();
1852
1852
 
1853
- return {
1854
- CallExpression(node) {
1855
- const { callee } = node;
1853
+ const checkCallSpacingHandler = (node, callee) => {
1854
+ // Get the last token of the callee (function name or member expression)
1855
+ const calleeLastToken = sourceCode.getLastToken(callee);
1856
1856
 
1857
- // Get the last token of the callee (function name or member expression)
1858
- const calleeLastToken = sourceCode.getLastToken(callee);
1857
+ // For calls with type arguments (generics), check space before <
1858
+ // e.g., axiosClient.get <Type>() should be axiosClient.get<Type>()
1859
+ if (node.typeArguments || node.typeParameters) {
1860
+ const typeArgs = node.typeArguments || node.typeParameters;
1861
+ const openAngle = sourceCode.getFirstToken(typeArgs);
1859
1862
 
1860
- // Get the opening parenthesis
1861
- const openParen = sourceCode.getTokenAfter(callee);
1863
+ if (openAngle && openAngle.value === "<") {
1864
+ const textBeforeAngle = sourceCode.text.slice(calleeLastToken.range[1], openAngle.range[0]);
1862
1865
 
1863
- if (!openParen || openParen.value !== "(") return;
1866
+ if (/\s/.test(textBeforeAngle)) {
1867
+ context.report({
1868
+ fix: (fixer) => fixer.replaceTextRange(
1869
+ [calleeLastToken.range[1], openAngle.range[0]],
1870
+ "",
1871
+ ),
1872
+ message: "No space between function name and generic type arguments",
1873
+ node: openAngle,
1874
+ });
1875
+ }
1876
+ }
1864
1877
 
1865
- // Check if there's space between callee and opening paren
1866
- const textBetween = sourceCode.text.slice(calleeLastToken.range[1], openParen.range[0]);
1878
+ // Also check space between closing > and opening (
1879
+ const closeAngle = sourceCode.getLastToken(typeArgs);
1880
+ const openParen = sourceCode.getTokenAfter(typeArgs);
1867
1881
 
1868
- if (textBetween.length > 0) {
1869
- context.report({
1870
- fix: (fixer) => fixer.replaceTextRange(
1871
- [calleeLastToken.range[1], openParen.range[0]],
1872
- "",
1873
- ),
1874
- message: "No space between function name and opening parenthesis",
1875
- node: openParen,
1876
- });
1882
+ if (openParen && openParen.value === "(") {
1883
+ const textBetween = sourceCode.text.slice(closeAngle.range[1], openParen.range[0]);
1884
+
1885
+ if (/\s/.test(textBetween)) {
1886
+ context.report({
1887
+ fix: (fixer) => fixer.replaceTextRange(
1888
+ [closeAngle.range[1], openParen.range[0]],
1889
+ "",
1890
+ ),
1891
+ message: "No space between generic type arguments and opening parenthesis",
1892
+ node: openParen,
1893
+ });
1894
+ }
1877
1895
  }
1896
+
1897
+ return;
1898
+ }
1899
+
1900
+ // Get the opening parenthesis
1901
+ const openParen = sourceCode.getTokenAfter(callee);
1902
+
1903
+ if (!openParen || openParen.value !== "(") return;
1904
+
1905
+ // Check if there's space between callee and opening paren
1906
+ const textBetween = sourceCode.text.slice(calleeLastToken.range[1], openParen.range[0]);
1907
+
1908
+ if (textBetween.length > 0) {
1909
+ context.report({
1910
+ fix: (fixer) => fixer.replaceTextRange(
1911
+ [calleeLastToken.range[1], openParen.range[0]],
1912
+ "",
1913
+ ),
1914
+ message: "No space between function name and opening parenthesis",
1915
+ node: openParen,
1916
+ });
1917
+ }
1918
+ };
1919
+
1920
+ return {
1921
+ CallExpression(node) {
1922
+ checkCallSpacingHandler(node, node.callee);
1923
+ },
1924
+
1925
+ NewExpression(node) {
1926
+ checkCallSpacingHandler(node, node.callee);
1878
1927
  },
1879
1928
  };
1880
1929
  },
@@ -5384,6 +5433,180 @@ const classNamingConvention = {
5384
5433
  },
5385
5434
  };
5386
5435
 
5436
+ /**
5437
+ * ───────────────────────────────────────────────────────────────
5438
+ * Rule: Class/Method Definition Format
5439
+ * ───────────────────────────────────────────────────────────────
5440
+ *
5441
+ * Description:
5442
+ * Enforce consistent spacing in class and method definitions:
5443
+ * - Space before opening brace { in class declarations
5444
+ * - No space between method name and opening parenthesis (
5445
+ * - Space before opening brace { in method definitions
5446
+ * - Opening brace must be on same line as method signature
5447
+ *
5448
+ * ✓ Good:
5449
+ * class ApiServiceClass {
5450
+ * getDataHandler(): string {
5451
+ * return "data";
5452
+ * }
5453
+ * async fetchUserHandler(id: string): Promise<User> {
5454
+ * return await fetch(id);
5455
+ * }
5456
+ * }
5457
+ *
5458
+ * ✗ Bad:
5459
+ * class ApiServiceClass{ // Missing space before {
5460
+ * class ApiServiceClass
5461
+ * { // Opening brace on different line
5462
+ * getDataHandler (): string { // Space before (
5463
+ * getDataHandler(): string{ // Missing space before {
5464
+ * getDataHandler(): string
5465
+ * { // Opening brace on different line
5466
+ */
5467
+ const classMethodDefinitionFormat = {
5468
+ create(context) {
5469
+ const sourceCode = context.sourceCode || context.getSourceCode();
5470
+
5471
+ return {
5472
+ ClassDeclaration(node) {
5473
+ const classBody = node.body;
5474
+
5475
+ if (!classBody) return;
5476
+
5477
+ // Find the opening brace of the class body
5478
+ const openBrace = sourceCode.getFirstToken(classBody);
5479
+
5480
+ if (!openBrace || openBrace.value !== "{") return;
5481
+
5482
+ // Get the token before the opening brace (class name or extends clause)
5483
+ const tokenBefore = sourceCode.getTokenBefore(openBrace);
5484
+
5485
+ if (!tokenBefore) return;
5486
+
5487
+ // Check if opening brace is on same line as class declaration
5488
+ if (tokenBefore.loc.end.line !== openBrace.loc.start.line) {
5489
+ context.report({
5490
+ fix: (fixer) => fixer.replaceTextRange(
5491
+ [tokenBefore.range[1], openBrace.range[0]],
5492
+ " ",
5493
+ ),
5494
+ message: "Opening brace should be on the same line as class declaration",
5495
+ node: openBrace,
5496
+ });
5497
+
5498
+ return;
5499
+ }
5500
+
5501
+ // Check for space before opening brace
5502
+ const textBetween = sourceCode.text.slice(tokenBefore.range[1], openBrace.range[0]);
5503
+
5504
+ if (textBetween !== " ") {
5505
+ context.report({
5506
+ fix: (fixer) => fixer.replaceTextRange(
5507
+ [tokenBefore.range[1], openBrace.range[0]],
5508
+ " ",
5509
+ ),
5510
+ message: "Expected single space before opening brace in class declaration",
5511
+ node: openBrace,
5512
+ });
5513
+ }
5514
+ },
5515
+
5516
+ MethodDefinition(node) {
5517
+ const methodKey = node.key;
5518
+ const methodValue = node.value;
5519
+
5520
+ if (!methodKey || !methodValue) return;
5521
+
5522
+ // Check for space between method name and opening parenthesis
5523
+ // For computed properties, we need to get the ] token
5524
+ const keyLastToken = node.computed
5525
+ ? sourceCode.getTokenAfter(methodKey, { filter: (t) => t.value === "]" })
5526
+ : sourceCode.getLastToken(methodKey);
5527
+
5528
+ if (!keyLastToken) return;
5529
+
5530
+ // Find the opening parenthesis of the parameters
5531
+ let openParen = sourceCode.getTokenAfter(keyLastToken);
5532
+
5533
+ // Skip over async, static, get, set keywords and * for generators
5534
+ while (openParen && openParen.value !== "(") {
5535
+ openParen = sourceCode.getTokenAfter(openParen);
5536
+ }
5537
+
5538
+ if (!openParen || openParen.value !== "(") return;
5539
+
5540
+ // Get the token immediately before the opening paren (method name or modifier)
5541
+ const tokenBeforeParen = sourceCode.getTokenBefore(openParen);
5542
+
5543
+ if (tokenBeforeParen) {
5544
+ const textBeforeParen = sourceCode.text.slice(tokenBeforeParen.range[1], openParen.range[0]);
5545
+
5546
+ if (/\s/.test(textBeforeParen)) {
5547
+ context.report({
5548
+ fix: (fixer) => fixer.replaceTextRange(
5549
+ [tokenBeforeParen.range[1], openParen.range[0]],
5550
+ "",
5551
+ ),
5552
+ message: "No space between method name and opening parenthesis",
5553
+ node: openParen,
5554
+ });
5555
+ }
5556
+ }
5557
+
5558
+ // Check for space before opening brace and brace on same line
5559
+ const functionBody = methodValue.body;
5560
+
5561
+ if (!functionBody || functionBody.type !== "BlockStatement") return;
5562
+
5563
+ const openBrace = sourceCode.getFirstToken(functionBody);
5564
+
5565
+ if (!openBrace || openBrace.value !== "{") return;
5566
+
5567
+ // Get the token before the opening brace (return type annotation or closing paren)
5568
+ const tokenBeforeBrace = sourceCode.getTokenBefore(openBrace);
5569
+
5570
+ if (!tokenBeforeBrace) return;
5571
+
5572
+ // Check if opening brace is on same line
5573
+ if (tokenBeforeBrace.loc.end.line !== openBrace.loc.start.line) {
5574
+ context.report({
5575
+ fix: (fixer) => fixer.replaceTextRange(
5576
+ [tokenBeforeBrace.range[1], openBrace.range[0]],
5577
+ " ",
5578
+ ),
5579
+ message: "Opening brace should be on the same line as method signature",
5580
+ node: openBrace,
5581
+ });
5582
+
5583
+ return;
5584
+ }
5585
+
5586
+ // Check for space before opening brace
5587
+ const textBeforeBrace = sourceCode.text.slice(tokenBeforeBrace.range[1], openBrace.range[0]);
5588
+
5589
+ if (textBeforeBrace !== " ") {
5590
+ context.report({
5591
+ fix: (fixer) => fixer.replaceTextRange(
5592
+ [tokenBeforeBrace.range[1], openBrace.range[0]],
5593
+ " ",
5594
+ ),
5595
+ message: "Expected single space before opening brace in method definition",
5596
+ node: openBrace,
5597
+ });
5598
+ }
5599
+ },
5600
+ };
5601
+ },
5602
+ meta: {
5603
+ docs: { description: "Enforce consistent spacing in class and method definitions" },
5604
+ fixable: "whitespace",
5605
+ schema: [],
5606
+ type: "layout",
5607
+ },
5608
+ };
5609
+
5387
5610
  /**
5388
5611
  * ───────────────────────────────────────────────────────────────
5389
5612
  * Rule: Enum Type Enforcement
@@ -9921,44 +10144,71 @@ const memberExpressionBracketSpacing = {
9921
10144
  create(context) {
9922
10145
  const sourceCode = context.sourceCode || context.getSourceCode();
9923
10146
 
9924
- return {
9925
- MemberExpression(node) {
9926
- // Only check computed member expressions (bracket notation)
9927
- if (!node.computed) return;
10147
+ const checkBracketSpacingHandler = (node, objectPart, indexPart) => {
10148
+ const openBracket = sourceCode.getTokenBefore(indexPart);
10149
+ const closeBracket = sourceCode.getTokenAfter(indexPart);
9928
10150
 
9929
- const openBracket = sourceCode.getTokenBefore(node.property);
9930
- const closeBracket = sourceCode.getTokenAfter(node.property);
10151
+ if (!openBracket || openBracket.value !== "[") return;
10152
+ if (!closeBracket || closeBracket.value !== "]") return;
9931
10153
 
9932
- if (!openBracket || openBracket.value !== "[") return;
9933
- if (!closeBracket || closeBracket.value !== "]") return;
10154
+ // Check for space before [ (between object and bracket)
10155
+ const objectLastToken = sourceCode.getLastToken(objectPart);
9934
10156
 
9935
- // Check for space after [
9936
- const textAfterOpen = sourceCode.text.slice(openBracket.range[1], node.property.range[0]);
10157
+ if (objectLastToken) {
10158
+ const textBeforeOpen = sourceCode.text.slice(objectLastToken.range[1], openBracket.range[0]);
9937
10159
 
9938
- if (textAfterOpen.includes(" ") || textAfterOpen.includes("\n")) {
10160
+ if (/\s/.test(textBeforeOpen)) {
9939
10161
  context.report({
9940
10162
  fix: (fixer) => fixer.replaceTextRange(
9941
- [openBracket.range[1], node.property.range[0]],
10163
+ [objectLastToken.range[1], openBracket.range[0]],
9942
10164
  "",
9943
10165
  ),
9944
- message: "No space after opening bracket in member expression",
10166
+ message: "No space before opening bracket in member expression",
9945
10167
  node: openBracket,
9946
10168
  });
9947
10169
  }
10170
+ }
9948
10171
 
9949
- // Check for space before ]
9950
- const textBeforeClose = sourceCode.text.slice(node.property.range[1], closeBracket.range[0]);
10172
+ // Check for space after [
10173
+ const textAfterOpen = sourceCode.text.slice(openBracket.range[1], indexPart.range[0]);
9951
10174
 
9952
- if (textBeforeClose.includes(" ") || textBeforeClose.includes("\n")) {
9953
- context.report({
9954
- fix: (fixer) => fixer.replaceTextRange(
9955
- [node.property.range[1], closeBracket.range[0]],
9956
- "",
9957
- ),
9958
- message: "No space before closing bracket in member expression",
9959
- node: closeBracket,
9960
- });
9961
- }
10175
+ if (textAfterOpen.includes(" ") || textAfterOpen.includes("\n")) {
10176
+ context.report({
10177
+ fix: (fixer) => fixer.replaceTextRange(
10178
+ [openBracket.range[1], indexPart.range[0]],
10179
+ "",
10180
+ ),
10181
+ message: "No space after opening bracket in member expression",
10182
+ node: openBracket,
10183
+ });
10184
+ }
10185
+
10186
+ // Check for space before ]
10187
+ const textBeforeClose = sourceCode.text.slice(indexPart.range[1], closeBracket.range[0]);
10188
+
10189
+ if (textBeforeClose.includes(" ") || textBeforeClose.includes("\n")) {
10190
+ context.report({
10191
+ fix: (fixer) => fixer.replaceTextRange(
10192
+ [indexPart.range[1], closeBracket.range[0]],
10193
+ "",
10194
+ ),
10195
+ message: "No space before closing bracket in member expression",
10196
+ node: closeBracket,
10197
+ });
10198
+ }
10199
+ };
10200
+
10201
+ return {
10202
+ MemberExpression(node) {
10203
+ // Only check computed member expressions (bracket notation)
10204
+ if (!node.computed) return;
10205
+
10206
+ checkBracketSpacingHandler(node, node.object, node.property);
10207
+ },
10208
+
10209
+ // Handle TypeScript indexed access types: Type["prop"]
10210
+ TSIndexedAccessType(node) {
10211
+ checkBracketSpacingHandler(node, node.objectType, node.indexType);
9962
10212
  },
9963
10213
  };
9964
10214
  },
@@ -13572,10 +13822,30 @@ const stringPropertySpacing = {
13572
13822
  *
13573
13823
  * Description:
13574
13824
  * Enforces that user-facing strings should be imported from
13575
- * constants/strings modules rather than hardcoded inline.
13825
+ * constants/strings/data modules rather than hardcoded inline.
13576
13826
  * This promotes maintainability, consistency, and enables
13577
13827
  * easier internationalization.
13578
13828
  *
13829
+ * The rule also detects special strings that should be enums:
13830
+ * - HTTP status codes (2xx, 4xx, 5xx like "200", "404", "500")
13831
+ * - HTTP methods ("GET", "POST", "PUT", "DELETE", etc.)
13832
+ * - Role/permission names ("admin", "user", "moderator", etc.)
13833
+ * - Environment names ("production", "development", "staging", etc.)
13834
+ * - Log levels ("debug", "info", "warn", "error", etc.)
13835
+ * - Status strings ("active", "pending", "approved", "rejected", etc.)
13836
+ * - Priority levels ("high", "medium", "low", "critical", etc.)
13837
+ *
13838
+ * Valid import sources for strings:
13839
+ * - @/data
13840
+ * - @/strings
13841
+ * - @/constants
13842
+ * - @/@strings
13843
+ * - @/@constants
13844
+ *
13845
+ * Valid import sources for enums:
13846
+ * - @/enums
13847
+ * - @/data
13848
+ *
13579
13849
  * Options:
13580
13850
  * { ignoreAttributes: ["className", "id", ...] } - JSX attributes to ignore (replaces defaults)
13581
13851
  * { extraIgnoreAttributes: ["tooltip", ...] } - Additional JSX attributes to ignore (extends defaults)
@@ -13584,16 +13854,21 @@ const stringPropertySpacing = {
13584
13854
  * ✓ Good:
13585
13855
  * import { BUTTON_LABEL, ERROR_MESSAGE } from "@/constants";
13586
13856
  * import { welcomeText } from "@/strings";
13857
+ * import { HttpStatus, UserRole } from "@/enums";
13587
13858
  *
13588
13859
  * <button>{BUTTON_LABEL}</button>
13589
13860
  * <span>{ERROR_MESSAGE}</span>
13590
13861
  * const message = welcomeText;
13862
+ * if (status === HttpStatus.NOT_FOUND) { ... }
13863
+ * if (role === UserRole.ADMIN) { ... }
13591
13864
  *
13592
13865
  * ✗ Bad:
13593
13866
  * <button>Submit</button>
13594
13867
  * <span>Something went wrong</span>
13595
13868
  * const message = "Welcome to the app";
13596
13869
  * return "User not found";
13870
+ * if (status === "404") { ... } // HTTP status code
13871
+ * if (role === "admin") { ... } // Role name
13597
13872
  */
13598
13873
  const noHardcodedStrings = {
13599
13874
  create(context) {
@@ -13786,8 +14061,6 @@ const noHardcodedStrings = {
13786
14061
  /^[A-Z][A-Z0-9_]*$/,
13787
14062
  // Common technical strings
13788
14063
  /^(true|false|null|undefined|NaN|Infinity)$/,
13789
- // HTTP methods
13790
- /^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|CONNECT|TRACE)$/,
13791
14064
  // Content types
13792
14065
  /^application\//,
13793
14066
  // Environment variables pattern
@@ -13816,16 +14089,181 @@ const noHardcodedStrings = {
13816
14089
 
13817
14090
  const allIgnorePatterns = [...technicalPatterns, ...extraIgnorePatterns];
13818
14091
 
13819
- // Check if a string matches any ignore pattern
13820
- const shouldIgnoreStringHandler = (str) => allIgnorePatterns.some((pattern) => pattern.test(str));
14092
+ // HTTP status codes that should NOT be hardcoded (2xx, 4xx, and 5xx)
14093
+ const httpStatusCodePattern = /^[245]\d{2}$/;
14094
+
14095
+ // Common role/permission names that should be imported from enums/data
14096
+ const rolePermissionNames = new Set([
14097
+ "admin",
14098
+ "administrator",
14099
+ "editor",
14100
+ "guest",
14101
+ "manager",
14102
+ "member",
14103
+ "moderator",
14104
+ "operator",
14105
+ "owner",
14106
+ "reviewer",
14107
+ "subscriber",
14108
+ "superadmin",
14109
+ "supervisor",
14110
+ "user",
14111
+ "viewer",
14112
+ ]);
14113
+
14114
+ // HTTP methods that should be imported from enums/data
14115
+ const httpMethods = new Set([
14116
+ "CONNECT",
14117
+ "DELETE",
14118
+ "GET",
14119
+ "HEAD",
14120
+ "OPTIONS",
14121
+ "PATCH",
14122
+ "POST",
14123
+ "PUT",
14124
+ "TRACE",
14125
+ ]);
14126
+
14127
+ // Environment names that should be imported from enums/data
14128
+ const environmentNames = new Set([
14129
+ "dev",
14130
+ "development",
14131
+ "local",
14132
+ "prod",
14133
+ "production",
14134
+ "qa",
14135
+ "sandbox",
14136
+ "staging",
14137
+ "test",
14138
+ "testing",
14139
+ "uat",
14140
+ ]);
14141
+
14142
+ // Log levels that should be imported from enums/data
14143
+ const logLevels = new Set([
14144
+ "debug",
14145
+ "error",
14146
+ "fatal",
14147
+ "info",
14148
+ "log",
14149
+ "trace",
14150
+ "warn",
14151
+ "warning",
14152
+ ]);
13821
14153
 
13822
- // Check if we're inside a constants/strings file
14154
+ // Status/state strings that should be imported from enums/data
14155
+ const statusStrings = new Set([
14156
+ "accepted",
14157
+ "active",
14158
+ "approved",
14159
+ "archived",
14160
+ "blocked",
14161
+ "cancelled",
14162
+ "closed",
14163
+ "completed",
14164
+ "declined",
14165
+ "deleted",
14166
+ "disabled",
14167
+ "draft",
14168
+ "enabled",
14169
+ "expired",
14170
+ "failed",
14171
+ "inactive",
14172
+ "open",
14173
+ "paused",
14174
+ "pending",
14175
+ "processing",
14176
+ "published",
14177
+ "rejected",
14178
+ "resolved",
14179
+ "scheduled",
14180
+ "submitted",
14181
+ "success",
14182
+ "suspended",
14183
+ "verified",
14184
+ ]);
14185
+
14186
+ // Priority levels that should be imported from enums/data
14187
+ const priorityLevels = new Set([
14188
+ "critical",
14189
+ "high",
14190
+ "highest",
14191
+ "low",
14192
+ "lowest",
14193
+ "medium",
14194
+ "normal",
14195
+ "urgent",
14196
+ ]);
14197
+
14198
+ // Check functions for each category
14199
+ const isHttpStatusCodeHandler = (str) => httpStatusCodePattern.test(str);
14200
+ const isRoleNameHandler = (str) => rolePermissionNames.has(str.toLowerCase());
14201
+ const isHttpMethodHandler = (str) => httpMethods.has(str.toUpperCase());
14202
+ const isEnvironmentNameHandler = (str) => environmentNames.has(str.toLowerCase());
14203
+ const isLogLevelHandler = (str) => logLevels.has(str.toLowerCase());
14204
+ const isStatusStringHandler = (str) => statusStrings.has(str.toLowerCase());
14205
+ const isPriorityLevelHandler = (str) => priorityLevels.has(str.toLowerCase());
14206
+
14207
+ // Check if string should be flagged even if it matches technical patterns
14208
+ const isFlaggedSpecialStringHandler = (str) => isHttpStatusCodeHandler(str)
14209
+ || isRoleNameHandler(str)
14210
+ || isHttpMethodHandler(str)
14211
+ || isEnvironmentNameHandler(str)
14212
+ || isLogLevelHandler(str)
14213
+ || isStatusStringHandler(str)
14214
+ || isPriorityLevelHandler(str);
14215
+
14216
+ // Get descriptive error message based on string type
14217
+ const getErrorMessageHandler = (str, context = "") => {
14218
+ const truncatedStr = str.length > 30 ? `${str.substring(0, 30)}...` : str;
14219
+ const contextPart = context ? ` in ${context}` : "";
14220
+
14221
+ if (isHttpStatusCodeHandler(str)) {
14222
+ return `Hardcoded HTTP status code "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
14223
+ }
14224
+
14225
+ if (isRoleNameHandler(str)) {
14226
+ return `Hardcoded role/permission "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
14227
+ }
14228
+
14229
+ if (isHttpMethodHandler(str)) {
14230
+ return `Hardcoded HTTP method "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
14231
+ }
14232
+
14233
+ if (isEnvironmentNameHandler(str)) {
14234
+ return `Hardcoded environment name "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
14235
+ }
14236
+
14237
+ if (isLogLevelHandler(str)) {
14238
+ return `Hardcoded log level "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
14239
+ }
14240
+
14241
+ if (isStatusStringHandler(str)) {
14242
+ return `Hardcoded status "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
14243
+ }
14244
+
14245
+ if (isPriorityLevelHandler(str)) {
14246
+ return `Hardcoded priority level "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
14247
+ }
14248
+
14249
+ return `Hardcoded string "${truncatedStr}"${contextPart} should be imported from @/data or @/strings or @/constants or @/@constants or @/@strings`;
14250
+ };
14251
+
14252
+ // Check if a string matches any ignore pattern (but not if it's a flagged special string)
14253
+ const shouldIgnoreStringHandler = (str) => {
14254
+ // Always flag HTTP status codes and role names
14255
+ if (isFlaggedSpecialStringHandler(str)) return false;
14256
+
14257
+ return allIgnorePatterns.some((pattern) => pattern.test(str));
14258
+ };
14259
+
14260
+ // Check if we're inside a constants/strings/data/enums file
13823
14261
  const isConstantsFileHandler = () => {
13824
14262
  const filename = context.filename || context.getFilename();
13825
14263
  const normalizedPath = filename.replace(/\\/g, "/").toLowerCase();
13826
14264
 
13827
- // Check if file is in constants/strings folders
13828
- return /\/(constants|strings|@constants|@strings)(\/|\.)/i.test(normalizedPath)
14265
+ // Check if file is in constants/strings/data/enums folders
14266
+ return /\/(constants|strings|@constants|@strings|data|@data|enums|@enums)(\/|\.)/i.test(normalizedPath)
13829
14267
  || /\/data\/(constants|strings)/i.test(normalizedPath);
13830
14268
  };
13831
14269
 
@@ -13838,8 +14276,8 @@ const noHardcodedStrings = {
13838
14276
 
13839
14277
  if (typeof importPath !== "string") return;
13840
14278
 
13841
- // Check if import is from constants/strings
13842
- const isFromConstants = /@?\/?(@?constants|@?strings|data\/constants|data\/strings)/i
14279
+ // Check if import is from constants/strings/data/enums
14280
+ const isFromConstants = /@?\/?(@?constants|@?strings|@?data|@?enums|data\/constants|data\/strings)/i
13843
14281
  .test(importPath);
13844
14282
 
13845
14283
  if (isFromConstants) {
@@ -13931,9 +14369,17 @@ const noHardcodedStrings = {
13931
14369
  if (current.type === "VariableDeclarator") {
13932
14370
  const varName = current.id && current.id.name;
13933
14371
 
13934
- // Check if variable name suggests it's a constants object
13935
- if (varName && /^[A-Z][A-Z0-9_]*$|CONSTANTS?|STRINGS?|MESSAGES?|LABELS?|TEXTS?/i.test(varName)) {
13936
- return true;
14372
+ if (varName) {
14373
+ // Check for SCREAMING_SNAKE_CASE (e.g., MY_CONSTANT, API_URL)
14374
+ if (/^[A-Z][A-Z0-9_]*$/.test(varName)) {
14375
+ return true;
14376
+ }
14377
+
14378
+ // Check for exact keywords or keywords at word boundaries (not in Handler names)
14379
+ // Match: MESSAGES, Messages, userMessages, but NOT longMessageHandler
14380
+ if (/^(constants?|strings?|messages?|labels?|texts?|data)$/i.test(varName)) {
14381
+ return true;
14382
+ }
13937
14383
  }
13938
14384
  }
13939
14385
 
@@ -13960,13 +14406,18 @@ const noHardcodedStrings = {
13960
14406
  JSXText(node) {
13961
14407
  const text = node.value.trim();
13962
14408
 
13963
- if (!text || shouldIgnoreStringHandler(text)) return;
14409
+ if (!text) return;
14410
+
14411
+ // Check if it's a flagged special string (status code, role name)
14412
+ const isSpecialString = isFlaggedSpecialStringHandler(text);
13964
14413
 
13965
- // Check if it looks like user-facing text (contains letters and spaces)
13966
- if (!/[a-zA-Z]/.test(text)) return;
14414
+ if (!isSpecialString && shouldIgnoreStringHandler(text)) return;
14415
+
14416
+ // Check if it looks like user-facing text (contains letters) - skip for special strings
14417
+ if (!isSpecialString && !/[a-zA-Z]/.test(text)) return;
13967
14418
 
13968
14419
  context.report({
13969
- message: `Hardcoded string "${text.substring(0, 30)}${text.length > 30 ? "..." : ""}" should be imported from constants/strings module`,
14420
+ message: getErrorMessageHandler(text, "JSX"),
13970
14421
  node,
13971
14422
  });
13972
14423
  },
@@ -13982,13 +14433,16 @@ const noHardcodedStrings = {
13982
14433
  if (expression.type === "Literal" && typeof expression.value === "string") {
13983
14434
  const str = expression.value;
13984
14435
 
13985
- if (shouldIgnoreStringHandler(str)) return;
14436
+ // Check if it's a flagged special string (status code, role name)
14437
+ const isSpecialString = isFlaggedSpecialStringHandler(str);
14438
+
14439
+ if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
13986
14440
 
13987
- // Check if it looks like user-facing text
13988
- if (!/[a-zA-Z]/.test(str)) return;
14441
+ // Check if it looks like user-facing text - skip for special strings
14442
+ if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
13989
14443
 
13990
14444
  context.report({
13991
- message: `Hardcoded string "${str.substring(0, 30)}${str.length > 30 ? "..." : ""}" should be imported from constants/strings module`,
14445
+ message: getErrorMessageHandler(str, "JSX expression"),
13992
14446
  node: expression,
13993
14447
  });
13994
14448
  }
@@ -13998,16 +14452,19 @@ const noHardcodedStrings = {
13998
14452
  expression.quasis.forEach((quasi) => {
13999
14453
  const str = quasi.value.cooked || quasi.value.raw;
14000
14454
 
14001
- if (shouldIgnoreStringHandler(str)) return;
14455
+ // Check if it's a flagged special string (status code, role name)
14456
+ const isSpecialString = isFlaggedSpecialStringHandler(str);
14002
14457
 
14003
- // Check if it contains user-facing text (more than just variable placeholders)
14004
- if (!/[a-zA-Z]{2,}/.test(str)) return;
14458
+ if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
14459
+
14460
+ // Check if it contains user-facing text - skip for special strings
14461
+ if (!isSpecialString && !/[a-zA-Z]{2,}/.test(str)) return;
14005
14462
 
14006
14463
  // Skip if it looks like a path or URL pattern
14007
14464
  if (/^[/.]|https?:\/\//.test(str)) return;
14008
14465
 
14009
14466
  context.report({
14010
- message: `Hardcoded string in template literal "${str.substring(0, 30)}${str.length > 30 ? "..." : ""}" should be imported from constants/strings module`,
14467
+ message: getErrorMessageHandler(str, "template literal"),
14011
14468
  node: quasi,
14012
14469
  });
14013
14470
  });
@@ -14034,15 +14491,19 @@ const noHardcodedStrings = {
14034
14491
  if (node.value.type === "Literal" && typeof node.value.value === "string") {
14035
14492
  const str = node.value.value;
14036
14493
 
14037
- if (shouldIgnoreStringHandler(str)) return;
14494
+ // Check if it's a flagged special string (status code, role name)
14495
+ const isSpecialString = isFlaggedSpecialStringHandler(str);
14496
+
14497
+ if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
14038
14498
 
14039
- // Check if it looks like user-facing text (contains letters and multiple words or is long)
14040
- if (!/[a-zA-Z]/.test(str)) return;
14499
+ // Check if it looks like user-facing text - skip for special strings
14500
+ if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
14041
14501
 
14042
- if (str.split(/\s+/).length < 2 && str.length < 10) return;
14502
+ // Require multiple words or reasonable length for non-special strings
14503
+ if (!isSpecialString && str.split(/\s+/).length < 2 && str.length < 10) return;
14043
14504
 
14044
14505
  context.report({
14045
- message: `Hardcoded string "${str.substring(0, 30)}${str.length > 30 ? "..." : ""}" in attribute "${attrName}" should be imported from constants/strings module`,
14506
+ message: getErrorMessageHandler(str, `attribute "${attrName}"`),
14046
14507
  node: node.value,
14047
14508
  });
14048
14509
  }
@@ -14057,14 +14518,18 @@ const noHardcodedStrings = {
14057
14518
  if (expression.type === "Literal" && typeof expression.value === "string") {
14058
14519
  const str = expression.value;
14059
14520
 
14060
- if (shouldIgnoreStringHandler(str)) return;
14521
+ // Check if it's a flagged special string (status code, role name)
14522
+ const isSpecialString = isFlaggedSpecialStringHandler(str);
14061
14523
 
14062
- if (!/[a-zA-Z]/.test(str)) return;
14524
+ if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
14063
14525
 
14064
- if (str.split(/\s+/).length < 2 && str.length < 10) return;
14526
+ if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
14527
+
14528
+ // Require multiple words or reasonable length for non-special strings
14529
+ if (!isSpecialString && str.split(/\s+/).length < 2 && str.length < 10) return;
14065
14530
 
14066
14531
  context.report({
14067
- message: `Hardcoded string "${str.substring(0, 30)}${str.length > 30 ? "..." : ""}" in attribute "${attrName}" should be imported from constants/strings module`,
14532
+ message: getErrorMessageHandler(str, `attribute "${attrName}"`),
14068
14533
  node: expression,
14069
14534
  });
14070
14535
  }
@@ -14078,8 +14543,11 @@ const noHardcodedStrings = {
14078
14543
 
14079
14544
  const str = node.value;
14080
14545
 
14081
- // Skip if it matches ignore patterns
14082
- if (shouldIgnoreStringHandler(str)) return;
14546
+ // Check if it's a flagged special string (status code, role name)
14547
+ const isSpecialString = isFlaggedSpecialStringHandler(str);
14548
+
14549
+ // Skip if it matches ignore patterns (but not special strings)
14550
+ if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
14083
14551
 
14084
14552
  // Skip if not in relevant context
14085
14553
  if (!isInRelevantContextHandler(node)) return;
@@ -14096,14 +14564,14 @@ const noHardcodedStrings = {
14096
14564
  // Skip object property keys
14097
14565
  if (node.parent.type === "Property" && node.parent.key === node) return;
14098
14566
 
14099
- // Skip if it doesn't look like user-facing text
14100
- if (!/[a-zA-Z]/.test(str)) return;
14567
+ // Skip if it doesn't look like user-facing text - but not for special strings
14568
+ if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
14101
14569
 
14102
- // Require at least 2 words or be reasonably long to be considered user-facing
14103
- if (str.split(/\s+/).length < 2 && str.length < 15) return;
14570
+ // Require at least 2 words or be reasonably long - but not for special strings
14571
+ if (!isSpecialString && str.split(/\s+/).length < 2 && str.length < 15) return;
14104
14572
 
14105
14573
  context.report({
14106
- message: `Hardcoded string "${str.substring(0, 30)}${str.length > 30 ? "..." : ""}" should be imported from constants/strings module`,
14574
+ message: getErrorMessageHandler(str),
14107
14575
  node,
14108
14576
  });
14109
14577
  },
@@ -14123,10 +14591,13 @@ const noHardcodedStrings = {
14123
14591
  node.quasis.forEach((quasi) => {
14124
14592
  const str = quasi.value.cooked || quasi.value.raw;
14125
14593
 
14126
- if (shouldIgnoreStringHandler(str)) return;
14594
+ // Check if it's a flagged special string (status code, role name)
14595
+ const isSpecialString = isFlaggedSpecialStringHandler(str);
14596
+
14597
+ if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
14127
14598
 
14128
- // Check if it contains substantial user-facing text
14129
- if (!/[a-zA-Z]{3,}/.test(str)) return;
14599
+ // Check if it contains substantial user-facing text - but not for special strings
14600
+ if (!isSpecialString && !/[a-zA-Z]{3,}/.test(str)) return;
14130
14601
 
14131
14602
  // Skip if it looks like a path, URL, or query
14132
14603
  if (/^[/.]|^https?:\/\/|^[?&]/.test(str)) return;
@@ -14135,7 +14606,7 @@ const noHardcodedStrings = {
14135
14606
  if (node.expressions.length > node.quasis.length) return;
14136
14607
 
14137
14608
  context.report({
14138
- message: `Hardcoded string in template literal "${str.substring(0, 30)}${str.length > 30 ? "..." : ""}" should be imported from constants/strings module`,
14609
+ message: getErrorMessageHandler(str, "template literal"),
14139
14610
  node: quasi,
14140
14611
  });
14141
14612
  });
@@ -19291,6 +19762,7 @@ export default {
19291
19762
  "ternary-condition-multiline": ternaryConditionMultiline,
19292
19763
 
19293
19764
  // Class rules
19765
+ "class-method-definition-format": classMethodDefinitionFormat,
19294
19766
  "class-naming-convention": classNamingConvention,
19295
19767
 
19296
19768
  // Function rules
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.8.4",
3
+ "version": "1.9.2",
4
4
  "description": "A custom ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",