"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _exportParser = _interopRequireDefault(require("../exportParser.cjs"));
var _iterateJsdoc = require("../iterateJsdoc.cjs");
var _jsdocUtils = require("../jsdocUtils.cjs");
var _jsdoccomment = require("@es-joy/jsdoccomment");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
 * @typedef {{
 *   ancestorsOnly: boolean,
 *   esm: boolean,
 *   initModuleExports: boolean,
 *   initWindow: boolean
 * }} RequireJsdocOpts
 */

/**
 * @typedef {import('eslint').Rule.Node|
 *   import('@typescript-eslint/types').TSESTree.Node} ESLintOrTSNode
 */

/** @type {import('json-schema').JSONSchema4} */
const OPTIONS_SCHEMA = {
  additionalProperties: false,
  properties: {
    checkConstructors: {
      default: true,
      type: 'boolean'
    },
    checkGetters: {
      anyOf: [{
        type: 'boolean'
      }, {
        enum: ['no-setter'],
        type: 'string'
      }],
      default: true
    },
    checkSetters: {
      anyOf: [{
        type: 'boolean'
      }, {
        enum: ['no-getter'],
        type: 'string'
      }],
      default: true
    },
    contexts: {
      items: {
        anyOf: [{
          type: 'string'
        }, {
          additionalProperties: false,
          properties: {
            context: {
              type: 'string'
            },
            inlineCommentBlock: {
              type: 'boolean'
            },
            minLineCount: {
              type: 'integer'
            }
          },
          type: 'object'
        }]
      },
      type: 'array'
    },
    enableFixer: {
      default: true,
      type: 'boolean'
    },
    exemptEmptyConstructors: {
      default: false,
      type: 'boolean'
    },
    exemptEmptyFunctions: {
      default: false,
      type: 'boolean'
    },
    fixerMessage: {
      default: '',
      type: 'string'
    },
    minLineCount: {
      type: 'integer'
    },
    publicOnly: {
      oneOf: [{
        default: false,
        type: 'boolean'
      }, {
        additionalProperties: false,
        default: {},
        properties: {
          ancestorsOnly: {
            type: 'boolean'
          },
          cjs: {
            type: 'boolean'
          },
          esm: {
            type: 'boolean'
          },
          window: {
            type: 'boolean'
          }
        },
        type: 'object'
      }]
    },
    require: {
      additionalProperties: false,
      default: {},
      properties: {
        ArrowFunctionExpression: {
          default: false,
          type: 'boolean'
        },
        ClassDeclaration: {
          default: false,
          type: 'boolean'
        },
        ClassExpression: {
          default: false,
          type: 'boolean'
        },
        FunctionDeclaration: {
          default: true,
          type: 'boolean'
        },
        FunctionExpression: {
          default: false,
          type: 'boolean'
        },
        MethodDefinition: {
          default: false,
          type: 'boolean'
        }
      },
      type: 'object'
    }
  },
  type: 'object'
};

/**
 * @param {string} interfaceName
 * @param {string} methodName
 * @param {import("eslint").Scope.Scope | null} scope
 * @returns {import('@typescript-eslint/types').TSESTree.TSMethodSignature|null}
 */
const getMethodOnInterface = (interfaceName, methodName, scope) => {
  let scp = scope;
  while (scp) {
    for (const {
      identifiers,
      name
    } of scp.variables) {
      if (interfaceName !== name) {
        continue;
      }
      for (const identifier of identifiers) {
        const interfaceDeclaration = /** @type {import('@typescript-eslint/types').TSESTree.Identifier & {parent: import('@typescript-eslint/types').TSESTree.TSInterfaceDeclaration}} */identifier.parent;
        /* c8 ignore next 3 -- TS */
        if (interfaceDeclaration.type !== 'TSInterfaceDeclaration') {
          continue;
        }
        for (const bodyItem of interfaceDeclaration.body.body) {
          const methodSig = /** @type {import('@typescript-eslint/types').TSESTree.TSMethodSignature} */
          bodyItem;
          if (methodName === /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */methodSig.key.name) {
            return methodSig;
          }
        }
      }
    }
    scp = scp.upper;
  }
  return null;
};

/**
 * @param {import('eslint').Rule.Node} node
 * @param {import('eslint').SourceCode} sourceCode
 * @param {import('eslint').Rule.RuleContext} context
 * @param {import('../iterateJsdoc.js').Settings} settings
 */
