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.
Files changed (3) hide show
  1. package/CHANGELOG.md +31 -18
  2. package/index.js +274 -244
  3. 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
- result += sourceCode.getText(param.typeAnnotation);
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
- // HTTP status codes that should NOT be hardcoded (2xx, 4xx, and 5xx)
14509
- const httpStatusCodePattern = /^[245]\d{2}$/;
14510
-
14511
- // Common role/permission names that should be imported from enums/data
14512
- const rolePermissionNames = new Set([
14513
- "admin",
14514
- "administrator",
14515
- "editor",
14516
- "guest",
14517
- "manager",
14518
- "member",
14519
- "moderator",
14520
- "operator",
14521
- "owner",
14522
- "reviewer",
14523
- "subscriber",
14524
- "superadmin",
14525
- "supervisor",
14526
- "user",
14527
- "viewer",
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
- // HTTP methods that should be imported from enums/data
14531
- const httpMethods = new Set([
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
- // Environment names that should be imported from enums/data
14544
- const environmentNames = new Set([
14545
- "dev",
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
- // Log levels that should be imported from enums/data
14559
- const logLevels = new Set([
14560
- "debug",
14561
- "error",
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
- // Status/state strings that should be imported from enums/data
14571
- const statusStrings = new Set([
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
- // Validation/form strings that should be imported from enums/data
14613
- const validationStrings = new Set([
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
- // Auth/permission state strings that should be imported from enums/data
14623
- const authStrings = new Set([
14624
- "anonymous",
14625
- "authenticated",
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
- // Priority levels that should be imported from enums/data
14644
- const priorityLevels = new Set([
14645
- "critical",
14646
- "high",
14647
- "highest",
14648
- "low",
14649
- "lowest",
14650
- "medium",
14651
- "normal",
14652
- "urgent",
14653
- ]);
14630
+ current = current.parent;
14631
+ }
14632
+
14633
+ return false;
14634
+ };
14654
14635
 
14655
- // Check functions for each category
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
- if (isHttpStatusCodeHandler(str)) {
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 (but not if it's a flagged special string)
14644
+ // Check if a string matches any ignore pattern
14722
14645
  const shouldIgnoreStringHandler = (str) => {
14723
- // Always flag HTTP status codes and role names
14724
- if (isFlaggedSpecialStringHandler(str)) return false;
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
- // Check if it's a flagged special string (status code, role name)
14912
- const isSpecialString = isFlaggedSpecialStringHandler(text);
14834
+ if (shouldIgnoreStringHandler(text)) return;
14913
14835
 
14914
- if (!isSpecialString && shouldIgnoreStringHandler(text)) return;
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
- // Check if it's a flagged special string (status code, role name)
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 - skip for special strings
14954
- if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
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
- // Check if it's a flagged special string (status code, role name)
14968
- const isSpecialString = isFlaggedSpecialStringHandler(str);
14884
+ if (shouldIgnoreStringHandler(str)) return;
14969
14885
 
14970
- if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
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
- // Check if it's a flagged special string (status code, role name)
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 - skip for special strings
15015
- 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;
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
- // Check if it's a flagged special string (status code, role name)
15037
- const isSpecialString = isFlaggedSpecialStringHandler(str);
14947
+ if (shouldIgnoreStringHandler(str)) return;
15038
14948
 
15039
- if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
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
- // Check if it's a flagged special string (status code, role name)
15059
- const isSpecialString = isFlaggedSpecialStringHandler(str);
14966
+ // Skip if it matches ignore patterns
14967
+ if (shouldIgnoreStringHandler(str)) return;
15060
14968
 
15061
- // Skip if it matches ignore patterns (but not special strings)
15062
- if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
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 - but not for special strings
15080
- if (!isSpecialString && !/[a-zA-Z]/.test(str)) return;
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
- // Check if it's a flagged special string (status code, role name)
15104
- const isSpecialString = isFlaggedSpecialStringHandler(str);
15014
+ if (shouldIgnoreStringHandler(str)) return;
15105
15015
 
15106
- if (!isSpecialString && shouldIgnoreStringHandler(str)) return;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "description": "A custom ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",