@depup/typescript-eslint__eslint-plugin 8.70.0-depup.0 → 8.70.1-depup.0

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 (30) hide show
  1. package/README.md +3 -3
  2. package/changes.json +2 -2
  3. package/dist/index.d.ts +1 -1
  4. package/dist/raw-plugin.d.ts +1 -1
  5. package/dist/rules/await-thenable.js +14 -3
  6. package/dist/rules/index.d.ts +1 -1
  7. package/dist/rules/no-explicit-any.js +10 -2
  8. package/dist/rules/no-generated-empty-object-type.js +6 -2
  9. package/dist/rules/no-meaningless-void-operator.js +5 -0
  10. package/dist/rules/no-misused-promises.js +14 -10
  11. package/dist/rules/no-misused-spread.js +4 -1
  12. package/dist/rules/no-unnecessary-condition.js +52 -5
  13. package/dist/rules/no-unnecessary-parameter-property-assignment.js +12 -4
  14. package/dist/rules/no-unnecessary-type-assertion.js +3 -6
  15. package/dist/rules/no-unnecessary-type-parameters.js +16 -18
  16. package/dist/rules/no-useless-default-assignment.d.ts +1 -1
  17. package/dist/rules/no-useless-default-assignment.js +27 -7
  18. package/dist/rules/return-await.js +3 -13
  19. package/dist/rules/unbound-method.js +31 -0
  20. package/dist/util/getAwaitTokenRemovalRange.d.ts +4 -0
  21. package/dist/util/getAwaitTokenRemovalRange.js +15 -0
  22. package/dist/util/getWrappingFixer.js +0 -1
  23. package/dist/util/index.d.ts +3 -1
  24. package/dist/util/index.js +3 -1
  25. package/dist/util/isStartOfArrowFunctionBodyNeedingParentheses.d.ts +2 -0
  26. package/dist/util/{isStartOfArrowFunctionBody.js → isStartOfArrowFunctionBodyNeedingParentheses.js} +6 -2
  27. package/dist/util/isStartOfExpressionStatementNeedingParentheses.d.ts +2 -0
  28. package/dist/util/isStartOfExpressionStatementNeedingParentheses.js +9 -0
  29. package/package.json +12 -12
  30. package/dist/util/isStartOfArrowFunctionBody.d.ts +0 -2
package/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @depup/typescript-eslint__eslint-plugin
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [@typescript-eslint/eslint-plugin](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin) @ 8.70.0 |
17
- | Processed | 2026-09-08 |
16
+ | Original | [@typescript-eslint/eslint-plugin](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin) @ 8.70.1 |
17
+ | Processed | 2026-09-22 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 1 |
20
20
 
@@ -22,7 +22,7 @@ npm install @depup/typescript-eslint__eslint-plugin
22
22
 
23
23
  | Dependency | From | To |
24
24
  |------------|------|-----|
25
- | ignore | ^7.0.5 | ^7.0.8 |
25
+ | ignore | ^7.0.5 | ^7.0.9 |
26
26
 
27
27
  ---
28
28
 
package/changes.json CHANGED
@@ -2,9 +2,9 @@
2
2
  "bumped": {
3
3
  "ignore": {
4
4
  "from": "^7.0.5",
5
- "to": "^7.0.8"
5
+ "to": "^7.0.9"
6
6
  }
7
7
  },
8
- "timestamp": "2026-09-08T00:33:07.330Z",
8
+ "timestamp": "2026-09-22T00:32:40.856Z",
9
9
  "totalUpdated": 1
10
10
  }
package/dist/index.d.ts CHANGED
@@ -994,7 +994,7 @@ declare const _default: {
994
994
  'no-useless-constructor': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noUselessConstructor" | "removeConstructor", [], import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
995
995
  name: string;
996
996
  };
997
- 'no-useless-default-assignment': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noStrictNullCheck" | "preferOptionalSyntax" | "uselessDefaultAssignment" | "uselessUndefined", [{
997
+ 'no-useless-default-assignment': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noStrictNullCheck" | "preferOptionalSyntax" | "removeDefaultAssignment" | "useOptionalSyntax" | "uselessDefaultAssignment" | "uselessUndefined", [{
998
998
  allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
999
999
  }], import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
1000
1000
  name: string;
