"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _lodash = _interopRequireDefault(require("lodash"));

var _WarnSettings = _interopRequireDefault(require("./WarnSettings"));

var _getDefaultTagStructureForMode = _interopRequireDefault(require("./getDefaultTagStructureForMode"));

var _tagNames = require("./tagNames");

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

let tagStructure;

const setTagStructure = mode => {
  tagStructure = (0, _getDefaultTagStructureForMode.default)(mode);
}; // Given a nested array of property names, reduce them to a single array,
// appending the name of the root element along the way if present.


const flattenRoots = (params, root = '') => {
  let hasRestElement = false;
  let hasPropertyRest = false;
  const rests = [];
  const names = params.reduce((acc, cur) => {
    if (Array.isArray(cur)) {
      let nms;

      if (Array.isArray(cur[1])) {
        nms = cur[1];
      } else {
        if (cur[1].hasRestElement) {
          hasRestElement = true;
        }

        if (cur[1].hasPropertyRest) {
          hasPropertyRest = true;
        }

        nms = cur[1].names;
      }

      const flattened = flattenRoots(nms, root ? `${root}.${cur[0]}` : cur[0]);

      if (flattened.hasRestElement) {
        hasRestElement = true;
      }

      if (flattened.hasPropertyRest) {
        hasPropertyRest = true;
      }

      const inner = [root ? `${root}.${cur[0]}` : cur[0], ...flattened.names].filter(Boolean);
      rests.push(false, ...flattened.rests);
      return acc.concat(inner);
    }

    if (typeof cur === 'object') {
      if (cur.isRestProperty) {
        hasPropertyRest = true;
        rests.push(true);
      } else {
        rests.push(false);
      }

      if (cur.restElement) {
        hasRestElement = true;
      }

      acc.push(root ? `${root}.${cur.name}` : cur.name);
    } else if (typeof cur !== 'undefined') {
      rests.push(false);
      acc.push(root ? `${root}.${cur}` : cur);
    }

    return acc;
  }, []);
  return {
    hasPropertyRest,
    hasRestElement,
    names,
    rests
  };
};

const getPropertiesFromPropertySignature = propSignature => {
  if (propSignature.type === 'TSIndexSignature' || propSignature.type === 'TSConstructSignatureDeclaration' || propSignature.type === 'TSCallSignatureDeclaration') {
    return undefined;
  }

  if (propSignature.typeAnnotation && propSignature.typeAnnotation.typeAnnotation.type === 'TSTypeLiteral') {
    return [propSignature.key.name, propSignature.typeAnnotation.typeAnnotation.members.map(member => {
      return getPropertiesFromPropertySignature(member);
    })];
  }

  return propSignature.key.name;
};

const getFunctionParameterNames = (functionNode, checkDefaultObjects) => {
  // eslint-disable-next-line complexity
  const getParamName = (param, isProperty) => {
    var _param$left, _param$left3;

    if (_lodash.default.has(param, 'typeAnnotation') || _lodash.default.has(param, 'left.typeAnnotation')) {
      const typeAnnotation = _lodash.default.has(param, 'left.typeAnnotation') ? param.left.typeAnnotation : param.typeAnnotation;

      if (typeAnnotation.typeAnnotation.type === 'TSTypeLiteral') {
        const propertyNames = typeAnnotation.typeAnnotation.members.map(member => {
          return getPropertiesFromPropertySignature(member);
        });
        const flattened = { ...flattenRoots(propertyNames),
          annotationParamName: param.name
        };

        if (_lodash.default.has(param, 'name') || _lodash.default.has(param, 'left.name')) {
          return [_lodash.default.has(param, 'left.name') ? param.left.name : param.name, flattened];
        }

        return [undefined, flattened];
      }
    }

    if (_lodash.default.has(param, 'name')) {
      return param.name;
    }

    if (_lodash.default.has(param, 'left.name')) {
      return param.left.name;
    }

    if (param.type === 'ObjectPattern' || ((_param$left = param.left) === null || _param$left === void 0 ? void 0 : _param$left.type) === 'ObjectPattern') {
      var _param$left2;

      const properties = param.properties || ((_param$left2 = param.left) === null || _param$left2 === void 0 ? void 0 : _param$left2.properties);
      const roots = properties.map(prop => {
        return getParamName(prop, true);
      });
      return [undefined, flattenRoots(roots)];
    }

    if (param.type === 'Property') {
      switch (param.value.type) {
        case 'ArrayPattern':
          return [param.key.name, param.value.elements.map((prop, idx) => {
            return {
              name: idx,
              restElement: prop.type === 'RestElement'
            };
          })];

        case 'ObjectPattern':
          return [param.key.name, param.value.properties.map(prop => {
            return getParamName(prop, isProperty);
          })];

        case 'AssignmentPattern':
          {
            switch (param.value.left.type) {
              case 'Identifier':
                // Default parameter
                if (checkDefaultObjects && param.value.right.type === 'ObjectExpression') {
                  return [param.key.name, param.value.right.properties.map(prop => {
                    return getParamName(prop, isProperty);
                  })];
                }

                break;

              case 'ObjectPattern':
                return [param.key.name, param.value.left.properties.map(prop => {
                  return getParamName(prop, isProperty);
                })];

              case 'ArrayPattern':
                return [param.key.name, param.value.left.elements.map((prop, idx) => {
                  return {
                    name: idx,
                    restElement: prop.type === 'RestElement'
                  };
                })];
            }
          }
      }

      switch (param.key.type) {
        case 'Identifier':
          return param.key.name;
        // The key of an object could also be a string or number

        case 'Literal':
          return param.key.raw || // istanbul ignore next -- `raw` may not be present in all parsers
          param.key.value;
        // case 'MemberExpression':

        default:
          // Todo: We should really create a structure (and a corresponding
          //   option analogous to `checkRestProperty`) which allows for
          //   (and optionally requires) dynamic properties to have a single
          //   line of documentation
          return undefined;
      }
    }

    if (param.type === 'ArrayPattern' || ((_param$left3 = param.left) === null || _param$left3 === void 0 ? void 0 : _param$left3.type) === 'ArrayPattern') {
      var _param$left4;

      const elements = param.elements || ((_param$left4 = param.left) === null || _param$left4 === void 0 ? void 0 : _param$left4.elements);
      const roots = elements.map((prop, idx) => {
        return {
          name: `"${idx}"`,
          restElement: (prop === null || prop === void 0 ? void 0 : prop.type) === 'RestElement'
        };
      });
      return [undefined, flattenRoots(roots)];
    }

    if (['RestElement', 'ExperimentalRestProperty'].includes(param.type)) {
      return {
        isRestProperty: isProperty,
        name: param.argument.name,
        restElement: true
      };
    }

    if (param.type === 'TSParameterProperty') {
      return getParamName(param.parameter, true);
    }

    throw new Error(`Unsupported function signature format: \`${param.type}\`.`);
  };

  return (functionNode.params || functionNode.value.params).map(param => {
    return getParamName(param);
  });
};

