8000 feat(eslint-plugin): [thenable-in-promise-aggregators] resolve #1804 · Tjstretchalot/typescript-eslint@e0ac8f3 · GitHub
[go: up one dir, main page]

Skip to content

Commit e0ac8f3

Browse files
committed
feat(eslint-plugin): [thenable-in-promise-aggregators] resolve typescript-eslint#1804
1 parent bc4b99a commit e0ac8f3

File tree

3 files changed

+484
-0
lines changed

3 files changed

+484
-0
lines changed

packages/eslint-plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ import spaceBeforeFunctionParen from './space-before-function-paren';
132132
import spaceInfixOps from './space-infix-ops';
133133
import strictBooleanExpressions from './strict-boolean-expressions';
134134
import switchExhaustivenessCheck from './switch-exhaustiveness-check';
135+
import thenableInPromiseAggregators from './thenable-in-promise-aggregators';
135136
import tripleSlashReference from './triple-slash-reference';
136137
import typeAnnotationSpacing from './type-annotation-spacing';
137138
import typedef from './typedef';
@@ -273,6 +274,7 @@ export default {
273274
'space-infix-ops': spaceInfixOps,
274275
'strict-boolean-expressions': strictBooleanExpressions,
275276
'switch-exhaustiveness-check': switchExhaustivenessCheck,
277+
'thenable-in-promise-aggregators': thenableInPromiseAggregators,
276278
'triple-slash-reference': tripleSlashReference,
277279
'type-annotation-spacing': typeAnnotationSpacing,
278280
typedef: typedef,
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import * as tsutils from 'ts-api-utils';
2+
import {
3+
isTypeAnyType,
4+
isTypeUnknownType,
5+
} from '@typescript-eslint/type-utils';
6+
import { createRule, getParserServices } from '../util';
7+
8+
export default createRule({
9+
name: 'thenable-in-promise-aggregators',
10+
meta: {
11+
docs: {
12+
description:
13+
'Disallow passing non-Thenable values to promise aggregators',
14+
recommended: 'recommended',
15+
requiresTypeChecking: true,
16+
},
17+
messages: {
18+
inArray:
19+
'Unexpected non-Thenable value in array passed to promise aggregator.',
20+
arrayArg:
21+
'Unexpected array of non-Thenable values passed to promise aggregator.',
22+
nonArrayArg: 'Unexpected non-array passed to promise aggregator.',
23+
},
24+
schema: [],
25+
type: 'problem',
26+
},
27+
defaultOptions: [],
28+
29+
create(context) {
30+
const services = getParserServices(context);
31+
const checker = services.program.getTypeChecker();
32+
33+
return {
34+
CallExpression(node): void {
35+
if (node.callee.type !== 'MemberExpression') {
36+
return;
37+
}
38+
if (node.callee.object.type !== 'Identifier') {
39+
return;
40+
}
41+
if (node.callee.object.name !== 'Promise') {
42+
return;
43+
}
44+
if (node.callee.property.type !== 'Identifier') {
45+
return;
46+
}
47+
48+
const { name } = node.callee.property;
49+
if (!['race', 'all', 'allSettled'].includes(name)) {
50+
return;
51+
}
52+
53+
const { arguments: args } = node;
54+
if (args.length !== 1) {
55+
return;
56+
}
57+
58+
const arg = args[0];
59+
if (arg.type === 'ArrayExpression') {
60+
const { elements } = arg;
61+
if (elements.length === 0) {
62+
return;
63+
}
64+
65+
for (const element of elements) {
66+
if (element === null) {
67+
continue;
68+
}
69+
const elementType = services.getTypeAtLocation(element);
70+
if (isTypeAnyType(elementType) || isTypeUnknownType(elementType)) {
71+
continue;
72+
}
73+
74+
const originalNode = services.esTreeNodeToTSNodeMap.get(element);
75+
if (tsutils.isThenableType(checker, originalNode, elementType)) {
76+
continue;
77+
}
78+
79+
context.report({
80+
messageId: 'inArray',
81+
node: element,
82+
});
83+
}
84+
} else {
85+
const argType = services.getTypeAtLocation(arg);
86+
if (isTypeAnyType(argType) || isTypeUnknownType(argType)) {
87+
return;
88+
}
89+
90+
if (!checker.isArrayType(argType)) {
91+
context.report({
92+
messageId: 'nonArrayArg',
93+
node: arg,
94+
});
95+
return;
96+
}
97+
98+
if (argType.typeArguments === undefined) {
99+
return;
100+
}
101+
102+
if (argType.typeArguments.length < 1) {
103+
return;
104+
}
105+
106+
const typeArg = argType.typeArguments[0];
107+
if (isTypeAnyType(typeArg) || isTypeUnknownType(typeArg)) {
108+
return;
109+
}
110+
111+
const originalNode = services.esTreeNodeToTSNodeMap.get(arg);
112+
if (tsutils.isThenableType(checker, originalNode, typeArg)) {
113+
return;
114+
}
115+
116+
context.report({
117+
messageId: 'arrayArg',
118+
node: arg,
119+
});
120+
}
121+
},
122+
};
123+
},
124+
});

0 commit comments

Comments
 (0)
0