@adrianepi/constant-folding
Version:
269 lines (253 loc) • 7.28 kB
JavaScript
// See https://github.com/babel/minify/tree/master/packages/babel-plugin-minify-constant-folding
const fs = require("fs");
const deb = require('../src/deb.js');
const escodegen = require("escodegen");
const espree = require("espree");
const estraverse = require("estraverse");
;
module.exports = constantFolding;
/**
* A function that creates the AST of the given code and modifies it for
* allowing the use of array and strings methods.
*
* @param {string} code A string with the js program code.
* @param {string} pattern The pattern (other parameters).
* @return {string} Returns a string with the output in AST format or JS
* format.
*/
function constantFolding(code, pattern) {
const t = espree.parse(code, { ecmaVersion: 6, loc: false });
estraverse.traverse(t, {
leave: function (n, p) {
if (n.type == "BinaryExpression" && n.left.type == "Literal" && n.right.type == "Literal") {
replaceByLiteral(n);
}
else if (n.type == "CallExpression") {
replaceByCallExpression(n);
}
else if (n.type == "MemberExpression" && p.type != "CallExpression") {
replaceByMemberExpression(n);
}
},
});
let result = "";
let c = escodegen.generate(t);
if (pattern == "--ast" || pattern == "-a") {
//result = 'AST Json code: \n\n';
result += JSON.stringify(t, 0, 2);
}
else if (pattern == "--js" || pattern == "-j") {
//result = '\n\n---\n\n JavaScript code:\n\n';
result += generateOutput(c);
}
else if (pattern == "--deb" || pattern == "-d") {
result += generateOutput(c);
deb(t);
}
else {
result = 'AST Json code: \n\n';
result += JSON.stringify(t, 0, 2);
result += '\n\n---\n\n JavaScript code:\n\n';
result += generateOutput(c);
}
return result;
}
/**
* Modifies the literal node for computing the operations and apply constant
* folding.
*
* @param {<type>} node The node.
*/
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;
}
/**
* Modifies the member expression node for allowing use for array and strings
* methods.
*
* @param {Node} node The node.
*/
function replaceByMemberExpression(node) {
node.type = "Literal";
// Array methods
if (node.object.type == "ArrayExpression") {
var arr = generateArray(node.object.elements);
if (node.property.type == "Literal") {
node.value = eval(`[${arr}][${node.property.value}]`);
}
else {
node.value = eval(`[${arr}].${node.property.name}`);
}
}
// String and number methods
else if (node.object.type == "Literal") {
if (node.property.type == "Literal") {
node.value = eval(`"${node.object.value}"[${node.property.value}]`);
}
else {
node.value = eval(`"${node.object.value}".${node.property.name}`);
}
// Add quotes to strings
if (typeof(node.value) != "number") {
node.value = "\"" + node.value + "\"";
}
}
node.raw = String(node.value);
node.computed = true;
delete node.object;
delete node.property;
}
/**
* Modifies the call expression node for allowing use for array and strings
* methods.
*
* @param {Node} node The node.
*/
function replaceByCallExpression(node) {
// Array methods
if (node.callee.object.type == "ArrayExpression") {
var arr = generateArray(node.callee.object.elements);
if (node.callee.property.name == "join" || node.callee.property.name == "shift" || node.callee.property.name == "pop") {
node.type = "Literal";
if (node.arguments.length > 0) {
var args = generateArray(node.arguments);
node.value = eval(`[${arr}].${node.callee.property.name}(${args})`);
}
else {
node.value = eval(`[${arr}].${node.callee.property.name}()`);
}
if (node.callee.property.name === "join") {
node.value = "\"" + node.value + "\"";
}
node.raw = String(node.value);
}
else {
node.type = "ArrayExpression";
var result = "";
if (node.arguments.length > 0) {
var args = generateArray(node.arguments)
result = eval(`[${arr}].${node.callee.property.name}(${args})`);
}
else {
result = eval(`[${arr}].${node.callee.property.name}()`);
}
var tmp = new Array();
for (var i = 0; i < result.length; i++) {
var newNode = Object.assign({} , node.callee.object.elements[0]);
if (newNode.hasOwnProperty("name")) {
newNode.name = result[i];
}
else if (newNode.hasOwnProperty("value")) {
newNode.value = result[i];
}
tmp.push(newNode);
}
node.elements = tmp;
}
}
// String and number methods
else if (node.callee.object.type == "Literal") {
node.type = "Literal";
if (node.arguments.length > 0) {
var args = generateArray(node.arguments);
node.value = eval(`"${node.callee.object.value}".${node.callee.property.name}(${args})`);
}
else {
node.value = eval(`"${node.callee.object.value}".${node.callee.property.name}()`);
}
// Add quotes in string methods
if (typeof(node.value) !== "number" && node.callee.property.name !== 'split') {
node.value = "\"" + node.value + "\"";
}
if (typeof(node.value) !== "number" && node.callee.property.name === 'split') {
let addQuotes = "[ ";
for (let i = 0; i < node.value.length; i++) {
addQuotes += "\"" + node.value[i] + "\""
if (i < node.value.length - 1) {
addQuotes += ", ";
}
}
node.value = addQuotes + " ]";
}
node.raw = String(node.value);
}
delete node.callee;
delete node.arguments;
}
/**
* Receives a node and generates an array from the elements of that node.
*
* @param {Node} node The node.
* @return {Array} Array with the elements of the node.
*/
function generateArray (node) {
var arr = new Array();
for (var i in node) {
if (node[i].type === "Identifier") {
arr.push(`"${node[i].name}"`);
}
else if (node[i].type === "Literal") {
if (typeof(node[i].value) === "string") {
arr.push(`"${node[i].value}"`);
}
else {
arr.push(`${node[i].value}`);
}
}
else if (node[i].type === "ArrayExpression") {
arr.push(generateArray(node[i].elements));
}
else {
console.log("Error, not valid expresion in generateArray");
}
}
return arr;
}
/**
* Modifies the JS output for printing it in one line and without many spaces.
*
* @param {string} code The code
* @return {string} The output with better format.
*/
function generateOutput(code) {
let result = "";
for (let i = 0; i < code.length; i++) {
if (code[i] !== '\n' && code[i] !== '\'') {
if (code[i] === ' ' && code[i - 1] === ' ') {
continue;
}
result += code[i];
if (code[i] === ';') {
result += '\n';
}
}
}
return result;
}
// const input = `
// var f = 3+null;
// var e = 4 | 3;
// var d = 3+"c";
// var b = 9 +1;
// var a = 2+3*5+b;
// [a, b, c].concat([d, e], f, g, [h]);
// ["a", "b", "c"].join();
// ["a", "b", "c"].join('@');
// [1, 2, 3].length;
// [1, 2, 3][2-1];
// [1, 2, 3].shift();
// [1, 2, 3].slice(0, 1+1);
// [a, b, c].pop();
// [a, b, c].reverse();
// "abc"[0];
// "abc".charAt();
// "abc".charAt(1);
// "abc".length;
// "a,b,c".split(",");
// (100 + 23).toString();
// `;
// console.log(constantFolding(input, "--js"));