├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── lib ├── injector.js └── profiler.js ├── package.json └── test ├── fixtures ├── if-block-return │ ├── input.js │ └── output.js ├── if-else-no-block-return │ ├── input.js │ └── output.js ├── if-no-block-return │ ├── input.js │ └── output.js └── this-member │ ├── input.js │ └── output.js ├── main.js ├── test.html └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.sjsp.js 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-sjsp - Simple JavaScript Profiler 2 | 3 | [![Build Status](https://secure.travis-ci.org/45deg/node-sjsp.png?branch=master)](http://travis-ci.org/45deg/node-sjsp) 4 | 5 | ## What is it 6 | 7 | This is a JavaScript profiler implemented in Node.js, inspired by [sjsp](https://github.com/itchyny/sjsp), which is implemented in Haskell. 8 | 9 | ## How to install 10 | 11 | ``` 12 | npm -g install node-sjsp 13 | ``` 14 | 15 | ## Usage 16 | 17 | If you want to inject profiling code into `something.js`, run 18 | ``` 19 | sjsp something.js 20 | ``` 21 | and then sjsp generates `something.sjsp.js` in the same directory. 22 | (you can use wildcard characters such that `sjsp *.js`) 23 | 24 | Next, rewrite your HTML code using `something.js` like below. 25 | ```html 26 | 27 | 28 | ``` 29 | 30 | Open the page with your browser and you can see profiles in the JavaScript console every 10 seconds. (you can change this interval by -i option) 31 | ``` 32 | ========== SORT BY TIME ========== 33 | time: 0.60sec count: 1777 something.js test1 (line: 7, col: 17) function test1(){ 34 | time: 0.60sec count: 1701 something.js test0 (line: 1, col: 17) function test0(){ 35 | time: 0.58sec count: 1601 something.js test4 (line: 25, col: 17) function test4(){ 36 | time: 0.57sec count: 1703 something.js test2 (line: 13, col: 17) function test2(){ 37 | time: 0.54sec count: 1632 something.js test3 (line: 19, col: 17) function test3(){ 38 | time: 0.53sec count: 1586 something.js test5 (line: 31, col: 17) function test5(){ 39 | ========== SORT BY COUNT ========== 40 | time: 0.60sec count: 1777 something.js test1 (line: 7, col: 17) function test1(){ 41 | time: 0.57sec count: 1703 something.js test2 (line: 13, col: 17) function test2(){ 42 | time: 0.60sec count: 1701 something.js test0 (line: 1, col: 17) function test0(){ 43 | time: 0.54sec count: 1632 something.js test3 (line: 19, col: 17) function test3(){ 44 | time: 0.58sec count: 1601 something.js test4 (line: 25, col: 17) function test4(){ 45 | ``` 46 | 47 | For details, see [original document](https://github.com/itchyny/sjsp#usage) 48 | 49 | ## How it works 50 | 51 | See [original document](https://github.com/itchyny/sjsp#how-it-works) 52 | 53 | ## Limitation 54 | 55 | This profiling is available for browser only now. 56 | 57 | ## Author 58 | 59 | 45deg ([Twitter](https://twitter.com/___zoj)) 60 | 61 | ## LICENSE 62 | 63 | MIT 64 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander'); 4 | var fs = require('fs'); 5 | var injector = require('./lib/injector'); 6 | var packageJson = require('./package.json'); 7 | 8 | program 9 | .version(packageJson.version) 10 | .usage('') 11 | .option('-i, --interval ', "interval time of logging the result in seconds (default 10)") 12 | .option('-p, --print', "print out the compiled result to stdout") 13 | .parse(process.argv); 14 | 15 | 16 | if(!program.args.length) { 17 | program.help(); 18 | } else { 19 | var interval = parseInt(program.interval) || 10; 20 | var files = program.args; 21 | var isStdout = program.print; 22 | 23 | files.forEach(function(fileName){ 24 | if(fileName.match(/sjsp.js$/)) return; 25 | 26 | var targetFileName = fileName.replace(/js$/, 'sjsp.js'); 27 | 28 | fs.readFile(fileName, 'utf8', function(err, source){ 29 | if(err) throw err; 30 | 31 | var injected = injector.inject(fileName, source, interval); 32 | if(isStdout){ 33 | process.stdout.write(injected); 34 | } else { 35 | fs.writeFile(targetFileName, injected, function (err) { 36 | if (err) throw err; 37 | }); 38 | } 39 | }); 40 | 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /lib/injector.js: -------------------------------------------------------------------------------- 1 | var esprima = require('esprima'); 2 | var escodegen = require('escodegen'); 3 | var estraverse = require('estraverse'); 4 | var fs = require('fs'); 5 | var profiler = require('./profiler'); 6 | 7 | /** 8 | * inject profile 9 | * @param fileName {String} target file name 10 | * @param source {String} target source code 11 | * @param interval {Number} interval(seconds) to output a profile 12 | * @return {String} profile-injected source code 13 | */ 14 | function inject(fileName, source, interval){ 15 | var srcLines = source.split(/\r?\n/); 16 | var ast = esprima.parse(source, {loc: true}); 17 | var lineLimit = 100; 18 | 19 | estraverse.traverse(ast, { 20 | enter: function (node, parent) { 21 | /* 22 | rewrite function 23 | function foo(){ body } -> function() { start('foo'); body; end; } 24 | function(){ body } -> function() { start('anonymous'); body; end; } 25 | */ 26 | if(node.type === "FunctionDeclaration" || 27 | node.type === "FunctionExpression"){ 28 | 29 | var funcName = node.id === null ? 'anonymous' : node.id.name; 30 | var funcPos = node.body.loc.start; 31 | var funcBody = node.body.body; 32 | 33 | // prepend 34 | funcBody.unshift( 35 | start(fileName, funcPos.line, funcPos.column + 1, 36 | funcName, srcLines[funcPos.line-1].substr(0, lineLimit)) 37 | ); 38 | 39 | // append 40 | funcBody.push( 41 | end() 42 | ); 43 | } 44 | }, 45 | 46 | leave: function (node, parent) { 47 | /* 48 | rewrite return 49 | return expr; -> return (function(arguments) { start(); var value = expr; end(); return value; }).call(this, arguments); 50 | */ 51 | if(node.type === 'ReturnStatement') { 52 | wrapReturn(node); 53 | } 54 | 55 | /* 56 | rewrite var func 57 | var test = function() { body; }; -> function() { start("test"); body; end(); }; 58 | */ 59 | if(node.type === "VariableDeclarator") { 60 | if(node.init && node.init.type === "FunctionExpression") { 61 | rewriteFuncName(node.init, node.id.name); 62 | } 63 | } 64 | 65 | /* 66 | rewrite assign func 67 | a.test = function() { body; }; -> function() { start("a.test"); body; end(); }; 68 | */ 69 | if(node.type === "AssignmentExpression") { 70 | if(node.right.type === "FunctionExpression") { 71 | rewriteFuncName(node.right, getStaticName(node.left)); 72 | } 73 | } 74 | } 75 | }); 76 | 77 | ast.body = profiler(interval).concat(ast.body); 78 | 79 | return escodegen.generate(ast); 80 | } 81 | 82 | function start(fname, line, col, name, linestr){ 83 | var template = parseStatement("var sjsp__state = sjsp__start()"); 84 | template.declarations[0].init.arguments = Array.prototype.map.call(arguments, function(arg){ 85 | return makeLiteral(arg); 86 | }); 87 | return template; 88 | } 89 | 90 | function end(){ 91 | return parseStatement("sjsp__end(sjsp__state)"); 92 | } 93 | 94 | function wrapReturn(returnStmt){ 95 | var wrapperFunc = parseStatement( 96 | "(function(arguments){" + 97 | " var sjsp__return = __here__;" + 98 | " sjsp__end(sjsp__state);" + 99 | " return sjsp__return;" + 100 | "}).call(this, arguments);" 101 | ); 102 | 103 | // rewrite __here__ 104 | wrapperFunc.expression.callee.object.body.body[0].declarations[0].init = 105 | returnStmt.argument; 106 | 107 | // assign express to argument. 108 | returnStmt.argument = wrapperFunc.expression; 109 | } 110 | 111 | function rewriteFuncName(funcAst, funcName){ 112 | var startArguments = funcAst.body.body[0].declarations[0].init.arguments; 113 | // argument[3]: function's name 114 | startArguments[3].value = funcName; 115 | } 116 | 117 | function getStaticName(expr){ 118 | if(expr.type === "MemberExpression") { 119 | return getStaticName(expr.object) + '.' + expr.property.name; 120 | } else if(expr.type === "Identifier") { 121 | return expr.name; 122 | } else if (expr.type === "ThisExpression") { 123 | return "this" 124 | } else { 125 | throw "Invalid member expression"; 126 | } 127 | } 128 | 129 | function parseStatement(code){ 130 | return esprima.parse(code).body[0]; 131 | } 132 | 133 | function makeLiteral(literal) { 134 | return { 135 | type: 'Literal', 136 | value: literal 137 | }; 138 | } 139 | 140 | exports.inject = inject; 141 | -------------------------------------------------------------------------------- /lib/profiler.js: -------------------------------------------------------------------------------- 1 | var esprima = require('esprima'); 2 | 3 | var profilerCode = [ 4 | // This profiling code is copied from https://github.com/itchyny/sjsp/blob/master/src/Injector.hs 5 | // original author: itchyny 6 | "window.sjsp__result = window.sjsp__result || {};", 7 | "window.sjsp__state = window.sjsp__state || { time: 0, line: 0, col: 0, name: '' };", 8 | "window.sjsp__start = window.sjsp__start || function (fname, line, col, name, linestr) {", 9 | " return {", 10 | " start: Date.now(), line: line, col: col,", 11 | " name: name, fname: fname, linestr: linestr", 12 | " };", 13 | "};", 14 | "window.sjsp__end = window.sjsp__end || function (x) {", 15 | " if (!x.start) return;", 16 | " var key = x.fname + ' :: ' + x.line + ' :: ' + x.col;", 17 | " sjsp__result[key] = sjsp__result[key] || {", 18 | " count: 0, time: 0, line: x.line, col: x.col,", 19 | " name: x.name, fname: x.fname, linestr: x.linestr", 20 | " };", 21 | " sjsp__result[key].time += Date.now() - x.start;", 22 | " sjsp__result[key].count += 1;", 23 | "};", 24 | "if (!window.hasOwnProperty('sjsp__interval'))", 25 | " window.sjsp__interval = setInterval(function () {", 26 | " var sjsp__print = function (x, n) {", 27 | " return Array(Math.max(0, n - x.toString().length + 1)).join(' ') + x;", 28 | " };", 29 | " var sjsp__format = function (x) {", 30 | " return 'time: ' + sjsp__print((x.time / 1000).toFixed(2), 7) + 'sec count: ' + sjsp__print(x.count, 7) + ' ' + sjsp__print(x.fname, 15) + ' ' + sjsp__print(x.name, 13) + ' ' + ' (line:' + sjsp__print(x.line, 4) + ', col:' + sjsp__print(x.col, 3) + ') ' + x.linestr;", 31 | " };", 32 | " var sjsp__result_time = Object.keys(sjsp__result).map(function (key) {", 33 | " return sjsp__result[key];", 34 | " }).sort(function (x, y) {", 35 | " return y.time - x.time;", 36 | " }).slice(0, 20).map(function (x) {", 37 | " return sjsp__format(x);", 38 | " });", 39 | " var sjsp__result_count = Object.keys(sjsp__result).map(function (key) {", 40 | " return sjsp__result[key];", 41 | " }).sort(function (x, y) {", 42 | " return y.count - x.count;", 43 | " }).slice(0, 20).map(function (x) {", 44 | " return sjsp__format(x);", 45 | " });", 46 | " console.log('========== SORT BY TIME ==========\\n' +", 47 | " sjsp__result_time.join(\"\\n\") +", 48 | " '\\n========== SORT BY COUNT ==========\\n' +", 49 | " sjsp__result_count.join(\"\\n\")", 50 | " );", 51 | " }, INTERVAL * 1000);", 52 | ]; 53 | 54 | function profiler(interval){ 55 | // rewrite interval number 56 | var copiedProfileCode = profilerCode.slice(); 57 | var last = copiedProfileCode.length-1; 58 | copiedProfileCode[last] = copiedProfileCode[last].replace(/INTERVAL/, String(interval)); 59 | 60 | return esprima.parse(copiedProfileCode.join('')).body; 61 | } 62 | 63 | module.exports = profiler; 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-sjsp", 3 | "version": "1.1.2", 4 | "description": "Simple JavaScript Profiler in Node.js", 5 | "preferGlobal": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/45deg/node-sjsp.git" 9 | }, 10 | "main": "lib/injector.js", 11 | "bin": { 12 | "sjsp": "index.js" 13 | }, 14 | "dependencies": { 15 | "commander": "^2.8.1", 16 | "escodegen": "^1.6.1", 17 | "esprima": "^4.0.0", 18 | "estraverse": "^4.1.0" 19 | }, 20 | "devDependencies": { 21 | "assert": "^1.3.0", 22 | "mocha": "^4.0.1" 23 | }, 24 | "scripts": { 25 | "test": "./node_modules/mocha/bin/mocha test/main.js" 26 | }, 27 | "keywords": [ 28 | "profiler" 29 | ], 30 | "author": "45deg", 31 | "license": "MIT", 32 | "directories": { 33 | "test": "test" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/fixtures/if-block-return/input.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | if (true) { 3 | return 1; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/if-block-return/output.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var sjsp__state = sjsp__start('example.js', 1, 15, 'fn', 'function fn() {'); 3 | if (true) { 4 | return function (arguments) { 5 | var sjsp__return = 1; 6 | sjsp__end(sjsp__state); 7 | return sjsp__return; 8 | }.call(this, arguments); 9 | } 10 | sjsp__end(sjsp__state); 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/if-else-no-block-return/input.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | if (true) 3 | return 1; 4 | else 5 | return 2; 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/if-else-no-block-return/output.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var sjsp__state = sjsp__start('example.js', 1, 15, 'fn', 'function fn() {'); 3 | if (true) 4 | return function (arguments) { 5 | var sjsp__return = 1; 6 | sjsp__end(sjsp__state); 7 | return sjsp__return; 8 | }.call(this, arguments); 9 | else 10 | return function (arguments) { 11 | var sjsp__return = 2; 12 | sjsp__end(sjsp__state); 13 | return sjsp__return; 14 | }.call(this, arguments); 15 | sjsp__end(sjsp__state); 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/if-no-block-return/input.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | if (true) 3 | return 1; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/if-no-block-return/output.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var sjsp__state = sjsp__start('example.js', 1, 15, 'fn', 'function fn() {'); 3 | if (true) 4 | return function (arguments) { 5 | var sjsp__return = 1; 6 | sjsp__end(sjsp__state); 7 | return sjsp__return; 8 | }.call(this, arguments); 9 | sjsp__end(sjsp__state); 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/this-member/input.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | this.method = function() { 3 | return 1; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/this-member/output.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var sjsp__state = sjsp__start('example.js', 1, 15, 'fn', 'function fn() {'); 3 | this.method = function () { 4 | var sjsp__state = sjsp__start('example.js', 2, 30, 'this.method', ' this.method = function() {'); 5 | return function (arguments) { 6 | var sjsp__return = 1; 7 | sjsp__end(sjsp__state); 8 | return sjsp__return; 9 | }.call(this, arguments); 10 | sjsp__end(sjsp__state); 11 | }; 12 | sjsp__end(sjsp__state); 13 | } 14 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var esprima = require('esprima'); 3 | var estraverse = require('estraverse'); 4 | var escodegen = require('escodegen'); 5 | var profiler = require('../lib/profiler'); 6 | var injector = require('../lib/injector'); 7 | var fs = require("fs"); 8 | var path = require("path"); 9 | 10 | describe("fixture tests", function() { 11 | const fixturesDir = path.join(__dirname, "fixtures"); 12 | const TEST_INTERVAL = 1; 13 | const profileBody = profiler(TEST_INTERVAL); 14 | const profileCode = escodegen.generate({ 15 | type: "Program", 16 | body: profileBody 17 | }); 18 | const dummyFileName = "example.js"; 19 | fs.readdirSync(fixturesDir).map(caseName => { 20 | it(`should inject profiler for ${caseName.replace(/-/g, " ")}`, () => { 21 | const fixtureDir = path.join(fixturesDir, caseName); 22 | const actualPath = path.join(fixtureDir, "input.js"); 23 | const actual = injector.inject(dummyFileName, fs.readFileSync(actualPath, "utf-8"), TEST_INTERVAL); 24 | const expected = fs.readFileSync(path.join(fixtureDir, "output.js"), "utf-8"); 25 | const profileWithExpected = profileCode + "\n"+ expected; 26 | assert.deepStrictEqual(actual.trim(), profileWithExpected.trim(), actual); 27 | }); 28 | }); 29 | }); 30 | 31 | describe("lib/profiler", function(){ 32 | it("setInterval argument should be SECONDS * 1000", function(){ 33 | var sec = 10; 34 | var body = profiler(sec); 35 | // wrap as a program 36 | var ast = { 37 | "type": "Program", 38 | body: body 39 | }; 40 | 41 | // get the second argument of setInterval 42 | estraverse.traverse(ast, { enter: function(node){ 43 | if(node.type === "CallExpression" && node.callee.name === "setInterval") { 44 | var secondArg = node.arguments[1]; 45 | var rawCode = escodegen.generate(secondArg); 46 | assert.equal(eval(rawCode), sec * 1000); 47 | } 48 | }}); 49 | }); 50 | }); 51 | 52 | describe("lib/injector", function(){ 53 | // offset to injected profiling code 54 | var offsetExpression; 55 | var dummyFileName = "example.js"; 56 | 57 | before(function(){ 58 | offsetExpression = profiler(0).length; 59 | }); 60 | 61 | describe("start & end injection", function(){ 62 | it("sjsp__start in FunctionDeclaration", function(){ 63 | var fname = "test"; 64 | var source = "function " + fname + "(){}"; 65 | var injected = injector.inject(dummyFileName, source, 0); 66 | var ast = esprima.parse(injected).body[offsetExpression].body; 67 | assertCallStart(ast, fname, 68 | [dummyFileName, 1, 16, fname, source]); 69 | }); 70 | 71 | it("sjsp__end in FunctionDeclaration", function(){ 72 | var fname = "test"; 73 | var source = "function " + fname + "(){}"; 74 | var injected = injector.inject(dummyFileName, source, 0); 75 | var ast = esprima.parse(injected).body[offsetExpression].body; 76 | assertCallEnd(ast); 77 | }); 78 | 79 | it("sjsp__start in AnonymousFunction", function(){ 80 | var fname = "anonymous"; 81 | var source = "(function(){})"; 82 | var injected = injector.inject(dummyFileName, source, 0); 83 | var ast = esprima.parse(injected).body[offsetExpression].expression.body; 84 | assertCallStart(ast, fname, 85 | [dummyFileName, 1, 12, fname, source]); 86 | }); 87 | 88 | it("sjsp__end in AnonymousFunction", function(){ 89 | var fname = "anonymous"; 90 | var source = "(function(){})"; 91 | var injected = injector.inject(dummyFileName, source, 0); 92 | var ast = esprima.parse(injected).body[offsetExpression].expression.body; 93 | assertCallEnd(ast); 94 | }); 95 | 96 | it("sjsp__start in VariableFunction", function(){ 97 | var fname = "test"; 98 | var source = "var " + fname + " = function(){};"; 99 | var injected = injector.inject(dummyFileName, source, 0); 100 | var varDecl = esprima.parse(injected).body[offsetExpression].declarations; 101 | var ast = varDecl[0].init.body; 102 | assertCallStart(ast, fname, 103 | [dummyFileName, 1, 22, fname, source]); 104 | }); 105 | 106 | it("sjsp__end in VariableFunction", function(){ 107 | var fname = "test"; 108 | var source = "var " + fname + " = function(){};"; 109 | var injected = injector.inject(dummyFileName, source, 0); 110 | var varDecl = esprima.parse(injected).body[offsetExpression].declarations; 111 | var ast = varDecl[0].init.body; 112 | assertCallEnd(ast); 113 | }); 114 | 115 | it("sjsp__start in MemberFunction", function(){ 116 | var fname = "a.b.c"; 117 | var source = fname + " = function(){};"; 118 | var injected = injector.inject(dummyFileName, source, 0); 119 | var ast = esprima.parse(injected).body[offsetExpression].expression.right.body; 120 | assertCallStart(ast, fname, 121 | [dummyFileName, 1, 19, fname, source]); 122 | }); 123 | 124 | it("sjsp__end in MemberFunction", function(){ 125 | var fname = "a.b.c"; 126 | var source = fname + " = function(){};"; 127 | var injected = injector.inject(dummyFileName, source, 0); 128 | var ast = esprima.parse(injected).body[offsetExpression].expression.right.body; 129 | assertCallEnd(ast); 130 | }); 131 | }); 132 | 133 | describe("wrapping return statement", function(){ 134 | var source, origRetVal, injected, body, returnArg, callee; 135 | 136 | before(function(){ 137 | source = "(function(){ return 1+1; })"; 138 | origRetVal = esprima.parse(source).body[0].expression.body.body[0].argument; 139 | 140 | injected = injector.inject(dummyFileName, source, 0); 141 | body = esprima.parse(injected).body[offsetExpression].expression.body.body; 142 | returnArg = body[1].argument; 143 | callee = returnArg.callee; 144 | }); 145 | 146 | it("(...).call(this, arguments)", function(){ 147 | assert.equal(returnArg.type, "CallExpression"); 148 | assert.equal(callee.property.name, "call"); 149 | 150 | var args = returnArg.arguments; 151 | assert.equal(args[0].type, "ThisExpression"); 152 | assert.equal(args[1].name, "arguments"); 153 | }); 154 | 155 | it("return (function(arguments){ ...", function(){ 156 | var func = callee.object; 157 | assert.equal(func.params[0].name, "arguments"); 158 | }); 159 | 160 | it("return original expression", function(){ 161 | var body = callee.object.body.body; 162 | 163 | assert.equal(body[1].type, "ExpressionStatement"); 164 | assert.equal(body[2].type, "ReturnStatement"); 165 | 166 | var varDecl = body[0].declarations[0]; 167 | var tmpVarName = varDecl.id.name; 168 | var tmpVarExpr = varDecl.init; 169 | var retVarName = body[2].argument.name; 170 | 171 | assert.equal(tmpVarName, retVarName); 172 | assert.deepEqual(tmpVarExpr, origRetVal); 173 | }); 174 | 175 | it("call sjsp__end", function(){ 176 | var func = callee.object; 177 | var body = func.body.body; 178 | var expr = body[1].expression; 179 | // assert "sjsp__end(...)" 180 | assert.equal(expr.type, "CallExpression"); 181 | assert.equal(expr.callee.name, "sjsp__end"); 182 | // assert the argument is "sjsp__state" 183 | assert.equal(expr.arguments.length, 1); 184 | assert.equal(expr.arguments[0].name, "sjsp__state"); 185 | }); 186 | }); 187 | 188 | /* 189 | * assert start(); injection 190 | */ 191 | function assertCallStart(ast, filename, args){ 192 | var firstExpr = ast.body[0]; 193 | // assert "var ... = ...;" 194 | assert.equal(firstExpr.type, "VariableDeclaration"); 195 | var decl = firstExpr.declarations[0]; 196 | // assert "var sjsp__state = ..." 197 | assert.equal(decl.id.name, "sjsp__state"); 198 | // assert "var sjsp__state = sjsp__start(...);" 199 | assert.equal(decl.init.type, "CallExpression"); 200 | assert.equal(decl.init.callee.name, "sjsp__start"); 201 | // assert arguments 202 | assert.deepEqual( 203 | decl.init.arguments.map(function(a){ return a.value; }), 204 | args 205 | ); 206 | } 207 | /* 208 | * assert end(); injection 209 | */ 210 | function assertCallEnd(ast){ 211 | var lastExpr = ast.body[ast.body.length-1].expression; 212 | // assert "sjsp__end(...)" 213 | assert.equal(lastExpr.type, "CallExpression"); 214 | assert.equal(lastExpr.callee.name, "sjsp__end"); 215 | // assert the argument is "sjsp__state" 216 | assert.equal(lastExpr.arguments.length, 1); 217 | assert.equal(lastExpr.arguments[0].name, "sjsp__state"); 218 | } 219 | }); 220 | 221 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | function test1(){ 2 | return 1; 3 | } 4 | 5 | function test2(){ 6 | // no return 7 | } 8 | 9 | function test3(x){ 10 | if(x > 0) { 11 | return x; 12 | } else if (x < 0) { 13 | return -x; 14 | } 15 | return 0; 16 | } 17 | 18 | function test4(x){ 19 | if(x > 0) { 20 | // very heavy addition 21 | return [1+1, 2+2, 3+3]; 22 | } 23 | 24 | return [4+4, 5+5, 6+6]; 25 | } 26 | 27 | var test5 = function(){}; 28 | 29 | var test6 = function(){ 30 | return function(){ 31 | return "anonymous"; 32 | }; 33 | }; 34 | 35 | var testobj = {}; 36 | 37 | testobj.member = function(){}; 38 | var x = testobj.member = function(){}; 39 | --------------------------------------------------------------------------------