"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _iterateJsdoc = require("../iterateJsdoc.cjs");
var _jsdocUtils = require("../jsdocUtils.cjs");
var _jsdoccomment = require("@es-joy/jsdoccomment");
/** @type {import('eslint').Rule.RuleModule} */
var _default = exports.default = {
  create(context) {
    /**
     * @typedef {import('eslint').AST.Token | import('estree').Comment | {
     *   type: import('eslint').AST.TokenType|"Line"|"Block"|"Shebang",
     *   range: [number, number],
     *   value: string
     * }} Token
     */

    /**
     * @callback AddComment
     * @param {boolean|undefined} inlineCommentBlock
     * @param {Token} comment
     * @param {string} indent
     * @param {number} lines
     * @param {import('eslint').Rule.RuleFixer} fixer
     */

    /* c8 ignore next -- Fallback to deprecated method */
    const {
      // @ts-expect-error ESLint < 10
      sourceCode = context.getSourceCode()
    } = context;
    const settings = (0, _iterateJsdoc.getSettings)(context);
    if (!settings) {
      return {};
    }
    const {
      allowedPrefixes = ['@ts-', 'istanbul ', 'c8 ', 'v8 ', 'eslint', 'prettier-'],
      contexts = settings.contexts || [],
      contextsAfter = (/** @type {string[]} */[]),
      contextsBeforeAndAfter = ['VariableDeclarator', 'TSPropertySignature', 'PropertyDefinition'],
      enableFixer = true,
      enforceJsdocLineStyle = 'multi',
      lineOrBlockStyle = 'both'
    } = context.options[0] ?? {};
    let reportingNonJsdoc = false;

    /**
     * @param {string} messageId
     * @param {import('estree').Comment|Token} comment
     * @param {import('eslint').Rule.Node} node
     * @param {import('eslint').Rule.ReportFixer} fixer
     */
    const report = (messageId, comment, node, fixer) => {
      const loc = {
        end: {
          column: 0,
          /* c8 ignore next 2 -- Guard */
          // @ts-expect-error Ok
          line: comment.loc?.start?.line ?? 1
        },
        start: {
          column: 0,
          /* c8 ignore next 2 -- Guard */
          // @ts-expect-error Ok
          line: comment.loc?.start?.line ?? 1
        }
      };
      context.report({
        fix: enableFixer ? fixer : null,
        loc,
        messageId,
        node
      });
    };

    /**
     * @param {import('eslint').Rule.Node} node
     * @param {import('eslint').AST.Token | import('estree').Comment | {
     *   type: import('eslint').AST.TokenType|"Line"|"Block"|"Shebang",
     *   range: [number, number],
     *   value: string
     * }} comment
     * @param {AddComment} addComment
     * @param {import('../iterateJsdoc.js').Context[]} ctxts
     */
    const getFixer = (node, comment, addComment, ctxts) => {
      return /** @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;
        let baseNode =
        /**
         * @type {import('@typescript-eslint/types').TSESTree.Node|import('eslint').Rule.Node}
         */
        (0, _jsdoccomment.getReducedASTNode)(node, sourceCode);
        const decorator = (0, _jsdoccomment.getDecorator)(/** @type {import('eslint').Rule.Node} */
        baseNode);
        if (decorator) {
          baseNode = /** @type {import('@typescript-eslint/types').TSESTree.Decorator} */
          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
         *   }[]}
         */
        ctxts.find(contxt => {
          if (typeof contxt === 'string') {
            return false;
          }
          const {
            context: ctxt
          } = contxt;
          return ctxt === node.type;
        }) || {};
        return addComment(inlineCommentBlock, comment, indent, lines, fixer);
      };
    };

    /**
     * @param {import('eslint').AST.Token | import('estree').Comment | {
     *   type: import('eslint').AST.TokenType|"Line"|"Block"|"Shebang",
     *   range: [number, number],
     *   value: string
     * }} comment
     * @param {import('eslint').Rule.Node} node
     * @param {AddComment} addComment
     * @param {import('../iterateJsdoc.js').Context[]} ctxts
     */
    const reportings = (comment, node, addComment, ctxts) => {
      const fixer = getFixer(node, comment, addComment, ctxts);
      if (comment.type === 'Block') {
        if (lineOrBlockStyle === 'line') {
          return;
        }
        report('blockCommentsJsdocStyle', comment, node, fixer);
        return;
      }
      if (comment.type === 'Line') {
        if (lineOrBlockStyle === 'block') {
          return;
        }
        report('lineCommentsJsdocStyle', comment, node, fixer);
      }
    };

    /**
     * @type {import('../iterateJsdoc.js').CheckJsdoc}
     */
    const checkNonJsdoc = (_info, _handler, node) => {
      const comment = (0, _jsdoccomment.getNonJsdocComment)(sourceCode, node, settings);
      if (!comment || /** @type {string[]} */
      allowedPrefixes.some(prefix => {
        return comment.value.trimStart().startsWith(prefix);
      })) {
        return;
      }
      reportingNonJsdoc = true;

      /** @type {AddComment} */
      const addComment = (inlineCommentBlock, commentToAdd, indent, lines, fixer) => {
        const insertion = (inlineCommentBlock || enforceJsdocLineStyle === 'single' ? `/** ${commentToAdd.value.trim()} ` : `/**\n${indent}*${commentToAdd.value.trimEnd()}\n${indent}`) + `*/${'\n'.repeat((lines || 1) - 1)}`;
        return fixer.replaceText(/** @type {import('eslint').AST.Token} */
        commentToAdd, insertion);
      };
      reportings(comment, node, addComment, contexts);
    };

    /**
     * @param {import('eslint').Rule.Node} node
     * @param {import('../iterateJsdoc.js').Context[]} ctxts
     */
    const checkNonJsdocAfter = (node, ctxts) => {
      const comment = (0, _jsdoccomment.getFollowingComment)(sourceCode, node);
      if (!comment || comment.value.startsWith('*') || /** @type {string[]} */
      allowedPrefixes.some(prefix => {
        return comment.value.trimStart().startsWith(prefix);
      })) {
        return;
      }

      /** @type {AddComment} */
      const addComment = (inlineCommentBlock, commentToAdd, indent, lines, fixer) => {
        const insertion = (inlineCommentBlock || enforceJsdocLineStyle === 'single' ? `/** ${commentToAdd.value.trim()} ` : `/**\n${indent}*${commentToAdd.value.trimEnd()}\n${indent}`) + `*/${'\n'.repeat((lines || 1) - 1)}${lines ? `\n${indent.slice(1)}` : ' '}`;
        return [fixer.remove(/** @type {import('eslint').AST.Token} */
        commentToAdd), fixer.insertTextBefore(node.type === 'VariableDeclarator' ? node.parent : node, insertion)];
      };
      reportings(comment, node, addComment, ctxts);
    };

    // Todo: add contexts to check after (and handle if want both before and after)
    return {
      ...(0, _jsdocUtils.getContextObject)((0, _jsdocUtils.enforcedContexts)(context, true, settings), checkNonJsdoc),
      ...(0, _jsdocUtils.getContextObject)(contextsAfter, (_info, _handler, node) => {
        checkNonJsdocAfter(node, contextsAfter);
      }),
      ...(0, _jsdocUtils.getContextObject)(contextsBeforeAndAfter, (_info, _handler, node) => {
        checkNonJsdoc({}, null, node);
        if (!reportingNonJsdoc) {
          checkNonJsdocAfter(node, contextsBeforeAndAfter);
        }
      })
    };
  },
  meta: {
    docs: {
      description: 'Converts non-JSDoc comments preceding or following nodes into JSDoc ones',
      url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/convert-to-jsdoc-comments.md#repos-sticky-header'
    },
    fixable: 'code',
    messages: {
      blockCommentsJsdocStyle: 'Block comments should be JSDoc-style.',
      lineCommentsJsdocStyle: 'Line comments should be JSDoc-style.'
    },
    schema: [{
      additionalProperties: false,
      properties: {
        allowedPrefixes: {
          description: `An array of prefixes to allow at the beginning of a comment.

Defaults to \`['@ts-', 'istanbul ', 'c8 ', 'v8 ', 'eslint', 'prettier-']\`.

Supplying your own value overrides the defaults.`,
          items: {
            type: 'string'
          },
          type: 'array'
        },
        contexts: {
          description: `The contexts array which will be checked for preceding content.

Can either be strings or an object with a \`context\` string and an optional, default \`false\` \`inlineCommentBlock\` boolean.

Defaults to \`ArrowFunctionExpression\`, \`FunctionDeclaration\`,
\`FunctionExpression\`, \`TSDeclareFunction\`.`,
          items: {
            anyOf: [{
              type: 'string'
            }, {
              additionalProperties: false,
              properties: {
                context: {
                  type: 'string'
                },
                inlineCommentBlock: {
                  type: 'boolean'
                }
              },
              type: 'object'
            }]
          },
          type: 'array'
        },
        contextsAfter: {
          description: `The contexts array which will be checked for content on the same line after.

Can either be strings or an object with a \`context\` string and an optional, default \`false\` \`inlineCommentBlock\` boolean.

Defaults to an empty array.`,
          items: {
            anyOf: [{
              type: 'string'
            }, {
              additionalProperties: false,
              properties: {
                context: {
                  type: 'string'
                },
                inlineCommentBlock: {
                  type: 'boolean'
                }
              },
              type: 'object'
            }]
          },
          type: 'array'
        },
        contextsBeforeAndAfter: {
          description: `The contexts array which will be checked for content before and on the same
line after.

Can either be strings or an object with a \`context\` string and an optional, default \`false\` \`inlineCommentBlock\` boolean.

Defaults to \`VariableDeclarator\`, \`TSPropertySignature\`, \`PropertyDefinition\`.`,
          items: {
            anyOf: [{
              type: 'string'
            }, {
              additionalProperties: false,
              properties: {
                context: {
                  type: 'string'
                },
                inlineCommentBlock: {
                  type: 'boolean'
                }
              },
              type: 'object'
            }]
          },
          type: 'array'
        },
        enableFixer: {
          description: 'Set to `false` to disable fixing.',
          type: 'boolean'
        },
        enforceJsdocLineStyle: {
          description: `What policy to enforce on the conversion of non-JSDoc comments without
line breaks. (Non-JSDoc (mulitline) comments with line breaks will always
be converted to \`multi\` style JSDoc comments.)

- \`multi\` - Convert to multi-line style
\`\`\`js
/**
 * Some text
 */
\`\`\`
- \`single\` - Convert to single-line style
\`\`\`js
/** Some text */
\`\`\`

Defaults to \`multi\`.`,
          enum: ['multi', 'single'],
          type: 'string'
        },
        lineOrBlockStyle: {
          description: `What style of comments to which to apply JSDoc conversion.

- \`block\` - Applies to block-style comments (\`/* ... */\`)
- \`line\` - Applies to line-style comments (\`// ...\`)
- \`both\` - Applies to both block and line-style comments

Defaults to \`both\`.`,
          enum: ['block', 'line', 'both'],
          type: 'string'
        }
      },
      type: 'object'
    }],
    type: 'suggestion'
  }
};
module.exports = exports.default;
//# sourceMappingURL=convertToJsdocComments.cjs.map