"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(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const debug = (0, _debug.default)('requireExportJsdoc');

const createNode = function () {
  return {
    props: {}
  };
};

const getSymbolValue = function (symbol) {
  /* istanbul ignore next */
  if (!symbol) {
    /* istanbul ignore next */
    return null;
  }
  /* istanbul ignore next */


  if (symbol.type === 'literal') {
    return symbol.value.value;
  }
  /* istanbul ignore next */


  return 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;
  }
  /* istanbul 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

  /* istanbul ignore next */


  if (globals.props[node.name]) {
    return globals.props[node.name];
  }

  return null;
};

let createSymbol = null;

const getSymbol = function (node, globals, scope, opt) {
  const opts = opt || {};
  /* istanbul ignore next */

  switch (node.type) {
    case 'Identifier':
      {
        return getIdentifier(node, globals, scope, opts);
      }

    case 'MemberExpression':
      {
        const obj = getSymbol(node.object, globals, scope, opts);
        const propertySymbol = getSymbol(node.property, globals, scope, {
          simpleIdentifier: !node.computed
        });
        const propertyValue = getSymbolValue(propertySymbol);
        /* istanbul ignore next */

        if (obj && propertyValue && obj.props[propertyValue]) {
          const block = obj.props[propertyValue];
          return block;
        }
        /*
        if (opts.createMissingProps && propertyValue) {
          obj.props[propertyValue] = createNode();
           return obj.props[propertyValue];
        }
        */

        /* istanbul ignore next */


        debug(`MemberExpression: Missing property ${node.property.name}`);
        /* istanbul ignore next */

        return null;
      }

    case 'TSTypeAliasDeclaration':
    case 'TSEnumDeclaration':
    case 'TSInterfaceDeclaration':
    case 'ClassDeclaration':
    case 'ClassExpression':
    case 'FunctionExpression':
    case 'FunctionDeclaration':
    case 'ArrowFunctionExpression':
      {
        const val = createNode();
        val.props.prototype = createNode();
        val.props.prototype.type = 'object';
        val.type = 'object';
        val.value = node;
        return val;
      }

    case 'AssignmentExpression':
      {
        return createSymbol(node.left, globals, node.right, scope, opts);
      }

    case 'ClassBody':
      {
        const val = createNode();
        node.body.forEach(method => {
          val.props[method.key.name] = createNode();
          val.props[method.key.name].type = 'object';
          val.props[method.key.name].value = method.value;
        });
        val.type = 'object';
        val.value = node;
        return val;
      }

    case 'ObjectExpression':
      {
        const val = createNode();
        val.type = 'object';
        node.properties.forEach(prop => {
          if ([// @typescript-eslint/parser, espree, acorn, etc.
          'SpreadElement', // @babel/eslint-parser
          'ExperimentalSpreadProperty'].includes(prop.type)) {
            return;
          }

          const propVal = getSymbol(prop.value, globals, scope, opts);
          /* istanbul ignore next */

          if (propVal) {
            val.props[prop.key.name] = propVal;
          }
        });
        return val;
      }

    case 'Literal':
      {
        const val = createNode();
        val.type = 'literal';
        val.value = node;
        return val;
      }
  }
  /* istanbul ignore next */


  return null;
};

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;
  let symbol;

  switch (node.type) {
    case 'FunctionDeclaration':
    /* istanbul ignore next */
    // Fall through

    case 'TSEnumDeclaration':
    case 'TSInterfaceDeclaration':
    /* istanbul ignore next */
    // Fall through

    case 'TSTypeAliasDeclaration':
    case 'ClassDeclaration':
      {
        /* istanbul ignore next */
        if (node.id && node.id.type === 'Identifier') {
          return createSymbol(node.id, globals, node, globals);
        }
        /* istanbul ignore next */


        break;
      }

    case 'Identifier':
      {
        if (value) {
          const valueSymbol = getSymbol(value, globals, block);
          /* istanbul ignore next */

          if (valueSymbol) {
            createBlockSymbol(block, node.name, valueSymbol, globals, isGlobal);
            return block.props[node.name];
          }
          /* istanbul ignore next */


          debug('Identifier: Missing value symbol for %s', node.name);
        } else {
          createBlockSymbol(block, node.name, createNode(), globals, isGlobal);
          return block.props[node.name];
        }
        /* istanbul ignore next */


        break;
      }

    case 'MemberExpression':
      {
        symbol = getSymbol(node.object, globals, block);
        const propertySymbol = getSymbol(node.property, globals, block, {
          simpleIdentifier: !node.computed
        });
        const propertyValue = getSymbolValue(propertySymbol);

        if (symbol && propertyValue) {
          createBlockSymbol(symbol, propertyValue, getSymbol(value, globals, block), globals, isGlobal);
          return symbol.props[propertyValue];
        }
        /* istanbul ignore next */


        debug('MemberExpression: Missing symbol: %s', node.property.name);
        break;
      }
  }

  return null;
}; // Creates variables from variable definitions


