eslint-plugin-code-style 1.8.3 → 1.9.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 +73 -1
- package/README.md +79 -9
- package/index.d.ts +2 -0
- package/index.js +438 -87
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,66 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.9.1] - 2026-02-03
|
|
11
|
+
|
|
12
|
+
### Enhanced
|
|
13
|
+
|
|
14
|
+
- **`no-hardcoded-strings`** - Now detects additional hardcoded values:
|
|
15
|
+
- HTTP status codes (4xx, 5xx like "404", "422", "500") with descriptive error message
|
|
16
|
+
- Role/permission names ("admin", "user", "moderator", etc.) with descriptive error message
|
|
17
|
+
- Added `@/data` as preferred import source for enums/objects
|
|
18
|
+
- Improved error messages to mention all valid import sources
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`no-hardcoded-strings`** - Fixed bug where `isInConstantsObjectHandler` incorrectly matched camelCase variable names due to case-insensitive regex flag
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## [1.9.0] - 2026-02-03
|
|
27
|
+
|
|
28
|
+
**New Rule: class-method-definition-format + Enhanced Spacing Rules**
|
|
29
|
+
|
|
30
|
+
**Version Range:** v1.8.1 → v1.9.0
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
**New Rules (1)**
|
|
35
|
+
- **`class-method-definition-format`** - Enforce consistent spacing in class and method definitions 🔧
|
|
36
|
+
- Space before opening brace `{` in class declarations
|
|
37
|
+
- No space between method name and opening parenthesis `(`
|
|
38
|
+
- Space before opening brace `{` in method definitions
|
|
39
|
+
- Opening brace must be on same line as class/method signature
|
|
40
|
+
|
|
41
|
+
### Enhanced
|
|
42
|
+
|
|
43
|
+
- **`function-call-spacing`** - Now also handles:
|
|
44
|
+
- `new` expressions (`new Class ()` → `new Class()`)
|
|
45
|
+
- Generic type calls (`get <Type>()` → `get<Type>()`)
|
|
46
|
+
- **`member-expression-bracket-spacing`** - Now also handles:
|
|
47
|
+
- TypeScript indexed access types (`Type ["prop"]` → `Type["prop"]`)
|
|
48
|
+
|
|
49
|
+
### Stats
|
|
50
|
+
|
|
51
|
+
- Total Rules: 71 (was 70)
|
|
52
|
+
- Auto-fixable: 64 rules 🔧 (was 63)
|
|
53
|
+
- Report-only: 7 rules
|
|
54
|
+
|
|
55
|
+
**Full Changelog:** [v1.8.1...v1.9.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.1...v1.9.0)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## [1.8.4] - 2026-02-03
|
|
60
|
+
|
|
61
|
+
### Documentation
|
|
62
|
+
|
|
63
|
+
- Fix v1.8.0 CHANGELOG entry to include Release Title, Version Range, and Full Changelog link (required for MINOR releases)
|
|
64
|
+
- Add missing comparison links in CHANGELOG.md (v1.3.9 through v1.8.3)
|
|
65
|
+
- Update manage-rule skill with CHANGELOG format requirements for new rules
|
|
66
|
+
- Update Current releases list in AGENTS.md to include v1.8.0
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
10
70
|
## [1.8.3] - 2026-02-03
|
|
11
71
|
|
|
12
72
|
### Fixed
|
|
@@ -41,9 +101,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
41
101
|
|
|
42
102
|
## [1.8.0] - 2026-02-03
|
|
43
103
|
|
|
104
|
+
**New Rule: no-hardcoded-strings**
|
|
105
|
+
|
|
106
|
+
**Version Range:** v1.7.1 → v1.8.0
|
|
107
|
+
|
|
44
108
|
### Added
|
|
45
109
|
|
|
46
|
-
|
|
110
|
+
**New Rules (1)**
|
|
111
|
+
- **`no-hardcoded-strings`** - Enforce importing strings from constants/strings modules instead of hardcoding them inline 🔧
|
|
47
112
|
- Detects hardcoded strings in JSX text content, attributes, and component logic
|
|
48
113
|
- Configurable `ignoreAttributes`, `extraIgnoreAttributes`, `ignorePatterns` options
|
|
49
114
|
- Automatically ignores technical strings (CSS units, URLs, paths, identifiers, etc.)
|
|
@@ -60,6 +125,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
60
125
|
- Auto-fixable: 63 rules 🔧
|
|
61
126
|
- Report-only: 7 rules (was 6)
|
|
62
127
|
|
|
128
|
+
**Full Changelog:** [v1.7.1...v1.8.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.1...v1.8.0)
|
|
129
|
+
|
|
63
130
|
---
|
|
64
131
|
|
|
65
132
|
## [1.7.6] - 2026-02-02
|
|
@@ -1193,6 +1260,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1193
1260
|
|
|
1194
1261
|
---
|
|
1195
1262
|
|
|
1263
|
+
[1.9.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.9.0...v1.9.1
|
|
1264
|
+
[1.9.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.4...v1.9.0
|
|
1265
|
+
[1.8.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.3...v1.8.4
|
|
1266
|
+
[1.8.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.2...v1.8.3
|
|
1267
|
+
[1.8.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.1...v1.8.2
|
|
1196
1268
|
[1.8.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.8.0...v1.8.1
|
|
1197
1269
|
[1.8.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.6...v1.8.0
|
|
1198
1270
|
[1.7.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.5...v1.7.6
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
**A powerful ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects.**
|
|
21
21
|
|
|
22
|
-
*
|
|
22
|
+
*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 **
|
|
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** —
|
|
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
|
-
**
|
|
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
|
-
> **
|
|
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,14 @@ 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:**
|
|
3431
|
+
- **HTTP status codes** (4xx, 5xx like "404", "500") — should be imported from `@/data` as enums/objects
|
|
3432
|
+
- **Role/permission names** ("admin", "user", "moderator", etc.) — should be imported from `@/data` as enums/objects
|
|
3433
|
+
|
|
3374
3434
|
**Options:**
|
|
3375
3435
|
|
|
3376
3436
|
| Option | Type | Default | Description |
|
|
@@ -3387,8 +3447,9 @@ const YetAnotherBad = ({ title }) => {
|
|
|
3387
3447
|
// ✅ Good — strings imported from constants
|
|
3388
3448
|
import { BUTTON_LABEL, ERROR_MESSAGE, welcomeText } from "@/constants";
|
|
3389
3449
|
import { FORM_LABELS } from "@/strings";
|
|
3450
|
+
import { HttpStatus, UserRole } from "@/data";
|
|
3390
3451
|
|
|
3391
|
-
const
|
|
3452
|
+
const ComponentHandler = () => (
|
|
3392
3453
|
<div>
|
|
3393
3454
|
<button>{BUTTON_LABEL}</button>
|
|
3394
3455
|
<span>{ERROR_MESSAGE}</span>
|
|
@@ -3396,7 +3457,11 @@ const Component = () => (
|
|
|
3396
3457
|
</div>
|
|
3397
3458
|
);
|
|
3398
3459
|
|
|
3399
|
-
const
|
|
3460
|
+
const getMessageHandler = () => ERROR_MESSAGE;
|
|
3461
|
+
|
|
3462
|
+
// ✅ Good — using enums for status codes and roles
|
|
3463
|
+
if (status === HttpStatus.NOT_FOUND) { ... }
|
|
3464
|
+
if (role === UserRole.ADMIN) { ... }
|
|
3400
3465
|
|
|
3401
3466
|
// ✅ Good — technical strings are allowed
|
|
3402
3467
|
<input type="text" className="input-field" />
|
|
@@ -3409,6 +3474,10 @@ const size = "100px";
|
|
|
3409
3474
|
<span>Something went wrong</span>
|
|
3410
3475
|
const message = "Welcome to the application";
|
|
3411
3476
|
return "User not found";
|
|
3477
|
+
|
|
3478
|
+
// ❌ Bad — hardcoded status codes and roles
|
|
3479
|
+
if (status === "404") { ... }
|
|
3480
|
+
if (role === "admin") { ... }
|
|
3412
3481
|
```
|
|
3413
3482
|
|
|
3414
3483
|
**Configuration example:**
|
|
@@ -3422,6 +3491,7 @@ return "User not found";
|
|
|
3422
3491
|
```
|
|
3423
3492
|
|
|
3424
3493
|
**Valid import paths for constants:**
|
|
3494
|
+
- `@/data` (preferred for enums/objects like HttpStatus, UserRole)
|
|
3425
3495
|
- `@/constants` or `@/@constants`
|
|
3426
3496
|
- `@/strings` or `@/@strings`
|
|
3427
3497
|
- `@/data/constants` or `@/data/strings`
|
|
@@ -3463,7 +3533,7 @@ const UseAuth = () => {}; // hooks should be camelCase
|
|
|
3463
3533
|
|
|
3464
3534
|
## 🔧 Auto-fixing
|
|
3465
3535
|
|
|
3466
|
-
|
|
3536
|
+
64 of 71 rules support auto-fixing. Run ESLint with the `--fix` flag:
|
|
3467
3537
|
|
|
3468
3538
|
```bash
|
|
3469
3539
|
# 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
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
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
|
-
|
|
1858
|
-
|
|
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
|
-
|
|
1861
|
-
|
|
1863
|
+
if (openAngle && openAngle.value === "<") {
|
|
1864
|
+
const textBeforeAngle = sourceCode.text.slice(calleeLastToken.range[1], openAngle.range[0]);
|
|
1862
1865
|
|
|
1863
|
-
|
|
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
|
-
//
|
|
1866
|
-
const
|
|
1878
|
+
// Also check space between closing > and opening (
|
|
1879
|
+
const closeAngle = sourceCode.getLastToken(typeArgs);
|
|
1880
|
+
const openParen = sourceCode.getTokenAfter(typeArgs);
|
|
1867
1881
|
|
|
1868
|
-
if (
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
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
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
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
|
-
|
|
9930
|
-
|
|
10151
|
+
if (!openBracket || openBracket.value !== "[") return;
|
|
10152
|
+
if (!closeBracket || closeBracket.value !== "]") return;
|
|
9931
10153
|
|
|
9932
|
-
|
|
9933
|
-
|
|
10154
|
+
// Check for space before [ (between object and bracket)
|
|
10155
|
+
const objectLastToken = sourceCode.getLastToken(objectPart);
|
|
9934
10156
|
|
|
9935
|
-
|
|
9936
|
-
const
|
|
10157
|
+
if (objectLastToken) {
|
|
10158
|
+
const textBeforeOpen = sourceCode.text.slice(objectLastToken.range[1], openBracket.range[0]);
|
|
9937
10159
|
|
|
9938
|
-
if (
|
|
10160
|
+
if (/\s/.test(textBeforeOpen)) {
|
|
9939
10161
|
context.report({
|
|
9940
10162
|
fix: (fixer) => fixer.replaceTextRange(
|
|
9941
|
-
[
|
|
10163
|
+
[objectLastToken.range[1], openBracket.range[0]],
|
|
9942
10164
|
"",
|
|
9943
10165
|
),
|
|
9944
|
-
message: "No space
|
|
10166
|
+
message: "No space before opening bracket in member expression",
|
|
9945
10167
|
node: openBracket,
|
|
9946
10168
|
});
|
|
9947
10169
|
}
|
|
10170
|
+
}
|
|
9948
10171
|
|
|
9949
|
-
|
|
9950
|
-
|
|
10172
|
+
// Check for space after [
|
|
10173
|
+
const textAfterOpen = sourceCode.text.slice(openBracket.range[1], indexPart.range[0]);
|
|
9951
10174
|
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
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,21 @@ 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:
|
|
13830
|
+
* - HTTP status codes (4xx, 5xx) that should be imported from @/data or enums
|
|
13831
|
+
* - Role/permission names (admin, user, moderator, etc.) that should be from @/data
|
|
13832
|
+
*
|
|
13833
|
+
* Valid import sources:
|
|
13834
|
+
* - @/data (enums, objects)
|
|
13835
|
+
* - @/strings
|
|
13836
|
+
* - @/constants
|
|
13837
|
+
* - @/@strings
|
|
13838
|
+
* - @/@constants
|
|
13839
|
+
*
|
|
13579
13840
|
* Options:
|
|
13580
13841
|
* { ignoreAttributes: ["className", "id", ...] } - JSX attributes to ignore (replaces defaults)
|
|
13581
13842
|
* { extraIgnoreAttributes: ["tooltip", ...] } - Additional JSX attributes to ignore (extends defaults)
|
|
@@ -13584,16 +13845,21 @@ const stringPropertySpacing = {
|
|
|
13584
13845
|
* ✓ Good:
|
|
13585
13846
|
* import { BUTTON_LABEL, ERROR_MESSAGE } from "@/constants";
|
|
13586
13847
|
* import { welcomeText } from "@/strings";
|
|
13848
|
+
* import { HttpStatus, UserRole } from "@/data";
|
|
13587
13849
|
*
|
|
13588
13850
|
* <button>{BUTTON_LABEL}</button>
|
|
13589
13851
|
* <span>{ERROR_MESSAGE}</span>
|
|
13590
13852
|
* const message = welcomeText;
|
|
13853
|
+
* if (status === HttpStatus.NOT_FOUND) { ... }
|
|
13854
|
+
* if (role === UserRole.ADMIN) { ... }
|
|
13591
13855
|
*
|
|
13592
13856
|
* ✗ Bad:
|
|
13593
13857
|
* <button>Submit</button>
|
|
13594
13858
|
* <span>Something went wrong</span>
|
|
13595
13859
|
* const message = "Welcome to the app";
|
|
13596
13860
|
* return "User not found";
|
|
13861
|
+
* if (status === "404") { ... } // HTTP status code
|
|
13862
|
+
* if (role === "admin") { ... } // Role name
|
|
13597
13863
|
*/
|
|
13598
13864
|
const noHardcodedStrings = {
|
|
13599
13865
|
create(context) {
|
|
@@ -13816,16 +14082,68 @@ const noHardcodedStrings = {
|
|
|
13816
14082
|
|
|
13817
14083
|
const allIgnorePatterns = [...technicalPatterns, ...extraIgnorePatterns];
|
|
13818
14084
|
|
|
13819
|
-
//
|
|
13820
|
-
const
|
|
14085
|
+
// HTTP status codes that should NOT be hardcoded (4xx and 5xx error codes)
|
|
14086
|
+
const httpStatusCodePattern = /^[45]\d{2}$/;
|
|
14087
|
+
|
|
14088
|
+
// Common role/permission names that should be imported from data/enums
|
|
14089
|
+
const rolePermissionNames = new Set([
|
|
14090
|
+
"admin",
|
|
14091
|
+
"administrator",
|
|
14092
|
+
"editor",
|
|
14093
|
+
"guest",
|
|
14094
|
+
"manager",
|
|
14095
|
+
"member",
|
|
14096
|
+
"moderator",
|
|
14097
|
+
"operator",
|
|
14098
|
+
"owner",
|
|
14099
|
+
"reviewer",
|
|
14100
|
+
"subscriber",
|
|
14101
|
+
"superadmin",
|
|
14102
|
+
"supervisor",
|
|
14103
|
+
"user",
|
|
14104
|
+
"viewer",
|
|
14105
|
+
]);
|
|
14106
|
+
|
|
14107
|
+
// Check if string looks like an HTTP status code
|
|
14108
|
+
const isHttpStatusCodeHandler = (str) => httpStatusCodePattern.test(str);
|
|
14109
|
+
|
|
14110
|
+
// Check if string is a role/permission name
|
|
14111
|
+
const isRoleNameHandler = (str) => rolePermissionNames.has(str.toLowerCase());
|
|
14112
|
+
|
|
14113
|
+
// Check if string should be flagged even if it matches technical patterns
|
|
14114
|
+
const isFlaggedSpecialStringHandler = (str) => isHttpStatusCodeHandler(str) || isRoleNameHandler(str);
|
|
14115
|
+
|
|
14116
|
+
// Get descriptive error message based on string type
|
|
14117
|
+
const getErrorMessageHandler = (str, context = "") => {
|
|
14118
|
+
const truncatedStr = str.length > 30 ? `${str.substring(0, 30)}...` : str;
|
|
14119
|
+
const contextPart = context ? ` in ${context}` : "";
|
|
14120
|
+
|
|
14121
|
+
if (isHttpStatusCodeHandler(str)) {
|
|
14122
|
+
return `Hardcoded HTTP status code "${truncatedStr}"${contextPart} should be imported from @/data (enum/object) or @/constants`;
|
|
14123
|
+
}
|
|
14124
|
+
|
|
14125
|
+
if (isRoleNameHandler(str)) {
|
|
14126
|
+
return `Hardcoded role/permission "${truncatedStr}"${contextPart} should be imported from @/data (enum/object) or @/constants`;
|
|
14127
|
+
}
|
|
14128
|
+
|
|
14129
|
+
return `Hardcoded string "${truncatedStr}"${contextPart} should be imported from @/data, @/strings, @/constants, or constants module`;
|
|
14130
|
+
};
|
|
14131
|
+
|
|
14132
|
+
// Check if a string matches any ignore pattern (but not if it's a flagged special string)
|
|
14133
|
+
const shouldIgnoreStringHandler = (str) => {
|
|
14134
|
+
// Always flag HTTP status codes and role names
|
|
14135
|
+
if (isFlaggedSpecialStringHandler(str)) return false;
|
|
14136
|
+
|
|
14137
|
+
return allIgnorePatterns.some((pattern) => pattern.test(str));
|
|
14138
|
+
};
|
|
13821
14139
|
|
|
13822
|
-
// Check if we're inside a constants/strings file
|
|
14140
|
+
// Check if we're inside a constants/strings/data file
|
|
13823
14141
|
const isConstantsFileHandler = () => {
|
|
13824
14142
|
const filename = context.filename || context.getFilename();
|
|
13825
14143
|
const normalizedPath = filename.replace(/\\/g, "/").toLowerCase();
|
|
13826
14144
|
|
|
13827
|
-
// Check if file is in constants/strings folders
|
|
13828
|
-
return /\/(constants|strings|@constants|@strings)(\/|\.)/i.test(normalizedPath)
|
|
14145
|
+
// Check if file is in constants/strings/data folders
|
|
14146
|
+
return /\/(constants|strings|@constants|@strings|data|@data)(\/|\.)/i.test(normalizedPath)
|
|
13829
14147
|
|| /\/data\/(constants|strings)/i.test(normalizedPath);
|
|
13830
14148
|
};
|
|
13831
14149
|
|
|
@@ -13838,8 +14156,8 @@ const noHardcodedStrings = {
|
|
|
13838
14156
|
|
|
13839
14157
|
if (typeof importPath !== "string") return;
|
|
13840
14158
|
|
|
13841
|
-
// Check if import is from constants/strings
|
|
13842
|
-
const isFromConstants = /@?\/?(@?constants|@?strings|data\/constants|data\/strings)/i
|
|
14159
|
+
// Check if import is from constants/strings/data
|
|
14160
|
+
const isFromConstants = /@?\/?(@?constants|@?strings|@?data|data\/constants|data\/strings)/i
|
|
13843
14161
|
.test(importPath);
|
|
13844
14162
|
|
|
13845
14163
|
if (isFromConstants) {
|
|
@@ -13931,9 +14249,16 @@ const noHardcodedStrings = {
|
|
|
13931
14249
|
if (current.type === "VariableDeclarator") {
|
|
13932
14250
|
const varName = current.id && current.id.name;
|
|
13933
14251
|
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
14252
|
+
if (varName) {
|
|
14253
|
+
// Check for SCREAMING_SNAKE_CASE (e.g., MY_CONSTANT, API_URL)
|
|
14254
|
+
if (/^[A-Z][A-Z0-9_]*$/.test(varName)) {
|
|
14255
|
+
return true;
|
|
14256
|
+
}
|
|
14257
|
+
|
|
14258
|
+
// Check for keywords that suggest constants (case-insensitive)
|
|
14259
|
+
if (/CONSTANTS?|STRINGS?|MESSAGES?|LABELS?|TEXTS?|DATA$/i.test(varName)) {
|
|
14260
|
+
return true;
|
|
14261
|
+
}
|
|
13937
14262
|
}
|
|
13938
14263
|
}
|
|
13939
14264
|
|
|
@@ -13960,13 +14285,18 @@ const noHardcodedStrings = {
|
|
|
13960
14285
|
JSXText(node) {
|
|
13961
14286
|
const text = node.value.trim();
|
|
13962
14287
|
|
|
13963
|
-
if (!text
|
|
14288
|
+
if (!text) return;
|
|
14289
|
+
|
|
14290
|
+
// Check if it's a flagged special string (status code, role name)
|
|
14291
|
+
const isSpecialString = isFlaggedSpecialStringHandler(text);
|
|
14292
|
+
|
|
14293
|
+
if (!isSpecialString && shouldIgnoreStringHandler(text)) return;
|
|
13964
14294
|
|
|
13965
|
-
// Check if it looks like user-facing text (contains letters
|
|
13966
|
-
if (!/[a-zA-Z]/.test(text)) return;
|
|
14295
|
+
// Check if it looks like user-facing text (contains letters) - skip for special strings
|
|
14296
|
+
if (!isSpecialString && !/[a-zA-Z]/.test(text)) return;
|
|
13967
14297
|
|
|
13968
14298
|
context.report({
|
|
13969
|
-
message:
|
|
14299
|
+
message: getErrorMessageHandler(text, "JSX"),
|
|
13970
14300
|
node,
|
|
13971
14301
|
});
|
|
13972
14302
|
},
|
|
@@ -13982,13 +14312,16 @@ const noHardcodedStrings = {
|
|
|
13982
14312
|
if (expression.type === "Literal" && typeof expression.value === "string") {
|
|
13983
14313
|
const str = expression.value;
|
|
13984
14314
|
|
|
13985
|
-
if (
|
|
14315
|
+
// Check if it's a flagged special string (status code, role name)
|
|
14316
|
+
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
13986
14317
|
|
|
13987
|
-
|
|
13988
|
-
|
|
14318
|
+
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14319
|
+
|
|
14320
|
+
// Check if it looks like user-facing text - skip for special strings
|
|
14321
|
+
if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
|
|
13989
14322
|
|
|
13990
14323
|
context.report({
|
|
13991
|
-
message:
|
|
14324
|
+
message: getErrorMessageHandler(str, "JSX expression"),
|
|
13992
14325
|
node: expression,
|
|
13993
14326
|
});
|
|
13994
14327
|
}
|
|
@@ -13998,16 +14331,19 @@ const noHardcodedStrings = {
|
|
|
13998
14331
|
expression.quasis.forEach((quasi) => {
|
|
13999
14332
|
const str = quasi.value.cooked || quasi.value.raw;
|
|
14000
14333
|
|
|
14001
|
-
if (
|
|
14334
|
+
// Check if it's a flagged special string (status code, role name)
|
|
14335
|
+
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14336
|
+
|
|
14337
|
+
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14002
14338
|
|
|
14003
|
-
// Check if it contains user-facing text
|
|
14004
|
-
if (!/[a-zA-Z]{2,}/.test(str)) return;
|
|
14339
|
+
// Check if it contains user-facing text - skip for special strings
|
|
14340
|
+
if (!isSpecialString && !/[a-zA-Z]{2,}/.test(str)) return;
|
|
14005
14341
|
|
|
14006
14342
|
// Skip if it looks like a path or URL pattern
|
|
14007
14343
|
if (/^[/.]|https?:\/\//.test(str)) return;
|
|
14008
14344
|
|
|
14009
14345
|
context.report({
|
|
14010
|
-
message:
|
|
14346
|
+
message: getErrorMessageHandler(str, "template literal"),
|
|
14011
14347
|
node: quasi,
|
|
14012
14348
|
});
|
|
14013
14349
|
});
|
|
@@ -14034,15 +14370,19 @@ const noHardcodedStrings = {
|
|
|
14034
14370
|
if (node.value.type === "Literal" && typeof node.value.value === "string") {
|
|
14035
14371
|
const str = node.value.value;
|
|
14036
14372
|
|
|
14037
|
-
if (
|
|
14373
|
+
// Check if it's a flagged special string (status code, role name)
|
|
14374
|
+
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14375
|
+
|
|
14376
|
+
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14038
14377
|
|
|
14039
|
-
// Check if it looks like user-facing text
|
|
14040
|
-
if (!/[a-zA-Z]/.test(str)) return;
|
|
14378
|
+
// Check if it looks like user-facing text - skip for special strings
|
|
14379
|
+
if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
|
|
14041
14380
|
|
|
14042
|
-
|
|
14381
|
+
// Require multiple words or reasonable length for non-special strings
|
|
14382
|
+
if (!isSpecialString && str.split(/\s+/).length < 2 && str.length < 10) return;
|
|
14043
14383
|
|
|
14044
14384
|
context.report({
|
|
14045
|
-
message:
|
|
14385
|
+
message: getErrorMessageHandler(str, `attribute "${attrName}"`),
|
|
14046
14386
|
node: node.value,
|
|
14047
14387
|
});
|
|
14048
14388
|
}
|
|
@@ -14057,14 +14397,18 @@ const noHardcodedStrings = {
|
|
|
14057
14397
|
if (expression.type === "Literal" && typeof expression.value === "string") {
|
|
14058
14398
|
const str = expression.value;
|
|
14059
14399
|
|
|
14060
|
-
if (
|
|
14400
|
+
// Check if it's a flagged special string (status code, role name)
|
|
14401
|
+
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14061
14402
|
|
|
14062
|
-
if (
|
|
14403
|
+
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14063
14404
|
|
|
14064
|
-
if (
|
|
14405
|
+
if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
|
|
14406
|
+
|
|
14407
|
+
// Require multiple words or reasonable length for non-special strings
|
|
14408
|
+
if (!isSpecialString && str.split(/\s+/).length < 2 && str.length < 10) return;
|
|
14065
14409
|
|
|
14066
14410
|
context.report({
|
|
14067
|
-
message:
|
|
14411
|
+
message: getErrorMessageHandler(str, `attribute "${attrName}"`),
|
|
14068
14412
|
node: expression,
|
|
14069
14413
|
});
|
|
14070
14414
|
}
|
|
@@ -14078,8 +14422,11 @@ const noHardcodedStrings = {
|
|
|
14078
14422
|
|
|
14079
14423
|
const str = node.value;
|
|
14080
14424
|
|
|
14081
|
-
//
|
|
14082
|
-
|
|
14425
|
+
// Check if it's a flagged special string (status code, role name)
|
|
14426
|
+
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14427
|
+
|
|
14428
|
+
// Skip if it matches ignore patterns (but not special strings)
|
|
14429
|
+
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14083
14430
|
|
|
14084
14431
|
// Skip if not in relevant context
|
|
14085
14432
|
if (!isInRelevantContextHandler(node)) return;
|
|
@@ -14096,14 +14443,14 @@ const noHardcodedStrings = {
|
|
|
14096
14443
|
// Skip object property keys
|
|
14097
14444
|
if (node.parent.type === "Property" && node.parent.key === node) return;
|
|
14098
14445
|
|
|
14099
|
-
// Skip if it doesn't look like user-facing text
|
|
14100
|
-
if (!/[a-zA-Z]/.test(str)) return;
|
|
14446
|
+
// Skip if it doesn't look like user-facing text - but not for special strings
|
|
14447
|
+
if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
|
|
14101
14448
|
|
|
14102
|
-
// Require at least 2 words or be reasonably long
|
|
14103
|
-
if (str.split(/\s+/).length < 2 && str.length < 15) return;
|
|
14449
|
+
// Require at least 2 words or be reasonably long - but not for special strings
|
|
14450
|
+
if (!isSpecialString && str.split(/\s+/).length < 2 && str.length < 15) return;
|
|
14104
14451
|
|
|
14105
14452
|
context.report({
|
|
14106
|
-
message:
|
|
14453
|
+
message: getErrorMessageHandler(str),
|
|
14107
14454
|
node,
|
|
14108
14455
|
});
|
|
14109
14456
|
},
|
|
@@ -14123,10 +14470,13 @@ const noHardcodedStrings = {
|
|
|
14123
14470
|
node.quasis.forEach((quasi) => {
|
|
14124
14471
|
const str = quasi.value.cooked || quasi.value.raw;
|
|
14125
14472
|
|
|
14126
|
-
if (
|
|
14473
|
+
// Check if it's a flagged special string (status code, role name)
|
|
14474
|
+
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14475
|
+
|
|
14476
|
+
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14127
14477
|
|
|
14128
|
-
// Check if it contains substantial user-facing text
|
|
14129
|
-
if (!/[a-zA-Z]{3,}/.test(str)) return;
|
|
14478
|
+
// Check if it contains substantial user-facing text - but not for special strings
|
|
14479
|
+
if (!isSpecialString && !/[a-zA-Z]{3,}/.test(str)) return;
|
|
14130
14480
|
|
|
14131
14481
|
// Skip if it looks like a path, URL, or query
|
|
14132
14482
|
if (/^[/.]|^https?:\/\/|^[?&]/.test(str)) return;
|
|
@@ -14135,7 +14485,7 @@ const noHardcodedStrings = {
|
|
|
14135
14485
|
if (node.expressions.length > node.quasis.length) return;
|
|
14136
14486
|
|
|
14137
14487
|
context.report({
|
|
14138
|
-
message:
|
|
14488
|
+
message: getErrorMessageHandler(str, "template literal"),
|
|
14139
14489
|
node: quasi,
|
|
14140
14490
|
});
|
|
14141
14491
|
});
|
|
@@ -19291,6 +19641,7 @@ export default {
|
|
|
19291
19641
|
"ternary-condition-multiline": ternaryConditionMultiline,
|
|
19292
19642
|
|
|
19293
19643
|
// Class rules
|
|
19644
|
+
"class-method-definition-format": classMethodDefinitionFormat,
|
|
19294
19645
|
"class-naming-convention": classNamingConvention,
|
|
19295
19646
|
|
|
19296
19647
|
// Function rules
|
package/package.json
CHANGED