"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = iterateJsdoc;
exports.getSettings = void 0;
Object.defineProperty(exports, "parseComment", {
  enumerable: true,
  get: function () {
    return _jsdoccomment.parseComment;
  }
});

var _jsdoccomment = require("@es-joy/jsdoccomment");

var _commentParser = require("comment-parser");

var _jsdocUtils = _interopRequireDefault(require("./jsdocUtils"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/* eslint-disable jsdoc/valid-types */
const {
  rewireSpecs,
  seedTokens
} = _commentParser.util;
/*
const {
   align as commentAlign,
  flow: commentFlow,
  indent: commentIndent,
} = transforms;
*/

const globalState = new Map();

const getBasicUtils = (context, {
  tagNamePreference,
  mode
}) => {
  const utils = {};

  utils.reportSettings = message => {
    context.report({
      loc: {
        start: {
          column: 1,
          line: 1
        }
      },
      message
    });
  };

  utils.parseClosureTemplateTag = tag => {
    return _jsdocUtils.default.parseClosureTemplateTag(tag);
  };

  utils.pathDoesNotBeginWith = _jsdocUtils.default.pathDoesNotBeginWith;

  utils.getPreferredTagNameObject = ({
    tagName
  }) => {
    const ret = _jsdocUtils.default.getPreferredTagName(context, mode, tagName, tagNamePreference);

    const isObject = ret && typeof ret === 'object';

    if (ret === false || isObject && !ret.replacement) {
      return {
        blocked: true,
        tagName
      };
    }

    return ret;
  };

  return utils;
};

const getUtils = (node, jsdoc, jsdocNode, settings, report, context, iteratingAll, ruleConfig, indent) => {
  const ancestors = context.getAncestors();
  const sourceCode = context.getSourceCode();
  const utils = getBasicUtils(context, settings);
  const {
    tagNamePreference,
    overrideReplacesDocs,
    ignoreReplacesDocs,
    implementsReplacesDocs,
    augmentsExtendsReplacesDocs,
    maxLines,
    minLines,
    mode
  } = settings;

  utils.isIteratingFunction = () => {
    return !iteratingAll || ['MethodDefinition', 'ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(node && node.type);
  };

  utils.isVirtualFunction = () => {
    return iteratingAll && utils.hasATag(['callback', 'function', 'func', 'method']);
  };

  utils.stringify = (tagBlock, specRewire) => {
    return (0, _commentParser.stringify)(specRewire ? rewireSpecs(tagBlock) : tagBlock);
  };

  utils.reportJSDoc = (msg, tag, handler, specRewire, data) => {
    report(msg, handler ? fixer => {
      handler();
      const replacement = utils.stringify(jsdoc, specRewire);
      return fixer.replaceText(jsdocNode, replacement);
    } : null, tag, data);
  };

  utils.getRegexFromString = (str, requiredFlags) => {
    return _jsdocUtils.default.getRegexFromString(str, requiredFlags);
  };

  utils.getTagDescription = tg => {
    const descriptions = [];
    tg.source.some(({
      tokens: {
        end,
        lineEnd,
        postDelimiter,
        tag,
        postTag,
        name,
        type,
        description
      }
    }) => {
      const desc = (tag && postTag || !tag && !name && !type && postDelimiter || '' // Remove space
      ).slice(1) + (description || '') + (lineEnd || '');

      if (end) {
        if (desc) {
          descriptions.push(desc);
        }

        return true;
      }

      descriptions.push(desc);
      return false;
    });
    return descriptions.join('\n');
  };

  utils.getDescription = () => {
    const descriptions = [];
    let lastDescriptionLine = 0;
    jsdoc.source.some(({
      tokens: {
        description,
        tag,
        end
      }
    }, idx) => {
      if (idx && (tag || end)) {
        lastDescriptionLine = idx - 1;
        return true;
      }

      if (idx || description) {
        descriptions.push(description);
      }

      return false;
    });
    return {
      description: descriptions.join('\n'),
      lastDescriptionLine
    };
  };

  utils.changeTag = (tag, ...tokens) => {
    for (const [idx, src] of tag.source.entries()) {
      src.tokens = { ...src.tokens,
        ...tokens[idx]
      };
    }
  };

  utils.setTag = (tag, tokens) => {
    tag.source = [{
      // Or tag.source[0].number?
      number: tag.line,
      tokens: seedTokens({
        delimiter: '*',
        postDelimiter: ' ',
        start: indent + ' ',
        tag: '@' + tag.tag,
        ...tokens
      })
    }];
  };

  utils.removeTag = idx => {
    return utils.removeTagItem(idx);
  };

  utils.removeTagItem = (tagIndex, tagSourceOffset = 0) => {
    const {
      source: tagSource
    } = jsdoc.tags[tagIndex];
    let lastIndex;
    const firstNumber = jsdoc.source[0].number;
    tagSource.some(({
      number
    }, tagIdx) => {
      const sourceIndex = jsdoc.source.findIndex(({
        number: srcNumber,
        tokens: {
          end
        }
      }) => {
        return number === srcNumber && !end;
      }); // istanbul ignore else

      if (sourceIndex > -1) {
        let spliceCount = 1;
        tagSource.slice(tagIdx + 1).some(({
          tokens: {
            tag,
            end
          }
        }) => {
          if (!tag && !end) {
            spliceCount++;
            return false;
          }

          return true;
        });
        jsdoc.source.splice(sourceIndex + tagSourceOffset, spliceCount - tagSourceOffset);
        tagSource.splice(tagIdx + tagSourceOffset, spliceCount - tagSourceOffset);
        lastIndex = sourceIndex;
        return true;
      } // istanbul ignore next


      return false;
    });

    for (const [idx, src] of jsdoc.source.slice(lastIndex).entries()) {
      src.number = firstNumber + lastIndex + idx;
    }
  };

  utils.addTag = targetTagName => {
    var _jsdoc$tags$source$0$, _jsdoc$tags, _jsdoc$tags$source$;

    const number = ((_jsdoc$tags$source$0$ = (_jsdoc$tags = jsdoc.tags[jsdoc.tags.length - 1]) === null || _jsdoc$tags === void 0 ? void 0 : (_jsdoc$tags$source$ = _jsdoc$tags.source[0]) === null || _jsdoc$tags$source$ === void 0 ? void 0 : _jsdoc$tags$source$.number) !== null && _jsdoc$tags$source$0$ !== void 0 ? _jsdoc$tags$source$0$ : 0) + 1;
    jsdoc.source.splice(number, 0, {
      number,
      source: '',
      tokens: seedTokens({
        delimiter: '*',
        postDelimiter: ' ',
        start: indent + ' ',
        tag: `@${targetTagName}`
      })
    });

    for (const src of jsdoc.source.slice(number + 1)) {
      src.number++;
    }
  };

  utils.seedTokens = seedTokens;

  utils.emptyTokens = tokens => {
    for (const prop of ['start', 'postDelimiter', 'tag', 'type', 'postType', 'postTag', 'name', 'postName', 'description', 'end', 'lineEnd']) {
      tokens[prop] = '';
    }
  };

  utils.addLine = (sourceIndex, tokens) => {
    var _jsdoc$source;

    const number = (((_jsdoc$source = jsdoc.source[sourceIndex - 1]) === null || _jsdoc$source === void 0 ? void 0 : _jsdoc$source.number) || 0) + 1;
    jsdoc.source.splice(sourceIndex, 0, {
      number,
      source: '',
      tokens: seedTokens(tokens)
    }); // If necessary, we can rewire the tags (misnamed method)
    // rewireSource(jsdoc);
  };

  utils.addLines = (tagIndex, tagSourceOffset, numLines) => {
    const {
      source: tagSource
    } = jsdoc.tags[tagIndex];
    let lastIndex;
    const firstNumber = jsdoc.source[0].number;
    tagSource.some(({
      number
    }) => {
      const makeLine = () => {
        return {
          number,
          source: '',
          tokens: seedTokens({
            delimiter: '*',
            start: indent + ' '
          })
        };
      };

      const makeLines = () => {
        return Array.from({
          length: numLines
        }, makeLine);
      };

      const sourceIndex = jsdoc.source.findIndex(({
        number: srcNumber,
        tokens: {
          end
        }
      }) => {
        return number === srcNumber && !end;
      }); // istanbul ignore else

      if (sourceIndex > -1) {
        const lines = makeLines();
        jsdoc.source.splice(sourceIndex + tagSourceOffset, 0, ...lines); // tagSource.splice(tagIdx + 1, 0, ...makeLines());

        lastIndex = sourceIndex;
        return true;
      } // istanbul ignore next


      return false;
    });

    for (const [idx, src] of jsdoc.source.slice(lastIndex).entries()) {
      src.number = firstNumber + lastIndex + idx;
    }
  };

  utils.makeMultiline = () => {
    const {
      source: [{
        tokens
      }]
    } = jsdoc;
    const {
      postDelimiter,
      description,
      lineEnd,
      tag,
      name,
      type
    } = tokens;
    let {
      tokens: {
        postName,
        postTag,
        postType
      }
    } = jsdoc.source[0]; // Strip trailing leftovers from single line ending

    if (!description) {
      if (postName) {
        postName = '';
      } else if (postType) {
        postType = ''; // eslint-disable-next-line no-inline-comments
      } else
        /* istanbul ignore else -- `comment-parser` prevents empty blocks currently per https://github.com/syavorsky/comment-parser/issues/128 */
        if (postTag) {
          postTag = '';
        }
    }

    utils.emptyTokens(tokens);
    utils.addLine(1, {
      delimiter: '*',
      // If a description were present, it may have whitespace attached
      //   due to being at the end of the single line
      description: description.trimEnd(),
      name,
      postDelimiter,
      postName,
      postTag,
      postType,
      start: indent + ' ',
      tag,
      type
    });
    utils.addLine(2, {
      end: '*/',
      lineEnd,
      start: indent + ' '
    });
  };

  utils.flattenRoots = params => {
    return _jsdocUtils.default.flattenRoots(params);
  };

  utils.getFunctionParameterNames = useDefaultObjectProperties => {
    return _jsdocUtils.default.getFunctionParameterNames(node, useDefaultObjectProperties);
  };

  utils.hasParams = () => {
    return _jsdocUtils.default.hasParams(node);
  };

  utils.isGenerator = () => {
    return node && (node.generator || node.type === 'MethodDefinition' && node.value.generator || ['ExportNamedDeclaration', 'ExportDefaultDeclaration'].includes(node.type) && node.declaration.generator);
  };

  utils.isConstructor = () => {
    return _jsdocUtils.default.isConstructor(node);
  };

  utils.getJsdocTagsDeep = tagName => {
    const name = utils.getPreferredTagName({
      tagName
    });

    if (!name) {
      return false;
    }

    return _jsdocUtils.default.getJsdocTagsDeep(jsdoc, name);
  };

  utils.getPreferredTagName = ({
    tagName,
    skipReportingBlockedTag = false,
    allowObjectReturn = false,
    defaultMessage = `Unexpected tag \`@${tagName}\``
  }) => {
    const ret = _jsdocUtils.default.getPreferredTagName(context, mode, tagName, tagNamePreference);

    const isObject = ret && typeof ret === 'object';

    if (utils.hasTag(tagName) && (ret === false || isObject && !ret.replacement)) {
      if (skipReportingBlockedTag) {
        return {
          blocked: true,
          tagName
        };
      }

      const message = isObject && ret.message || defaultMessage;
      report(message, null, utils.getTags(tagName)[0]);
      return false;
    }

    return isObject && !allowObjectReturn ? ret.replacement : ret;
  };

  utils.isValidTag = (name, definedTags) => {
    return _jsdocUtils.default.isValidTag(context, mode, name, definedTags);
  };

  utils.hasATag = names => {
    return _jsdocUtils.default.hasATag(jsdoc, names);
  };

  utils.hasTag = name => {
    return _jsdocUtils.default.hasTag(jsdoc, name);
  };

  utils.comparePaths = name => {
    return _jsdocUtils.default.comparePaths(name);
  };

  utils.dropPathSegmentQuotes = name => {
    return _jsdocUtils.default.dropPathSegmentQuotes(name);
  };

  utils.avoidDocs = () => {
    var _context$options$0$ex, _context$options$;

    if (ignoreReplacesDocs !== false && (utils.hasTag('ignore') || utils.classHasTag('ignore')) || overrideReplacesDocs !== false && (utils.hasTag('override') || utils.classHasTag('override')) || implementsReplacesDocs !== false && (utils.hasTag('implements') || utils.classHasTag('implements')) || augmentsExtendsReplacesDocs && (utils.hasATag(['augments', 'extends']) || utils.classHasTag('augments') || utils.classHasTag('extends'))) {
      return true;
    }

    if (_jsdocUtils.default.exemptSpeciaMethods(jsdoc, node, context, ruleConfig.meta.schema)) {
      return true;
    }

    const exemptedBy = (_context$options$0$ex = (_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.exemptedBy) !== null && _context$options$0$ex !== void 0 ? _context$options$0$ex : ['inheritDoc', ...(mode === 'closure' ? [] : ['inheritdoc'])];

    if (exemptedBy.length && utils.getPresentTags(exemptedBy).length) {
      return true;
    }

    return false;
  };

  for (const method of ['tagMightHaveNamePosition', 'tagMightHaveTypePosition']) {
    utils[method] = (tagName, otherModeMaps) => {
      const result = _jsdocUtils.default[method](tagName);

      if (result) {
        return true;
      }

      if (!otherModeMaps) {
        return false;
      }

      const otherResult = otherModeMaps.some(otherModeMap => {
        return _jsdocUtils.default[method](tagName, otherModeMap);
      });
      return otherResult ? {
        otherMode: true
      } : false;
    };
  }

  for (const method of ['tagMustHaveNamePosition', 'tagMustHaveTypePosition', 'tagMissingRequiredTypeOrNamepath']) {
    utils[method] = (tagName, otherModeMaps) => {
      const result = _jsdocUtils.default[method](tagName);

      if (!result) {
        return false;
      } // if (!otherModeMaps) { return true; }


      const otherResult = otherModeMaps.every(otherModeMap => {
        return _jsdocUtils.default[method](tagName, otherModeMap);
      });
      return otherResult ? true : {
        otherMode: false
      };
    };
  }

  for (const method of ['isNamepathDefiningTag', 'tagMightHaveNamepath']) {
    utils[method] = tagName => {
      return _jsdocUtils.default[method](tagName);
    };
  }

  utils.getTagStructureForMode = mde => {
    return _jsdocUtils.default.getTagStructureForMode(mde, settings.structuredTags);
  };

  utils.hasDefinedTypeTag = tag => {
    return _jsdocUtils.default.hasDefinedTypeTag(tag);
  };

  utils.hasValueOrExecutorHasNonEmptyResolveValue = anyPromiseAsReturn => {
    return _jsdocUtils.default.hasValueOrExecutorHasNonEmptyResolveValue(node, anyPromiseAsReturn);
  };

  utils.hasYieldValue = () => {
    if (['ExportNamedDeclaration', 'ExportDefaultDeclaration'].includes(node.type)) {
      return _jsdocUtils.default.hasYieldValue(node.declaration);
    }

    return _jsdocUtils.default.hasYieldValue(node);
  };

  utils.hasYieldReturnValue = () => {
    return _jsdocUtils.default.hasYieldValue(node, true);
  };

  utils.hasThrowValue = () => {
    return _jsdocUtils.default.hasThrowValue(node);
  };

  utils.isAsync = () => {
    return node.async;
  };

  utils.getTags = tagName => {
    return utils.filterTags(item => {
      return item.tag === tagName;
    });
  };

  utils.getPresentTags = tagList => {
    return utils.filterTags(tag => {
      return tagList.includes(tag.tag);
    });
  };

  utils.filterTags = filter => {
    return _jsdocUtils.default.filterTags(jsdoc.tags, filter);
  };

  utils.getTagsByType = tags => {
    return _jsdocUtils.default.getTagsByType(context, mode, tags, tagNamePreference);
  };

  utils.hasOptionTag = tagName => {
    var _context$options$2;

    const {
      tags
    } = (_context$options$2 = context.options[0]) !== null && _context$options$2 !== void 0 ? _context$options$2 : {};
    return Boolean(tags && tags.includes(tagName));
  };

  utils.getClassNode = () => {
    return [...ancestors, node].reverse().find(parent => {
      return parent && ['ClassDeclaration', 'ClassExpression'].includes(parent.type);
    }) || null;
  };

  utils.getClassJsdoc = () => {
    const classNode = utils.getClassNode();

    if (!classNode) {
      return null;
    }

    const classJsdocNode = (0, _jsdoccomment.getJSDocComment)(sourceCode, classNode, {
      maxLines,
      minLines
    });

    if (classJsdocNode) {
      const indnt = ' '.repeat(classJsdocNode.loc.start.column);
      return (0, _jsdoccomment.parseComment)(classJsdocNode, indnt);
    }

    return null;
  };

  utils.classHasTag = tagName => {
    const classJsdoc = utils.getClassJsdoc();
    return Boolean(classJsdoc) && _jsdocUtils.default.hasTag(classJsdoc, tagName);
  };

  utils.forEachPreferredTag = (tagName, arrayHandler, skipReportingBlockedTag = false) => {
    const targetTagName = utils.getPreferredTagName({
      skipReportingBlockedTag,
      tagName
    });

    if (!targetTagName || skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object') {
      return;
    }

    const matchingJsdocTags = jsdoc.tags.filter(({
      tag
    }) => {
      return tag === targetTagName;
    });

    for (const matchingJsdocTag of matchingJsdocTags) {
      arrayHandler(matchingJsdocTag, targetTagName);
    }
  };

  return utils;
};

const getSettings = context => {
  var _context$settings$jsd, _context$settings$jsd2, _context$settings$jsd3, _context$settings$jsd4, _context$settings$jsd5, _context$settings$jsd6, _context$settings$jsd7, _context$settings$jsd8, _context$settings$jsd9, _context$settings$jsd10, _context$settings$jsd11, _context$settings$jsd12, _context$settings$jsd13, _context$settings$jsd14, _context$settings$jsd15, _context$settings$jsd16, _context$settings$jsd17, _context$settings$jsd18;

  /* eslint-disable canonical/sort-keys */
  const settings = {
    // All rules
    ignorePrivate: Boolean((_context$settings$jsd = context.settings.jsdoc) === null || _context$settings$jsd === void 0 ? void 0 : _context$settings$jsd.ignorePrivate),
    ignoreInternal: Boolean((_context$settings$jsd2 = context.settings.jsdoc) === null || _context$settings$jsd2 === void 0 ? void 0 : _context$settings$jsd2.ignoreInternal),
    maxLines: Number((_context$settings$jsd3 = (_context$settings$jsd4 = context.settings.jsdoc) === null || _context$settings$jsd4 === void 0 ? void 0 : _context$settings$jsd4.maxLines) !== null && _context$settings$jsd3 !== void 0 ? _context$settings$jsd3 : 1),
    minLines: Number((_context$settings$jsd5 = (_context$settings$jsd6 = context.settings.jsdoc) === null || _context$settings$jsd6 === void 0 ? void 0 : _context$settings$jsd6.minLines) !== null && _context$settings$jsd5 !== void 0 ? _context$settings$jsd5 : 0),
    // `check-tag-names` and many returns/param rules
    tagNamePreference: (_context$settings$jsd7 = (_context$settings$jsd8 = context.settings.jsdoc) === null || _context$settings$jsd8 === void 0 ? void 0 : _context$settings$jsd8.tagNamePreference) !== null && _context$settings$jsd7 !== void 0 ? _context$settings$jsd7 : {},
    // `check-types` and `no-undefined-types`
    preferredTypes: (_context$settings$jsd9 = (_context$settings$jsd10 = context.settings.jsdoc) === null || _context$settings$jsd10 === void 0 ? void 0 : _context$settings$jsd10.preferredTypes) !== null && _context$settings$jsd9 !== void 0 ? _context$settings$jsd9 : {},
    // `check-types`, `no-undefined-types`, `valid-types`
    structuredTags: (_context$settings$jsd11 = (_context$settings$jsd12 = context.settings.jsdoc) === null || _context$settings$jsd12 === void 0 ? void 0 : _context$settings$jsd12.structuredTags) !== null && _context$settings$jsd11 !== void 0 ? _context$settings$jsd11 : {},
    // `require-param`, `require-description`, `require-example`,
    // `require-returns`, `require-throw`, `require-yields`
    overrideReplacesDocs: (_context$settings$jsd13 = context.settings.jsdoc) === null || _context$settings$jsd13 === void 0 ? void 0 : _context$settings$jsd13.overrideReplacesDocs,
    ignoreReplacesDocs: (_context$settings$jsd14 = context.settings.jsdoc) === null || _context$settings$jsd14 === void 0 ? void 0 : _context$settings$jsd14.ignoreReplacesDocs,
    implementsReplacesDocs: (_context$settings$jsd15 = context.settings.jsdoc) === null || _context$settings$jsd15 === void 0 ? void 0 : _context$settings$jsd15.implementsReplacesDocs,
    augmentsExtendsReplacesDocs: (_context$settings$jsd16 = context.settings.jsdoc) === null || _context$settings$jsd16 === void 0 ? void 0 : _context$settings$jsd16.augmentsExtendsReplacesDocs,
    // Many rules, e.g., `check-tag-names`
    mode: (_context$settings$jsd17 = (_context$settings$jsd18 = context.settings.jsdoc) === null || _context$settings$jsd18 === void 0 ? void 0 : _context$settings$jsd18.mode) !== null && _context$settings$jsd17 !== void 0 ? _context$settings$jsd17 : context.parserPath.includes('@typescript-eslint') ? 'typescript' : 'jsdoc'
  };
  /* eslint-enable canonical/sort-keys */

  _jsdocUtils.default.setTagStructure(settings.mode);

  try {
    _jsdocUtils.default.overrideTagStructure(settings.structuredTags);
  } catch (error) {
    context.report({
      loc: {
        start: {
          column: 1,
          line: 1
        }
      },
      message: error.message
    });
    return false;
  }

  return settings;
};
/**
 * Create the report function
 *
 * @param {object} context
 * @param {object} commentNode
 */


exports.getSettings = getSettings;

const makeReport = (context, commentNode) => {
  const report = (message, fix = null, jsdocLoc = null, data = null) => {
    let loc;

    if (jsdocLoc) {
      if (!('line' in jsdocLoc)) {
        jsdocLoc.line = jsdocLoc.source[0].number;
      }

      const lineNumber = commentNode.loc.start.line + jsdocLoc.line;
      loc = {
        end: {
          line: lineNumber
        },
        start: {
          line: lineNumber
        }
      }; // Todo: Remove ignore once `check-examples` can be restored for ESLint 8+
      // istanbul ignore if

      if (jsdocLoc.column) {
        const colNumber = commentNode.loc.start.column + jsdocLoc.column;
        loc.end.column = colNumber;
        loc.start.column = colNumber;
      }
    }

    context.report({
      data,
      fix,
      loc,
      message,
      node: commentNode
    });
  };

  return report;
};
/**
 * @typedef {ReturnType<typeof getUtils>} Utils
 * @typedef {ReturnType<typeof getSettings>} Settings
 * @typedef {(
 *   arg: {
 *     context: object,
 *     sourceCode: object,
 *     indent: string,
 *     jsdoc: object,
 *     jsdocNode: object,
 *     node: object | null,
 *     report: ReturnType<typeof makeReport>,
 *     settings: Settings,
 *     utils: Utils,
 *   }
 * ) => any } JsdocVisitor
 */


const iterate = (info, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state, iteratingAll) => {
  const report = makeReport(context, jsdocNode);
  const utils = getUtils(node, jsdoc, jsdocNode, settings, report, context, iteratingAll, ruleConfig, indent);

  if (!ruleConfig.checkInternal && settings.ignoreInternal && utils.hasTag('internal')) {
    return;
  }

  if (!ruleConfig.checkPrivate && settings.ignorePrivate && (utils.hasTag('private') || jsdoc.tags.filter(({
    tag
  }) => {
    return tag === 'access';
  }).some(({
    description
  }) => {
    return description === 'private';
  }))) {
    return;
  }

  iterator({
    context,
    globalState,
    indent,
    info,
    iteratingAll,
    jsdoc,
    jsdocNode,
    node,
    report,
    settings,
    sourceCode,
    state,
    utils
  });
};

const getIndentAndJSDoc = function (lines, jsdocNode) {
  const sourceLine = lines[jsdocNode.loc.start.line - 1];
  const indnt = sourceLine.charAt(0).repeat(jsdocNode.loc.start.column);
  const jsdc = (0, _jsdoccomment.parseComment)(jsdocNode, indnt);
  return [indnt, jsdc];
};
/**
 * Create an eslint rule that iterates over all JSDocs, regardless of whether
 * they are attached to a function-like node.
 *
 * @param {JsdocVisitor} iterator
 * @param {{meta: any}} ruleConfig
 * @param contexts
 * @param {boolean} additiveContexts
 */


const iterateAllJsdocs = (iterator, ruleConfig, contexts, additiveContexts) => {
  const trackedJsdocs = [];
  let handler;
  let settings;

  const callIterator = (context, node, jsdocNodes, state, lastCall) => {
    const sourceCode = context.getSourceCode();
    const {
      lines
    } = sourceCode;
    const utils = getBasicUtils(context, settings);

    for (const jsdocNode of jsdocNodes) {
      if (!/^\/\*\*\s/u.test(sourceCode.getText(jsdocNode))) {
        continue;
      }

      const [indent, jsdoc] = getIndentAndJSDoc(lines, jsdocNode);

      if (additiveContexts) {
        for (const [idx, {
          comment
        }] of contexts.entries()) {
          if (comment && handler(comment, jsdoc) === false) {
            continue;
          }

          iterate({
            comment,
            lastIndex: idx,
            selector: node === null || node === void 0 ? void 0 : node.type
          }, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state, true);
        }

        continue;
      }

      let lastComment;
      let lastIndex; // eslint-disable-next-line no-loop-func

      if (contexts && contexts.every(({
        comment
      }, idx) => {
        lastComment = comment;
        lastIndex = idx;
        return comment && handler(comment, jsdoc) === false;
      })) {
        continue;
      }

      iterate(lastComment ? {
        comment: lastComment,
        lastIndex,
        selector: node === null || node === void 0 ? void 0 : node.type
      } : {
        lastIndex,
        selector: node === null || node === void 0 ? void 0 : node.type
      }, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state, true);
    }

    if (lastCall && ruleConfig.exit) {
      ruleConfig.exit({
        context,
        state,
        utils
      });
    }
  };

  return {
    create(context) {
      const sourceCode = context.getSourceCode();
      settings = getSettings(context);

      if (!settings) {
        return {};
      }

      if (contexts) {
        handler = (0, _jsdoccomment.commentHandler)(settings);
      }

      const state = {};
      return {
        '*:not(Program)'(node) {
          const reducedNode = (0, _jsdoccomment.getReducedASTNode)(node, sourceCode);

          if (node !== reducedNode) {
            return;
          }

          const commentNode = (0, _jsdoccomment.getJSDocComment)(sourceCode, node, settings);

          if (trackedJsdocs.includes(commentNode)) {
            return;
          }

          if (!commentNode) {
            if (ruleConfig.nonComment) {
              ruleConfig.nonComment({
                node,
                state
              });
            }

            return;
          }

          trackedJsdocs.push(commentNode);
          callIterator(context, node, [commentNode], state);
        },

        'Program:exit'() {
          const allComments = sourceCode.getAllComments();
          const untrackedJSdoc = allComments.filter(node => {
            return !trackedJsdocs.includes(node);
          });
          callIterator(context, null, untrackedJSdoc, state, true);
        }

      };
    },

    meta: ruleConfig.meta
  };
};
/**
 * Create an eslint rule that iterates over all JSDocs, regardless of whether
 * they are attached to a function-like node.
 *
 * @param {JsdocVisitor} iterator
 * @param {{meta: any}} ruleConfig
 */


const checkFile = (iterator, ruleConfig) => {
  return {
    create(context) {
      const sourceCode = context.getSourceCode();
      const settings = getSettings(context);

      if (!settings) {
        return {};
      }

      return {
        'Program:exit'() {
          const allComments = sourceCode.getAllComments();
          const {
            lines
          } = sourceCode;
          const utils = getBasicUtils(context, settings);
          iterator({
            allComments,
            context,
            lines,
            makeReport,
            settings,
            sourceCode,
            utils
          });
        }

      };
    },

    meta: ruleConfig.meta
  };
};

/**
 * @param {JsdocVisitor} iterator
 * @param {{
 *   meta: any,
 *   contextDefaults?: true | string[],
 *   contextSelected?: true,
 *   iterateAllJsdocs?: true,
 * }} ruleConfig
 */
function iterateJsdoc(iterator, ruleConfig) {
  var _ruleConfig$meta;

  const metaType = ruleConfig === null || ruleConfig === void 0 ? void 0 : (_ruleConfig$meta = ruleConfig.meta) === null || _ruleConfig$meta === void 0 ? void 0 : _ruleConfig$meta.type;

  if (!metaType || !['problem', 'suggestion', 'layout'].includes(metaType)) {
    throw new TypeError('Rule must include `meta.type` option (with value "problem", "suggestion", or "layout")');
  }

  if (typeof iterator !== 'function') {
    throw new TypeError('The iterator argument must be a function.');
  }

  if (ruleConfig.checkFile) {
    return checkFile(iterator, ruleConfig);
  }

  if (ruleConfig.iterateAllJsdocs) {
    return iterateAllJsdocs(iterator, ruleConfig);
  }

  return {
    /**
     * The entrypoint for the JSDoc rule.
     *
     * @param {*} context
     *   a reference to the context which hold all important information
     *   like settings and the sourcecode to check.
     * @returns {object}
     *   a list with parser callback function.
     */
    create(context) {
      const settings = getSettings(context);

      if (!settings) {
        return {};
      }

      let contexts;

      if (ruleConfig.contextDefaults || ruleConfig.contextSelected || ruleConfig.matchContext) {
        var _context$options$3, _contexts, _contexts2;

        contexts = ruleConfig.matchContext && (_context$options$3 = context.options[0]) !== null && _context$options$3 !== void 0 && _context$options$3.match ? context.options[0].match : _jsdocUtils.default.enforcedContexts(context, ruleConfig.contextDefaults);

        if (contexts) {
          contexts = contexts.map(obj => {
            if (typeof obj === 'object' && !obj.context) {
              return { ...obj,
                context: 'any'
              };
            }

            return obj;
          });
        }

        const hasPlainAny = (_contexts = contexts) === null || _contexts === void 0 ? void 0 : _contexts.includes('any');
        const hasObjectAny = !hasPlainAny && ((_contexts2 = contexts) === null || _contexts2 === void 0 ? void 0 : _contexts2.find(ctxt => {
          return (ctxt === null || ctxt === void 0 ? void 0 : ctxt.context) === 'any';
        }));

        if (hasPlainAny || hasObjectAny) {
          return iterateAllJsdocs(iterator, ruleConfig, hasObjectAny ? contexts : null, ruleConfig.matchContext).create(context);
        }
      }

      const sourceCode = context.getSourceCode();
      const {
        lines
      } = sourceCode;
      const state = {};

      const checkJsdoc = (info, handler, node) => {
        const jsdocNode = (0, _jsdoccomment.getJSDocComment)(sourceCode, node, settings);

        if (!jsdocNode) {
          return;
        }

        const [indent, jsdoc] = getIndentAndJSDoc(lines, jsdocNode);

        if ( // Note, `handler` should already be bound in its first argument
        //  with these only to be called after the value of
        //  `comment`
        handler && handler(jsdoc) === false) {
          return;
        }

        iterate(info, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state);
      };

      let contextObject = {};

      if (contexts && (ruleConfig.contextDefaults || ruleConfig.contextSelected || ruleConfig.matchContext)) {
        contextObject = _jsdocUtils.default.getContextObject(contexts, checkJsdoc, (0, _jsdoccomment.commentHandler)(settings));
      } else {
        for (const prop of ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression']) {
          contextObject[prop] = checkJsdoc.bind(null, {
            selector: prop
          }, null);
        }
      }

      if (ruleConfig.exit) {
        contextObject['Program:exit'] = () => {
          ruleConfig.exit({
            context,
            state
          });
        };
      }

      return contextObject;
    },

    meta: ruleConfig.meta
  };
}
//# sourceMappingURL=iterateJsdoc.js.map