eslint-plugin-code-style 1.11.0 → 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 +31 -18
- package/index.js +274 -244
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.11.2] - 2026-02-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
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)
|
|
20
|
+
|
|
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
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **`component-props-inline-type`** - Single-property type annotations spanning multiple lines now auto-fix to single line format `{ prop: Type }`
|
|
35
|
+
- **`function-params-per-line`** - Normalize single-member type annotations to prevent circular fix conflicts
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
10
39
|
## [1.11.0] - 2026-02-03
|
|
11
40
|
|
|
12
41
|
**New Rule: svg-component-icon-naming + Multiple Component Props Fixes**
|
|
@@ -42,25 +71,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
42
71
|
|
|
43
72
|
## [1.10.3] - 2026-02-03
|
|
44
73
|
|
|
45
|
-
**Bug Fixes: className template literals and trailing comma removal**
|
|
46
|
-
|
|
47
|
-
**Version Range:** v1.10.2 → v1.10.3
|
|
48
|
-
|
|
49
74
|
### Fixed
|
|
50
75
|
|
|
51
76
|
- **`no-hardcoded-strings`** - Skip template literals inside className/style attributes (Tailwind classes in template literals)
|
|
52
77
|
- **`component-props-inline-type`** - Auto-fix to REMOVE trailing comma for single property (not just skip adding it)
|
|
53
78
|
|
|
54
|
-
**Full Changelog:** [v1.10.2...v1.10.3](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.2...v1.10.3)
|
|
55
|
-
|
|
56
79
|
---
|
|
57
80
|
|
|
58
81
|
## [1.10.2] - 2026-02-03
|
|
59
82
|
|
|
60
|
-
**Bug Fixes: component-props-inline-type and no-hardcoded-strings**
|
|
61
|
-
|
|
62
|
-
**Version Range:** v1.10.1 → v1.10.2
|
|
63
|
-
|
|
64
83
|
### Fixed
|
|
65
84
|
|
|
66
85
|
- **`component-props-inline-type`** - Don't require trailing comma for single property in inline type definitions
|
|
@@ -70,23 +89,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
70
89
|
- **`no-hardcoded-strings`** - Skip CSS property values (cursor: pointer, display: flex, position: absolute, etc.)
|
|
71
90
|
- **`no-hardcoded-strings`** - Skip SVG filter result identifiers (BackgroundImageFix, SourceGraphic, etc.)
|
|
72
91
|
|
|
73
|
-
**Full Changelog:** [v1.10.1...v1.10.2](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.1...v1.10.2)
|
|
74
|
-
|
|
75
92
|
---
|
|
76
93
|
|
|
77
94
|
## [1.10.1] - 2026-02-03
|
|
78
95
|
|
|
79
|
-
**Bug Fix: logical-expression-multiline rule improvements**
|
|
80
|
-
|
|
81
|
-
**Version Range:** v1.10.0 → v1.10.1
|
|
82
|
-
|
|
83
96
|
### Fixed
|
|
84
97
|
|
|
85
98
|
- **`logical-expression-multiline`** - Add collapse to single line for simple expressions (≤3 operands)
|
|
86
99
|
- **`logical-expression-multiline`** - Skip collapsing when any operand is multiline (e.g., JSX elements)
|
|
87
100
|
|
|
88
|
-
**Full Changelog:** [v1.10.0...v1.10.1](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.0...v1.10.1)
|
|
89
|
-
|
|
90
101
|
---
|
|
91
102
|
|
|
92
103
|
## [1.10.0] - 2026-02-03
|
|
@@ -1461,6 +1472,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1461
1472
|
|
|
1462
1473
|
---
|
|
1463
1474
|
|
|
1475
|
+
[1.11.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.11.1...v1.11.2
|
|
1476
|
+
[1.11.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.11.0...v1.11.1
|
|
1464
1477
|
[1.11.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.3...v1.11.0
|
|
1465
1478
|
[1.10.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.2...v1.10.3
|
|
1466
1479
|
[1.10.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.1...v1.10.2
|
package/index.js
CHANGED
|
@@ -2552,7 +2552,30 @@ const functionParamsPerLine = {
|
|
|
2552
2552
|
|
|
2553
2553
|
// Preserve type annotation if present
|
|
2554
2554
|
if (param.typeAnnotation) {
|
|
2555
|
-
|
|
2555
|
+
let typeText = sourceCode.getText(param.typeAnnotation);
|
|
2556
|
+
const typeAnnotation = param.typeAnnotation.typeAnnotation;
|
|
2557
|
+
|
|
2558
|
+
// For single-member inline types, collapse to one line
|
|
2559
|
+
if (typeAnnotation) {
|
|
2560
|
+
let members = null;
|
|
2561
|
+
|
|
2562
|
+
if (typeAnnotation.type === "TSTypeLiteral") {
|
|
2563
|
+
members = typeAnnotation.members;
|
|
2564
|
+
} else if (typeAnnotation.type === "TSIntersectionType") {
|
|
2565
|
+
const typeLiteral = typeAnnotation.types.find((t) => t.type === "TSTypeLiteral");
|
|
2566
|
+
|
|
2567
|
+
if (typeLiteral) {
|
|
2568
|
+
members = typeLiteral.members;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
if (members && members.length === 1) {
|
|
2573
|
+
// Normalize whitespace to collapse to one line, remove trailing comma
|
|
2574
|
+
typeText = typeText.replace(/\s+/g, " ").trim().replace(/,\s*}/, " }").replace(/,\s*&/, " &");
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
result += typeText;
|
|
2556
2579
|
}
|
|
2557
2580
|
|
|
2558
2581
|
return result;
|
|
@@ -4711,6 +4734,25 @@ const ternaryConditionMultiline = {
|
|
|
4711
4734
|
return lineText.match(/^\s*/)[0].length;
|
|
4712
4735
|
};
|
|
4713
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
|
+
|
|
4714
4756
|
// Check if branches have complex objects (should stay multiline)
|
|
4715
4757
|
const hasComplexObjectHandler = (n) => {
|
|
4716
4758
|
if (n.type === "ObjectExpression" && n.properties.length >= 2) return true;
|
|
@@ -4720,6 +4762,9 @@ const ternaryConditionMultiline = {
|
|
|
4720
4762
|
return false;
|
|
4721
4763
|
};
|
|
4722
4764
|
|
|
4765
|
+
// Check if branches contain JSX (should stay multiline)
|
|
4766
|
+
const hasJsxBranchesHandler = (node) => containsJsxHandler(node.consequent) || containsJsxHandler(node.alternate);
|
|
4767
|
+
|
|
4723
4768
|
// Check if a nested ternary has complex condition (>maxOperands)
|
|
4724
4769
|
const hasComplexNestedTernaryHandler = (node) => {
|
|
4725
4770
|
const checkBranch = (branch) => {
|
|
@@ -4771,6 +4816,11 @@ const ternaryConditionMultiline = {
|
|
|
4771
4816
|
return false;
|
|
4772
4817
|
}
|
|
4773
4818
|
|
|
4819
|
+
// Skip ternaries with JSX branches (should stay multiline for readability)
|
|
4820
|
+
if (hasJsxBranchesHandler(node)) {
|
|
4821
|
+
return false;
|
|
4822
|
+
}
|
|
4823
|
+
|
|
4774
4824
|
// Skip parenthesized nested ternaries with complex condition (>maxOperands)
|
|
4775
4825
|
// These should be formatted manually or stay as-is
|
|
4776
4826
|
if (hasComplexNestedTernaryHandler(node)) {
|
|
@@ -4945,9 +4995,14 @@ const ternaryConditionMultiline = {
|
|
|
4945
4995
|
return; // Don't collapse - nested group needs multiline
|
|
4946
4996
|
}
|
|
4947
4997
|
|
|
4948
|
-
// Skip if branches have complex objects
|
|
4998
|
+
// Skip if branches have complex objects or JSX elements
|
|
4949
4999
|
const hasComplexBranches = hasComplexObjectHandler(node.consequent) || hasComplexObjectHandler(node.alternate);
|
|
4950
5000
|
|
|
5001
|
+
// Skip ternaries with JSX branches (should stay multiline for readability)
|
|
5002
|
+
if (hasJsxBranchesHandler(node)) {
|
|
5003
|
+
return;
|
|
5004
|
+
}
|
|
5005
|
+
|
|
4951
5006
|
// Skip unparenthesized nested ternaries (parenthesized ones count as 1 operand)
|
|
4952
5007
|
const hasUnparenthesizedNestedTernary = (node.consequent.type === "ConditionalExpression" && !isParenthesizedHandler(node.consequent))
|
|
4953
5008
|
|| (node.alternate.type === "ConditionalExpression" && !isParenthesizedHandler(node.alternate));
|
|
@@ -14432,6 +14487,24 @@ const noHardcodedStrings = {
|
|
|
14432
14487
|
/^&[a-z]+;$/,
|
|
14433
14488
|
// Punctuation only
|
|
14434
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)\(.+\)$/,
|
|
14435
14508
|
];
|
|
14436
14509
|
|
|
14437
14510
|
const extraIgnorePatterns = (options.ignorePatterns || []).map((p) => {
|
|
@@ -14505,223 +14578,73 @@ const noHardcodedStrings = {
|
|
|
14505
14578
|
// UI component patterns - only ignored in JSX attributes, not in logic
|
|
14506
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)$/;
|
|
14507
14580
|
|
|
14508
|
-
//
|
|
14509
|
-
const
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
"
|
|
14514
|
-
"
|
|
14515
|
-
"
|
|
14516
|
-
"
|
|
14517
|
-
"
|
|
14518
|
-
"
|
|
14519
|
-
"
|
|
14520
|
-
"
|
|
14521
|
-
"
|
|
14522
|
-
"
|
|
14523
|
-
"
|
|
14524
|
-
"
|
|
14525
|
-
"
|
|
14526
|
-
"
|
|
14527
|
-
"
|
|
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",
|
|
14528
14605
|
]);
|
|
14529
14606
|
|
|
14530
|
-
//
|
|
14531
|
-
const
|
|
14532
|
-
"CONNECT",
|
|
14533
|
-
"DELETE",
|
|
14534
|
-
"GET",
|
|
14535
|
-
"HEAD",
|
|
14536
|
-
"OPTIONS",
|
|
14537
|
-
"PATCH",
|
|
14538
|
-
"POST",
|
|
14539
|
-
"PUT",
|
|
14540
|
-
"TRACE",
|
|
14541
|
-
]);
|
|
14607
|
+
// Check if string is an HTML input type
|
|
14608
|
+
const isHtmlInputTypeHandler = (str) => htmlInputTypes.has(str.toLowerCase());
|
|
14542
14609
|
|
|
14543
|
-
//
|
|
14544
|
-
const
|
|
14545
|
-
|
|
14546
|
-
"development",
|
|
14547
|
-
"local",
|
|
14548
|
-
"prod",
|
|
14549
|
-
"production",
|
|
14550
|
-
"qa",
|
|
14551
|
-
"sandbox",
|
|
14552
|
-
"staging",
|
|
14553
|
-
"test",
|
|
14554
|
-
"testing",
|
|
14555
|
-
"uat",
|
|
14556
|
-
]);
|
|
14610
|
+
// Check if node is inside a style object expression (style={{ ... }})
|
|
14611
|
+
const isInsideStyleObjectHandler = (node) => {
|
|
14612
|
+
let current = node.parent;
|
|
14557
14613
|
|
|
14558
|
-
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
|
|
14562
|
-
"fatal",
|
|
14563
|
-
"info",
|
|
14564
|
-
"log",
|
|
14565
|
-
"trace",
|
|
14566
|
-
"warn",
|
|
14567
|
-
"warning",
|
|
14568
|
-
]);
|
|
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;
|
|
14569
14618
|
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
"accepted",
|
|
14573
|
-
"active",
|
|
14574
|
-
"approved",
|
|
14575
|
-
"archived",
|
|
14576
|
-
"blocked",
|
|
14577
|
-
"cancelled",
|
|
14578
|
-
"closed",
|
|
14579
|
-
"completed",
|
|
14580
|
-
"declined",
|
|
14581
|
-
"deleted",
|
|
14582
|
-
"disabled",
|
|
14583
|
-
"done",
|
|
14584
|
-
"draft",
|
|
14585
|
-
"enabled",
|
|
14586
|
-
"expired",
|
|
14587
|
-
"failed",
|
|
14588
|
-
"finished",
|
|
14589
|
-
"inactive",
|
|
14590
|
-
"inprogress",
|
|
14591
|
-
"open",
|
|
14592
|
-
"paused",
|
|
14593
|
-
"pending",
|
|
14594
|
-
"processing",
|
|
14595
|
-
"published",
|
|
14596
|
-
"queued",
|
|
14597
|
-
"ready",
|
|
14598
|
-
"rejected",
|
|
14599
|
-
"resolved",
|
|
14600
|
-
"running",
|
|
14601
|
-
"scheduled",
|
|
14602
|
-
"started",
|
|
14603
|
-
"stopped",
|
|
14604
|
-
"submitted",
|
|
14605
|
-
"success",
|
|
14606
|
-
"successful",
|
|
14607
|
-
"suspended",
|
|
14608
|
-
"verified",
|
|
14609
|
-
"waiting",
|
|
14610
|
-
]);
|
|
14619
|
+
if (objExpr.parent && objExpr.parent.type === "JSXExpressionContainer") {
|
|
14620
|
+
const jsxExprContainer = objExpr.parent;
|
|
14611
14621
|
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
"empty",
|
|
14615
|
-
"invalid",
|
|
14616
|
-
"missing",
|
|
14617
|
-
"optional",
|
|
14618
|
-
"required",
|
|
14619
|
-
"valid",
|
|
14620
|
-
]);
|
|
14622
|
+
if (jsxExprContainer.parent && jsxExprContainer.parent.type === "JSXAttribute") {
|
|
14623
|
+
const attrName = jsxExprContainer.parent.name && jsxExprContainer.parent.name.name;
|
|
14621
14624
|
|
|
14622
|
-
|
|
14623
|
-
|
|
14624
|
-
|
|
14625
|
-
|
|
14626
|
-
"authed",
|
|
14627
|
-
"authorized",
|
|
14628
|
-
"denied",
|
|
14629
|
-
"expired",
|
|
14630
|
-
"forbidden",
|
|
14631
|
-
"granted",
|
|
14632
|
-
"locked",
|
|
14633
|
-
"loggedin",
|
|
14634
|
-
"loggedout",
|
|
14635
|
-
"revoked",
|
|
14636
|
-
"unauthenticated",
|
|
14637
|
-
"unauthorized",
|
|
14638
|
-
"unlocked",
|
|
14639
|
-
"unverified",
|
|
14640
|
-
"verified",
|
|
14641
|
-
]);
|
|
14625
|
+
if (attrName === "style") return true;
|
|
14626
|
+
}
|
|
14627
|
+
}
|
|
14628
|
+
}
|
|
14642
14629
|
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
"low",
|
|
14649
|
-
"lowest",
|
|
14650
|
-
"medium",
|
|
14651
|
-
"normal",
|
|
14652
|
-
"urgent",
|
|
14653
|
-
]);
|
|
14630
|
+
current = current.parent;
|
|
14631
|
+
}
|
|
14632
|
+
|
|
14633
|
+
return false;
|
|
14634
|
+
};
|
|
14654
14635
|
|
|
14655
|
-
//
|
|
14656
|
-
const isHttpStatusCodeHandler = (str) => httpStatusCodePattern.test(str);
|
|
14657
|
-
const isRoleNameHandler = (str) => rolePermissionNames.has(str.toLowerCase());
|
|
14658
|
-
const isHttpMethodHandler = (str) => httpMethods.has(str.toUpperCase());
|
|
14659
|
-
const isEnvironmentNameHandler = (str) => environmentNames.has(str.toLowerCase());
|
|
14660
|
-
const isLogLevelHandler = (str) => logLevels.has(str.toLowerCase());
|
|
14661
|
-
const isStatusStringHandler = (str) => statusStrings.has(str.toLowerCase());
|
|
14662
|
-
const isPriorityLevelHandler = (str) => priorityLevels.has(str.toLowerCase());
|
|
14663
|
-
const isValidationStringHandler = (str) => validationStrings.has(str.toLowerCase());
|
|
14664
|
-
const isAuthStringHandler = (str) => authStrings.has(str.toLowerCase());
|
|
14665
|
-
|
|
14666
|
-
// Check if string should be flagged even if it matches technical patterns
|
|
14667
|
-
const isFlaggedSpecialStringHandler = (str) => isHttpStatusCodeHandler(str)
|
|
14668
|
-
|| isRoleNameHandler(str)
|
|
14669
|
-
|| isHttpMethodHandler(str)
|
|
14670
|
-
|| isEnvironmentNameHandler(str)
|
|
14671
|
-
|| isLogLevelHandler(str)
|
|
14672
|
-
|| isStatusStringHandler(str)
|
|
14673
|
-
|| isPriorityLevelHandler(str)
|
|
14674
|
-
|| isValidationStringHandler(str)
|
|
14675
|
-
|| isAuthStringHandler(str);
|
|
14676
|
-
|
|
14677
|
-
// Get descriptive error message based on string type
|
|
14636
|
+
// Get descriptive error message - unified message for all hardcoded strings
|
|
14678
14637
|
const getErrorMessageHandler = (str, context = "") => {
|
|
14679
14638
|
const truncatedStr = str.length > 30 ? `${str.substring(0, 30)}...` : str;
|
|
14680
14639
|
const contextPart = context ? ` in ${context}` : "";
|
|
14681
14640
|
|
|
14682
|
-
|
|
14683
|
-
return `Hardcoded HTTP status code "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14684
|
-
}
|
|
14685
|
-
|
|
14686
|
-
if (isRoleNameHandler(str)) {
|
|
14687
|
-
return `Hardcoded role/permission "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14688
|
-
}
|
|
14689
|
-
|
|
14690
|
-
if (isHttpMethodHandler(str)) {
|
|
14691
|
-
return `Hardcoded HTTP method "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14692
|
-
}
|
|
14693
|
-
|
|
14694
|
-
if (isEnvironmentNameHandler(str)) {
|
|
14695
|
-
return `Hardcoded environment name "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14696
|
-
}
|
|
14697
|
-
|
|
14698
|
-
if (isLogLevelHandler(str)) {
|
|
14699
|
-
return `Hardcoded log level "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14700
|
-
}
|
|
14701
|
-
|
|
14702
|
-
if (isStatusStringHandler(str)) {
|
|
14703
|
-
return `Hardcoded status "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14704
|
-
}
|
|
14705
|
-
|
|
14706
|
-
if (isPriorityLevelHandler(str)) {
|
|
14707
|
-
return `Hardcoded priority level "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14708
|
-
}
|
|
14709
|
-
|
|
14710
|
-
if (isValidationStringHandler(str)) {
|
|
14711
|
-
return `Hardcoded validation string "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14712
|
-
}
|
|
14713
|
-
|
|
14714
|
-
if (isAuthStringHandler(str)) {
|
|
14715
|
-
return `Hardcoded auth state "${truncatedStr}"${contextPart} should be imported from @/enums or @/data`;
|
|
14716
|
-
}
|
|
14717
|
-
|
|
14718
|
-
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`;
|
|
14719
14642
|
};
|
|
14720
14643
|
|
|
14721
|
-
// Check if a string matches any ignore pattern
|
|
14644
|
+
// Check if a string matches any ignore pattern
|
|
14722
14645
|
const shouldIgnoreStringHandler = (str) => {
|
|
14723
|
-
//
|
|
14724
|
-
if (
|
|
14646
|
+
// Skip HTML input types (text, password, email, etc.)
|
|
14647
|
+
if (isHtmlInputTypeHandler(str)) return true;
|
|
14725
14648
|
|
|
14726
14649
|
// Skip Tailwind/CSS class strings
|
|
14727
14650
|
if (isTailwindClassStringHandler(str)) return true;
|
|
@@ -14908,13 +14831,10 @@ const noHardcodedStrings = {
|
|
|
14908
14831
|
|
|
14909
14832
|
if (!text) return;
|
|
14910
14833
|
|
|
14911
|
-
|
|
14912
|
-
const isSpecialString = isFlaggedSpecialStringHandler(text);
|
|
14834
|
+
if (shouldIgnoreStringHandler(text)) return;
|
|
14913
14835
|
|
|
14914
|
-
if
|
|
14915
|
-
|
|
14916
|
-
// Check if it looks like user-facing text (contains letters) - skip for special strings
|
|
14917
|
-
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;
|
|
14918
14838
|
|
|
14919
14839
|
context.report({
|
|
14920
14840
|
message: getErrorMessageHandler(text, "JSX"),
|
|
@@ -14945,13 +14865,10 @@ const noHardcodedStrings = {
|
|
|
14945
14865
|
if (expression.type === "Literal" && typeof expression.value === "string") {
|
|
14946
14866
|
const str = expression.value;
|
|
14947
14867
|
|
|
14948
|
-
|
|
14949
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14950
|
-
|
|
14951
|
-
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14868
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
14952
14869
|
|
|
14953
|
-
// Check if it looks like user-facing text
|
|
14954
|
-
if (
|
|
14870
|
+
// Check if it looks like user-facing text
|
|
14871
|
+
if (!/[a-zA-Z]/.test(str)) return;
|
|
14955
14872
|
|
|
14956
14873
|
context.report({
|
|
14957
14874
|
message: getErrorMessageHandler(str, "JSX expression"),
|
|
@@ -14964,13 +14881,10 @@ const noHardcodedStrings = {
|
|
|
14964
14881
|
expression.quasis.forEach((quasi) => {
|
|
14965
14882
|
const str = quasi.value.cooked || quasi.value.raw;
|
|
14966
14883
|
|
|
14967
|
-
|
|
14968
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14884
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
14969
14885
|
|
|
14970
|
-
if
|
|
14971
|
-
|
|
14972
|
-
// Check if it contains user-facing text - skip for special strings
|
|
14973
|
-
if (!isSpecialString && !/[a-zA-Z]{2,}/.test(str)) return;
|
|
14886
|
+
// Check if it contains user-facing text
|
|
14887
|
+
if (!/[a-zA-Z]{2,}/.test(str)) return;
|
|
14974
14888
|
|
|
14975
14889
|
// Skip if it looks like a path or URL pattern
|
|
14976
14890
|
if (/^[/.]|https?:\/\//.test(str)) return;
|
|
@@ -15006,13 +14920,10 @@ const noHardcodedStrings = {
|
|
|
15006
14920
|
// Skip UI component patterns in JSX attributes (variant, size, position props)
|
|
15007
14921
|
if (uiComponentPattern.test(str)) return;
|
|
15008
14922
|
|
|
15009
|
-
|
|
15010
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
15011
|
-
|
|
15012
|
-
if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
|
|
14923
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
15013
14924
|
|
|
15014
|
-
// Check if it looks like user-facing text
|
|
15015
|
-
if (
|
|
14925
|
+
// Check if it looks like user-facing text
|
|
14926
|
+
if (!/[a-zA-Z]/.test(str)) return;
|
|
15016
14927
|
|
|
15017
14928
|
context.report({
|
|
15018
14929
|
message: getErrorMessageHandler(str, `attribute "${attrName}"`),
|
|
@@ -15033,12 +14944,9 @@ const noHardcodedStrings = {
|
|
|
15033
14944
|
// Skip UI component patterns in JSX attributes (variant, size, position props)
|
|
15034
14945
|
if (uiComponentPattern.test(str)) return;
|
|
15035
14946
|
|
|
15036
|
-
|
|
15037
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
14947
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
15038
14948
|
|
|
15039
|
-
if (
|
|
15040
|
-
|
|
15041
|
-
if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
|
|
14949
|
+
if (!/[a-zA-Z]/.test(str)) return;
|
|
15042
14950
|
|
|
15043
14951
|
context.report({
|
|
15044
14952
|
message: getErrorMessageHandler(str, `attribute "${attrName}"`),
|
|
@@ -15055,11 +14963,11 @@ const noHardcodedStrings = {
|
|
|
15055
14963
|
|
|
15056
14964
|
const str = node.value;
|
|
15057
14965
|
|
|
15058
|
-
//
|
|
15059
|
-
|
|
14966
|
+
// Skip if it matches ignore patterns
|
|
14967
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
15060
14968
|
|
|
15061
|
-
// Skip if
|
|
15062
|
-
if (
|
|
14969
|
+
// Skip if inside a style object (style={{ transform: "..." }})
|
|
14970
|
+
if (isInsideStyleObjectHandler(node)) return;
|
|
15063
14971
|
|
|
15064
14972
|
// Skip if not in relevant context
|
|
15065
14973
|
if (!isInRelevantContextHandler(node)) return;
|
|
@@ -15076,8 +14984,8 @@ const noHardcodedStrings = {
|
|
|
15076
14984
|
// Skip object property keys
|
|
15077
14985
|
if (node.parent.type === "Property" && node.parent.key === node) return;
|
|
15078
14986
|
|
|
15079
|
-
// Skip if it doesn't look like user-facing text
|
|
15080
|
-
if (
|
|
14987
|
+
// Skip if it doesn't look like user-facing text
|
|
14988
|
+
if (!/[a-zA-Z]/.test(str)) return;
|
|
15081
14989
|
|
|
15082
14990
|
context.report({
|
|
15083
14991
|
message: getErrorMessageHandler(str),
|
|
@@ -15090,6 +14998,9 @@ const noHardcodedStrings = {
|
|
|
15090
14998
|
// Skip if in JSX (handled separately)
|
|
15091
14999
|
if (node.parent.type === "JSXExpressionContainer") return;
|
|
15092
15000
|
|
|
15001
|
+
// Skip if inside a style object (style={{ background: `...` }})
|
|
15002
|
+
if (isInsideStyleObjectHandler(node)) return;
|
|
15003
|
+
|
|
15093
15004
|
// Skip if not in relevant context
|
|
15094
15005
|
if (!isInRelevantContextHandler(node)) return;
|
|
15095
15006
|
|
|
@@ -15100,13 +15011,10 @@ const noHardcodedStrings = {
|
|
|
15100
15011
|
node.quasis.forEach((quasi) => {
|
|
15101
15012
|
const str = quasi.value.cooked || quasi.value.raw;
|
|
15102
15013
|
|
|
15103
|
-
|
|
15104
|
-
const isSpecialString = isFlaggedSpecialStringHandler(str);
|
|
15014
|
+
if (shouldIgnoreStringHandler(str)) return;
|
|
15105
15015
|
|
|
15106
|
-
if
|
|
15107
|
-
|
|
15108
|
-
// Check if it contains substantial user-facing text - but not for special strings
|
|
15109
|
-
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;
|
|
15110
15018
|
|
|
15111
15019
|
// Skip if it looks like a path, URL, or query
|
|
15112
15020
|
if (/^[/.]|^https?:\/\/|^[?&]/.test(str)) return;
|
|
@@ -17071,6 +16979,28 @@ const componentPropsInlineType = {
|
|
|
17071
16979
|
}
|
|
17072
16980
|
}
|
|
17073
16981
|
|
|
16982
|
+
// Collapse single-member type to single line if it spans multiple lines
|
|
16983
|
+
if (members.length === 1 && openBraceToken && closeBraceToken) {
|
|
16984
|
+
const member = members[0];
|
|
16985
|
+
|
|
16986
|
+
// Check if the type spans multiple lines
|
|
16987
|
+
if (openBraceToken.loc.end.line !== closeBraceToken.loc.start.line) {
|
|
16988
|
+
let memberText = sourceCode.getText(member);
|
|
16989
|
+
|
|
16990
|
+
// Remove trailing comma/semicolon if any
|
|
16991
|
+
memberText = memberText.replace(/[,;]\s*$/, "");
|
|
16992
|
+
|
|
16993
|
+
context.report({
|
|
16994
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
16995
|
+
[openBraceToken.range[0], closeBraceToken.range[1]],
|
|
16996
|
+
`{ ${memberText} }`,
|
|
16997
|
+
),
|
|
16998
|
+
message: "Single props type property should be on a single line",
|
|
16999
|
+
node: typeLiteral,
|
|
17000
|
+
});
|
|
17001
|
+
}
|
|
17002
|
+
}
|
|
17003
|
+
|
|
17074
17004
|
// Check each member for formatting
|
|
17075
17005
|
members.forEach((member, index) => {
|
|
17076
17006
|
const memberText = sourceCode.getText(member);
|
|
@@ -17320,6 +17250,28 @@ const componentPropsInlineType = {
|
|
|
17320
17250
|
}
|
|
17321
17251
|
}
|
|
17322
17252
|
|
|
17253
|
+
// Collapse single-member type to single line if it spans multiple lines
|
|
17254
|
+
if (members.length === 1 && openBraceToken && closeBraceToken) {
|
|
17255
|
+
const member = members[0];
|
|
17256
|
+
|
|
17257
|
+
// Check if the type spans multiple lines
|
|
17258
|
+
if (openBraceToken.loc.end.line !== closeBraceToken.loc.start.line) {
|
|
17259
|
+
let memberText = sourceCode.getText(member);
|
|
17260
|
+
|
|
17261
|
+
// Remove trailing comma/semicolon if any
|
|
17262
|
+
memberText = memberText.replace(/[,;]\s*$/, "");
|
|
17263
|
+
|
|
17264
|
+
context.report({
|
|
17265
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
17266
|
+
[openBraceToken.range[0], closeBraceToken.range[1]],
|
|
17267
|
+
`{ ${memberText} }`,
|
|
17268
|
+
),
|
|
17269
|
+
message: "Single props type property should be on a single line",
|
|
17270
|
+
node: typeAnnotation,
|
|
17271
|
+
});
|
|
17272
|
+
}
|
|
17273
|
+
}
|
|
17274
|
+
|
|
17323
17275
|
// Check each member for semicolons vs commas and line formatting
|
|
17324
17276
|
members.forEach((member, index) => {
|
|
17325
17277
|
const memberText = sourceCode.getText(member);
|
|
@@ -17696,6 +17648,81 @@ const noInlineTypeDefinitions = {
|
|
|
17696
17648
|
const maxUnionMembers = options.maxUnionMembers ?? 2;
|
|
17697
17649
|
const maxLength = options.maxLength ?? 50;
|
|
17698
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
|
+
|
|
17699
17726
|
// Count union type members
|
|
17700
17727
|
const countUnionMembersHandler = (node) => {
|
|
17701
17728
|
if (node.type !== "TSUnionType") return 1;
|
|
@@ -17715,6 +17742,9 @@ const noInlineTypeDefinitions = {
|
|
|
17715
17742
|
|
|
17716
17743
|
// Handle union types directly
|
|
17717
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
|
+
|
|
17718
17748
|
const memberCount = countUnionMembersHandler(typeNode);
|
|
17719
17749
|
const typeText = sourceCode.getText(typeNode);
|
|
17720
17750
|
|
package/package.json
CHANGED