"use strict";

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

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

var _jsdoctypeparser = require("jsdoctypeparser");

var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc"));

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

function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }

function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }

function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }

function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }

const strictNativeTypes = ['undefined', 'null', 'boolean', 'number', 'bigint', 'string', 'symbol', 'object', 'Array', 'Function', 'Date', 'RegExp'];

const adjustNames = (type, preferred, isGenericMatch, nodeName, node, parentNode) => {
  let ret = preferred;

  if (isGenericMatch) {
    if (preferred === '[]') {
      parentNode.meta.syntax = 'SQUARE_BRACKET';
      ret = 'Array';
    } else {
      const dotBracketEnd = preferred.match(/\.(?:<>)?$/u);

      if (dotBracketEnd) {
        parentNode.meta.syntax = 'ANGLE_BRACKET_WITH_DOT';
        ret = preferred.slice(0, -dotBracketEnd[0].length);
      } else {
        const bracketEnd = preferred.endsWith('<>');

        if (bracketEnd) {
          parentNode.meta.syntax = 'ANGLE_BRACKET';
          ret = preferred.slice(0, -2);
        }
      }
    }
  } else if (type === 'ANY') {
    node.type = 'NAME';
  }

  node.name = ret.replace(/(?:\.|<>|\.<>|\[\])$/u, ''); // For bare pseudo-types like `<>`

  if (!ret) {
    node.name = nodeName;
  }
};

