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.
- package/CHANGELOG.md +12 -0
- package/index.js +197 -7
- 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
|
-
|
|
4058
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
4063
|
-
const
|
|
4065
|
+
// Calculate prefix based on left side of assignment
|
|
4066
|
+
const leftText = sourceCode.getText(parent.left);
|
|
4064
4067
|
|
|
4065
|
-
prefixLength =
|
|
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