eslint-plugin-code-style 1.7.0 → 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.
Files changed (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/index.js +197 -7
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ 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
+
10
21
  ## [1.7.0] - 2026-02-02
11
22
 
12
23
  **New Rules for Blocks, Classes & Enum Enforcement + Multiple Enhancements**
@@ -1064,6 +1075,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1064
1075
 
1065
1076
  ---
1066
1077
 
1078
+ [1.7.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.0...v1.7.1
1067
1079
  [1.7.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.6...v1.7.0
1068
1080
  [1.6.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.5...v1.6.6
1069
1081
  [1.6.5]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.4...v1.6.5
package/index.js CHANGED
@@ -4054,15 +4054,18 @@ const ternaryConditionMultiline = {
4054
4054
  const parent = node.parent;
4055
4055
 
4056
4056
  if (parent && parent.type === "VariableDeclarator" && parent.init === node) {
4057
- const declarationLine = sourceCode.lines[parent.loc.start.line - 1];
4058
- const beforeTernary = declarationLine.slice(0, node.loc.start.column);
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);
4059
4061
 
4060
- prefixLength = beforeTernary.length - indent;
4062
+ // Prefix is "const varName = " or similar
4063
+ prefixLength = declKeyword.length + 1 + varName.length + 3; // keyword + space + name + " = "
4061
4064
  } else if (parent && parent.type === "AssignmentExpression" && parent.right === node) {
4062
- const assignmentLine = sourceCode.lines[parent.loc.start.line - 1];
4063
- const beforeTernary = assignmentLine.slice(0, node.loc.start.column);
4065
+ // Calculate prefix based on left side of assignment
4066
+ const leftText = sourceCode.getText(parent.left);
4064
4067
 
4065
- prefixLength = beforeTernary.length - indent;
4068
+ prefixLength = leftText.length + 3; // left + " = "
4066
4069
  }
4067
4070
 
4068
4071
  // Check if single line would fit
@@ -4143,6 +4146,15 @@ const ternaryConditionMultiline = {
4143
4146
  if (!isCorrectionNeeded && hasOperatorAtEndOfLineHandler(test)) {
4144
4147
  isCorrectionNeeded = true;
4145
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
+ }
4146
4158
  }
4147
4159
 
4148
4160
  if (isCorrectionNeeded) {
@@ -4480,7 +4492,7 @@ const enumTypeEnforcement = {
4480
4492
  annotation.members.forEach((member) => {
4481
4493
  if (member.type === "TSPropertySignature" && member.key?.type === "Identifier") {
4482
4494
  const propName = member.key.name;
4483
- const typeInfo = extractTypeInfoHandler(member);
4495
+ const typeInfo = extractTypeInfoHandler(member.typeAnnotation);
4484
4496
 
4485
4497
  if (typeInfo) {
4486
4498
  typeAnnotatedVars.set(propName, typeInfo);
@@ -9565,6 +9577,29 @@ const noEmptyLinesInFunctionParams = {
9565
9577
  });
9566
9578
  }
9567
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
+ });
9568
9603
  };
9569
9604
 
9570
9605
  return {
@@ -14227,6 +14262,161 @@ const componentPropsInlineType = {
14227
14262
  }
14228
14263
  }
14229
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
+
14230
14420
  // Check if type is a reference (TSTypeReference) instead of inline (TSTypeLiteral)
14231
14421
  // Only enforce inline types for React components - regular functions can use interface/type references
14232
14422
  if (typeAnnotation.type === "TSTypeReference") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.7.0",
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",