const hasParams = functionNode => {
  // Should also check `functionNode.value.params` if supporting `MethodDefinition`
  return functionNode.params.length;
};
/**
 * Gets all names of the target type, including those that refer to a path, e.g.
 * "@param foo; @param foo.bar".
 */


const getJsdocTagsDeep = (jsdoc, targetTagName) => {
  const ret = [];
  jsdoc.tags.forEach(({
    name,
    tag,
    type
  }, idx) => {
    if (tag !== targetTagName) {
      return;
    }

    ret.push({
      idx,
      name,
      type
    });
  });
  return ret;
};

const modeWarnSettings = (0, _WarnSettings.default)();

const getTagNamesForMode = (mode, context) => {
  switch (mode) {
    case 'jsdoc':
      return _tagNames.jsdocTags;

    case 'typescript':
      return _tagNames.typeScriptTags;

    case 'closure':
    case 'permissive':
      return _tagNames.closureTags;

    default:
      if (!modeWarnSettings.hasBeenWarned(context, 'mode')) {
        context.report({
          loc: {
            start: {
              column: 1,
              line: 1
            }
          },
          message: `Unrecognized value \`${mode}\` for \`settings.jsdoc.mode\`.`
        });
        modeWarnSettings.markSettingAsWarned(context, 'mode');
      } // We'll avoid breaking too many other rules


      return _tagNames.jsdocTags;
  }
};

const getPreferredTagName = (context, mode, name, tagPreference = {}) => {
  var _Object$entries$find;

  const prefValues = Object.values(tagPreference);

  if (prefValues.includes(name) || prefValues.some(prefVal => {
    return prefVal && typeof prefVal === 'object' && prefVal.replacement === name;
  })) {
    return name;
  } // Allow keys to have a 'tag ' prefix to avoid upstream bug in ESLint
  // that disallows keys that conflict with Object.prototype,
  // e.g. 'tag constructor' for 'constructor':
  // https://github.com/eslint/eslint/issues/13289
  // https://github.com/gajus/eslint-plugin-jsdoc/issues/537


  const tagPreferenceFixed = _lodash.default.mapKeys(tagPreference, (_value, key) => {
    return key.replace('tag ', '');
  });

  if (_lodash.default.has(tagPreferenceFixed, name)) {
    return tagPreferenceFixed[name];
  }

  const tagNames = getTagNamesForMode(mode, context);
  const preferredTagName = (_Object$entries$find = Object.entries(tagNames).find(([, aliases]) => {
    return aliases.includes(name);
  })) === null || _Object$entries$find === void 0 ? void 0 : _Object$entries$find[0];

  if (preferredTagName) {
    return preferredTagName;
  }

  return name;
};

