Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/rules/require-rejects.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,5 +352,10 @@ async function quux () {
throw new Error('abc');
}
// Settings: {"jsdoc":{"tagNamePreference":{"rejects":false}}}

/** @param bar Something. */
export function foo(bar: string): void {
throw new Error(`some error: ${bar}`);
}
````

32 changes: 18 additions & 14 deletions src/rules/requireRejects.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import iterateJsdoc from '../iterateJsdoc.js';
* Checks if a node or its children contain Promise rejection patterns
* @param {import('eslint').Rule.Node} node
* @param {boolean} [innerFunction]
* @param {boolean} [isAsync]
* @returns {boolean}
*/
// eslint-disable-next-line complexity -- Temporary
const hasRejectValue = (node, innerFunction) => {
const hasRejectValue = (node, innerFunction, isAsync) => {
if (!node) {
return false;
}
Expand All @@ -19,16 +20,19 @@ const hasRejectValue = (node, innerFunction) => {
// For inner functions in async contexts, check if they throw
// (they could be called and cause rejection)
if (innerFunction) {
// Check the inner function's body for throw statements
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), false);
// Check inner functions for throws - if called from async context, throws become rejections
const innerIsAsync = node.async;
// Pass isAsync=true if the inner function is async OR if we're already in an async context
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), false, innerIsAsync || isAsync);
}

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

case 'BlockStatement': {
return node.body.some((bodyNode) => {
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (bodyNode), innerFunction);
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (bodyNode), innerFunction, isAsync);
});
}

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

case 'WithStatement': {
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), innerFunction);
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.body), innerFunction, isAsync);
}

case 'ExpressionStatement': {
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.expression), innerFunction);
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.expression), innerFunction, isAsync);
}

case 'IfStatement': {
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.consequent), innerFunction) || hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.alternate), innerFunction);
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.consequent), innerFunction, isAsync) || hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.alternate), innerFunction, isAsync);
}

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

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

case 'ReturnStatement': {
if (node.argument) {
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.argument), innerFunction);
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.argument), innerFunction, isAsync);
}

return false;
Expand All @@ -101,20 +105,20 @@ const hasRejectValue = (node, innerFunction) => {
return node.cases.some(
(someCase) => {
return someCase.consequent.some((nde) => {
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (nde), innerFunction);
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (nde), innerFunction, isAsync);
});
},
);
}

// Throw statements in async functions become rejections
case 'ThrowStatement': {
return true;
return isAsync === true;
}

case 'TryStatement': {
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.handler && node.handler.body), innerFunction) ||
hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.finalizer), innerFunction);
return hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.handler && node.handler.body), innerFunction, isAsync) ||
hasRejectValue(/** @type {import('eslint').Rule.Node} */ (node.finalizer), innerFunction, isAsync);
}

default: {
Expand Down
20 changes: 18 additions & 2 deletions test/rules/assertions/requireRejects.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export default {
import {
parser as typescriptEslintParser,
} from 'typescript-eslint';

export default /** @type {import('../index.js').TestCases} */ ({
invalid: [
{
code: `
Expand Down Expand Up @@ -485,5 +489,17 @@ export default {
},
},
},
{
code: `
/** @param bar Something. */
export function foo(bar: string): void {
throw new Error(\`some error: \${bar}\`);
}
`,
languageOptions: {
parser: typescriptEslintParser,
sourceType: 'module',
},
},
],
};
});
Loading