"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