const isValidTag = (context, mode, name, definedTags) => {
  const tagNames = getTagNamesForMode(mode, context);
  const validTagNames = Object.keys(tagNames).concat(Object.values(tagNames).flat());
  const additionalTags = definedTags;
  const allTags = validTagNames.concat(additionalTags);
  return allTags.includes(name);
};

const hasTag = (jsdoc, targetTagName) => {
  const targetTagLower = targetTagName.toLowerCase();
  return _lodash.default.some(jsdoc.tags, doc => {
    return doc.tag.toLowerCase() === targetTagLower;
  });
};

const hasATag = (jsdoc, targetTagNames) => {
  return targetTagNames.some(targetTagName => {
    return hasTag(jsdoc, targetTagName);
  });
};
/**
 * Checks if the JSDoc comment declares a defined type.
 *
 * @param {JsDocTag} tag
 *   the tag which should be checked.
 * @returns {boolean}
 *   true in case a defined type is declared; otherwise false.
 */


const hasDefinedTypeTag = tag => {
  // The function should not continue in the event the type is not defined...
  if (typeof tag === 'undefined' || tag === null) {
    return false;
  } // .. same applies if it declares an `{undefined}` or `{void}` type


  const tagType = tag.type.trim();

  if (tagType === 'undefined' || tagType === 'void') {
    return false;
  } // In any other case, a type is present


  return true;
};

const ensureMap = (map, tag) => {
  if (!map.has(tag)) {
    map.set(tag, new Map());
  }

  return map.get(tag);
};

const overrideTagStructure = (structuredTags, tagMap = tagStructure) => {
  Object.entries(structuredTags).forEach(([tag, {
    name,
    type,
    required = []
  }]) => {
    const tagStruct = ensureMap(tagMap, tag);
    tagStruct.set('nameContents', name);
    tagStruct.set('typeAllowed', type);
    const requiredName = required.includes('name');

    if (requiredName && name === false) {
      throw new Error('Cannot add "name" to `require` with the tag\'s `name` set to `false`');
    }

    tagStruct.set('nameRequired', requiredName);
    const requiredType = required.includes('type');

    if (requiredType && type === false) {
      throw new Error('Cannot add "type" to `require` with the tag\'s `type` set to `false`');
    }

    tagStruct.set('typeRequired', requiredType);
    const typeOrNameRequired = required.includes('typeOrNameRequired');

    if (typeOrNameRequired && name === false) {
      throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `name` set to `false`');
    }

    if (typeOrNameRequired && type === false) {
      throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `type` set to `false`');
    }

    tagStruct.set('typeOrNameRequired', typeOrNameRequired);
  });
};

const getTagStructureForMode = (mode, structuredTags) => {
  const tagStruct = (0, _getDefaultTagStructureForMode.default)(mode);

  try {
    overrideTagStructure(structuredTags, tagStruct);
  } catch {//
  }

  return tagStruct;
};

const isNamepathDefiningTag = (tag, tagMap = tagStructure) => {
  const tagStruct = ensureMap(tagMap, tag);
  return tagStruct.get('nameContents') === 'namepath-defining';
};

const tagMustHaveTypePosition = (tag, tagMap = tagStructure) => {
  const tagStruct = ensureMap(tagMap, tag);
  return tagStruct.get('typeRequired');
};

const tagMightHaveTypePosition = (tag, tagMap = tagStructure) => {
  if (tagMustHaveTypePosition(tag, tagMap)) {
    return true;
  }

  const tagStruct = ensureMap(tagMap, tag);
  const ret = tagStruct.get('typeAllowed');
  return ret === undefined ? true : ret;
};

const namepathTypes = new Set(['namepath-defining', 'namepath-referencing']);

const tagMightHaveNamePosition = (tag, tagMap = tagStructure) => {
  const tagStruct = ensureMap(tagMap, tag);
  const ret = tagStruct.get('nameContents');
  return ret === undefined ? true : Boolean(ret);
};

const tagMightHaveNamepath = (tag, tagMap = tagStructure) => {
  const tagStruct = ensureMap(tagMap, tag);
  return namepathTypes.has(tagStruct.get('nameContents'));
};

const tagMustHaveNamePosition = (tag, tagMap = tagStructure) => {
  const tagStruct = ensureMap(tagMap, tag);
  return tagStruct.get('nameRequired');
};

const tagMightHaveEitherTypeOrNamePosition = (tag, tagMap) => {
  return tagMightHaveTypePosition(tag, tagMap) || tagMightHaveNamepath(tag, tagMap);
};

const tagMustHaveEitherTypeOrNamePosition = (tag, tagMap) => {
  const tagStruct = ensureMap(tagMap, tag);
  return tagStruct.get('typeOrNameRequired');
};

