UNPKG

8.26 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 util = __importStar(require("../util"));
25exports.default = util.createRule({
26 name: 'require-await',
27 meta: {
28 type: 'suggestion',
29 docs: {
30 description: 'Disallow async functions which have no `await` expression',
31 recommended: 'error',
32 requiresTypeChecking: true,
33 extendsBaseRule: true,
34 },
35 schema: [],
36 messages: {
37 missingAwait: "{{name}} has no 'await' expression.",
38 },
39 },
40 defaultOptions: [],
41 create(context) {
42 const parserServices = util.getParserServices(context);
43 const checker = parserServices.program.getTypeChecker();
44 const sourceCode = context.getSourceCode();
45 let scopeInfo = null;
46 /**
47 * Push the scope info object to the stack.
48 */
49 function enterFunction(node) {
50 scopeInfo = {
51 upper: scopeInfo,
52 hasAwait: false,
53 hasAsync: node.async,
54 isGen: node.generator || false,
55 isAsyncYield: false,
56 };
57 }
58 /**
59 * Pop the top scope info object from the stack.
60 * Also, it reports the function if needed.
61 */
62 function exitFunction(node) {
63 /* istanbul ignore if */ if (!scopeInfo) {
64 // this shouldn't ever happen, as we have to exit a function after we enter it
65 return;
66 }
67 if (node.async &&
68 !scopeInfo.hasAwait &&
69 !isEmptyFunction(node) &&
70 !(scopeInfo.isGen && scopeInfo.isAsyncYield)) {
71 context.report({
72 node,
73 loc: getFunctionHeadLoc(node, sourceCode),
74 messageId: 'missingAwait',
75 data: {
76 name: util.upperCaseFirst(util.getFunctionNameWithKind(node)),
77 },
78 });
79 }
80 scopeInfo = scopeInfo.upper;
81 }
82 /**
83 * Checks if the node returns a thenable type
84 */
85 function isThenableType(node) {
86 const type = checker.getTypeAtLocation(node);
87 return tsutils.isThenableType(checker, node, type);
88 }
89 /**
90 * Marks the current scope as having an await
91 */
92 function markAsHasAwait() {
93 if (!scopeInfo) {
94 return;
95 }
96 scopeInfo.hasAwait = true;
97 }
98 /**
99 * mark `scopeInfo.isAsyncYield` to `true` if its a generator
100 * function and the delegate is `true`
101 */
102 function markAsHasDelegateGen(node) {
103 var _a;
104 if (!scopeInfo || !scopeInfo.isGen || !node.argument) {
105 return;
106 }
107 if (((_a = node === null || node === void 0 ? void 0 : node.argument) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.Literal) {
108 // making this `false` as for literals we don't need to check the definition
109 // eg : async function* run() { yield* 1 }
110 scopeInfo.isAsyncYield || (scopeInfo.isAsyncYield = false);
111 }
112 const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node === null || node === void 0 ? void 0 : node.argument);
113 const type = checker.getTypeAtLocation(tsNode);
114 const typesToCheck = expandUnionOrIntersectionType(type);
115 for (const type of typesToCheck) {
116 const asyncIterator = tsutils.getWellKnownSymbolPropertyOfType(type, 'asyncIterator', checker);
117 if (asyncIterator !== undefined) {
118 scopeInfo.isAsyncYield = true;
119 break;
120 }
121 }
122 }
123 return {
124 FunctionDeclaration: enterFunction,
125 FunctionExpression: enterFunction,
126 ArrowFunctionExpression: enterFunction,
127 'FunctionDeclaration:exit': exitFunction,
128 'FunctionExpression:exit': exitFunction,
129 'ArrowFunctionExpression:exit': exitFunction,
130 AwaitExpression: markAsHasAwait,
131 'ForOfStatement[await = true]': markAsHasAwait,
132 'YieldExpression[delegate = true]': markAsHasDelegateGen,
133 // check body-less async arrow function.
134 // ignore `async () => await foo` because it's obviously correct
135 'ArrowFunctionExpression[async = true] > :not(BlockStatement, AwaitExpression)'(node) {
136 const expression = parserServices.esTreeNodeToTSNodeMap.get(node);
137 if (expression && isThenableType(expression)) {
138 markAsHasAwait();
139 }
140 },
141 ReturnStatement(node) {
142 // short circuit early to avoid unnecessary type checks
143 if (!scopeInfo || scopeInfo.hasAwait || !scopeInfo.hasAsync) {
144 return;
145 }
146 const { expression } = parserServices.esTreeNodeToTSNodeMap.get(node);
147 if (expression && isThenableType(expression)) {
148 markAsHasAwait();
149 }
150 },
151 };
152 },
153});
154function isEmptyFunction(node) {
155 var _a;
156 return (((_a = node.body) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.BlockStatement &&
157 node.body.body.length === 0);
158}
159// https://github.com/eslint/eslint/blob/03a69dbe86d5b5768a310105416ae726822e3c1c/lib/rules/utils/ast-utils.js#L382-L392
160/**
161 * Gets the `(` token of the given function node.
162 */
163function getOpeningParenOfParams(node, sourceCode) {
164 return util.nullThrows(node.id
165 ? sourceCode.getTokenAfter(node.id, util.isOpeningParenToken)
166 : sourceCode.getFirstToken(node, util.isOpeningParenToken), util.NullThrowsReasons.MissingToken('(', node.type));
167}
168// https://github.com/eslint/eslint/blob/03a69dbe86d5b5768a310105416ae726822e3c1c/lib/rules/utils/ast-utils.js#L1220-L1242
169/**
170 * Gets the location of the given function node for reporting.
171 */
172function getFunctionHeadLoc(node, sourceCode) {
173 const parent = util.nullThrows(node.parent, util.NullThrowsReasons.MissingParent);
174 let start = null;
175 let end = null;
176 if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
177 const arrowToken = util.nullThrows(sourceCode.getTokenBefore(node.body, util.isArrowToken), util.NullThrowsReasons.MissingToken('=>', node.type));
178 start = arrowToken.loc.start;
179 end = arrowToken.loc.end;
180 }
181 else if (parent.type === utils_1.AST_NODE_TYPES.Property ||
182 parent.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
183 start = parent.loc.start;
184 end = getOpeningParenOfParams(node, sourceCode).loc.start;
185 }
186 else {
187 start = node.loc.start;
188 end = getOpeningParenOfParams(node, sourceCode).loc.start;
189 }
190 return {
191 start,
192 end,
193 };
194}
195function expandUnionOrIntersectionType(type) {
196 if (type.isUnionOrIntersection()) {
197 return type.types.flatMap(expandUnionOrIntersectionType);
198 }
199 return [type];
200}
201//# sourceMappingURL=require-await.js.map
\No newline at end of file