Skip to content

Commit 874030e

Browse files
committed
Check generator throw types
1 parent 61358c5 commit 874030e

File tree

4 files changed

+791
-2
lines changed

4 files changed

+791
-2
lines changed

src/rules/check-throws-tag-type.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ const {
99
getFirst,
1010
getLast,
1111
getNodeIndent,
12+
getCallee,
1213
isInHandledContext,
1314
isInAsyncHandledContext,
1415
isNodeReturned,
16+
isGeneratorLike,
1517
isPromiseType,
1618
isPromiseConstructorCallbackNode,
1719
isThenableCallbackNode,
@@ -248,6 +250,62 @@ module.exports = createRule({
248250
}
249251
};
250252

253+
/**
254+
* Visit iterable node and collect types.
255+
*
256+
* @param {import('@typescript-eslint/utils').TSESTree.Node} node
257+
*/
258+
const visitIterableNode = (node) => {
259+
const iterableType = services.getTypeAtLocation(node);
260+
if (!isGeneratorLike(iterableType)) return;
261+
262+
const callerDeclaration = findClosestFunctionNode(node);
263+
if (!callerDeclaration) return;
264+
265+
const calleeNode = getCallee(node);
266+
if (!calleeNode) return;
267+
268+
// TODO: Extract duplicated logic of extracting narrowed type declaration
269+
const calleeDeclaration =
270+
(calleeNode.type === AST_NODE_TYPES.CallExpression ||
271+
calleeNode.type === AST_NODE_TYPES.NewExpression)
272+
? getCallSignatureDeclaration(services, calleeNode)
273+
: calleeNode.parent?.type === AST_NODE_TYPES.CallExpression
274+
? getCallSignatureDeclaration(services, calleeNode.parent)
275+
: getCalleeDeclaration(
276+
services,
277+
/** @type {import('@typescript-eslint/utils').TSESTree.Expression} */
278+
(calleeNode)
279+
);
280+
281+
if (!calleeDeclaration) return;
282+
283+
const calleeThrowsTypes = getJSDocThrowsTagTypes(checker, calleeDeclaration);
284+
if (!calleeThrowsTypes.length) return;
285+
286+
for (const type of calleeThrowsTypes) {
287+
if (isPromiseType(services, type)) {
288+
if (isInAsyncHandledContext(sourceCode, node)) continue;
289+
290+
const flattened =
291+
toFlattenedTypeArray([checker.getAwaitedType(type) ?? type]);
292+
293+
rejectTypes.add(callerDeclaration, flattened);
294+
295+
flattened
296+
.forEach(t => metadata.set(t, { pos: node.range[0] }));
297+
} else {
298+
if (isInHandledContext(node)) continue;
299+
const flattened = toFlattenedTypeArray([type]);
300+
301+
throwTypes.add(callerDeclaration, flattened);
302+
303+
flattened
304+
.forEach(t => metadata.set(t, { pos: node.range[0] }));
305+
}
306+
}
307+
};
308+
251309
/** @param {import('@typescript-eslint/utils').TSESTree.FunctionLike} node */
252310
const visitFunctionOnExit = (node) => {
253311
if (visitedFunctionNodes.has(getNodeID(node))) return;
@@ -759,6 +817,99 @@ module.exports = createRule({
759817
'CallExpression[callee.property.name=/^(then|finally)$/] > MemberExpression:first-child:exit':
760818
visitPromiseCallbackOnExit,
761819

820+
/**
821+
* Collect throwable types of generators
822+
*/
823+
/**
824+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of MDN}
825+
* @example
826+
* ```
827+
* for (const item of iterable) { ... }
828+
* // ^ this
829+
* ```
830+
*/
831+
'ForOfStatement'(node) {
832+
visitIterableNode(node.right);
833+
},
834+
/**
835+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax MDN}
836+
* @example
837+
* ```
838+
* [...iterable]
839+
* // ^ this
840+
* ```
841+
*/
842+
'SpreadElement'(node) {
843+
visitIterableNode(node.argument);
844+
},
845+
/**
846+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from MDN}
847+
* @param {import('@typescript-eslint/utils').TSESTree.CallExpression} node
848+
* @example
849+
* ```
850+
* Array.from(iterable)
851+
* // ^ this
852+
* ```
853+
*/
854+
'CallExpression:has(> MemberExpression[object.name="Array"][property.name="from"])'(node) {
855+
if (node.arguments.length < 1) return;
856+
857+
const [firstArgumentNode] = node.arguments;
858+
859+
visitIterableNode(firstArgumentNode);
860+
},
861+
/**
862+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync MDN}
863+
* @param {import('@typescript-eslint/utils').TSESTree.CallExpression} node
864+
* @example
865+
* ```
866+
* await Array.fromAsync(iterable)
867+
* // ^ this
868+
* ```
869+
*/
870+
'CallExpression:has(> MemberExpression[object.name="Array"][property.name="fromAsync"])'(node) {
871+
if (node.arguments.length < 1) return;
872+
873+
const [firstArgumentNode] = node.arguments;
874+
875+
visitIterableNode(firstArgumentNode);
876+
},
877+
/**
878+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield* MDN}
879+
* @param {import('@typescript-eslint/utils').TSESTree.YieldExpression} node
880+
* @example
881+
* ```
882+
* function* gen() {
883+
* yield* iterable;
884+
* // ^ this
885+
* }
886+
* // or
887+
* async function* asyncGen() {
888+
* yield* iterable;
889+
* // ^ this
890+
* }
891+
* ```
892+
*/
893+
'YieldExpression[delegate=true]'(node) {
894+
if (!node.argument) return;
895+
896+
visitIterableNode(node.argument);
897+
},
898+
/**
899+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next MDN}
900+
* @param {import('@typescript-eslint/utils').TSESTree.CallExpression} node
901+
* @example
902+
* ```
903+
* iterable.next();
904+
* // ^ this
905+
* ```
906+
*/
907+
'CallExpression[callee.property.name="next"]'(node) {
908+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return;
909+
910+
visitIterableNode(node.callee.object);
911+
},
912+
762913
/**
763914
* Process collected types when each function node exits
764915
*/

src/utils.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,23 @@ const isCatchMethodCalled = (node) => {
940940
*/
941941
const isAwaitCatchPattern = (node) => {
942942
return (
943-
node.type === AST_NODE_TYPES.AwaitExpression &&
943+
(
944+
node.type === AST_NODE_TYPES.AwaitExpression ||
945+
// yield*
946+
findParent(node, (parent) =>
947+
parent.type === AST_NODE_TYPES.YieldExpression &&
948+
parent.delegate
949+
) ||
950+
// for await
951+
isParentOrAncestor(
952+
node,
953+
/** @type {import('@typescript-eslint/utils').TSESTree.ForOfStatement} */
954+
(findParent(node, (parent) =>
955+
parent.type === AST_NODE_TYPES.ForOfStatement &&
956+
parent.await
957+
))?.right
958+
)
959+
) &&
944960
isParentOrAncestor(
945961
node,
946962
/** @type {import('@typescript-eslint/utils').TSESTree.TryStatement} */

0 commit comments

Comments
 (0)