UNPKG

9.89 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: 'return-await',
28 meta: {
29 docs: {
30 description: 'Enforces consistent returning of awaited values',
31 recommended: false,
32 requiresTypeChecking: true,
33 extendsBaseRule: 'no-return-await',
34 },
35 fixable: 'code',
36 hasSuggestions: true,
37 type: 'problem',
38 messages: {
39 nonPromiseAwait: 'Returning an awaited value that is not a promise is not allowed.',
40 disallowedPromiseAwait: 'Returning an awaited promise is not allowed in this context.',
41 requiredPromiseAwait: 'Returning an awaited promise is required in this context.',
42 },
43 schema: [
44 {
45 enum: ['in-try-catch', 'always', 'never'],
46 },
47 ],
48 },
49 defaultOptions: ['in-try-catch'],
50 create(context, [option]) {
51 const parserServices = util.getParserServices(context);
52 const checker = parserServices.program.getTypeChecker();
53 const sourceCode = context.getSourceCode();
54 const scopeInfoStack = [];
55 function enterFunction(node) {
56 scopeInfoStack.push({
57 hasAsync: node.async,
58 owningFunc: node,
59 });
60 }
61 function exitFunction() {
62 scopeInfoStack.pop();
63 }
64 function inTry(node) {
65 let ancestor = node.parent;
66 while (ancestor && !ts.isFunctionLike(ancestor)) {
67 if (ts.isTryStatement(ancestor)) {
68 return true;
69 }
70 ancestor = ancestor.parent;
71 }
72 return false;
73 }
74 function inCatch(node) {
75 let ancestor = node.parent;
76 while (ancestor && !ts.isFunctionLike(ancestor)) {
77 if (ts.isCatchClause(ancestor)) {
78 return true;
79 }
80 ancestor = ancestor.parent;
81 }
82 return false;
83 }
84 function isReturnPromiseInFinally(node) {
85 let ancestor = node.parent;
86 while (ancestor && !ts.isFunctionLike(ancestor)) {
87 if (ts.isTryStatement(ancestor.parent) &&
88 ts.isBlock(ancestor) &&
89 ancestor.parent.end === ancestor.end) {
90 return true;
91 }
92 ancestor = ancestor.parent;
93 }
94 return false;
95 }
96 function hasFinallyBlock(node) {
97 let ancestor = node.parent;
98 while (ancestor && !ts.isFunctionLike(ancestor)) {
99 if (ts.isTryStatement(ancestor)) {
100 return !!ancestor.finallyBlock;
101 }
102 ancestor = ancestor.parent;
103 }
104 return false;
105 }
106 // function findTokensToRemove()
107 function removeAwait(fixer, node) {
108 // Should always be an await node; but let's be safe.
109 /* istanbul ignore if */ if (!util.isAwaitExpression(node)) {
110 return null;
111 }
112 const awaitToken = sourceCode.getFirstToken(node, util.isAwaitKeyword);
113 // Should always be the case; but let's be safe.
114 /* istanbul ignore if */ if (!awaitToken) {
115 return null;
116 }
117 const startAt = awaitToken.range[0];
118 let endAt = awaitToken.range[1];
119 // Also remove any extraneous whitespace after `await`, if there is any.
120 const nextToken = sourceCode.getTokenAfter(awaitToken, {
121 includeComments: true,
122 });
123 if (nextToken) {
124 endAt = nextToken.range[0];
125 }
126 return fixer.removeRange([startAt, endAt]);
127 }
128 function insertAwait(fixer, node) {
129 if (node.type !== utils_1.AST_NODE_TYPES.TSAsExpression) {
130 return fixer.insertTextBefore(node, 'await ');
131 }
132 return [
133 fixer.insertTextBefore(node, 'await ('),
134 fixer.insertTextAfter(node, ')'),
135 ];
136 }
137 function test(node, expression) {
138 let child;
139 const isAwait = ts.isAwaitExpression(expression);
140 if (isAwait) {
141 child = expression.getChildAt(1);
142 }
143 else {
144 child = expression;
145 }
146 const type = checker.getTypeAtLocation(child);
147 const isThenable = tsutils.isThenableType(checker, expression, type);
148 if (!isAwait && !isThenable) {
149 return;
150 }
151 if (isAwait && !isThenable) {
152 // any/unknown could be thenable; do not auto-fix
153 const useAutoFix = !(util.isTypeAnyType(type) || util.isTypeUnknownType(type));
154 const fix = (fixer) => removeAwait(fixer, node);
155 context.report(Object.assign({ messageId: 'nonPromiseAwait', node }, (useAutoFix
156 ? { fix }
157 : {
158 suggest: [
159 {
160 messageId: 'nonPromiseAwait',
161 fix,
162 },
163 ],
164 })));
165 return;
166 }
167 if (option === 'always') {
168 if (!isAwait && isThenable) {
169 context.report({
170 messageId: 'requiredPromiseAwait',
171 node,
172 fix: fixer => insertAwait(fixer, node),
173 });
174 }
175 return;
176 }
177 if (option === 'never') {
178 if (isAwait) {
179 context.report({
180 messageId: 'disallowedPromiseAwait',
181 node,
182 fix: fixer => removeAwait(fixer, node),
183 });
184 }
185 return;
186 }
187 if (option === 'in-try-catch') {
188 const isInTryCatch = inTry(expression) || inCatch(expression);
189 if (isAwait && !isInTryCatch) {
190 context.report({
191 messageId: 'disallowedPromiseAwait',
192 node,
193 fix: fixer => removeAwait(fixer, node),
194 });
195 }
196 else if (!isAwait && isInTryCatch) {
197 if (inCatch(expression) && !hasFinallyBlock(expression)) {
198 return;
199 }
200 if (isReturnPromiseInFinally(expression)) {
201 return;
202 }
203 context.report({
204 messageId: 'requiredPromiseAwait',
205 node,
206 fix: fixer => insertAwait(fixer, node),
207 });
208 }
209 return;
210 }
211 }
212 function findPossiblyReturnedNodes(node) {
213 if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
214 return [
215 ...findPossiblyReturnedNodes(node.alternate),
216 ...findPossiblyReturnedNodes(node.consequent),
217 ];
218 }
219 return [node];
220 }
221 return {
222 FunctionDeclaration: enterFunction,
223 FunctionExpression: enterFunction,
224 ArrowFunctionExpression: enterFunction,
225 'FunctionDeclaration:exit': exitFunction,
226 'FunctionExpression:exit': exitFunction,
227 'ArrowFunctionExpression:exit': exitFunction,
228 // executes after less specific handler, so exitFunction is called
229 'ArrowFunctionExpression[async = true]:exit'(node) {
230 if (node.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
231 findPossiblyReturnedNodes(node.body).forEach(node => {
232 const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
233 test(node, tsNode);
234 });
235 }
236 },
237 ReturnStatement(node) {
238 const scopeInfo = scopeInfoStack[scopeInfoStack.length - 1];
239 if (!scopeInfo || !scopeInfo.hasAsync || !node.argument) {
240 return;
241 }
242 findPossiblyReturnedNodes(node.argument).forEach(node => {
243 const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
244 test(node, tsNode);
245 });
246 },
247 };
248 },
249});
250//# sourceMappingURL=return-await.js.map
\No newline at end of file