Skip to content

Commit f776d63

Browse files
committed
fix(require-rejects): do not treat synchronous throw as rejection; fixes #1603
1 parent af2d940 commit f776d63

File tree

3 files changed

+41
-16
lines changed

3 files changed

+41
-16
lines changed

docs/rules/require-rejects.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,5 +352,10 @@ async function quux () {
352352
throw new Error('abc');
353353
}
354354
// Settings: {"jsdoc":{"tagNamePreference":{"rejects":false}}}
355+
356+
/** @param bar Something. */
357+
export function foo(bar: string): void {
358+
throw new Error(`some error: ${bar}`);
359+
}
355360
````
356361

src/rules/requireRejects.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import iterateJsdoc from '../iterateJsdoc.js';
44
* Checks if a node or its children contain Promise rejection patterns
55
* @param {import('eslint').Rule.Node} node
66
* @param {boolean} [innerFunction]
7+
* @param {boolean} [isAsync]
78
* @returns {boolean}
89
*/
910
// eslint-disable-next-line complexity -- Temporary
10-
const hasRejectValue = (node, innerFunction) => {
11+
const hasRejectValue = (node, innerFunction, isAsync) => {
1112
if (!node) {
1213
return false;
1314
}
@@ -19,16 +20,19 @@ const hasRejectValue = (node, innerFunction) => {
1920
// For inner functions in async contexts, check if they throw
2021
// (they could be called and cause rejection)
2122
if (innerFunction) {
22-
// Check the inner function's body for throw statements
23-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), false);
23+
// Check inner functions for throws - if called from async context, throws become rejections
24+
const innerIsAsync = node.async;
25+
// Pass isAsync=true if the inner function is async OR if we're already in an async context
26+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), false, innerIsAsync || isAsync);
2427
}
2528

26-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), true);
29+
// This is the top-level function we're checking
30+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), true, node.async);
2731
}
2832

2933
case 'BlockStatement': {
3034
return node.body.some((bodyNode) => {
31-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (bodyNode), innerFunction);
35+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (bodyNode), innerFunction, isAsync);
3236
});
3337
}
3438

@@ -65,15 +69,15 @@ const hasRejectValue = (node, innerFunction) => {
6569
case 'WhileStatement':
6670

6771
case 'WithStatement': {
68-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), innerFunction);
72+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), innerFunction, isAsync);
6973
}
7074

7175
case 'ExpressionStatement': {
72-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.expression), innerFunction);
76+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.expression), innerFunction, isAsync);
7377
}
7478

7579
case 'IfStatement': {
76-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.consequent), innerFunction) || hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.alternate), innerFunction);
80+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.consequent), innerFunction, isAsync) || hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.alternate), innerFunction, isAsync);
7781
}
7882

7983
case 'NewExpression': {
@@ -82,7 +86,7 @@ const hasRejectValue = (node, innerFunction) => {
8286
const executor = node.arguments[0];
8387
if (executor.type === 'ArrowFunctionExpression' || executor.type === 'FunctionExpression') {
8488
// Check if the executor has reject() calls
85-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (executor.body), false);
89+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (executor.body), false, false);
8690
}
8791
}
8892

@@ -91,7 +95,7 @@ const hasRejectValue = (node, innerFunction) => {
9195

9296
case 'ReturnStatement': {
9397
if (node.argument) {
94-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.argument), innerFunction);
98+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.argument), innerFunction, isAsync);
9599
}
96100

97101
return false;
@@ -101,20 +105,20 @@ const hasRejectValue = (node, innerFunction) => {
101105
return node.cases.some(
102106
(someCase) => {
103107
return someCase.consequent.some((nde) => {
104-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (nde), innerFunction);
108+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (nde), innerFunction, isAsync);
105109
});
106110
},
107111
);
108112
}
109113

110114
// Throw statements in async functions become rejections
111115
case 'ThrowStatement': {
112-
return true;
116+
return isAsync === true;
113117
}
114118

115119
case 'TryStatement': {
116-
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.handler && node.handler.body), innerFunction) ||
117-
hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.finalizer), innerFunction);
120+
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.handler && node.handler.body), innerFunction, isAsync) ||
121+
hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.finalizer), innerFunction, isAsync);
118122
}
119123

120124
default: {

test/rules/assertions/requireRejects.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
export default {
1+
import {
2+
parser as typescriptEslintParser,
3+
} from 'typescript-eslint';
4+
5+
export default /** @type {import('../index.js').TestCases} */ ({
26
invalid: [
37
{
48
code: `
@@ -485,5 +489,17 @@ export default {
485489
},
486490
},
487491
},
492+
{
493+
code: `
494+
/** @param bar Something. */
495+
export function foo(bar: string): void {
496+
throw new Error(\`some error: \${bar}\`);
497+
}
498+
`,
499+
languageOptions: {
500+
parser: typescriptEslintParser,
501+
sourceType: 'module',
502+
},
503+
},
488504
],
489-
};
505+
});

0 commit comments

Comments
 (0)