const tagMissingRequiredTypeOrNamepath = (tag, tagMap = tagStructure) => {
  const mustHaveTypePosition = tagMustHaveTypePosition(tag.tag, tagMap);
  const mightHaveTypePosition = tagMightHaveTypePosition(tag.tag, tagMap);
  const hasTypePosition = mightHaveTypePosition && Boolean(tag.type);
  const hasNameOrNamepathPosition = (tagMustHaveNamePosition(tag.tag, tagMap) || tagMightHaveNamepath(tag.tag, tagMap)) && Boolean(tag.name);
  const mustHaveEither = tagMustHaveEitherTypeOrNamePosition(tag.tag, tagMap);
  const hasEither = tagMightHaveEitherTypeOrNamePosition(tag.tag, tagMap) && (hasTypePosition || hasNameOrNamepathPosition);
  return mustHaveEither && !hasEither && !mustHaveTypePosition;
};
/**
 * Checks if a node is a promise but has no resolve value or an empty value.
 * An `undefined` resolve does not count.
 *
 * @param {object} node
 * @returns {boolean}
 */


const isNewPromiseExpression = node => {
  return node.type === 'NewExpression' && node.callee.type === 'Identifier' && node.callee.name === 'Promise';
};
/**
 * @callback PromiseFilter
 * @param {object} node
 * @returns {boolean}
 */

/**
 * Checks if a node has a return statement. Void return does not count.
 *
 * @param {object} node
 * @param {PromiseFilter} promFilter
 * @returns {boolean|Node}
 */
// eslint-disable-next-line complexity


const hasReturnValue = (node, promFilter) => {
  var _node$returnType, _node$returnType$type;

  if (!node) {
    return false;
  }

  switch (node.type) {
    case 'TSFunctionType':
    case 'TSMethodSignature':
      return !['TSVoidKeyword', 'TSUndefinedKeyword'].includes(node === null || node === void 0 ? void 0 : (_node$returnType = node.returnType) === null || _node$returnType === void 0 ? void 0 : (_node$returnType$type = _node$returnType.typeAnnotation) === null || _node$returnType$type === void 0 ? void 0 : _node$returnType$type.type);

    case 'MethodDefinition':
      return hasReturnValue(node.value, promFilter);

    case 'FunctionExpression':
    case 'FunctionDeclaration':
    case 'ArrowFunctionExpression':
      {
        return node.expression || hasReturnValue(node.body, promFilter);
      }

    case 'BlockStatement':
      {
        return node.body.some(bodyNode => {
          return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, promFilter);
        });
      }

    case 'LabeledStatement':
    case 'WhileStatement':
    case 'DoWhileStatement':
    case 'ForStatement':
    case 'ForInStatement':
    case 'ForOfStatement':
    case 'WithStatement':
      {
        return hasReturnValue(node.body, promFilter);
      }

    case 'IfStatement':
      {
        return hasReturnValue(node.consequent, promFilter) || hasReturnValue(node.alternate, promFilter);
      }

    case 'TryStatement':
      {
        return hasReturnValue(node.block, promFilter) || hasReturnValue(node.handler && node.handler.body, promFilter) || hasReturnValue(node.finalizer, promFilter);
      }

    case 'SwitchStatement':
      {
        return node.cases.some(someCase => {
          return someCase.consequent.some(nde => {
            return hasReturnValue(nde, promFilter);
          });
        });
      }

    case 'ReturnStatement':
      {
        // void return does not count.
        if (node.argument === null) {
          return false;
        }

        if (promFilter && isNewPromiseExpression(node.argument)) {
          // Let caller decide how to filter, but this is, at the least,
          //   a return of sorts and truthy
          return promFilter(node.argument);
        }

        return true;
      }

    default:
      {
        return false;
      }
  }
};
/**
 * Avoids further checking child nodes if a nested function shadows the
 * resolver, but otherwise, if name is used (by call or passed in as an
 * argument to another function), will be considered as non-empty.
 *
 * This could check for redeclaration of the resolver, but as such is
 * unlikely, we avoid the performance cost of checking everywhere for
 * (re)declarations or assignments.
 *
 * @param {AST} node
 * @param {string} resolverName
 * @returns {boolean}
 */
// eslint-disable-next-line complexity