@@ -1017,7 +1017,7 @@ declare const _default: {
1017
1017
  'no-useless-constructor': TSESLint.RuleModule<"noUselessConstructor" | "removeConstructor", [], import("../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
1018
1018
  name: string;
1019
1019
  };
1020
- 'no-useless-default-assignment': TSESLint.RuleModule<"noStrictNullCheck" | "preferOptionalSyntax" | "uselessDefaultAssignment" | "uselessUndefined", [{
1020
+ 'no-useless-default-assignment': TSESLint.RuleModule<"noStrictNullCheck" | "preferOptionalSyntax" | "removeDefaultAssignment" | "useOptionalSyntax" | "uselessDefaultAssignment" | "uselessUndefined", [{
1021
1021
  allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
1022
1022
  }], import("../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
1023
1023
  name: string;
@@ -77,7 +77,18 @@ exports.default = (0, util_1.createRule)({
77
77
  messageId: 'removeAwait',
78
78
  fix(fixer) {
79
79
  const awaitKeyword = (0, util_1.nullThrows)(context.sourceCode.getFirstToken(node, util_1.isAwaitKeyword), util_1.NullThrowsReasons.MissingToken('await', 'await expression'));
80
- return fixer.remove(awaitKeyword);
80
+ const awaitTokenRemovalRange = (0, util_1.getAwaitTokenRemovalRange)(context.sourceCode, awaitKeyword);
81
+ const awaitRemovalFix = fixer.removeRange(awaitTokenRemovalRange);
82
+ const firstOperandToken = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(awaitKeyword), util_1.NullThrowsReasons.MissingToken('operand', 'await expression'));
83
+ if ((0, util_1.isStartOfArrowFunctionBodyNeedingParentheses)(node, firstOperandToken, context.sourceCode) ||
84
+ (0, util_1.isStartOfExpressionStatementNeedingParentheses)(node, firstOperandToken)) {
85
+ return [
86
+ awaitRemovalFix,
87
+ fixer.insertTextBefore(awaitArgumentEsNode, '('),
88
+ fixer.insertTextAfter(awaitArgumentEsNode, ')'),
89
+ ];
90
+ }
91
+ return awaitRemovalFix;
81
92
  },
82
93
  },
83
94
  ],
@@ -135,7 +146,7 @@ exports.default = (0, util_1.createRule)({
135
146
  messageId: 'convertToOrdinaryFor',
136
147
  fix(fixer) {
137
148
  const awaitToken = (0, util_1.nullThrows)(context.sourceCode.getFirstToken(node, util_1.isAwaitKeyword), util_1.NullThrowsReasons.MissingToken('await', 'for await loop'));
138
- return fixer.remove(awaitToken);
149
+ return fixer.removeRange((0, util_1.getAwaitTokenRemovalRange)(context.sourceCode, awaitToken));
139
150
  },
140
151
  },
141
152
  ],
@@ -168,7 +179,7 @@ exports.default = (0, util_1.createRule)({
168
179
  messageId: 'removeAwait',
169
180
  fix(fixer) {
170
181
  const awaitToken = (0, util_1.nullThrows)(context.sourceCode.getFirstToken(node, util_1.isAwaitKeyword), util_1.NullThrowsReasons.MissingToken('await', 'await using'));
171
- return fixer.remove(awaitToken);
182
+ return fixer.removeRange((0, util_1.getAwaitTokenRemovalRange)(context.sourceCode, awaitToken));
172
183
  },
173
184
  },
174
185
  }),
@@ -315,7 +315,7 @@ declare const rules: {
315
315
  'no-useless-constructor': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noUselessConstructor" | "removeConstructor", [], import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
316
316
  name: string;
317
317
  };
318
- 'no-useless-default-assignment': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noStrictNullCheck" | "preferOptionalSyntax" | "uselessDefaultAssignment" | "uselessUndefined", [{
318
+ 'no-useless-default-assignment': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noStrictNullCheck" | "preferOptionalSyntax" | "removeDefaultAssignment" | "useOptionalSyntax" | "uselessDefaultAssignment" | "uselessUndefined", [{
319
319
  allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
320
320
  }], import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
321
321
  name: string;
@@ -155,6 +155,14 @@ exports.default = (0, util_1.createRule)({
155
155
  return fixer.replaceText(node.parent, 'PropertyKey');
156
156
  };
157
157
  }
