@typescript-eslint/eslint-plugin 8.71.1-alpha.9 → 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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
51
|
-
"@typescript-eslint/type-utils": "8.71.1
|
|
52
|
-
"@typescript-eslint/utils": "8.71.1
|
|
53
|
-
"@typescript-eslint/visitor-keys": "8.71.1
|
|
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
|
|
64
|
-
"@typescript-eslint/rule-tester": "8.71.1
|
|
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
|
|
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
|
},
|