@typescript-eslint/eslint-plugin 8.71.1-alpha.8 → 8.71.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -317,7 +317,27 @@ exports.default = (0, util_1.createRule)({
317
317
  ? node.argument
318
318
  : node.body;
319
319
  const type = (0, util_1.getConstrainedTypeAtLocation)(services, targetNode);
320
- return tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike);
320
+ if (!tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike)) {
321
+ return false;
322
+ }
323
+ const functionNode = (0, util_1.nullThrows)(node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression
324
+ ? node
325
+ : (0, getParentFunctionNode_1.getParentFunctionNode)(node), 'Expected a return statement to be inside a function.');
326
+ return functionDeclarationAllowsEmptyReturn(functionNode);
327
+ }
328
+ function functionDeclarationAllowsEmptyReturn(functionNode) {
329
+ if (!functionNode.returnType) {
330
+ return true;
331
+ }
332
+ const declaredReturnType = services.getTypeFromTypeNode(functionNode.returnType.typeAnnotation);
333
+ const checker = services.program.getTypeChecker();
334
+ const resolvedReturnType = functionNode.async
335
+ ? (checker.getAwaitedType(declaredReturnType) ?? declaredReturnType)
336
+ : declaredReturnType;
337
+ return tsutils
338
+ .unionConstituents(resolvedReturnType)
339
+ .some(part => tsutils.isTypeFlagSet(part, ts.TypeFlags.Any) ||
340
+ tsutils.isTypeFlagSet(part, ts.TypeFlags.VoidLike));
321
341
  }
