eslint-plugin-code-style 1.11.1 → 1.11.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 +20 -23
- package/index.js +206 -243
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,19 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
## [1.11.
|
|
10
|
+
## [1.11.2] - 2026-02-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
- **`no-hardcoded-strings`**
|
|
15
|
+
- Skip strings inside style object expressions (CSS values like `radial-gradient(...)`, `rotate(90deg)`, etc.)
|
|
16
|
+
- Skip HTML input types (`text`, `password`, `email`, `number`, etc.)
|
|
17
|
+
- Add CSS function patterns (transform, gradient, animation) to ignore list
|
|
18
|
+
- Simplify error message to unified format: "should be imported from @/data, @/strings, @/constants, or @/enums"
|
|
19
|
+
- Remove forced flagging of status codes, roles, HTTP methods (user intent is ambiguous)
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
- **`ternary-condition-multiline`**
|
|
22
|
+
- Skip collapsing ternaries with JSX branches (JSX ternaries should stay multiline for readability)
|
|
23
|
+
|
|
24
|
+
- **`no-inline-type-definitions`**
|
|
25
|
+
- Skip union types with only built-in/native types (e.g., `Error | null`, `string | null`)
|
|
26
|
+
- Only flag unions with custom inline types like `{ user: string }`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## [1.11.1] - 2026-02-03
|
|
15
31
|
|
|
16
32
|
### Fixed
|
|
17
33
|
|
|
18
34
|
- **`component-props-inline-type`** - Single-property type annotations spanning multiple lines now auto-fix to single line format `{ prop: Type }`
|
|
19
35
|
- **`function-params-per-line`** - Normalize single-member type annotations to prevent circular fix conflicts
|
|
20
36
|
|
|
21
|
-
**Full Changelog:** [v1.11.0...v1.11.1](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.11.0...v1.11.1)
|
|
22
|
-
|
|
23
37
|
---
|
|
24
38
|
|
|
25
39
|
## [1.11.0] - 2026-02-03
|
|
@@ -57,25 +71,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
57
71
|
|
|
58
72
|
## [1.10.3] - 2026-02-03
|
|
59
73
|
|
|
60
|
-
**Bug Fixes: className template literals and trailing comma removal**
|
|
61
|
-
|
|
62
|
-
**Version Range:** v1.10.2 → v1.10.3
|
|
63
|
-
|
|
64
74
|
### Fixed
|
|
65
75
|
|
|
66
76
|
- **`no-hardcoded-strings`** - Skip template literals inside className/style attributes (Tailwind classes in template literals)
|
|
67
77
|
- **`component-props-inline-type`** - Auto-fix to REMOVE trailing comma for single property (not just skip adding it)
|
|
68
78
|
|
|
69
|
-
**Full Changelog:** [v1.10.2...v1.10.3](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.2...v1.10.3)
|
|
70
|
-
|
|
71
79
|
---
|
|
72
80
|
|
|
73
81
|
## [1.10.2] - 2026-02-03
|
|
74
82
|
|
|
75
|
-
**Bug Fixes: component-props-inline-type and no-hardcoded-strings**
|
|
76
|
-
|
|
77
|
-
**Version Range:** v1.10.1 → v1.10.2
|
|
78
|
-
|
|
79
83
|
### Fixed
|
|
80
84
|
|
|
81
85
|
- **`component-props-inline-type`** - Don't require trailing comma for single property in inline type definitions
|
|
@@ -85,23 +89,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
85
89
|
- **`no-hardcoded-strings`** - Skip CSS property values (cursor: pointer, display: flex, position: absolute, etc.)
|
|
86
90
|
- **`no-hardcoded-strings`** - Skip SVG filter result identifiers (BackgroundImageFix, SourceGraphic, etc.)
|
|
87
91
|
|
|
88
|
-
**Full Changelog:** [v1.10.1...v1.10.2](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.1...v1.10.2)
|
|
89
|
-
|
|
90
92
|
---
|
|
91
93
|
|
|
92
94
|
## [1.10.1] - 2026-02-03
|
|
93
95
|
|
|
94
|
-
**Bug Fix: logical-expression-multiline rule improvements**
|
|
95
|
-
|
|
96
|
-
**Version Range:** v1.10.0 → v1.10.1
|
|
97
|
-
|
|
98
96
|
### Fixed
|
|
99
97
|
|
|
100
98
|
- **`logical-expression-multiline`** - Add collapse to single line for simple expressions (≤3 operands)
|
|
101
99
|
- **`logical-expression-multiline`** - Skip collapsing when any operand is multiline (e.g., JSX elements)
|
|
102
100
|
|
|
103
|
-
**Full Changelog:** [v1.10.0...v1.10.1](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.0...v1.10.1)
|
|
104
|
-
|
|
105
101
|
---
|
|
106
102
|
|
|
107
103
|
## [1.10.0] - 2026-02-03
|
|
@@ -1476,6 +1472,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1476
1472
|
|
|
1477
1473
|
---
|
|
1478
1474
|
|
|
1475
|
+
[1.11.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.11.1...v1.11.2
|
|
1479
1476
|
[1.11.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.11.0...v1.11.1
|
|
1480
1477
|
[1.11.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.3...v1.11.0
|
|
1481
1478
|
[1.10.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.2...v1.10.3
|
package/index.js
CHANGED
|
@@ -4734,6 +4734,25 @@ const ternaryConditionMultiline = {
|
|
|
4734
4734
|
return lineText.match(/^\s*/)[0].length;
|
|
4735
4735
|
};
|
|
4736
4736
|
|
|
4737
|
+
// Check if a node contains JSX elements (recursively)
|
|
4738
|
+
const containsJsxHandler = (n) => {
|
|
4739
|
+
if (!n) return false;
|
|
4740
|
+
|
|
4741
|
+
if (n.type === "JSXElement" || n.type === "JSXFragment") return true;
|
|
4742
|
+
|
|
4743
|
+
if (n.type === "ParenthesizedExpression") return containsJsxHandler(n.expression);
|
|
4744
|
+
|
|
4745
|
+
if (n.type === "ConditionalExpression") {
|
|
4746
|
+
return containsJsxHandler(n.consequent) || containsJsxHandler(n.alternate);
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4749
|
+
if (n.type === "LogicalExpression") {
|
|
4750
|
+
return containsJsxHandler(n.left) || containsJsxHandler(n.right);
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
return false;
|
|
4754
|
+
};
|
|
4755
|
+
|
|
4737
4756
|
// Check if branches have complex objects (should stay multiline)
|
|
4738
4757
|
const hasComplexObjectHandler = (n) => {
|
|
4739
4758
|
if (n.type === "ObjectExpression" && n.properties.length >= 2) return true;
|
|
@@ -4743,6 +4762,9 @@ const ternaryConditionMultiline = {
|
|
|
4743
4762
|
return false;
|
|
4744
4763
|
};
|
|
4745
4764
|
|
|
4765
|
+
// Check if branches contain JSX (should stay multiline)
|
|
4766
|
+
const hasJsxBranchesHandler = (node) => containsJsxHandler(node.consequent) || containsJsxHandler(node.alternate);
|
|
4767
|
+
|
|
4746
4768
|
// Check if a nested ternary has complex condition (>maxOperands)
|
|
4747
4769
|
const hasComplexNestedTernaryHandler = (node) => {
|
|
4748
4770
|
const checkBranch = (branch) => {
|
|
@@ -4794,6 +4816,11 @@ const ternaryConditionMultiline = {
|
|
|
4794
4816
|
return false;
|
|
4795
4817
|
}
|
|
4796
4818
|
|
|
4819
|
+
// Skip ternaries with JSX branches (should stay multiline for readability)
|
|
4820
|
+
if (hasJsxBranchesHandler(node)) {
|
|
4821
|
+
return false;
|
|
4822
|
+
}
|
|
4823
|
+
|
|
4797
4824
|
// Skip parenthesized nested ternaries with complex condition (>maxOperands)
|
|
4798
4825
|
// These should be formatted manually or stay as-is
|
|
4799
4826
|
if (hasComplexNestedTernaryHandler(node)) {
|
|
@@ -4968,9 +4995,14 @@ const ternaryConditionMultiline = {
|
|
|
4968
4995
|
return; // Don't collapse - nested group needs multiline
|
|
4969
4996
|
}
|
|
4970
4997
|
|
|
4971
|
-
// Skip if branches have complex objects
|
|
4998
|
+
// Skip if branches have complex objects or JSX elements
|
|
4972
4999
|
const hasComplexBranches = hasComplexObjectHandler(node.consequent) || hasComplexObjectHandler(node.alternate);
|
|
4973
5000
|
|
|
5001
|
+
// Skip ternaries with JSX branches (should stay multiline for readability)
|
|
5002
|
+
if (hasJsxBranchesHandler(node)) {
|
|
5003
|
+
return;
|
|
5004
|
+
}
|
|
5005
|
+
|
|
4974
5006
|
// Skip unparenthesized nested ternaries (parenthesized ones count as 1 operand)
|
|
4975
5007
|
const hasUnparenthesizedNestedTernary = (node.consequent.type === "ConditionalExpression" && !isParenthesizedHandler(node.consequent))
|
|
4976
5008
|
|| (node.alternate.type === "ConditionalExpression" && !isParenthesizedHandler(node.alternate));
|
|
@@ -14455,6 +14487,24 @@ const noHardcodedStrings = {
|
|
|
14455
14487
|
/^&[a-z]+;$/,
|
|
14456
14488
|
// Punctuation only
|
|
14457
14489
|
/^[.!?,;:'"()\[\]{}]+$/,
|
|
14490
|
+
// CSS transform functions: rotate(), translate(), scale(), skew(), matrix(), etc.
|
|
14491
|
+
/^(rotate|translate|translateX|translateY|translateZ|translate3d|scale|scaleX|scaleY|scaleZ|scale3d|skew|skewX|skewY|matrix|matrix3d|perspective)\(.+\)$/,
|
|
14492
|
+
// CSS transform values with multiple functions: "rotate(90deg) scaleX(-1)"
|
|
14493
|
+
/^(rotate|translate|translateX|translateY|scale|scaleX|scaleY|skew|skewX|skewY|matrix)\([^)]+\)(\s+(rotate|translate|translateX|translateY|scale|scaleX|scaleY|skew|skewX|skewY|matrix)\([^)]+\))+$/,
|
|
14494
|
+
// CSS gradient functions
|
|
14495
|
+
/^(linear-gradient|radial-gradient|conic-gradient|repeating-linear-gradient|repeating-radial-gradient)\(.+\)$/,
|
|
14496
|
+
// CSS animation shorthand: "spin 2s linear infinite"
|
|
14497
|
+
/^[a-zA-Z][\w-]*\s+[\d.]+m?s\s+[\w-]+(\s+[\w-]+)*$/,
|
|
14498
|
+
// CSS transform-origin values: "50% 50%", "center center", "top left"
|
|
14499
|
+
/^(\d+%|center|top|bottom|left|right)(\s+(\d+%|center|top|bottom|left|right))?$/,
|
|
14500
|
+
// CSS calc() function
|
|
14501
|
+
/^calc\(.+\)$/,
|
|
14502
|
+
// CSS var() function
|
|
14503
|
+
/^var\(.+\)$/,
|
|
14504
|
+
// CSS clamp() function
|
|
14505
|
+
/^clamp\(.+\)$/,
|
|
14506
|
+
// CSS min/max functions
|
|
14507
|
+
/^(min|max)\(.+\)$/,
|
|
14458
14508
|
];
|
|
14459
14509
|
|
|
14460
14510
|
const extraIgnorePatterns = (options.ignorePatterns || []).map((p) => {
|
|
@@ -14528,223 +14578,73 @@ const noHardcodedStrings = {
|
|
|
14528
14578
|
// UI component patterns - only ignored in JSX attributes, not in logic
|
|
14529
14579
|
const uiComponentPattern = /^(primary|secondary|tertiary|ghost|outline|link|muted|danger|warning|info|success|error|default|subtle|solid|soft|plain|flat|elevated|filled|tonal|text|contained|standard|xs|sm|md|lg|xl|2xl|3xl|4xl|5xl|xxs|xxl|small|medium|large|tiny|huge|compact|comfortable|spacious|left|right|center|top|bottom|start|end|middle|baseline|stretch|between|around|evenly|horizontal|vertical|row|column|inline|block|flex|grid|auto|none|hidden|visible|static|relative|absolute|fixed|sticky|on|off|hover|focus|click|blur|always|never)$/;
|
|
14530
14580
|
|
|
14531
|
-
//
|
|
14532
|
-
const
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
"
|
|
14537
|
-
"
|
|
14538
|
-
"
|
|
14539
|
-
"
|
|
14540
|
-
"
|
|
14541
|
-
"
|
|
14542
|
-
"
|
|
14543
|
-
"
|
|
14544
|
-
"
|
|
14545
|
-
"
|
|
14546
|
-
"
|
|
14547
|
-
"
|
|
14548
|
-
"
|
|
14549
|
-
"
|
|
14550
|
-
"
|
|
14581
|
+
// HTML input types - standard browser input types, not hardcoded strings
|
|
14582
|
+
const htmlInputTypes = new Set([
|
|
14583
|
+
"button",
|
|
14584
|
+
"checkbox",
|
|
14585
|
+
"color",
|
|
14586
|
+
"date",
|
|
14587
|
+
"datetime-local",
|
|
14588
|
+
"email",
|
|
14589
|
+
"file",
|
|
14590
|
+
"hidden",
|
|
14591
|
+
"image",
|
|
14592
|
+
"month",
|
|
14593
|
+
"number",
|
|
14594
|
+
"password",
|
|
14595
|
+
"radio",
|
|
14596
|
+
"range",
|
|
14597
|
+
"reset",
|
|
14598
|
+
"search",
|
|
14599
|
+
"submit",
|
|
14600
|
+
"tel",
|
|
14601
|
+
"text",
|
|
14602
|
+
"time",
|
|
14603
|
+
"url",
|
|
14604
|
+
"week",
|
|
14551
14605
|
]);
|
|
14552
14606
|
|
|
14553
|
-
//
|
|
14554
|
-
const
|
|
14555
|
-
"CONNECT",
|
|
14556
|
-
"DELETE",
|
|
14557
|
-
"GET",
|
|
14558
|
-
"HEAD",
|
|
14559
|
-
"OPTIONS",
|
|
14560
|
-
"PATCH",
|
|
14561
|
-
"POST",
|
|
14562
|
-
"PUT",
|
|
14563
|
-
"TRACE",
|
|
14564
|
-
]);
|
|
14607
|
+
// Check if string is an HTML input type
|
|
14608
|
+
const isHtmlInputTypeHandler = (str) => htmlInputTypes.has(str.toLowerCase());
|
|
14565
14609
|
|
|
14566
|
-
//
|
|
14567
|
-
const
|
|
14568
|
-
|
|
14569
|
-
"development",
|
|
14570
|
-
"local",
|
|
14571
|
-
"prod",
|
|
14572
|
-
"production",
|
|
14573
|
-
"qa",
|
|
14574
|
-
"sandbox",
|
|
14575
|
-
"staging",
|
|
14576
|
-
"test",
|
|
14577
|
-
"testing",
|
|
14578
|
-
"uat",
|
|
14579
|
-
]);
|
|
14610
|
+
// Check if node is inside a style object expression (style={{ ... }})
|
|
14611
|
+
const isInsideStyleObjectHandler = (node) => {
|
|
14612
|
+
let current = node.parent;
|
|
14580
14613
|
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
"fatal",
|
|
14586
|
-
"info",
|
|
14587
|
-
"log",
|
|
14588
|
-
"trace",
|
|
14589
|
-
"warn",
|
|
14590
|
-
"warning",
|
|
14591
|
-
]);
|
|
14614
|
+
while (current) {
|
|
14615
|
+
// Check if we're in a Property inside an ObjectExpression inside a JSXAttribute named "style"
|
|
14616
|
+
if (current.type === "Property" && current.parent && current.parent.type === "ObjectExpression") {
|
|
14617
|
+
const objExpr = current.parent;
|
|
14592
14618
|
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
"accepted",
|
|
14596
|
-
"active",
|
|
14597
|
-
"approved",
|
|
14598
|
-
"archived",
|
|
14599
|
-
"blocked",
|
|
14600
|
-
"cancelled",
|
|
14601
|
-
"closed",
|
|
14602
|
-
"completed",
|
|
14603
|
-
"declined",
|
|
14604
|
-
"deleted",
|
|
14605
|
-
"disabled",
|
|
14606
|
-
"done",
|
|
14607
|
-
"draft",
|
|
14608
|
-
"enabled",
|
|
14609
|
-
"expired",
|
|
14610
|
-
"failed",
|
|
14611
|
-
"finished",
|
|
14612
|
-
"inactive",
|
|
14613
|
-
"inprogress",
|
|
14614
|
-
"open",
|
|
14615
|
-
"paused",
|
|
14616
|
-
"pending",
|
|
14617
|
-
"processing",
|
|
14618
|
-
"published",
|
|
14619
|
-
"queued",
|
|
14620
|
-
"ready",
|
|
14621
|
-
"rejected",
|
|
14622
|
-
"resolved",
|
|
14623
|
-
"running",
|
|
14624
|
-
"scheduled",
|
|
14625
|
-
"started",
|
|
14626
|
-
"stopped",
|
|
14627
|
-
"submitted",
|
|
14628
|
-
"success",
|
|
14629
|
-
"successful",
|
|
14630
|
-
"suspended",
|
|
14631
|
-
"verified",
|
|
14632
|
-
"waiting",
|
|
14633
|
-
]);
|
|
14619
|
+
if (objExpr.parent && objExpr.parent.type === "JSXExpressionContainer") {
|
|
14620
|
+
const jsxExprContainer = objExpr.parent;
|
|
14634
14621
|
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
"empty",
|
|
14638
|
-
"invalid",
|
|
14639
|
-
"missing",
|
|
14640
|
-
"optional",
|
|
14641
|
-
"required",
|
|
14642
|
-
"valid",
|
|
14643
|
-
]);
|
|
14622
|
+
if (jsxExprContainer.parent && jsxExprContainer.parent.type === "JSXAttribute") {
|
|
14623
|
+
const attrName = jsxExprContainer.parent.name && jsxExprContainer.parent.name.name;
|
|
14644
14624
|
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
"authed",
|
|
14650
|
-
"authorized",
|
|
14651
|
-
"denied",
|
|
14652
|
-
"expired",
|
|
14653
|
-
"forbidden",
|
|
14654
|
-
"granted",
|
|
14655
|
-
"locked",
|
|
14656
|
-
"loggedin",
|
|
14657
|
-
"loggedout",
|
|
14658
|
-
"revoked",
|
|
14659
|
-
"unauthenticated",
|
|
14660
|
-
"unauthorized",
|
|
14661
|
-
"unlocked",
|
|
14662
|
-
"unverified",
|
|
14663
|
-
"verified",
|
|
14664
|
-
]);
|
|
14625
|
+
if (attrName === "style") return true;
|
|
14626
|
+
}
|
|
14627
|
+
}
|
|
14628
|
+
}
|
|
14665
14629
|
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
"critical",
|
|
14669
|
-
"high",
|
|
14670
|
-
"highest",
|
|
14671
|
-
"low",
|
|
14672
|
-
"lowest",
|
|
14673
|
-
"medium",
|
|
14674
|
-
"normal",
|
|
14675
|
-
"urgent",
|
|
14676
|
-
]);
|
|
14630
|
+
current = current.parent;
|
|
14631
|
+
}
|
|
14677
14632
|
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
const isEnvironmentNameHandler = (str) => environmentNames.has(str.toLowerCase());
|
|
14683
|
-
const isLogLevelHandler = (str) => logLevels.has(str.toLowerCase());
|
|
14684
|
-
const isStatusStringHandler = (str) => statusStrings.has(str.toLowerCase());
|
|
14685
|
-
const isPriorityLevelHandler = (str) => priorityLevels.has(str.toLowerCase());
|
|
14686
|
-
const isValidationStringHandler = (str) => validationStrings.has(str.toLowerCase());
|
|
14687
|
-
const isAuthStringHandler = (str) => authStrings.has(str.toLowerCase());
|
|
14688
|
-
|
|
14689
|
-
// Check if string should be flagged even if it matches technical patterns
|
|
14690
|
-
const isFlaggedSpecialStringHandler = (str) => isHttpStatusCodeHandler(str)
|
|
14691
|
-
|| isRoleNameHandler(str)
|
|
14692
|
-
|| isHttpMethodHandler(str)
|
|
14693
|
-
|| isEnvironmentNameHandler(str)
|
|
14694
|
-
|| isLogLevelHandler(str)
|
|
14695
|
-
|| isStatusStringHandler(str)
|
|
14696
|
-
|| isPriorityLevelHandler(str)
|
|
14697
|
-
|| isValidationStringHandler(str)
|
|
14698
|
-
|| isAuthStringHandler(str);
|
|
14699
|
-
|
|
14700
|
-
// Get descriptive error message based on string type
|
|
14633
|
+
return false;
|
|
14634
|
+
};
|
|
14635
|
+
|
|
14636
|
+
// Get descriptive error message - unified message for all hardcoded strings
|
|
14701
14637
|
const getErrorMessageHandler = (str, context = "") => {
|
|
14702
14638
|
const truncatedStr = str.length > 30 ? `${str.substring(0, 30)}...` : str;
|
|
14703
14639
|
const contextPart = context ? ` in ${context}` : "";
|
|
14704
14640
|
|
|
14705
|
-
|
|
14706
|
-
return `Hardcoded HTTP status code "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14707
|
-
}
|
|
14708
|
-
|
|
14709
|
-
if (isRoleNameHandler(str)) {
|
|
14710
|
-
return `Hardcoded role/permission "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14711
|
-
}
|
|
14712
|
-
|
|
14713
|
-
if (isHttpMethodHandler(str)) {
|
|
14714
|
-
return `Hardcoded HTTP method "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14715
|
-
}
|
|
14716
|
-
|
|
14717
|
-
if (isEnvironmentNameHandler(str)) {
|
|
14718
|
-
return `Hardcoded environment name "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14719
|
-
}
|
|
14720
|
-
|
|
14721
|
-
if (isLogLevelHandler(str)) {
|
|
14722
|
-
return `Hardcoded log level "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14723
|
-
}
|
|
14724
|
-
|
|
14725
|
-
if (isStatusStringHandler(str)) {
|
|
14726
|
-
return `Hardcoded status "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14727
|
-
}
|
|
14728
|
-
|
|
14729
|
-
if (isPriorityLevelHandler(str)) {
|
|
14730
|
-
return `Hardcoded priority level "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14731
|
-
}
|
|
14732
|
-
|
|
14733
|
-
if (isValidationStringHandler(str)) {
|
|
14734
|
-
return `Hardcoded validation string "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14735
|
-
}
|
|
14736
|
-
|
|
14737
|
-
if (isAuthStringHandler(str)) {
|
|
14738
|
-
return `Hardcoded auth state "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14739
|
-
}
|
|
14740
|
-
|
|
14741
|
-
return `Hardcoded string "${truncatedStr}"${contextPart} should be imported from @/data or @/strings or @/constants or @/@constants or @/@strings`;
|
|
14641
|
+
return `Hardcoded string "${truncatedStr}"${contextPart} should be imported from @/data, @/strings, @/constants, or @/enums`;
|
|
14742
14642
|
};
|
|
14743
14643
|
|
|
14744
|
-
// Check if a string matches any ignore pattern
|
|
14644
|
+
// Check if a string matches any ignore pattern
|
|
14745
14645
|
const shouldIgnoreStringHandler = (str) => {
|
|
14746
|
-
//
|
|
14747
|
-
if (
|
|
14646
|
+
// Skip HTML input types (text, password, email, etc.)
|
|
14647
|
+
if (isHtmlInputTypeHandler(str)) return true;
|
|
14748
14648
|
|
|
14749
14649
|
// Skip Tailwind/CSS class strings
|
|
14750
14650
|
if (isTailwindClassStringHandler(str)) return true;
|
|
@@ -14931,13 +14831,10 @@ const noHardcodedStrings = {
|
|
|
14931
14831
|
|
|
14932
14832
|
if (!text) return;
|
|
14933
14833
|
|
|
14934
|
-
|
|
14935
|
-
const isSpecialString = isFlaggedSpecialStringHandler(text);
|
|
14834
|
+
if (shouldIgnoreStringHandler(text)) return;
|
|
14936
14835
|
|
|
14937
|
-
if
|
|
14938
|
-
|
|
14939
|
-
// Check if it looks like user-facing text (contains letters) - skip for special strings
|
|
14940
|
-
if (!isSpecialString && !/[a-zA-Z]/.test(text)) return;
|
|
14836
|
+
// Check if it looks like user-facing text (contains letters)
|
|
14837
|
+
if (!/[a-zA-Z]/.test(text)) return;
|
|
14941
14838
|
|
|
14942
14839
|
context.report({
|
|
14943
14840
|
message: getErrorMessageHandler(text, "JSX"),
|
|
@@ -14968,13 +14865,10 @@ const noHardcodedStrings = {
|
|
|
14968
14865
|
if (expression.type === "Literal" && typeof expression.value === "string") {
|
|
14969
14866
|
const str = expression.value;
|
|
14970
14867
|
|
|
14971
|
-
|
|
14972
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14973
|
-
|
|
14974
|
-
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14868
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
14975
14869
|
|
|
14976
|
-
// Check if it looks like user-facing text
|
|
14977
|
-
if (
|
|
14870
|
+
// Check if it looks like user-facing text
|
|
14871
|
+
if (!/[a-zA-Z]/.test(str)) return;
|
|
14978
14872
|
|
|
14979
14873
|
context.report({
|
|
14980
14874
|
message: getErrorMessageHandler(str, "JSX expression"),
|
|
@@ -14987,13 +14881,10 @@ const noHardcodedStrings = {
|
|
|
14987
14881
|
expression.quasis.forEach((quasi) => {
|
|
14988
14882
|
const str = quasi.value.cooked || quasi.value.raw;
|
|
14989
14883
|
|
|
14990
|
-
|
|
14991
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14992
|
-
|
|
14993
|
-
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14884
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
14994
14885
|
|
|
14995
|
-
// Check if it contains user-facing text
|
|
14996
|
-
if (
|
|
14886
|
+
// Check if it contains user-facing text
|
|
14887
|
+
if (!/[a-zA-Z]{2,}/.test(str)) return;
|
|
14997
14888
|
|
|
14998
14889
|
// Skip if it looks like a path or URL pattern
|
|
14999
14890
|
if (/^[/.]|https?:\/\//.test(str)) return;
|
|
@@ -15029,13 +14920,10 @@ const noHardcodedStrings = {
|
|
|
15029
14920
|
// Skip UI component patterns in JSX attributes (variant, size, position props)
|
|
15030
14921
|
if (uiComponentPattern.test(str)) return;
|
|
15031
14922
|
|
|
15032
|
-
|
|
15033
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14923
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
15034
14924
|
|
|
15035
|
-
if
|
|
15036
|
-
|
|
15037
|
-
// Check if it looks like user-facing text - skip for special strings
|
|
15038
|
-
if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
|
|
14925
|
+
// Check if it looks like user-facing text
|
|
14926
|
+
if (!/[a-zA-Z]/.test(str)) return;
|
|
15039
14927
|
|
|
15040
14928
|
context.report({
|
|
15041
14929
|
message: getErrorMessageHandler(str, `attribute "${attrName}"`),
|
|
@@ -15056,12 +14944,9 @@ const noHardcodedStrings = {
|
|
|
15056
14944
|
// Skip UI component patterns in JSX attributes (variant, size, position props)
|
|
15057
14945
|
if (uiComponentPattern.test(str)) return;
|
|
15058
14946
|
|
|
15059
|
-
|
|
15060
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
15061
|
-
|
|
15062
|
-
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14947
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
15063
14948
|
|
|
15064
|
-
if (
|
|
14949
|
+
if (!/[a-zA-Z]/.test(str)) return;
|
|
15065
14950
|
|
|
15066
14951
|
context.report({
|
|
15067
14952
|
message: getErrorMessageHandler(str, `attribute "${attrName}"`),
|
|
@@ -15078,11 +14963,11 @@ const noHardcodedStrings = {
|
|
|
15078
14963
|
|
|
15079
14964
|
const str = node.value;
|
|
15080
14965
|
|
|
15081
|
-
//
|
|
15082
|
-
|
|
14966
|
+
// Skip if it matches ignore patterns
|
|
14967
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
15083
14968
|
|
|
15084
|
-
// Skip if
|
|
15085
|
-
if (
|
|
14969
|
+
// Skip if inside a style object (style={{ transform: "..." }})
|
|
14970
|
+
if (isInsideStyleObjectHandler(node)) return;
|
|
15086
14971
|
|
|
15087
14972
|
// Skip if not in relevant context
|
|
15088
14973
|
if (!isInRelevantContextHandler(node)) return;
|
|
@@ -15099,8 +14984,8 @@ const noHardcodedStrings = {
|
|
|
15099
14984
|
// Skip object property keys
|
|
15100
14985
|
if (node.parent.type === "Property" && node.parent.key === node) return;
|
|
15101
14986
|
|
|
15102
|
-
// Skip if it doesn't look like user-facing text
|
|
15103
|
-
if (
|
|
14987
|
+
// Skip if it doesn't look like user-facing text
|
|
14988
|
+
if (!/[a-zA-Z]/.test(str)) return;
|
|
15104
14989
|
|
|
15105
14990
|
context.report({
|
|
15106
14991
|
message: getErrorMessageHandler(str),
|
|
@@ -15113,6 +14998,9 @@ const noHardcodedStrings = {
|
|
|
15113
14998
|
// Skip if in JSX (handled separately)
|
|
15114
14999
|
if (node.parent.type === "JSXExpressionContainer") return;
|
|
15115
15000
|
|
|
15001
|
+
// Skip if inside a style object (style={{ background: `...` }})
|
|
15002
|
+
if (isInsideStyleObjectHandler(node)) return;
|
|
15003
|
+
|
|
15116
15004
|
// Skip if not in relevant context
|
|
15117
15005
|
if (!isInRelevantContextHandler(node)) return;
|
|
15118
15006
|
|
|
@@ -15123,13 +15011,10 @@ const noHardcodedStrings = {
|
|
|
15123
15011
|
node.quasis.forEach((quasi) => {
|
|
15124
15012
|
const str = quasi.value.cooked || quasi.value.raw;
|
|
15125
15013
|
|
|
15126
|
-
|
|
15127
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
15014
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
15128
15015
|
|
|
15129
|
-
if
|
|
15130
|
-
|
|
15131
|
-
// Check if it contains substantial user-facing text - but not for special strings
|
|
15132
|
-
if (!isSpecialString && !/[a-zA-Z]{3,}/.test(str)) return;
|
|
15016
|
+
// Check if it contains substantial user-facing text
|
|
15017
|
+
if (!/[a-zA-Z]{3,}/.test(str)) return;
|
|
15133
15018
|
|
|
15134
15019
|
// Skip if it looks like a path, URL, or query
|
|
15135
15020
|
if (/^[/.]|^https?:\/\/|^[?&]/.test(str)) return;
|
|
@@ -17763,6 +17648,81 @@ const noInlineTypeDefinitions = {
|
|
|
17763
17648
|
const maxUnionMembers = options.maxUnionMembers ?? 2;
|
|
17764
17649
|
const maxLength = options.maxLength ?? 50;
|
|
17765
17650
|
|
|
17651
|
+
// Built-in type keywords that don't need to be extracted
|
|
17652
|
+
const builtInTypeKeywords = new Set([
|
|
17653
|
+
"any",
|
|
17654
|
+
"bigint",
|
|
17655
|
+
"boolean",
|
|
17656
|
+
"never",
|
|
17657
|
+
"null",
|
|
17658
|
+
"number",
|
|
17659
|
+
"object",
|
|
17660
|
+
"string",
|
|
17661
|
+
"symbol",
|
|
17662
|
+
"undefined",
|
|
17663
|
+
"unknown",
|
|
17664
|
+
"void",
|
|
17665
|
+
]);
|
|
17666
|
+
|
|
17667
|
+
// Built-in type references (classes/interfaces that are built-in)
|
|
17668
|
+
const builtInTypeReferences = new Set([
|
|
17669
|
+
"Array",
|
|
17670
|
+
"BigInt",
|
|
17671
|
+
"Boolean",
|
|
17672
|
+
"Date",
|
|
17673
|
+
"Error",
|
|
17674
|
+
"Function",
|
|
17675
|
+
"Map",
|
|
17676
|
+
"Number",
|
|
17677
|
+
"Object",
|
|
17678
|
+
"Promise",
|
|
17679
|
+
"ReadonlyArray",
|
|
17680
|
+
"RegExp",
|
|
17681
|
+
"Set",
|
|
17682
|
+
"String",
|
|
17683
|
+
"Symbol",
|
|
17684
|
+
"WeakMap",
|
|
17685
|
+
"WeakSet",
|
|
17686
|
+
]);
|
|
17687
|
+
|
|
17688
|
+
// Check if a type node is a built-in type
|
|
17689
|
+
const isBuiltInTypeHandler = (node) => {
|
|
17690
|
+
if (!node) return false;
|
|
17691
|
+
|
|
17692
|
+
// Keyword types: string, number, boolean, null, undefined, etc.
|
|
17693
|
+
if (node.type === "TSStringKeyword" || node.type === "TSNumberKeyword"
|
|
17694
|
+
|| node.type === "TSBooleanKeyword" || node.type === "TSNullKeyword"
|
|
17695
|
+
|| node.type === "TSUndefinedKeyword" || node.type === "TSVoidKeyword"
|
|
17696
|
+
|| node.type === "TSAnyKeyword" || node.type === "TSUnknownKeyword"
|
|
17697
|
+
|| node.type === "TSNeverKeyword" || node.type === "TSObjectKeyword"
|
|
17698
|
+
|| node.type === "TSSymbolKeyword" || node.type === "TSBigIntKeyword") {
|
|
17699
|
+
return true;
|
|
17700
|
+
}
|
|
17701
|
+
|
|
17702
|
+
// Type reference: Error, Promise, Array, etc.
|
|
17703
|
+
if (node.type === "TSTypeReference" && node.typeName) {
|
|
17704
|
+
const typeName = node.typeName.name || (node.typeName.type === "Identifier" && node.typeName.name);
|
|
17705
|
+
|
|
17706
|
+
if (typeName && builtInTypeReferences.has(typeName)) {
|
|
17707
|
+
return true;
|
|
17708
|
+
}
|
|
17709
|
+
}
|
|
17710
|
+
|
|
17711
|
+
// Literal types: true, false, specific strings/numbers
|
|
17712
|
+
if (node.type === "TSLiteralType") {
|
|
17713
|
+
return true;
|
|
17714
|
+
}
|
|
17715
|
+
|
|
17716
|
+
return false;
|
|
17717
|
+
};
|
|
17718
|
+
|
|
17719
|
+
// Check if all members of a union are built-in types
|
|
17720
|
+
const isBuiltInUnionHandler = (unionNode) => {
|
|
17721
|
+
if (unionNode.type !== "TSUnionType") return false;
|
|
17722
|
+
|
|
17723
|
+
return unionNode.types.every((type) => isBuiltInTypeHandler(type));
|
|
17724
|
+
};
|
|
17725
|
+
|
|
17766
17726
|
// Count union type members
|
|
17767
17727
|
const countUnionMembersHandler = (node) => {
|
|
17768
17728
|
if (node.type !== "TSUnionType") return 1;
|
|
@@ -17782,6 +17742,9 @@ const noInlineTypeDefinitions = {
|
|
|
17782
17742
|
|
|
17783
17743
|
// Handle union types directly
|
|
17784
17744
|
if (typeNode.type === "TSUnionType") {
|
|
17745
|
+
// Skip union types with only built-in types (e.g., string | null, Error | null)
|
|
17746
|
+
if (isBuiltInUnionHandler(typeNode)) return;
|
|
17747
|
+
|
|
17785
17748
|
const memberCount = countUnionMembersHandler(typeNode);
|
|
17786
17749
|
const typeText = sourceCode.getText(typeNode);
|
|
17787
17750
|
|
package/package.json
CHANGED