"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _jsdoccomment = require("@es-joy/jsdoccomment");
var _debug = _interopRequireDefault(require("debug"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const debug = (0, _debug.default)('requireExportJsdoc');

/**
 * @typedef {{
 *   value: string
 * }} ValueObject
 */

/**
 * @typedef {{
 *   type?: string,
 *   value?: ValueObject|import('eslint').Rule.Node|import('@typescript-eslint/types').TSESTree.Node,
 *   props: {
 *     [key: string]: CreatedNode|null,
 *   },
 *   special?: true,
 *   globalVars?: CreatedNode,
 *   exported?: boolean,
 *   ANONYMOUS_DEFAULT?: import('eslint').Rule.Node
 * }} CreatedNode
 */

/**
 * @returns {CreatedNode}
 */
const createNode = function () {
  return {
    props: {}
  };
};

/**
 * @param {CreatedNode|null} symbol
 * @returns {string|null}
 */
const getSymbolValue = function (symbol) {
  /* c8 ignore next 3 */
  if (!symbol) {
    return null;
  }

  /* c8 ignore else */
  if (symbol.type === 'literal') {
    return /** @type {ValueObject} */symbol.value.value;
  }
  /* c8 ignore next 2 */
  // eslint-disable-next-line @stylistic/padding-line-between-statements -- c8
  return null;
};

/**
 *
 * @param {import('estree').Identifier} node
 * @param {CreatedNode} globals
 * @param {CreatedNode} scope
 * @param {SymbolOptions} opts
 * @returns {CreatedNode|null}
 */
const getIdentifier = function (node, globals, scope, opts) {
  if (opts.simpleIdentifier) {
    // Type is Identier for noncomputed properties
    const identifierLiteral = createNode();
    identifierLiteral.type = 'literal';
    identifierLiteral.value = {
      value: node.name
    };
    return identifierLiteral;
  }

  /* c8 ignore next */
  const block = scope || globals;

  // As scopes are not currently supported, they are not traversed upwards recursively
  if (block.props[node.name]) {
    return block.props[node.name];
  }

  // Seems this will only be entered once scopes added and entered
  /* c8 ignore next 3 */
  if (globals.props[node.name]) {
    return globals.props[node.name];
  }
  return null;
};

/**
 * @callback CreateSymbol
 * @param {import('eslint').Rule.Node|null} node
 * @param {CreatedNode} globals
 * @param {import('eslint').Rule.Node|import('@typescript-eslint/types').TSESTree.Node|null} value
 * @param {CreatedNode} [scope]
 * @param {boolean|SymbolOptions} [isGlobal]
 * @returns {CreatedNode|null}
 */

/** @type {CreateSymbol} */
let createSymbol; // eslint-disable-line prefer-const

/**
 * @typedef {{
 *   simpleIdentifier?: boolean
 * }} SymbolOptions
 */

/**
 *
 * @param {import('eslint').Rule.Node|import('@typescript-eslint/types').TSESTree.Node} node
 * @param {CreatedNode} globals
 * @param {CreatedNode} scope
 * @param {SymbolOptions} [opt]
 * @returns {CreatedNode|null}
 */
const getSymbol = function (node, globals, scope, opt) {
  const opts = opt || {};
  /* c8 ignore next */
  switch (node.type) {
    /* c8 ignore next 4 -- No longer needed? */
    case 'ArrowFunctionExpression':

    // Fallthrough
    case 'ClassDeclaration':
    case 'FunctionDeclaration':
    case 'FunctionExpression':
    case 'TSEnumDeclaration':
    case 'TSInterfaceDeclaration':
    case 'TSTypeAliasDeclaration':
      {
        const val = createNode();
        val.props.prototype = createNode();
        val.props.prototype.type = 'object';
        val.type = 'object';
        val.value = node;
        return val;
      }
    case 'AssignmentExpression':
      {
        return createSymbol(/** @type {import('eslint').Rule.Node} */
        node.left, globals, /** @type {import('eslint').Rule.Node} */
        node.right, scope, opts);
      }
    case 'ClassBody':
      {
        const val = createNode();
        for (const method of node.body) {
          // StaticBlock
          if (!('key' in method)) {
            continue;
          }
          val.props[/** @type {import('estree').Identifier} */(/** @type {import('estree').MethodDefinition} */method.key).name] = createNode();
          /** @type {{[key: string]: CreatedNode}} */
          val.props[/** @type {import('estree').Identifier} */(/** @type {import('estree').MethodDefinition} */method.key).name].type = 'object';
          /** @type {{[key: string]: CreatedNode}} */
          val.props[/** @type {import('estree').Identifier} */(/** @type {import('estree').MethodDefinition} */method.key).name].value = /** @type {import('eslint').Rule.Node} */
          /** @type {import('estree').MethodDefinition} */method.value;
        }
        val.type = 'object';
        val.value = node.parent;
        return val;
      }
    case 'ClassExpression':
      {
        return getSymbol(/** @type {import('eslint').Rule.Node} */
        node.body, globals, scope, opts);
      }
    case 'Identifier':
      {
        return getIdentifier(node, globals, scope, opts);
      }
    case 'Literal':
      {
        const val = createNode();
        val.type = 'literal';
        val.value = node;
        return val;
      }
    case 'MemberExpression':
      {
        const obj = getSymbol(/** @type {import('eslint').Rule.Node} */
        node.object, globals, scope, opts);
        const propertySymbol = getSymbol(/** @type {import('eslint').Rule.Node} */
        node.property, globals, scope, {
          simpleIdentifier: !node.computed
        });
        const propertyValue = getSymbolValue(propertySymbol);

        /* c8 ignore else */
        if (obj && propertyValue && obj.props[propertyValue]) {
          const block = obj.props[propertyValue];
          return block;
        }
        /* c8 ignore next 11 */
        /*
        if (opts.createMissingProps && propertyValue) {
        obj.props[propertyValue] = createNode();
         return obj.props[propertyValue];
        }
        */
        // eslint-disable-next-line @stylistic/padding-line-between-statements -- c8
        debug(`MemberExpression: Missing property ${/** @type {import('estree').PrivateIdentifier} */node.property.name}`);
        /* c8 ignore next 2 */
        return null;
      }
    case 'ObjectExpression':
      {
        const val = createNode();
        val.type = 'object';
        for (const prop of node.properties) {
          if ([
          // @babel/eslint-parser
          'ExperimentalSpreadProperty',
          // typescript-eslint, espree, acorn, etc.
          'SpreadElement'].includes(prop.type)) {
            continue;
          }
          const propVal = getSymbol(/** @type {import('eslint').Rule.Node} */
          /** @type {import('estree').Property} */
          prop.value, globals, scope, opts);
          /* c8 ignore next 8 */
          if (propVal) {
            val.props[/** @type {import('estree').PrivateIdentifier} */
            (/** @type {import('estree').Property} */prop.key).name] = propVal;
          }
        }
        return val;
      }
  }
  /* c8 ignore next 2 */
  // eslint-disable-next-line @stylistic/padding-line-between-statements -- c8
  return null;
};

/**
 *
 * @param {CreatedNode} block
 * @param {string} name
 * @param {CreatedNode|null} value
 * @param {CreatedNode} globals
 * @param {boolean|SymbolOptions|undefined} isGlobal
 * @returns {void}
 */
const createBlockSymbol = function (block, name, value, globals, isGlobal) {
  block.props[name] = value;
  if (isGlobal && globals.props.window && globals.props.window.special) {
    globals.props.window.props[name] = value;
  }
};
createSymbol = function (node, globals, value, scope, isGlobal) {
  const block = scope || globals;
  /* c8 ignore next 3 */
  if (!node) {
    return null;
  }
  let symbol;
  switch (node.type) {
    case 'ClassDeclaration':
    /* c8 ignore next */
    // @ts-expect-error TS OK
    // Fall through
    case 'FunctionDeclaration':
    case 'TSEnumDeclaration':
    /* c8 ignore next */
    // @ts-expect-error TS OK
    // Fall through
    case 'TSInterfaceDeclaration':
    case 'TSTypeAliasDeclaration':
      {
        const nde = /** @type {import('estree').ClassDeclaration} */node;
        /* c8 ignore else */
        if (nde.id && nde.id.type === 'Identifier') {
          return createSymbol(/** @type {import('eslint').Rule.Node} */nde.id, globals, node, globals);
        }
        /* c8 ignore next 3 */
        // eslint-disable-next-line @stylistic/padding-line-between-statements -- c8
        break;
      }
    case 'Identifier':
      {
        const nde = /** @type {import('estree').Identifier} */node;
        if (value) {
          const valueSymbol = getSymbol(value, globals, block);
          /* c8 ignore else */
          if (valueSymbol) {
            createBlockSymbol(block, nde.name, valueSymbol, globals, isGlobal);
            return block.props[nde.name];
          }
          /* c8 ignore next 2 */
          // eslint-disable-next-line @stylistic/padding-line-between-statements -- c8
          debug('Identifier: Missing value symbol for %s', nde.name);
        } else {
          createBlockSymbol(block, nde.name, createNode(), globals, isGlobal);
          return block.props[nde.name];
        }
        /* c8 ignore next 3 */
        // eslint-disable-next-line @stylistic/padding-line-between-statements -- c8
        break;
      }
    case 'MemberExpression':
      {
        const nde = /** @type {import('estree').MemberExpression} */node;
        symbol = getSymbol(/** @type {import('eslint').Rule.Node} */nde.object, globals, block);
        const propertySymbol = getSymbol(/** @type {import('eslint').Rule.Node} */nde.property, globals, block, {
          simpleIdentifier: !nde.computed
        });
        const propertyValue = getSymbolValue(propertySymbol);
        if (symbol && propertyValue) {
          createBlockSymbol(symbol, propertyValue, getSymbol(/** @type {import('eslint').Rule.Node} */
          value, globals, block), globals, isGlobal);
          return symbol.props[propertyValue];
        }
        debug('MemberExpression: Missing symbol: %s', /** @type {import('estree').Identifier} */nde.property.name);
        break;
      }
  }
  return null;
};

/**
 * Creates variables from variable definitions
 * @param {import('eslint').Rule.Node} node
 * @param {CreatedNode} globals
 * @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opts
 * @returns {void}
 */
const initVariables = function (node, globals, opts) {
  switch (node.type) {
    case 'ExportNamedDeclaration':
      {
        if (node.declaration) {
          initVariables(/** @type {import('eslint').Rule.Node} */
          node.declaration, globals, opts);
        }
        break;
      }
    case 'ExpressionStatement':
      {
        initVariables(/** @type {import('eslint').Rule.Node} */
        node.expression, globals, opts);
        break;
      }
    case 'Program':
      {
        for (const childNode of node.body) {
          initVariables(/** @type {import('eslint').Rule.Node} */
          childNode, globals, opts);
        }
        break;
      }
    case 'VariableDeclaration':
      {
        for (const declaration of node.declarations) {
          // let and const
          const symbol = createSymbol(/** @type {import('eslint').Rule.Node} */
          declaration.id, globals, null, globals);
          if (opts.initWindow && node.kind === 'var' && globals.props.window) {
            // If var, also add to window
            globals.props.window.props[/** @type {import('estree').Identifier} */
            declaration.id.name] = symbol;
          }
        }
        break;
      }
  }
};

/**
 * Populates variable maps using AST
 * @param {import('eslint').Rule.Node|import('@typescript-eslint/types').TSESTree.Node} node
 * @param {CreatedNode} globals
 * @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opt
 * @param {true} [isExport]
 * @returns {boolean}
 */
const mapVariables = function (node, globals, opt, isExport) {
  /* c8 ignore next */
  const opts = opt || {};
  /* c8 ignore next */
  switch (node.type) {
    case 'AssignmentExpression':
      {
        createSymbol(/** @type {import('eslint').Rule.Node} */
        node.left, globals, /** @type {import('eslint').Rule.Node} */
        node.right);
        break;
      }
    case 'ClassDeclaration':
      {
        createSymbol(/** @type {import('eslint').Rule.Node|null} */node.id, globals, /** @type {import('eslint').Rule.Node} */node.body, globals);
        break;
      }
    case 'ExportDefaultDeclaration':
      {
        const symbol = createSymbol(/** @type {import('eslint').Rule.Node} */
        node.declaration, globals, /** @type {import('eslint').Rule.Node} */
        node.declaration);
        if (symbol) {
          symbol.exported = true;
          /* c8 ignore next 6 */
        } else {
          // if (!node.id) {
          globals.ANONYMOUS_DEFAULT = /** @type {import('eslint').Rule.Node} */
          node.declaration;
        }
        break;
      }
    case 'ExportNamedDeclaration':
      {
        if (node.declaration) {
          if (node.declaration.type === 'VariableDeclaration') {
            mapVariables(/** @type {import('eslint').Rule.Node} */
            node.declaration, globals, opts, true);
          } else {
            const symbol = createSymbol(/** @type {import('eslint').Rule.Node} */
            node.declaration, globals, /** @type {import('eslint').Rule.Node} */
            node.declaration);
            /* c8 ignore next 3 */
            if (symbol) {
              symbol.exported = true;
            }
          }
        }
        for (const specifier of node.specifiers) {
          mapVariables(/** @type {import('eslint').Rule.Node} */
          specifier, globals, opts);
        }
        break;
      }
    case 'ExportSpecifier':
      {
        const symbol = getSymbol(/** @type {import('eslint').Rule.Node} */
        node.local, globals, globals);
        /* c8 ignore next 3 */
        if (symbol) {
          symbol.exported = true;
        }
        break;
      }
    case 'ExpressionStatement':
      {
        mapVariables(/** @type {import('eslint').Rule.Node} */
        node.expression, globals, opts);
        break;
      }
    case 'FunctionDeclaration':
    case 'TSTypeAliasDeclaration':
      {
        /* c8 ignore next 10 */
        if (/** @type {import('estree').Identifier} */node.id.type === 'Identifier') {
          createSymbol(/** @type {import('eslint').Rule.Node} */
          node.id, globals, node, globals, true);
        }
        break;
      }
    case 'Program':
      {
        if (opts.ancestorsOnly) {
          return false;
        }
        for (const childNode of node.body) {
          mapVariables(/** @type {import('eslint').Rule.Node} */
          childNode, globals, opts);
        }
        break;
      }
    case 'VariableDeclaration':
      {
        for (const declaration of node.declarations) {
          const isGlobal = Boolean(opts.initWindow && node.kind === 'var' && globals.props.window);
          const symbol = createSymbol(/** @type {import('eslint').Rule.Node} */
          declaration.id, globals, /** @type {import('eslint').Rule.Node} */
          declaration.init, globals, isGlobal);
          if (symbol && isExport) {
            symbol.exported = true;
          }
        }
        break;
      }
    default:
      {
        /* c8 ignore next */
        return false;
      }
  }
  return true;
};

/**
 *
 * @param {import('eslint').Rule.Node} node
 * @param {CreatedNode|ValueObject|string|undefined|
 *   import('eslint').Rule.Node|import('@typescript-eslint/types').TSESTree.Node} block
 * @param {(CreatedNode|ValueObject|string|
 *   import('eslint').Rule.Node|import('@typescript-eslint/types').TSESTree.Node)[]} [cache]
 * @returns {boolean}
 */
const findNode = function (node, block, cache) {
  let blockCache = cache || [];
  if (!block || blockCache.includes(block)) {
    return false;
  }
  blockCache = blockCache.slice();
  blockCache.push(block);
  if (typeof block === 'object' && 'type' in block && (block.type === 'object' || block.type === 'MethodDefinition') && block.value === node) {
    return true;
  }
  if (typeof block !== 'object') {
    return false;
  }
  const props = 'props' in block && block.props || 'body' in block && block.body;
  for (const propval of Object.values(props || {})) {
    if (Array.isArray(propval)) {
      /* c8 ignore next 5 */
      if (propval.some(val => {
        return findNode(node, val, blockCache);
      })) {
        return true;
      }
    } else if (findNode(node, propval, blockCache)) {
      return true;
    }
  }
  return false;
};
const exportTypes = new Set(['ExportDefaultDeclaration', 'ExportNamedDeclaration']);
const ignorableNestedTypes = new Set(['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression']);

/**
 * @param {import('eslint').Rule.Node} nde
 * @returns {import('eslint').Rule.Node|false}
 */
const getExportAncestor = function (nde) {
  let node = nde;
  let idx = 0;
  const ignorableIfDeep = ignorableNestedTypes.has(nde?.type);
  while (node) {
    // Ignore functions nested more deeply than say `export default function () {}`
    if (idx >= 2 && ignorableIfDeep) {
      break;
    }
    if (exportTypes.has(node.type)) {
      return node;
    }
    node = node.parent;
    idx++;
  }
  return false;
};
const canBeExportedByAncestorType = new Set(['ClassProperty', 'Method', 'PropertyDefinition', 'TSMethodSignature', 'TSPropertySignature']);
const canExportChildrenType = new Set(['ClassBody', 'ClassDeclaration', 'ClassDefinition', 'ClassExpression', 'Program', 'TSInterfaceBody', 'TSInterfaceDeclaration', 'TSTypeAliasDeclaration', 'TSTypeLiteral', 'TSTypeParameterInstantiation', 'TSTypeReference']);

/**
 * @param {import('eslint').Rule.Node} nde
 * @returns {false|import('eslint').Rule.Node}
 */
const isExportByAncestor = function (nde) {
  if (!canBeExportedByAncestorType.has(nde.type)) {
    return false;
  }
  let node = nde.parent;
  while (node) {
    if (exportTypes.has(node.type)) {
      return node;
    }
    if (!canExportChildrenType.has(node.type)) {
      return false;
    }
    node = node.parent;
  }
  return false;
};

/**
 *
 * @param {CreatedNode} block
 * @param {import('eslint').Rule.Node} node
 * @param {CreatedNode[]} [cache] Currently unused
 * @returns {boolean}
 */
const findExportedNode = function (block, node, cache) {
  /* c8 ignore next 3 */
  if (block === null) {
    return false;
  }
  const blockCache = cache || [];
  const {
    props
  } = block;
  for (const propval of Object.values(props)) {
    const pval = /** @type {CreatedNode} */propval;
    blockCache.push(pval);
    if (pval.exported && (node === pval.value || findNode(node, pval.value))) {
      return true;
    }

    // No need to check `propval` for exported nodes as ESM
    //  exports are only global
  }
  return false;
};

/**
 *
 * @param {import('eslint').Rule.Node} node
 * @param {CreatedNode} globals
 * @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opt
 * @returns {boolean}
 */
const isNodeExported = function (node, globals, opt) {
  const moduleExports = globals.props.module?.props?.exports;
  if (opt.initModuleExports && moduleExports && findNode(node, moduleExports)) {
    return true;
  }
  if (opt.initWindow && globals.props.window && findNode(node, globals.props.window)) {
    return true;
  }
  if (opt.esm && findExportedNode(globals, node)) {
    return true;
  }
  return false;
};

/**
 *
 * @param {import('eslint').Rule.Node} node
 * @param {CreatedNode} globalVars
 * @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opts
 * @returns {boolean}
 */
const parseRecursive = function (node, globalVars, opts) {
  // Iterate from top using recursion - stop at first processed node from top
  if (node.parent && parseRecursive(node.parent, globalVars, opts)) {
    return true;
  }
  return mapVariables(node, globalVars, opts);
};

/**
 *
 * @param {import('eslint').Rule.Node} ast
 * @param {import('eslint').Rule.Node} node
 * @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opt
 * @returns {CreatedNode}
 */
const parse = function (ast, node, opt) {
  /* c8 ignore next 6 */
  const opts = opt || {
    ancestorsOnly: false,
    esm: true,
    initModuleExports: true,
    initWindow: true
  };
  const globalVars = createNode();
  if (opts.initModuleExports) {
    globalVars.props.module = createNode();
    globalVars.props.module.props.exports = createNode();
    globalVars.props.exports = globalVars.props.module.props.exports;
  }
  if (opts.initWindow) {
    globalVars.props.window = createNode();
    globalVars.props.window.special = true;
  }
  if (opts.ancestorsOnly) {
    parseRecursive(node, globalVars, opts);
  } else {
    initVariables(ast, globalVars, opts);
    mapVariables(ast, globalVars, opts);
  }
  return {
    globalVars,
    props: {}
  };
};
const accessibilityNodes = new Set(['MethodDefinition', 'PropertyDefinition']);

/**
 *
 * @param {import('eslint').Rule.Node} node
 * @returns {boolean}
 */
const isPrivate = node => {
  return accessibilityNodes.has(node.type) && 'accessibility' in node && node.accessibility !== 'public' && node.accessibility !== undefined || 'key' in node && node.key.type === 'PrivateIdentifier';
};

/**
 *
 * @param {import('eslint').Rule.Node} node
 * @param {import('eslint').SourceCode} sourceCode
 * @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opt
 * @param {import('./iterateJsdoc.js').Settings} settings
 * @returns {boolean}
 */
const isUncommentedExport = function (node, sourceCode, opt, settings) {
  // console.log({node});
  // Optimize with ancestor check for esm
  if (opt.esm) {
    if (isPrivate(node) || node.parent && isPrivate(node.parent)) {
      return false;
    }
    const exportNode = getExportAncestor(node);

    // Is export node comment
    if (exportNode && !(0, _jsdoccomment.findJSDocComment)(exportNode, sourceCode, settings)) {
      return true;
    }

    /**
     * Some typescript types are not in variable map, but inherit exported (interface property and method)
     */
    if (isExportByAncestor(node) && !(0, _jsdoccomment.findJSDocComment)(node, sourceCode, settings)) {
      return true;
    }
  }
  const ast = /** @type {unknown} */sourceCode.ast;
  const parseResult = parse(/** @type {import('eslint').Rule.Node} */
  ast, node, opt);
  return isNodeExported(node, /** @type {CreatedNode} */parseResult.globalVars, opt);
};
var _default = exports.default = {
  isUncommentedExport,
  parse
};
module.exports = exports.default;
//# sourceMappingURL=exportParser.cjs.map