eslint-plugin-code-style 1.6.6 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.7.1] - 2026-02-02
11
+
12
+ ### Fixed
13
+
14
+ - **`no-empty-lines-in-function-params`** - Detect empty lines between destructured properties inside ObjectPattern params
15
+ - **`component-props-inline-type`** - Handle TSIntersectionType (e.g., `ButtonHTMLAttributes & { prop: Type }`): check `&` position, opening brace position, and apply formatting rules to type literals within intersections
16
+ - **`enum-type-enforcement`** - Fix argument passed to extractTypeInfoHandler for TSPropertySignature members (was passing `member`, now correctly passes `member.typeAnnotation`)
17
+ - **`ternary-condition-multiline`** - Improve simple ternary prefix calculation to use actual declaration text instead of column positions; add check for `?` on same line as multiline condition end
18
+
19
+ ---
20
+
21
+ ## [1.7.0] - 2026-02-02
22
+
23
+ **New Rules for Blocks, Classes & Enum Enforcement + Multiple Enhancements**
24
+
25
+ **Version Range:** v1.6.1 → v1.7.0
26
+
27
+ ### Added
28
+
29
+ **New Rules (3)**
30
+ - `empty-line-after-block` - Require empty line between closing `}` of block statement and next statement 🔧
31
+ - `class-naming-convention` - Enforce class declarations end with "Class" suffix 🔧
32
+ - `enum-type-enforcement` - Enforce using enum values instead of string literals for typed variables (e.g., `ButtonVariantEnum.PRIMARY` instead of `"primary"`) 🔧
33
+
34
+ ### Enhanced
35
+
36
+ - **`ternary-condition-multiline`** - Now also collapses simple ternaries to single line when they fit within max line length (default: 120 chars). Added `maxLineLength` option.
37
+ - **`function-object-destructure`** - Add auto-fix (replaces destructured usages with dot notation), expand module paths (services, constants, config, api, utils, helpers, lib, apis, configs, utilities, routes)
38
+ - **`function-params-per-line`** - Handle callbacks with mixed params (destructured + simple like `({ item }, index)`)
39
+ - **`array-callback-destructure`** - Fix closing brace on same line as last property
40
+ - **`simple-call-single-line`** - Skip callbacks with 2+ params to avoid conflicts
41
+ - **`jsx-simple-element-one-line`**, **`jsx-children-on-new-line`**, **`jsx-element-child-new-line`** - Treat simple function calls (0-1 args) as simple expressions
42
+
43
+ ### Fixed
44
+
45
+ - **`component-props-destructure`** - Detect body destructuring patterns even without type annotations, add auto-fix for body destructuring, preserve TypeScript type annotation when auto-fixing
46
+
47
+ ### Stats
48
+
49
+ - Total Rules: 69 (was 66)
50
+ - Auto-fixable: 63 rules 🔧
51
+ - Report-only: 6 rules
52
+
53
+ **Full Changelog:** [v1.6.1...v1.7.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.1...v1.7.0)
54
+
55
+ ---
56
+
10
57
  ## [1.6.6] - 2026-02-01
11
58
 
12
59
  ### Fixed
@@ -1028,6 +1075,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1028
1075
 
1029
1076
  ---
1030
1077
 