const hasNonEmptyResolverCall = (node, resolverName) => {
  if (!node) {
    return false;
  } // Arrow function without block


  switch (node.type) {
    // istanbul ignore next -- In Babel?
    case 'OptionalCallExpression':
    case 'CallExpression':
      return node.callee.name === resolverName && ( // Implicit or expliit undefined
      node.arguments.length > 1 || node.arguments[0] !== undefined) || node.arguments.some(nde => {
        // Being passed in to another function (which might invoke it)
        return nde.type === 'Identifier' && nde.name === resolverName || // Handle nested items
        hasNonEmptyResolverCall(nde, resolverName);
      });

    case 'ChainExpression':
    case 'Decorator':
    case 'ExpressionStatement':
      return hasNonEmptyResolverCall(node.expression, resolverName);

    case 'ClassBody':
    case 'BlockStatement':
      return node.body.some(bodyNode => {
        return hasNonEmptyResolverCall(bodyNode, resolverName);
      });

    case 'FunctionExpression':
    case 'FunctionDeclaration':
    case 'ArrowFunctionExpression':
      {
        var _node$params$;

        // Shadowing
        if (((_node$params$ = node.params[0]) === null || _node$params$ === void 0 ? void 0 : _node$params$.name) === resolverName) {
          return false;
        }

        return hasNonEmptyResolverCall(node.body, resolverName);
      }

    case 'LabeledStatement':
    case 'WhileStatement':
    case 'DoWhileStatement':
    case 'ForStatement':
    case 'ForInStatement':
    case 'ForOfStatement':
    case 'WithStatement':
      {
        return hasNonEmptyResolverCall(node.body, resolverName);
      }

    case 'ConditionalExpression':
    case 'IfStatement':
      {
        return hasNonEmptyResolverCall(node.test, resolverName) || hasNonEmptyResolverCall(node.consequent, resolverName) || hasNonEmptyResolverCall(node.alternate, resolverName);
      }

    case 'TryStatement':
      {
        return hasNonEmptyResolverCall(node.block, resolverName) || hasNonEmptyResolverCall(node.handler && node.handler.body, resolverName) || hasNonEmptyResolverCall(node.finalizer, resolverName);
      }

    case 'SwitchStatement':
      {
        return node.cases.some(someCase => {
          return someCase.consequent.some(nde => {
            return hasNonEmptyResolverCall(nde, resolverName);
          });
        });
      }

    case 'ArrayPattern':
    case 'ArrayExpression':
      return node.elements.some(element => {
        return hasNonEmptyResolverCall(element, resolverName);
      });

    case 'AssignmentPattern':
      return hasNonEmptyResolverCall(node.right, resolverName);

    case 'AssignmentExpression':
    case 'BinaryExpression':
    case 'LogicalExpression':
      {
        return hasNonEmptyResolverCall(node.left, resolverName) || hasNonEmptyResolverCall(node.right, resolverName);
      }
    // Comma

    case 'SequenceExpression':
    case 'TemplateLiteral':
      return node.expressions.some(subExpression => {
        return hasNonEmptyResolverCall(subExpression, resolverName);
      });

    case 'ObjectPattern':
    case 'ObjectExpression':
      return node.properties.some(property => {
        return hasNonEmptyResolverCall(property, resolverName);
      });
    // istanbul ignore next -- In Babel?

    case 'ClassMethod':
    case 'MethodDefinition':
      return node.decorators && node.decorators.some(decorator => {
        return hasNonEmptyResolverCall(decorator, resolverName);
      }) || node.computed && hasNonEmptyResolverCall(node.key, resolverName) || hasNonEmptyResolverCall(node.value, resolverName);
    // istanbul ignore next -- In Babel?

    case 'ObjectProperty':
    /* eslint-disable no-fallthrough */
    // istanbul ignore next -- In Babel?

    case 'ClassProperty':
    /* eslint-enable no-fallthrough */

    case 'Property':
      return node.computed && hasNonEmptyResolverCall(node.key, resolverName) || hasNonEmptyResolverCall(node.value, resolverName);
    // istanbul ignore next -- In Babel?

    case 'ObjectMethod':
      // istanbul ignore next -- In Babel?
      return node.computed && hasNonEmptyResolverCall(node.key, resolverName) || node.arguments.some(nde => {
        return hasNonEmptyResolverCall(nde, resolverName);
      });

    case 'ClassExpression':
    case 'ClassDeclaration':
      return hasNonEmptyResolverCall(node.body, resolverName);

    case 'AwaitExpression':
    case 'SpreadElement':
    case 'UnaryExpression':
    case 'YieldExpression':
      return hasNonEmptyResolverCall(node.argument, resolverName);

    case 'VariableDeclaration':
      {
        return node.declarations.some(nde => {
          return hasNonEmptyResolverCall(nde, resolverName);
        });
      }

    case 'VariableDeclarator':
      {
        return hasNonEmptyResolverCall(node.id, resolverName) || hasNonEmptyResolverCall(node.init, resolverName);
      }

    case 'TaggedTemplateExpression':
      return hasNonEmptyResolverCall(node.quasi, resolverName);
    // ?.
    // istanbul ignore next -- In Babel?

    case 'OptionalMemberExpression':
    case 'MemberExpression':
      return hasNonEmptyResolverCall(node.object, resolverName) || hasNonEmptyResolverCall(node.property, resolverName);
    // istanbul ignore next -- In Babel?

    case 'Import':
    case 'ImportExpression':
      return hasNonEmptyResolverCall(node.source, resolverName);

    case 'ReturnStatement':
      {
        if (node.argument === null) {
          return false;
        }

        return hasNonEmptyResolverCall(node.argument, resolverName);
      }

    /*
    // Shouldn't need to parse literals/literal components, etc.
     case 'Identifier':
    case 'TemplateElement':
    case 'Super':
    // Exports not relevant in this context
    */

    default:
      return false;
  }
};
/**
 * Checks if a Promise executor has no resolve value or an empty value.
 * An `undefined` resolve does not count.
 *
 * @param {object} node
 * @param {boolean} anyPromiseAsReturn
 * @returns {boolean}
 */