const initVariables = function (node, globals, opts) {
  switch (node.type) {
    case 'Program':
      {
        node.body.forEach(childNode => {
          initVariables(childNode, globals, opts);
        });
        break;
      }

    case 'ExpressionStatement':
      {
        initVariables(node.expression, globals, opts);
        break;
      }

    case 'VariableDeclaration':
      {
        node.declarations.forEach(declaration => {
          // let and const
          const symbol = createSymbol(declaration.id, globals, null, globals);

          if (opts.initWindow && node.kind === 'var' && globals.props.window) {
            // If var, also add to window
            globals.props.window.props[declaration.id.name] = symbol;
          }
        });
        break;
      }

    case 'ExportNamedDeclaration':
      {
        if (node.declaration) {
          initVariables(node.declaration, globals, opts);
        }

        break;
      }
  }
}; // Populates variable maps using AST


const mapVariables = function (node, globals, opt, isExport) {
  /* istanbul ignore next */
  const opts = opt || {};
  /* istanbul ignore next */

  switch (node.type) {
    case 'Program':
      {
        if (opts.ancestorsOnly) {
          return false;
        }

        node.body.forEach(childNode => {
          mapVariables(childNode, globals, opts);
        });
        break;
      }

    case 'ExpressionStatement':
      {
        mapVariables(node.expression, globals, opts);
        break;
      }

    case 'AssignmentExpression':
      {
        createSymbol(node.left, globals, node.right);
        break;
      }

    case 'VariableDeclaration':
      {
        node.declarations.forEach(declaration => {
          const isGlobal = opts.initWindow && node.kind === 'var' && globals.props.window;
          const symbol = createSymbol(declaration.id, globals, declaration.init, globals, isGlobal);

          if (symbol && isExport) {
            symbol.exported = true;
          }
        });
        break;
      }

    case 'FunctionDeclaration':
      {
        /* istanbul ignore next */
        if (node.id.type === 'Identifier') {
          createSymbol(node.id, globals, node, globals, true);
        }

        break;
      }

    case 'ExportDefaultDeclaration':
      {
        const symbol = createSymbol(node.declaration, globals, node.declaration);

        if (symbol) {
          symbol.exported = true;
        } else if (!node.id) {
          globals.ANONYMOUS_DEFAULT = node.declaration;
        }

        break;
      }

    case 'ExportNamedDeclaration':
      {
        if (node.declaration) {
          if (node.declaration.type === 'VariableDeclaration') {
            mapVariables(node.declaration, globals, opts, true);
          } else {
            const symbol = createSymbol(node.declaration, globals, node.declaration);
            /* istanbul ignore next */

            if (symbol) {
              symbol.exported = true;
            }
          }
        }

        node.specifiers.forEach(specifier => {
          mapVariables(specifier, globals, opts);
        });
        break;
      }

    case 'ExportSpecifier':
      {
        const symbol = getSymbol(node.local, globals, globals);
        /* istanbul ignore next */

        if (symbol) {
          symbol.exported = true;
        }

        break;
      }

    case 'ClassDeclaration':
      {
        createSymbol(node.id, globals, node.body, globals);
        break;
      }

    default:
      {
        /* istanbul ignore next */
        return false;
      }
  }

  return true;
};

const findNode = function (node, block, cache) {
  let blockCache = cache || [];
  /* istanbul ignore next */

  if (!block || blockCache.includes(block)) {
    return false;
  }

  blockCache = blockCache.slice();
  blockCache.push(block);

  if ((block.type === 'object' || block.type === 'MethodDefinition') && block.value === node) {
    return true;
  }

  const {
    props = block.body
  } = block;

  for (const propval of Object.values(props || {})) {
    if (Array.isArray(propval)) {
      /* istanbul ignore if */
      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(['ExportNamedDeclaration', 'ExportDefaultDeclaration']);

const getExportAncestor = function (nde) {
  let node = nde;

  while (node) {
    if (exportTypes.has(node.type)) {
      return node;
    }

    node = node.parent;
  }

  return false;
};

const canExportedByAncestorType = new Set(['TSPropertySignature', 'TSMethodSignature', 'ClassProperty', 'Method']);
const canExportChildrenType = new Set(['TSInterfaceBody', 'TSInterfaceDeclaration', 'ClassDefinition', 'ClassExpression', 'Program']);

const isExportByAncestor = function (nde) {
  if (!canExportedByAncestorType.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;
};

const findExportedNode = function (block, node, cache) {
  /* istanbul ignore next */
  if (block === null) {
    return false;
  }

  const blockCache = cache || [];
  const {
    props
  } = block;

  for (const propval of Object.values(props)) {
    blockCache.push(propval);

    if (propval.exported && (node === propval.value || findNode(node, propval.value))) {
      return true;
    } // No need to check `propval` for exported nodes as ESM
    //  exports are only global

  }

  return false;
};

const isNodeExported = function (node, globals, opt) {
  if (opt.initModuleExports && globals.props.module && globals.props.module.props.exports && findNode(node, globals.props.module.props.exports)) {
    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;
};

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);
};

const parse = function (ast, node, opt) {
  /* istanbul ignore next */
  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
  };
};

const isUncommentedExport = function (node, sourceCode, opt, settings) {
  // console.log({node});
  // Optimize with ancestor check for esm
  if (opt.esm) {
    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 parseResult = parse(sourceCode.ast, node, opt);
  return isNodeExported(node, parseResult.globalVars, opt);
};

var _default = {
  isUncommentedExport,
  parse
};
exports.default = _default;
module.exports = exports.default;
//# sourceMappingURL=exportParser.js.map