322
342
  function isFunctionReturnTypeIncludesVoid(functionType) {
323
343
  const callSignatures = tsutils.getCallSignaturesOfType(functionType);
@@ -353,9 +353,24 @@ function collectTypeParameterUsageCounts(checker, node, foundIdentifierUsages, f
353
353
  // TS treats mapped types like `{[k in "a"]: T}` like `{a: T}`.
354
354
  // They have properties, so we need to avoid double-counting.
355
355
  visitType(type.templateType ?? type.constraintType, false);
356
+ // `templateType` is a lazily populated checker cache. Once it is
357
+ // populated on an instantiated mapped type, its constraint is
358
+ // reachable only here: the type parameter's declaration holds the
359
+ // original constraint from before instantiation.
360
+ if (type.templateType &&
361
+ type.constraintType &&
362
+ type.constraintType !==
363
+ getDeclaredConstraintType(type.typeParameter)) {
364
+ visitType(type.constraintType, false);
365
+ }
356
366
  }
357
367
  // TS doesn't count mapped types key remapping (`{[K in 'a' as T]: K}`)
358
368
  // but handles this under `MappedType.nameType`, so we need to visit that too.
369
+ // Instantiated mapped types populate it lazily, which getting the
370
+ // awaited type forces.
371
+ if (!type.nameType) {
372
+ checker.getAwaitedType(type);
373
+ }
359
374
  if (type.nameType) {
360
375
  visitType(type.nameType, false);
361
376
  }
@@ -386,6 +401,13 @@ function collectTypeParameterUsageCounts(checker, node, foundIdentifierUsages, f
386
401
  typeUsages.set(type, count);
387
402
  return count;
388
403
  }
404
+ function getDeclaredConstraintType(typeParameter) {
405
+ const constraint = typeParameter
406
+ ?.getSymbol()
407
+ ?.getDeclarations()
408
+ ?.find(ts.isTypeParameterDeclaration)?.constraint;
409
+ return constraint && checker.getTypeAtLocation(constraint);
410
+ }
389
411
  function visitSignature(signature) {
390
412
  if (!signature) {
391
413
  return;
@@ -147,7 +147,7 @@ exports.default = (0, util_1.createRule)({
147
147
  }
148
148
  }
149
149
  function isUnsafeAssignment(senderNode, receiverType, senderType = services.getTypeAtLocation(senderNode)) {
150
- return (hasDeepEnumAssignmentMismatch(checker, senderType, receiverType) &&
150
+ return (hasEnumAssignmentMismatch(checker, senderType, receiverType) &&
151
151
  !isSafeEnumBitwiseExpression(senderNode, receiverType));
152
152
  }
153
153
  function checkAssignment(receiverType, senderNode, reportingNode, messageId = 'unsafeEnumAssignment', senderType) {
@@ -181,7 +181,7 @@ exports.default = (0, util_1.createRule)({
181
181
  .flatMap(elementType => {
182
182
  const parameterType = signature.getNextParameterType();
183
183
  return parameterType != null &&
184
- hasDeepEnumAssignmentMismatch(checker, elementType, parameterType)
184
+ hasEnumAssignmentMismatch(checker, elementType, parameterType)
185
185
  ? [parameterType]
186
186
  : [];
187
187
  });
@@ -311,7 +311,8 @@ exports.default = (0, util_1.createRule)({
311
311
  // declare const foo: { [key in Fruit]: string };
312
312
  // foo[0];
313
313
  const senderType = services.getTypeAtLocation(node.property);
314
- if (receiverTypes.every(receiverType => hasDeepEnumAssignmentMismatch(checker, senderType, receiverType) || !checker.isTypeAssignableTo(senderType, receiverType))) {
314
+ if (receiverTypes.every(receiverType => hasEnumAssignmentMismatch(checker, senderType, receiverType) ||
315
+ !checker.isTypeAssignableTo(senderType, receiverType))) {
315
316
  report(node.property, 'unsafeEnumAccess', receiverTypes);
316
317
  }
317
318
  },
@@ -355,9 +356,46 @@ function getConstraintType(checker, type) {
355
356
  function getTypeArguments(checker, type) {
356
357
  return tsutils.isTypeReference(type) ? checker.getTypeArguments(type) : [];
357
358
  }
358
- function hasDeepEnumAssignmentMismatch(checker, senderType, receiverType, visited = new WeakMap()) {
359
+ const genericTypes = new WeakMap();
360
+ function isGenericType(checker, type) {
361
+ let generic = genericTypes.get(type);
362
+ if (generic == null) {
363
+ genericTypes.set(type, false);
364
+ generic =
365
+ tsutils.isTypeFlagSet(type, ts.TypeFlags.Instantiable) ||
366
+ [
367
+ ...(tsutils.isUnionOrIntersectionType(type) ? type.types : []),
368
+ ...(type.aliasTypeArguments ?? []),
369
+ ...getTypeArguments(checker, type),
370
+ ].some(part => isGenericType(checker, part));
371
+ genericTypes.set(type, generic);
372
+ }
373
+ return generic;
374
+ }
375
+ const maximumDepth = 5;
376
+ const topLevelMismatches = new WeakMap();
377
+ function hasEnumAssignmentMismatch(checker, senderType, receiverType) {
378
+ let receiverMismatches = topLevelMismatches.get(senderType);
379
+ if (receiverMismatches == null) {
380
+ receiverMismatches = new WeakMap();
381
+ topLevelMismatches.set(senderType, receiverMismatches);
382
+ }
383
+ let mismatch = receiverMismatches.get(receiverType);
384
+ if (mismatch == null) {
385
+ mismatch = hasDeepEnumAssignmentMismatch(checker, senderType, receiverType);
386
+ receiverMismatches.set(receiverType, mismatch);
387
+ }
388
+ return mismatch;
389
+ }
390
+ function hasDeepEnumAssignmentMismatch(checker, senderType, receiverType, visited = new WeakMap(), depth = 0, withinGenericMembers = false) {
391
+ if (depth > maximumDepth) {
392
+ return false;
393
+ }
359
394
  const constrainedSenderType = getConstraintType(checker, senderType);
360
395
  const constrainedReceiverType = getConstraintType(checker, receiverType);
396
+ if (constrainedSenderType === constrainedReceiverType) {
397
+ return false;
398
+ }
361
399
  // Recursive types would otherwise be visited endlessly.
362
400
  let visitedReceiverTypes = visited.get(constrainedSenderType);
363
401
  if (visitedReceiverTypes == null) {
@@ -375,22 +413,28 @@ function hasDeepEnumAssignmentMismatch(checker, senderType, receiverType, visite
375
413
  const senderTypeArguments = getTypeArguments(checker, constrainedSenderType);
376
414
  const receiverTypeArguments = getTypeArguments(checker, constrainedReceiverType);
377
415
  if (senderTypeArguments.length === receiverTypeArguments.length &&
378
- senderTypeArguments.some((senderTypeArgument, index) => hasDeepEnumAssignmentMismatch(checker, senderTypeArgument, receiverTypeArguments[index], visited))) {
416
+ senderTypeArguments.some((senderTypeArgument, index) => hasDeepEnumAssignmentMismatch(checker, senderTypeArgument, receiverTypeArguments[index], visited, depth + 1, withinGenericMembers))) {
379
417
  return true;
380
418
  }
419
+ // Members of generic types can instantiate ever-larger generic types.
420
+ const generic = isGenericType(checker, constrainedSenderType) ||
421
+ isGenericType(checker, constrainedReceiverType);
422
+ if (generic && withinGenericMembers) {
423
+ return false;
424
+ }
381
425
  // [number, Fruit] -> Fruit[]
382
426
  const senderElementType = constrainedSenderType.getNumberIndexType();
383
427
  const receiverElementType = constrainedReceiverType.getNumberIndexType();
384
428
  if (senderElementType &&
385
429
  receiverElementType &&
386
- hasDeepEnumAssignmentMismatch(checker, senderElementType, receiverElementType, visited)) {
430
+ hasDeepEnumAssignmentMismatch(checker, senderElementType, receiverElementType, visited, depth + 1, generic || withinGenericMembers)) {
387
431
  return true;
388
432
  }
389
433
  // { fruit: number } -> { fruit: Fruit }
390
434
  return constrainedReceiverType.getProperties().some(receiverProperty => {
391
435
  const senderProperty = constrainedSenderType.getProperty(receiverProperty.name);
392
436
  return (senderProperty != null &&
393
- hasDeepEnumAssignmentMismatch(checker, checker.getTypeOfSymbol(senderProperty), checker.getTypeOfSymbol(receiverProperty), visited));
437
+ hasDeepEnumAssignmentMismatch(checker, checker.getTypeOfSymbol(senderProperty), checker.getTypeOfSymbol(receiverProperty), visited, depth + 1, generic || withinGenericMembers));
394
438
  });
395
439
  }
396
440
  function getMappedKeyConstraintTypes(checker, declaration) {
@@ -52,21 +52,25 @@ function includesType(parserServices, node, typeFlagIn) {
52
52
  }
53
53
  return false;
54
54
  }
55
+ const MAYBE_UNDEFINED_FLAGS = ts.TypeFlags.Any |
56
+ ts.TypeFlags.Unknown |
57
+ ts.TypeFlags.InstantiableNonPrimitive |
58
+ ts.TypeFlags.Undefined |
59
+ ts.TypeFlags.Void;
55
60
  function isValidAndLastChainOperand(ComparisonValueType, comparisonType, parserServices) {
56
- const type = parserServices.getTypeAtLocation(ComparisonValueType);
57
- const ANY_UNKNOWN_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown;
61
+ const type = (0, util_1.getConstrainedTypeAtLocation)(parserServices, ComparisonValueType);
58
62
  const types = (0, ts_api_utils_1.unionConstituents)(type);
59
63
  switch (comparisonType) {
60
64
  case gatherLogicalOperands_1.ComparisonType.Equal: {
61
65
  const isNullish = types.some(t => (0, util_1.isTypeFlagSet)(t,
62
66
  // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-assignment
63
- ANY_UNKNOWN_FLAGS | ts.TypeFlags.Null | ts.TypeFlags.Undefined));
67
+ MAYBE_UNDEFINED_FLAGS | ts.TypeFlags.Null));
64
68
  return !isNullish;
65
69
  }
66
70
  case gatherLogicalOperands_1.ComparisonType.StrictEqual: {
67
71
  const isUndefined = types.some(t =>
68
72
  // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-assignment
69
- (0, util_1.isTypeFlagSet)(t, ANY_UNKNOWN_FLAGS | ts.TypeFlags.Undefined));
73
+ (0, util_1.isTypeFlagSet)(t, MAYBE_UNDEFINED_FLAGS));
70
74
  return !isUndefined;
71
75
  }
72
76
  case gatherLogicalOperands_1.ComparisonType.NotStrictEqual: {
@@ -78,20 +82,19 @@ function isValidAndLastChainOperand(ComparisonValueType, comparisonType, parserS
78
82
  }
79
83
  }
80
84
  function isValidOrLastChainOperand(ComparisonValueType, comparisonType, parserServices) {
81
- const type = parserServices.getTypeAtLocation(ComparisonValueType);
82
- const ANY_UNKNOWN_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown;
85
+ const type = (0, util_1.getConstrainedTypeAtLocation)(parserServices, ComparisonValueType);
83
86
  const types = (0, ts_api_utils_1.unionConstituents)(type);
84
87
  switch (comparisonType) {
85
88
  case gatherLogicalOperands_1.ComparisonType.NotEqual: {
86
89
  const isNullish = types.some(t => (0, util_1.isTypeFlagSet)(t,
87
90
  // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-assignment
88
- ANY_UNKNOWN_FLAGS | ts.TypeFlags.Null | ts.TypeFlags.Undefined));
91
+ MAYBE_UNDEFINED_FLAGS | ts.TypeFlags.Null));
89
92
  return !isNullish;
90
93
  }
91
94
  case gatherLogicalOperands_1.ComparisonType.NotStrictEqual: {
92
95
  const isUndefined = types.some(t =>
93
96
  // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-assignment
94
- (0, util_1.isTypeFlagSet)(t, ANY_UNKNOWN_FLAGS | ts.TypeFlags.Undefined));
97
+ (0, util_1.isTypeFlagSet)(t, MAYBE_UNDEFINED_FLAGS));
95
98
  return !isUndefined;
96
99
  }
97
100
  case gatherLogicalOperands_1.ComparisonType.Equal:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typescript-eslint/eslint-plugin",
3
- "version": "8.71.1-alpha.8",
3
+ "version": "8.71.1",
4
4
  "description": "TypeScript plugin for ESLint",
5
5
  "files": [
6
6
  "dist",
@@ -47,10 +47,10 @@
47
47
  ],
48
48
  "dependencies": {
49
49
  "@eslint-community/regexpp": "^4.12.2",
50
- "@typescript-eslint/scope-manager": "8.71.1-alpha.8",
51
- "@typescript-eslint/type-utils": "8.71.1-alpha.8",
52
- "@typescript-eslint/utils": "8.71.1-alpha.8",
53
- "@typescript-eslint/visitor-keys": "8.71.1-alpha.8",
50
+ "@typescript-eslint/scope-manager": "8.71.1",
51
+ "@typescript-eslint/type-utils": "8.71.1",
52
+ "@typescript-eslint/utils": "8.71.1",
53
+ "@typescript-eslint/visitor-keys": "8.71.1",
54
54
  "ignore": "^7.0.5",
55
55
  "natural-compare": "^1.4.0",
56
56
  "ts-api-utils": "^2.5.0"
@@ -60,8 +60,8 @@
60
60
  "@types/mdast": "^4.0.4",
61
61
  "@types/natural-compare": "^1.4.3",
62
62
  "@types/react": "^18.3.21",
63
- "@typescript-eslint/rule-schema-to-typescript-types": "8.71.1-alpha.8",
64
- "@typescript-eslint/rule-tester": "8.71.1-alpha.8",
63
+ "@typescript-eslint/rule-schema-to-typescript-types": "8.71.1",
64
+ "@typescript-eslint/rule-tester": "8.71.1",
65
65
  "@typescript/native": "npm:typescript@^7.0.2",
66
66
  "@vitest/coverage-v8": "^4.0.18",
67
67
  "ajv": "^6.12.6",
@@ -81,7 +81,7 @@
81
81
  "vitest": "^4.0.18"
82
82
  },
83
83
  "peerDependencies": {
84
- "@typescript-eslint/parser": "^8.71.1-alpha.8",
84
+ "@typescript-eslint/parser": "^8.71.1",
85
85
  "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
86
86
  "typescript": ">=4.8.4 <6.1.0"
87
87
  },