const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn) => {
  return hasReturnValue(node, prom => {
    if (anyPromiseAsReturn) {
      return true;
    }

    const [{
      params,
      body
    } = {}] = prom.arguments;

    if (!(params !== null && params !== void 0 && params.length)) {
      return false;
    }

    const [{
      name: resolverName
    }] = params;
    return hasNonEmptyResolverCall(body, resolverName);
  });
}; // eslint-disable-next-line complexity


const hasNonFunctionYield = (node, checkYieldReturnValue) => {
  if (!node) {
    return false;
  }

  switch (node.type) {
    case 'BlockStatement':
      {
        return node.body.some(bodyNode => {
          return !['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(bodyNode.type) && hasNonFunctionYield(bodyNode, checkYieldReturnValue);
        });
      }
    // istanbul ignore next -- In Babel?

    case 'OptionalCallExpression':
    case 'CallExpression':
      return node.arguments.some(element => {
        return hasNonFunctionYield(element, checkYieldReturnValue);
      });

    case 'ChainExpression':
    case 'ExpressionStatement':
      {
        return hasNonFunctionYield(node.expression, checkYieldReturnValue);
      }

    case 'LabeledStatement':
    case 'WhileStatement':
    case 'DoWhileStatement':
    case 'ForStatement':
    case 'ForInStatement':
    case 'ForOfStatement':
    case 'WithStatement':
      {
        return hasNonFunctionYield(node.body, checkYieldReturnValue);
      }

    case 'ConditionalExpression':
    case 'IfStatement':
      {
        return hasNonFunctionYield(node.test, checkYieldReturnValue) || hasNonFunctionYield(node.consequent, checkYieldReturnValue) || hasNonFunctionYield(node.alternate, checkYieldReturnValue);
      }

    case 'TryStatement':
      {
        return hasNonFunctionYield(node.block, checkYieldReturnValue) || hasNonFunctionYield(node.handler && node.handler.body, checkYieldReturnValue) || hasNonFunctionYield(node.finalizer, checkYieldReturnValue);
      }

    case 'SwitchStatement':
      {
        return node.cases.some(someCase => {
          return someCase.consequent.some(nde => {
            return hasNonFunctionYield(nde, checkYieldReturnValue);
          });
        });
      }

    case 'ArrayPattern':
    case 'ArrayExpression':
      return node.elements.some(element => {
        return hasNonFunctionYield(element, checkYieldReturnValue);
      });

    case 'AssignmentPattern':
      return hasNonFunctionYield(node.right, checkYieldReturnValue);

    case 'VariableDeclaration':
      {
        return node.declarations.some(nde => {
          return hasNonFunctionYield(nde, checkYieldReturnValue);
        });
      }

    case 'VariableDeclarator':
      {
        return hasNonFunctionYield(node.id, checkYieldReturnValue) || hasNonFunctionYield(node.init, checkYieldReturnValue);
      }

    case 'AssignmentExpression':
    case 'BinaryExpression':
    case 'LogicalExpression':
      {
        return hasNonFunctionYield(node.left, checkYieldReturnValue) || hasNonFunctionYield(node.right, checkYieldReturnValue);
      }
    // Comma

    case 'SequenceExpression':
    case 'TemplateLiteral':
      return node.expressions.some(subExpression => {
        return hasNonFunctionYield(subExpression, checkYieldReturnValue);
      });

    case 'ObjectPattern':
    case 'ObjectExpression':
      return node.properties.some(property => {
        return hasNonFunctionYield(property, checkYieldReturnValue);
      });
    // istanbul ignore next -- In Babel?

    case 'ObjectProperty':
    /* eslint-disable no-fallthrough */
    // istanbul ignore next -- In Babel?

    case 'ClassProperty':
    /* eslint-enable no-fallthrough */

    case 'Property':
      return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) || hasNonFunctionYield(node.value, checkYieldReturnValue);
    // istanbul ignore next -- In Babel?

    case 'ObjectMethod':
      // istanbul ignore next -- In Babel?
      return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) || node.arguments.some(nde => {
        return hasNonFunctionYield(nde, checkYieldReturnValue);
      });

    case 'SpreadElement':
    case 'UnaryExpression':
      return hasNonFunctionYield(node.argument, checkYieldReturnValue);

    case 'TaggedTemplateExpression':
      return hasNonFunctionYield(node.quasi, checkYieldReturnValue);
    // ?.
    // istanbul ignore next -- In Babel?

    case 'OptionalMemberExpression':
    case 'MemberExpression':
      return hasNonFunctionYield(node.object, checkYieldReturnValue) || hasNonFunctionYield(node.property, checkYieldReturnValue);
    // istanbul ignore next -- In Babel?

    case 'Import':
    case 'ImportExpression':
      return hasNonFunctionYield(node.source, checkYieldReturnValue);

    case 'ReturnStatement':
      {
        if (node.argument === null) {
          return false;
        }

        return hasNonFunctionYield(node.argument, checkYieldReturnValue);
      }

    case 'YieldExpression':
      {
        if (checkYieldReturnValue) {
          if (node.parent.type === 'VariableDeclarator') {
            return true;
          }

          return false;
        } // void return does not count.


        if (node.argument === null) {
          return false;
        }

        return true;
      }

    default:
      {
        return false;
      }
  }
};
/**
 * Checks if a node has a return statement. Void return does not count.
 *
 * @param {object} node
 * @returns {boolean}
 */