158
+ function createUnknownFixer(node) {
159
+ return (fixer) => {
160
+ return fixer.replaceText(node, node.parent.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation &&
161
+ isNodeRestElementInFunction(node.parent.parent)
162
+ ? 'unknown[]'
163
+ : 'unknown');
164
+ };
165
+ }
158
166
  return {
159
167
  TSAnyKeyword(node) {
160
168
  const isKeyofAny = isNodeWithinKeyofAny(node);
@@ -173,7 +181,7 @@ exports.default = (0, util_1.createRule)({
173
181
  : [
174
182
  {
175
183
  messageId: 'suggestUnknown',
176
- fix: fixer => fixer.replaceText(node, 'unknown'),
184
+ fix: createUnknownFixer(node),
177
185
  },
178
186
  {
179
187
  messageId: 'suggestNever',
@@ -184,7 +192,7 @@ exports.default = (0, util_1.createRule)({
184
192
  if (fixToUnknown) {
185
193
  fixOrSuggest.fix = isKeyofAny
186
194
  ? createPropertyKeyFixer(node)
187
- : fixer => fixer.replaceText(node, 'unknown');
195
+ : createUnknownFixer(node);
188
196
  }
189
197
  context.report({
190
198
  node,
@@ -63,8 +63,12 @@ exports.default = (0, util_1.createRule)({
63
63
  type.getCallSignatures().length === 0 &&
64
64
  type.getConstructSignatures().length === 0 &&
65
65
  // Types still awaiting type arguments, such as `Record<T, unknown>`
66
- // inside a generic declaration, also have no members yet.
67
- checker.isTypeAssignableTo(checker.getNumberType(), type));
66
+ // inside a generic declaration, also have no members yet. Every
67
+ // primitive is assignable to `{}`, so probing with more than one of
68
+ // them rules those out: a mapped type whose keys are not resolved yet,
69
+ // such as `{ [K in Keys<T>]: K }`, accepts `number` but not `string`.
70
+ checker.isTypeAssignableTo(checker.getNumberType(), type) &&
71
+ checker.isTypeAssignableTo(checker.getStringType(), type));
68
72
  }
69
73
  function containsEmptyObjectType(type) {
70
74
  return (isEmptyObjectType(type) ||
@@ -84,6 +84,11 @@ exports.default = (0, util_1.createRule)({
84
84
  if (inner.type === utils_1.AST_NODE_TYPES.Literal && inner.value === 0) {
85
85
  return;
86
86
  }
87
+ // `void (x = value)` discards the assignment result so the
88
+ // expression evaluates to undefined (e.g. `() => void (x = 1)`).
89
+ if (inner.type === utils_1.AST_NODE_TYPES.AssignmentExpression) {
90
+ return;
91
+ }
87
92
  const tsArgument = services.esTreeNodeToTSNodeMap.get(node.argument);
88
93
  const argType = services.getTypeAtLocation(node.argument);
89
94
  // Allow `void promiseValue` so this rule does not fight no-floating-promises.
@@ -841,19 +841,23 @@ function hasWellKnownSymbolWithVoidReturn(checker, node, type, symbolName) {
841
841
  function hasMatchingPromiseTypeArgument(checker, node) {
842
842
  const type = checker.getTypeAtLocation(node);
843
843
  const unionConstituents = tsutils.unionConstituents(checker.getApparentType(type));
844
- const promiseType = unionConstituents.find(type => tsutils.isThenableType(checker, node, type));
845
- if (!promiseType) {
844
+ const promiseTypes = unionConstituents.filter(type => tsutils.isThenableType(checker, node, type));
845
+ if (promiseTypes.length === 0) {
846
846
  return false;
847
847
  }
848
- const nonPromiseUnionConstituents = unionConstituents.filter(type => type !== promiseType);
849
- const awaitedType = checker.getAwaitedType(promiseType);
850
- if (!awaitedType) {
851
- return false;
848
+ const nonPromiseUnionConstituents = unionConstituents.filter(type => !promiseTypes.includes(type));
849
+ const awaitedTypeConstituents = [];
850
+ for (const promiseType of promiseTypes) {
851
+ const awaitedType = checker.getAwaitedType(promiseType);
852
+ if (!awaitedType) {
853
+ return false;
854
+ }
855
+ awaitedTypeConstituents.push(...tsutils.unionConstituents(awaitedType));
852
856
  }
853
- const awaitedTypeConstituents = tsutils.unionConstituents(awaitedType);
854
- return (nonPromiseUnionConstituents.length === awaitedTypeConstituents.length &&
855
- nonPromiseUnionConstituents.every(type => awaitedTypeConstituents.some(awaited => checker.isTypeAssignableTo(type, awaited) &&
856
- checker.isTypeAssignableTo(awaited, type))));
857
+ const typesAreEquivalent = (left, right) => checker.isTypeAssignableTo(left, right) &&
858
+ checker.isTypeAssignableTo(right, left);
859
+ return (nonPromiseUnionConstituents.every(type => awaitedTypeConstituents.some(awaited => typesAreEquivalent(type, awaited))) &&
860
+ awaitedTypeConstituents.every(awaited => nonPromiseUnionConstituents.some(type => typesAreEquivalent(type, awaited))));
857
861
  }
858
862
  function normalizeFlagUnionsOption(checksConditionals) {
859
863
  if (!checksConditionals || checksConditionals === true) {
@@ -98,7 +98,7 @@ exports.default = (0, util_1.createRule)({
98
98
  }
99
99
  function getMapSpreadSuggestions(node, type) {
100
100
  const types = tsutils.unionConstituents(type);
101
- if (types.some(t => !isMap(services.program, t))) {
101
+ if (types.some(t => !isIterableMap(services.program, t))) {
102
102
  return null;
103
103
  }
104
104
  if (node.parent.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
@@ -252,6 +252,9 @@ function isClassDeclaration(type) {
252
252
  function isMap(program, type) {
253
253
  return isTypeRecurser(type, t => (0, util_1.isBuiltinSymbolLike)(program, t, ['Map', 'ReadonlyMap', 'WeakMap']));
254
254
  }
255
+ function isIterableMap(program, type) {
256
+ return isTypeRecurser(type, t => (0, util_1.isBuiltinSymbolLike)(program, t, ['Map', 'ReadonlyMap']));
257
+ }
255
258
  function isTypeRecurser(type, predicate) {
256
259
  if (type.isUnionOrIntersection()) {
257
260
  return type.types.some(t => isTypeRecurser(t, predicate));
@@ -55,6 +55,15 @@ function isPossiblyNullish(type) {
55
55
  .unionConstituents(type)
56
56
  .some(t => isNullishType(t) || (0, util_1.isTypeFlagSet)(t, ts.TypeFlags.Void));
57
57
  }
58
+ /**
59
+ * Note that this differs from {@link isNullableType} in that it doesn't consider
60
+ * `any` or `unknown` to be nullable.
61
+ */
62
+ function isPossiblyNonNullish(type) {
63
+ return tsutils
64
+ .unionConstituents(type)
65
+ .some(t => !isNullishType(t) && !(0, util_1.isTypeFlagSet)(t, ts.TypeFlags.Void));
66
+ }
58
67
  function toStaticValue(type) {
59
68
  // type.isLiteral() only covers numbers/bigints and strings, hence the rest of the branches.
60
69
  if (tsutils.isBooleanLiteralType(type)) {
@@ -262,11 +271,48 @@ exports.default = (0, util_1.createRule)({
262
271
  const propertyType = objectType
263
272
  .getProperties()
264
273
  .find(prop => prop.name === propertyName);
265
- if (propertyType &&
266
- tsutils.isSymbolFlagSet(propertyType, ts.SymbolFlags.Optional)) {
267
- return true;
274
+ return (propertyType != null &&
275
+ tsutils.isSymbolFlagSet(propertyType, ts.SymbolFlags.Optional));
276
+ }
277
+ /**
278
+ * Obtains the property types of the computed member expression
279
+ * @param node The member expression
280
+ * @returns The property types, unless the node isn't computed.
281
+ */
282
+ function getComputedMemberPropertyTypes(node) {
283
+ if (!node.computed) {
284
+ return undefined;
285
+ }
286
+ const objectType = (0, util_1.getConstrainedTypeAtLocation)(services, node.object);
287
+ const propertyType = (0, util_1.getConstrainedTypeAtLocation)(services, node.property);
288
+ const propertyTypes = [];
289
+ for (const keyType of tsutils.unionConstituents(propertyType)) {
290
+ if (!keyType.isStringLiteral() && !keyType.isNumberLiteral()) {
291
+ return undefined;
292
+ }
293
+ const selectedType = (0, util_1.getTypeOfPropertyOfName)(checker, objectType, String(keyType.value));
294
+ if (!selectedType) {
295
+ return undefined;
296
+ }
297
+ propertyTypes.push(selectedType);
268
298
  }
269
- return false;
299
+ return propertyTypes;
300
+ }
301
+ /**
302
+ * Verify if the node may have non-nullish properties
303
+ * @param node The node to verify
304
+ * @returns If it may have non-nullish properties, unless the node isn't a
305
+ * MemberExpression
306
+ */
307
+ function hasPossiblyNonNullishComputedMemberProperty(node) {
308
+ if (node.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
309
+ return false;
310
+ }
311
+ const propertyTypes = getComputedMemberPropertyTypes(node);
312
+ if (propertyTypes == null) {
313
+ return false;
314
+ }
315
+ return propertyTypes.some(isPossiblyNonNullish);
270
316
  }
271
317
  /**
272
318
  * Checks if a conditional node is necessary:
@@ -344,7 +390,8 @@ exports.default = (0, util_1.createRule)({
344
390
  messageId = 'neverNullish';
345
391
  }
346
392
  }
347
- else if (isAlwaysNullish(type)) {
393
+ else if (isAlwaysNullish(type) &&
394
+ !hasPossiblyNonNullishComputedMemberProperty(node)) {
348
395
  messageId = 'alwaysNullish';
349
396
  }
350
397
  if (messageId) {
@@ -101,10 +101,6 @@ exports.default = (0, util_1.createRule)({
101
101
  });
102
102
  },
103
103
  "MethodDefinition[kind='constructor'] > FunctionExpression AssignmentExpression"(node) {
104
- const leftName = getPropertyName(node.left);
105
- if (!leftName) {
106
- return;
107
- }
108
104
  let functionNode = findParentFunction(node);
109
105
  if (functionNode && isArrowIIFE(functionNode)) {
110
106
  functionNode = findParentFunction(functionNode.parent);
@@ -113,6 +109,18 @@ exports.default = (0, util_1.createRule)({
113
109
  return;
114
110
  }
115
111
  const { assignedBeforeUnnecessary, unnecessaryAssignments } = (0, util_1.nullThrows)(reportInfoStack.at(reportInfoStack.length - 1), 'The top of stack should exist');
112
+ // A prior write to the parameter binding means a later `this.x = x`
113
+ // may copy a different value than the parameter property initialized.
114
+ if (node.left.type === utils_1.AST_NODE_TYPES.Identifier) {
115
+ if (isReferenceFromParameter(node.left)) {
116
+ assignedBeforeUnnecessary.add(node.left.name);
117
+ }
118
+ return;
119
+ }
120
+ const leftName = getPropertyName(node.left);
121
+ if (!leftName) {
122
+ return;
123
+ }
116
124
  if (!UNNECESSARY_OPERATORS.has(node.operator)) {
117
125
  assignedBeforeUnnecessary.add(leftName);
118
126
  return;
@@ -178,7 +178,7 @@ exports.default = (0, util_1.createRule)({
178
178
  }
179
179
  if (((0, util_1.isTypeFlagSet)(uncast, ts.TypeFlags.NonPrimitive) &&
180
180
  !(0, util_1.isTypeFlagSet)(cast, ts.TypeFlags.NonPrimitive)) ||
181
- (hasIndexSignature(uncast) && !hasIndexSignature(cast)) ||
181
+ hasIndexSignature(uncast) !== hasIndexSignature(cast) ||
182
182
  containsAny(uncast) ||
183
183
  containsAny(cast) ||
184
184
  (containsTypeVariable(cast) && !containsTypeVariable(uncast))) {
@@ -587,11 +587,8 @@ exports.default = (0, util_1.createRule)({
587
587
  // is parsed as a block body. In those positions the operand must be
588
588
  // wrapped in parentheses to stay an expression.
589
589
  const firstOperandToken = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(closingAngleBracket), util_1.NullThrowsReasons.MissingToken('operand', 'type assertion'));
590
- const breaksExpressionStatement = ['{', 'function', 'class'].includes(firstOperandToken.value) &&
591
- (0, util_1.isStartOfExpressionStatement)(node);
592
- const breaksArrowFunctionBody = firstOperandToken.value === '{' &&
593
- (0, util_1.isStartOfArrowFunctionBody)(node, context.sourceCode);
594
- const needsParens = breaksExpressionStatement || breaksArrowFunctionBody;
590
+ const needsParens = (0, util_1.isStartOfExpressionStatementNeedingParentheses)(node, firstOperandToken) ||
591
+ (0, util_1.isStartOfArrowFunctionBodyNeedingParentheses)(node, firstOperandToken, context.sourceCode);
595
592
  const fixes = [];
596
593
  if (needsParens) {
597
594
  fixes.push(fixer.insertTextBefore(node, '('));
@@ -101,27 +101,25 @@ exports.default = (0, util_1.createRule)({
101
101
  for (const reference of smTypeParameterVariable.references) {
102
102
  if (reference.isTypeReference) {
103
103
  const referenceNode = reference.identifier;
104
- const isComplexType = constraint?.type === utils_1.AST_NODE_TYPES.TSUnionType ||
104
+ const isWeakPrecedenceConstraint = constraint?.type === utils_1.AST_NODE_TYPES.TSUnionType ||
105
105
  constraint?.type === utils_1.AST_NODE_TYPES.TSIntersectionType ||
106
- constraint?.type === utils_1.AST_NODE_TYPES.TSConditionalType;
107
- const hasMatchingAncestorType = [
108
- utils_1.AST_NODE_TYPES.TSArrayType,
109
- utils_1.AST_NODE_TYPES.TSIndexedAccessType,
110
- utils_1.AST_NODE_TYPES.TSIntersectionType,
111
- utils_1.AST_NODE_TYPES.TSUnionType,
112
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
113
- ].some(type => referenceNode.parent.parent.type === type);
114
- if (isComplexType && hasMatchingAncestorType) {
115
- const fixResult = (0, util_1.getWrappingFixer)({
116
- node: referenceNode,
117
- innerNode: constraint,
118
- sourceCode: context.sourceCode,
119
- wrap: constraintNode => constraintNode,
120
- })(fixer);
121
- yield fixResult;
106
+ constraint?.type === utils_1.AST_NODE_TYPES.TSConditionalType ||
107
+ constraint?.type === utils_1.AST_NODE_TYPES.TSTypeOperator ||
108
+ constraint?.type === utils_1.AST_NODE_TYPES.TSFunctionType ||
109
+ constraint?.type === utils_1.AST_NODE_TYPES.TSConstructorType;
110
+ const grandparent = (0, util_1.nullThrows)(referenceNode.parent.parent, util_1.NullThrowsReasons.MissingParent);
111
+ const isWeakPrecedenceTypeParent = grandparent.type === utils_1.AST_NODE_TYPES.TSArrayType ||
112
+ grandparent.type === utils_1.AST_NODE_TYPES.TSIndexedAccessType ||
113
+ grandparent.type === utils_1.AST_NODE_TYPES.TSIntersectionType ||
114
+ grandparent.type === utils_1.AST_NODE_TYPES.TSUnionType ||
115
+ grandparent.type === utils_1.AST_NODE_TYPES.TSConditionalType ||
116
+ grandparent.type === utils_1.AST_NODE_TYPES.TSTypeOperator;
117
+ if (isWeakPrecedenceConstraint &&
118
+ isWeakPrecedenceTypeParent) {
119
+ yield fixer.replaceText(referenceNode.parent, `(${constraintText})`);
122
120
  }
123
121
  else {
124
- yield fixer.replaceText(referenceNode, constraintText);
122
+ yield fixer.replaceText(referenceNode.parent, constraintText);
125
123
  }
126
124
  }
127
125
  }
@@ -1,5 +1,5 @@
1
1
  import type { TSESLint } from '@typescript-eslint/utils';
2
- type MessageId = 'noStrictNullCheck' | 'preferOptionalSyntax' | 'uselessDefaultAssignment' | 'uselessUndefined';
2
+ type MessageId = 'noStrictNullCheck' | 'preferOptionalSyntax' | 'removeDefaultAssignment' | 'uselessDefaultAssignment' | 'uselessUndefined' | 'useOptionalSyntax';
3
3
  type Options = [
4
4
  {
5
5
  allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
@@ -46,12 +46,14 @@ exports.default = (0, util_1.createRule)({
46
46
  recommended: 'strict',
47
47
  requiresTypeChecking: true,
48
48
  },
49
- fixable: 'code',
49
+ hasSuggestions: true,
50
50
  messages: {
51
51
  noStrictNullCheck: 'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.',
52
- preferOptionalSyntax: 'Using `= undefined` to make a parameter optional adds unnecessary runtime logic. Use the `?` optional syntax instead.',
52
+ preferOptionalSyntax: 'Using `= undefined` to make a parameter optional adds unnecessary runtime logic.',
53
+ removeDefaultAssignment: 'Remove the default value.',
53
54
  uselessDefaultAssignment: 'Default value is useless because the {{ type }} is not optional.',
54
55
  uselessUndefined: 'Default value is useless because it is undefined. Optional {{ type }}s are already undefined by default.',
56
+ useOptionalSyntax: 'Use the `?` optional syntax instead.',
55
57
  },
56
58
  schema: [
57
59
  {
@@ -183,8 +185,14 @@ exports.default = (0, util_1.createRule)({
183
185
  if (elementIndex < 0 || elementIndex >= tupleArgs.length) {
184
186
  return;
185
187
  }
186
- const elementType = tupleArgs[elementIndex];
187
- if (!canBeUndefined(elementType)) {
188
+ const { fixedLength, minLength } = sourceType.target;
189
+ if (elementIndex >= minLength) {
190
+ return;
191
+ }
192
+ const elementTypes = elementIndex < fixedLength
193
+ ? [tupleArgs[elementIndex]]
194
+ : tupleArgs.slice(fixedLength);
195
+ if (!elementTypes.some(canBeUndefined)) {
188
196
  reportUselessDefaultAssignment(node, 'property');
189
197
  }
190
198
  }
@@ -280,7 +288,7 @@ exports.default = (0, util_1.createRule)({
280
288
  node: node.right,
281
289
  messageId: 'uselessDefaultAssignment',
282
290
  data: { type },
283
- fix: fixer => removeDefault(fixer, node),
291
+ suggest: [removeDefaultSuggestion(node)],
284
292
  });
285
293
  }
286
294
  function reportUselessUndefined(node, type) {
@@ -288,13 +296,19 @@ exports.default = (0, util_1.createRule)({
288
296
  node: node.right,
289
297
  messageId: 'uselessUndefined',
290
298
  data: { type },
291
- fix: fixer => removeDefault(fixer, node),
299
+ suggest: [removeDefaultSuggestion(node)],
292
300
  });
293
301
  }
294
302
  function reportPreferOptionalSyntax(node) {
295
303
  context.report({
296
304
  node: node.right,
297
305
  messageId: 'preferOptionalSyntax',
306
+ suggest: [useOptionalSyntaxSuggestion(node)],
307
+ });
308
+ }
309
+ function useOptionalSyntaxSuggestion(node) {
310
+ return {
311
+ messageId: 'useOptionalSyntax',
298
312
  *fix(fixer) {
299
313
  yield removeDefault(fixer, node);
300
314
  const { left } = node;
@@ -302,7 +316,13 @@ exports.default = (0, util_1.createRule)({
302
316
  yield fixer.insertTextAfterRange([left.range[0], left.range[0] + left.name.length], '?');
303
317
  }
304
318
  },
305
- });
319
+ };
320
+ }
321
+ function removeDefaultSuggestion(node) {
322
+ return {
323
+ messageId: 'removeDefaultAssignment',
324
+ fix: fixer => removeDefault(fixer, node),
325
+ };
306
326
  }
307
327
  function removeDefault(fixer, node) {
308
328
  const start = node.left.range[1];
@@ -211,20 +211,10 @@ exports.default = (0, util_1.createRule)({
211
211
  /* istanbul ignore if */ if (!awaitToken) {
212
212
  return null;
213
213
  }
214
- const startAt = awaitToken.range[0];
215
- let endAt = awaitToken.range[1];
216
- // Also remove any extraneous whitespace after `await`, if there is any.
217
- const nextToken = context.sourceCode.getTokenAfter(awaitToken, {
218
- includeComments: true,
219
- });
220
- if (nextToken) {
221
- endAt = nextToken.range[0];
222
- }
223
- const awaitRemovalFix = fixer.removeRange([startAt, endAt]);
214
+ const awaitTokenRemovalRange = (0, util_1.getAwaitTokenRemovalRange)(context.sourceCode, awaitToken);
215
+ const awaitRemovalFix = fixer.removeRange(awaitTokenRemovalRange);
224
216
  const firstOperandToken = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(awaitToken), util_1.NullThrowsReasons.MissingToken('operand', 'await expression'));
225
- const shouldWrapInParentheses = (0, util_1.isOpeningBraceToken)(firstOperandToken) &&
226
- (0, util_1.isStartOfArrowFunctionBody)(node, context.sourceCode);
227
- if (shouldWrapInParentheses) {
217
+ if ((0, util_1.isStartOfArrowFunctionBodyNeedingParentheses)(node, firstOperandToken, context.sourceCode)) {
228
218
  return [
229
219
  awaitRemovalFix,
230
220
  fixer.insertTextBefore(node.argument, '('),
@@ -90,6 +90,19 @@ const SUPPORTED_GLOBAL_TYPES = [
90
90
  'Math',
91
91
  'JSON',
92
92
  ];
93
+ /**
94
+ * Methods on instances of built-in classes that are defined by the ECMAScript
95
+ * spec as *bound* to their instance (i.e. they are safe to call without their
96
+ * receiver), but whose TypeScript declarations don't model that binding - they
97
+ * are declared as plain methods that would require a `this` value.
98
+ *
99
+ * For example, `Intl.Collator.prototype.compare` is defined as a bound getter
100
+ * function per the ECMA-402 spec:
101
+ * https://tc39.es/ecma402/#sec-intl.collator.prototype.compare
102
+ * ...but `lib.es5.d.ts` declares it as a regular method, so this rule would
103
+ * otherwise incorrectly flag usages such as `array.sort(collator.compare)`.
104
+ */
105
+ const nativelyBoundInstanceMethods = new Set(['Collator.compare']);
93
106
  const isNotImported = (symbol, currentSourceFile) => {
94
107
  const { valueDeclaration } = symbol;
95
108
  if (!valueDeclaration) {
@@ -99,6 +112,18 @@ const isNotImported = (symbol, currentSourceFile) => {
99
112
  return (!!currentSourceFile &&
100
113
  currentSourceFile !== valueDeclaration.getSourceFile());
101
114
  };
115
+ /**
116
+ * Returns `true` if `propertyName` is a member of a built-in class instance
117
+ * that is defined by the spec as bound to that instance (see
118
+ * `nativelyBoundInstanceMethods` above).
119
+ */
120
+ function isSpecBoundBuiltinMethod(program, objectType, propertyName) {
121
+ const symbol = objectType.getSymbol();
122
+ if (!symbol || !(0, util_1.isSymbolFromDefaultLibrary)(program, symbol)) {
123
+ return false;
124
+ }
125
+ return nativelyBoundInstanceMethods.has(`${symbol.getName()}.${propertyName}`);
126
+ }
102
127
  const BASE_MESSAGE = [
103
128
  `A method that is not declared with \`this: void\` may cause unintentional scoping of \`this\` when separated from its object.`,
104
129
  `Consider using an arrow function or explicitly \`.bind()\`ing the method to avoid calling the method with an unintended \`this\` value. `,
@@ -196,6 +221,12 @@ exports.default = (0, util_1.createRule)({
196
221
  return true;
197
222
  }
198
223
  }
224
+ if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
225
+ const objectType = services.getTypeAtLocation(object);
226
+ if (isSpecBoundBuiltinMethod(services.program, objectType, property.name)) {
227
+ return true;
228
+ }
229
+ }
199
230
  // if `${object.name}.${property.name}` doesn't match any of
200
231
  // the nativelyBoundMembers, then we fallback to type-level checks
201
232
  return ((0, util_1.isBuiltinSymbolLike)(services.program, services.getTypeAtLocation(object), SUPPORTED_GLOBAL_TYPES) &&
@@ -0,0 +1,4 @@
1
+ import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
+ export declare function getAwaitTokenRemovalRange(sourceCode: TSESLint.SourceCode, awaitToken: TSESTree.IdentifierToken & {
3
+ value: 'await';
4
+ }): TSESTree.Range;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAwaitTokenRemovalRange = getAwaitTokenRemovalRange;
4
+ function getAwaitTokenRemovalRange(sourceCode, awaitToken) {
5
+ const startAt = awaitToken.range[0];
6
+ let endAt = awaitToken.range[1];
7
+ const nextToken = sourceCode.getTokenAfter(awaitToken, {
8
+ includeComments: true,
9
+ });
10
+ /* istanbul ignore else */ if (nextToken) {
11
+ endAt = nextToken.range[0];
12
+ }
13
+ return [startAt, endAt];
14
+ }
15
+ //# sourceMappingURL=getAwaitTokenRemovalRange.js.map
@@ -73,7 +73,6 @@ function isStrongPrecedenceNode(innerNode) {
73
73
  return (innerNode.type === utils_1.AST_NODE_TYPES.Literal ||
74
74
  innerNode.type === utils_1.AST_NODE_TYPES.Identifier ||
75
75
  innerNode.type === utils_1.AST_NODE_TYPES.TSTypeReference ||
76
- innerNode.type === utils_1.AST_NODE_TYPES.TSTypeOperator ||
77
76
  innerNode.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
78
77
  innerNode.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
79
78
  innerNode.type === utils_1.AST_NODE_TYPES.MemberExpression ||
@@ -4,6 +4,7 @@ export * from './baseTypeUtils';
4
4
  export * from './collectUnusedVariables';
5
5
  export * from './createRule';
6
6
  export * from './FunctionSignature';
7
+ export * from './getAwaitTokenRemovalRange';
7
8
  export * from './getBaseTypesOfClassMember';
8
9
  export * from './getFixOrSuggest';
9
10
  export * from './getFunctionHeadLoc';
@@ -19,8 +20,9 @@ export * from './isAssignee';
19
20
  export * from './isConditionalTest';
20
21
  export * from './isNodeEqual';
21
22
  export * from './isNullLiteral';
22
- export * from './isStartOfArrowFunctionBody';
23
+ export * from './isStartOfArrowFunctionBodyNeedingParentheses';
23
24
  export * from './isStartOfExpressionStatement';
25
+ export * from './isStartOfExpressionStatementNeedingParentheses';
24
26
  export * from './isUndefinedIdentifier';
25
27
  export * from './misc';
26
28
  export * from './needsPrecedingSemiColon';
@@ -21,6 +21,7 @@ __exportStar(require("./baseTypeUtils"), exports);
21
21
  __exportStar(require("./collectUnusedVariables"), exports);
22
22
  __exportStar(require("./createRule"), exports);
23
23
  __exportStar(require("./FunctionSignature"), exports);
24
+ __exportStar(require("./getAwaitTokenRemovalRange"), exports);
24
25
  __exportStar(require("./getBaseTypesOfClassMember"), exports);
25
26
  __exportStar(require("./getFixOrSuggest"), exports);
26
27
  __exportStar(require("./getFunctionHeadLoc"), exports);
@@ -36,8 +37,9 @@ __exportStar(require("./isAssignee"), exports);
36
37
  __exportStar(require("./isConditionalTest"), exports);
37
38
  __exportStar(require("./isNodeEqual"), exports);
38
39
  __exportStar(require("./isNullLiteral"), exports);
39
- __exportStar(require("./isStartOfArrowFunctionBody"), exports);
40
+ __exportStar(require("./isStartOfArrowFunctionBodyNeedingParentheses"), exports);
40
41
  __exportStar(require("./isStartOfExpressionStatement"), exports);
42
+ __exportStar(require("./isStartOfExpressionStatementNeedingParentheses"), exports);
41
43
  __exportStar(require("./isUndefinedIdentifier"), exports);
42
44
  __exportStar(require("./misc"), exports);
43
45
  __exportStar(require("./needsPrecedingSemiColon"), exports);
@@ -0,0 +1,2 @@
1
+ import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
+ export declare function isStartOfArrowFunctionBodyNeedingParentheses(node: TSESTree.Node, firstToken: TSESTree.Token, sourceCode: TSESLint.SourceCode): boolean;
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isStartOfArrowFunctionBody = isStartOfArrowFunctionBody;
3
+ exports.isStartOfArrowFunctionBodyNeedingParentheses = isStartOfArrowFunctionBodyNeedingParentheses;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const astUtils_1 = require("./astUtils");
6
+ function isStartOfArrowFunctionBodyNeedingParentheses(node, firstToken, sourceCode) {
7
+ return ((0, astUtils_1.isOpeningBraceToken)(firstToken) &&
8
+ isStartOfArrowFunctionBody(node, sourceCode));
9
+ }
6
10
  function isStartOfArrowFunctionBody(node, sourceCode) {
7
11
  let current = node;
8
12
  while (true) {
@@ -23,4 +27,4 @@ function isStartOfArrowFunctionBody(node, sourceCode) {
23
27
  current = parent;
24
28
  }
25
29
  }
26
- //# sourceMappingURL=isStartOfArrowFunctionBody.js.map
30
+ //# sourceMappingURL=isStartOfArrowFunctionBodyNeedingParentheses.js.map
@@ -0,0 +1,2 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ export declare function isStartOfExpressionStatementNeedingParentheses(node: TSESTree.Node, firstToken: TSESTree.Token): boolean;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isStartOfExpressionStatementNeedingParentheses = isStartOfExpressionStatementNeedingParentheses;
4
+ const isStartOfExpressionStatement_1 = require("./isStartOfExpressionStatement");
5
+ function isStartOfExpressionStatementNeedingParentheses(node, firstToken) {
6
+ return (['{', 'class', 'function'].includes(firstToken.value) &&
7
+ (0, isStartOfExpressionStatement_1.isStartOfExpressionStatement)(node));
8
+ }
9
+ //# sourceMappingURL=isStartOfExpressionStatementNeedingParentheses.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depup/typescript-eslint__eslint-plugin",
3
- "version": "8.70.0-depup.0",
3
+ "version": "8.70.1-depup.0",
4
4
  "description": "TypeScript plugin for ESLint (with updated dependencies)",
5
5
  "files": [
6
6
  "dist",
@@ -55,11 +55,11 @@
55
55
  ],
56
56
  "dependencies": {
57
57
  "@eslint-community/regexpp": "^4.12.2",
58
- "@typescript-eslint/scope-manager": "8.70.0",
59
- "@typescript-eslint/type-utils": "8.70.0",
60
- "@typescript-eslint/utils": "8.70.0",
61
- "@typescript-eslint/visitor-keys": "8.70.0",
62
- "ignore": "^7.0.8",
58
+ "@typescript-eslint/scope-manager": "8.70.1",
59
+ "@typescript-eslint/type-utils": "8.70.1",
60
+ "@typescript-eslint/utils": "8.70.1",
61
+ "@typescript-eslint/visitor-keys": "8.70.1",
62
+ "ignore": "^7.0.9",
63
63
  "natural-compare": "^1.4.0",
64
64
  "ts-api-utils": "^2.5.0"
65
65
  },
@@ -68,8 +68,8 @@
68
68
  "@types/mdast": "^4.0.4",
69
69
  "@types/natural-compare": "^1.4.3",
70
70
  "@types/react": "^18.3.21",
71
- "@typescript-eslint/rule-schema-to-typescript-types": "8.70.0",
72
- "@typescript-eslint/rule-tester": "8.70.0",
71
+ "@typescript-eslint/rule-schema-to-typescript-types": "8.70.1",
72
+ "@typescript-eslint/rule-tester": "8.70.1",
73
73
  "@typescript/native": "npm:typescript@^7.0.2",
74
74
  "@vitest/coverage-v8": "^4.0.18",
75
75
  "ajv": "^6.12.6",
@@ -89,7 +89,7 @@
89
89
  "vitest": "^4.0.18"
90
90
  },
91
91
  "peerDependencies": {
92
- "@typescript-eslint/parser": "^8.70.0",
92
+ "@typescript-eslint/parser": "^8.70.1",
93
93
  "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
94
94
  "typescript": ">=4.8.4 <6.1.0"
95
95
  },
@@ -144,13 +144,13 @@
144
144
  "changes": {
145
145
  "ignore": {
146
146
  "from": "^7.0.5",
147
- "to": "^7.0.8"
147
+ "to": "^7.0.9"
148
148
  }
149
149
  },
150
150
  "depsUpdated": 1,
151
151
  "originalPackage": "@typescript-eslint/eslint-plugin",
152
- "originalVersion": "8.70.0",
153
- "processedAt": "2026-09-08T00:33:17.890Z",
152
+ "originalVersion": "8.70.1",
153
+ "processedAt": "2026-09-22T00:32:50.942Z",
154
154
  "smokeTest": "passed"
155
155
  }
156
156
  }
@@ -1,2 +0,0 @@
1
- import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
- export declare function isStartOfArrowFunctionBody(node: TSESTree.Node, sourceCode: TSESLint.SourceCode): boolean;