const isExemptedImplementer = (node, sourceCode, context, settings) => {
  if (node.type === 'FunctionExpression' && node.parent.type === 'MethodDefinition' && node.parent.parent.type === 'ClassBody' && node.parent.parent.parent.type === 'ClassDeclaration' && 'implements' in node.parent.parent.parent) {
    const implments = /** @type {import('@typescript-eslint/types').TSESTree.TSClassImplements[]} */
    node.parent.parent.parent.implements;
    const {
      name: methodName
    } = /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */
    node.parent.key;
    for (const impl of implments) {
      const {
        name: interfaceName
      } = /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */
      impl.expression;
      const interfaceMethodNode = getMethodOnInterface(interfaceName, methodName, node && (sourceCode.getScope && /* c8 ignore next 2 */
      sourceCode.getScope(node) || context.getScope()));
      if (interfaceMethodNode) {
        // @ts-expect-error Ok
        const comment = (0, _jsdoccomment.getJSDocComment)(sourceCode, interfaceMethodNode, settings);
        if (comment) {
          return true;
        }
      }
    }
  }
  return false;
};

/**
 * @param {import('eslint').Rule.RuleContext} context
 * @param {import('json-schema').JSONSchema4Object} baseObject
 * @param {string} option
 * @param {string} key
 * @returns {boolean|undefined}
 */
const getOption = (context, baseObject, option, key) => {
  if (context.options[0] && option in context.options[0] && (
  // Todo: boolean shouldn't be returning property, but
  //   tests currently require
  typeof context.options[0][option] === 'boolean' || key in context.options[0][option])) {
    return context.options[0][option][key];
  }
  return /** @type {{[key: string]: {default?: boolean|undefined}}} */baseObject.properties[key].default;
};

/**
 * @param {import('eslint').Rule.RuleContext} context
 * @param {import('../iterateJsdoc.js').Settings} settings
 * @returns {{
 *   contexts: (string|{
 *     context: string,
 *     inlineCommentBlock: boolean,
 *     minLineCount: import('../iterateJsdoc.js').Integer
 *   })[],
 *   enableFixer: boolean,
 *   exemptEmptyConstructors: boolean,
 *   exemptEmptyFunctions: boolean,
 *   fixerMessage: string,
 *   minLineCount: undefined|import('../iterateJsdoc.js').Integer,
 *   publicOnly: boolean|{[key: string]: boolean|undefined}
 *   require: {[key: string]: boolean|undefined}
 * }}
 */
const getOptions = (context, settings) => {
  const {
    contexts = settings.contexts || [],
    enableFixer = true,
    exemptEmptyConstructors = true,
    exemptEmptyFunctions = false,
    fixerMessage = '',
    minLineCount = undefined,
    publicOnly
  } = context.options[0] || {};
  return {
    contexts,
    enableFixer,
    exemptEmptyConstructors,
    exemptEmptyFunctions,
    fixerMessage,
    minLineCount,
    publicOnly: (baseObj => {
      if (!publicOnly) {
        return false;
      }

      /** @type {{[key: string]: boolean|undefined}} */
      const properties = {};
      for (const prop of Object.keys(/** @type {import('json-schema').JSONSchema4Object} */
      /** @type {import('json-schema').JSONSchema4Object} */baseObj.properties)) {
        const opt = getOption(context, /** @type {import('json-schema').JSONSchema4Object} */baseObj, 'publicOnly', prop);
        properties[prop] = opt;
      }
      return properties;
    })(/** @type {import('json-schema').JSONSchema4Object} */
    (/** @type {import('json-schema').JSONSchema4Object} */
    (/** @type {import('json-schema').JSONSchema4Object} */
    OPTIONS_SCHEMA.properties.publicOnly).oneOf)[1]),
    require: (baseObj => {
      /** @type {{[key: string]: boolean|undefined}} */
      const properties = {};
      for (const prop of Object.keys(/** @type {import('json-schema').JSONSchema4Object} */
      /** @type {import('json-schema').JSONSchema4Object} */baseObj.properties)) {
        const opt = getOption(context, /** @type {import('json-schema').JSONSchema4Object} */
        baseObj, 'require', prop);
        properties[prop] = opt;
      }
      return properties;
    })(/** @type {import('json-schema').JSONSchema4Object} */
    OPTIONS_SCHEMA.properties.require)
  };
};

