eslint-plugin-code-style 1.7.3 → 1.7.6
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 +35 -0
- package/README.md +15 -10
- package/index.js +171 -147
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.7.6] - 2026-02-02
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **`ternary-condition-multiline`** - Now depends only on operand count, not line length:
|
|
15
|
+
- ≤maxOperands (default: 3): Always collapse to single line regardless of line length
|
|
16
|
+
- \>maxOperands: Format multiline with each operand on its own line
|
|
17
|
+
- Removed `maxLineLength` option (no longer used)
|
|
18
|
+
- This aligns behavior with `multiline-if-conditions` rule
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [1.7.5] - 2026-02-02
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **`ternary-condition-multiline`** - For ≤3 operands, always collapse to single line when `?` is on different line than condition end (enforces `condition ? value : value` format for simple ternaries)
|
|
27
|
+
- **`no-empty-lines-in-function-params`** - Add detection for empty lines in TSTypeLiteral (type annotation objects like `{ prop: Type }` in intersection types)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## [1.7.4] - 2026-02-02
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **`no-empty-lines-in-function-params`** - Fix bug that deleted TypeScript type annotations when fixing empty lines in destructured params; now uses `getTokenAfter` instead of `getLastToken` to find closing brace
|
|
36
|
+
- **`no-inline-type-definitions`** - Change threshold comparison from `>` to `>=` so 2-member unions are now flagged (with default `maxUnionMembers: 2`)
|
|
37
|
+
- **`ternary-condition-multiline`** - Fix multiline formatting for >3 operands: first operand stays on same line as property key, `?` and `:` each on their own lines; fix circular fix bug for ≤3 operands case
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
10
41
|
## [1.7.3] - 2026-02-02
|
|
11
42
|
|
|
12
43
|
### Fixed
|
|
@@ -1107,6 +1138,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1107
1138
|
|
|
1108
1139
|
---
|
|
1109
1140
|
|
|
1141
|
+
[1.7.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.5...v1.7.6
|
|
1142
|
+
[1.7.5]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.4...v1.7.5
|
|
1143
|
+
[1.7.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.3...v1.7.4
|
|
1144
|
+
[1.7.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.2...v1.7.3
|
|
1110
1145
|
[1.7.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.1...v1.7.2
|
|
1111
1146
|
[1.7.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.7.0...v1.7.1
|
|
1112
1147
|
[1.7.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.6...v1.7.0
|
package/README.md
CHANGED
|
@@ -295,7 +295,7 @@ rules: {
|
|
|
295
295
|
| `if-statement-format` | `{` on same line as `if`/`else if`, `else` on same line as `}`, proper spacing 🔧 |
|
|
296
296
|
| `multiline-if-conditions` | Conditions exceeding threshold get one operand per line with proper indentation (default: >3) 🔧 ⚙️ |
|
|
297
297
|
| `no-empty-lines-in-switch-cases` | No empty line after `case X:` before code, no empty lines between cases 🔧 |
|
|
298
|
-
| `ternary-condition-multiline` |
|
|
298
|
+
| `ternary-condition-multiline` | ≤maxOperands always single line; >maxOperands multiline (based on operand count, not line length) 🔧 ⚙️ |
|
|
299
299
|
| **Function Rules** | |
|
|
300
300
|
| `function-call-spacing` | No space between function name and `(`: `fn()` not `fn ()` 🔧 |
|
|
301
301
|
| `function-declaration-style` | Auto-fix for `func-style`: converts function declarations to arrow expressions 🔧 |
|
|
@@ -1211,24 +1211,24 @@ switch (status) {
|
|
|
1211
1211
|
|
|
1212
1212
|
### `ternary-condition-multiline`
|
|
1213
1213
|
|
|
1214
|
-
**What it does:**
|
|
1215
|
-
-
|
|
1216
|
-
-
|
|
1214
|
+
**What it does:** Formats ternary expressions based on condition operand count:
|
|
1215
|
+
- ≤maxOperands (default: 3): Always collapse to single line regardless of line length
|
|
1216
|
+
- \>maxOperands: Expand to multiline with each operand on its own line
|
|
1217
1217
|
|
|
1218
|
-
**Why use it:**
|
|
1218
|
+
**Why use it:** Consistent formatting based on complexity, not line length. Simple conditions stay readable on one line; complex conditions get proper multiline formatting.
|
|
1219
1219
|
|
|
1220
1220
|
**Options:**
|
|
1221
1221
|
|
|
1222
1222
|
| Option | Type | Default | Description |
|
|
1223
1223
|
|--------|------|---------|-------------|
|
|
1224
|
-
| `maxOperands` | `integer` | `3` | Maximum operands to keep on single line |
|
|
1225
|
-
| `maxLineLength` | `integer` | `120` | Maximum line length for single-line ternaries |
|
|
1224
|
+
| `maxOperands` | `integer` | `3` | Maximum condition operands to keep ternary on single line |
|
|
1226
1225
|
|
|
1227
1226
|
```javascript
|
|
1228
|
-
// ✅ Good —
|
|
1227
|
+
// ✅ Good — ≤3 operands always on single line
|
|
1229
1228
|
const x = a && b && c ? "yes" : "no";
|
|
1229
|
+
const url = lang === "ar" ? `${apiEndpoints.exam.status}/${jobId}?lang=ar` : `${apiEndpoints.exam.status}/${jobId}`;
|
|
1230
1230
|
|
|
1231
|
-
// ✅ Good —
|
|
1231
|
+
// ✅ Good — >3 operands formatted multiline
|
|
1232
1232
|
const style = variant === "ghost"
|
|
1233
1233
|
|| variant === "ghost-danger"
|
|
1234
1234
|
|| variant === "muted"
|
|
@@ -1236,7 +1236,12 @@ const style = variant === "ghost"
|
|
|
1236
1236
|
? "transparent"
|
|
1237
1237
|
: "solid";
|
|
1238
1238
|
|
|
1239
|
-
// ❌ Bad —
|
|
1239
|
+
// ❌ Bad — ≤3 operands split across lines
|
|
1240
|
+
const x = a && b && c
|
|
1241
|
+
? "yes"
|
|
1242
|
+
: "no";
|
|
1243
|
+
|
|
1244
|
+
// ❌ Bad — >3 operands crammed on one line
|
|
1240
1245
|
const style = variant === "ghost" || variant === "ghost-danger" || variant === "muted" || variant === "primary" ? "transparent" : "solid";
|
|
1241
1246
|
```
|
|
1242
1247
|
|
package/index.js
CHANGED
|
@@ -3868,21 +3868,31 @@ const multilineIfConditions = {
|
|
|
3868
3868
|
* ───────────────────────────────────────────────────────────────
|
|
3869
3869
|
*
|
|
3870
3870
|
* Description:
|
|
3871
|
-
*
|
|
3872
|
-
*
|
|
3871
|
+
* Formats ternary expressions based on condition operand count:
|
|
3872
|
+
* - ≤maxOperands (default: 3): Always collapse to single line
|
|
3873
|
+
* - >maxOperands: Format multiline with each operand on its own line
|
|
3873
3874
|
*
|
|
3874
|
-
*
|
|
3875
|
+
* Options:
|
|
3876
|
+
* { maxOperands: 3 } - Maximum operands to keep on single line (default: 3)
|
|
3877
|
+
*
|
|
3878
|
+
* ✓ Good (≤3 operands - single line):
|
|
3875
3879
|
* const x = a && b && c ? "yes" : "no";
|
|
3880
|
+
* const url = lang === "ar" ? "/ar/path" : "/en/path";
|
|
3876
3881
|
*
|
|
3877
|
-
*
|
|
3878
|
-
*
|
|
3882
|
+
* ✓ Good (>3 operands - multiline):
|
|
3883
|
+
* const x = variant === "ghost"
|
|
3879
3884
|
* || variant === "ghost-danger"
|
|
3880
3885
|
* || variant === "muted"
|
|
3881
3886
|
* || variant === "primary"
|
|
3882
|
-
*
|
|
3883
|
-
*
|
|
3887
|
+
* ? "value1"
|
|
3888
|
+
* : "value2";
|
|
3884
3889
|
*
|
|
3885
|
-
* ✗ Bad:
|
|
3890
|
+
* ✗ Bad (≤3 operands split across lines):
|
|
3891
|
+
* const x = a && b && c
|
|
3892
|
+
* ? "yes"
|
|
3893
|
+
* : "no";
|
|
3894
|
+
*
|
|
3895
|
+
* ✗ Bad (>3 operands on single line):
|
|
3886
3896
|
* const x = variant === "ghost" || variant === "ghost-danger" || variant === "muted" || variant === "primary" ? "value1" : "value2";
|
|
3887
3897
|
*/
|
|
3888
3898
|
const ternaryConditionMultiline = {
|
|
@@ -3890,7 +3900,6 @@ const ternaryConditionMultiline = {
|
|
|
3890
3900
|
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
3891
3901
|
const options = context.options[0] || {};
|
|
3892
3902
|
const maxOperands = options.maxOperands ?? 3;
|
|
3893
|
-
const maxLineLength = options.maxLineLength ?? 120;
|
|
3894
3903
|
|
|
3895
3904
|
// Check if node is wrapped in parentheses
|
|
3896
3905
|
const isParenthesizedHandler = (node) => {
|
|
@@ -4048,7 +4057,7 @@ const ternaryConditionMultiline = {
|
|
|
4048
4057
|
return false;
|
|
4049
4058
|
};
|
|
4050
4059
|
|
|
4051
|
-
// Handle simple ternaries - collapse to single line
|
|
4060
|
+
// Handle simple ternaries (≤maxOperands) - always collapse to single line
|
|
4052
4061
|
const handleSimpleTernaryHandler = (node) => {
|
|
4053
4062
|
const isOnSingleLine = node.loc.start.line === node.loc.end.line;
|
|
4054
4063
|
const hasOperatorOnOwnLine = isOperatorOnOwnLineHandler(node);
|
|
@@ -4068,46 +4077,15 @@ const ternaryConditionMultiline = {
|
|
|
4068
4077
|
|
|
4069
4078
|
// Calculate what the single line would look like
|
|
4070
4079
|
const singleLineText = getTernarySingleLineHandler(node);
|
|
4071
|
-
const indent = getLineIndentHandler(node);
|
|
4072
|
-
|
|
4073
|
-
// Check if the parent needs prefix text (like "const x = " or "key: ")
|
|
4074
|
-
let prefixLength = 0;
|
|
4075
|
-
const parent = node.parent;
|
|
4076
|
-
|
|
4077
|
-
if (parent && parent.type === "VariableDeclarator" && parent.init === node) {
|
|
4078
|
-
// Calculate prefix based on parent's start line (where "const x = " is)
|
|
4079
|
-
const varDecl = parent.parent;
|
|
4080
|
-
const declKeyword = varDecl ? sourceCode.getFirstToken(varDecl).value : "const";
|
|
4081
|
-
const varName = parent.id.name || sourceCode.getText(parent.id);
|
|
4082
|
-
|
|
4083
|
-
// Prefix is "const varName = " or similar
|
|
4084
|
-
prefixLength = declKeyword.length + 1 + varName.length + 3; // keyword + space + name + " = "
|
|
4085
|
-
} else if (parent && parent.type === "AssignmentExpression" && parent.right === node) {
|
|
4086
|
-
// Calculate prefix based on left side of assignment
|
|
4087
|
-
const leftText = sourceCode.getText(parent.left);
|
|
4088
|
-
|
|
4089
|
-
prefixLength = leftText.length + 3; // left + " = "
|
|
4090
|
-
} else if (parent && parent.type === "Property" && parent.value === node) {
|
|
4091
|
-
// Object property: key: ternary
|
|
4092
|
-
const keyText = sourceCode.getText(parent.key);
|
|
4093
|
-
|
|
4094
|
-
prefixLength = keyText.length + 2; // key + ": "
|
|
4095
|
-
}
|
|
4096
|
-
|
|
4097
|
-
// Check if single line would fit
|
|
4098
|
-
const totalLength = indent + prefixLength + singleLineText.length + 1;
|
|
4099
|
-
|
|
4100
|
-
if (totalLength <= maxLineLength) {
|
|
4101
|
-
context.report({
|
|
4102
|
-
fix: (fixer) => fixer.replaceText(node, singleLineText),
|
|
4103
|
-
message: "Simple ternary should be on a single line",
|
|
4104
|
-
node,
|
|
4105
|
-
});
|
|
4106
4080
|
|
|
4107
|
-
|
|
4108
|
-
|
|
4081
|
+
// For ≤maxOperands conditions, always collapse to single line regardless of length
|
|
4082
|
+
context.report({
|
|
4083
|
+
fix: (fixer) => fixer.replaceText(node, singleLineText),
|
|
4084
|
+
message: `Ternary with ≤${maxOperands} operands should be on a single line`,
|
|
4085
|
+
node,
|
|
4086
|
+
});
|
|
4109
4087
|
|
|
4110
|
-
return
|
|
4088
|
+
return true;
|
|
4111
4089
|
};
|
|
4112
4090
|
|
|
4113
4091
|
// Handle complex logical expressions - format multiline
|
|
@@ -4118,100 +4096,41 @@ const ternaryConditionMultiline = {
|
|
|
4118
4096
|
const testEndLine = test.loc.end.line;
|
|
4119
4097
|
const isMultiLine = testStartLine !== testEndLine;
|
|
4120
4098
|
|
|
4121
|
-
// ≤maxOperands operands:
|
|
4099
|
+
// ≤maxOperands operands: always collapse to single line (regardless of line length)
|
|
4122
4100
|
if (operands.length <= maxOperands) {
|
|
4123
|
-
const firstOperandStartLine = operands[0].loc.start.line;
|
|
4124
|
-
const allOperandsStartOnSameLine = operands.every(
|
|
4125
|
-
(op) => op.loc.start.line === firstOperandStartLine,
|
|
4126
|
-
);
|
|
4127
|
-
|
|
4128
|
-
const hasSplitBinaryExpression = operands.some(
|
|
4129
|
-
(op) => isBinaryExpressionSplitHandler(op),
|
|
4130
|
-
);
|
|
4131
|
-
|
|
4132
|
-
// Check if ? or : is on its own line without its value
|
|
4133
|
-
const hasOperatorOnOwnLine = isOperatorOnOwnLineHandler(node);
|
|
4134
|
-
|
|
4135
|
-
// Check if ternary is multiline (could be collapsed)
|
|
4136
|
-
const isTernaryMultiline = node.loc.start.line !== node.loc.end.line;
|
|
4137
|
-
|
|
4138
|
-
// Helper to build single line condition
|
|
4139
|
-
const buildSameLineHandler = (n) => {
|
|
4140
|
-
if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
|
|
4141
|
-
const leftText = buildSameLineHandler(n.left);
|
|
4142
|
-
const rightText = buildSameLineHandler(n.right);
|
|
4143
|
-
|
|
4144
|
-
return `${leftText} ${n.operator} ${rightText}`;
|
|
4145
|
-
}
|
|
4146
|
-
|
|
4147
|
-
if (n.type === "BinaryExpression" && isBinaryExpressionSplitHandler(n)) {
|
|
4148
|
-
return buildBinaryExpressionSingleLineHandler(n);
|
|
4149
|
-
}
|
|
4150
|
-
|
|
4151
|
-
return getSourceTextWithGroupsHandler(n);
|
|
4152
|
-
};
|
|
4153
|
-
|
|
4154
|
-
// Check if whole ternary can fit on one line
|
|
4155
|
-
const singleLineText = getTernarySingleLineHandler(node);
|
|
4156
|
-
const indent = getLineIndentHandler(node);
|
|
4157
|
-
|
|
4158
|
-
// Calculate prefix length for context
|
|
4159
|
-
let prefixLength = 0;
|
|
4160
|
-
const parent = node.parent;
|
|
4161
|
-
|
|
4162
|
-
if (parent && parent.type === "VariableDeclarator" && parent.init === node) {
|
|
4163
|
-
const varDecl = parent.parent;
|
|
4164
|
-
const declKeyword = varDecl ? sourceCode.getFirstToken(varDecl).value : "const";
|
|
4165
|
-
const varName = parent.id.name || sourceCode.getText(parent.id);
|
|
4166
|
-
|
|
4167
|
-
prefixLength = declKeyword.length + 1 + varName.length + 3;
|
|
4168
|
-
} else if (parent && parent.type === "AssignmentExpression" && parent.right === node) {
|
|
4169
|
-
const leftText = sourceCode.getText(parent.left);
|
|
4170
|
-
|
|
4171
|
-
prefixLength = leftText.length + 3;
|
|
4172
|
-
} else if (parent && parent.type === "Property" && parent.value === node) {
|
|
4173
|
-
const keyText = sourceCode.getText(parent.key);
|
|
4174
|
-
|
|
4175
|
-
prefixLength = keyText.length + 2;
|
|
4176
|
-
}
|
|
4177
|
-
|
|
4178
|
-
const totalLength = indent + prefixLength + singleLineText.length + 1;
|
|
4179
|
-
const canFitOnOneLine = totalLength <= maxLineLength;
|
|
4180
|
-
|
|
4181
4101
|
// Skip if branches have complex objects
|
|
4182
4102
|
const hasComplexBranches = hasComplexObjectHandler(node.consequent) || hasComplexObjectHandler(node.alternate);
|
|
4183
4103
|
|
|
4184
4104
|
// Skip nested ternaries
|
|
4185
4105
|
const hasNestedTernary = node.consequent.type === "ConditionalExpression" || node.alternate.type === "ConditionalExpression";
|
|
4186
4106
|
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
const needsOperatorFix = hasOperatorOnOwnLine;
|
|
4107
|
+
if (hasComplexBranches || hasNestedTernary) {
|
|
4108
|
+
return;
|
|
4109
|
+
}
|
|
4191
4110
|
|
|
4192
|
-
if
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
node,
|
|
4199
|
-
});
|
|
4200
|
-
} else if (needsConditionFix) {
|
|
4201
|
-
// Otherwise just fix the condition to be on single line
|
|
4202
|
-
context.report({
|
|
4203
|
-
fix: (fixer) => fixer.replaceText(test, buildSameLineHandler(test)),
|
|
4204
|
-
message: `Ternary conditions with ≤${maxOperands} operands should be single line`,
|
|
4205
|
-
node: test,
|
|
4206
|
-
});
|
|
4207
|
-
}
|
|
4111
|
+
// Check if already properly formatted (single line)
|
|
4112
|
+
const isOnSingleLine = node.loc.start.line === node.loc.end.line;
|
|
4113
|
+
const hasOperatorOnOwnLine = isOperatorOnOwnLineHandler(node);
|
|
4114
|
+
|
|
4115
|
+
if (isOnSingleLine && !hasOperatorOnOwnLine) {
|
|
4116
|
+
return;
|
|
4208
4117
|
}
|
|
4209
4118
|
|
|
4119
|
+
// Collapse to single line
|
|
4120
|
+
const singleLineText = getTernarySingleLineHandler(node);
|
|
4121
|
+
|
|
4122
|
+
context.report({
|
|
4123
|
+
fix: (fixer) => fixer.replaceText(node, singleLineText),
|
|
4124
|
+
message: `Ternary with ≤${maxOperands} operands should be on a single line`,
|
|
4125
|
+
node,
|
|
4126
|
+
});
|
|
4127
|
+
|
|
4210
4128
|
return;
|
|
4211
4129
|
}
|
|
4212
4130
|
|
|
4213
4131
|
// More than maxOperands: each on its own line
|
|
4214
4132
|
let isCorrectionNeeded = !isMultiLine;
|
|
4133
|
+
const parent = node.parent;
|
|
4215
4134
|
|
|
4216
4135
|
if (isMultiLine) {
|
|
4217
4136
|
for (let i = 0; i < operands.length - 1; i += 1) {
|
|
@@ -4257,14 +4176,57 @@ const ternaryConditionMultiline = {
|
|
|
4257
4176
|
if (!isCorrectionNeeded && isOperatorOnOwnLineHandler(node)) {
|
|
4258
4177
|
isCorrectionNeeded = true;
|
|
4259
4178
|
}
|
|
4179
|
+
|
|
4180
|
+
// Check if : is on same line as ? (both should be on their own lines for multiline)
|
|
4181
|
+
if (!isCorrectionNeeded) {
|
|
4182
|
+
const questionToken = sourceCode.getTokenAfter(test, (t) => t.value === "?");
|
|
4183
|
+
const colonToken = sourceCode.getTokenAfter(node.consequent, (t) => t.value === ":");
|
|
4184
|
+
|
|
4185
|
+
if (questionToken && colonToken && questionToken.loc.start.line === colonToken.loc.start.line) {
|
|
4186
|
+
isCorrectionNeeded = true;
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
// Check if first operand is not on same line as parent property key
|
|
4191
|
+
if (!isCorrectionNeeded && parent && parent.type === "Property" && parent.value === node) {
|
|
4192
|
+
const keyEndLine = parent.key.loc.end.line;
|
|
4193
|
+
const firstOperandLine = operands[0].loc.start.line;
|
|
4194
|
+
|
|
4195
|
+
if (firstOperandLine !== keyEndLine) {
|
|
4196
|
+
isCorrectionNeeded = true;
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4260
4199
|
}
|
|
4261
4200
|
|
|
4262
4201
|
if (isCorrectionNeeded) {
|
|
4263
4202
|
context.report({
|
|
4264
4203
|
fix: (fixer) => {
|
|
4265
|
-
// Get
|
|
4266
|
-
|
|
4267
|
-
|
|
4204
|
+
// Get proper base indent
|
|
4205
|
+
let baseIndent;
|
|
4206
|
+
let includePropertyKey = false;
|
|
4207
|
+
let propertyKeyText = "";
|
|
4208
|
+
|
|
4209
|
+
// Check if parent is Property and value starts on different line
|
|
4210
|
+
if (parent && parent.type === "Property" && parent.value === node) {
|
|
4211
|
+
const keyEndLine = parent.key.loc.end.line;
|
|
4212
|
+
const firstOperandLine = operands[0].loc.start.line;
|
|
4213
|
+
|
|
4214
|
+
if (firstOperandLine !== keyEndLine) {
|
|
4215
|
+
// Need to include property key in fix
|
|
4216
|
+
includePropertyKey = true;
|
|
4217
|
+
propertyKeyText = sourceCode.getText(parent.key) + ": ";
|
|
4218
|
+
const propertyLineText = sourceCode.lines[parent.loc.start.line - 1];
|
|
4219
|
+
|
|
4220
|
+
baseIndent = propertyLineText.match(/^\s*/)[0];
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
|
|
4224
|
+
if (!baseIndent) {
|
|
4225
|
+
const lineText = sourceCode.lines[node.loc.start.line - 1];
|
|
4226
|
+
|
|
4227
|
+
baseIndent = lineText.match(/^\s*/)[0];
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4268
4230
|
const conditionIndent = baseIndent + " ";
|
|
4269
4231
|
|
|
4270
4232
|
const buildMultilineHandler = (n) => {
|
|
@@ -4281,9 +4243,17 @@ const ternaryConditionMultiline = {
|
|
|
4281
4243
|
const consequentText = sourceCode.getText(node.consequent);
|
|
4282
4244
|
const alternateText = sourceCode.getText(node.alternate);
|
|
4283
4245
|
|
|
4284
|
-
//
|
|
4285
|
-
|
|
4286
|
-
const newText = `${
|
|
4246
|
+
// Build multiline with ? and : each on their own lines
|
|
4247
|
+
const conditionPart = buildMultilineHandler(test);
|
|
4248
|
+
const newText = `${conditionPart}\n${conditionIndent}? ${consequentText}\n${conditionIndent}: ${alternateText}`;
|
|
4249
|
+
|
|
4250
|
+
if (includePropertyKey) {
|
|
4251
|
+
// Replace the entire property value including fixing the key position
|
|
4252
|
+
return fixer.replaceTextRange(
|
|
4253
|
+
[parent.key.range[0], node.range[1]],
|
|
4254
|
+
`${propertyKeyText}${newText}`,
|
|
4255
|
+
);
|
|
4256
|
+
}
|
|
4287
4257
|
|
|
4288
4258
|
return fixer.replaceText(node, newText);
|
|
4289
4259
|
},
|
|
@@ -4310,21 +4280,15 @@ const ternaryConditionMultiline = {
|
|
|
4310
4280
|
};
|
|
4311
4281
|
},
|
|
4312
4282
|
meta: {
|
|
4313
|
-
docs: { description: "Enforce consistent ternary formatting:
|
|
4283
|
+
docs: { description: "Enforce consistent ternary formatting based on condition operand count: ≤maxOperands collapses to single line, >maxOperands expands to multiline" },
|
|
4314
4284
|
fixable: "code",
|
|
4315
4285
|
schema: [
|
|
4316
4286
|
{
|
|
4317
4287
|
additionalProperties: false,
|
|
4318
4288
|
properties: {
|
|
4319
|
-
maxLineLength: {
|
|
4320
|
-
default: 120,
|
|
4321
|
-
description: "Maximum line length for single-line ternaries (default: 120)",
|
|
4322
|
-
minimum: 80,
|
|
4323
|
-
type: "integer",
|
|
4324
|
-
},
|
|
4325
4289
|
maxOperands: {
|
|
4326
4290
|
default: 3,
|
|
4327
|
-
description: "Maximum operands to keep on single line (default: 3)",
|
|
4291
|
+
description: "Maximum condition operands to keep ternary on single line (default: 3). Ternaries with more operands are formatted multiline.",
|
|
4328
4292
|
minimum: 1,
|
|
4329
4293
|
type: "integer",
|
|
4330
4294
|
},
|
|
@@ -9747,10 +9711,10 @@ const noEmptyLinesInFunctionParams = {
|
|
|
9747
9711
|
}
|
|
9748
9712
|
}
|
|
9749
9713
|
|
|
9750
|
-
// Find the closing brace of ObjectPattern
|
|
9751
|
-
const closeBrace = sourceCode.
|
|
9714
|
+
// Find the closing brace of ObjectPattern (first } after last prop, not last token which could be from type annotation)
|
|
9715
|
+
const closeBrace = sourceCode.getTokenAfter(lastProp, (t) => t.value === "}");
|
|
9752
9716
|
|
|
9753
|
-
if (closeBrace
|
|
9717
|
+
if (closeBrace) {
|
|
9754
9718
|
// Check for empty line before closing brace
|
|
9755
9719
|
if (closeBrace.loc.start.line - lastProp.loc.end.line > 1) {
|
|
9756
9720
|
context.report({
|
|
@@ -9788,14 +9752,74 @@ const noEmptyLinesInFunctionParams = {
|
|
|
9788
9752
|
});
|
|
9789
9753
|
};
|
|
9790
9754
|
|
|
9755
|
+
// Check TSTypeLiteral for empty lines (type annotation objects like { prop: Type })
|
|
9756
|
+
const checkTypeLiteralHandler = (node) => {
|
|
9757
|
+
if (!node.members || node.members.length === 0) return;
|
|
9758
|
+
|
|
9759
|
+
const firstMember = node.members[0];
|
|
9760
|
+
const lastMember = node.members[node.members.length - 1];
|
|
9761
|
+
|
|
9762
|
+
// Find opening brace
|
|
9763
|
+
const openBrace = sourceCode.getFirstToken(node);
|
|
9764
|
+
|
|
9765
|
+
if (openBrace && openBrace.value === "{") {
|
|
9766
|
+
// Check for empty line after opening brace
|
|
9767
|
+
if (firstMember.loc.start.line - openBrace.loc.end.line > 1) {
|
|
9768
|
+
context.report({
|
|
9769
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
9770
|
+
[openBrace.range[1], firstMember.range[0]],
|
|
9771
|
+
"\n" + " ".repeat(firstMember.loc.start.column),
|
|
9772
|
+
),
|
|
9773
|
+
message: "No empty line after opening brace in type definition",
|
|
9774
|
+
node: firstMember,
|
|
9775
|
+
});
|
|
9776
|
+
}
|
|
9777
|
+
}
|
|
9778
|
+
|
|
9779
|
+
// Find closing brace
|
|
9780
|
+
const closeBrace = sourceCode.getLastToken(node);
|
|
9781
|
+
|
|
9782
|
+
if (closeBrace && closeBrace.value === "}") {
|
|
9783
|
+
// Check for empty line before closing brace
|
|
9784
|
+
if (closeBrace.loc.start.line - lastMember.loc.end.line > 1) {
|
|
9785
|
+
context.report({
|
|
9786
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
9787
|
+
[lastMember.range[1], closeBrace.range[0]],
|
|
9788
|
+
"\n" + " ".repeat(closeBrace.loc.start.column),
|
|
9789
|
+
),
|
|
9790
|
+
message: "No empty line before closing brace in type definition",
|
|
9791
|
+
node: lastMember,
|
|
9792
|
+
});
|
|
9793
|
+
}
|
|
9794
|
+
}
|
|
9795
|
+
|
|
9796
|
+
// Check for empty lines between members
|
|
9797
|
+
for (let i = 0; i < node.members.length - 1; i += 1) {
|
|
9798
|
+
const current = node.members[i];
|
|
9799
|
+
const next = node.members[i + 1];
|
|
9800
|
+
|
|
9801
|
+
if (next.loc.start.line - current.loc.end.line > 1) {
|
|
9802
|
+
context.report({
|
|
9803
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
9804
|
+
[current.range[1], next.range[0]],
|
|
9805
|
+
"\n" + " ".repeat(next.loc.start.column),
|
|
9806
|
+
),
|
|
9807
|
+
message: "No empty lines between type members",
|
|
9808
|
+
node: next,
|
|
9809
|
+
});
|
|
9810
|
+
}
|
|
9811
|
+
}
|
|
9812
|
+
};
|
|
9813
|
+
|
|
9791
9814
|
return {
|
|
9792
9815
|
ArrowFunctionExpression: checkFunctionHandler,
|
|
9793
9816
|
FunctionDeclaration: checkFunctionHandler,
|
|
9794
9817
|
FunctionExpression: checkFunctionHandler,
|
|
9818
|
+
TSTypeLiteral: checkTypeLiteralHandler,
|
|
9795
9819
|
};
|
|
9796
9820
|
},
|
|
9797
9821
|
meta: {
|
|
9798
|
-
docs: { description: "Disallow empty lines in function parameters" },
|
|
9822
|
+
docs: { description: "Disallow empty lines in function parameters and type definitions" },
|
|
9799
9823
|
fixable: "whitespace",
|
|
9800
9824
|
schema: [],
|
|
9801
9825
|
type: "layout",
|
|
@@ -14993,7 +15017,7 @@ const noInlineTypeDefinitions = {
|
|
|
14993
15017
|
const memberCount = countUnionMembersHandler(typeNode);
|
|
14994
15018
|
const typeText = sourceCode.getText(typeNode);
|
|
14995
15019
|
|
|
14996
|
-
if (memberCount
|
|
15020
|
+
if (memberCount >= maxUnionMembers || typeText.length > maxLength) {
|
|
14997
15021
|
context.report({
|
|
14998
15022
|
message: `Inline union type with ${memberCount} members is too complex. Extract to a named type in a types file.`,
|
|
14999
15023
|
node: typeNode,
|
|
@@ -15023,7 +15047,7 @@ const noInlineTypeDefinitions = {
|
|
|
15023
15047
|
const memberCount = countUnionMembersHandler(propType);
|
|
15024
15048
|
const typeText = sourceCode.getText(propType);
|
|
15025
15049
|
|
|
15026
|
-
if (memberCount
|
|
15050
|
+
if (memberCount >= maxUnionMembers || typeText.length > maxLength) {
|
|
15027
15051
|
context.report({
|
|
15028
15052
|
message: `Property "${propName}" has inline union type with ${memberCount} members. Extract to a named type in a types file.`,
|
|
15029
15053
|
node: propType,
|
package/package.json
CHANGED