├── .coveralls.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── Readme.md ├── _cmd.js ├── cmd.js ├── examples ├── auto │ ├── fixture.js │ └── index.js ├── loadable │ └── index.js └── transform │ ├── fixture.js │ └── index.js ├── index.js ├── lib └── auto.js ├── package.json ├── semantics.json └── test ├── api.js ├── behaviors.js ├── fixtures ├── original.js └── transformed.js └── transformations.js /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: bQAgHTWcHvzl2isStbMa8y653t25aTvWr -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | coverage/* 3 | *.log 4 | node_modules 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage 2 | coverage/* 3 | *.log 4 | node_modules 5 | *.yml 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Decofun – Debug tool 2 | 3 | Decofun is a JavaScript function deanonymizer. 4 | 5 | It parses your code and names any anonymous 6 | functions according to their context. 7 | 8 | [![Build Status](https://travis-ci.org/davidmarkclements/decofun.svg?branch=master)](https://travis-ci.org/davidmarkclements/decofun) 9 | 10 | [![Coverage Status](https://img.shields.io/coveralls/davidmarkclements/decofun.svg?bust)](https://coveralls.io/r/davidmarkclements/decofun?branch=master) 11 | 12 | Version 1.2.x 13 | 14 | - [install](#install) 15 | - [features](#features) 16 | - [tests](#tests) 17 | - [examples](#examples) 18 | 19 | 20 | ## Install 21 | 22 | ```sh 23 | npm i decofun 24 | ``` 25 | 26 | 27 | # Features 28 | 29 | ## Command Line Tool 30 | ``` 31 | sudo npm -g i decofun 32 | ``` 33 | New from version 1.2.x we can simply use the `deco` 34 | executable in place of `node` to instrument 35 | anonymous functions. 36 | 37 | ```sh 38 | deco examples/loadable 39 | ``` 40 | 41 | Since version 1.3.x the `deco` executable can also be 42 | instructed to use the [cute-stack](https://github.com/davidmarkclements/cute-stack) module to prettify stack traces, just add a `--cute` flag 43 | 44 | ```sh 45 | deco examples/loadable --cute 46 | ``` 47 | 48 | To set the `cute-stack`'s display type, 49 | pass it as the value of `--cute` 50 | 51 | ```sh 52 | deco examples/loadable --cute table 53 | ``` 54 | 55 | To set the stack size pass a number 56 | 57 | ```sh 58 | deco examples/loadable --cute 20 59 | ``` 60 | 61 | To set both the display type and number, 62 | pass a JSON array 63 | 64 | ```sh 65 | deco examples/loadable --cute '["table", 20]' 66 | ``` 67 | 68 | 69 | ## Automatic Instrumentation 70 | New from version 1.1.x, we can use decofun to automatically 71 | instrument any required modules, simply call the `auto` method, 72 | before requring any other modules 73 | 74 | ```javascript 75 | require('decofun').auto() 76 | var myMod = require('./lib/myModule.js') 77 | var somePub = require('somePublishedMod') 78 | ``` 79 | 80 | Both myMod and somePub will now have their anonymous functions named. 81 | 82 | Alternatively, decofun can be called directly as a 83 | function (without supplying a path argument) to 84 | achieve the same result: 85 | 86 | ```javascript 87 | require('decofun')() # same as require('decofun').auto() 88 | var myMod = require('./lib/myModule.js') 89 | var somePub = require('somePublishedMod') 90 | ``` 91 | 92 | To undo automatic instrumentation and restore former 93 | behavior, simply use the `restore` method: 94 | 95 | ```javascript 96 | var decofun = require('decofun'); 97 | decofun.auto(); 98 | var myMod = require('./aModToDebug') 99 | decofun.restore(); 100 | var anotherMod = require('./noDebugNeededThx') 101 | ``` 102 | 103 | ## Client-side Instrumenting 104 | 105 | For instrumenting browser code, you can use 106 | the [deco-server](https://npmjs.org/package/deco-server) module. 107 | 108 | There is a live `deco-server` running on heroku, for instance 109 | we can deanonymize jQuery by with the following URL: 110 | 111 | [http://decofun.herokuapp.com/?addr=http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js](http://decofun.herokuapp.com/?addr=http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js) 112 | 113 | A deco transform is also planned for those who 114 | use browserify - for now we can simply 115 | use a `deco-server` to host our browserified code. 116 | 117 | ## Programmatic Transform 118 | 119 | We can transform any string containing JavaScript with 120 | the `transform` method. 121 | 122 | ```javascript 123 | var decofun = require('decofun') 124 | var fs = require('fs'); 125 | var path = require('path') 126 | var fixture = fs.readFileSync(path.join(__dirname, './fixture.js')); 127 | 128 | console.log(decofun.transform(fixture)) 129 | ``` 130 | 131 | As with the `auto` method, transforms can also be achieved 132 | with the polymorphic function representing the decofun module. 133 | When we pass a string to the decofun function, it executes a 134 | transform (and when we don't pass a string it calls `decofun.auto`): 135 | 136 | ```javascript 137 | console.log(decofun(fixture)) 138 | 139 | //same thing: 140 | //console.log(decofun.transform(fixture)) 141 | 142 | ``` 143 | 144 | 145 | 146 | ## Tests 147 | 148 | Run tests with 149 | 150 | ``` 151 | npm test 152 | ``` 153 | 154 | 155 | 156 | ## Examples 157 | 158 | The examples folder is a good place to start. 159 | 160 | To see how a direct transform would work, 161 | from the install directory we could run these commands: 162 | 163 | ```sh 164 | cat node_modules/decofun/examples/transform/fixture.js #view the original 165 | node node_modules/decofun/examples/transform # view the result 166 | ``` 167 | 168 | To see instrumentation upon `require` (the Auto feature), 169 | run the following: 170 | 171 | ```sh 172 | cat node_modules/decofun/examples/auto/fixture.js #view the original 173 | node node_modules/decofun/examples/auto # view the result 174 | ``` 175 | 176 | 177 | 178 | 179 | ### functions assigned to variables 180 | Are labelled "of var | line N". 181 | 182 | ```javascript 183 | var myFn = function () {} 184 | ``` 185 | 186 | Transforms into: 187 | ```javascript 188 | var myFn = function asᅠvarᅠmyFnᅠㅣlineᅠ1 () {} 189 | ``` 190 | 191 | 192 | ### function parameters 193 | Are labelled "passed into | line N". 194 | 195 | ```javascript 196 | someFunc('blah', function () {}) 197 | 198 | ``` 199 | 200 | Transforms into: 201 | ```javascript 202 | someFunc('blah', function passedᅠintoᅠsomeFuncᅠㅣlineᅠ1 () {}) 203 | ``` 204 | 205 | 206 | ### method parameters 207 | Are labelled "passed into ː | line N". 208 | 209 | ```javascript 210 | obj.prop(function () { }) 211 | ``` 212 | 213 | Transforms into: 214 | ```javascript 215 | obj.prop(function passedᅠintoᅠobjːpropᅠㅣlineᅠ1 () { }) 216 | ``` 217 | 218 | 219 | ### sub-object method parameters 220 | Are labelled "passed into ː | line N". 221 | 222 | ```javascript 223 | obj.subobj.prop(function () { }) 224 | ``` 225 | 226 | Transforms into: 227 | ```javascript 228 | obj.subobj.prop(function passedᅠintoᅠsubobjːpropᅠㅣlineᅠ1 () { }) 229 | ``` 230 | 231 | 232 | ### returned functions 233 | Are labelled "returned from | line N". 234 | 235 | ```javascript 236 | function f() {return function () { }} 237 | ``` 238 | 239 | Transforms into: 240 | ```javascript 241 | function f() {return function returnedᅠfromᅠfᅠㅣlineᅠ1 () { }} 242 | ``` 243 | 244 | 245 | ### returned functions of returned anonymous functions 246 | Are labelled "returned from ᐸ ᐳ | line N". 247 | 248 | ```javascript 249 | function contain () { 250 | return function () { 251 | return function () { 252 | } 253 | } 254 | } 255 | ``` 256 | 257 | Transforms into: 258 | ```javascript 259 | function contain() { 260 | return function returnedᅠfromᅠcontainᅠㅣlineᅠ2 () { 261 | return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠcontainᅠᐳᅠㅣlineᅠ3 () { 262 | 263 | } 264 | } 265 | } 266 | 267 | ``` 268 | 269 | 270 | ### methods declared in object literals 271 | Are labelled "as property ㅣ line N". 272 | 273 | ```javascript 274 | function contain () { 275 | return { 276 | propInLiteral: function () {} 277 | } 278 | } 279 | ``` 280 | 281 | Transforms into: 282 | ```javascript 283 | function contain() { 284 | return { 285 | propInLiteral: function asᅠpropertyᅠpropInLiteralᅠㅣlineᅠ3 () {} 286 | } 287 | } 288 | ``` 289 | 290 | 291 | ### methods assigned to instantiated objects 292 | Are labelled "as property ㅣ line N". 293 | 294 | ```javascript 295 | var o = {}; o.p = function (cb) { } 296 | ``` 297 | 298 | Transforms into: 299 | ```javascript 300 | var o = {}; o.p = function asᅠpropertyᅠpᅠㅣlineᅠ1 (cb) { } 301 | ``` 302 | 303 | 304 | ### immediately invoked function expressions 305 | Are labelled "IIFEㅣ line N". 306 | 307 | ```javascript 308 | !function() {}() 309 | ;(function(){}()) 310 | ``` 311 | 312 | Transforms into: 313 | 314 | ```javascript 315 | !function IIFEᅠㅣlineᅠ1() {}() 316 | ;(function IIFEᅠㅣlineᅠ2(){}()) 317 | ``` 318 | 319 | # Kudos 320 | 321 | Sponsered by [nearForm](http://nearform.com) 322 | 323 | 324 | -------------------------------------------------------------------------------- /_cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var argv = require('minimist')(process.argv.slice(2)) 4 | var path = require('path'); 5 | var cute = require('cute-stack'); 6 | 7 | if (!argv._.length) { 8 | //tell parent process to simply spawn a REPL 9 | process.exit(209); 10 | return; 11 | } 12 | 13 | function parse() { 14 | try { 15 | argv.cute = JSON.parse(argv.cute) 16 | } catch(e) { 17 | console.log('shit', e); 18 | } 19 | } 20 | 21 | if (argv.cute) { 22 | parse(); 23 | console.log(argv.cute) 24 | if (Array.isArray(argv.cute)) { 25 | console.log('passing', argv.cute) 26 | cute.apply(cute, argv.cute); 27 | } else { 28 | cute(argv.cute !== true && argv.cute); 29 | } 30 | } 31 | 32 | require('./')(); 33 | require(path.join(process.cwd(), argv._[0])); -------------------------------------------------------------------------------- /cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * This tiny wrapper file checks for known node flags 5 | * and appends them when found, before invoking the "real" _cmd 6 | * executable. Inspired by similar mocha -> _mocha interactions 7 | */ 8 | 9 | var spawn = require('child_process').spawn 10 | , args = [ __dirname + '/_cmd' ]; 11 | 12 | process.argv.slice(2).forEach(function(arg){ 13 | var flag = arg.split('=')[0]; 14 | 15 | switch (flag) { 16 | case '-d': 17 | args.unshift('--debug'); 18 | args.push('--no-timeouts'); 19 | break; 20 | case 'debug': 21 | case '--debug': 22 | case '--debug-brk': 23 | args.unshift(arg); 24 | args.push('--no-timeouts'); 25 | break; 26 | case '-gc': 27 | case '--expose-gc': 28 | args.unshift('--expose-gc'); 29 | break; 30 | case '--gc-global': 31 | case '--harmony': 32 | case '--harmony-proxies': 33 | case '--harmony-collections': 34 | case '--harmony-generators': 35 | case '--no-deprecation': 36 | case '--prof': 37 | case '--basic-perf-prof': 38 | case '--throw-deprecation': 39 | case '--trace-deprecation': 40 | args.unshift(arg); 41 | break; 42 | default: 43 | if (0 == arg.indexOf('--trace')) args.unshift(arg); 44 | else args.push(arg); 45 | break; 46 | } 47 | }); 48 | var proc = spawn(process.argv[0], args, { stdio: 'inherit' }); 49 | proc.on('exit', function onExit(code, signal) { 50 | if (+code === 209) { 51 | proc = spawn('node', args.slice(1), { stdio: 'inherit' }); 52 | proc.on('exit', onExit); 53 | } 54 | process.on('exit', function(){ 55 | if (signal) { 56 | process.kill(process.pid, signal); 57 | } else { 58 | process.exit(code); 59 | } 60 | }); 61 | }); 62 | 63 | // terminate children. 64 | process.on('SIGINT', function () { 65 | proc.kill('SIGINT'); // calls runner.abort() 66 | proc.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die. 67 | process.kill(process.pid, 'SIGINT'); 68 | }); 69 | -------------------------------------------------------------------------------- /examples/auto/fixture.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | 3 | } 4 | module.exports.gravy = function () { 5 | return function () { 6 | return { 7 | prop: function () { 8 | setTimeout(function () { 9 | console.trace('Getting a trace...'); 10 | }, 10) 11 | 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /examples/auto/index.js: -------------------------------------------------------------------------------- 1 | require('../..')(); 2 | 3 | var fixture = require('./fixture') 4 | 5 | console.log(fixture.gravy+'') 6 | 7 | fixture.gravy()().prop(); 8 | -------------------------------------------------------------------------------- /examples/loadable/index.js: -------------------------------------------------------------------------------- 1 | function gravy () { 2 | return function () { 3 | return { 4 | prop: function () { 5 | setTimeout(function () { 6 | console.trace('Getting a trace...'); 7 | }, 10) 8 | 9 | } 10 | } 11 | } 12 | } 13 | 14 | console.log(gravy+''); 15 | gravy()().prop(); -------------------------------------------------------------------------------- /examples/transform/fixture.js: -------------------------------------------------------------------------------- 1 | function one (a, cb) { 2 | 3 | } 4 | 5 | 6 | one('blah', function () { 7 | 8 | }) 9 | 10 | function two () { 11 | return function () { } 12 | } 13 | 14 | 15 | function three () { 16 | return { 17 | shoe: function () {} 18 | } 19 | } 20 | 21 | function four () { 22 | return function () { 23 | return function () { 24 | 25 | } 26 | } 27 | } 28 | 29 | function five () { 30 | return function () { 31 | return function () { 32 | return function () { 33 | foo('blue', function () { 34 | 35 | }) 36 | } 37 | } 38 | } 39 | } 40 | 41 | 42 | var six = function () { 43 | 44 | } 45 | 46 | 47 | var seven = function (err, cb) { 48 | 49 | return function () { 50 | cb(function () { 51 | 52 | }) 53 | } 54 | 55 | } 56 | 57 | var o = {}; 58 | o.eight = function (cb) { } 59 | 60 | 61 | o.eight(function () { }) 62 | 63 | o.eight.nine = function () {} 64 | o.eight.nine(function () { }) 65 | 66 | var o2; 67 | 68 | o2 = function () { } 69 | 70 | 71 | ;(function () {}()) 72 | 73 | !function () { }() 74 | 75 | 76 | 77 | function toodeep () { 78 | return function () { 79 | return function () { 80 | return function () { 81 | 82 | return function () { 83 | return function () { 84 | return function () { 85 | 86 | return function () { 87 | return function () { 88 | return function () { 89 | 90 | return function () { 91 | 92 | } 93 | 94 | } 95 | } 96 | } 97 | 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | 107 | -------------------------------------------------------------------------------- /examples/transform/index.js: -------------------------------------------------------------------------------- 1 | var decofun = require('../..') 2 | var fs = require('fs'); 3 | var path = require('path') 4 | var fixture = fs.readFileSync(path.join(__dirname, './fixture.js')); 5 | 6 | console.log(decofun(fixture)) 7 | 8 | //same result as: 9 | //console.log(decofun.transform(fixture)) 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var falafel = require('falafel'); 2 | var fs = require('fs'); 3 | var semantics = require('./semantics.json') 4 | var space = '\uffa0'; 5 | 6 | module.exports = decofun; 7 | decofun.auto = require('./lib/auto')(decofun); 8 | decofun.restore = require('./lib/auto').restore; 9 | 10 | Object.keys(semantics).forEach(function (k) { 11 | semantics[k] = semantics[k].replace(/ /g, space); 12 | }) 13 | 14 | function nameFunc(fnstr, name) { 15 | return fnstr.replace(/function/, 'function ' + name); 16 | } 17 | 18 | var again; 19 | var output; 20 | 21 | rewrite.count = 0; 22 | rewrite.max = 44; 23 | function rewrite (src) { 24 | if (rewrite.count > rewrite.max) { 25 | again = false; 26 | rewrite.count = 0; 27 | return; 28 | } 29 | output = falafel(src, {loc: true}, function (node) { 30 | try { 31 | if (node.type !== 'FunctionExpression' || node.id) { 32 | return; 33 | } 34 | 35 | var name = ''; 36 | var pType = node.parent.type; 37 | if (pType === 'CallExpression') { 38 | 39 | if (node.parent.callee.name) { 40 | name = semantics.argTo + node.parent.callee.name; 41 | } else { 42 | 43 | if (!node.parent.callee.object) { 44 | name = 'IIFE'; 45 | 46 | } else { 47 | if (node.parent.callee.object.type === 'Identifier') { 48 | name = semantics.argTo + node.parent.callee.object.name + 'ː' + node.parent.callee.property.name; 49 | } 50 | 51 | if (node.parent.callee.object.type === 'MemberExpression') { 52 | name = semantics.argTo + node.parent.callee.object.property.name + 'ː' + node.parent.callee.property.name; 53 | } 54 | 55 | } 56 | 57 | 58 | } 59 | 60 | } 61 | 62 | if (pType === 'ReturnStatement') { 63 | if (!node.parent.parent.parent.id) { 64 | again = true; 65 | rewrite.count += 1; 66 | return; 67 | } 68 | 69 | 70 | 71 | if (Object.keys(semantics).map(function (k) { 72 | return node.parent.parent.parent.id.name.match(semantics[k]); 73 | }).filter(Boolean).length) { 74 | 75 | name = semantics.returnedFrom + 'ᐸ' + space + node.parent.parent.parent.id.name.split('ᅠㅣ')[0] + space + 'ᐳ' 76 | 77 | } else { 78 | name = semantics.returnedFrom + node.parent.parent.parent.id.name; 79 | } 80 | 81 | } 82 | 83 | if (pType === 'Property') { 84 | name = semantics.onProperty + node.parent.key.name; 85 | } 86 | 87 | if (pType === 'AssignmentExpression') { 88 | if (node.parent.left.property) { 89 | name = semantics.onProperty + node.parent.left.property.name; 90 | } else { 91 | name = semantics.ofVar + node.parent.left.name; 92 | } 93 | 94 | } 95 | 96 | if (pType === 'VariableDeclarator') { 97 | name = semantics.ofVar + node.parent.id.name; 98 | } 99 | 100 | name += 'ᅠㅣline' + space + node.loc.start.line; 101 | 102 | node.update(nameFunc(node.source(), name)) 103 | 104 | } catch (e) { 105 | //if failure should occur, just don't update the node. 106 | } 107 | }) 108 | 109 | if (again) { again = false; rewrite(output+''); } 110 | 111 | } 112 | 113 | function decofun(src) { 114 | if ((src instanceof Buffer) || typeof src === 'string') { 115 | return decofun.transform(src); 116 | } 117 | return module.exports.auto.apply(this, arguments); 118 | } 119 | 120 | 121 | decofun.transform = function (src) { 122 | rewrite.count = 0; 123 | rewrite(src+''); 124 | return output+''; 125 | } 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /lib/auto.js: -------------------------------------------------------------------------------- 1 | var originalCompile = module.__proto__._compile; 2 | 3 | module.exports = function (decofun) { 4 | return function () { 5 | module.__proto__._compile = (function (compile) { 6 | return function (content, filename) { 7 | this.deanonymized = true; 8 | compile.call(this, decofun(content), filename); 9 | } 10 | }(module._compile)); 11 | } 12 | } 13 | 14 | module.exports.restore = function () { 15 | Object.keys(require.cache).filter(function(k) { 16 | var mod = require.cache[k]; 17 | return mod.deanonymized 18 | }).forEach(function (k) { 19 | delete require.cache[k]; 20 | }) 21 | 22 | module.__proto__._compile = originalCompile; 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "decofun", 3 | "version": "1.3.0", 4 | "description": "", 5 | "main": "index.js", 6 | "bin": { 7 | "deco": "./cmd.js" 8 | }, 9 | "scripts": { 10 | "test": "npm run coverage", 11 | "test-no-cov": "./node_modules/.bin/mocha -u qunit", 12 | "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -u qunit", 13 | "coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", 14 | "cov-analysis": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- -u qunit" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/davidmarkclements/decofun.git" 19 | }, 20 | "author": "David Mark Clements", 21 | "license": "ISC", 22 | "dependencies": { 23 | "cute-stack": "^1.0.2", 24 | "falafel": "^0.3.1", 25 | "minimist": "^1.1.0" 26 | }, 27 | "devDependencies": { 28 | "chai": "^1.9.1", 29 | "istanbul": "^0.3.2", 30 | "mocha": "^1.21.4", 31 | "coveralls": "^2.11.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /semantics.json: -------------------------------------------------------------------------------- 1 | { 2 | "argTo": "passed into ", 3 | "returnedFrom": "returned from ", 4 | "onProperty": "as property ", 5 | "ofVar": "as var " 6 | } -------------------------------------------------------------------------------- /test/api.js: -------------------------------------------------------------------------------- 1 | var decofun = require('../') 2 | var should = require('chai').should(); 3 | var _ = '\uffa0'; //space 4 | 5 | 6 | //transformations: 7 | 8 | suite('functions assigned to declared variables') 9 | 10 | test('are labelled "of var | line N"', function () { 11 | var input = 'var myFn = function () {}' 12 | var expected = 'var myFn = function as'+_+'var'+_+'myFn'+_+'ㅣline'+_+'1 () {}' 13 | 14 | decofun(input).should.equal(expected) 15 | 16 | }) 17 | 18 | suite('functions assigned to variables after declaration') 19 | 20 | test('are labelled "of var | line N"', function () { 21 | var input = 'var myFn; myFn = function () {}' 22 | var expected = 'var myFn; myFn = function as'+_+'var'+_+'myFn'+_+'ㅣline'+_+'1 () {}' 23 | 24 | decofun(input).should.equal(expected) 25 | 26 | }) 27 | 28 | 29 | suite('function parameters') 30 | 31 | test('are labelled "passed into | line N"', function () { 32 | var input = 'someFunc(\'blah\', function () {})' 33 | var expected = 'someFunc(\'blah\', function passed'+_+'into'+_+'someFunc'+_+'ㅣline'+_+'1 () {})' 34 | 35 | decofun(input).should.equal(expected) 36 | 37 | }) 38 | 39 | 40 | suite('method parameters') 41 | 42 | test('are labelled "passed into ː | line N"', function () { 43 | var input = 'obj.prop(function () { })' 44 | var name = 'passed into objːprop ㅣline 1'.replace(/ /g, _); 45 | var expected = 'obj.prop(function '+name+' () { })' 46 | 47 | decofun(input).should.equal(expected) 48 | 49 | }) 50 | 51 | suite('sub-object method parameters') 52 | 53 | test('are labelled "passed into ː | line N"', function () { 54 | var input = 'obj.subobj.prop(function () { })' 55 | var name = 'passed into subobjːprop ㅣline 1'.replace(/ /g, _); 56 | var expected = 'obj.subobj.prop(function '+name+' () { })' 57 | 58 | decofun(input).should.equal(expected) 59 | 60 | }) 61 | 62 | 63 | suite('returned functions') 64 | 65 | test('are labelled "returned from | line N"', function () { 66 | var input = 'function f() {return function () { }}' 67 | var name = 'returned from f ㅣline 1'.replace(/ /g, _); 68 | var expected = 'function f() {return function '+name+' () { }}' 69 | 70 | decofun(input).should.equal(expected) 71 | 72 | }) 73 | 74 | 75 | 76 | suite('returned functions of returned anonymous functions') 77 | 78 | test('are labelled "returned from ᐸ ᐳ | line N"', function () { 79 | var input = function contain () { 80 | return function () { 81 | return function () { 82 | 83 | } 84 | } 85 | }.toString(); 86 | 87 | var firstName = 'returned from contain ㅣline 2'.replace(/ /g, _); 88 | var secondName = 'returned from ᐸ returned from contain ᐳ ㅣline 3'.replace(/ /g, _); 89 | 90 | var expected = ['function contain() {', 91 | ' return function ' + firstName + ' () { ', 92 | ' return function ' + secondName + ' () {', 93 | '', 94 | ' }', 95 | ' }', 96 | ' }'].join('\n') 97 | 98 | 99 | 100 | decofun(input).should.equal(expected) 101 | 102 | 103 | 104 | }) 105 | 106 | 107 | suite('methods declared in object literals') 108 | 109 | test('are labelled "as property ㅣ line N"', function () { 110 | var input = function contain () { 111 | return { 112 | propInLiteral: function () {} 113 | } 114 | }.toString() 115 | 116 | var name = 'as property propInLiteral ㅣline 3'.replace(/ /g, _) 117 | 118 | var expected = ['function contain() {', 119 | ' return {', 120 | ' propInLiteral: function ' + name + ' () {}', 121 | ' }', 122 | ' }'].join('\n') 123 | 124 | decofun(input).should.equal(expected) 125 | 126 | }) 127 | 128 | 129 | suite('methods assigned to instantiated objects') 130 | 131 | test('are labelled "as property ㅣ line N"', function () { 132 | var input = 'var o = {}; o.p = function (cb) { }' 133 | var name = 'as property p ㅣline 1'.replace(/ /g, _) 134 | var expected = 'var o = {}; o.p = function ' + name + ' (cb) { }' 135 | 136 | decofun(input).should.equal(expected) 137 | 138 | }) 139 | 140 | 141 | suite('immediately invoked function expressions') 142 | 143 | test('are labelled "IIFEㅣ line N"', function () { 144 | var input = '!function() {}()' 145 | var expected = '!function IIFE'+_+'ㅣline'+_+'1() {}()'; 146 | decofun(input).should.equal(expected) 147 | 148 | input = '(function(){}());' 149 | expected = '(function IIFE'+_+'ㅣline'+_+'1(){}());' 150 | 151 | decofun(input).should.equal(expected) 152 | 153 | }) 154 | 155 | //API: 156 | 157 | suite('decofun.auto') 158 | 159 | test('will transform modules loaded with require', function () { 160 | var transformed = require('./fixtures/transformed').test+'' 161 | decofun.auto(); 162 | var original = require('./fixtures/original').test+''; 163 | original.should.equal(transformed) 164 | 165 | decofun.restore(); 166 | }) 167 | 168 | suite('decofun.transform') 169 | 170 | test('performs the same as decofun(src)', function () { 171 | var input = 'var myFn = function () {}' 172 | var expected = 'var myFn = function as'+_+'var'+_+'myFn'+_+'ㅣline'+_+'1 () {}' 173 | 174 | decofun.transform(input).should.equal(expected) 175 | }) 176 | 177 | 178 | suite('decofun implicit auto via polymorphism') 179 | 180 | test('calls the auto method when no source string is passed', function () { 181 | var transformed = require('./fixtures/transformed').test+'' 182 | decofun(); 183 | var original = require('./fixtures/original').test+''; 184 | original.should.equal(transformed) 185 | 186 | decofun.restore(); 187 | }) 188 | 189 | suite('decofun.restore') 190 | 191 | test('will restore the require to pre-auto state', function () { 192 | decofun.auto(); 193 | var original = require('./fixtures/original').test+''; 194 | 195 | var transformed = require('./fixtures/transformed').test+'' 196 | original.should.equal(transformed) 197 | 198 | decofun.restore(); 199 | genuineOriginal = require('./fixtures/original').test+''; 200 | 201 | genuineOriginal.should.not.equal(transformed) 202 | }) 203 | 204 | 205 | //features 206 | 207 | suite('recursion safety') 208 | 209 | test('supports a maximum depth of returned functions', function () { 210 | var input = 'function toodeep () {\n return function () {\n return function () {\n return function () {\n \n return function () {\n return function () {\n return function () {\n \n return function () {\n return function () {\n return function () {\n\n return function () {\n \n } \n \n }\n }\n } \n\n }\n }\n } \n }\n }\n }\n}'; 211 | var expected = 'function toodeep () {\n return function returnedᅠfromᅠtoodeepᅠㅣlineᅠ2 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠㅣlineᅠ3 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠㅣlineᅠ4 () {\n \n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ6 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ7 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ8 () {\n \n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ10 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ11 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ12 () {\n\n return function () {\n \n } \n \n }\n }\n } \n\n }\n }\n } \n }\n }\n }\n}'; 212 | 213 | decofun(input).should.equal.expected; 214 | }) 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /test/behaviors.js: -------------------------------------------------------------------------------- 1 | var decofun = require('../') 2 | var should = require('chai').should(); 3 | 4 | suite('recursion safety') 5 | 6 | test('supports a maximum depth of returned functions', function () { 7 | var input = 'function toodeep () {\n return function () {\n return function () {\n return function () {\n \n return function () {\n return function () {\n return function () {\n \n return function () {\n return function () {\n return function () {\n\n return function () {\n \n } \n \n }\n }\n } \n\n }\n }\n } \n }\n }\n }\n}'; 8 | var expected = 'function toodeep () {\n return function returnedᅠfromᅠtoodeepᅠㅣlineᅠ2 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠㅣlineᅠ3 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠㅣlineᅠ4 () {\n \n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ6 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ7 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ8 () {\n \n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ10 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ11 () {\n return function returnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠᐸᅠreturnedᅠfromᅠtoodeepᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠᐳᅠㅣlineᅠ12 () {\n\n return function () {\n \n } \n \n }\n }\n } \n\n }\n }\n } \n }\n }\n }\n}'; 9 | 10 | decofun(input).should.equal.expected; 11 | }) 12 | -------------------------------------------------------------------------------- /test/fixtures/original.js: -------------------------------------------------------------------------------- 1 | module.exports.test = function () { 2 | return function () { 3 | return { 4 | prop: function () { 5 | setTimeout(function () { 6 | console.trace('Getting a trace...'); 7 | }, 10) 8 | 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/fixtures/transformed.js: -------------------------------------------------------------------------------- 1 | module.exports.test = function asᅠpropertyᅠtestᅠㅣlineᅠ1() { 2 | return function returnedᅠfromᅠᐸᅠasᅠpropertyᅠtestᅠᐳᅠㅣlineᅠ2 () { 3 | return { 4 | prop: function asᅠpropertyᅠpropᅠㅣlineᅠ4 () { 5 | setTimeout(function passedᅠintoᅠsetTimeoutᅠㅣlineᅠ5 () { 6 | console.trace('Getting a trace...'); 7 | }, 10) 8 | 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/transformations.js: -------------------------------------------------------------------------------- 1 | var decofun = require('../') 2 | var should = require('chai').should(); 3 | var _ = '\uffa0'; //space 4 | 5 | suite('functions assigned to declared variables') 6 | 7 | test('are labelled "of var | line N"', function () { 8 | var input = 'var myFn = function () {}' 9 | var expected = 'var myFn = function as'+_+'var'+_+'myFn'+_+'ㅣline'+_+'1 () {}' 10 | 11 | decofun(input).should.equal(expected) 12 | 13 | }) 14 | 15 | suite('functions assigned to variables after declaration') 16 | 17 | test('are labelled "of var | line N"', function () { 18 | var input = 'var myFn; myFn = function () {}' 19 | var expected = 'var myFn; myFn = function as'+_+'var'+_+'myFn'+_+'ㅣline'+_+'1 () {}' 20 | 21 | decofun(input).should.equal(expected) 22 | 23 | }) 24 | 25 | 26 | suite('function parameters') 27 | 28 | test('are labelled "passed into | line N"', function () { 29 | var input = 'someFunc(\'blah\', function () {})' 30 | var expected = 'someFunc(\'blah\', function passed'+_+'into'+_+'someFunc'+_+'ㅣline'+_+'1 () {})' 31 | 32 | decofun(input).should.equal(expected) 33 | 34 | }) 35 | 36 | 37 | suite('method parameters') 38 | 39 | test('are labelled "passed into ː | line N"', function () { 40 | var input = 'obj.prop(function () { })' 41 | var name = 'passed into objːprop ㅣline 1'.replace(/ /g, _); 42 | var expected = 'obj.prop(function '+name+' () { })' 43 | 44 | decofun(input).should.equal(expected) 45 | 46 | }) 47 | 48 | suite('sub-object method parameters') 49 | 50 | test('are labelled "passed into ː | line N"', function () { 51 | var input = 'obj.subobj.prop(function () { })' 52 | var name = 'passed into subobjːprop ㅣline 1'.replace(/ /g, _); 53 | var expected = 'obj.subobj.prop(function '+name+' () { })' 54 | 55 | decofun(input).should.equal(expected) 56 | 57 | }) 58 | 59 | 60 | suite('returned functions') 61 | 62 | test('are labelled "returned from | line N"', function () { 63 | var input = 'function f() {return function () { }}' 64 | var name = 'returned from f ㅣline 1'.replace(/ /g, _); 65 | var expected = 'function f() {return function '+name+' () { }}' 66 | 67 | decofun(input).should.equal(expected) 68 | 69 | }) 70 | 71 | 72 | 73 | suite('returned functions of returned anonymous functions') 74 | 75 | test('are labelled "returned from ᐸ ᐳ | line N"', function () { 76 | var input = function contain () { 77 | return function () { 78 | return function () { 79 | 80 | } 81 | } 82 | }.toString(); 83 | 84 | var firstName = 'returned from contain ㅣline 2'.replace(/ /g, _); 85 | var secondName = 'returned from ᐸ returned from contain ᐳ ㅣline 3'.replace(/ /g, _); 86 | 87 | var expected = ['function contain() {', 88 | ' return function ' + firstName + ' () { ', 89 | ' return function ' + secondName + ' () {', 90 | '', 91 | ' }', 92 | ' }', 93 | ' }'].join('\n') 94 | 95 | 96 | 97 | decofun(input).should.equal(expected) 98 | 99 | 100 | 101 | }) 102 | 103 | 104 | suite('methods declared in object literals') 105 | 106 | test('are labelled "as property ㅣ line N"', function () { 107 | var input = function contain () { 108 | return { 109 | propInLiteral: function () {} 110 | } 111 | }.toString() 112 | 113 | var name = 'as property propInLiteral ㅣline 3'.replace(/ /g, _) 114 | 115 | var expected = ['function contain() {', 116 | ' return {', 117 | ' propInLiteral: function ' + name + ' () {}', 118 | ' }', 119 | ' }'].join('\n') 120 | 121 | decofun(input).should.equal(expected) 122 | 123 | }) 124 | 125 | 126 | suite('methods assigned to instantiated objects') 127 | 128 | test('are labelled "as property ㅣ line N"', function () { 129 | var input = 'var o = {}; o.p = function (cb) { }' 130 | var name = 'as property p ㅣline 1'.replace(/ /g, _) 131 | var expected = 'var o = {}; o.p = function ' + name + ' (cb) { }' 132 | 133 | decofun(input).should.equal(expected) 134 | 135 | }) 136 | 137 | 138 | suite('immediately invoked function expressions') 139 | 140 | test('are labelled "IIFEㅣ line N"', function () { 141 | var input = '!function() {}()' 142 | var expected = '!function IIFE'+_+'ㅣline'+_+'1() {}()'; 143 | decofun(input).should.equal(expected) 144 | 145 | input = '(function(){}());' 146 | expected = '(function IIFE'+_+'ㅣline'+_+'1(){}());' 147 | 148 | decofun(input).should.equal(expected) 149 | 150 | }) --------------------------------------------------------------------------------