UNPKG

eslint-plugin-jsdoc

Version:
226 lines (216 loc) 7.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc.cjs")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /** * 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, isAsync) => { if (!node) { return false; } switch (node.type) { case 'ArrowFunctionExpression': case 'FunctionDeclaration': case 'FunctionExpression': { // For inner functions in async contexts, check if they throw // (they could be called and cause rejection) if (innerFunction) { // 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); } // 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, isAsync); }); } case 'CallExpression': { // Check for Promise.reject() if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'Promise' && node.callee.property.type === 'Identifier' && node.callee.property.name === 'reject') { return true; } // Check for reject() call (in Promise executor context) if (node.callee.type === 'Identifier' && node.callee.name === 'reject') { return true; } // Check if this is calling an inner function that might reject if (innerFunction && node.callee.type === 'Identifier') { // We found a function call inside - check if it could be calling a function that rejects // We'll handle this in function body traversal return false; } return false; } case 'DoWhileStatement': case 'ForInStatement': case 'ForOfStatement': case 'ForStatement': case 'LabeledStatement': case 'WhileStatement': case 'WithStatement': { return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.body, innerFunction, isAsync); } case 'ExpressionStatement': { return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.expression, innerFunction, isAsync); } case 'IfStatement': { return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.consequent, innerFunction, isAsync) || hasRejectValue(/** @type {import('eslint').Rule.Node} */node.alternate, innerFunction, isAsync); } case 'NewExpression': { // Check for new Promise((resolve, reject) => { reject(...) }) if (node.callee.type === 'Identifier' && node.callee.name === 'Promise' && node.arguments.length > 0) { 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, false); } } return false; } case 'ReturnStatement': { if (node.argument) { return hasRejectValue(/** @type {import('eslint').Rule.Node} */node.argument, innerFunction, isAsync); } return false; } case 'SwitchStatement': { return node.cases.some(someCase => { return someCase.consequent.some(nde => { return hasRejectValue(/** @type {import('eslint').Rule.Node} */nde, innerFunction, isAsync); }); }); } // Throw statements in async functions become rejections case 'ThrowStatement': { return isAsync === true; } case 'TryStatement': { 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: { return false; } } }; /** * We can skip checking for a rejects value, in case the documentation is inherited * or the method is abstract. * @param {import('../iterateJsdoc.js').Utils} utils * @returns {boolean} */ const canSkip = utils => { return utils.hasATag(['abstract', 'virtual', 'type']) || utils.avoidDocs(); }; var _default = exports.default = (0, _iterateJsdoc.default)(({ node, report, utils }) => { if (canSkip(utils)) { return; } const tagName = /** @type {string} */utils.getPreferredTagName({ tagName: 'rejects' }); if (!tagName) { return; } const tags = utils.getTags(tagName); const iteratingFunction = utils.isIteratingFunction(); const [tag] = tags; const missingRejectsTag = typeof tag === 'undefined' || tag === null; const shouldReport = () => { if (!missingRejectsTag) { return false; } // Check if this is an async function or returns a Promise const isAsync = utils.isAsync(); if (!isAsync && !iteratingFunction) { return false; } // For async functions, check for throw statements // For regular functions, check for Promise.reject or reject calls return hasRejectValue(/** @type {import('eslint').Rule.Node} */node); }; if (shouldReport()) { report('Promise-rejecting function requires `@rejects` tag'); } }, { contextDefaults: true, meta: { docs: { description: 'Requires that Promise rejections are documented with `@rejects` tags.', url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-rejects.md#repos-sticky-header' }, schema: [{ additionalProperties: false, properties: { contexts: { description: `Set this to an array of strings representing the AST context (or objects with optional \`context\` and \`comment\` properties) where you wish the rule to be applied. \`context\` defaults to \`any\` and \`comment\` defaults to no specific comment context. Overrides the default contexts (\`ArrowFunctionExpression\`, \`FunctionDeclaration\`, \`FunctionExpression\`).`, items: { anyOf: [{ type: 'string' }, { additionalProperties: false, properties: { comment: { type: 'string' }, context: { type: 'string' } }, type: 'object' }] }, type: 'array' }, exemptedBy: { description: `Array of tags (e.g., \`['type']\`) whose presence on the document block avoids the need for a \`@rejects\`. Defaults to an array with \`abstract\`, \`virtual\`, and \`type\`. If you set this array, it will overwrite the default, so be sure to add back those tags if you wish their presence to cause exemption of the rule.`, items: { type: 'string' }, type: 'array' } }, type: 'object' }], type: 'suggestion' } }); module.exports = exports.default; //# sourceMappingURL=requireRejects.cjs.map