eslint-plugin-code-style 1.7.1 → 1.7.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 +12 -2
  2. package/index.js +246 -32
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,14 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.7.2] - 2026-02-02
11
+
12
+ ### Fixed
13
+
14
+ - **`enum-format`** - Fix double comma bug when auto-fixing trailing comma and closing brace position; check for comma token after member, not just member text
15
+ - **`interface-format`** - Same fix as enum-format for trailing comma detection
16
+
17
+ ---
18
+
10
19
  ## [1.7.1] - 2026-02-02
11
20
 
12
21
  ### Fixed
13
22
 
14
23
  - **`no-empty-lines-in-function-params`** - Detect empty lines between destructured properties inside ObjectPattern params
15
24
  - **`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
25
+ - **`enum-type-enforcement`** - Handle TSIntersectionType to track typed props; fix extractTypeInfoHandler argument for TSPropertySignature members
26
+ - **`ternary-condition-multiline`** - Improve simple ternary prefix calculation for object properties; add checks for `?` on same line as condition end and empty lines before `?` or `:`; fix multiline formatting to not add leading newline
18
27
 
19
28
  ---
20
29
 
@@ -1075,6 +1084,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1075
1084
 
1076
1085
  ---
1077
1086
 
1087
+ [1.7.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.1...v1.7.2
1078
1088
  [1.7.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.0...v1.7.1
1079
1089
  [1.7.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.6...v1.7.0
1080
1090
  [1.6.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.5...v1.6.6
package/index.js CHANGED
@@ -4049,7 +4049,7 @@ const ternaryConditionMultiline = {
4049
4049
  const singleLineText = getTernarySingleLineHandler(node);
4050
4050
  const indent = getLineIndentHandler(node);
4051
4051
 
4052
- // Check if the parent needs prefix text (like "const x = ")
4052
+ // Check if the parent needs prefix text (like "const x = " or "key: ")
4053
4053
  let prefixLength = 0;
4054
4054
  const parent = node.parent;
4055
4055
 
@@ -4066,6 +4066,11 @@ const ternaryConditionMultiline = {
4066
4066
  const leftText = sourceCode.getText(parent.left);
4067
4067
 
4068
4068
  prefixLength = leftText.length + 3; // left + " = "
4069
+ } else if (parent && parent.type === "Property" && parent.value === node) {
4070
+ // Object property: key: ternary
4071
+ const keyText = sourceCode.getText(parent.key);
4072
+
4073
+ prefixLength = keyText.length + 2; // key + ": "
4069
4074
  }
4070
4075
 
4071
4076
  // Check if single line would fit
@@ -4155,6 +4160,24 @@ const ternaryConditionMultiline = {
4155
4160
  isCorrectionNeeded = true;
4156
4161
  }
4157
4162
  }
4163
+
4164
+ // Check for empty lines before ? (between condition and ?)
4165
+ if (!isCorrectionNeeded) {
4166
+ const questionToken = sourceCode.getTokenAfter(test, (t) => t.value === "?");
4167
+
4168
+ if (questionToken && questionToken.loc.start.line > test.loc.end.line + 1) {
4169
+ isCorrectionNeeded = true;
4170
+ }
4171
+ }
4172
+
4173
+ // Check for empty lines before : (between consequent and :)
4174
+ if (!isCorrectionNeeded) {
4175
+ const colonToken = sourceCode.getTokenAfter(node.consequent, (t) => t.value === ":");
4176
+
4177
+ if (colonToken && colonToken.loc.start.line > node.consequent.loc.end.line + 1) {
4178
+ isCorrectionNeeded = true;
4179
+ }
4180
+ }
4158
4181
  }
4159
4182
 
4160
4183
  if (isCorrectionNeeded) {
@@ -4164,7 +4187,6 @@ const ternaryConditionMultiline = {
4164
4187
  const lineText = sourceCode.lines[node.loc.start.line - 1];
4165
4188
  const baseIndent = lineText.match(/^\s*/)[0];
4166
4189
  const conditionIndent = baseIndent + " ";
4167
- const branchIndent = baseIndent + " ";
4168
4190
 
4169
4191
  const buildMultilineHandler = (n) => {
4170
4192
  if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
@@ -4180,7 +4202,9 @@ const ternaryConditionMultiline = {
4180
4202
  const consequentText = sourceCode.getText(node.consequent);
4181
4203
  const alternateText = sourceCode.getText(node.alternate);
4182
4204
 
4183
- const newText = `\n${conditionIndent}${buildMultilineHandler(test)}\n${branchIndent}? ${consequentText}\n${branchIndent}: ${alternateText}`;
4205
+ // No leading newline - keep first operand on same line as whatever precedes it
4206
+ // Use conditionIndent for ? and : to align with || operators
4207
+ const newText = `${buildMultilineHandler(test)}\n${conditionIndent}? ${consequentText}\n${conditionIndent}: ${alternateText}`;
4184
4208
 
4185
4209
  return fixer.replaceText(node, newText);
4186
4210
  },
@@ -4481,6 +4505,20 @@ const enumTypeEnforcement = {
4481
4505
  return null;
4482
4506
  };
4483
4507
 
4508
+ // Helper to process TSTypeLiteral members
4509
+ const processTypeLiteralMembersHandler = (members) => {
4510
+ members.forEach((member) => {
4511
+ if (member.type === "TSPropertySignature" && member.key?.type === "Identifier") {
4512
+ const propName = member.key.name;
4513
+ const typeInfo = extractTypeInfoHandler(member.typeAnnotation);
4514
+
4515
+ if (typeInfo) {
4516
+ typeAnnotatedVars.set(propName, typeInfo);
4517
+ }
4518
+ }
4519
+ });
4520
+ };
4521
+
4484
4522
  // Track type-annotated parameters in function/component definitions
4485
4523
  const trackTypedParamsHandler = (params) => {
4486
4524
  params.forEach((param) => {
@@ -4489,14 +4527,14 @@ const enumTypeEnforcement = {
4489
4527
  const annotation = param.typeAnnotation.typeAnnotation;
4490
4528
 
4491
4529
  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);
4530
+ processTypeLiteralMembersHandler(annotation.members);
4531
+ }
4496
4532
 
4497
- if (typeInfo) {
4498
- typeAnnotatedVars.set(propName, typeInfo);
4499
- }
4533
+ // Handle intersection types: ButtonHTMLAttributes<HTMLButtonElement> & { variant?: ButtonVariantType }
4534
+ if (annotation && annotation.type === "TSIntersectionType") {
4535
+ annotation.types.forEach((intersectionType) => {
4536
+ if (intersectionType.type === "TSTypeLiteral") {
4537
+ processTypeLiteralMembersHandler(intersectionType.members);
4500
4538
  }
4501
4539
  });
4502
4540
  }
@@ -14412,6 +14450,36 @@ const componentPropsInlineType = {
14412
14450
  }
14413
14451
  }
14414
14452
  });
14453
+
14454
+ // Check that last member has trailing comma
14455
+ if (members.length > 0) {
14456
+ const lastMember = members[members.length - 1];
14457
+ const lastMemberText = sourceCode.getText(lastMember);
14458
+
14459
+ if (!lastMemberText.trimEnd().endsWith(",")) {
14460
+ context.report({
14461
+ fix: (fixer) => fixer.insertTextAfter(lastMember, ","),
14462
+ message: "Last props type property must have trailing comma",
14463
+ node: lastMember,
14464
+ });
14465
+ }
14466
+ }
14467
+
14468
+ // Check for empty lines before closing brace
14469
+ if (members.length > 0 && closeBraceToken) {
14470
+ const lastMember = members[members.length - 1];
14471
+
14472
+ if (closeBraceToken.loc.start.line - lastMember.loc.end.line > 1) {
14473
+ context.report({
14474
+ fix: (fixer) => fixer.replaceTextRange(
14475
+ [lastMember.range[1], closeBraceToken.range[0]],
14476
+ "\n" + baseIndent,
14477
+ ),
14478
+ message: "No empty line before closing brace in props type",
14479
+ node: closeBraceToken,
14480
+ });
14481
+ }
14482
+ }
14415
14483
  }
14416
14484
 
14417
14485
  return;
@@ -14619,6 +14687,20 @@ const componentPropsInlineType = {
14619
14687
  }
14620
14688
  }
14621
14689
  });
14690
+
14691
+ // Check that last member has trailing comma
14692
+ if (members.length > 0) {
14693
+ const lastMember = members[members.length - 1];
14694
+ const lastMemberText = sourceCode.getText(lastMember);
14695
+
14696
+ if (!lastMemberText.trimEnd().endsWith(",")) {
14697
+ context.report({
14698
+ fix: (fixer) => fixer.insertTextAfter(lastMember, ","),
14699
+ message: "Last props type property must have trailing comma",
14700
+ node: lastMember,
14701
+ });
14702
+ }
14703
+ }
14622
14704
  }
14623
14705
  };
14624
14706
 
@@ -16956,19 +17038,24 @@ const enumFormat = {
16956
17038
  }
16957
17039
 
16958
17040
  // Check member ends with comma, not semicolon
16959
- const memberText = sourceCode.getText(member);
17041
+ // Skip last member when multiple members - handled by combined check below
17042
+ const isLastMember = index === members.length - 1;
16960
17043
 
16961
- if (memberText.trimEnd().endsWith(";")) {
16962
- context.report({
16963
- fix(fixer) {
16964
- const lastChar = memberText.lastIndexOf(";");
16965
- const absolutePos = member.range[0] + lastChar;
17044
+ if (!isLastMember || members.length === 1) {
17045
+ const memberText = sourceCode.getText(member);
16966
17046
 
16967
- return fixer.replaceTextRange([absolutePos, absolutePos + 1], ",");
16968
- },
16969
- message: "Enum members must end with comma (,) not semicolon (;)",
16970
- node: member,
16971
- });
17047
+ if (memberText.trimEnd().endsWith(";")) {
17048
+ context.report({
17049
+ fix(fixer) {
17050
+ const lastChar = memberText.lastIndexOf(";");
17051
+ const absolutePos = member.range[0] + lastChar;
17052
+
17053
+ return fixer.replaceTextRange([absolutePos, absolutePos + 1], ",");
17054
+ },
17055
+ message: "Enum members must end with comma (,) not semicolon (;)",
17056
+ node: member,
17057
+ });
17058
+ }
16972
17059
  }
16973
17060
 
16974
17061
  // Check formatting for multiple members
@@ -17018,6 +17105,67 @@ const enumFormat = {
17018
17105
  }
17019
17106
  }
17020
17107
  });
17108
+
17109
+ // Check closing brace position and trailing comma/semicolon (for multiple members)
17110
+ if (members.length > 1) {
17111
+ const lastMemberText = sourceCode.getText(lastMember);
17112
+ const trimmedText = lastMemberText.trimEnd();
17113
+ // Check both: text ends with comma OR there's a comma token after the member
17114
+ const tokenAfterLast = sourceCode.getTokenAfter(lastMember);
17115
+ const hasTrailingComma = trimmedText.endsWith(",") || (tokenAfterLast && tokenAfterLast.value === ",");
17116
+ const hasTrailingSemicolon = trimmedText.endsWith(";");
17117
+ const braceOnSameLine = closeBraceToken && closeBraceToken.loc.start.line === lastMember.loc.end.line;
17118
+
17119
+ // Handle semicolon on last member (needs replacement with comma)
17120
+ if (hasTrailingSemicolon) {
17121
+ const lastSemicolon = lastMemberText.lastIndexOf(";");
17122
+ const absolutePos = lastMember.range[0] + lastSemicolon;
17123
+
17124
+ if (braceOnSameLine) {
17125
+ // Both semicolon and brace issues - fix together
17126
+ context.report({
17127
+ fix: (fixer) => fixer.replaceTextRange(
17128
+ [absolutePos, closeBraceToken.range[0]],
17129
+ ",\n" + baseIndent,
17130
+ ),
17131
+ message: "Last enum member must end with comma and closing brace must be on its own line",
17132
+ node: lastMember,
17133
+ });
17134
+ } else {
17135
+ // Just semicolon issue
17136
+ context.report({
17137
+ fix: (fixer) => fixer.replaceTextRange([absolutePos, absolutePos + 1], ","),
17138
+ message: "Enum members must end with comma (,) not semicolon (;)",
17139
+ node: lastMember,
17140
+ });
17141
+ }
17142
+ } else if (!hasTrailingComma && braceOnSameLine) {
17143
+ // Both missing comma and brace issues - fix together
17144
+ context.report({
17145
+ fix: (fixer) => fixer.replaceTextRange(
17146
+ [lastMember.range[1], closeBraceToken.range[0]],
17147
+ ",\n" + baseIndent,
17148
+ ),
17149
+ message: "Last enum member must have trailing comma and closing brace must be on its own line",
17150
+ node: lastMember,
17151
+ });
17152
+ } else if (!hasTrailingComma) {
17153
+ context.report({
17154
+ fix: (fixer) => fixer.insertTextAfter(lastMember, ","),
17155
+ message: "Last enum member must have trailing comma",
17156
+ node: lastMember,
17157
+ });
17158
+ } else if (braceOnSameLine) {
17159
+ context.report({
17160
+ fix: (fixer) => fixer.replaceTextRange(
17161
+ [lastMember.range[1], closeBraceToken.range[0]],
17162
+ "\n" + baseIndent,
17163
+ ),
17164
+ message: "Closing brace must be on its own line",
17165
+ node: closeBraceToken,
17166
+ });
17167
+ }
17168
+ }
17021
17169
  },
17022
17170
  };
17023
17171
  },
@@ -17201,19 +17349,24 @@ const interfaceFormat = {
17201
17349
  }
17202
17350
 
17203
17351
  // Check property ends with comma, not semicolon
17204
- const memberText = sourceCode.getText(member);
17352
+ // Skip last member when multiple members - handled by combined check below
17353
+ const isLastMember = index === members.length - 1;
17205
17354
 
17206
- if (memberText.trimEnd().endsWith(";")) {
17207
- context.report({
17208
- fix(fixer) {
17209
- const lastChar = memberText.lastIndexOf(";");
17210
- const absolutePos = member.range[0] + lastChar;
17355
+ if (!isLastMember || members.length === 1) {
17356
+ const memberText = sourceCode.getText(member);
17211
17357
 
17212
- return fixer.replaceTextRange([absolutePos, absolutePos + 1], ",");
17213
- },
17214
- message: "Interface properties must end with comma (,) not semicolon (;)",
17215
- node: member,
17216
- });
17358
+ if (memberText.trimEnd().endsWith(";")) {
17359
+ context.report({
17360
+ fix(fixer) {
17361
+ const lastChar = memberText.lastIndexOf(";");
17362
+ const absolutePos = member.range[0] + lastChar;
17363
+
17364
+ return fixer.replaceTextRange([absolutePos, absolutePos + 1], ",");
17365
+ },
17366
+ message: "Interface properties must end with comma (,) not semicolon (;)",
17367
+ node: member,
17368
+ });
17369
+ }
17217
17370
  }
17218
17371
 
17219
17372
  // Check formatting for multiple members
@@ -17263,6 +17416,67 @@ const interfaceFormat = {
17263
17416
  }
17264
17417
  }
17265
17418
  });
17419
+
17420
+ // Check closing brace position and trailing comma/semicolon (for multiple members)
17421
+ if (members.length > 1) {
17422
+ const lastMemberText = sourceCode.getText(lastMember);
17423
+ const trimmedText = lastMemberText.trimEnd();
17424
+ // Check both: text ends with comma OR there's a comma token after the member
17425
+ const tokenAfterLast = sourceCode.getTokenAfter(lastMember);
17426
+ const hasTrailingComma = trimmedText.endsWith(",") || (tokenAfterLast && tokenAfterLast.value === ",");
17427
+ const hasTrailingSemicolon = trimmedText.endsWith(";");
17428
+ const braceOnSameLine = closeBraceToken.loc.start.line === lastMember.loc.end.line;
17429
+
17430
+ // Handle semicolon on last member (needs replacement with comma)
17431
+ if (hasTrailingSemicolon) {
17432
+ const lastSemicolon = lastMemberText.lastIndexOf(";");
17433
+ const absolutePos = lastMember.range[0] + lastSemicolon;
17434
+
17435
+ if (braceOnSameLine) {
17436
+ // Both semicolon and brace issues - fix together
17437
+ context.report({
17438
+ fix: (fixer) => fixer.replaceTextRange(
17439
+ [absolutePos, closeBraceToken.range[0]],
17440
+ ",\n" + baseIndent,
17441
+ ),
17442
+ message: "Last interface property must end with comma and closing brace must be on its own line",
17443
+ node: lastMember,
17444
+ });
17445
+ } else {
17446
+ // Just semicolon issue
17447
+ context.report({
17448
+ fix: (fixer) => fixer.replaceTextRange([absolutePos, absolutePos + 1], ","),
17449
+ message: "Interface properties must end with comma (,) not semicolon (;)",
17450
+ node: lastMember,
17451
+ });
17452
+ }
17453
+ } else if (!hasTrailingComma && braceOnSameLine) {
17454
+ // Both missing comma and brace issues - fix together
17455
+ context.report({
17456
+ fix: (fixer) => fixer.replaceTextRange(
17457
+ [lastMember.range[1], closeBraceToken.range[0]],
17458
+ ",\n" + baseIndent,
17459
+ ),
17460
+ message: "Last interface property must have trailing comma and closing brace must be on its own line",
17461
+ node: lastMember,
17462
+ });
17463
+ } else if (!hasTrailingComma) {
17464
+ context.report({
17465
+ fix: (fixer) => fixer.insertTextAfter(lastMember, ","),
17466
+ message: "Last interface property must have trailing comma",
17467
+ node: lastMember,
17468
+ });
17469
+ } else if (braceOnSameLine) {
17470
+ context.report({
17471
+ fix: (fixer) => fixer.replaceTextRange(
17472
+ [lastMember.range[1], closeBraceToken.range[0]],
17473
+ "\n" + baseIndent,
17474
+ ),
17475
+ message: "Closing brace must be on its own line",
17476
+ node: closeBraceToken,
17477
+ });
17478
+ }
17479
+ }
17266
17480
  },
17267
17481
  };
17268
17482
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.7.1",
3
+ "version": "1.7.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",