const hasYieldValue = (node, checkYieldReturnValue) => {
  return node.generator && (node.expression || hasNonFunctionYield(node.body, checkYieldReturnValue));
};
/**
 * Checks if a node has a throws statement.
 *
 * @param {object} node
 * @param {boolean} innerFunction
 * @returns {boolean}
 */
// eslint-disable-next-line complexity


const hasThrowValue = (node, innerFunction) => {
  if (!node) {
    return false;
  } // There are cases where a function may execute its inner function which
  //   throws, but we're treating functions atomically rather than trying to
  //   follow them


  switch (node.type) {
    case 'FunctionExpression':
    case 'FunctionDeclaration':
    case 'ArrowFunctionExpression':
      {
        return !innerFunction && !node.async && hasThrowValue(node.body, true);
      }

    case 'BlockStatement':
      {
        return node.body.some(bodyNode => {
          return bodyNode.type !== 'FunctionDeclaration' && hasThrowValue(bodyNode);
        });
      }

    case 'LabeledStatement':
    case 'WhileStatement':
    case 'DoWhileStatement':
    case 'ForStatement':
    case 'ForInStatement':
    case 'ForOfStatement':
    case 'WithStatement':
      {
        return hasThrowValue(node.body);
      }

    case 'IfStatement':
      {
        return hasThrowValue(node.consequent) || hasThrowValue(node.alternate);
      }
    // We only consider it to throw an error if the catch or finally blocks throw an error.

    case 'TryStatement':
      {
        return hasThrowValue(node.handler && node.handler.body) || hasThrowValue(node.finalizer);
      }

    case 'SwitchStatement':
      {
        return node.cases.some(someCase => {
          return someCase.consequent.some(nde => {
            return hasThrowValue(nde);
          });
        });
      }

    case 'ThrowStatement':
      {
        return true;
      }

    default:
      {
        return false;
      }
  }
};
/** @param {string} tag */

/*
const isInlineTag = (tag) => {
  return /^(@link|@linkcode|@linkplain|@tutorial) /u.test(tag);
};
*/

/**
 * Parses GCC Generic/Template types
 *
 * @see {https://github.com/google/closure-compiler/wiki/Generic-Types}
 * @see {https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template}
 * @param {JsDocTag} tag
 * @returns {Array<string>}
 */


const parseClosureTemplateTag = tag => {
  return tag.name.split(',').map(type => {
    return type.trim();
  });
};
/**
 * Checks user option for `contexts` array, defaulting to
 *   contexts designated by the rule. Returns an array of
 *   ESTree AST types, indicating allowable contexts.
 *
 * @param {*} context
 * @param {true|string[]} defaultContexts
 * @returns {string[]}
 */


const enforcedContexts = (context, defaultContexts) => {
  const {
    contexts = defaultContexts === true ? ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'] : defaultContexts
  } = context.options[0] || {};
  return contexts;
};
/**
 * @param {string[]} contexts
 * @param {Function} checkJsdoc
 * @param {Function} handler
 */


const getContextObject = (contexts, checkJsdoc, handler) => {
  const properties = {};
  contexts.forEach((prop, idx) => {
    if (typeof prop === 'object') {
      const selInfo = {
        lastIndex: idx,
        selector: prop.context
      };

      if (prop.comment) {
        properties[prop.context] = checkJsdoc.bind(null, { ...selInfo,
          comment: prop.comment
        }, handler.bind(null, prop.comment));
      } else {
        properties[prop.context] = checkJsdoc.bind(null, selInfo, null);
      }
    } else {
      const selInfo = {
        lastIndex: idx,
        selector: prop
      };
      properties[prop] = checkJsdoc.bind(null, selInfo, null);
    }
  });
  return properties;
};

const filterTags = (tags, filter) => {
  return tags.filter(filter);
};

const tagsWithNamesAndDescriptions = new Set(['param', 'arg', 'argument', 'property', 'prop', 'template', // These two are parsed by our custom parser as though having a `name`
'returns', 'return']);

const getTagsByType = (context, mode, tags, tagPreference) => {
  const descName = getPreferredTagName(context, mode, 'description', tagPreference);
  const tagsWithoutNames = [];
  const tagsWithNames = filterTags(tags, tag => {
    const {
      tag: tagName
    } = tag;
    const tagWithName = tagsWithNamesAndDescriptions.has(tagName);

    if (!tagWithName && tagName !== descName) {
      tagsWithoutNames.push(tag);
    }

    return tagWithName;
  });
  return {
    tagsWithNames,
    tagsWithoutNames
  };
};

