eslint-plugin-code-style 1.7.0 → 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.
- package/CHANGELOG.md +22 -0
- package/index.js +442 -38
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ 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
|
+
|
|
19
|
+
## [1.7.1] - 2026-02-02
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **`no-empty-lines-in-function-params`** - Detect empty lines between destructured properties inside ObjectPattern params
|
|
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
|
|
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
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
10
30
|
## [1.7.0] - 2026-02-02
|
|
11
31
|
|
|
12
32
|
**New Rules for Blocks, Classes & Enum Enforcement + Multiple Enhancements**
|
|
@@ -1064,6 +1084,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1064
1084
|
|
|
1065
1085
|
---
|
|
1066
1086
|
|
|
1087
|
+
[1.7.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.1...v1.7.2
|
|
1088
|
+
[1.7.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.0...v1.7.1
|
|
1067
1089
|
[1.7.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.6...v1.7.0
|
|
1068
1090
|
[1.6.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.5...v1.6.6
|
|
1069
1091
|
[1.6.5]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.4...v1.6.5
|
package/index.js
CHANGED
|
@@ -4049,20 +4049,28 @@ 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
|
|
|
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 + " = "
|
|
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 + ": "
|
|
4066
4074
|
}
|
|
4067
4075
|
|
|
4068
4076
|
// Check if single line would fit
|
|
@@ -4143,6 +4151,33 @@ const ternaryConditionMultiline = {
|
|
|
4143
4151
|
if (!isCorrectionNeeded && hasOperatorAtEndOfLineHandler(test)) {
|
|
4144
4152
|
isCorrectionNeeded = true;
|
|
4145
4153
|
}
|
|
4154
|
+
|
|
4155
|
+
// Check if ? is on same line as end of multiline condition (BAD - should be on its own line)
|
|
4156
|
+
if (!isCorrectionNeeded) {
|
|
4157
|
+
const questionToken = sourceCode.getTokenAfter(test, (t) => t.value === "?");
|
|
4158
|
+
|
|
4159
|
+
if (questionToken && questionToken.loc.start.line === test.loc.end.line) {
|
|
4160
|
+
isCorrectionNeeded = true;
|
|
4161
|
+
}
|
|
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
|
+
}
|
|
4146
4181
|
}
|
|
4147
4182
|
|
|
4148
4183
|
if (isCorrectionNeeded) {
|
|
@@ -4152,7 +4187,6 @@ const ternaryConditionMultiline = {
|
|
|
4152
4187
|
const lineText = sourceCode.lines[node.loc.start.line - 1];
|
|
4153
4188
|
const baseIndent = lineText.match(/^\s*/)[0];
|
|
4154
4189
|
const conditionIndent = baseIndent + " ";
|
|
4155
|
-
const branchIndent = baseIndent + " ";
|
|
4156
4190
|
|
|
4157
4191
|
const buildMultilineHandler = (n) => {
|
|
4158
4192
|
if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
|
|
@@ -4168,7 +4202,9 @@ const ternaryConditionMultiline = {
|
|
|
4168
4202
|
const consequentText = sourceCode.getText(node.consequent);
|
|
4169
4203
|
const alternateText = sourceCode.getText(node.alternate);
|
|
4170
4204
|
|
|
4171
|
-
|
|
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}`;
|
|
4172
4208
|
|
|
4173
4209
|
return fixer.replaceText(node, newText);
|
|
4174
4210
|
},
|
|
@@ -4469,6 +4505,20 @@ const enumTypeEnforcement = {
|
|
|
4469
4505
|
return null;
|
|
4470
4506
|
};
|
|
4471
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
|
+
|
|
4472
4522
|
// Track type-annotated parameters in function/component definitions
|
|
4473
4523
|
const trackTypedParamsHandler = (params) => {
|
|
4474
4524
|
params.forEach((param) => {
|
|
@@ -4477,14 +4527,14 @@ const enumTypeEnforcement = {
|
|
|
4477
4527
|
const annotation = param.typeAnnotation.typeAnnotation;
|
|
4478
4528
|
|
|
4479
4529
|
if (annotation && annotation.type === "TSTypeLiteral") {
|
|
4480
|
-
annotation.members
|
|
4481
|
-
|
|
4482
|
-
const propName = member.key.name;
|
|
4483
|
-
const typeInfo = extractTypeInfoHandler(member);
|
|
4530
|
+
processTypeLiteralMembersHandler(annotation.members);
|
|
4531
|
+
}
|
|
4484
4532
|
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
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);
|
|
4488
4538
|
}
|
|
4489
4539
|
});
|
|
4490
4540
|
}
|
|
@@ -9565,6 +9615,29 @@ const noEmptyLinesInFunctionParams = {
|
|
|
9565
9615
|
});
|
|
9566
9616
|
}
|
|
9567
9617
|
}
|
|
9618
|
+
|
|
9619
|
+
// Check inside ObjectPattern params for empty lines between destructured props
|
|
9620
|
+
params.forEach((param) => {
|
|
9621
|
+
if (param.type === "ObjectPattern" && param.properties.length > 1) {
|
|
9622
|
+
for (let i = 0; i < param.properties.length - 1; i += 1) {
|
|
9623
|
+
const current = param.properties[i];
|
|
9624
|
+
const next = param.properties[i + 1];
|
|
9625
|
+
|
|
9626
|
+
if (next.loc.start.line - current.loc.end.line > 1) {
|
|
9627
|
+
const commaToken = sourceCode.getTokenAfter(current, (t) => t.value === ",");
|
|
9628
|
+
|
|
9629
|
+
context.report({
|
|
9630
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
9631
|
+
[commaToken.range[1], next.range[0]],
|
|
9632
|
+
"\n" + " ".repeat(next.loc.start.column),
|
|
9633
|
+
),
|
|
9634
|
+
message: "No empty lines between destructured properties",
|
|
9635
|
+
node: next,
|
|
9636
|
+
});
|
|
9637
|
+
}
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
});
|
|
9568
9641
|
};
|
|
9569
9642
|
|
|
9570
9643
|
return {
|
|
@@ -14227,6 +14300,191 @@ const componentPropsInlineType = {
|
|
|
14227
14300
|
}
|
|
14228
14301
|
}
|
|
14229
14302
|
|
|
14303
|
+
// Handle intersection types: ButtonHTMLAttributes<HTMLButtonElement> & { prop: Type }
|
|
14304
|
+
if (typeAnnotation.type === "TSIntersectionType" && isComponent) {
|
|
14305
|
+
const types = typeAnnotation.types;
|
|
14306
|
+
|
|
14307
|
+
// Check & operators are on same line as previous type
|
|
14308
|
+
for (let i = 0; i < types.length - 1; i += 1) {
|
|
14309
|
+
const currentType = types[i];
|
|
14310
|
+
const nextType = types[i + 1];
|
|
14311
|
+
|
|
14312
|
+
const ampersandToken = sourceCode.getTokenAfter(currentType, (t) => t.value === "&");
|
|
14313
|
+
|
|
14314
|
+
if (ampersandToken && ampersandToken.loc.start.line !== currentType.loc.end.line) {
|
|
14315
|
+
context.report({
|
|
14316
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
14317
|
+
[currentType.range[1], ampersandToken.range[1]],
|
|
14318
|
+
" &",
|
|
14319
|
+
),
|
|
14320
|
+
message: "\"&\" must be on same line as previous type",
|
|
14321
|
+
node: ampersandToken,
|
|
14322
|
+
});
|
|
14323
|
+
}
|
|
14324
|
+
|
|
14325
|
+
// { should be on same line as &
|
|
14326
|
+
if (nextType.type === "TSTypeLiteral" && ampersandToken) {
|
|
14327
|
+
const openBrace = sourceCode.getFirstToken(nextType);
|
|
14328
|
+
|
|
14329
|
+
if (openBrace && openBrace.loc.start.line !== ampersandToken.loc.end.line) {
|
|
14330
|
+
context.report({
|
|
14331
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
14332
|
+
[ampersandToken.range[1], openBrace.range[0]],
|
|
14333
|
+
" ",
|
|
14334
|
+
),
|
|
14335
|
+
message: "Opening brace must be on same line as \"&\"",
|
|
14336
|
+
node: openBrace,
|
|
14337
|
+
});
|
|
14338
|
+
}
|
|
14339
|
+
}
|
|
14340
|
+
}
|
|
14341
|
+
|
|
14342
|
+
// Find TSTypeLiteral in the intersection and apply formatting rules
|
|
14343
|
+
const typeLiteral = types.find((t) => t.type === "TSTypeLiteral");
|
|
14344
|
+
|
|
14345
|
+
if (typeLiteral) {
|
|
14346
|
+
const members = typeLiteral.members;
|
|
14347
|
+
|
|
14348
|
+
// Get the base indentation from the component declaration
|
|
14349
|
+
const componentLine = sourceCode.lines[node.loc.start.line - 1];
|
|
14350
|
+
const baseIndent = componentLine.match(/^\s*/)[0];
|
|
14351
|
+
const propIndent = baseIndent + " ";
|
|
14352
|
+
|
|
14353
|
+
// Get opening and closing brace tokens
|
|
14354
|
+
const openBraceToken = sourceCode.getFirstToken(typeLiteral);
|
|
14355
|
+
const closeBraceToken = sourceCode.getLastToken(typeLiteral);
|
|
14356
|
+
|
|
14357
|
+
// For multiple members, first member should be on new line after opening brace
|
|
14358
|
+
if (members.length > 1 && members[0]) {
|
|
14359
|
+
const firstMember = members[0];
|
|
14360
|
+
|
|
14361
|
+
if (firstMember.loc.start.line === openBraceToken.loc.end.line) {
|
|
14362
|
+
context.report({
|
|
14363
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
14364
|
+
[openBraceToken.range[1], firstMember.range[0]],
|
|
14365
|
+
"\n" + propIndent,
|
|
14366
|
+
),
|
|
14367
|
+
message: "First props type property must be on a new line when there are multiple properties",
|
|
14368
|
+
node: firstMember,
|
|
14369
|
+
});
|
|
14370
|
+
}
|
|
14371
|
+
}
|
|
14372
|
+
|
|
14373
|
+
// Check closing brace position - should be on its own line for multiple members
|
|
14374
|
+
if (members.length > 1 && closeBraceToken) {
|
|
14375
|
+
const lastMember = members[members.length - 1];
|
|
14376
|
+
|
|
14377
|
+
if (closeBraceToken.loc.start.line === lastMember.loc.end.line) {
|
|
14378
|
+
context.report({
|
|
14379
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
14380
|
+
[lastMember.range[1], closeBraceToken.range[0]],
|
|
14381
|
+
"\n" + baseIndent,
|
|
14382
|
+
),
|
|
14383
|
+
message: "Closing brace must be on its own line when there are multiple properties",
|
|
14384
|
+
node: closeBraceToken,
|
|
14385
|
+
});
|
|
14386
|
+
}
|
|
14387
|
+
}
|
|
14388
|
+
|
|
14389
|
+
// Check each member for formatting
|
|
14390
|
+
members.forEach((member, index) => {
|
|
14391
|
+
const memberText = sourceCode.getText(member);
|
|
14392
|
+
|
|
14393
|
+
// Check property ends with comma, not semicolon
|
|
14394
|
+
if (memberText.trimEnd().endsWith(";")) {
|
|
14395
|
+
context.report({
|
|
14396
|
+
fix: (fixer) => {
|
|
14397
|
+
const lastChar = memberText.lastIndexOf(";");
|
|
14398
|
+
const absolutePos = member.range[0] + lastChar;
|
|
14399
|
+
|
|
14400
|
+
return fixer.replaceTextRange([absolutePos, absolutePos + 1], ",");
|
|
14401
|
+
},
|
|
14402
|
+
message: "Props type properties must end with comma (,) not semicolon (;)",
|
|
14403
|
+
node: member,
|
|
14404
|
+
});
|
|
14405
|
+
}
|
|
14406
|
+
|
|
14407
|
+
// If more than one member, check each is on its own line
|
|
14408
|
+
if (members.length > 1 && index > 0) {
|
|
14409
|
+
const prevMember = members[index - 1];
|
|
14410
|
+
|
|
14411
|
+
if (member.loc.start.line === prevMember.loc.end.line) {
|
|
14412
|
+
context.report({
|
|
14413
|
+
fix: (fixer) => {
|
|
14414
|
+
let commaToken = sourceCode.getTokenAfter(prevMember);
|
|
14415
|
+
|
|
14416
|
+
while (commaToken && commaToken.value !== "," && commaToken.range[0] < member.range[0]) {
|
|
14417
|
+
commaToken = sourceCode.getTokenAfter(commaToken);
|
|
14418
|
+
}
|
|
14419
|
+
|
|
14420
|
+
const insertPoint = commaToken && commaToken.value === "," ? commaToken.range[1] : prevMember.range[1];
|
|
14421
|
+
|
|
14422
|
+
return fixer.replaceTextRange(
|
|
14423
|
+
[insertPoint, member.range[0]],
|
|
14424
|
+
"\n" + propIndent,
|
|
14425
|
+
);
|
|
14426
|
+
},
|
|
14427
|
+
message: "Each props type property must be on its own line when there are multiple properties",
|
|
14428
|
+
node: member,
|
|
14429
|
+
});
|
|
14430
|
+
}
|
|
14431
|
+
|
|
14432
|
+
// Check for empty lines between properties
|
|
14433
|
+
if (member.loc.start.line - prevMember.loc.end.line > 1) {
|
|
14434
|
+
context.report({
|
|
14435
|
+
fix: (fixer) => {
|
|
14436
|
+
const textBetween = sourceCode.getText().slice(
|
|
14437
|
+
prevMember.range[1],
|
|
14438
|
+
member.range[0],
|
|
14439
|
+
);
|
|
14440
|
+
const newText = textBetween.replace(/\n\s*\n/g, "\n");
|
|
14441
|
+
|
|
14442
|
+
return fixer.replaceTextRange(
|
|
14443
|
+
[prevMember.range[1], member.range[0]],
|
|
14444
|
+
newText,
|
|
14445
|
+
);
|
|
14446
|
+
},
|
|
14447
|
+
message: "No empty lines allowed between props type properties",
|
|
14448
|
+
node: member,
|
|
14449
|
+
});
|
|
14450
|
+
}
|
|
14451
|
+
}
|
|
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
|
+
}
|
|
14483
|
+
}
|
|
14484
|
+
|
|
14485
|
+
return;
|
|
14486
|
+
}
|
|
14487
|
+
|
|
14230
14488
|
// Check if type is a reference (TSTypeReference) instead of inline (TSTypeLiteral)
|
|
14231
14489
|
// Only enforce inline types for React components - regular functions can use interface/type references
|
|
14232
14490
|
if (typeAnnotation.type === "TSTypeReference") {
|
|
@@ -14429,6 +14687,20 @@ const componentPropsInlineType = {
|
|
|
14429
14687
|
}
|
|
14430
14688
|
}
|
|
14431
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
|
+
}
|
|
14432
14704
|
}
|
|
14433
14705
|
};
|
|
14434
14706
|
|
|
@@ -16766,19 +17038,24 @@ const enumFormat = {
|
|
|
16766
17038
|
}
|
|
16767
17039
|
|
|
16768
17040
|
// Check member ends with comma, not semicolon
|
|
16769
|
-
|
|
17041
|
+
// Skip last member when multiple members - handled by combined check below
|
|
17042
|
+
const isLastMember = index === members.length - 1;
|
|
16770
17043
|
|
|
16771
|
-
if (
|
|
16772
|
-
|
|
16773
|
-
fix(fixer) {
|
|
16774
|
-
const lastChar = memberText.lastIndexOf(";");
|
|
16775
|
-
const absolutePos = member.range[0] + lastChar;
|
|
17044
|
+
if (!isLastMember || members.length === 1) {
|
|
17045
|
+
const memberText = sourceCode.getText(member);
|
|
16776
17046
|
|
|
16777
|
-
|
|
16778
|
-
|
|
16779
|
-
|
|
16780
|
-
|
|
16781
|
-
|
|
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
|
+
}
|
|
16782
17059
|
}
|
|
16783
17060
|
|
|
16784
17061
|
// Check formatting for multiple members
|
|
@@ -16828,6 +17105,67 @@ const enumFormat = {
|
|
|
16828
17105
|
}
|
|
16829
17106
|
}
|
|
16830
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
|
+
}
|
|
16831
17169
|
},
|
|
16832
17170
|
};
|
|
16833
17171
|
},
|
|
@@ -17011,19 +17349,24 @@ const interfaceFormat = {
|
|
|
17011
17349
|
}
|
|
17012
17350
|
|
|
17013
17351
|
// Check property ends with comma, not semicolon
|
|
17014
|
-
|
|
17352
|
+
// Skip last member when multiple members - handled by combined check below
|
|
17353
|
+
const isLastMember = index === members.length - 1;
|
|
17015
17354
|
|
|
17016
|
-
if (
|
|
17017
|
-
|
|
17018
|
-
fix(fixer) {
|
|
17019
|
-
const lastChar = memberText.lastIndexOf(";");
|
|
17020
|
-
const absolutePos = member.range[0] + lastChar;
|
|
17355
|
+
if (!isLastMember || members.length === 1) {
|
|
17356
|
+
const memberText = sourceCode.getText(member);
|
|
17021
17357
|
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
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
|
+
}
|
|
17027
17370
|
}
|
|
17028
17371
|
|
|
17029
17372
|
// Check formatting for multiple members
|
|
@@ -17073,6 +17416,67 @@ const interfaceFormat = {
|
|
|
17073
17416
|
}
|
|
17074
17417
|
}
|
|
17075
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
|
+
}
|
|
17076
17480
|
},
|
|
17077
17481
|
};
|
|
17078
17482
|
},
|
package/package.json
CHANGED