1078
+ [1.7.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.0...v1.7.1
1079
+ [1.7.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.6...v1.7.0
1080
+ [1.6.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.5...v1.6.6
1031
1081
  [1.6.5]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.4...v1.6.5
1032
1082
  [1.6.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.3...v1.6.4
1033
1083
  [1.6.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.2...v1.6.3
package/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  **A powerful ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects.**
21
21
 
22
- *66 rules (60 auto-fixable) to keep your codebase clean and consistent*
22
+ *69 rules (63 auto-fixable) to keep your codebase clean and consistent*
23
23
 
24
24
  </div>
25
25
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## 🎯 Why This Plugin?
29
29
 
30
- This plugin provides **66 custom rules** (60 auto-fixable) for code formatting. Built for **ESLint v9 flat configs**.
30
+ This plugin provides **69 custom rules** (63 auto-fixable) for code formatting. Built for **ESLint v9 flat configs**.
31
31
 
32
32
  > **Note:** ESLint [deprecated 79 formatting rules](https://eslint.org/blog/2023/10/deprecating-formatting-rules/) in v8.53.0. Our recommended configs use `@stylistic/eslint-plugin` as the replacement for these deprecated rules.
33
33
 
@@ -36,7 +36,7 @@ This plugin provides **66 custom rules** (60 auto-fixable) for code formatting.
36
36
  - **Works alongside existing tools** — Complements ESLint's built-in rules and packages like eslint-plugin-react, eslint-plugin-import, etc
37
37
  - **Self-sufficient rules** — Each rule handles complete formatting independently
38
38
  - **Consistency at scale** — Reduces code-style differences between team members by enforcing uniform formatting across your projects
39
- - **Highly automated** — 60 of 66 rules support auto-fix with `eslint --fix`
39
+ - **Highly automated** — 63 of 69 rules support auto-fix with `eslint --fix`
40
40
 
41
41
  When combined with ESLint's native rules and other popular plugins, this package helps create a complete code style solution that keeps your codebase clean and consistent.
42
42
 
@@ -60,7 +60,7 @@ We provide **ready-to-use ESLint flat configuration files** that combine `eslint
60
60
 
61
61
  ### 💡 Why Use These Configs?
62
62
 
63
- - **Complete Coverage** — Combines ESLint built-in rules, third-party plugins, and all 64 code-style rules
63
+ - **Complete Coverage** — Combines ESLint built-in rules, third-party plugins, and all 69 code-style rules
64
64
  - **Ready-to-Use** — Copy the config file and start linting immediately
65
65
  - **Battle-Tested** — These configurations have been refined through real-world usage
66
66
  - **Fully Documented** — Each config includes detailed instructions and explanations
@@ -97,7 +97,7 @@ We provide **ready-to-use ESLint flat configuration files** that combine `eslint
97
97
  <td width="50%">
98
98
 
99
99
  ### 🔧 Auto-Fixable Rules
100
- **60 rules** support automatic fixing with `eslint --fix`. 6 rules are report-only (require manual changes).
100
+ **63 rules** support automatic fixing with `eslint --fix`. 6 rules are report-only (require manual changes).
101
101
 
102
102
  </td>
103
103
  <td width="50%">
@@ -252,7 +252,7 @@ rules: {
252
252
 
253
253
  ## 📖 Rules Categories
254
254
 
255
- > **66 rules total** — 60 with auto-fix 🔧, 6 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
255
+ > **69 rules total** — 63 with auto-fix 🔧, 6 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
256
256
  >
257
257
  > **Legend:** 🔧 Auto-fixable with `eslint --fix` • ⚙️ Customizable options
258
258
 
@@ -279,13 +279,16 @@ rules: {
279
279
  | **Component Rules** | |
280
280
  | `component-props-destructure` | Component props must be destructured `({ prop })` not received as `(props)` 🔧 |
281
281
  | `component-props-inline-type` | Inline type annotation `} : {` with matching props, proper spacing, commas, no interface reference 🔧 |
282
+ | **Class Rules** | |
283
+ | `class-naming-convention` | Class declarations must end with "Class" suffix (e.g., `ApiServiceClass`) 🔧 |
282
284
  | **Control Flow Rules** | |
283
285
  | `block-statement-newlines` | Newline after `{` and before `}` in if/for/while/function blocks 🔧 |
286
+ | `empty-line-after-block` | Empty line required between closing `}` of block and next statement 🔧 |
284
287
  | `if-else-spacing` | Empty line between consecutive if blocks, no empty line between single-line if/else 🔧 |
285
288
  | `if-statement-format` | `{` on same line as `if`/`else if`, `else` on same line as `}`, proper spacing 🔧 |
286
289
  | `multiline-if-conditions` | Conditions exceeding threshold get one operand per line with proper indentation (default: >3) 🔧 ⚙️ |
287
290
  | `no-empty-lines-in-switch-cases` | No empty line after `case X:` before code, no empty lines between cases 🔧 |
288
- | `ternary-condition-multiline` | Ternary conditions exceeding threshold get one operand per line (default: >3) 🔧 ⚙️ |
291
+ | `ternary-condition-multiline` | Collapse simple ternaries to single line; expand complex conditions (>3 operands) to multiline 🔧 ⚙️ |
289
292
  | **Function Rules** | |
290
293
  | `function-call-spacing` | No space between function name and `(`: `fn()` not `fn ()` 🔧 |
291
294
  | `function-declaration-style` | Auto-fix for `func-style`: converts function declarations to arrow expressions 🔧 |
@@ -330,6 +333,7 @@ rules: {
330
333
  | `member-expression-bracket-spacing` | No spaces inside brackets in computed member expressions: `arr[0]` not `arr[ 0 ]` 🔧 |
331
334
  | **TypeScript Rules** | |
332
335
  | `enum-format` | Enforce enum naming (PascalCase + Enum suffix), UPPER_CASE members, no empty lines, and trailing commas 🔧 |
336
+ | `enum-type-enforcement` | Enforce using enum values instead of string literals for variables typed with `*Type` (e.g., use `ButtonVariantEnum.PRIMARY` not `"primary"`) 🔧 |
333
337
  | `interface-format` | Enforce interface naming (PascalCase + Interface suffix), camelCase properties, no empty lines, and trailing commas 🔧 |
334
338
  | `no-inline-type-definitions` | Inline union types in function params should be extracted to named types ⚙️ |
335
339
  | `type-annotation-spacing` | Enforce consistent spacing in type annotations: no space before colon/generic/array brackets, one space after colon 🔧 |
@@ -3066,7 +3070,7 @@ const UseAuth = () => {}; // hooks should be camelCase
3066
3070
 
3067
3071
  ## 🔧 Auto-fixing
3068
3072
 
3069
- 60 of 66 rules support auto-fixing. Run ESLint with the `--fix` flag:
3073
+ 63 of 69 rules support auto-fixing. Run ESLint with the `--fix` flag:
3070
3074
 
3071
3075
  ```bash
3072
3076
  # Fix all files in src directory
package/index.d.ts CHANGED
@@ -13,11 +13,14 @@ export type RuleNames =
13
13
  | "code-style/arrow-function-simplify"
14
14
  | "code-style/assignment-value-same-line"
15
15
  | "code-style/block-statement-newlines"
16
+ | "code-style/class-naming-convention"
16
17
  | "code-style/comment-format"
17
18
  | "code-style/component-props-destructure"
18
19
  | "code-style/react-code-order"
19
20
  | "code-style/component-props-inline-type"
20
21
  | "code-style/curried-arrow-same-line"
22
+ | "code-style/empty-line-after-block"
23
+ | "code-style/enum-type-enforcement"
21
24
  | "code-style/export-format"
22
25
  | "code-style/function-arguments-format"
23
26
  | "code-style/function-call-spacing"
@@ -101,11 +104,14 @@ interface PluginRules {
101
104
  "arrow-function-simplify": Rule.RuleModule;
102
105
  "assignment-value-same-line": Rule.RuleModule;
103
106
  "block-statement-newlines": Rule.RuleModule;
107
+ "class-naming-convention": Rule.RuleModule;
104
108
  "comment-format": Rule.RuleModule;
105
109
  "component-props-destructure": Rule.RuleModule;
106
110
  "react-code-order": Rule.RuleModule;
107
111
  "component-props-inline-type": Rule.RuleModule;
108
112
  "curried-arrow-same-line": Rule.RuleModule;
113
+ "empty-line-after-block": Rule.RuleModule;
114
+ "enum-type-enforcement": Rule.RuleModule;
109
115
  "export-format": Rule.RuleModule;
110
116
  "function-arguments-format": Rule.RuleModule;
111
117
  "function-call-spacing": Rule.RuleModule;
package/index.js CHANGED
@@ -3890,6 +3890,7 @@ const ternaryConditionMultiline = {
3890
3890
  const sourceCode = context.sourceCode || context.getSourceCode();
3891
3891
  const options = context.options[0] || {};
3892
3892
  const maxOperands = options.maxOperands ?? 3;
3893
+ const maxLineLength = options.maxLineLength ?? 120;
3893
3894
 
3894
3895
  // Check if node is wrapped in parentheses
3895
3896
  const isParenthesizedHandler = (node) => {
@@ -3984,115 +3985,240 @@ const ternaryConditionMultiline = {
3984
3985
  return false;
3985
3986
  };
3986
3987
 
3987
- return {
3988
- ConditionalExpression(node) {
3989
- const { test } = node;
3988
+ // Check if the test is a simple condition (not a complex logical expression)
3989
+ const isSimpleConditionHandler = (test) => {
3990
+ if (test.type === "Identifier") return true;
3990
3991
 
3991
- // Only handle ternaries with logical expression conditions
3992
- if (test.type !== "LogicalExpression") return;
3992
+ if (test.type === "MemberExpression") return true;
3993
3993
 
3994
- const operands = collectOperandsHandler(test);
3995
- const testStartLine = test.loc.start.line;
3996
- const testEndLine = test.loc.end.line;
3997
- const isMultiLine = testStartLine !== testEndLine;
3994
+ if (test.type === "UnaryExpression") return true;
3998
3995
 
3999
- // ≤maxOperands operands: keep on single line
4000
- if (operands.length <= maxOperands) {
4001
- const firstOperandStartLine = operands[0].loc.start.line;
4002
- const allOperandsStartOnSameLine = operands.every(
4003
- (op) => op.loc.start.line === firstOperandStartLine,
4004
- );
3996
+ if (test.type === "BinaryExpression") return true;
4005
3997
 
4006
- const hasSplitBinaryExpression = operands.some(
4007
- (op) => isBinaryExpressionSplitHandler(op),
4008
- );
3998
+ if (test.type === "CallExpression") return true;
4009
3999
 
4010
- if (!allOperandsStartOnSameLine || hasSplitBinaryExpression) {
4011
- context.report({
4012
- fix: (fixer) => {
4013
- const buildSameLineHandler = (n) => {
4014
- if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
4015
- const leftText = buildSameLineHandler(n.left);
4016
- const rightText = buildSameLineHandler(n.right);
4000
+ // Logical expression with ≤maxOperands is still simple
4001
+ if (test.type === "LogicalExpression") {
4002
+ return collectOperandsHandler(test).length <= maxOperands;
4003
+ }
4017
4004
 
4018
- return `${leftText} ${n.operator} ${rightText}`;
4019
- }
4005
+ return false;
4006
+ };
4020
4007
 
4021
- if (n.type === "BinaryExpression" && isBinaryExpressionSplitHandler(n)) {
4022
- return buildBinaryExpressionSingleLineHandler(n);
4023
- }
4008
+ // Get the full ternary as single line text
4009
+ const getTernarySingleLineHandler = (node) => {
4010
+ const testText = sourceCode.getText(node.test).replace(/\s+/g, " ").trim();
4011
+ const consequentText = sourceCode.getText(node.consequent).replace(/\s+/g, " ").trim();
4012
+ const alternateText = sourceCode.getText(node.alternate).replace(/\s+/g, " ").trim();
4024
4013
 
4025
- return getSourceTextWithGroupsHandler(n);
4026
- };
4014
+ return `${testText} ? ${consequentText} : ${alternateText}`;
4015
+ };
4027
4016
 
4028
- return fixer.replaceText(test, buildSameLineHandler(test));
4029
- },
4030
- message: `Ternary conditions with ≤${maxOperands} operands should be single line`,
4031
- node: test,
4032
- });
4033
- }
4017
+ // Get the indentation level for the line
4018
+ const getLineIndentHandler = (node) => {
4019
+ const lineText = sourceCode.lines[node.loc.start.line - 1];
4034
4020
 
4035
- return;
4036
- }
4021
+ return lineText.match(/^\s*/)[0].length;
4022
+ };
4037
4023
 
4038
- // More than maxOperands: each on its own line
4039
- let isCorrectionNeeded = !isMultiLine;
4024
+ // Check if branches have complex objects (should stay multiline)
4025
+ const hasComplexObjectHandler = (n) => {
4026
+ if (n.type === "ObjectExpression" && n.properties.length >= 2) return true;
4040
4027
 
4041
- if (isMultiLine) {
4042
- for (let i = 0; i < operands.length - 1; i += 1) {
4043
- if (operands[i].loc.end.line === operands[i + 1].loc.start.line) {
4044
- isCorrectionNeeded = true;
4045
- break;
4046
- }
4047
- }
4028
+ if (n.type === "ArrayExpression" && n.elements.length >= 3) return true;
4048
4029
 
4049
- // Check if any operator is at end of line (should be at beginning)
4050
- if (!isCorrectionNeeded && hasOperatorAtEndOfLineHandler(test)) {
4051
- isCorrectionNeeded = true;
4052
- }
4053
- }
4030
+ return false;
4031
+ };
4032
+
4033
+ // Handle simple ternaries - collapse to single line if they fit
4034
+ const handleSimpleTernaryHandler = (node) => {
4035
+ // Skip if already on single line
4036
+ if (node.loc.start.line === node.loc.end.line) return false;
4037
+
4038
+ // Skip nested ternaries
4039
+ if (node.consequent.type === "ConditionalExpression" || node.alternate.type === "ConditionalExpression") {
4040
+ return false;
4041
+ }
4042
+
4043
+ // Skip if branches have complex objects
4044
+ if (hasComplexObjectHandler(node.consequent) || hasComplexObjectHandler(node.alternate)) {
4045
+ return false;
4046
+ }
4047
+
4048
+ // Calculate what the single line would look like
4049
+ const singleLineText = getTernarySingleLineHandler(node);
4050
+ const indent = getLineIndentHandler(node);
4051
+
4052
+ // Check if the parent needs prefix text (like "const x = ")
4053
+ let prefixLength = 0;
4054
+ const parent = node.parent;
4055
+
4056
+ if (parent && parent.type === "VariableDeclarator" && parent.init === node) {
4057
+ // Calculate prefix based on parent's start line (where "const x = " is)
4058
+ const varDecl = parent.parent;
4059
+ const declKeyword = varDecl ? sourceCode.getFirstToken(varDecl).value : "const";
4060
+ const varName = parent.id.name || sourceCode.getText(parent.id);
4061
+
4062
+ // Prefix is "const varName = " or similar
4063
+ prefixLength = declKeyword.length + 1 + varName.length + 3; // keyword + space + name + " = "
4064
+ } else if (parent && parent.type === "AssignmentExpression" && parent.right === node) {
4065
+ // Calculate prefix based on left side of assignment
4066
+ const leftText = sourceCode.getText(parent.left);
4067
+
4068
+ prefixLength = leftText.length + 3; // left + " = "
4069
+ }
4070
+
4071
+ // Check if single line would fit
4072
+ const totalLength = indent + prefixLength + singleLineText.length + 1;
4054
4073
 
4055
- if (isCorrectionNeeded) {
4074
+ if (totalLength <= maxLineLength) {
4075
+ context.report({
4076
+ fix: (fixer) => fixer.replaceText(node, singleLineText),
4077
+ message: "Simple ternary should be on a single line",
4078
+ node,
4079
+ });
4080
+
4081
+ return true;
4082
+ }
4083
+
4084
+ return false;
4085
+ };
4086
+
4087
+ // Handle complex logical expressions - format multiline
4088
+ const handleComplexLogicalTernaryHandler = (node) => {
4089
+ const { test } = node;
4090
+ const operands = collectOperandsHandler(test);
4091
+ const testStartLine = test.loc.start.line;
4092
+ const testEndLine = test.loc.end.line;
4093
+ const isMultiLine = testStartLine !== testEndLine;
4094
+
4095
+ // ≤maxOperands operands: keep condition on single line
4096
+ if (operands.length <= maxOperands) {
4097
+ const firstOperandStartLine = operands[0].loc.start.line;
4098
+ const allOperandsStartOnSameLine = operands.every(
4099
+ (op) => op.loc.start.line === firstOperandStartLine,
4100
+ );
4101
+
4102
+ const hasSplitBinaryExpression = operands.some(
4103
+ (op) => isBinaryExpressionSplitHandler(op),
4104
+ );
4105
+
4106
+ if (!allOperandsStartOnSameLine || hasSplitBinaryExpression) {
4056
4107
  context.report({
4057
4108
  fix: (fixer) => {
4058
- // Get the indentation based on where the ternary starts
4059
- const lineText = sourceCode.lines[node.loc.start.line - 1];
4060
- const baseIndent = lineText.match(/^\s*/)[0];
4061
- const conditionIndent = baseIndent + " ";
4062
- const branchIndent = baseIndent + " ";
4063
-
4064
- const buildMultilineHandler = (n) => {
4109
+ const buildSameLineHandler = (n) => {
4065
4110
  if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
4066
- const leftText = buildMultilineHandler(n.left);
4067
- const rightText = buildMultilineHandler(n.right);
4111
+ const leftText = buildSameLineHandler(n.left);
4112
+ const rightText = buildSameLineHandler(n.right);
4068
4113
 
4069
- return `${leftText}\n${conditionIndent}${n.operator} ${rightText}`;
4114
+ return `${leftText} ${n.operator} ${rightText}`;
4115
+ }
4116
+
4117
+ if (n.type === "BinaryExpression" && isBinaryExpressionSplitHandler(n)) {
4118
+ return buildBinaryExpressionSingleLineHandler(n);
4070
4119
  }
4071
4120
 
4072
4121
  return getSourceTextWithGroupsHandler(n);
4073
4122
  };
4074
4123
 
4075
- const consequentText = sourceCode.getText(node.consequent);
4076
- const alternateText = sourceCode.getText(node.alternate);
4077
-
4078
- const newText = `\n${conditionIndent}${buildMultilineHandler(test)}\n${branchIndent}? ${consequentText}\n${branchIndent}: ${alternateText}`;
4079
-
4080
- return fixer.replaceText(node, newText);
4124
+ return fixer.replaceText(test, buildSameLineHandler(test));
4081
4125
  },
4082
- message: `Ternary conditions with more than ${maxOperands} operands should be multiline, with each operand on its own line`,
4126
+ message: `Ternary conditions with ≤${maxOperands} operands should be single line`,
4083
4127
  node: test,
4084
4128
  });
4085
4129
  }
4130
+
4131
+ return;
4132
+ }
4133
+
4134
+ // More than maxOperands: each on its own line
4135
+ let isCorrectionNeeded = !isMultiLine;
4136
+
4137
+ if (isMultiLine) {
4138
+ for (let i = 0; i < operands.length - 1; i += 1) {
4139
+ if (operands[i].loc.end.line === operands[i + 1].loc.start.line) {
4140
+ isCorrectionNeeded = true;
4141
+ break;
4142
+ }
4143
+ }
4144
+
4145
+ // Check if any operator is at end of line (should be at beginning)
4146
+ if (!isCorrectionNeeded && hasOperatorAtEndOfLineHandler(test)) {
4147
+ isCorrectionNeeded = true;
4148
+ }
4149
+
4150
+ // Check if ? is on same line as end of multiline condition (BAD - should be on its own line)
4151
+ if (!isCorrectionNeeded) {
4152
+ const questionToken = sourceCode.getTokenAfter(test, (t) => t.value === "?");
4153
+
4154
+ if (questionToken && questionToken.loc.start.line === test.loc.end.line) {
4155
+ isCorrectionNeeded = true;
4156
+ }
4157
+ }
4158
+ }
4159
+
4160
+ if (isCorrectionNeeded) {
4161
+ context.report({
4162
+ fix: (fixer) => {
4163
+ // Get the indentation based on where the ternary starts
4164
+ const lineText = sourceCode.lines[node.loc.start.line - 1];
4165
+ const baseIndent = lineText.match(/^\s*/)[0];
4166
+ const conditionIndent = baseIndent + " ";
4167
+ const branchIndent = baseIndent + " ";
4168
+
4169
+ const buildMultilineHandler = (n) => {
4170
+ if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
4171
+ const leftText = buildMultilineHandler(n.left);
4172
+ const rightText = buildMultilineHandler(n.right);
4173
+
4174
+ return `${leftText}\n${conditionIndent}${n.operator} ${rightText}`;
4175
+ }
4176
+
4177
+ return getSourceTextWithGroupsHandler(n);
4178
+ };
4179
+
4180
+ const consequentText = sourceCode.getText(node.consequent);
4181
+ const alternateText = sourceCode.getText(node.alternate);
4182
+
4183
+ const newText = `\n${conditionIndent}${buildMultilineHandler(test)}\n${branchIndent}? ${consequentText}\n${branchIndent}: ${alternateText}`;
4184
+
4185
+ return fixer.replaceText(node, newText);
4186
+ },
4187
+ message: `Ternary conditions with more than ${maxOperands} operands should be multiline, with each operand on its own line`,
4188
+ node: test,
4189
+ });
4190
+ }
4191
+ };
4192
+
4193
+ return {
4194
+ ConditionalExpression(node) {
4195
+ const { test } = node;
4196
+
4197
+ // First, try to collapse simple ternaries to single line
4198
+ if (isSimpleConditionHandler(test)) {
4199
+ if (handleSimpleTernaryHandler(node)) return;
4200
+ }
4201
+
4202
+ // For complex logical expressions, handle multiline formatting
4203
+ if (test.type === "LogicalExpression") {
4204
+ handleComplexLogicalTernaryHandler(node);
4205
+ }
4086
4206
  },
4087
4207
  };
4088
4208
  },
4089
4209
  meta: {
4090
- docs: { description: "Enforce multiline formatting for ternary expressions with complex conditions" },
4210
+ docs: { description: "Enforce consistent ternary formatting: collapse simple ternaries to single line, expand complex conditions to multiline" },
4091
4211
  fixable: "code",
4092
4212
  schema: [
4093
4213
  {
4094
4214
  additionalProperties: false,
4095
4215
  properties: {
4216
+ maxLineLength: {
4217
+ default: 120,
4218
+ description: "Maximum line length for single-line ternaries (default: 120)",
4219
+ minimum: 80,
4220
+ type: "integer",
4221
+ },
4096
4222
  maxOperands: {
4097
4223
  default: 3,
4098
4224
  description: "Maximum operands to keep on single line (default: 3)",
@@ -4107,6 +4233,372 @@ const ternaryConditionMultiline = {
4107
4233
  },
4108
4234
  };
4109
4235
 
4236
+ /**
4237
+ * ───────────────────────────────────────────────────────────────
4238
+ * Rule: Empty Line After Block
4239
+ * ───────────────────────────────────────────────────────────────
4240
+ *
4241
+ * Description:
4242
+ * Require an empty line between a closing brace `}` of a block
4243
+ * statement (if, try, for, while, etc.) and the next statement,
4244
+ * unless the next statement is part of the same construct (else, catch, finally).
4245
+ *
4246
+ * ✓ Good:
4247
+ * if (condition) {
4248
+ * doSomething();
4249
+ * }
4250
+ *
4251
+ * const x = 1;
4252
+ *
4253
+ * ✗ Bad:
4254
+ * if (condition) {
4255
+ * doSomething();
4256
+ * }
4257
+ * const x = 1;
4258
+ */
4259
+ const emptyLineAfterBlock = {
4260
+ create(context) {
4261
+ const sourceCode = context.sourceCode || context.getSourceCode();
4262
+
4263
+ // Check if a node is a block-containing statement
4264
+ const isBlockStatementHandler = (node) => {
4265
+ const blockTypes = [
4266
+ "IfStatement",
4267
+ "ForStatement",
4268
+ "ForInStatement",
4269
+ "ForOfStatement",
4270
+ "WhileStatement",
4271
+ "DoWhileStatement",
4272
+ "TryStatement",
4273
+ "SwitchStatement",
4274
+ "WithStatement",
4275
+ ];
4276
+
4277
+ return blockTypes.includes(node.type);
4278
+ };
4279
+
4280
+ // Get the actual end line of a statement (including else, catch, finally)
4281
+ const getStatementEndLineHandler = (node) => {
4282
+ if (node.type === "IfStatement" && node.alternate) {
4283
+ return getStatementEndLineHandler(node.alternate);
4284
+ }
4285
+
4286
+ if (node.type === "TryStatement") {
4287
+ if (node.finalizer) return node.finalizer.loc.end.line;
4288
+
4289
+ if (node.handler) return node.handler.loc.end.line;
4290
+ }
4291
+
4292
+ return node.loc.end.line;
4293
+ };
4294
+
4295
+ return {
4296
+ "BlockStatement:exit"(node) {
4297
+ const parent = node.parent;
4298
+
4299
+ // Only check for block-containing statements
4300
+ if (!parent || !isBlockStatementHandler(parent)) return;
4301
+
4302
+ // Skip if this block is followed by else, catch, or finally
4303
+ if (parent.type === "IfStatement" && parent.consequent === node && parent.alternate) {
4304
+ return;
4305
+ }
4306
+
4307
+ if (parent.type === "TryStatement" && (parent.block === node || parent.handler?.body === node) && (parent.handler || parent.finalizer)) {
4308
+ if (parent.block === node && (parent.handler || parent.finalizer)) return;
4309
+
4310
+ if (parent.handler?.body === node && parent.finalizer) return;
4311
+ }
4312
+
4313
+ // Get the parent's container (the block that contains the parent statement)
4314
+ const grandparent = parent.parent;
4315
+
4316
+ if (!grandparent || grandparent.type !== "BlockStatement") return;
4317
+
4318
+ // Find the index of the parent statement in the grandparent's body
4319
+ const stmtIndex = grandparent.body.indexOf(parent);
4320
+
4321
+ if (stmtIndex === -1 || stmtIndex === grandparent.body.length - 1) return;
4322
+
4323
+ // Get the next statement
4324
+ const nextStmt = grandparent.body[stmtIndex + 1];
4325
+
4326
+ // Get the actual end of the current statement
4327
+ const currentEndLine = getStatementEndLineHandler(parent);
4328
+ const nextStartLine = nextStmt.loc.start.line;
4329
+
4330
+ // Check if there's an empty line between them
4331
+ if (nextStartLine - currentEndLine < 2) {
4332
+ context.report({
4333
+ fix: (fixer) => {
4334
+ const endToken = sourceCode.getLastToken(parent);
4335
+
4336
+ return fixer.insertTextAfter(endToken, "\n");
4337
+ },
4338
+ message: "Expected empty line after block statement",
4339
+ node: nextStmt,
4340
+ });
4341
+ }
4342
+ },
4343
+ };
4344
+ },
4345
+ meta: {
4346
+ docs: { description: "Require empty line between block statement closing brace and next statement" },
4347
+ fixable: "whitespace",
4348
+ schema: [],
4349
+ type: "layout",
4350
+ },
4351
+ };
4352
+
4353
+ /**
4354
+ * ───────────────────────────────────────────────────────────────
4355
+ * Rule: Class Naming Convention
4356
+ * ───────────────────────────────────────────────────────────────
4357
+ *
4358
+ * Description:
4359
+ * Enforce that class declarations must end with "Class" suffix.
4360
+ * This distinguishes class definitions from other PascalCase names
4361
+ * like React components or type definitions.
4362
+ *
4363
+ * ✓ Good:
4364
+ * class ApiServiceClass { ... }
4365
+ * class UserRepositoryClass { ... }
4366
+ *
4367
+ * ✗ Bad:
4368
+ * class ApiService { ... }
4369
+ * class UserRepository { ... }
4370
+ */
4371
+ const classNamingConvention = {
4372
+ create(context) {
4373
+ const sourceCode = context.sourceCode || context.getSourceCode();
4374
+
4375
+ return {
4376
+ ClassDeclaration(node) {
4377
+ if (!node.id || !node.id.name) return;
4378
+
4379
+ const className = node.id.name;
4380
+
4381
+ if (!className.endsWith("Class")) {
4382
+ context.report({
4383
+ fix: (fixer) => {
4384
+ const newName = `${className}Class`;
4385
+
4386
+ // Find all references to this class and rename them
4387
+ const scope = context.sourceCode.getScope
4388
+ ? context.sourceCode.getScope(node)
4389
+ : context.getScope();
4390
+ const variable = scope.set.get(className);
4391
+ const fixes = [fixer.replaceText(node.id, newName)];
4392
+
4393
+ if (variable && variable.references) {
4394
+ variable.references.forEach((ref) => {
4395
+ if (ref.identifier !== node.id) {
4396
+ fixes.push(fixer.replaceText(ref.identifier, newName));
4397
+ }
4398
+ });
4399
+ }
4400
+
4401
+ return fixes;
4402
+ },
4403
+ message: `Class name "${className}" should end with "Class" suffix`,
4404
+ node: node.id,
4405
+ });
4406
+ }
4407
+ },
4408
+ };
4409
+ },
4410
+ meta: {
4411
+ docs: { description: "Enforce class names end with 'Class' suffix" },
4412
+ fixable: "code",
4413
+ schema: [],
4414
+ type: "suggestion",
4415
+ },
4416
+ };
4417
+
4418
+ /**
4419
+ * ───────────────────────────────────────────────────────────────
4420
+ * Rule: Enum Type Enforcement
4421
+ * ───────────────────────────────────────────────────────────────
4422
+ *
4423
+ * Description:
4424
+ * When a variable/parameter has a type like "ButtonVariantType",
4425
+ * enforce using the corresponding enum "ButtonVariantEnum.VALUE"
4426
+ * instead of string literals like "primary" or "ghost".
4427
+ *
4428
+ * The rule detects:
4429
+ * - Default values in destructuring: `variant = "primary"` → `variant = ButtonVariantEnum.PRIMARY`
4430
+ * - Comparisons: `variant === "ghost"` → `variant === ButtonVariantEnum.GHOST`
4431
+ * - Object property values matching the type
4432
+ *
4433
+ * ✓ Good:
4434
+ * const Button = ({ variant = ButtonVariantEnum.PRIMARY }: { variant?: ButtonVariantType }) => ...
4435
+ * if (variant === ButtonVariantEnum.GHOST) { ... }
4436
+ *
4437
+ * ✗ Bad:
4438
+ * const Button = ({ variant = "primary" }: { variant?: ButtonVariantType }) => ...
4439
+ * if (variant === "ghost") { ... }
4440
+ */
4441
+ const enumTypeEnforcement = {
4442
+ create(context) {
4443
+ const sourceCode = context.sourceCode || context.getSourceCode();
4444
+
4445
+ // Map to track variables with Type annotations and their corresponding Enum
4446
+ // e.g., "variant" -> { typeName: "ButtonVariantType", enumName: "ButtonVariantEnum" }
4447
+ const typeAnnotatedVars = new Map();
4448
+
4449
+ // Convert type name to enum name: ButtonVariantType -> ButtonVariantEnum
4450
+ const getEnumNameFromTypeHandler = (typeName) => {
4451
+ if (typeName.endsWith("Type")) {
4452
+ return typeName.slice(0, -4) + "Enum";
4453
+ }
4454
+
4455
+ return null;
4456
+ };
4457
+
4458
+ // Convert string literal to enum member: "primary" -> "PRIMARY", "ghost-danger" -> "GHOST_DANGER"
4459
+ const toEnumMemberHandler = (str) => str.toUpperCase().replace(/-/g, "_");
4460
+
4461
+ // Check if a type annotation references a Type that has a corresponding Enum
4462
+ const extractTypeInfoHandler = (typeAnnotation) => {
4463
+ if (!typeAnnotation) return null;
4464
+
4465
+ const annotation = typeAnnotation.typeAnnotation;
4466
+
4467
+ if (!annotation) return null;
4468
+
4469
+ // Handle direct type reference: : ButtonVariantType
4470
+ if (annotation.type === "TSTypeReference" && annotation.typeName?.type === "Identifier") {
4471
+ const typeName = annotation.typeName.name;
4472
+
4473
+ if (typeName.endsWith("Type")) {
4474
+ return {
4475
+ enumName: getEnumNameFromTypeHandler(typeName),
4476
+ typeName,
4477
+ };
4478
+ }
4479
+ }
4480
+
4481
+ return null;
4482
+ };
4483
+
4484
+ // Track type-annotated parameters in function/component definitions
4485
+ const trackTypedParamsHandler = (params) => {
4486
+ params.forEach((param) => {
4487
+ // Handle destructured params: ({ variant }: { variant?: ButtonVariantType })
4488
+ if (param.type === "ObjectPattern" && param.typeAnnotation) {
4489
+ const annotation = param.typeAnnotation.typeAnnotation;
4490
+
4491
+ if (annotation && annotation.type === "TSTypeLiteral") {
4492
+ annotation.members.forEach((member) => {
4493
+ if (member.type === "TSPropertySignature" && member.key?.type === "Identifier") {
4494
+ const propName = member.key.name;
4495
+ const typeInfo = extractTypeInfoHandler(member.typeAnnotation);
4496
+
4497
+ if (typeInfo) {
4498
+ typeAnnotatedVars.set(propName, typeInfo);
4499
+ }
4500
+ }
4501
+ });
4502
+ }
4503
+ }
4504
+
4505
+ // Handle simple typed param: (variant: ButtonVariantType)
4506
+ if (param.type === "Identifier" && param.typeAnnotation) {
4507
+ const typeInfo = extractTypeInfoHandler(param);
4508
+
4509
+ if (typeInfo) {
4510
+ typeAnnotatedVars.set(param.name, typeInfo);
4511
+ }
4512
+ }
4513
+ });
4514
+ };
4515
+
4516
+ return {
4517
+ // Track function parameters
4518
+ "ArrowFunctionExpression, FunctionDeclaration, FunctionExpression"(node) {
4519
+ trackTypedParamsHandler(node.params);
4520
+ },
4521
+
4522
+ // Check default values in destructuring patterns
4523
+ AssignmentPattern(node) {
4524
+ // Pattern like: variant = "primary"
4525
+ if (node.left.type !== "Identifier") return;
4526
+
4527
+ const varName = node.left.name;
4528
+ const typeInfo = typeAnnotatedVars.get(varName);
4529
+
4530
+ if (!typeInfo) return;
4531
+
4532
+ // Check if the default is a string literal
4533
+ if (node.right.type === "Literal" && typeof node.right.value === "string") {
4534
+ const stringValue = node.right.value;
4535
+ const enumMember = toEnumMemberHandler(stringValue);
4536
+ const replacement = `${typeInfo.enumName}.${enumMember}`;
4537
+
4538
+ context.report({
4539
+ fix: (fixer) => fixer.replaceText(node.right, replacement),
4540
+ message: `Use "${replacement}" instead of string literal "${stringValue}"`,
4541
+ node: node.right,
4542
+ });
4543
+ }
4544
+ },
4545
+
4546
+ // Check comparisons: variant === "ghost"
4547
+ BinaryExpression(node) {
4548
+ if (node.operator !== "===" && node.operator !== "!==") return;
4549
+
4550
+ let varNode = null;
4551
+ let literalNode = null;
4552
+
4553
+ if (node.left.type === "Identifier" && node.right.type === "Literal") {
4554
+ varNode = node.left;
4555
+ literalNode = node.right;
4556
+ } else if (node.right.type === "Identifier" && node.left.type === "Literal") {
4557
+ varNode = node.right;
4558
+ literalNode = node.left;
4559
+ }
4560
+
4561
+ if (!varNode || !literalNode) return;
4562
+
4563
+ if (typeof literalNode.value !== "string") return;
4564
+
4565
+ const typeInfo = typeAnnotatedVars.get(varNode.name);
4566
+
4567
+ if (!typeInfo) return;
4568
+
4569
+ const stringValue = literalNode.value;
4570
+ const enumMember = toEnumMemberHandler(stringValue);
4571
+ const replacement = `${typeInfo.enumName}.${enumMember}`;
4572
+
4573
+ context.report({
4574
+ fix: (fixer) => fixer.replaceText(literalNode, replacement),
4575
+ message: `Use "${replacement}" instead of string literal "${stringValue}"`,
4576
+ node: literalNode,
4577
+ });
4578
+ },
4579
+
4580
+ // Clear tracked vars when exiting function scope
4581
+ "ArrowFunctionExpression:exit"() {
4582
+ typeAnnotatedVars.clear();
4583
+ },
4584
+
4585
+ "FunctionDeclaration:exit"() {
4586
+ typeAnnotatedVars.clear();
4587
+ },
4588
+
4589
+ "FunctionExpression:exit"() {
4590
+ typeAnnotatedVars.clear();
4591
+ },
4592
+ };
4593
+ },
4594
+ meta: {
4595
+ docs: { description: "Enforce using enum values instead of string literals for typed variables" },
4596
+ fixable: "code",
4597
+ schema: [],
4598
+ type: "suggestion",
4599
+ },
4600
+ };
4601
+
4110
4602
  /**
4111
4603
  * ───────────────────────────────────────────────────────────────
4112
4604
  * Rule: Absolute Imports Only
@@ -9085,6 +9577,29 @@ const noEmptyLinesInFunctionParams = {
9085
9577
  });
9086
9578
  }
9087
9579
  }
9580
+
9581
+ // Check inside ObjectPattern params for empty lines between destructured props
9582
+ params.forEach((param) => {
9583
+ if (param.type === "ObjectPattern" && param.properties.length > 1) {
9584
+ for (let i = 0; i < param.properties.length - 1; i += 1) {
9585
+ const current = param.properties[i];
9586
+ const next = param.properties[i + 1];
9587
+
9588
+ if (next.loc.start.line - current.loc.end.line > 1) {
9589
+ const commaToken = sourceCode.getTokenAfter(current, (t) => t.value === ",");
9590
+
9591
+ context.report({
9592
+ fix: (fixer) => fixer.replaceTextRange(
9593
+ [commaToken.range[1], next.range[0]],
9594
+ "\n" + " ".repeat(next.loc.start.column),
9595
+ ),
9596
+ message: "No empty lines between destructured properties",
9597
+ node: next,
9598
+ });
9599
+ }
9600
+ }
9601
+ }
9602
+ });
9088
9603
  };
9089
9604
 
9090
9605
  return {
@@ -13747,6 +14262,161 @@ const componentPropsInlineType = {
13747
14262
  }
13748
14263
  }
13749
14264
 
14265
+ // Handle intersection types: ButtonHTMLAttributes<HTMLButtonElement> & { prop: Type }
14266
+ if (typeAnnotation.type === "TSIntersectionType" && isComponent) {
14267
+ const types = typeAnnotation.types;
14268
+
14269
+ // Check & operators are on same line as previous type
14270
+ for (let i = 0; i < types.length - 1; i += 1) {
14271
+ const currentType = types[i];
14272
+ const nextType = types[i + 1];
14273
+
14274
+ const ampersandToken = sourceCode.getTokenAfter(currentType, (t) => t.value === "&");
14275
+
14276
+ if (ampersandToken && ampersandToken.loc.start.line !== currentType.loc.end.line) {
14277
+ context.report({
14278
+ fix: (fixer) => fixer.replaceTextRange(
14279
+ [currentType.range[1], ampersandToken.range[1]],
14280
+ " &",
14281
+ ),
14282
+ message: "\"&\" must be on same line as previous type",
14283
+ node: ampersandToken,
14284
+ });
14285
+ }
14286
+
14287
+ // { should be on same line as &
14288
+ if (nextType.type === "TSTypeLiteral" && ampersandToken) {
14289
+ const openBrace = sourceCode.getFirstToken(nextType);
14290
+
14291
+ if (openBrace && openBrace.loc.start.line !== ampersandToken.loc.end.line) {
14292
+ context.report({
14293
+ fix: (fixer) => fixer.replaceTextRange(
14294
+ [ampersandToken.range[1], openBrace.range[0]],
14295
+ " ",
14296
+ ),
14297
+ message: "Opening brace must be on same line as \"&\"",
14298
+ node: openBrace,
14299
+ });
14300
+ }
14301
+ }
14302
+ }
14303
+
14304
+ // Find TSTypeLiteral in the intersection and apply formatting rules
14305
+ const typeLiteral = types.find((t) => t.type === "TSTypeLiteral");
14306
+
14307
+ if (typeLiteral) {
14308
+ const members = typeLiteral.members;
14309
+
14310
+ // Get the base indentation from the component declaration
14311
+ const componentLine = sourceCode.lines[node.loc.start.line - 1];
14312
+ const baseIndent = componentLine.match(/^\s*/)[0];
14313
+ const propIndent = baseIndent + " ";
14314
+
14315
+ // Get opening and closing brace tokens
14316
+ const openBraceToken = sourceCode.getFirstToken(typeLiteral);
14317
+ const closeBraceToken = sourceCode.getLastToken(typeLiteral);
14318
+
14319
+ // For multiple members, first member should be on new line after opening brace
14320
+ if (members.length > 1 && members[0]) {
14321
+ const firstMember = members[0];
14322
+
14323
+ if (firstMember.loc.start.line === openBraceToken.loc.end.line) {
14324
+ context.report({
14325
+ fix: (fixer) => fixer.replaceTextRange(
14326
+ [openBraceToken.range[1], firstMember.range[0]],
14327
+ "\n" + propIndent,
14328
+ ),
14329
+ message: "First props type property must be on a new line when there are multiple properties",
14330
+ node: firstMember,
14331
+ });
14332
+ }
14333
+ }
14334
+
14335
+ // Check closing brace position - should be on its own line for multiple members
14336
+ if (members.length > 1 && closeBraceToken) {
14337
+ const lastMember = members[members.length - 1];
14338
+
14339
+ if (closeBraceToken.loc.start.line === lastMember.loc.end.line) {
14340
+ context.report({
14341
+ fix: (fixer) => fixer.replaceTextRange(
14342
+ [lastMember.range[1], closeBraceToken.range[0]],
14343
+ "\n" + baseIndent,
14344
+ ),
14345
+ message: "Closing brace must be on its own line when there are multiple properties",
14346
+ node: closeBraceToken,
14347
+ });
14348
+ }
14349
+ }
14350
+
14351
+ // Check each member for formatting
14352
+ members.forEach((member, index) => {
14353
+ const memberText = sourceCode.getText(member);
14354
+
14355
+ // Check property ends with comma, not semicolon
14356
+ if (memberText.trimEnd().endsWith(";")) {
14357
+ context.report({
14358
+ fix: (fixer) => {
14359
+ const lastChar = memberText.lastIndexOf(";");
14360
+ const absolutePos = member.range[0] + lastChar;
14361
+
14362
+ return fixer.replaceTextRange([absolutePos, absolutePos + 1], ",");
14363
+ },
14364
+ message: "Props type properties must end with comma (,) not semicolon (;)",
14365
+ node: member,
14366
+ });
14367
+ }
14368
+
14369
+ // If more than one member, check each is on its own line
14370
+ if (members.length > 1 && index > 0) {
14371
+ const prevMember = members[index - 1];
14372
+
14373
+ if (member.loc.start.line === prevMember.loc.end.line) {
14374
+ context.report({
14375
+ fix: (fixer) => {
14376
+ let commaToken = sourceCode.getTokenAfter(prevMember);
14377
+
14378
+ while (commaToken && commaToken.value !== "," && commaToken.range[0] < member.range[0]) {
14379
+ commaToken = sourceCode.getTokenAfter(commaToken);
14380
+ }
14381
+
14382
+ const insertPoint = commaToken && commaToken.value === "," ? commaToken.range[1] : prevMember.range[1];
14383
+
14384
+ return fixer.replaceTextRange(
14385
+ [insertPoint, member.range[0]],
14386
+ "\n" + propIndent,
14387
+ );
14388
+ },
14389
+ message: "Each props type property must be on its own line when there are multiple properties",
14390
+ node: member,
14391
+ });
14392
+ }
14393
+
14394
+ // Check for empty lines between properties
14395
+ if (member.loc.start.line - prevMember.loc.end.line > 1) {
14396
+ context.report({
14397
+ fix: (fixer) => {
14398
+ const textBetween = sourceCode.getText().slice(
14399
+ prevMember.range[1],
14400
+ member.range[0],
14401
+ );
14402
+ const newText = textBetween.replace(/\n\s*\n/g, "\n");
14403
+
14404
+ return fixer.replaceTextRange(
14405
+ [prevMember.range[1], member.range[0]],
14406
+ newText,
14407
+ );
14408
+ },
14409
+ message: "No empty lines allowed between props type properties",
14410
+ node: member,
14411
+ });
14412
+ }
14413
+ }
14414
+ });
14415
+ }
14416
+
14417
+ return;
14418
+ }
14419
+
13750
14420
  // Check if type is a reference (TSTypeReference) instead of inline (TSTypeLiteral)
13751
14421
  // Only enforce inline types for React components - regular functions can use interface/type references
13752
14422
  if (typeAnnotation.type === "TSTypeReference") {
@@ -16694,12 +17364,16 @@ export default {
16694
17364
 
16695
17365
  // Control flow rules
16696
17366
  "block-statement-newlines": blockStatementNewlines,
17367
+ "empty-line-after-block": emptyLineAfterBlock,
16697
17368
  "if-else-spacing": ifElseSpacing,
16698
17369
  "if-statement-format": ifStatementFormat,
16699
17370
  "multiline-if-conditions": multilineIfConditions,
16700
17371
  "no-empty-lines-in-switch-cases": noEmptyLinesInSwitchCases,
16701
17372
  "ternary-condition-multiline": ternaryConditionMultiline,
16702
17373
 
17374
+ // Class rules
17375
+ "class-naming-convention": classNamingConvention,
17376
+
16703
17377
  // Function rules
16704
17378
  "function-call-spacing": functionCallSpacing,
16705
17379
  "function-declaration-style": functionDeclarationStyle,
@@ -16756,6 +17430,9 @@ export default {
16756
17430
  "type-format": typeFormat,
16757
17431
  "typescript-definition-location": typescriptDefinitionLocation,
16758
17432
 
17433
+ // Type/Enum rules
17434
+ "enum-type-enforcement": enumTypeEnforcement,
17435
+
16759
17436
  // Variable rules
16760
17437
  "variable-naming-convention": variableNamingConvention,
16761
17438
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.6.6",
3
+ "version": "1.7.1",
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",