var _default = (0, _iterateJsdoc.default)(({
  jsdocNode,
  sourceCode,
  report,
  utils,
  settings,
  context
}) => {
  const jsdocTagsWithPossibleType = utils.filterTags(tag => {
    return utils.tagMightHaveTypePosition(tag.tag);
  });
  const preferredTypes = settings.preferredTypes;

  const _ref = context.options[0] || {},
        noDefaults = _ref.noDefaults,
        unifyParentAndChildTypeChecks = _ref.unifyParentAndChildTypeChecks,
        _ref$exemptTagContext = _ref.exemptTagContexts,
        exemptTagContexts = _ref$exemptTagContext === void 0 ? [] : _ref$exemptTagContext;

  const getPreferredTypeInfo = (type, nodeName, parentName, parentNode) => {
    let hasMatchingPreferredType;
    let isGenericMatch;
    let typeName = nodeName;

    if (Object.keys(preferredTypes).length) {
      const parentType = parentName === 'subject';

      if (unifyParentAndChildTypeChecks || parentType) {
        const syntax = _lodash.default.get(parentNode, 'meta.syntax');

        [['.', 'ANGLE_BRACKET_WITH_DOT'], ['.<>', 'ANGLE_BRACKET_WITH_DOT'], ['<>', 'ANGLE_BRACKET']].some(([checkPostFix, syn]) => {
          isGenericMatch = _lodash.default.get(preferredTypes, nodeName + checkPostFix) !== undefined && syntax === syn;

          if (isGenericMatch) {
            typeName += checkPostFix;
          }

          return isGenericMatch;
        });

        if (!isGenericMatch && parentType) {
          [['[]', 'SQUARE_BRACKET'], ['.', 'ANGLE_BRACKET_WITH_DOT'], ['.<>', 'ANGLE_BRACKET_WITH_DOT'], ['<>', 'ANGLE_BRACKET']].some(([checkPostFix, syn]) => {
            isGenericMatch = _lodash.default.get(preferredTypes, checkPostFix) !== undefined && syntax === syn;

            if (isGenericMatch) {
              typeName = checkPostFix;
            }

            return isGenericMatch;
          });
        }
      }

      const directNameMatch = _lodash.default.get(preferredTypes, nodeName) !== undefined;
      const unifiedSyntaxParentMatch = parentType && directNameMatch && unifyParentAndChildTypeChecks;
      isGenericMatch = isGenericMatch || unifiedSyntaxParentMatch;
      hasMatchingPreferredType = isGenericMatch || directNameMatch && !parentType;
    }

    return [hasMatchingPreferredType, typeName, isGenericMatch];
  };

  jsdocTagsWithPossibleType.forEach(jsdocTag => {
    const invalidTypes = [];
    let typeAst;

    try {
      typeAst = (0, _jsdoctypeparser.parse)(jsdocTag.type);
    } catch (error) {
      return;
    }

    (0, _jsdoctypeparser.traverse)(typeAst, (node, parentName, parentNode) => {
      const type = node.type,
            name = node.name;

      if (!['NAME', 'ANY'].includes(type)) {
        return;
      }

      let nodeName = type === 'ANY' ? '*' : name;

      const _getPreferredTypeInfo = getPreferredTypeInfo(type, nodeName, parentName, parentNode),
            _getPreferredTypeInfo2 = _slicedToArray(_getPreferredTypeInfo, 3),
            hasMatchingPreferredType = _getPreferredTypeInfo2[0],
            typeName = _getPreferredTypeInfo2[1],
            isGenericMatch = _getPreferredTypeInfo2[2];

      let preferred;

      if (hasMatchingPreferredType) {
        const preferredSetting = preferredTypes[typeName];
        nodeName = typeName === '[]' ? typeName : nodeName;

        if (!preferredSetting) {
          invalidTypes.push([nodeName]);
        } else if (typeof preferredSetting === 'string') {
          preferred = preferredSetting;
          invalidTypes.push([nodeName, preferred]);
        } else if (typeof preferredSetting === 'object') {
          preferred = _lodash.default.get(preferredSetting, 'replacement');
          invalidTypes.push([nodeName, preferred, _lodash.default.get(preferredSetting, 'message')]);
        } else {
          utils.reportSettings('Invalid `settings.jsdoc.preferredTypes`. Values must be falsy, a string, or an object.');
          return;
        }
      } else if (!noDefaults && type === 'NAME') {
        var _iteratorNormalCompletion = true;
        var _didIteratorError = false;
        var _iteratorError = undefined;

        try {
          for (var _iterator = strictNativeTypes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
            const strictNativeType = _step.value;

            if (strictNativeType.toLowerCase() === nodeName.toLowerCase() && strictNativeType !== nodeName && ( // Don't report if user has own map for a strict native type
            !preferredTypes || _lodash.default.get(preferredTypes, strictNativeType) === undefined)) {
              preferred = strictNativeType;
              invalidTypes.push([nodeName, preferred]);
              break;
            }
          }
        } catch (err) {
          _didIteratorError = true;
          _iteratorError = err;
        } finally {
          try {
            if (!_iteratorNormalCompletion && _iterator.return != null) {
              _iterator.return();
            }
          } finally {
            if (_didIteratorError) {
              throw _iteratorError;
            }
          }
        }
      } // For fixer


      if (preferred) {
        adjustNames(type, preferred, isGenericMatch, nodeName, node, parentNode);
      }
    });

    if (invalidTypes.length) {
      const fixedType = (0, _jsdoctypeparser.publish)(typeAst);
      const tagName = jsdocTag.tag;
      invalidTypes.forEach(([badType, preferredType = '', message]) => {
        const fix = fixer => {
          return fixer.replaceText(jsdocNode, sourceCode.getText(jsdocNode).replace(`{${jsdocTag.type}}`, `{${fixedType}}`));
        };

        const tagValue = jsdocTag.name ? ` "${jsdocTag.name}"` : '';

        if (exemptTagContexts.some(({
          tag,
          types
        }) => {
          return tag === tagName && (types === true || types.includes(jsdocTag.type));
        })) {
          return;
        }

        report(message || `Invalid JSDoc @${tagName}${tagValue} type "${badType}"` + (preferredType ? '; ' : '.') + (preferredType ? `prefer: "${preferredType}".` : ''), preferredType ? fix : null, jsdocTag, message ? {
          tagName,
          tagValue
        } : null);
      });
    }
  });
}, {
  iterateAllJsdocs: true,
  meta: {
    fixable: 'code',
    schema: [{
      additionalProperties: false,
      properties: {
        exemptTagContexts: {
          items: {
            additionalProperties: false,
            properties: {
              tag: {
                type: 'string'
              },
              types: {
                oneOf: [{
                  type: 'boolean'
                }, {
                  items: {
                    type: 'string'
                  },
                  type: 'array'
                }]
              }
            },
            type: 'object'
          },
          type: 'array'
        },
        noDefaults: {
          type: 'boolean'
        },
        unifyParentAndChildTypeChecks: {
          type: 'boolean'
        }
      },
      type: 'object'
    }],
    type: 'suggestion'
  }
});

exports.default = _default;
module.exports = exports.default;
//# sourceMappingURL=checkTypes.js.map