UNPKG

9.57 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 tsutils = __importStar(require("tsutils"));
23const utils_1 = require("@typescript-eslint/utils");
24const util = __importStar(require("../util"));
25exports.default = util.createRule({
26 name: 'no-floating-promises',
27 meta: {
28 docs: {
29 description: 'Requires Promise-like values to be handled appropriately',
30 recommended: 'error',
31 suggestion: true,
32 requiresTypeChecking: true,
33 },
34 hasSuggestions: true,
35 messages: {
36 floating: 'Promises must be awaited, end with a call to .catch, or end with a call to .then with a rejection handler.',
37 floatingVoid: 'Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler' +
38 ' or be explicitly marked as ignored with the `void` operator.',
39 floatingFixVoid: 'Add void operator to ignore.',
40 },
41 schema: [
42 {
43 type: 'object',
44 properties: {
45 ignoreVoid: { type: 'boolean' },
46 ignoreIIFE: { type: 'boolean' },
47 },
48 additionalProperties: false,
49 },
50 ],
51 type: 'problem',
52 },
53 defaultOptions: [
54 {
55 ignoreVoid: true,
56 ignoreIIFE: false,
57 },
58 ],
59 create(context, [options]) {
60 const parserServices = util.getParserServices(context);
61 const checker = parserServices.program.getTypeChecker();
62 const sourceCode = context.getSourceCode();
63 return {
64 ExpressionStatement(node) {
65 if (options.ignoreIIFE && isAsyncIife(node)) {
66 return;
67 }
68 let expression = node.expression;
69 if (expression.type === utils_1.AST_NODE_TYPES.ChainExpression) {
70 expression = expression.expression;
71 }
72 if (isUnhandledPromise(checker, expression)) {
73 if (options.ignoreVoid) {
74 context.report({
75 node,
76 messageId: 'floatingVoid',
77 suggest: [
78 {
79 messageId: 'floatingFixVoid',
80 fix(fixer) {
81 let code = sourceCode.getText(node);
82 code = `void ${code}`;
83 return fixer.replaceText(node, code);
84 },
85 },
86 ],
87 });
88 }
89 else {
90 context.report({
91 node,
92 messageId: 'floating',
93 });
94 }
95 }
96 },
97 };
98 function isAsyncIife(node) {
99 if (node.expression.type !== utils_1.AST_NODE_TYPES.CallExpression) {
100 return false;
101 }
102 return (node.expression.type === utils_1.AST_NODE_TYPES.CallExpression &&
103 (node.expression.callee.type ===
104 utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
105 node.expression.callee.type === utils_1.AST_NODE_TYPES.FunctionExpression));
106 }
107 function isUnhandledPromise(checker, node) {
108 // First, check expressions whose resulting types may not be promise-like
109 if (node.type === utils_1.AST_NODE_TYPES.SequenceExpression) {
110 // Any child in a comma expression could return a potentially unhandled
111 // promise, so we check them all regardless of whether the final returned
112 // value is promise-like.
113 return node.expressions.some(item => isUnhandledPromise(checker, item));
114 }
115 if (!options.ignoreVoid &&
116 node.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
117 node.operator === 'void') {
118 // Similarly, a `void` expression always returns undefined, so we need to
119 // see what's inside it without checking the type of the overall expression.
120 return isUnhandledPromise(checker, node.argument);
121 }
122 // Check the type. At this point it can't be unhandled if it isn't a promise
123 if (!isPromiseLike(checker, parserServices.esTreeNodeToTSNodeMap.get(node))) {
124 return false;
125 }
126 if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
127 // If the outer expression is a call, it must be either a `.then()` or
128 // `.catch()` that handles the promise.
129 return (!isPromiseCatchCallWithHandler(node) &&
130 !isPromiseThenCallWithRejectionHandler(node) &&
131 !isPromiseFinallyCallWithHandler(node));
132 }
133 else if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
134 // We must be getting the promise-like value from one of the branches of the
135 // ternary. Check them directly.
136 return (isUnhandledPromise(checker, node.alternate) ||
137 isUnhandledPromise(checker, node.consequent));
138 }
139 else if (node.type === utils_1.AST_NODE_TYPES.MemberExpression ||
140 node.type === utils_1.AST_NODE_TYPES.Identifier ||
141 node.type === utils_1.AST_NODE_TYPES.NewExpression) {
142 // If it is just a property access chain or a `new` call (e.g. `foo.bar` or
143 // `new Promise()`), the promise is not handled because it doesn't have the
144 // necessary then/catch call at the end of the chain.
145 return true;
146 }
147 // We conservatively return false for all other types of expressions because
148 // we don't want to accidentally fail if the promise is handled internally but
149 // we just can't tell.
150 return false;
151 }
152 },
153});
154// Modified from tsutils.isThenable() to only consider thenables which can be
155// rejected/caught via a second parameter. Original source (MIT licensed):
156//
157// https://github.com/ajafff/tsutils/blob/49d0d31050b44b81e918eae4fbaf1dfe7b7286af/util/type.ts#L95-L125
158function isPromiseLike(checker, node) {
159 const type = checker.getTypeAtLocation(node);
160 for (const ty of tsutils.unionTypeParts(checker.getApparentType(type))) {
161 const then = ty.getProperty('then');
162 if (then === undefined) {
163 continue;
164 }
165 const thenType = checker.getTypeOfSymbolAtLocation(then, node);
166 if (hasMatchingSignature(thenType, signature => signature.parameters.length >= 2 &&
167 isFunctionParam(checker, signature.parameters[0], node) &&
168 isFunctionParam(checker, signature.parameters[1], node))) {
169 return true;
170 }
171 }
172 return false;
173}
174function hasMatchingSignature(type, matcher) {
175 for (const t of tsutils.unionTypeParts(type)) {
176 if (t.getCallSignatures().some(matcher)) {
177 return true;
178 }
179 }
180 return false;
181}
182function isFunctionParam(checker, param, node) {
183 const type = checker.getApparentType(checker.getTypeOfSymbolAtLocation(param, node));
184 for (const t of tsutils.unionTypeParts(type)) {
185 if (t.getCallSignatures().length !== 0) {
186 return true;
187 }
188 }
189 return false;
190}
191function isPromiseCatchCallWithHandler(expression) {
192 return (expression.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
193 expression.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
194 expression.callee.property.name === 'catch' &&
195 expression.arguments.length >= 1);
196}
197function isPromiseThenCallWithRejectionHandler(expression) {
198 return (expression.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
199 expression.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
200 expression.callee.property.name === 'then' &&
201 expression.arguments.length >= 2);
202}
203function isPromiseFinallyCallWithHandler(expression) {
204 return (expression.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
205 expression.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
206 expression.callee.property.name === 'finally' &&
207 expression.arguments.length >= 1);
208}
209//# sourceMappingURL=no-floating-promises.js.map
\No newline at end of file