"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc.cjs"));
var _escapeStringRegexp = _interopRequireDefault(require("escape-string-regexp"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
// https://babeljs.io/docs/en/babel-plugin-transform-react-jsx/
const jsxTagNames = new Set(['jsx', 'jsxFrag', 'jsxImportSource', 'jsxRuntime']);
const typedTagsAlwaysUnnecessary = new Set(['augments', 'callback', 'class', 'enum', 'implements', 'private', 'property', 'protected', 'public', 'readonly', 'this', 'type', 'typedef']);
const typedTagsNeedingName = new Set(['template']);
const typedTagsUnnecessaryOutsideDeclare = new Set(['abstract', 'access', 'class', 'constant', 'constructs', 'default', 'enum', 'export', 'exports', 'function', 'global', 'inherits', 'instance', 'interface', 'member', 'memberof', 'memberOf', 'method', 'mixes', 'mixin', 'module', 'name', 'namespace', 'override', 'property', 'requires', 'static', 'this']);
var _default = exports.default = (0, _iterateJsdoc.default)(({
  context,
  jsdoc,
  jsdocNode,
  node,
  report,
  settings,
  sourceCode,
  utils
}) => {
  const
  /**
   * @type {{
   *   definedTags: string[],
   *   enableFixer: boolean,
   *   inlineTags: string[],
   *   jsxTags: boolean,
   *   typed: boolean
   }} */
  {
    definedTags = [],
    enableFixer = true,
    inlineTags = [
    // jsdoc
    'link', 'linkcode', 'linkplain', 'tutorial',
    // https://tsdoc.org/pages/spec/tag_kinds/#inline-tags
    'inheritDoc', 'label',
    // https://typedoc.org/documents/Tags.html#inline-tags
    'include', 'includeCode'],
    jsxTags,
    typed
  } = context.options[0] || {};

  /** @type {(string|undefined)[]} */
  let definedPreferredTags = [];
  const {
    structuredTags,
    tagNamePreference
  } = settings;
  const definedStructuredTags = Object.keys(structuredTags);
  const definedNonPreferredTags = Object.keys(tagNamePreference);
  if (definedNonPreferredTags.length) {
    definedPreferredTags = Object.values(tagNamePreference).map(preferredTag => {
      if (typeof preferredTag === 'string') {
        // May become an empty string but will be filtered out below
        return preferredTag;
      }
      if (!preferredTag) {
        return undefined;
      }
      if (typeof preferredTag !== 'object') {
        utils.reportSettings('Invalid `settings.jsdoc.tagNamePreference`. Values must be falsy, a string, or an object.');
      }
      return preferredTag.replacement;
    }).filter(Boolean);
  }

  /**
   * @param {import('eslint').Rule.Node} subNode
   * @returns {boolean}
   */
  const isInAmbientContext = subNode => {
    return subNode.type === 'Program' ? /* c8 ignore next 2 -- Support old ESLint */
    // @ts-expect-error ESLint < 10
    (context.filename ?? context.getFilename()).endsWith('.d.ts') : Boolean(/** @type {import('@typescript-eslint/types').TSESTree.VariableDeclaration} */subNode.declare) || isInAmbientContext(subNode.parent);
  };

  /**
   * @param {import('comment-parser').Spec} jsdocTag
   * @returns {boolean}
   */
  const tagIsRedundantWhenTyped = jsdocTag => {
    if (!typedTagsUnnecessaryOutsideDeclare.has(jsdocTag.tag)) {
      return false;
    }
    if (jsdocTag.tag === 'default') {
      return false;
    }
    if (node === null) {
      return false;
    }

    /* c8 ignore next 2 -- Support old ESLint */
    // @ts-expect-error ESLint < 10
    if ((context.filename ?? context.getFilename()).endsWith('.d.ts') && [null, 'Program', undefined].includes(node?.parent?.type)) {
      return false;
    }
    if (isInAmbientContext(/** @type {import('eslint').Rule.Node} */node)) {
      return false;
    }
    return true;
  };

  /**
   * @param {string} message
   * @param {import('comment-parser').Spec} jsdocTag
   * @param {import('../iterateJsdoc.js').Integer} tagIndex
   * @param {Partial<import('comment-parser').Tokens>} [additionalTagChanges]
   * @returns {void}
   */
  const reportWithTagRemovalFixer = (message, jsdocTag, tagIndex, additionalTagChanges) => {
    utils.reportJSDoc(message, jsdocTag, enableFixer ? () => {
      if (jsdocTag.description.trim()) {
        utils.changeTag(jsdocTag, {
          postType: '',
          type: '',
          ...additionalTagChanges
        });
      } else {
        utils.removeTag(tagIndex, {
          removeEmptyBlock: true
        });
      }
    } : null, true);
  };

  /**
   * @param {import('comment-parser').Spec} jsdocTag
   * @param {import('../iterateJsdoc.js').Integer} tagIndex
   * @returns {boolean}
   */
  const checkTagForTypedValidity = (jsdocTag, tagIndex) => {
    if (typedTagsAlwaysUnnecessary.has(jsdocTag.tag)) {
      reportWithTagRemovalFixer(`'@${jsdocTag.tag}' is redundant when using a type system.`, jsdocTag, tagIndex, {
        postTag: '',
        tag: ''
      });
      return true;
    }
    if (tagIsRedundantWhenTyped(jsdocTag)) {
      reportWithTagRemovalFixer(`'@${jsdocTag.tag}' is redundant outside of ambient (\`declare\`/\`.d.ts\`) contexts when using a type system.`, jsdocTag, tagIndex);
      return true;
    }
    if (typedTagsNeedingName.has(jsdocTag.tag) && !jsdocTag.name) {
      reportWithTagRemovalFixer(`'@${jsdocTag.tag}' without a name is redundant when using a type system.`, jsdocTag, tagIndex);
      return true;
    }
    return false;
  };
  for (let tagIndex = 0; tagIndex < jsdoc.tags.length; tagIndex += 1) {
    const jsdocTag = jsdoc.tags[tagIndex];
    const tagName = jsdocTag.tag;
    if (jsxTags && jsxTagNames.has(tagName)) {
      continue;
    }
    if (typed && checkTagForTypedValidity(jsdocTag, tagIndex)) {
      continue;
    }
    const validTags = [...definedTags, ...(/** @type {string[]} */definedPreferredTags), ...definedNonPreferredTags, ...definedStructuredTags, ...(typed ? typedTagsNeedingName : [])];
    if (utils.isValidTag(tagName, validTags)) {
      let preferredTagName = utils.getPreferredTagName({
        allowObjectReturn: true,
        defaultMessage: `Blacklisted tag found (\`@${tagName}\`)`,
        tagName
      });
      if (!preferredTagName) {
        continue;
      }
      let message;
      if (typeof preferredTagName === 'object') {
        ({
          message,
          replacement: preferredTagName
        } = /** @type {{message: string; replacement?: string | undefined;}} */
        preferredTagName);
      }
      if (!message) {
        message = `Invalid JSDoc tag (preference). Replace "${tagName}" JSDoc tag with "${preferredTagName}".`;
      }
      if (preferredTagName !== tagName) {
        report(message, fixer => {
          const replacement = sourceCode.getText(jsdocNode).replace(new RegExp(`@${(0, _escapeStringRegexp.default)(tagName)}\\b`, 'v'), `@${preferredTagName}`);
          return fixer.replaceText(jsdocNode, replacement);
        }, jsdocTag);
      }
    } else {
      report(`Invalid JSDoc tag name "${tagName}".`, null, jsdocTag);
    }
  }
  for (const inlineTag of utils.getInlineTags()) {
    if (!inlineTags.includes(inlineTag.tag)) {
      report(`Invalid JSDoc inline tag name "${inlineTag.tag}"`, null, inlineTag);
    }
  }
}, {
  iterateAllJsdocs: true,
  meta: {
    docs: {
      description: 'Reports invalid block tag names.',
      url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-tag-names.md#repos-sticky-header'
    },
    fixable: 'code',
    schema: [{
      additionalProperties: false,
      properties: {
        definedTags: {
          description: `Use an array of \`definedTags\` strings to configure additional, allowed tags.
The format is as follows:

\`\`\`json
{
  "definedTags": ["note", "record"]
}
\`\`\``,
          items: {
            type: 'string'
          },
          type: 'array'
        },
        enableFixer: {
          description: 'Set to `false` to disable auto-removal of types that are redundant with the [`typed` option](#typed).',
          type: 'boolean'
        },
        inlineTags: {
          description: `List of tags to allow inline.

Defaults to array of \`'link', 'linkcode', 'linkplain', 'tutorial', 'inheritDoc', 'label', 'include', and 'includeCode'\``,
          items: {
            type: 'string'
          },
          type: 'array'
        },
        jsxTags: {
          description: `If this is set to \`true\`, all of the following tags used to control JSX output are allowed:

\`\`\`
jsx
jsxFrag
jsxImportSource
jsxRuntime
\`\`\`

For more information, see the [babel documentation](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx).`,
          type: 'boolean'
        },
        typed: {
          description: `If this is set to \`true\`, additionally checks for tag names that are redundant when using a type checker such as TypeScript.

These tags are always unnecessary when using TypeScript or similar:

\`\`\`
augments
callback
class
enum
implements
private
property
protected
public
readonly
this
type
typedef
\`\`\`

These tags are unnecessary except when inside a TypeScript \`declare\` context:

\`\`\`
abstract
access
class
constant
constructs
default
enum
export
exports
function
global
inherits
instance
interface
member
memberof
memberOf
method
mixes
mixin
module
name
namespace
override
property
requires
static
this
\`\`\``,
          type: 'boolean'
        }
      },
      type: 'object'
    }],
    type: 'suggestion'
  }
});
module.exports = exports.default;
//# sourceMappingURL=checkTagNames.cjs.map