"use strict";

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

var _debug = _interopRequireDefault(require("debug"));

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

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

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

const getSymbolValue = function getSymbolValue(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 getIdentifier(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 getSymbol(node, globals, scope, opt) {
  const opts = opt || {};

  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 '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
          '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 createBlockSymbol(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 createSymbol(node, globals, value, scope, isGlobal) {
  const block = scope || globals;
  let symbol;

  switch (node.type) {
    case 'ClassDeclaration':
      {
        if (node.id && node.id.type === 'Identifier') {
          return _createSymbol(node.id, globals, node, globals);
        }

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

    case 'FunctionDeclaration':
      {
        if (node.id && node.id.type === 'Identifier') {
          return _createSymbol(node.id, globals, node, globals);
        }

        break;
      }
  }

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


const initVariables = function initVariables(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 mapVariables(node, globals, opt, isExport) {
  /* istanbul ignore next */
  const opts = opt || {};
  /* istanbul ignore next */

  switch (node.type) {
    case 'Program':
      {
        if (opts.ancestorsOnly) {
          return false;
        } else {
          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 findNode(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') {
    if (block.value === node) {
      return true;
    }
  }

  const props = block.props;

  for (const prop in props) {
    /* istanbul ignore next */
    if (Object.prototype.hasOwnProperty.call(props, prop)) {
      const propval = props[prop]; // Only check node if it had resolvable value

      if (propval && findNode(node, propval, blockCache)) {
        return true;
      }
    }
  }

  return false;
};

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


  if (block === null) {
    return false;
  }

  const blockCache = cache || [];
  const props = block.props;

  for (const key in props) {
    /* istanbul ignore next */
    if (Object.prototype.hasOwnProperty.call(props, key)) {
      blockCache.push(props[key]);

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

    }
  }

  return false;
};

const isNodeExported = function isNodeExported(node, globals, opt) {
  if (opt.initModuleExports && globals.props.module && globals.props.module.props.exports) {
    if (findNode(node, globals.props.module.props.exports)) {
      return true;
    }
  }

  if (opt.initWindow && globals.props.window) {
    if (findNode(node, globals.props.window)) {
      return true;
    }
  }

  if (opt.esm && findExportedNode(globals, node)) {
    return true;
  }

  return false;
};

const parseRecursive = function parseRecursive(node, globalVars, opts) {
  // Iterate from top using recursion - stop at first processed node from top
  if (node.parent) {
    if (parseRecursive(node.parent, globalVars, opts)) {
      return true;
    }
  }

  return mapVariables(node, globalVars, opts);
};

const parse = function parse(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 isExported = function isExported(node, parseResult, opt) {
  return isNodeExported(node, parseResult.globalVars, opt);
};

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