├── .gitignore ├── .jshintrc ├── .npmignore ├── History.md ├── bin └── redscript ├── lib ├── cmdLine.js ├── compile.js ├── render.js └── transform.js ├── license ├── package.json ├── readme.md ├── spec.md └── test ├── cmdLine-spec.coffee ├── compile-spec.coffee ├── examples ├── aliases.js ├── aliases.rs ├── functions.js ├── functions.rs ├── immutable.js ├── immutable.rs ├── module.js ├── module.rs ├── pipe_operator.js ├── pipe_operator.rs ├── sample.js └── sample.rs └── transform-spec.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | *.swo 11 | 12 | pids 13 | logs 14 | results 15 | 16 | npm-debug.log 17 | node_modules 18 | 19 | DS_Store 20 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": false, 4 | "es5": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": false, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | //"quotmark": true, 15 | "regexp": false, 16 | "undef": true, 17 | "unused": true, 18 | "strict": false, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "laxcomma": true, 22 | "laxbreak": true, 23 | "white": false 24 | "predef": [ 25 | "options", 26 | "state" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | test/ 3 | support 4 | support/ 5 | examples 6 | examples/ 7 | .gitmodules 8 | History.md 9 | Makefile 10 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | ### 2-12-13 0.0.9 2 | 3 | * added ruby-esque object literals 4 | * added `def foo` for methods inside object literal 5 | * added `def foo.bar` for methods outside object literal 6 | * added `def foo >>> bar` methods for attaching prototype 7 | 8 | ``` 9 | object myWidget 10 | key, "value" 11 | def sayHi(name) 12 | puts puts "Hello #{name}" 13 | end, 14 | def getKey 15 | return @key 16 | end 17 | end 18 | 19 | # define method outside of object literal 20 | def myWidget.sayBye 21 | puts "goodbye!" 22 | end 23 | 24 | # attaches baz to String's prototype 25 | def String >>> baz(x) 26 | # do stuff 27 | end 28 | ``` 29 | 30 | 31 | ### 2-11-13 0.0.8 32 | 33 | * added anonymous function blocks. These currently still need parens preceding the block (ex below). 34 | * dropping support for coffeescript style arrow functions in favor of block like syntax. 35 | 36 | ``` 37 | myBtn.on('click', do |x| 38 | e.preventDefault() 39 | @slideToggle "slow" 40 | end) 41 | ``` 42 | 43 | ### 2-6-13 0.0.7 44 | 45 | * added bracketless if/else if/else statements. 46 | * found bug were some keywords in string are getting transformed, working on a fix for this. 47 | * added string interpolation. You can add anything inside of #{....} except for curly brackets.... that won't compile. 48 | 49 | ### 2-1-13 0.0.6 50 | 51 | * Added `func` keyword. This now defines a function using a variable name. 52 | 53 | ``` 54 | func foo var foo = function() { 55 | puts "hello world" console.log("hello world"); 56 | end } 57 | 58 | func bar(p1, p2) var bar = function(p1, p2) { 59 | puts p1 + p2 console.log(p1 + p2); 60 | end } 61 | ``` 62 | * Added Ruby/CoffeeScript style case/switch statement. Switch is kept the same, as using Ruby's case would be confusing/awkward to most JS devs. A few caveats, currently you still must use break to prevent falling through, unless it's a one liner while/then. In that case the break gets appended automatically. Also the default keyword is still default. I can't use else without lexing the case statement first... perhaps this will change to else in the future. Vanilla Switch/Case still works as normal 63 | 64 | ### 2-1-13 0.0.5 65 | 66 | * Added arrow functions. These work like CoffeeScript with one caveat. They need to be closed with a `}` or end. This means using them as a callback will require `});` or `end);` for the time being. 67 | * 68 | 69 | ### 1-31-13 0.0.4 70 | 71 | * Added error message when no files are passed into compiler 72 | * Added alias for printf (node only, aliases `#process.stdout.write`) 73 | * Added aliases `do` and `end`, works with properly formatted do while loops 74 | * Added alias `puts` (aliased to console.log) 75 | * Fixed commenting out string interpolation " #{foo} " 76 | * Fixed issue with @ not working in 0.0.3, `@` now aliases `this` properly 77 | 78 | ### 1-26-13 0.0.3 79 | 80 | * Tests made for cmdLine.js and compile.js 81 | * File watch now works with individual files (dir watch coming soon) 82 | * Single line hash comments working 83 | * @ alias partially working, a floating @ for a context param will compile to @. 84 | 85 | ### 1-25-13 0.0.2 86 | * Lots of housecleaning 87 | 88 | ### 1-24-13 0.0.1 89 | 90 | * Setup app structure and basic file IO -------------------------------------------------------------------------------- /bin/redscript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /*! 3 | * RedScript compiler 4 | * Copyright(c) 2013 Adam Brodzinski 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Dependencies 10 | */ 11 | var fs = require('fs') 12 | , cmd = require('../lib/cmdLine') 13 | , render = require('../lib/render'); 14 | 15 | var args = process.argv.slice(2) 16 | , opts = cmd.getOptions(args) 17 | , files = cmd.getFiles(args); 18 | 19 | // make options from arglist global 20 | global.options = opts; 21 | 22 | // Initialize / reset entire state object 23 | process.on('state:reset', function() { 24 | global.state = { 25 | debug: false, 26 | declaredVars: [], 27 | ittIndex: 0, 28 | hasClass: false 29 | }; 30 | }); 31 | 32 | // init the state object 33 | process.emit('state:reset') 34 | 35 | 36 | // update each key's value in state 37 | process.on('state:update', function(obj) { 38 | for (var key in obj) { 39 | state[key] = obj[key]; 40 | } 41 | }); 42 | 43 | 44 | // Increment a key's value by one 45 | // @type {string} key 46 | process.on('ittIndex:inc', function(key) { 47 | //console.log('before inc:', state.ittIndex); 48 | state.ittIndex += 1; 49 | // send an updated count 50 | process.emit('state:send', state.ittIndex); 51 | //console.log(state); 52 | }); 53 | 54 | 55 | /** 56 | * If user passes watch flag, check every 100ms to see if the current 57 | * modification date is more than the previous check. If so, render file. 58 | */ 59 | if (opts.watchFiles && files.length) { 60 | files.forEach(function(file) { 61 | render(file); 62 | }); 63 | console.log(); 64 | console.log(">>> RedScript is watching for changes. Pres Ctrl-C to quit"); 65 | files.forEach(function(file) { 66 | fs.watchFile(file, {interval: 100}, function (curr, prev) { 67 | if (curr.mtime > prev.mtime) { 68 | render(file); 69 | } 70 | }); 71 | }); 72 | 73 | /** 74 | * If no watch flag is passed, render each file 75 | */ 76 | } else if (files.length) { 77 | files.forEach(function(file) { 78 | render(file); 79 | }); 80 | } else { 81 | console.log(""+ 82 | " No RedScript files specified, type --help for more info \n" + 83 | " redscript [filename1] [filename2] \n" + 84 | " redscript watch [filename1] [filename2]" 85 | ); 86 | } 87 | 88 | // OCD induced blank line 89 | 90 | -------------------------------------------------------------------------------- /lib/cmdLine.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * RedScript compiler 3 | * v 0.0.1 4 | * Copyright(c) 2013 Adam Brodzinski 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Dependencies 10 | */ 11 | var colors = require('colors'); 12 | 13 | /** 14 | * Find options flags in argv 15 | * @param {array} argList -- Pass in process.argV 16 | * @return {object} -- return options with boolean state 17 | * @api public 18 | */ 19 | exports.getOptions = function(argList){ 20 | // default options 21 | var options = { 22 | watchFiles: false, 23 | moduleType: 'commonjs', 24 | aliases: true, 25 | insertSemiColons: true, 26 | classInsertion: true 27 | }; 28 | 29 | argList.forEach(function(arg){ 30 | switch (arg) { 31 | case 'watch': // watch or -w or --watch, turn on watchFiles 32 | case '-w': 33 | case '--watch': 34 | options.watchFiles = true; 35 | break; 36 | case '--no-semicolon-comp': // turn off janky semicolon insertion 37 | options.insertSemiColons = false; 38 | break; 39 | case '--no-class-insertion': // turn off insertion of class methods 40 | options.classInsertion = false; 41 | break; 42 | default: // do nothing, it's a file (hopefully) 43 | } 44 | }); 45 | 46 | return options; 47 | }; 48 | 49 | 50 | /** 51 | * Filter through arguments and return only files. If the file 52 | * name does not have a .rs extension, an error is thrown. 53 | * 54 | * @param {array} argList -- expecting from process.argv 55 | * @return {array} -- list of files 56 | * @api public 57 | */ 58 | exports.getFiles = function(argList){ 59 | var files = []; 60 | // filter through args and return only files 61 | argList.forEach(function(arg){ 62 | // if it's a flag, fall through and do nothing 63 | if (arg === 'watch' 64 | || arg === '-w' 65 | || arg === '--watch' 66 | || arg === '--no-class-insertion') { 67 | } else { 68 | // it's a file, push to files array 69 | if (!/.+\.rs/i.test(arg)) 70 | throw new Error("✖ expected an .rs file"); 71 | files.push(arg); 72 | } 73 | }); 74 | return files; 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /lib/compile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RedScript compiler 3 | * Copyright(c) 2013 Adam Brodzinski 4 | * MIT Licensed 5 | */ 6 | 7 | var transform = require('./transform'); 8 | var colors = require('colors'); 9 | 10 | /** 11 | * #lastChar - Finds the last character in a string. 12 | * @return {string} - char 13 | */ 14 | String.prototype.lastChar = function(){ 15 | return this[this.length - 1]; 16 | }; 17 | 18 | /** 19 | * Checks to see if keyword is used inside a string 20 | * @param {string} match keyword to match 21 | * @param {string} ln line to match against 22 | * @return {bool} true if match is found 23 | */ 24 | function insideString(match, ln) { 25 | var regex = "['\"][\\w\\s]*" + match + "[\\w\\s]*['\"]"; 26 | var insideStr = new RegExp(regex, "g"); 27 | return insideStr.test(ln); 28 | } 29 | 30 | 31 | /** Export compile function 32 | * Takes a multi line string and splits each line into array lines. 33 | * Each line is processed and transformed if it matches the criteria. 34 | * Final array of strings are joined and returned. 35 | * @param {string} file 36 | * @return {string} 37 | * @api public 38 | */ 39 | module.exports = function(file, fileName) { 40 | var lines = file.split('\n'); 41 | var debug = false; 42 | var state = { 43 | hasModule: false, 44 | }; 45 | 46 | // reset state object for each file 47 | process.emit('state:reset'); 48 | 49 | // Iterate through each line and transform text 50 | lines = lines.map(function(line, index, array) { 51 | try { 52 | // normalize # comments to // 53 | line = transform.comments(line); 54 | 55 | // if line begins with a comment, return 56 | if (/^\/\//.test(line.trimLeft())) return line; 57 | 58 | line = transform.merge_immutable(line); 59 | 60 | line = transform.wrap_with_immutable(line); 61 | 62 | // wrap with _.chain(foo) if used with |> pipes 63 | line = transform.wrap_with_chain(line, index, array); 64 | 65 | // pipe operator 66 | line = transform.pipe_operator_single_line(line, index, array); 67 | line = transform.pipe_operator_multi_line(line, index, array); 68 | 69 | // public & priv module functions 70 | line = transform.define_function(line); 71 | 72 | // insert const keyword where declared 73 | line = transform.declare_const(line); 74 | 75 | // XXX TODO warn if using shorthand assignment for leaks 76 | // /^(\s*)([\w$]+\s+)(\/=|\*=|\-=|\+=|=|\|\|=)\s+(.*?)$/ 77 | 78 | // question mark operator 79 | line = transform.question_operator(line); 80 | 81 | line = transform.define_module(line, array, state); 82 | 83 | // ------------------ XXX refactor below into transform.js --------------- 84 | 85 | /** 86 | * Matches `when` OR `when` and `then`. 87 | * @type {RegExp} See function for more info 88 | */ 89 | var switchWhenThen = /(when)\s(.+)(then)(.+)|(when)\s(.+)/; 90 | 91 | 92 | /** 93 | * Alias `end` with `}` 94 | * fakes a negative lookbehind for a word char 95 | * @return {string} returns `}` if lookbehind is false 96 | */ 97 | line = line.replace(/(\w)?end(?!\w)/g, function($0, $1) { 98 | return $1 ? $0 : '}'; 99 | }); 100 | 101 | 102 | /** 103 | * Inserts brackets into switch statement 104 | * @param {string} $0 Entire match 105 | * @param {string} $1 Matches switch 106 | * @param {string} $2 Matches expression 107 | * @return {string} Processed switch line 108 | */ 109 | line = line.replace(/(switch) (.+)/, function($0, $1, $2) { 110 | return $1 + " (" + $2 + ") {"; 111 | }); 112 | 113 | 114 | /** 115 | * Replaces when with case. If then is found a one line 116 | * statement is assumed. This currently inserts a semicolon to prevent 117 | * errors with appended break. 118 | * @param {string} $0 Entire match 119 | * @param {string} $1 Matches `when` 120 | * @param {string} $2 Label 121 | * @param {string} $3 Matches `then` 122 | * @param {string} $4 clause (after then) 123 | * OR 124 | * @param {string} $5 Matches `when` 125 | * @param {string} $6 Label 126 | * @return {string} Processed when line 127 | */ 128 | line = line.replace(switchWhenThen, function($0, $1 ,$2 ,$3 ,$4 ,$5 ,$6) { 129 | // has `when` and `then` one liner 130 | if ($1 && $3) { 131 | return "case " + $2 + ":" + $4 + ' ; break;'; 132 | // line just has `when` 133 | } else if ($5) { 134 | return "case " + $6 + ":"; 135 | } 136 | }); 137 | 138 | 139 | /** 140 | * Replaces default with default: in a switch statement 141 | */ 142 | line = line.replace(/\bdefault\b(?!\:)/, function($0, $1) { 143 | // if entire line is just default 144 | if (line.trim() === 'default') return 'default:'; 145 | // else it's prob something else like `def default` 146 | else return $0; 147 | }); 148 | 149 | 150 | /** 151 | * Replaces an `else if` with `} else if () {` 152 | * @param {string} $0 Entire match 153 | * @param {string} $1 Matches `condition` 154 | */ 155 | line = line.replace(/else if (?!\s*\()(.+)/, function($0, $1) { 156 | // if `else if` is inside a string, bail out 157 | if (insideString("else if", line)) return $0; 158 | return '} else if (' + $1 + ') {'; 159 | }); 160 | 161 | 162 | /** 163 | * Replaces an `else` with `} else {` 164 | * @param {string} $0 Entire match 165 | */ 166 | line = line.replace(/else(?!(?:\s*\{)| if)/, function($0) { 167 | // if `else` is inside a string, bail out 168 | if (insideString("else", line)) return $0; 169 | return '} else {'; 170 | }); 171 | 172 | 173 | /** 174 | * Replaces an `if condition` with `if (condition) {` 175 | * @param {string} $0 Entire match 176 | * @param {string} $1 Matches `condition` 177 | */ 178 | line = line.replace(/if (?!\s*\()(.+)/, function($0, $1) { 179 | // if `if` is inside a string, bail out 180 | if (insideString("if", line)) return $0; 181 | return 'if (' + $1 + ') {'; 182 | }); 183 | 184 | 185 | /** 186 | * Conditional assignment operator 187 | * $0 matches `foo =|| bar` 188 | * $1 matches `foo` 189 | * $2 matches `bar` 190 | * @return {String} transformed line 191 | */ 192 | //var condAssignment = /([\w\$]+)\s*\|\|=\s*(.+)/; 193 | //line = line.replace(condAssignment, function($0, $1, $2) { 194 | ////if (insideString("||=", line)) return $0; 195 | //return $1 + ' = ' + $1 + ' || ' + $2; 196 | //}); 197 | 198 | if (debug) console.log(index + " " + line); 199 | 200 | // all done, this is not the return that returns final joined array (see below) 201 | return line; 202 | 203 | } catch (error) { 204 | var stacktrace = error.stack 205 | .split('\n') 206 | .splice(1) // remove redundant message 207 | .slice(0, 6) // only the first few lines are relevant 208 | .join('\n') 209 | .gray; // output a gray color 210 | 211 | var apology = "\n\nSorry, there was an error compiling " + fileName + 212 | " line " + (index + 1) + " :\n\n " + line.trim(); 213 | 214 | console.log(apology.red); 215 | console.log('\n Error:'.red, error.message.red, '\n'); 216 | console.log(stacktrace, '\n\n'); 217 | 218 | process.exit(); 219 | } 220 | }); 221 | 222 | lines.unshift("import Immutable from 'seamless-immutable';"); 223 | 224 | // de-dent module methods 225 | if (state.hasModule) { 226 | lines = lines.map(function(line) { 227 | var firstCharEmpty = line[0] && !line[0].trim(); 228 | var secondCharEmpty = line[1] && !line[1].trim(); 229 | 230 | if (firstCharEmpty && secondCharEmpty) { 231 | return line.slice(2); 232 | } else { 233 | return line; 234 | } 235 | }); 236 | } 237 | 238 | lines = lines.filter(function(line) { 239 | return ! line.match('RS_REMOVE_LINE'); 240 | }) 241 | 242 | // return transformed lines 243 | return lines.join('\n'); 244 | }; 245 | -------------------------------------------------------------------------------- /lib/render.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * RedScript compiler 3 | * v 0.0.1 4 | * Copyright(c) 2013 Adam Brodzinski 5 | * MIT Licensed 6 | */ 7 | 8 | 9 | /** 10 | * Dependencies 11 | */ 12 | 13 | var fs = require('fs') 14 | , compile = require('./compile') 15 | , cmd = require('../lib/cmdLine') 16 | , colors = require('colors') 17 | , opts = cmd.getOptions(process.argv.slice(2)); 18 | 19 | 20 | /** 21 | * Takes a string filename as input, fetches the files contents 22 | * and calles the compile method on it. Once finished, it's saved to disk 23 | * in the same directory as the source file. 24 | * 25 | * @param {string} filePath -- relative path to file 26 | * @api public 27 | */ 28 | module.exports = function(filePath) { 29 | var file, targetPath; 30 | 31 | // slice off file path and make it's extension .js 32 | targetPath = filePath.substring(0, filePath.lastIndexOf('.')) + '.js'; 33 | 34 | // read file from filePath and pass into #compile 35 | file = compile(fs.readFileSync(filePath, 'utf-8'), filePath); 36 | 37 | // write out file to disk 38 | fs.writeFileSync(targetPath, file); 39 | 40 | // tell user 41 | if (opts.watchFiles) 42 | console.log(">>> Change detected on " + filePath); 43 | console.log("✔ Writing ".green + targetPath); 44 | }; 45 | -------------------------------------------------------------------------------- /lib/transform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RedScript compiler 3 | * Copyright(c) 2013 Adam Brodzinski 4 | * MIT Licensed 5 | * 6 | * Transforms input line when regex matches otherwise returns 7 | * the same line untouched 8 | */ 9 | 10 | 11 | // Checks to see if keyword is used inside a string 12 | // match: String - keyword to match 13 | // ln: String - line to match against 14 | // -> Bool - true if found 15 | // 16 | function insideString(match, ln) { 17 | var regex = "['\"][\\w\\s]*" + match + "[\\w\\s]*['\"]"; 18 | var insideStr = new RegExp(regex, "g"); 19 | return insideStr.test(ln); 20 | } 21 | 22 | // check if array contains element 23 | // ex [1, 2].contains(2) -> true 24 | Array.prototype.contains = function(str) { 25 | var i = this.length; 26 | while (i--) { 27 | if (this[i] === str) { 28 | return true; 29 | } 30 | } 31 | return false; 32 | }; 33 | 34 | 35 | module.exports = { 36 | // defines public and private module functions 37 | // returns String a -> String b 38 | define_function: function(line) { 39 | // example: def bar(a, b) do 40 | // 41 | // (^\s*) begining of line 42 | // (def|defp) $1 public or private def 43 | // (.*?) $2 in betweend def ... do 44 | // \s* optional space 45 | // do opening keyword (bracket in JS) 46 | // \s*$ end of line 47 | var parts = line.match(/^(\s*)(defp\s|def\s)(.*?)\s*do\s*$/); 48 | if (!parts) return line; 49 | 50 | // ^( $1 51 | // \s*[\w$]+ function name 52 | // ) 53 | // ( $2 optional params including parens 54 | // \( literal parens 55 | // (.*?) $3 just params 56 | // \) literal parens 57 | // )? 58 | // \s*$ trailing space 59 | var middle = /^(\s*[\w$]+)(\((.*?)\))?\s*$/; 60 | 61 | var leading = parts[1]; 62 | var funcName = parts[3].trim().match(middle)[1]; 63 | var params = parts[3].trim().match(middle)[3] || ''; 64 | var _export = (parts[2].trim() === 'def') ? 'export ' : ''; 65 | 66 | return leading + _export + 'function ' + funcName + '('+ params +') {'; 67 | }, 68 | 69 | 70 | // merges immutable objects or arrays together 71 | // returns String a -> String b 72 | merge_immutable: function(line) { 73 | var newLine; 74 | 75 | // ex: [foo <- 1,2,3] 76 | // [ opening array 77 | // (.*) $1 array to concat into 78 | // (<-) $2 79 | // (.*) $3 content to be merged 80 | // ] closing array 81 | var regexArr = /\[(.*)(\<\-)(.*)\]/; 82 | 83 | // ex: {foo <- foo: true, bar: 2} 84 | // { opening object 85 | // (.*) $1 obj to merge into 86 | // (<-) $2 87 | // (.*) $3 content to be merged 88 | // } closing object 89 | var regexObj = /\{(.*)(\<\-)(.*)\}/; 90 | 91 | newLine = line.replace(regexArr, function($0, $1, $2, $3) { 92 | return $1.trim() + '.concat([' + $3 + ']);'; 93 | }); 94 | 95 | return newLine.replace(regexObj, function($0, $1, $2, $3) { 96 | return $1.trim() + '.merge({' + $3 + '});'; 97 | }); 98 | }, 99 | 100 | 101 | // wraps value to be piped with lodash #chain method 102 | // returns String a -> String b 103 | wrap_with_immutable: function(line) { 104 | // (return|=)\s* group $1 return OR = plus spaces 105 | // ( group $2 106 | // [.*] outermost array inc contents 107 | // | OR 108 | // {.*} outermost object inc. contents 109 | // ) 110 | var regex = /(return|=)\s*(\[.*\]|\{.*\})/ 111 | 112 | return line.replace(regex, function($0, $1, $2) { 113 | return $1 + ' Immutable(' + $2 + ')'; 114 | }); 115 | }, 116 | 117 | 118 | // One liner piping 119 | // returns String a -> String b 120 | pipe_operator_single_line: function(line, index, lines) { 121 | var newLine; 122 | if (insideString('|>', line)) return line; 123 | // line does not have a one liner pipeline 124 | if (!line.match(/(\=|return)(.*?)(\|\>)/)) return line; 125 | // if next line has a pipe operator 126 | if (lines[index] && lines[index + 1].match(/^\s*\|\>/)) return line; 127 | 128 | // http://rubular.com/r/wiBJtf12Vn 129 | // (^.+?) $1 value to pipe 130 | // \s* optional spaces 131 | // (\|\>) $2 |> pipe operator 132 | // (.*?)$ $3 tail minus first pipe ($2) 133 | // 134 | var parts = line.match(/(^.+?)\s*(\|\>)(.*?)$/); 135 | var head = parts[1] 136 | var tail = parts[2].concat(parts[3]); 137 | 138 | // process head depending on if it's immuttable or not 139 | if (head.match('Immutable')) { 140 | head = head.replace('Immutable', '_.chain(Immutable'); 141 | } 142 | else if (head.match(/^\s*return/)) { 143 | head = head.replace('return ', 'return _.chain('); 144 | } 145 | else if (head.match(/\s=\s/)) { 146 | head = head.replace('= ', '= _.chain('); 147 | } 148 | 149 | tail = tail.replace(/(\s*\|\>\s*)/g, function($0, $1) { 150 | return ').pipesCall('; 151 | }) 152 | 153 | return head + tail + ').value();' 154 | }, 155 | 156 | 157 | // wraps value to be piped with lodash #chain method 158 | // returns String a -> String b 159 | wrap_with_chain: function(line, index, lines) { 160 | var hasPipe = /^\s*\|\>/; 161 | var nextLine = lines[index + 1] || ''; 162 | 163 | if (line.match(hasPipe)|| !nextLine.match(hasPipe)) { 164 | return line; 165 | } 166 | // handle wrapping returned values 167 | else if (line.match(/^\s*return/)) { // starts with return 168 | return line.replace(/^return (.+)/, function($0, $1) { 169 | return 'return _.chain(' + $1 +')'; 170 | }); 171 | } 172 | // handle wrapping assignments eg, foo = [1, 2, 3] 173 | else if (line.match(/^\s*[\w$]+\s+\=\s+/)) { // starts with `foo =` 174 | return line.replace(/(^\s*[\w$]+\s+\=\s+)(.+)/, function($0, $1, $2) { 175 | return $1 + '_.chain(' + $2 +')'; 176 | }); 177 | } 178 | else if (!!line.trim()) { 179 | return '_.chain(' + line + ')'; 180 | } 181 | else { 182 | return line; 183 | } 184 | }, 185 | 186 | 187 | // Transforms pipe |> operator into a lo-dash call (mixed-in) 188 | // returns String a -> String b 189 | pipe_operator_multi_line: function(line, index, lines) { 190 | if (insideString('|>', line)) return line; 191 | 192 | // ^\s? // leading spaces 193 | // (\|\>\s*) // |> pipe operator inc trailing spaces 194 | // ([\.\w_]+\s*) // function/method name inc trailing spaces 195 | // (.*) // additional (optional) params 196 | // 197 | // $0 `|> my_func take 99` 198 | // $1 `|>` 199 | // $2 `my_func` 200 | // $3 `99` 201 | // 202 | var regex = /^(\|\>\s*)([\.\w_]+\s*)(.*)$/; 203 | 204 | return line.replace(regex, function($0, $1, $2, $3) { 205 | var comma; 206 | var newLine; 207 | var hasLambda = $3.match(/=>/); 208 | 209 | // strip parens 210 | if (!hasLambda) { 211 | $3 = $3.replace(/^\(|\)$/g, ''); 212 | } 213 | 214 | comma = $3 ? ', ' : ''; 215 | 216 | newLine = '.pipesCall(' + $2.trim() + comma + $3 + ')'; 217 | 218 | // if next line doesnt have a pipe operator 219 | if (!lines[index + 1].match(/^\s*\|\>/)) { 220 | newLine = newLine.trimRight() + '.value();'; 221 | } 222 | return newLine; 223 | }); 224 | }, 225 | 226 | 227 | // adds `const` to any declaration, works with shorthand operators 228 | // String a -> String b 229 | declare_const: function(line) { 230 | //^ 231 | // (\s*) $1 opts leading spaces 232 | // ([\w$]+\s*) $2 variable name & trailing spaces 233 | // (\=) $3 division equals OR 234 | // \s* opt spaces 235 | // (.*?) rest of expression to assign 236 | // $ 237 | var parts = line.match(/^(\s*)([\w$]+)\s*(\=)\s*(.*?)$/); 238 | if (!parts) return line; 239 | 240 | var leadingSpace = parts[1]; 241 | var name = parts[2].trim(); 242 | var operator = parts[3].trim(); 243 | var rest = parts[4]; 244 | 245 | return leadingSpace + 'const ' + name + ' ' + operator + ' ' + rest; 246 | }, 247 | 248 | 249 | // transform ? to a JS safe char foo? -> foo__q 250 | // String a -> String b 251 | question_operator: function(line) { 252 | // ([\w$]+) $1 root name 253 | // (\?) $2 ? suffix 254 | return line.replace(/([\w$]+)(\?)/, '$1__q'); 255 | }, 256 | 257 | 258 | // removes defmodule keyword since ES6 modules don't have 259 | // to be wrapped inside an object literal. Removes the last 260 | // line of the module to remove dangling module `end` 261 | // 262 | // state - Object used to keep state of file 263 | // hasModule: Bool - flag used to prepend import 264 | // 265 | // String a -> String b 266 | define_module: function(line, lines, state) { 267 | if (!line.match(/^\s*defmodule/)) return line; 268 | 269 | state.hasModule = true; 270 | var lline; 271 | var newLine = line.replace(/^\s*defmodule\s+([\w$]+)\s+do\s*$/, 'RS_REMOVE_LINE'); 272 | 273 | // ** side effect, removes last 'end' 274 | for (var i=1, n=lines.length; i < n; i++) { 275 | lline = lines[lines.length - i]; 276 | if (lline.match('end')) { 277 | lines[lines.length - i] = ''; 278 | return newLine; 279 | } 280 | } 281 | }, 282 | 283 | 284 | // XXX cleanup impl 285 | /** Convert Comments from # to // 286 | * @param {string} - accepts line as a string 287 | * @return {string} - transformed line 288 | * @api public 289 | */ 290 | comments: function(line) { 291 | if (line.match(/^\s*\#+/g)) { // line starts with comment 292 | return line.replace(/\#/, '//'); 293 | } else { 294 | return line; 295 | } 296 | }, 297 | }; 298 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Adam Brodzinski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redscript", 3 | "description": "RedScript, a Ruby Flavored Superset Language", 4 | "version": "0.1.0", 5 | "author": { 6 | "name": "Adam Brodzinski", 7 | "email": "adambrodzinski@gmail.com" 8 | }, 9 | "repository": "git://github.com/AdamBrodzinski/RedScript", 10 | "keywords": ["redscript", "compiler"], 11 | "bin": { 12 | "redscript": "./bin/redscript" 13 | }, 14 | "directories.lib":"./lib", 15 | "dependencies": { 16 | "colors": "*" 17 | }, 18 | "devDependencies": { 19 | "mocha": "*", 20 | "chai": "*" 21 | }, 22 | "scripts": { 23 | "test": "mocha -R spec", 24 | "prepublish": "npm prune" 25 | }, 26 | "preferGlobal": "true" 27 | } 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # RedScript 2.0 2 | 3 | 4 | ## Project is Deprecated 5 | 6 | After the recent changes in ReasonML there really isn't a valid reason to keep working on this project anymore as ReasonML does a much better job, provides more safety, and in my opinion has an even nicer syntax and developer exerience. 7 | 8 | https://reasonml.github.io/ 9 | 10 | ----------------------- 11 | 12 | 13 | 14 | ### It's like CoffeeScript but immutable & functional 15 | 16 | Why use yet another sugar for JavaScript? Here are some features that go beyond a nice sytax: 17 | 18 | * **Immutable data** 19 | * **Pattern matching in module functions** 20 | * **No support for classes or OO forces a functional (yet pragmatic) coding style** 21 | * **Think in *data transformations* with the pipe operator `|>` (like Unix pipes, F# & Elixir)** 22 | * **Standard Lib that utilizes Lodash (patched to be immutable)** 23 | * **CLI for compiling and creating projects ** 24 | * **Compile time type inference checking using Flow** 25 | 26 | 27 | RedScript was created to provide a first class functional experience in the browser. I was tired of trying to coerce JavaScript into being a functional language (by not using the bad parts by convention and using other libs). 28 | 29 | It is inspired from Elixir but it does not have all of the features ([ElixirScript](https://github.com/bryanjos/elixirscript) aims to do this). Our main goal is to provide easy interoperability with other JavaScript libraries (like CoffeeScript, not like Elm) while still providing a first class functional experience in JavaScript. 30 | 31 | 32 | #### To Install Globally and Run 33 | 34 | ``` 35 | # 2.0 is a work in progress, not all features implemented 36 | # Note, this compiler is just a prototype and is not production ready 37 | sudo npm install -g redscript 38 | redscript watch [fileName fileName2] 39 | ``` 40 | 41 | #### Think in pipes instead of nesting functions 42 | 43 | ```elixir 44 | # use functions like unix pipe 45 | 46 | "hello world " |> String.trim |> String.uppercase 47 | #>>> "HELLO WORLD" 48 | 49 | ["foo", "bar", " baz"] 50 | |> Enum.map((x) -> String.upcase(x)) 51 | |> inspect() 52 | |> take(2) 53 | 54 | # inspect-output ["FOO", "BAR", "BAZ"] 55 | # >>> ["FOO", "BAR"] 56 | ``` 57 | 58 | #### Immutable Data (not currently supported) 59 | Mutable data in JavaScript can lead to tricky bugs 60 | ```elixir 61 | state = {bar: 3} 62 | 63 | # throws an error 64 | state.bar = 10 65 | 66 | # create copy and merge in new key/values 67 | new_state = {state <- foo: 2, baz: 4} 68 | # >> {foo: 2, bar: 3, baz: 4} 69 | 70 | list = [1, 2] 71 | list_two = [5, 6] 72 | combined_list = [list <- 3, 4, list_two] 73 | ``` 74 | 75 | #### Modules exposing public functions (no classes!) 76 | 77 | ```elixir 78 | import {some_js_function} from "some-js-or-redscript-module" 79 | 80 | defmodule PhoneUtils do 81 | def normalize_number(number) do 82 | return number 83 | |> imported_func('-') 84 | |> remove_char(' ') 85 | |> remove_us_code 86 | end 87 | 88 | def remove_us_code(str) do 89 | return str.replace(/^+1/g, '') 90 | end 91 | 92 | defp remove_char(str, char) do 93 | return str.replace(/\s/g, '') 94 | end 95 | end 96 | ``` 97 | 98 | #### Built In Support For Lo-Dash 99 | Just snake case the [API you already know](https://lodash.com/docs). To keep things tidy, functions are placed in namespaces. Collections are in the Enum namespace, Array in List, Strings are in a String namespace, etc... (however you could still just import lo-dash ES6 style to remove the namespace). 100 | 101 | ```elixir 102 | # or use pipe operator with lo-dash 103 | List.take(["a", "b", "c"], 2) ["a", "b", "c"] |> List.take(2) 104 | #>> ["a", "b"] 105 | 106 | List.flatten_deep([1, [2, 3, [4]]]) [1, [2, 3, [4]]] |> List.flatten_deep 107 | #>> [1, 2, 3, 4] 108 | 109 | Enum.reject([1, 2, 3, 4], (n) => n % 2 == 0) [1, 2, 3, 4] |> Enum.reject((n) => n % 2 == 0) 110 | #>> [1, 3] 111 | ``` 112 | 113 | 114 | #### Pattern Matching Coming Soon! 115 | Pattern matching can eliminate the use of if statements and can really clean up code. This is on the backlog but PRs are welcome! 116 | ```elixir 117 | defmodule MyUtils do 118 | def count([]) do 119 | return 0 120 | end 121 | 122 | def count([head|tail]) do 123 | return 1 + count(tail) 124 | end 125 | end 126 | ``` 127 | 128 | 129 | ### [Full Syntax](https://github.com/AdamBrodzinski/RedScript/blob/master/spec.md) 130 | -------------------------------------------------------------------------------- /spec.md: -------------------------------------------------------------------------------- 1 | # RedScript Spec Sheet 2 | 3 | If you have suggestions on different syntax, please create 4 | an issue: [here](https://github.com/AdamBrodzinski/RedScript/issues). 5 | 6 |
7 | 8 | ## Variables 9 | 10 | RedScript has one kind of variable and does not need to be declared. 11 | However they have the same scoping rules as JavaScript's `let`. 12 | 13 | ```elixir 14 | result = 10 + 5 15 | result = result + 5 16 | # IO.inspect(result) 17 | # 15 18 | ``` 19 | 20 | Compiled JavaScript: 21 | ```javascript 22 | let result = 10 + 5 23 | result = result + 5 24 | // IO.inspect(result) 25 | // 15 26 | ``` 27 | 28 | 29 | 30 | ## Comments 31 | 32 | RedScript has one kind of comment, a single line. All text is ignored to the right of the `#`. 33 | 34 | ```coffeescript 35 | # this is a single line comment 36 | # commented_out_func() 37 | ``` 38 | 39 | ## String 40 | 41 | RedScript has one kind of string, a double quote. Single quotes will not compile.
42 | Strings can be interpolated with the `#{}` construct. 43 | 44 | ```elixir 45 | name = "Jane" 46 | "Hello #{name}" 47 | ``` 48 | 49 | Compiles to JavaScript: 50 | 51 | ```javascript 52 | let name = "Jane" 53 | `Hello ${name}` 54 | ``` 55 | 56 | 57 | ## Number 58 | 59 | Number is the same as the JavaScript implementation. 60 | 61 | ```javascript 62 | typeof 1 == "number" 63 | typeof 1.4 == "number" 64 | ``` 65 | 66 | ## Symbol 67 | 68 | Symbol is the same as the ES6 JavaScript implementation with a syntax similar to Elixir/Ruby. 69 | 70 | 71 | ```elixir 72 | env = :prod 73 | 74 | typeof :prod == "symbol" 75 | # >> true 76 | typeof env == "symbol" 77 | # >> true 78 | 79 | # use a symbol with a map (string keys are standard) 80 | res = {foo: 1, [:bar]: 2} 81 | ``` 82 | 83 | Compiles to JavaScript: 84 | 85 | ```javascript 86 | let env = Symbol.for(":prod") 87 | 88 | typeof Symbol.for(":prod") == "symbol" 89 | typeof env == "symbol" 90 | 91 | // use a symbol with a map (string keys are standard) 92 | let res = {foo: 1, [Symbol.for(":bar")]: 2} 93 | ``` 94 | 95 | 96 | 97 | ## Boolean 98 | 99 | Booleans are the same as JavaScript and many other languages. 100 | 101 | ```javascript 102 | true 103 | false 104 | ``` 105 | 106 | ## Null 107 | 108 | Null is the same as the JavaScript implementation. 109 | 110 | ```javascript 111 | null 112 | ``` 113 | 114 | 115 | ## Undefined 116 | 117 | Undefined is the same as the JavaScript implementation. 118 | 119 | ```javascript 120 | undefined 121 | ``` 122 | 123 | ## Basic Arithmetic 124 | 125 | Arithmetic is the same as the JavaScript implementation. 126 | 127 | ```elixir 128 | 1 + 2 129 | # 3 130 | 5 * 5 131 | # 25 132 | 10 / 2 133 | # 5 134 | ``` 135 | 136 | 137 | 138 | ## Anonymous functions 139 | 140 | Anonymous functions have the same scoping rules as JavaScript but the syntax has a skinny arrow instead of a fat arrow. 141 | 142 | ```elixir 143 | double_num = (x) -> x * 2 144 | ``` 145 | 146 | ```javascript 147 | let double_num = (x) => x * 2 148 | ``` 149 | 150 | 151 | ## Maps 152 | 153 | RedScript does not not have an "Object" type like JavaScript. It has 154 | a "Map" type that acts like an immutable JavaScript literal. To make a change 155 | you must copy the object and merge in the new key/values. 156 | 157 | You can copy maps in several ways. However `Map.put` and the literal notation are 158 | the most frequently used. `Map.put` is ideal for changing a map inside a pipeline. 159 | Both of these are similar to using ES6 spread syntax in JavaScript. 160 | 161 | If you *have* to mutate a map for performance reasons you have to explicitly 162 | call `Map.mutate` 163 | 164 | 165 | #### trying to mutate JS style will throw an error 166 | ```text 167 | map.bar = 4 168 | # or 169 | map["bar"] = 4 170 | 171 | Error: you can't mutate Maps 172 | Perhaps you want to try using: 173 | map_copy = {old_map <- bar: 4} 174 | or 175 | map_copy = Map.put(old_map, "bar", 4) 176 | ``` 177 | 178 | In order to change a value of a map you must use Map.put or the shorthand notation. 179 | 180 | ```elixir 181 | map = {foo: 1, bar: 2} 182 | map = Map.put(map, "bar", 4) 183 | IO.inspect(map) 184 | #log {foo: 1, bar: 4} 185 | 186 | # or literal notation 187 | map = {map <- foo: 2} 188 | IO.inspect(map) 189 | #log {foo: 2, bar: 4} 190 | ``` 191 | 192 | Compiled JavaScript 193 | 194 | ```javascript 195 | map = {foo: 1, bar: 2} 196 | map = Map.put(map, "bar", 4) 197 | IO.inspect(map) 198 | //log {foo: 1, bar: 4} 199 | 200 | // or literal notation 201 | map = {...map, foo: 2} 202 | IO.inspect(map) 203 | //log {foo: 2, bar: 4} 204 | ``` 205 | 206 | 207 | 208 | 209 | ## If Else Uness 210 | 211 | RedScript borrows the same sytax from Elixir for if, else, and unless 212 | 213 | ```elixir 214 | 215 | if foo > 2 do 216 | # do work 217 | end 218 | 219 | if foo > 2 do 220 | # do work 221 | else if foo > 2 222 | # do work 223 | else 224 | # do work 225 | end 226 | 227 | unless foo > 2 do 228 | # do work 229 | end 230 | ``` 231 | 232 | Compiled JavaScript 233 | 234 | ```javascript 235 | 236 | if (foo > 2) { 237 | // do work 238 | } 239 | 240 | if (foo > 2) { 241 | // do work 242 | } else if (foo > 2) { 243 | // do work 244 | } else { 245 | // do work 246 | } 247 | 248 | if (!(foo > 2)) { 249 | // do work 250 | } 251 | ``` 252 | 253 | 254 | -------------------------------------------------------------------------------- /test/cmdLine-spec.coffee: -------------------------------------------------------------------------------- 1 | chai = require 'chai'; chai.should(); expect = chai.expect; 2 | cmd = require '../lib/cmdLine' 3 | 4 | describe 'cmd#getOptions', -> 5 | it 'should have defaults with no flags', -> 6 | termArgs = ["source1.rs"] 7 | opts = cmd.getOptions(termArgs) 8 | opts.watchFiles.should.eq false 9 | #opts.moduleType.should.eq 'requirejs' 10 | #opts.ES5.should.eq true 11 | opts.aliases.should.eq true 12 | #opts.defMethods.should.eq true 13 | opts.insertSemiColons.should.eq true 14 | #opts.varTypes.should.eq true 15 | 16 | it 'should change value when watch flag is passed', -> 17 | opts = cmd.getOptions ["-w", "file.rs"] 18 | expect( opts.watchFiles ).to.eq true 19 | opts = cmd.getOptions ["file.rs","--watch"] 20 | expect( opts.watchFiles ).to.eq true 21 | opts = cmd.getOptions ["file.rs","watch"] 22 | expect( opts.watchFiles ).to.eq true 23 | 24 | it 'should change value when no-semi flag is passed', -> 25 | opts = cmd.getOptions ["source1.rs", "--no-semicolon-comp"] 26 | expect( opts.insertSemiColons ).to.eq false 27 | 28 | 29 | describe 'cmd#getFiles', -> 30 | it 'should return files', -> 31 | termArgs = ["source1.rs", "-w"] 32 | cmd.getFiles(termArgs).should.include "source1.rs" 33 | -------------------------------------------------------------------------------- /test/compile-spec.coffee: -------------------------------------------------------------------------------- 1 | chai = require 'chai'; chai.should(); expect = chai.expect 2 | 3 | compile = require '../lib/compile' 4 | 5 | describe '#compile', -> 6 | describe 'Comments', -> 7 | it 'should convert # comments to //', -> 8 | line = "# commented line" 9 | compile(line).should.eq '// commented line' 10 | it 'should allow JS comments', -> 11 | line = "// commented line" 12 | compile(line).should.eq '// commented line' 13 | it 'should not skip comments that are in the middle', -> 14 | line = "something # commented line" 15 | compile(line).should.eq 'something // commented line' 16 | it 'should not process line that starts with a comment', -> 17 | line = " # commented do" 18 | compile(line).should.eq ' // commented do' 19 | it 'should not comment out # inside a string', -> 20 | line = " $('#someClass') " 21 | compile(line).should.eq " $('#someClass') " 22 | 23 | describe '@ symbol', -> 24 | it 'should alias this', -> 25 | compile('@prop').should.eq 'this.prop' 26 | compile('@_prop').should.eq 'this._prop' 27 | compile('@$prop').should.eq 'this.$prop' 28 | it 'should work when @ is floating?', -> 29 | line = 'function (callBack, @)' 30 | compile(line).should.eq 'function (callBack, this)' 31 | line = 'function (callBack, @ )' 32 | compile(line).should.eq 'function (callBack, this )' 33 | it 'should work with a trailling dot', -> 34 | compile('@.foo').should.eq 'this.foo' 35 | compile('@.foo @bar').should.eq 'this.foo this.bar' 36 | compile('@_foo').should.eq 'this._foo' 37 | compile('@$foo').should.eq 'this.$foo' 38 | it 'should work when two @ are used', -> 39 | compile('@prop1 = @prop2').should.eq 'this.prop1 = this.prop2' 40 | 41 | describe 'do end aliases', -> 42 | #it 'should alias correctly', -> 43 | #line = ''' 44 | #function foo() do 45 | #end 46 | #''' 47 | #compile(line).should.eq ''' 48 | #function foo() { 49 | #} 50 | #''' 51 | it 'should not alias a do loop', -> 52 | line = 'do { ' 53 | compile(line).should.eq 'do { ' 54 | #it 'should pass when used on a single line', -> 55 | #line = 'function foo() do return 1*2 end' 56 | #compile(line).should.eq 'function foo() { return 1*2 }' 57 | #compile(' do end ').should.eq ' { } ' 58 | it 'should pass lookbehind tests', -> 59 | compile('doing').should.eq 'doing' 60 | compile('ending').should.eq 'ending' 61 | it 'should not transform strings' 62 | #compile(' "my string do and end "').should.eq ' "my string do and end "' 63 | 64 | describe 'kludgey end- alias', -> 65 | it 'should alias `end-` `end);`', -> 66 | compile('end-').should.eq '});' 67 | 68 | 69 | describe '#puts', -> 70 | it 'should pass with parens', -> 71 | compile('puts(foo)').should.eq 'console.log(foo)' 72 | compile('puts(method(param))').should.eq 'console.log(method(param))' 73 | it 'should pass without parens', -> 74 | compile('puts foo').should.eq 'console.log(foo);' 75 | compile('puts "bar"').should.eq 'console.log("bar");' 76 | compile('puts(foo); puts "bar"').should.eq 'console.log(foo); console.log("bar");' 77 | compile('puts method(param)').should.eq 'console.log(method(param));' 78 | 79 | describe '#printf', -> 80 | it 'should pass with parens', -> 81 | compile('printf(foo)').should.eq 'process.stdout.write(foo)' 82 | compile('printf(method(param))').should.eq 'process.stdout.write(method(param))' 83 | it 'should pass without parens', -> 84 | compile('printf foo').should.eq 'process.stdout.write(foo);' 85 | compile('printf "bar"').should.eq 'process.stdout.write("bar");' 86 | compile('printf(foo); printf "bar"').should.eq 'process.stdout.write(foo); process.stdout.write("bar");' 87 | compile('printf method(param)').should.eq 'process.stdout.write(method(param));' 88 | 89 | describe 'func', -> 90 | it 'should alias to function using parens', -> 91 | compile('func foo()').should.eq 'var foo = function() {' 92 | compile('func $bar(foo)').should.eq 'var $bar = function(foo) {' 93 | compile('func $bar(foo, bar)').should.eq 'var $bar = function(foo, bar) {' 94 | it 'should alias to function with bang and question chars' 95 | #compile('func hasUser?()').should.eq 'var hasUser_Q = function() {' 96 | #compile('func replace!()').should.eq 'var replace_B = function() {' 97 | it 'should have optional parens', -> 98 | compile('func bar').should.eq 'var bar = function() {' 99 | compile('func $_bar').should.eq 'var $_bar = function() {' 100 | 101 | describe 'switch statement', -> 102 | it 'should compile properly', -> 103 | redSwitch = ''' 104 | switch fruit() 105 | when "Oranges" 106 | alert("oranges"); 107 | break; 108 | when "Apples" then alert() 109 | default 110 | alert("something") 111 | end 112 | ''' 113 | compile(redSwitch).should.eq ''' 114 | switch (fruit()) { 115 | case "Oranges": 116 | alert("oranges"); 117 | break; 118 | case "Apples" : alert() ; break; 119 | default: 120 | alert("something") 121 | } 122 | ''' 123 | 124 | describe 'if statement', -> 125 | it 'should alias to if with parens', -> 126 | line = 'if foo === 10' 127 | compile(line).should.eq 'if (foo === 10) {' 128 | it 'should not transform if with parens', -> 129 | compile('if (err) throw err;').should.eq 'if (err) throw err;' 130 | compile('if (foo === 10) {').should.eq 'if (foo === 10) {' 131 | it 'should not convert strings', -> 132 | compile(' "foo b if bar" ').should.eq ' "foo b if bar" ' 133 | compile(' "i saw a gif on reddit" ').should.eq ' "i saw a gif on reddit" ' 134 | 135 | describe 'else statement', -> 136 | it 'should transform to else with brackets', -> 137 | compile('else').should.eq '} else {' 138 | compile(' else ').should.eq ' } else { ' 139 | it 'should not transform if it already has brackets', -> 140 | compile('else {').should.eq 'else {' 141 | compile('} else {').should.eq '} else {' 142 | compile('}else{').should.eq '}else{' 143 | compile('} else {').should.eq '} else {' 144 | it 'should not transform an else if statement', -> 145 | compile('else if ').should.eq 'else if ' 146 | it 'should not convert strings', -> 147 | compile(' "foo else b else bar" ').should.eq ' "foo else b else bar" ' 148 | 149 | describe 'elsif statement', -> 150 | it 'should transform to else if with brackets', -> 151 | compile('elsif foo').should.eq '} else if (foo) {' 152 | compile(' elsif foo').should.eq ' } else if (foo) {' 153 | compile('elsif foo === 20').should.eq '} else if (foo === 20) {' 154 | it 'should not convert strings', -> 155 | compile(' "foo elsif baz bar" ').should.eq ' "foo elsif baz bar" ' 156 | 157 | describe 'else if statement', -> 158 | it 'should transform to else if with brackets', -> 159 | compile('else if foo').should.eq '} else if (foo) {' 160 | compile(' else if foo').should.eq ' } else if (foo) {' 161 | compile('else if foo === 20').should.eq '} else if (foo === 20) {' 162 | it 'should not transform if it already has parens', -> 163 | compile('} else if (foo === 20) {').should.eq '} else if (foo === 20) {' 164 | compile('} else if (foo){').should.eq '} else if (foo){' 165 | it 'should not convert strings', -> 166 | compile(' "foo else if baz bar" ').should.eq ' "foo else if baz bar" ' 167 | 168 | describe 'string interpolation', -> 169 | it 'should convert to string concatination', -> 170 | line = '"Hello #{name}, how are you?"' 171 | compile(line).should.eq '"Hello " + name + ", how are you?"' 172 | 173 | it 'should not concat right side if interp is on right edge of quotes', -> 174 | line = ''' 175 | "Hello #{name}" 176 | ''' 177 | compile(line).should.eq ''' 178 | "Hello " + name 179 | ''' 180 | it 'should not concat left side if interp is on left edge of quotes', -> 181 | line = ''' 182 | "#{name} Hello" 183 | ''' 184 | compile(line).should.eq ''' 185 | name + " Hello" 186 | ''' 187 | it 'should wrap scary interpolated chars inside parens', -> 188 | line = ''' 189 | "#{foo + bar} Hello" 190 | ''' 191 | compile(line).should.eq ''' 192 | (foo + bar) + " Hello" 193 | ''' 194 | it 'should wrap scary interpolated chars inside parens', -> 195 | line = ''' 196 | "foo #{2 * 3 - 3 / 7 % 2} Hello" 197 | ''' 198 | compile(line).should.eq ''' 199 | "foo " + (2 * 3 - 3 / 7 % 2) + " Hello" 200 | ''' 201 | describe 'anonymous function block', -> 202 | it 'should work without params', -> 203 | compile('on("change", do').should.eq 'on("change", function() {' 204 | compile('method( do').should.eq 'method( function() {' 205 | it 'should work with params', -> 206 | compile('method( do |x|').should.eq 'method( function(x) {' 207 | compile('method( do |x,y|').should.eq 'method( function(x,y) {' 208 | line = 'readFile("passwd", do |err, data|' 209 | compile(line).should.eq 'readFile("passwd", function(err, data) {' 210 | it 'should work without a preceding comma', -> 211 | line = 'get("/users/:user" do |x|' 212 | compile(line).should.eq 'get("/users/:user" , function(x) {' 213 | line = 'get("/users/:user" do' 214 | compile(line).should.eq 'get("/users/:user" , function() {' 215 | 216 | describe 'object litteral', -> 217 | it 'should transform to vanilla object syntax', -> 218 | compile('object objectName').should.eq 'var objectName = {' 219 | compile(' object _$Bar').should.eq ' var _$Bar = {' 220 | it 'should not transform object inside of strings', -> 221 | compile(' "i love object lamp" ').should.eq ' "i love object lamp" ' 222 | 223 | describe 'def methods', -> 224 | it 'should work with parens inside an object literal', -> 225 | compile('def foo(p1, p2)').should.eq 'foo: function(p1, p2) {' 226 | compile('def Bo_$o()').should.eq 'Bo_$o: function() {' 227 | it 'should work without parens inside an object literal', -> 228 | compile('def foo').should.eq 'foo: function() {' 229 | compile('def Bo_$o').should.eq 'Bo_$o: function() {' 230 | it 'should not transform `def foo.bar` or `def foo >> bar`' 231 | #compile('def foo.bar').should.eq 'def foo.bar' 232 | #compile('def Bo_$o >>> baz').should.eq 'def Bo_$o >>> baz' 233 | it 'should transform def default properly', -> 234 | compile('def default').should.eq 'default: function() {' 235 | 236 | describe 'def foo.bar methods', -> 237 | it 'should work with parens', -> 238 | compile('def foo.bar(p1, p2)').should.eq 'foo.bar = function(p1, p2) {' 239 | compile('def foo.Bo_$o()').should.eq 'foo.Bo_$o = function() {' 240 | it 'should work without parens inside an object literal', -> 241 | compile('def foo.bar').should.eq 'foo.bar = function() {' 242 | compile('def foo.Bo_$o').should.eq 'foo.Bo_$o = function() {' 243 | 244 | describe 'def proto methods', -> 245 | it 'should work with parens', -> 246 | compile('def foo >> bar(p1, p2)').should.eq 'foo.prototype.bar = function(p1, p2) {' 247 | compile('def foo >> Bo_$o()').should.eq 'foo.prototype.Bo_$o = function() {' 248 | it 'should work without parens inside an object literal', -> 249 | compile('def foo >> bar').should.eq 'foo.prototype.bar = function() {' 250 | compile('def foo >> Bo_$o').should.eq 'foo.prototype.Bo_$o = function() {' 251 | 252 | describe 'Conditional Assigment Operator', -> 253 | it 'should work', -> 254 | compile('app ||= {}').should.eq 'app = app || {}' 255 | compile('foo ||= bar').should.eq 'foo = foo || bar' 256 | compile('foo ||= bar').should.eq 'foo = foo || bar' 257 | compile('_foo ||= b$_ar').should.eq '_foo = _foo || b$_ar' 258 | # Regex test cases: http://gskinner.com/RegExr/?33qm8 259 | 260 | describe 'Bracketless for in', -> 261 | it 'should compile without conflicts', -> 262 | compile('for key in obbj').should.eq 'for (var key in obbj) {' 263 | 264 | -------------------------------------------------------------------------------- /test/examples/aliases.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | // this is a single line comment 3 | 4 | //# 5 | block comments 6 | this should be 7 | commented out 8 | //# 9 | 10 | 11 | // The at sign should is aliased to this 12 | @myProperly = false 13 | 14 | // It also works correctly when a period is appended 15 | @.myProperty = false 16 | -------------------------------------------------------------------------------- /test/examples/aliases.rs: -------------------------------------------------------------------------------- 1 | # this is a single line comment 2 | 3 | ## 4 | block comments 5 | this should be 6 | commented out 7 | ## 8 | 9 | 10 | # The at sign should is aliased to this 11 | @myProperly = false 12 | 13 | # It also works correctly when a period is appended 14 | @.myProperty = false 15 | -------------------------------------------------------------------------------- /test/examples/functions.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | // compiles to eq. JavaScript, this example does not 3 | // include the module syntax: `defmodule` as that is 4 | // a separate transform (see test/examples/module.rs) 5 | // 6 | // export function foo() { 7 | // return "baz" 8 | // } 9 | // 10 | // # private func 11 | 12 | // function baz() { 13 | // return "baz" 14 | // } 15 | 16 | export function foo() { 17 | return "baz" 18 | } 19 | 20 | export function foo_two() { 21 | return "baz" 22 | } 23 | 24 | export function bar(a, b) { 25 | return "baz" 26 | } 27 | 28 | function baz() { 29 | return "baz" 30 | } 31 | 32 | function baz(a, b) { 33 | return "baz" 34 | } 35 | -------------------------------------------------------------------------------- /test/examples/functions.rs: -------------------------------------------------------------------------------- 1 | # compiles to eq. JavaScript, this example does not 2 | # include the module syntax: `defmodule` as that is 3 | # a separate transform (see test/examples/module.rs) 4 | # 5 | # export function foo() { 6 | # return "baz" 7 | # } 8 | # 9 | # # private func 10 | 11 | # function baz() { 12 | # return "baz" 13 | # } 14 | 15 | def foo do 16 | return "baz" 17 | end 18 | 19 | def foo_two() do 20 | return "baz" 21 | end 22 | 23 | def bar(a, b) do 24 | return "baz" 25 | end 26 | 27 | defp baz do 28 | return "baz" 29 | end 30 | 31 | defp baz(a, b) do 32 | return "baz" 33 | end 34 | -------------------------------------------------------------------------------- /test/examples/immutable.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | // const arr = Immutable([1, 2, 3]); 3 | const arr = Immutable([1, 2, 3]) 4 | const arr = Immutable([1, {foo: 1}, [2,[3]] , 3]) 5 | 6 | return Immutable([1, 2, 3]) 7 | 8 | // doesnt convert json 9 | const json = { 10 | foo: [1, 2, 3] 11 | } 12 | 13 | // const arr = Immutable({foo: 1, bar: 2}); 14 | const arr = Immutable({foo: 1, bar: 2}) 15 | const arr = Immutable({one: 1, two: {three: []}}) 16 | 17 | // merge/concat 18 | 19 | // const state = state.merge({foo: 2, bar: 3}); 20 | const state = Immutable({isValid: false}) 21 | const state2 = state.merge({ isValid: true, loading: false}); 22 | 23 | // const list2 = list.concat([1, 2, 3]); 24 | const list = Immutable([1, 2, 3]) 25 | const list2 = list.concat([ 4, 5, 6]); 26 | -------------------------------------------------------------------------------- /test/examples/immutable.rs: -------------------------------------------------------------------------------- 1 | # const arr = Immutable([1, 2, 3]); 2 | arr = [1, 2, 3] 3 | arr = [1, {foo: 1}, [2,[3]] , 3] 4 | 5 | return [1, 2, 3] 6 | 7 | # doesnt convert json 8 | json = { 9 | foo: [1, 2, 3] 10 | } 11 | 12 | # const arr = Immutable({foo: 1, bar: 2}); 13 | arr = {foo: 1, bar: 2} 14 | arr = {one: 1, two: {three: []}} 15 | 16 | # merge/concat 17 | 18 | # const state = state.merge({foo: 2, bar: 3}); 19 | state = {isValid: false} 20 | state2 = {state <- isValid: true, loading: false} 21 | 22 | # const list2 = list.concat([1, 2, 3]); 23 | list = [1, 2, 3] 24 | list2 = [list <- 4, 5, 6] 25 | -------------------------------------------------------------------------------- /test/examples/module.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | export function add(a, b) { 3 | return a + b 4 | } 5 | 6 | export function multiply(a, b) { 7 | return a * b 8 | } 9 | 10 | function say_hi() { 11 | console.log("hello") 12 | } 13 | 14 | -------------------------------------------------------------------------------- /test/examples/module.rs: -------------------------------------------------------------------------------- 1 | defmodule Calculator do 2 | def add(a, b) do 3 | return a + b 4 | end 5 | 6 | def multiply(a, b) do 7 | return a * b 8 | end 9 | 10 | defp say_hi do 11 | console.log("hello") 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/examples/pipe_operator.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | // single line pipes not finished 3 | const bar = _.chain(Immutable([1, 2, 3])).pipesCall(Enum.uppcase).value(); 4 | 5 | // [1, 2, 3] .pipesCall(Enum.uppcase).pipesCall(Enum.uppcase).value(); 6 | // [1, 2, 3] |> Enum.uppcase |> Enum.uppcase 7 | 8 | const f = _.chain(Immutable([1, 2, 3])).pipesCall(Enum.uppcase(3, 2)).pipesCall(Enum.uppcase).value(); 9 | return _.chain(Immutable([5, 2, 3])).pipesCall(Enum.uppcase).value(); 10 | const b = _.chain(bar).pipesCall(Enum.uppcase).value(); 11 | return _.chain(bar).pipesCall(Enum.uppcase).value(); 12 | 13 | 14 | const foo = 20 15 | 16 | return _.chain(foo) 17 | .pipesCall(multi_no_parens, 10, 20) 18 | .pipesCall(multi_has_parens, 10, 20) 19 | .pipesCall(no_parens) 20 | .pipesCall(empty_parens).value(); 21 | 22 | 23 | const res = _.chain(foo) 24 | .pipesCall(has_parens, 10, 20) 25 | .pipesCall(map, (x) => x * 2) 26 | .pipesCall(map, ((x) => x * 2)) 27 | .pipesCall(map, (x => x * 2)) 28 | .pipesCall(map, x => x * 2).value(); 29 | 30 | -------------------------------------------------------------------------------- /test/examples/pipe_operator.rs: -------------------------------------------------------------------------------- 1 | # single line pipes not finished 2 | bar = [1, 2, 3] |> Enum.uppcase 3 | 4 | # [1, 2, 3] .pipesCall(Enum.uppcase).pipesCall(Enum.uppcase).value(); 5 | # [1, 2, 3] |> Enum.uppcase |> Enum.uppcase 6 | 7 | f = [1, 2, 3] |> Enum.uppcase(3, 2) |> Enum.uppcase 8 | return [5, 2, 3] |> Enum.uppcase 9 | b = bar |> Enum.uppcase 10 | return bar |> Enum.uppcase 11 | 12 | 13 | foo = 20 14 | 15 | return foo 16 | |> multi_no_parens 10, 20 17 | |> multi_has_parens(10, 20) 18 | |> no_parens 19 | |> empty_parens() 20 | 21 | 22 | res = foo 23 | |> has_parens(10, 20) 24 | |> map (x) => x * 2 25 | |> map((x) => x * 2) 26 | |> map(x => x * 2) 27 | |> map x => x * 2 28 | 29 | -------------------------------------------------------------------------------- /test/examples/sample.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | // auto declared constants 3 | const bar = 2 4 | 5 | // perhaps throw warning/error if user uses these 6 | // as they will be global and leak 7 | bar ||= 2 8 | bar += 2 9 | bar *= 2 10 | bar /= 10 11 | 12 | // Alias @ with this. 13 | @someProp = foo 14 | 15 | // valid__q = true 16 | valid__q = true 17 | 18 | // double check ||= behaves with Immutable 19 | opts ||= Immutable({thing: false}) 20 | opts ||= state.merge({ thing: false}); 21 | 22 | if (foo == false do) { 23 | // do stuff 24 | } 25 | 26 | if (isValid__q do) { 27 | return true 28 | } else { 29 | return false 30 | } 31 | 32 | 33 | $('#someID').on('click', (foo, bar) => 34 | // do stuff 35 | }); 36 | 37 | 38 | const Foo = React.createClass({ 39 | render() { 40 | return ( 41 |
42 | Hello World! 43 |
44 | ); 45 | }, 46 | }) 47 | -------------------------------------------------------------------------------- /test/examples/sample.rs: -------------------------------------------------------------------------------- 1 | # auto declared constants 2 | bar = 2 3 | 4 | # perhaps throw warning/error if user uses these 5 | # as they will be global and leak 6 | bar ||= 2 7 | bar += 2 8 | bar *= 2 9 | bar /= 10 10 | 11 | # Alias @ with this. 12 | @someProp = foo 13 | 14 | # valid__q = true 15 | valid? = true 16 | 17 | # double check ||= behaves with Immutable 18 | opts ||= {thing: false} 19 | opts ||= {state <- thing: false} 20 | 21 | if foo == false do 22 | # do stuff 23 | end 24 | 25 | if isValid? do 26 | return true 27 | else 28 | return false 29 | end 30 | 31 | 32 | $('#someID').on('click', (foo, bar) => 33 | # do stuff 34 | end); 35 | 36 | 37 | Foo = React.createClass({ 38 | render() { 39 | return ( 40 |
41 | Hello World! 42 |
43 | ); 44 | }, 45 | }) 46 | -------------------------------------------------------------------------------- /test/transform-spec.coffee: -------------------------------------------------------------------------------- 1 | chai = require 'chai'; chai.should(); expect = chai.expect 2 | 3 | ts = require '../lib/transform' 4 | 5 | # ---------------- Mock `/bin/redscript` events ---------------- 6 | state = {} 7 | # Reset entire state object 8 | process.on 'state:reset', -> 9 | state = 10 | debug: false, 11 | declaredVars: [], 12 | ittIndex: 0 13 | 14 | # remove any listeners from previous mocha run 15 | process.removeAllListeners 'ittIndex:inc' 16 | 17 | process.on 'ittIndex:inc', (key) -> 18 | state.ittIndex += 1 19 | # send an updated count 20 | process.emit('state:send', state.ittIndex) 21 | 22 | global.options = 23 | watchFiles: false 24 | moduleType: 'requirejs' 25 | aliases: true 26 | insertSemiColons: true 27 | classInsertion: false 28 | # -------------------------------------------------------------- 29 | 30 | describe 'method parentProperty', -> 31 | it 'should transform `parent*` to `__proto__`', -> 32 | ts.parentProperty('parent*').should.eq '__proto__' 33 | it 'should work next to an object literal', -> 34 | ts.parentProperty('parent*: foo,').should.eq '__proto__: foo,' 35 | it 'should work when assigning to a method with dot opperator', -> 36 | ts.parentProperty('bar.parent* = foo').should.eq 'bar.__proto__ = foo' 37 | # Regex test cases - http://regexr.com?342i2 38 | 39 | describe 'method objLiteral', -> 40 | it 'should transform `<` to `__proto__', -> 41 | line = 'object bar < foo' 42 | ts.objLiteral(line).should.eq 'var bar = { __proto__: foo,' 43 | line = 'object _$bar < _f$oo' 44 | ts.objLiteral(line).should.eq 'var _$bar = { __proto__: _f$oo,' 45 | # Regex test cases - bit.ly/ZHcSKt 46 | 47 | describe 'method insertVars', -> 48 | it 'should insert `var` when needed', -> 49 | line = 'foo = true' 50 | ts.insertVars(line, []).should.eq 'var foo = true' 51 | it 'should work with multiple declarations', -> 52 | line = 'foo = true; bar = false' 53 | ts.insertVars(line, []).should.eq 'var foo = true; var bar = false' 54 | it 'should work if one var is decalred', -> 55 | line = 'foo = true; var bar = 20' 56 | ts.insertVars(line, []).should.eq 'var foo = true; var bar = 20' 57 | it 'should skip over declarations with var', -> 58 | line = 'var baz = 10' 59 | ts.insertVars(line, []).should.eq 'var baz = 10' 60 | it 'should only declare var once on a one liner', -> 61 | line = 'baz = 10; baz = 10' 62 | ts.insertVars(line, []).should.eq 'var baz = 10; baz = 10' 63 | it 'should only assign first in mult assignment', -> 64 | line = 'foo = baz = 10' 65 | ts.insertVars(line, []).should.eq 'var foo = baz = 10' 66 | it 'shouldnt match `==` or `===`', -> 67 | line = 'baz == 10' 68 | ts.insertVars(line, []).should.eq 'baz == 10' 69 | line = 'baz === 10' 70 | ts.insertVars(line, []).should.eq 'baz === 10' 71 | it 'shouldnt insert var on properties or instance vars', -> 72 | line = '@foo = 20' 73 | ts.insertVars(line, []).should.eq '@foo = 20' 74 | line = 'foo.bar = 20' 75 | ts.insertVars(line, []).should.eq 'foo.bar = 20' 76 | it 'should recognize manually declared vars as declared', -> 77 | line = 'var foo = 40; foo = 20' 78 | ts.insertVars(line, []).should.eq 'var foo = 40; foo = 20' 79 | it 'shouldnt insert a var if its already declared', -> 80 | vstate = [] 81 | line1 = 'foo = 12' 82 | line2 = 'foo = 20' 83 | ts.insertVars(line1, vstate).should.eq 'var foo = 12' 84 | ts.insertVars(line2, vstate).should.eq 'foo = 20' 85 | # Regex test cases - http://bit.ly/X5ls6a 86 | 87 | describe 'method classes', -> 88 | it 'should transform a single class', -> 89 | line = 'class Foo' 90 | ts.classes(line).should.eq 'var Foo = Class.extend({' 91 | it 'should transform inheriting classes', -> 92 | line = 'class Bar < Foo' 93 | ts.classes(line).should.eq 'var Bar = Foo.extend({' 94 | it 'should transform a class using dot notation', -> 95 | line = 'class App.Model < Backbone.Model' 96 | ts.classes(line).should.eq 'App.Model = Backbone.Model.extend({' 97 | # Regex test cases - http://regexr.com?342po 98 | 99 | describe 'method _super', -> 100 | it 'should transform super to this._super', -> 101 | line = 'super f00_$' 102 | ts.callSuper(line).should.eq 'this._super(f00_$);' 103 | it 'should transform without args', -> 104 | line = 'super' 105 | ts.callSuper(line).should.eq 'this._super();' 106 | it 'should transform with multiple args', -> 107 | line = 'super foo, bar, baz' 108 | ts.callSuper(line).should.eq 'this._super(foo, bar, baz);' 109 | it 'should not transform inside a string or comment', -> 110 | line = ' "this is super cool" ' 111 | ts.callSuper(line).should.eq ' "this is super cool" ' 112 | # Regex test cases - http://regexr.com?342qs 113 | 114 | describe 'method whileLoop', -> 115 | it 'should transform correctly', -> 116 | line = 'while foo < 200' 117 | ts.whileLoop(line).should.eq 'while (foo < 200) {' 118 | it 'should not compile a while loop with brackets', -> 119 | line = 'while (foo < 2) {moo}' 120 | ts.whileLoop(line).should.eq 'while (foo < 2) {moo}' 121 | it 'should accept parens as its parameter', -> 122 | line = 'while (x - 2) / 2' 123 | ts.whileLoop(line).should.eq 'while ((x - 2) / 2) {' 124 | # Regex test cases - http://bit.ly/X0vXdg 125 | 126 | describe 'method untilLoop', -> 127 | it 'should transform into a while loop with comment', -> 128 | line = 'until foo === 5' 129 | ts.untilLoop(line).should.eq 'while (!( foo === 5 )) { //until' 130 | it 'should accept parens as a parameter', -> 131 | line = 'until (x - 2) / 2' 132 | ts.untilLoop(line).should.eq 'while (!( (x - 2) / 2 )) { //until' 133 | # Regex test cases - http://bit.ly/106gt3K 134 | 135 | describe 'method forLoopRang', -> 136 | it 'should transform for in with range to for loop', -> 137 | line = 'for i in 0..5' 138 | ts.forLoopRange(line).should.eq 'for (var i=0; i < 5; i++) {' 139 | it 'should transform with three dots', -> 140 | line = 'for i in 0...5' 141 | ts.forLoopRange(line).should.eq 'for (var i=0; i <= 5; i++) {' 142 | it 'should handle variables', -> 143 | line = 'for ct in b..e' 144 | ts.forLoopRange(line).should.eq 'for (var ct=b; ct < e; ct++) {' 145 | # Regex test cases - bit.ly/16Ofgn2 146 | 147 | describe 'method forIn', -> 148 | it 'should transform correctly', -> 149 | line = 'for key in obj' 150 | ts.forIn(line).should.eq 'for (var key in obj) {' 151 | it 'should transform with an obj literal', -> 152 | line = 'for key in {one: 1}' 153 | ts.forIn(line).should.eq 'for (var key in {one: 1}) {' 154 | # Regex test cases - bit.ly/ZkxXHv 155 | 156 | describe 'method forInArr', -> 157 | beforeEach -> 158 | process.emit 'state:reset' #console.log "resetting state" 159 | it 'should transform into a for loop', -> 160 | line = 'for fruit inArr basket' 161 | ts.forInArr(line).should.eq 'for (var i1=0, len1=basket.length; i1 ' + 162 | '< len1; i1++) { var fruit = basket[i1];' 163 | it 'should alias `inStr` to `inArr` for iterating strings', -> 164 | line = 'for char inStr myString' 165 | ts.forInArr(line).should.eq 'for (var i1=0, len1=myString.length; i1 < ' + 166 | 'len1; i1++) { var char = myString[i1];' 167 | # Regex test cases - bit.ly/WPApt4 168 | 169 | describe 'method forKeyVal', -> 170 | it 'should transform key and value correctly', -> 171 | line = 'for key,val in obj' 172 | ts.forKeyVal(line).should.eq 'for (var key in obj) { var val = obj[key];' 173 | line = 'for $k , _v in users' 174 | ts.forKeyVal(line).should.eq 'for (var $k in users) { var _v = users[$k];' 175 | # Regex test cases - bit.ly/13r7y3b 176 | 177 | describe 'method func', -> 178 | it 'should transform with name and params', -> 179 | line = 'func foo (a, b)' 180 | ts.func(line).should.eq 'var foo = function(a, b) {' 181 | it 'should pass with name & empty parens', -> 182 | line = 'func foo()' 183 | ts.func(line).should.eq 'var foo = function() {' 184 | it 'should transfrom with name & without params', -> 185 | line = 'func bar' 186 | ts.func(line).should.eq 'var bar = function() {' 187 | it 'should transfrom without name & with params', -> 188 | line = 'func(a, b)' 189 | ts.func(line).should.eq 'function(a, b) {' 190 | it 'it should pass without name and empty parens', -> 191 | line = 'func()' 192 | ts.func(line).should.eq 'function() {' 193 | it 'should transfrom without name & without params', -> 194 | line = 'func bar' 195 | ts.func(line).should.eq 'var bar = function() {' 196 | it 'should transform with just func keyword', -> 197 | line = 'func' 198 | ts.func(line).should.eq 'function() {' 199 | it 'should pass using parens', -> 200 | ts.func('func foo()').should.eq 'var foo = function() {' 201 | ts.func('func $bar(foo)').should.eq 'var $bar = function(foo) {' 202 | ts.func('func $bar(foo, bar)').should.eq 'var $bar = function(foo, bar) {' 203 | it 'should have optional parens', -> 204 | ts.func('func bar').should.eq 'var bar = function() {' 205 | ts.func('func $_bar').should.eq 'var $_bar = function() {' 206 | # Regex test cases - bit.ly/YBld3l 207 | 208 | describe 'method comments', -> 209 | it 'should transform # comments into // comments', -> 210 | line = '# this is a commented line' 211 | ts.comments(line).should.eq '// this is a commented line' 212 | it 'should not comment out a # inside of strings', -> 213 | line = ' "foo === 2 # comment about foo" ' 214 | ts.comments(line).should.eq ' "foo === 2 # comment about foo" ' 215 | it 'should convert # comments to //', -> 216 | line = "# commented line" 217 | ts.comments(line).should.eq '// commented line' 218 | it 'should allow JS comments', -> 219 | line = "// commented line" 220 | ts.comments(line).should.eq '// commented line' 221 | it 'should not skip comments that are in the middle', -> 222 | line = "something # commented line" 223 | ts.comments(line).should.eq 'something // commented line' 224 | it 'should not process line that starts with a comment', -> 225 | line = " # commented do" 226 | ts.comments(line).should.eq ' // commented do' 227 | it 'should not comment out # inside a string', -> 228 | line = " $('#someClass') " 229 | ts.comments(line).should.eq " $('#someClass') " 230 | #http://bit.ly/YH5rke 231 | 232 | describe 'method privateBlock', -> 233 | it 'should transform private an iife beginning', -> 234 | line = 'private' 235 | ts.privateBlock(line).should.eq ';(function() {' 236 | it 'should transform with surrounding spaces', -> 237 | line = ' private ' 238 | ts.privateBlock(line).should.eq ' ;(function() { ' 239 | # Regex test cases - bit.ly/ZtC2wB 240 | it 'should transform endPriv into ending iifee', -> 241 | line = 'endPriv' 242 | ts.privateBlock(line).should.eq '})();' 243 | it 'should transform endPriv with surrounding spaces', -> 244 | line = ' endPriv ' 245 | ts.privateBlock(line).should.eq ' })(); ' 246 | # Regex test cases - bit.ly/Ye61cO 247 | 248 | describe 'method cjsRequire', -> 249 | it 'should transform a short require call', -> 250 | line = ' require "http"' 251 | ts.cjsRequire(line).should.eq 'var http = require("http");' 252 | it 'shouldnt allow a short call with file path', -> 253 | line = 'require "../lib/Widget"' 254 | ts.cjsRequire(line).should.eq 'var Widget = require("../lib/Widget");' 255 | it 'should transform a long require call', -> 256 | line = ' require "http" as myHttp' 257 | ts.cjsRequire(line).should.eq 'var myHttp = require("http");' 258 | it 'should transform a long require call with property', -> 259 | line = ' require "http".prop as myHttp' 260 | ts.cjsRequire(line).should.eq 'var myHttp = require("http").prop;' 261 | # Regex test cases - http://bit.ly/11ehvgU 262 | 263 | --------------------------------------------------------------------------------