├── .gitignore ├── README.md ├── index.js ├── lib └── helper.js ├── package.json ├── test.js └── visitors ├── functionToArrowVisitor.js └── simpleExprVisitor.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 5to6 2 | 3 | Convert your es5 code to es6, using basic transforms. 4 | Right now what happens is that simple anonymous functions are converted to arrow syntax. 5 | 6 | ``` 7 | function(a,b,c) { 8 | a = b + c; 9 | return a + 2; 10 | } 11 | ``` 12 | 13 | gets converted to 14 | ``` 15 | (a,b,c) => {a = b + c; return a + 2;} 16 | ``` 17 | 18 | ## Using 19 | 20 | Just call ```node index.js ``` to transpile to sysout 21 | ``` 22 | > node index.js test.js 23 | ``` 24 | 25 | alternatively, write directly to same infile 26 | 27 | ``` 28 | > cat test.js | node index.js | tee test.js 29 | 30 | ``` 31 | 32 | ## TODO 33 | - [ ] allow flexible configuration 34 | - [ ] add more visitors 35 | - [x] parens free arrows 36 | - [ ] const 37 | - [ ] let 38 | - [ ] default argument values 39 | - ... 40 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Reads a source file that may (or may not) contain ES6 classes *or* arrow 3 | * functions, transforms them to ES5 compatible code using the pre-bundled ES6 4 | * visitors, and prints out the result. 5 | */ 6 | 7 | var fs = require('fs'); 8 | var jstransform = require('jstransform'); 9 | var fnToArrowVisitor = require('./visitors/functionToArrowVisitor').visitor; 10 | var simpleExprVisitor = require('./visitors/simpleExprVisitor').visitor; 11 | 12 | 13 | /** 14 | * main entry point 15 | */ 16 | var visitors = [fnToArrowVisitor, simpleExprVisitor]; 17 | 18 | // I prefer to ensure that jstransform re-parses the code 19 | // otherwise the fnToArrow would have to generate a new ArrowFunctionExpression node 20 | // and I don't know how to do it with jstransform 21 | var applyVisitors = function(str) { 22 | return visitors.reduce(function(str, visitor){ 23 | return jstransform.transform([visitor], str).code 24 | }, str); 25 | } 26 | 27 | 28 | // either process a file as argument or stdin 29 | if(process.argv.length > 2) { 30 | process.stdout.write(applyVisitors(fs.readFileSync(process.argv[2], 'utf8').toString())); 31 | } 32 | else { 33 | var stdin = process.stdin, 34 | stdout = process.stdout, 35 | inputChunks = []; 36 | 37 | stdin.resume(); 38 | stdin.setEncoding('utf8'); 39 | 40 | stdin.on('data', function (chunk) { 41 | inputChunks.push(chunk); 42 | }); 43 | 44 | stdin.on('end', function () { 45 | var inputFile = inputChunks.join(); 46 | var output = applyVisitors(inputFile); 47 | //process.stdout.write(output.code); 48 | console.log(output.code) 49 | }); 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/helper.js: -------------------------------------------------------------------------------- 1 | var jstransform = require('jstransform'); 2 | var utils = require('jstransform/src/utils'); 3 | 4 | var Syntax = jstransform.Syntax; 5 | 6 | function elideString(str) { 7 | return ''; 8 | } 9 | 10 | function isES5FunctionNode(node) { 11 | return node.type === Syntax.FunctionDeclaration 12 | || node.type === Syntax.FunctionExpression; 13 | } 14 | 15 | function renderParams(traverse, node, path, state) { 16 | utils.append('(', state); 17 | if (node.params.length !== 0) { 18 | path.unshift(node); 19 | traverse(node.params, path, state); 20 | path.unshift(); 21 | } 22 | utils.append(') => ', state); 23 | } 24 | 25 | 26 | function renderNoParams(traverse, node, path, state) { 27 | utils.append('() =>', state); 28 | } 29 | 30 | function containsImmediateUneligibleExpr(node, root) { 31 | var foundUneligible = false; 32 | 33 | function nodeTypeAnalyzer(n) { 34 | if (n !== root && n.type === Syntax.FunctionExpression) { 35 | //stop, the new function expression will be analyzed later 36 | return false; 37 | } 38 | 39 | //TODO arguments are uneligible only if they are on top level 40 | if (n.type === Syntax.ThisExpression 41 | || (n.type === Syntax.Identifier && n.name === 'arguments')) { 42 | 43 | foundUneligible = true; 44 | return false; //stop 45 | } 46 | } 47 | 48 | function nodeTypeTraverser(child, path, state) { 49 | if (!foundUneligible) { 50 | foundUneligible = containsImmediateUneligibleExpr(child, root); 51 | } 52 | } 53 | utils.analyzeAndTraverse( 54 | nodeTypeAnalyzer, 55 | nodeTypeTraverser, 56 | node, 57 | [] 58 | ); 59 | return foundUneligible; 60 | } 61 | 62 | 63 | exports.elideString = elideString; 64 | exports.renderParams = renderParams; 65 | exports.renderNoParams = renderNoParams; 66 | exports.isES5FunctionNode = isES5FunctionNode; 67 | exports.containsImmediateUneligibleExpr = containsImmediateUneligibleExpr; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "5to6", 3 | "version": "0.0.1", 4 | "description": "jumpstart you es6 usage by converting all your es5 code to es6", 5 | "main": "index.js", 6 | "scripts": { 7 | "parse": "node index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/mping/5to6" 12 | }, 13 | "keywords": [ 14 | "es5", 15 | "es6", 16 | "ast", 17 | "ecma5", 18 | "ecma6" 19 | ], 20 | "author": "Miguel Ping (https://github.com/mping)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/mping/5to6/issues" 24 | }, 25 | "homepage": "https://github.com/mping/5to6", 26 | "dependencies": { 27 | "jstransform": "~9.1.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /*var a = function(a){ return 1;} 2 | 3 | var b = function(a){ return a+1;} 4 | 5 | var b2 = function(a){ return {k:a+1}} 6 | 7 | _(list).map(function(i){ a.push({b:2})}) 8 | 9 | _(list).each(function(i){ b(i) }) 10 | */ 11 | 12 | a.filter('orderObjectBy', function() { 13 | return function(items, field, reverse) { 14 | filtered.sort(function (a, b) { 15 | return (a[field] > b[field] ? 1 : -1); 16 | }); 17 | return filtered; 18 | }; 19 | }) 20 | ; 21 | -------------------------------------------------------------------------------- /visitors/functionToArrowVisitor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Reads a source file that may (or may not) contain ES6 classes *or* arrow 3 | * functions, transforms them to ES5 compatible code using the pre-bundled ES6 4 | * visitors, and prints out the result. 5 | */ 6 | 7 | var fs = require('fs'); 8 | var jstransform = require('jstransform'); 9 | var utils = require('jstransform/src/utils'); 10 | 11 | var helper = require('../lib/helper.js') 12 | 13 | var Syntax = jstransform.Syntax; 14 | var knownLocs = []; 15 | 16 | 17 | /** 18 | * Indicates if the node has a member expression 19 | * node should be a function node 20 | */ 21 | function fnBodyHasUneligibleExpr(node, path, state) { 22 | return helper.containsImmediateUneligibleExpr(node, node); 23 | } 24 | 25 | 26 | /** 27 | * functions that 28 | * - are named 29 | * - have a `this` binding 30 | * cannot be simplified to arrow functions 31 | */ 32 | function isUneligibleForArrow(node, path, state) { 33 | //TODO named functions are uneligible only if referenced 34 | return node.id 35 | //only for small funcs TODO: should be configurable 36 | || (node.loc.end.line - node.loc.start.line) > 3 37 | || fnBodyHasUneligibleExpr(node, path, state); 38 | } 39 | 40 | /** 41 | * Main Visitor. 42 | * Replace basic functions to es6 notation. 43 | * Named functions are ignored because we would need to determine if the name is used somewhere. 44 | */ 45 | function functionToArrowVisitor(traverse, node, path, state) { 46 | // HACK HACK HACK can't really tell why a node is being traversed twice 47 | // my guess is that when I call `traverse(fnBody.body, path, state);` 48 | // I'll enter the visitor again, and a second time because of the initial js parsing 49 | if(knownLocs.indexOf(node.loc) >=0) return; knownLocs.push(node.loc); 50 | 51 | //named functions can be referenced elsewhere, we don't want to change that 52 | if(isUneligibleForArrow(node, path, state)) return; 53 | 54 | //write params if any, then write arrow 55 | var renderFnParams = node.params.length 56 | ? helper.renderParams 57 | : helper.renderNoParams; 58 | 59 | if(node.params.length) { 60 | //utils.catchupWhiteOut(node.params[0].range[0], state); 61 | utils.catchup(node.params[0].range[0], state, helper.elideString) 62 | } 63 | renderFnParams(traverse, node, path, state); 64 | 65 | //can we shorten the fn body? 66 | var fnBody = node.body, 67 | bodyLen = fnBody.body.length; 68 | 69 | //body is multiline 70 | if(bodyLen > 1) { 71 | utils.catchup(node.body.range[0], state, helper.elideString) 72 | } 73 | //body is single line 74 | else if(bodyLen === 1) { 75 | //can it be like (a) => fn(a+b)? 76 | //or we should use a block? 77 | utils.append('{', state); 78 | utils.catchup(fnBody.body[0].range[0], state, helper.elideString); 79 | 80 | //hopefully we're removing the last ';' because we can't have that in parens free mode 81 | traverse(fnBody.body, path, state);//this bugger is traversing same function twice! 82 | 83 | utils.append('}', state); 84 | //finally end the body 85 | utils.catchupWhiteOut(node.body.range[1], state); 86 | } 87 | //no body 88 | else { 89 | utils.catchupWhiteOut(node.body.range[0], state); 90 | } 91 | } 92 | functionToArrowVisitor.test = function(node, path, state) { 93 | return helper.isES5FunctionNode(node); 94 | }; 95 | 96 | // module exports 97 | exports.visitor = functionToArrowVisitor; -------------------------------------------------------------------------------- /visitors/simpleExprVisitor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Reads a source file that may (or may not) contain ES6 classes *or* arrow 3 | * functions, transforms them to ES5 compatible code using the pre-bundled ES6 4 | * visitors, and prints out the result. 5 | */ 6 | 7 | var fs = require('fs'); 8 | var jstransform = require('jstransform'); 9 | var utils = require('jstransform/src/utils'); 10 | 11 | var helper = require('../lib/helper.js') 12 | 13 | var Syntax = jstransform.Syntax; 14 | var knownLocs = []; 15 | 16 | /** 17 | * Indicates if the node has a member expression 18 | * node should be a function node 19 | */ 20 | function fnBodyHasUneligibleExpr(node, path, state) { 21 | return helper.containsImmediateUneligibleExpr(node, node); 22 | } 23 | 24 | 25 | /** 26 | * functions that 27 | * - are named 28 | * - have a `this` binding 29 | * cannot be simplified to arrow functions 30 | */ 31 | function isUneligibleForArrow(node, path, state) { 32 | //TODO named functions are uneligible only if referenced 33 | return node.id 34 | //only for small funcs TODO: should be configurable 35 | || (node.loc.end.line - node.loc.start.line) > 3 36 | || fnBodyHasUneligibleExpr(node, path, state); 37 | } 38 | 39 | /** 40 | * Main Visitor. 41 | * Replace basic functions to es6 notation. 42 | * Named functions are ignored because we would need to determine if the name is used somewhere. 43 | */ 44 | function simpleExprVisitor(traverse, node, path, state) { 45 | // HACK HACK HACK can't really tell why a node is being traversed twice 46 | // my guess is that when I call `traverse(fnBody.body, path, state);` 47 | // I'll enter the visitor again, and a second time because of the initial js parsing 48 | if(knownLocs.indexOf(node.loc) >=0) return; knownLocs.push(node.loc); 49 | 50 | //write params if any, then write arrow 51 | var renderFnParams = node.params.length 52 | ? helper.renderParams 53 | : helper.renderNoParams; 54 | 55 | 56 | if(node.params.length) { 57 | utils.catchup(node.params[0].range[0], state, helper.elideString) 58 | } 59 | renderFnParams(traverse, node, path, state); 60 | 61 | var fnBody = node.body; 62 | switch(fnBody.body[0].type) { 63 | case Syntax.ReturnStatement: 64 | utils.append('(', state); 65 | //elide "return " string so we can convert it to 66 | utils.catchup(fnBody.body[0].range[0]+'return'.length+1, state, helper.elideString); 67 | 68 | //TODO: hopefully we're removing the last ';' because we can't have that in these exprs 69 | fnBody.body[0].range[1] = fnBody.body[0].range[1] -1; 70 | traverse(fnBody.body, path, state); 71 | 72 | utils.append(')', state); 73 | //finally end the body 74 | //utils.catchupWhiteOut(node.body.range[1], state); 75 | utils.catchup(node.body.range[1]+1, state, helper.elideString); 76 | 77 | break; 78 | 79 | case Syntax.ExpressionStatement: 80 | utils.append('(', state); //void is the proper transpiling, because the expression returns undefined 81 | utils.catchup(fnBody.body[0].range[0], state, helper.elideString); 82 | 83 | traverse(fnBody.body[0], path, state); 84 | 85 | utils.append(')', state); 86 | //finally end the body 87 | //utils.catchupWhiteOut(node.body.range[1], state); 88 | utils.catchup(node.body.range[1]+1, state, helper.elideString); 89 | } 90 | } 91 | 92 | simpleExprVisitor.test = function(node, path, state) { 93 | return node.type === Syntax.ArrowFunctionExpression 94 | && node.body.body.length === 1 95 | && (node.body.body[0].type === Syntax.ReturnStatement 96 | || node.body.body[0].type === Syntax.ExpressionStatement) 97 | && !isUneligibleForArrow(node, path, state); 98 | }; 99 | 100 | 101 | exports.visitor = simpleExprVisitor; 102 | --------------------------------------------------------------------------------