/** @type {import('eslint').Rule.RuleModule} */
var _default = exports.default = {
  create(context) {
    /* c8 ignore next -- Fallback to deprecated method */
    const {
      sourceCode = context.getSourceCode()
    } = context;
    const settings = (0, _iterateJsdoc.getSettings)(context);
    if (!settings) {
      return {};
    }
    const opts = getOptions(context, settings);
    const {
      contexts,
      enableFixer,
      exemptEmptyConstructors,
      exemptEmptyFunctions,
      fixerMessage,
      minLineCount,
      require: requireOption
    } = opts;
    const publicOnly =
    /**
     * @type {{
     *   [key: string]: boolean | undefined;
     * }}
     */
    opts.publicOnly;

    /**
     * @type {import('../iterateJsdoc.js').CheckJsdoc}
     */
    const checkJsDoc = (info, _handler, node) => {
      if (
      // Optimize
      minLineCount !== undefined || contexts.some(ctxt => {
        if (typeof ctxt === 'string') {
          return false;
        }
        const {
          minLineCount: count
        } = ctxt;
        return count !== undefined;
      })) {
        /**
         * @param {undefined|import('../iterateJsdoc.js').Integer} count
         */
        const underMinLine = count => {
          return count !== undefined && count > (sourceCode.getText(node).match(/\n/gv)?.length ?? 0) + 1;
        };
        if (underMinLine(minLineCount)) {
          return;
        }
        const {
          minLineCount: contextMinLineCount
        } =
        /**
         * @type {{
         *   context: string;
         *   inlineCommentBlock: boolean;
         *   minLineCount: number;
         * }}
         */
        contexts.find(ctxt => {
          if (typeof ctxt === 'string') {
            return false;
          }
          const {
            context: ctx
          } = ctxt;
          return ctx === (info.selector || node.type);
        }) || {};
        if (underMinLine(contextMinLineCount)) {
          return;
        }
      }
      const jsDocNode = (0, _jsdoccomment.getJSDocComment)(sourceCode, node, settings);
      if (jsDocNode) {
        return;
      }

      // For those who have options configured against ANY constructors (or
      //  setters or getters) being reported
      if ((0, _jsdocUtils.exemptSpeciaMethods)({
        description: '',
        inlineTags: [],
        problems: [],
        source: [],
        tags: []
      }, node, context, [OPTIONS_SCHEMA])) {
        return;
      }
      if (
      // Avoid reporting param-less, return-less functions (when
      //  `exemptEmptyFunctions` option is set)
      exemptEmptyFunctions && info.isFunctionContext ||
      // Avoid reporting  param-less, return-less constructor methods (when
      //  `exemptEmptyConstructors` option is set)
      exemptEmptyConstructors && (0, _jsdocUtils.isConstructor)(node)) {
        const functionParameterNames = (0, _jsdocUtils.getFunctionParameterNames)(node);
        if (!functionParameterNames.length && !(0, _jsdocUtils.hasReturnValue)(node)) {
          return;
        }
      }
      if (isExemptedImplementer(node, sourceCode, context, settings)) {
        return;
      }
      const fix = /** @type {import('eslint').Rule.ReportFixer} */fixer => {
        // Default to one line break if the `minLines`/`maxLines` settings allow
        const lines = settings.minLines === 0 && settings.maxLines >= 1 ? 1 : settings.minLines;
        /** @type {ESLintOrTSNode|import('@typescript-eslint/types').TSESTree.Decorator} */
        let baseNode = (0, _jsdoccomment.getReducedASTNode)(node, sourceCode);
        const decorator = (0, _jsdoccomment.getDecorator)(/** @type {import('eslint').Rule.Node} */
        baseNode);
        if (decorator) {
          baseNode = decorator;
        }
        const indent = (0, _jsdocUtils.getIndent)({
          text: sourceCode.getText(/** @type {import('eslint').Rule.Node} */baseNode, /** @type {import('eslint').AST.SourceLocation} */
          (/** @type {import('eslint').Rule.Node} */baseNode.loc).start.column)
        });
        const {
          inlineCommentBlock
        } =
        /**
         * @type {{
         *     context: string,
         *     inlineCommentBlock: boolean,
         *     minLineCount: import('../iterateJsdoc.js').Integer
         *   }}
         */
        contexts.find(contxt => {
          if (typeof contxt === 'string') {
            return false;
          }
          const {
            context: ctxt
          } = contxt;
          return ctxt === node.type;
        }) || {};
        const insertion = (inlineCommentBlock ? `/** ${fixerMessage}` : `/**\n${indent}*${fixerMessage}\n${indent}`) + `*/${'\n'.repeat(lines)}${indent.slice(0, -1)}`;
        return fixer.insertTextBefore(/** @type {import('eslint').Rule.Node} */
        baseNode, insertion);
      };
      const report = () => {
        const {
          start
        } = /** @type {import('eslint').AST.SourceLocation} */node.loc;
        const loc = {
          end: {
            column: 0,
            line: start.line + 1
          },
          start
        };
        context.report({
          fix: enableFixer ? fix : null,
          loc,
          messageId: 'missingJsDoc',
          node
        });
      };
      if (publicOnly) {
        /** @type {RequireJsdocOpts} */
        const opt = {
          ancestorsOnly: Boolean(publicOnly?.ancestorsOnly ?? false),
          esm: Boolean(publicOnly?.esm ?? true),
          initModuleExports: Boolean(publicOnly?.cjs ?? true),
          initWindow: Boolean(publicOnly?.window ?? false)
        };
        const exported = _exportParser.default.isUncommentedExport(node, sourceCode, opt, settings);
        if (exported) {
          report();
        }
      } else {
        report();
      }
    };

    /**
     * @param {string} prop
     * @returns {boolean}
     */
    const hasOption = prop => {
      return requireOption[prop] || contexts.some(ctxt => {
        return typeof ctxt === 'object' ? ctxt.context === prop : ctxt === prop;
      });
    };
    return {
      ...(0, _jsdocUtils.getContextObject)((0, _jsdocUtils.enforcedContexts)(context, [], settings), checkJsDoc),
      ArrowFunctionExpression(node) {
        if (!hasOption('ArrowFunctionExpression')) {
          return;
        }
        if (['AssignmentExpression', 'ExportDefaultDeclaration', 'VariableDeclarator'].includes(node.parent.type) || ['ClassProperty', 'ObjectProperty', 'Property', 'PropertyDefinition'].includes(node.parent.type) && node ===
        /**
         * @type {import('@typescript-eslint/types').TSESTree.Property|
         *   import('@typescript-eslint/types').TSESTree.PropertyDefinition
         * }
         */
        node.parent.value) {
          checkJsDoc({
            isFunctionContext: true
          }, null, node);
        }
      },
      ClassDeclaration(node) {
        if (!hasOption('ClassDeclaration')) {
          return;
        }
        checkJsDoc({
          isFunctionContext: false
        }, null, node);
      },
      ClassExpression(node) {
        if (!hasOption('ClassExpression')) {
          return;
        }
        checkJsDoc({
          isFunctionContext: false
        }, null, node);
      },
      FunctionDeclaration(node) {
        if (!hasOption('FunctionDeclaration')) {
          return;
        }
        checkJsDoc({
          isFunctionContext: true
        }, null, node);
      },
      FunctionExpression(node) {
        if (!hasOption('FunctionExpression')) {
          return;
        }
        if (['AssignmentExpression', 'ExportDefaultDeclaration', 'VariableDeclarator'].includes(node.parent.type) || ['ClassProperty', 'ObjectProperty', 'Property', 'PropertyDefinition'].includes(node.parent.type) && node ===
        /**
         * @type {import('@typescript-eslint/types').TSESTree.Property|
         *   import('@typescript-eslint/types').TSESTree.PropertyDefinition
         * }
         */
        node.parent.value) {
          checkJsDoc({
            isFunctionContext: true
          }, null, node);
        }
      },
      MethodDefinition(node) {
        if (!hasOption('MethodDefinition')) {
          return;
        }
        checkJsDoc({
          isFunctionContext: true,
          selector: 'MethodDefinition'
        }, null, /** @type {import('eslint').Rule.Node} */node.value);
      }
    };
  },
  meta: {
    docs: {
      category: 'Stylistic Issues',
      description: 'Require JSDoc comments',
      recommended: true,
      url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-jsdoc.md#repos-sticky-header'
    },
    fixable: 'code',
    messages: {
      missingJsDoc: 'Missing JSDoc comment.'
    },
    schema: [OPTIONS_SCHEMA],
    type: 'suggestion'
  }
};
module.exports = exports.default;
//# sourceMappingURL=requireJsdoc.cjs.map