const getIndent = sourceCode => {
  var _sourceCode$text$matc, _sourceCode$text$matc2;

  return ((_sourceCode$text$matc = (_sourceCode$text$matc2 = sourceCode.text.match(/^\n*([ \t]+)/u)) === null || _sourceCode$text$matc2 === void 0 ? void 0 : _sourceCode$text$matc2[1]) !== null && _sourceCode$text$matc !== void 0 ? _sourceCode$text$matc : '') + ' ';
};

const isConstructor = node => {
  var _node$parent;

  return (node === null || node === void 0 ? void 0 : node.type) === 'MethodDefinition' && node.kind === 'constructor' || (node === null || node === void 0 ? void 0 : (_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.kind) === 'constructor';
};

const isGetter = node => {
  return node && node.parent.kind === 'get';
};

const isSetter = node => {
  return node && node.parent.kind === 'set';
};

const hasAccessorPair = node => {
  const {
    type,
    kind: sourceKind,
    key: {
      name: sourceName
    }
  } = node;
  const oppositeKind = sourceKind === 'get' ? 'set' : 'get';
  const children = type === 'MethodDefinition' ? 'body' : 'properties';
  return node.parent[children].some(({
    kind,
    key: {
      name
    }
  }) => {
    return kind === oppositeKind && name === sourceName;
  });
};

const exemptSpeciaMethods = (jsdoc, node, context, schema) => {
  const hasSchemaOption = prop => {
    var _context$options$0$pr, _context$options$;

    const schemaProperties = schema[0].properties;
    return (_context$options$0$pr = (_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$[prop]) !== null && _context$options$0$pr !== void 0 ? _context$options$0$pr : schemaProperties[prop] && schemaProperties[prop].default;
  };

  const checkGetters = hasSchemaOption('checkGetters');
  const checkSetters = hasSchemaOption('checkSetters');
  return !hasSchemaOption('checkConstructors') && (isConstructor(node) || hasATag(jsdoc, ['class', 'constructor'])) || isGetter(node) && (!checkGetters || checkGetters === 'no-setter' && hasAccessorPair(node.parent)) || isSetter(node) && (!checkSetters || checkSetters === 'no-getter' && hasAccessorPair(node.parent));
};
/**
 * Since path segments may be unquoted (if matching a reserved word,
 * identifier or numeric literal) or single or double quoted, in either
 * the `@param` or in source, we need to strip the quotes to give a fair
 * comparison.
 *
 * @param {string} str
 * @returns {string}
 */


const dropPathSegmentQuotes = str => {
  return str.replace(/\.(['"])(.*)\1/gu, '.$2');
};

const comparePaths = name => {
  return otherPathName => {
    return otherPathName === name || dropPathSegmentQuotes(otherPathName) === dropPathSegmentQuotes(name);
  };
};

const pathDoesNotBeginWith = (name, otherPathName) => {
  return !name.startsWith(otherPathName) && !dropPathSegmentQuotes(name).startsWith(dropPathSegmentQuotes(otherPathName));
};

const getRegexFromString = (regexString, requiredFlags) => {
  const match = regexString.match(/^\/(.*)\/([gimyus]*)$/us);
  let flags = 'u';
  let regex = regexString;

  if (match) {
    [, regex, flags] = match;

    if (!flags) {
      flags = 'u';
    }
  }

  const uniqueFlags = [...new Set(flags + (requiredFlags || ''))];
  flags = uniqueFlags.join('');
  return new RegExp(regex, flags);
};

var _default = {
  comparePaths,
  dropPathSegmentQuotes,
  enforcedContexts,
  exemptSpeciaMethods,
  filterTags,
  flattenRoots,
  getContextObject,
  getFunctionParameterNames,
  getIndent,
  getJsdocTagsDeep,
  getPreferredTagName,
  getRegexFromString,
  getTagsByType,
  getTagStructureForMode,
  hasATag,
  hasDefinedTypeTag,
  hasParams,
  hasReturnValue,
  hasTag,
  hasThrowValue,
  hasValueOrExecutorHasNonEmptyResolveValue,
  hasYieldValue,
  isConstructor,
  isGetter,
  isNamepathDefiningTag,
  isSetter,
  isValidTag,
  overrideTagStructure,
  parseClosureTemplateTag,
  pathDoesNotBeginWith,
  setTagStructure,
  tagMightHaveNamepath,
  tagMightHaveNamePosition,
  tagMightHaveTypePosition,
  tagMissingRequiredTypeOrNamepath,
  tagMustHaveNamePosition,
  tagMustHaveTypePosition
};
exports.default = _default;
module.exports = exports.default;
//# sourceMappingURL=jsdocUtils.js.map