"use strict";

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

/** @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 {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 {
    publicOnly,
    contexts = settings.contexts || [],
    exemptEmptyConstructors = true,
    exemptEmptyFunctions = false,
    enableFixer = true,
    fixerMessage = '',
    minLineCount = undefined
  } = 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) {
    // istanbul 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 {
      require: requireOption,
      contexts,
      exemptEmptyFunctions,
      exemptEmptyConstructors,
      enableFixer,
      fixerMessage,
      minLineCount
    } = 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 => {
          var _sourceCode$getText$m;
          return count !== undefined && count > (((_sourceCode$getText$m = sourceCode.getText(node).match(/\n/gu)) === null || _sourceCode$getText$m === void 0 ? void 0 : _sourceCode$getText$m.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 (_jsdocUtils.default.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 && _jsdocUtils.default.isConstructor(node)) {
        const functionParameterNames = _jsdocUtils.default.getFunctionParameterNames(node);
        if (!functionParameterNames.length && !_jsdocUtils.default.hasReturnValue(node)) {
          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 {import('eslint').Rule.Node|import('@typescript-eslint/types').TSESTree.Decorator} */
        let baseNode = (0, _jsdoccomment.getReducedASTNode)(node, sourceCode);
        const decorator = (0, _jsdoccomment.getDecorator)(baseNode);
        if (decorator) {
          baseNode = decorator;
        }
        const indent = _jsdocUtils.default.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 === null || publicOnly === void 0 ? void 0 : publicOnly.ancestorsOnly) ?? false),
          esm: Boolean((publicOnly === null || publicOnly === void 0 ? void 0 : publicOnly.esm) ?? true),
          initModuleExports: Boolean((publicOnly === null || publicOnly === void 0 ? void 0 : publicOnly.cjs) ?? true),
          initWindow: Boolean((publicOnly === null || publicOnly === void 0 ? void 0 : 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 {
      ..._jsdocUtils.default.getContextObject(_jsdocUtils.default.enforcedContexts(context, [], settings), checkJsDoc),
      ArrowFunctionExpression(node) {
        if (!hasOption('ArrowFunctionExpression')) {
          return;
        }
        if (['VariableDeclarator', 'AssignmentExpression', 'ExportDefaultDeclaration'].includes(node.parent.type) || ['Property', 'ObjectProperty', 'ClassProperty', '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 (['VariableDeclarator', 'AssignmentExpression', 'ExportDefaultDeclaration'].includes(node.parent.type) || ['Property', 'ObjectProperty', 'ClassProperty', '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.js.map