UNPKG

10 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22const utils_1 = require("@typescript-eslint/utils");
23const tsutils = __importStar(require("tsutils"));
24const ts = __importStar(require("typescript"));
25const util = __importStar(require("../util"));
26exports.default = util.createRule({
27 name: 'no-misused-promises',
28 meta: {
29 docs: {
30 description: 'Avoid using promises in places not designed to handle them',
31 recommended: 'error',
32 requiresTypeChecking: true,
33 },
34 messages: {
35 voidReturn: 'Promise returned in function argument where a void return was expected.',
36 conditional: 'Expected non-Promise value in a boolean conditional.',
37 },
38 schema: [
39 {
40 type: 'object',
41 properties: {
42 checksConditionals: {
43 type: 'boolean',
44 },
45 checksVoidReturn: {
46 type: 'boolean',
47 },
48 },
49 },
50 ],
51 type: 'problem',
52 },
53 defaultOptions: [
54 {
55 checksConditionals: true,
56 checksVoidReturn: true,
57 },
58 ],
59 create(context, [{ checksConditionals, checksVoidReturn }]) {
60 const parserServices = util.getParserServices(context);
61 const checker = parserServices.program.getTypeChecker();
62 const checkedNodes = new Set();
63 const conditionalChecks = {
64 ConditionalExpression: checkTestConditional,
65 DoWhileStatement: checkTestConditional,
66 ForStatement: checkTestConditional,
67 IfStatement: checkTestConditional,
68 LogicalExpression: checkConditional,
69 'UnaryExpression[operator="!"]'(node) {
70 checkConditional(node.argument, true);
71 },
72 WhileStatement: checkTestConditional,
73 };
74 const voidReturnChecks = {
75 CallExpression: checkArguments,
76 NewExpression: checkArguments,
77 };
78 function checkTestConditional(node) {
79 if (node.test) {
80 checkConditional(node.test, true);
81 }
82 }
83 /**
84 * This function analyzes the type of a node and checks if it is a Promise in a boolean conditional.
85 * It uses recursion when checking nested logical operators.
86 * @param node The AST node to check.
87 * @param isTestExpr Whether the node is a descendant of a test expression.
88 */
89 function checkConditional(node, isTestExpr = false) {
90 // prevent checking the same node multiple times
91 if (checkedNodes.has(node)) {
92 return;
93 }
94 checkedNodes.add(node);
95 if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
96 // ignore the left operand for nullish coalescing expressions not in a context of a test expression
97 if (node.operator !== '??' || isTestExpr) {
98 checkConditional(node.left, isTestExpr);
99 }
100 // we ignore the right operand when not in a context of a test expression
101 if (isTestExpr) {
102 checkConditional(node.right, isTestExpr);
103 }
104 return;
105 }
106 const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
107 if (isAlwaysThenable(checker, tsNode)) {
108 context.report({
109 messageId: 'conditional',
110 node,
111 });
112 }
113 }
114 function checkArguments(node) {
115 const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
116 const voidParams = voidFunctionParams(checker, tsNode);
117 if (voidParams.size === 0) {
118 return;
119 }
120 for (const [index, argument] of node.arguments.entries()) {
121 if (!voidParams.has(index)) {
122 continue;
123 }
124 const tsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);
125 if (returnsThenable(checker, tsNode)) {
126 context.report({
127 messageId: 'voidReturn',
128 node: argument,
129 });
130 }
131 }
132 }
133 return Object.assign(Object.assign({}, (checksConditionals ? conditionalChecks : {})), (checksVoidReturn ? voidReturnChecks : {}));
134 },
135});
136// Variation on the thenable check which requires all forms of the type (read:
137// alternates in a union) to be thenable. Otherwise, you might be trying to
138// check if something is defined or undefined and get caught because one of the
139// branches is thenable.
140function isAlwaysThenable(checker, node) {
141 const type = checker.getTypeAtLocation(node);
142 for (const subType of tsutils.unionTypeParts(checker.getApparentType(type))) {
143 const thenProp = subType.getProperty('then');
144 // If one of the alternates has no then property, it is not thenable in all
145 // cases.
146 if (thenProp === undefined) {
147 return false;
148 }
149 // We walk through each variation of the then property. Since we know it
150 // exists at this point, we just need at least one of the alternates to
151 // be of the right form to consider it thenable.
152 const thenType = checker.getTypeOfSymbolAtLocation(thenProp, node);
153 let hasThenableSignature = false;
154 for (const subType of tsutils.unionTypeParts(thenType)) {
155 for (const signature of subType.getCallSignatures()) {
156 if (signature.parameters.length !== 0 &&
157 isFunctionParam(checker, signature.parameters[0], node)) {
158 hasThenableSignature = true;
159 break;
160 }
161 }
162 // We only need to find one variant of the then property that has a
163 // function signature for it to be thenable.
164 if (hasThenableSignature) {
165 break;
166 }
167 }
168 // If no flavors of the then property are thenable, we don't consider the
169 // overall type to be thenable
170 if (!hasThenableSignature) {
171 return false;
172 }
173 }
174 // If all variants are considered thenable (i.e. haven't returned false), we
175 // consider the overall type thenable
176 return true;
177}
178function isFunctionParam(checker, param, node) {
179 const type = checker.getApparentType(checker.getTypeOfSymbolAtLocation(param, node));
180 for (const subType of tsutils.unionTypeParts(type)) {
181 if (subType.getCallSignatures().length !== 0) {
182 return true;
183 }
184 }
185 return false;
186}
187// Get the positions of parameters which are void functions (and not also
188// thenable functions). These are the candidates for the void-return check at
189// the current call site.
190function voidFunctionParams(checker, node) {
191 const voidReturnIndices = new Set();
192 const thenableReturnIndices = new Set();
193 const type = checker.getTypeAtLocation(node.expression);
194 for (const subType of tsutils.unionTypeParts(type)) {
195 // Standard function calls and `new` have two different types of signatures
196 const signatures = ts.isCallExpression(node)
197 ? subType.getCallSignatures()
198 : subType.getConstructSignatures();
199 for (const signature of signatures) {
200 for (const [index, parameter] of signature.parameters.entries()) {
201 const type = checker.getTypeOfSymbolAtLocation(parameter, node.expression);
202 for (const subType of tsutils.unionTypeParts(type)) {
203 for (const signature of subType.getCallSignatures()) {
204 const returnType = signature.getReturnType();
205 if (tsutils.isTypeFlagSet(returnType, ts.TypeFlags.Void)) {
206 voidReturnIndices.add(index);
207 }
208 else if (tsutils.isThenableType(checker, node.expression, returnType)) {
209 thenableReturnIndices.add(index);
210 }
211 }
212 }
213 }
214 }
215 }
216 // If a certain positional argument accepts both thenable and void returns,
217 // a promise-returning function is valid
218 for (const thenable of thenableReturnIndices) {
219 voidReturnIndices.delete(thenable);
220 }
221 return voidReturnIndices;
222}
223// Returns true if the expression is a function that returns a thenable
224function returnsThenable(checker, node) {
225 const type = checker.getApparentType(checker.getTypeAtLocation(node));
226 for (const subType of tsutils.unionTypeParts(type)) {
227 for (const signature of subType.getCallSignatures()) {
228 const returnType = signature.getReturnType();
229 if (tsutils.isThenableType(checker, node, returnType)) {
230 return true;
231 }
232 }
233 }
234 return false;
235}
236//# sourceMappingURL=no-misused-promises.js.map
\No newline at end of file