Source: constant-folding.js

const fs = require("fs");
const deb = require('../src/deb.js');
const escodegen = require("escodegen");
const espree = require("espree");
const estraverse = require("estraverse");
const { Console } = require("console");

"use strict";

module.exports = constantFolding;

/**
 * A function that fold constants
 * @param {string} code A string with the input code
 * @param {bool} debug Activates debug mode (print AST)
 * @returns {string} A string with the new code with constant folded
 */
function constantFolding(code, debug = false) {
  const ast = espree.parse(code, { ecmaVersion: 6, loc: false });
  estraverse.traverse(ast, {
    leave: function (node, parent) {
      if (
        node.type == "BinaryExpression" &&
        node.left.type == "Literal" && node.right.type == "Literal"
      ) {
        replaceByLiteral(node);
      } else if (
        node.type === "CallExpression" &&
        node.callee.type === "MemberExpression" &&
        node.callee.object.type === "ArrayExpression" &&
        node.callee.property.name === "concat") {
        replaceByArray(node);
      }
      else if (
        node.type === "MemberExpression" &&
        node.object.type === "ArrayExpression" &&
        node.property.name === "length") {
        replaceByNumber(node);
      }
      else if (
        node.type === "CallExpression" &&
        node.callee.type === "MemberExpression" &&
        node.callee.object.type === "ArrayExpression" &&
        node.callee.property.name === "pop") {
        popReplace(node);
      }
    },
  });
  if (debug) { deb(ast); };
  let newCode = escodegen.generate(ast);
  return newCode;
}

/**
 * A function that replace a binary expresion node to a literal node
 * @param {object} node Node of AST
 */
function replaceByLiteral(node) {
  node.type = "Literal";

  node.value = eval(`${node.left.raw} ${node.operator} ${node.right.raw}`);
  node.raw = String(node.value);

  delete node.left;
  delete node.right;
}


/**
 * Replace concat member by the result array
 * @param {object} node Node of AST
 */
function replaceByArray(node) {
  node.type = "ArrayExpression";

  let result = node.callee.object.elements;

  for (let argumentNode of node.arguments) {
    if (argumentNode.type === 'ArrayExpression') {
      result = result.concat(argumentNode.elements)
    } else if (argumentNode.type === 'Literal') {
      result.push(argumentNode);
    }
  }

  node.elements = result;

  delete node.callee;
  delete node.arguments;
  delete node.optional;
}

/**
 * Replace a length member by the result number
 * @param {object} node Node of AST
 */
function replaceByNumber(node) {
  node.type = "Literal";
  node.value = node.object.elements.length;
  node.raw = String(node.value);

  delete node.object;
  delete node.property;

}

/**
 * Replaces an array by the last element
 * @param {object} node Node of AST
 */
function popReplace(node) {


  let newNode = node.callee.object.elements.pop();
  delete node.type;
  delete node.callee;
  delete node.arguments;
  delete node.optional;

  for (let key in newNode) {
    node[key] = newNode[key];
  }
}