UNPKG

@adrianepi/constant-folding

Version:
269 lines (253 loc) 7.28 kB
// 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"); "use strict"; 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"));