@typescript-eslint/eslint-plugin 8.61.1-alpha.3 → 8.61.1-alpha.4

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.
@@ -163,11 +163,11 @@ exports.default = (0, util_1.createRule)({
163
163
  typeof against.value !== 'boolean') {
164
164
  continue;
165
165
  }
166
- const { value: literalBooleanInComparison } = against;
166
+ const booleanLiteral = against.value ? 'true' : 'false';
167
167
  const negated = !comparisonType.isPositive;
168
168
  return {
169
+ booleanLiteral,
169
170
  expression,
170
- literalBooleanInComparison,
171
171
  negated,
172
172
  };
173
173
  }
@@ -183,11 +183,11 @@ exports.default = (0, util_1.createRule)({
183
183
  return;
184
184
  }
185
185
  if (comparison.expressionIsNullableBoolean) {
186
- if (comparison.literalBooleanInComparison &&
186
+ if (comparison.booleanLiteral === 'true' &&
187
187
  options.allowComparingNullableBooleansToTrue) {
188
188
  return;
189
189
  }
190
- if (!comparison.literalBooleanInComparison &&
190
+ if (comparison.booleanLiteral === 'false' &&
191
191
  options.allowComparingNullableBooleansToFalse) {
192
192
  return;
193
193
  }
@@ -195,7 +195,7 @@ exports.default = (0, util_1.createRule)({
195
195
  context.report({
196
196
  node,
197
197
  messageId: comparison.expressionIsNullableBoolean
198
- ? comparison.literalBooleanInComparison
198
+ ? comparison.booleanLiteral === 'true'
199
199
  ? comparison.negated
200
200
  ? 'comparingNullableToTrueNegated'
201
201
  : 'comparingNullableToTrueDirect'
@@ -203,30 +203,36 @@ exports.default = (0, util_1.createRule)({
203
203
  : comparison.negated
204
204
  ? 'negated'
205
205
  : 'direct',
206
- *fix(fixer) {
207
- // 1. isUnaryNegation - parent negation
208
- // 2. literalBooleanInComparison - is compared to literal boolean
209
- // 3. negated - is expression negated
210
- const isUnaryNegation = nodeIsUnaryNegation(node.parent);
211
- const shouldNegate = comparison.negated !== comparison.literalBooleanInComparison;
212
- const mutatedNode = isUnaryNegation ? node.parent : node;
213
- yield fixer.replaceText(mutatedNode, context.sourceCode.getText(comparison.expression));
214
- // if `isUnaryNegation === literalBooleanInComparison === !negated` is true - negate the expression
215
- if (shouldNegate === isUnaryNegation) {
216
- yield fixer.insertTextBefore(mutatedNode, '!');
217
- // if the expression `exp` is not a strong precedence node, wrap it in parentheses
218
- if (!(0, util_1.isStrongPrecedenceNode)(comparison.expression)) {
219
- yield fixer.insertTextBefore(mutatedNode, '(');
220
- yield fixer.insertTextAfter(mutatedNode, ')');
206
+ fix(fixer) {
207
+ const isWrappedInUnaryNegation = nodeIsUnaryNegation(node.parent);
208
+ const mutatedNode = isWrappedInUnaryNegation ? node.parent : node;
209
+ // Whether the truth table of the overall expression being replaced
210
+ // is negated, _ignoring the nullish cases_.
211
+ const isOverallNegated = booleanXor(isWrappedInUnaryNegation, comparison.negated, comparison.booleanLiteral === 'false');
212
+ // we'll build up the replacement text from the compared expression outwards.
213
+ let replacementText = context.sourceCode.getText(comparison.expression);
214
+ let mayNeedParentheses = !(0, util_1.isStrongPrecedenceNode)(comparison.expression);
215
+ // In maybeNullish === false, nullish values have the same truth table
216
+ // as `true`.
217
+ if (comparison.expressionIsNullableBoolean &&
218
+ comparison.booleanLiteral === 'false') {
219
+ if (mayNeedParentheses) {
220
+ replacementText = parenthesize(replacementText);
221
221
  }
222
+ replacementText = `${replacementText} ?? true`;
223
+ mayNeedParentheses = true;
222
224
  }
223
- // if the expression `exp` is nullable, and we're not comparing to `true`, insert `?? true`
224
- if (comparison.expressionIsNullableBoolean &&
225
- !comparison.literalBooleanInComparison) {
226
- // provide the default `true`
227
- yield fixer.insertTextBefore(mutatedNode, '(');
228
- yield fixer.insertTextAfter(mutatedNode, ' ?? true)');
225
+ if (isOverallNegated) {
226
+ if (mayNeedParentheses) {
227
+ replacementText = parenthesize(replacementText);
228
+ }
229
+ replacementText = `!${replacementText}`;
230
+ mayNeedParentheses = false;
229
231
  }
232
+ if (mayNeedParentheses && (0, util_1.isWeakPrecedenceParent)(mutatedNode)) {
233
+ replacementText = parenthesize(replacementText);
234
+ }
235
+ return fixer.replaceText(mutatedNode, replacementText);
230
236
  },
231
237
  });
232
238
  },
@@ -259,3 +265,10 @@ function getEqualsKind(operator) {
259
265
  return undefined;
260
266
  }
261
267
  }
268
+ function booleanXor(arg0, ...args) {
269
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion
270
+ return args.reduce((acc, curr) => acc !== Boolean(curr), Boolean(arg0));
271
+ }
272
+ function parenthesize(text) {
273
+ return `(${text})`;
274
+ }
@@ -357,5 +357,5 @@ function isWhitespace(x) {
357
357
  return /^\s*$/.test(x);
358
358
  }
359
359
  function startsWithNewLine(x) {
360
- return x.startsWith('\n') || x.startsWith('\r\n');
360
+ return utils_1.ASTUtils.LINEBREAK_MATCHER.exec(x)?.index === 0;
361
361
  }
@@ -565,10 +565,29 @@ exports.default = (0, util_1.createRule)({
565
565
  token.value === '<'), util_1.NullThrowsReasons.MissingToken('<', 'type annotation'));
566
566
  const closingAngleBracket = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(node.typeAnnotation, token => token.type === utils_1.AST_TOKEN_TYPES.Punctuator &&
567
567
  token.value === '>'), util_1.NullThrowsReasons.MissingToken('>', 'type annotation'));
568
- return fixer.removeRange([
568
+ // Removing the angle-bracketed type can leave a bare object
569
+ // literal in a position where `{` is parsed as a block (concise
570
+ // arrow body, or the start of an expression statement). Wrap the
571
+ // result in parentheses to preserve the original expression
572
+ // semantics.
573
+ const needsParens = node.expression.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
574
+ ((node.parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
575
+ node.parent.body === node) ||
576
+ node.parent.type === utils_1.AST_NODE_TYPES.ExpressionStatement) &&
577
+ !(0, util_1.isParenthesized)(node, context.sourceCode) &&
578
+ !(0, util_1.isParenthesized)(node.expression, context.sourceCode);
579
+ const fixes = [];
580
+ if (needsParens) {
581
+ fixes.push(fixer.insertTextBefore(node, '('));
582
+ }
583
+ fixes.push(fixer.removeRange([
569
584
  openingAngleBracket.range[0],
570
585
  closingAngleBracket.range[1],
571
- ]);
586
+ ]));
587
+ if (needsParens) {
588
+ fixes.push(fixer.insertTextAfter(node, ')'));
589
+ }
590
+ return fixes;
572
591
  }
573
592
  const asToken = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(node.expression, token => token.type === utils_1.AST_TOKEN_TYPES.Identifier && token.value === 'as'), util_1.NullThrowsReasons.MissingToken('>', 'type annotation'));
574
593
  const tokenBeforeAs = (0, util_1.nullThrows)(context.sourceCode.getTokenBefore(asToken, {
@@ -39,4 +39,8 @@ export declare function getMovedNodeCode(params: {
39
39
  * Check if a node will always have the same precedence if its parent changes.
40
40
  */
41
41
  export declare function isStrongPrecedenceNode(innerNode: TSESTree.Node): boolean;
42
+ /**
43
+ * Check if a node's parent could have different precedence if the node changes.
44
+ */
45
+ export declare function isWeakPrecedenceParent(node: TSESTree.Node): boolean;
42
46
  export {};
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getWrappingFixer = getWrappingFixer;
4
4
  exports.getMovedNodeCode = getMovedNodeCode;
5
5
  exports.isStrongPrecedenceNode = isStrongPrecedenceNode;
6
+ exports.isWeakPrecedenceParent = isWeakPrecedenceParent;
6
7
  const utils_1 = require("@typescript-eslint/utils");
7
8
  /**
8
9
  * Wraps node with some code. Adds parentheses as necessary.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typescript-eslint/eslint-plugin",
3
- "version": "8.61.1-alpha.3",
3
+ "version": "8.61.1-alpha.4",
4
4
  "description": "TypeScript plugin for ESLint",
5
5
  "files": [
6
6
  "dist",
@@ -52,10 +52,10 @@
52
52
  "ignore": "^7.0.5",
53
53
  "natural-compare": "^1.4.0",
54
54
  "ts-api-utils": "^2.5.0",
55
- "@typescript-eslint/type-utils": "8.61.1-alpha.3",
56
- "@typescript-eslint/scope-manager": "8.61.1-alpha.3",
57
- "@typescript-eslint/visitor-keys": "8.61.1-alpha.3",
58
- "@typescript-eslint/utils": "8.61.1-alpha.3"
55
+ "@typescript-eslint/scope-manager": "8.61.1-alpha.4",
56
+ "@typescript-eslint/type-utils": "8.61.1-alpha.4",
57
+ "@typescript-eslint/utils": "8.61.1-alpha.4",
58
+ "@typescript-eslint/visitor-keys": "8.61.1-alpha.4"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/json-schema": "^7.0.15",
@@ -79,13 +79,13 @@
79
79
  "typescript": ">=4.8.4 <6.1.0",
80
80
  "unist-util-visit": "^5.0.0",
81
81
  "vitest": "^4.0.18",
82
- "@typescript-eslint/rule-schema-to-typescript-types": "8.61.1-alpha.3",
83
- "@typescript-eslint/rule-tester": "8.61.1-alpha.3"
82
+ "@typescript-eslint/rule-schema-to-typescript-types": "8.61.1-alpha.4",
83
+ "@typescript-eslint/rule-tester": "8.61.1-alpha.4"
84
84
  },
85
85
  "peerDependencies": {
86
86
  "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
87
87
  "typescript": ">=4.8.4 <6.1.0",
88
- "@typescript-eslint/parser": "^8.61.1-alpha.3"
88
+ "@typescript-eslint/parser": "^8.61.1-alpha.4"
89
89
  },
90
90
  "funding": {
91
91
  "type": "opencollective",