├── .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 | [](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 |
--------------------------------------------------------------------------------