Coverage

95%
573
550
23

/Users/fish/workspace/jscoverage/index.js

87%
89
78
11
LineHitsSource
1/*!
2 * jscoverage: index.js
3 * Authors : fish <zhengxinlin@gmail.com> (https://github.com/fishbar)
4 * Create : 2014-04-03 15:20:13
5 * CopyRight 2014 (c) Fish And Other Contributors
6 *
7 */
8var fs = require('xfs');
9var path = require('path');
10var argv = require('optimist').argv;
11var patch = require('./lib/patch');
12var cmd = argv['$0'];
13var MODE_MOCHA = false;
14var FLAG_LOCK = false;
15if (/mocha/.test(cmd)) {
161 MODE_MOCHA = true;
17}
18
19if (MODE_MOCHA) {
201 prepareMocha();
21}
22/**
23 * prepare env for mocha test
24 * @covignore
25 */
26function prepareMocha() {
27 var covIgnore = argv.covignore;
28 var cwd = process.cwd();
29 var covlevel = argv.coverage;
30 if (covlevel) {
31 var tmp = covlevel.split(',');
32 covlevel = {
33 high: parseFloat(tmp[0], 10),
34 middle: parseFloat(tmp[1], 10),
35 low: parseFloat(tmp[2], 10)
36 };
37 } else {
38 covlevel = {
39 high: 0.9,
40 middle: 0.7,
41 low: 0.3
42 };
43 }
44 /**
45 * add after hook
46 * @return {[type]} [description]
47 */
48 process.nextTick(function () {
49 try {
50 after(function () {
51 if (FLAG_LOCK) {
52 return;
53 }
54 FLAG_LOCK = true;
55 if (typeof _$jscoverage === 'undefined') {
56 return;
57 }
58 try {
59 if (argv.covout === 'none') {
60 return;
61 }
62 if (!argv.covout) {
63 argv.covout = 'summary';
64 }
65 var reporter;
66 if (/^\w+$/.test(argv.covout)) {
67 reporter = require('./reporter/' + argv.covout);
68 } else {
69 reporter = require(argv.covout);
70 }
71 reporter.process(_$jscoverage, exports.coverageStats(), covlevel);
72 } catch (e) {
73 console.error('jscoverage reporter error', e);
74 }
75 });
76 } catch (e) {
77 // do nothing
78 }
79 });
80 if (argv.covinject) {
81 patch.enableInject(true);
82 }
83 if (!covIgnore) {
84 try {
85 var stat = fs.statSync('.covignore');
86 stat && (covIgnore = '.covignore');
87 } catch (e) {
88 return;
89 }
90 }
91 try {
92 covIgnore = fs.readFileSync(covIgnore).toString().split(/\r?\n/g);
93 } catch (e) {
94 throw new Error('jscoverage loading covIgnore file error:' + covIgnore);
95 }
96 covIgnore.forEach(function (v, i, a) {
97 if (v.indexOf('/') === 0) {
98 v = '^' + cwd + v;
99 }
100 a[i] = new RegExp(v.replace(/\./g, '\\.').replace(/\*/g, '.*'));
101 });
102
103 patch.setCovIgnore(covIgnore);
104}
105
106var jscoverage = require('./lib/jscoverage');
107
108/**
109 * enableInject description
110 * @param {Boolean} true or false
111 */
112exports.enableInject = patch.enableInject;
113/**
114 * config the inject function names
115 * @param {Object} obj {get, replace, call, reset}
116 * @example
117 *
118 * jsc.config({get:'$get', replace:'$replace'});
119 *
120 * =====================
121 *
122 * testMod = require('testmodule');
123 * testMod.$get('name');
124 * testMod.$replace('name', obj);
125 */
126exports.config = function (obj) {
1271 var inject_functions = patch.getInjectFunctions();
128 for (var i in obj) {
1294 inject_functions[i] = obj[i];
130 }
131};
132/**
133 * process Code, inject the coverage code to the input Code string
134 * @param {String} filename jscoverage file flag
135 * @param {Code} content
136 * @return {Code} instrumented code
137 */
138exports.process = jscoverage.process;
139
140/**
141 * processFile, instrument singfile
142 * @sync
143 * @param {Path} source absolute Path
144 * @param {Path} dest absolute Path
145 * @param {Object} option [description]
146 */
147exports.processFile = function (source, dest, option) {
1486 var content;
1496 var stats;
150 // test source is file or dir, or not a file
151 try {
1526 stats = fs.statSync(source);
153 if (stats.isDirectory()) {
1541 throw new Error('path is dir');
155 } else if (!stats.isFile()) {
1561 throw new Error('path is not a regular file');
157 }
158 } catch (e) {
1595 throw new Error('source file error' + e);
160 }
161
1621 fs.sync().mkdir(path.dirname(dest));
163
1641 content = fs.readFileSync(source).toString();
1651 content = content.toString();
1661 content = this.process(source, content);
1671 fs.writeFileSync(dest, content);
168};
169
170
171/**
172 * sum the coverage rate
173 * @public
174 */
175exports.coverageStats = function () {
1761 var file;
1771 var tmp;
1781 var total;
1791 var touched;
1801 var n, len;
1811 var stats = {};
1821 var conds, condsMap, cond;
1831 var line, start, offset;
184 if (typeof _$jscoverage === 'undefined') {
1850 return;
186 }
187 for (var i in _$jscoverage) {
18811 file = i;
18911 tmp = _$jscoverage[i];
190 if (!tmp.length) {
1910 continue;
192 }
19311 total = touched = 0;
194 for (n = 0, len = tmp.length; n < len; n++) {
195 if (tmp[n] !== undefined) {
196358 total ++;
197 if (tmp[n] > 0) {
198335 touched ++;
199 }
200 }
201 }
20211 conds = tmp.condition;
20311 condsMap = {};
204 for (n in conds) {
205 if (conds[i] === 0) {
2060 cond = i.split('_');
2070 line = cond[0];
2080 start = cond[1];
2090 offset = cond[2];
210 if (!condsMap[line]) {
2110 condsMap[line] = [];
212 }
2130 condsMap[line].push([start, offset]);
214 } else {
215215 touched ++;
216 }
217215 total ++;
218 }
21911 stats[file] = {
220 sloc: total,
221 hits: touched,
222 coverage: total ? touched / total : 0,
223 percent: total ? ((touched / total) * 100).toFixed(2) + '%' : '~',
224 condition: condsMap
225 };
226 }
2271 return stats;
228};
229
230/**
231 * get lcov report
232 * @return {[type]} [description]
233 */
234exports.getLCOV = function () {
2351 var tmp;
2361 var total;
2371 var touched;
2381 var n, len;
2391 var lcov = '';
240 if (typeof _$jscoverage === 'undefined') {
2410 return;
242 }
2431 Object.keys(_$jscoverage).forEach(function (file) {
24410 lcov += 'SF:' + file + '\n';
24510 tmp = _$jscoverage[file];
246 if (!tmp.length) {
2470 return;
248 }
24910 total = touched = 0;
250 for (n = 0, len = tmp.length; n < len; n++) {
251 if (tmp[n] !== undefined) {
252330 lcov += 'DA:' + n + ',' + tmp[n] + '\n';
253330 total ++;
254 if (tmp[n] > 0) {
255201 touched++;
256 }
257 }
258 }
25910 lcov += 'end_of_record\n';
260 });
2611 return lcov;
262};
263

/Users/fish/workspace/jscoverage/lib/instrument.js

98%
102
100
2
LineHitsSource
1/*!
2 * jscoverage: lib/instrument.js
3 * Authors : fish <zhengxinlin@gmail.com> (https://github.com/fishbar)
4 * Create : 2014-04-03 15:20:13
5 * CopyRight 2014 (c) Fish And Other Contributors
6 */
7/**
8 * instrument code
9 * @example
10 * var ist = new Instrument();
11 * var resCode = ist.process(str);
12 */
13var Uglify = require('uglify-js');
14
15function Instrument() {
16 /**
17 * filename needed
18 * @type {String}
19 */
201 this.filename = null;
21 /**
22 * store injected code
23 * @type {String}
24 */
251 this.code = null;
26 /**
27 * 储存line信息
28 * @type {Array}
29 */
301 this.lines = [];
31 /**
32 * 储存condition信息
33 * @type {Object}
34 */
351 this.conds = {};
36 /**
37 * source code in array
38 * @type {Array}
39 */
401 this.source = null;
41}
42
43Instrument.prototype = {
44 // 行类型
45 T_LINE: 'line',
46 T_COND: 'cond',
47 /**
48 * process code
49 * @public
50 * @param {String} code source code
51 * @return {String} injected code
52 */
53 process: function (filename, code) {
54 if (!filename) {
550 throw new Error('[jscoverage]instrument need filename!');
56 }
57
581 var ist = this;
59 // parse ast
601 var ast = Uglify.parse(code);
61
621 this.filename = filename;
631 this.source = code.split(/\r?\n/);
64
65 // init walker
661 var walker = new Uglify.TreeWalker(function (node) {
67 if (node instanceof Uglify.AST_Toplevel) {
681 return;
69 }
70 if (ist.checkIfIgnore(node, walker.stack)) {
714 return;
72 }
73
74 if (node instanceof Uglify.AST_Conditional) { // 三元判断
751 node.consequent = ist.inject('cond', node.consequent.start.line, node.consequent);
761 node.alternative = ist.inject('cond', node.alternative.start.line, node.alternative);
77 } else if (node.TYPE === 'Binary') {
78 if (!(node.left instanceof Uglify.AST_Constant)) {
793 node.left = ist.inject('cond', node.left.start.line, node.left);
80 }
81 if (!(node.right instanceof Uglify.AST_Constant)) {
822 node.right = ist.inject('cond', node.right.start.line, node.right);
83 }
84 }
85233 var len = node.body ? node.body.length : 0;
86 if (len) {
876 var res = [];
886 var subNode;
89 for (var i = 0; i < len; i++) {
9019 subNode = node.body[i];
91 if (ist.checkIfIgnore(subNode, walker.stack)) {
921 res.push(subNode);
931 continue;
94 }
95 if (subNode instanceof Uglify.AST_Statement) {
96 if (ist.ifExclude(subNode)) {
974 res.push(subNode);
984 continue;
99 }
10014 res.push(ist.inject('line', subNode.start.line));
101 } else if (subNode instanceof Uglify.AST_Var) {
1020 res.push(ist.inject('line', subNode.start.line));
103 }
10414 res.push(subNode);
105 }
1066 node.body = res;
107 }
108 });
109 // figure_out_scope
1101 ast.figure_out_scope();
111 // walk process
1121 ast.walk(walker);
113
1141 var out = Uglify.OutputStream({
115 preserve_line : true,
116 comments: 'all',
117 beautify: true
118 });
119 // rebuild file
1201 ast.print(out);
1211 this.code = out.toString();
1221 return this;
123 },
124 /**
125 * 注入覆盖率查询方法
126 * @private
127 * @param {String} type inject type, line | conds
128 * @param {Number} line line number
129 * @param {Object} expr any expression, or node, or statement
130 * @return {AST_Func} Object
131 */
132 inject: function (type, line, expr) {
13321 var args = [];
134 if (type === this.T_LINE) {
13514 this.lines.push(line);
13614 args = [
137 new Uglify.AST_String({value: this.filename}),
138 new Uglify.AST_String({value: type}),
139 new Uglify.AST_Number({value: line})
140 ];
141 } else if (type === this.T_COND) {
1427 var start = expr.start.col;
1437 var offset = expr.end.endpos - expr.start.pos;
1447 var key = line + '_' + start + '_' + offset; // 编码
1457 this.conds[key] = 0;
1467 args = [
147 new Uglify.AST_String({value: this.filename}),
148 new Uglify.AST_String({value: type}),
149 new Uglify.AST_String({value: key}),
150 expr
151 ];
152 }
153
15421 var call = new Uglify.AST_Call({
155 expression: new Uglify.AST_SymbolRef({name: '_$jscmd'}),
156 //end: new Uglify.AST_
157 args: args
158 });
159
160 if (type === this.T_LINE) {
16114 return new Uglify.AST_SimpleStatement({
162 body: call,
163 end: new Uglify.AST_Token({value: ';'})
164 });
165 } else {
1667 return call;
167 }
168 },
169 /**
170 * check if need inject
171 * @param {AST_Node} node
172 * @return {Boolean}
173 */
174 ifExclude: function (node) {
175 if (node instanceof Uglify.AST_LoopControl) {
1762 return false;
177 }
178 if (
179 node instanceof Uglify.AST_IterationStatement ||
180 node instanceof Uglify.AST_StatementWithBody ||
181 node instanceof Uglify.AST_Block
182 ) {
1834 return true;
184 }
185 },
186 checkIfIgnore: function (node, stack) {
187256 var cmt;
188 if (node.start && node.start.comments_before.length) {
18913 cmt = node.start.comments_before[node.start.comments_before.length - 1];
190 if (/@covignore/.test(cmt.value)) {
1914 node.__covignore = true;
192 }
193 }
194 if (node.__covignore) {
1954 return true;
196 }
197 if (stack) {
198 for (var i = stack.length - 1; i > 0; i--) {
199 if (stack[i].__covignore) {
2001 return true;
201 }
202 }
203 }
204251 return false;
205 }
206};
207
208module.exports = Instrument;

/Users/fish/workspace/jscoverage/lib/jscoverage.js

90%
55
50
5
LineHitsSource
1/*!
2 * jscoverage: lib/jscoverage.js
3 * Authors : fish <zhengxinlin@gmail.com> (https://github.com/fishbar)
4 * Create : 2014-04-03 15:20:13
5 * CopyRight 2014 (c) Fish And Other Contributors
6 */
7var Instrument = require('./instrument');
8
9/**
10 * do not exec this function
11 * the function body will insert into instrument files
12 *
13 * _$jscoverage = {
14 * filename : {
15 * line1: 0
16 * line2: 1
17 * line3: undefined
18 * ....
19 * source: [],
20 * condition: [
21 * line_start_offset
22 * ]
23 * }
24 * }
25 */
26function jscFunctionBody() {
27 // instrument by jscoverage, do not modifly this file
281 (function (file, lines, conds, source) {
291 var BASE;
30 if (typeof global === 'object') {
311 BASE = global;
32 } else if (typeof window === 'object') {
330 BASE = window;
34 } else {
350 throw new Error('[jscoverage] unknow ENV!');
36 }
37 if (BASE._$jscoverage) {
380 BASE._$jscmd(file, 'init', lines, conds, source);
390 return;
40 }
411 var cov = {};
42 /**
43 * jsc(file, 'init', lines, condtions)
44 * jsc(file, 'line', lineNum)
45 * jsc(file, 'cond', lineNum, expr, start, offset)
46 */
47 function jscmd(file, type, line, express, start, offset) {
483 var storage;
49 switch (type) {
50 case 'init':
511 storage = [];
52 for (var i = 0; i < line.length; i ++) {
531 storage[line[i]] = 0;
54 }
551 var condition = express;
561 var source = start;
571 storage.condition = condition;
581 storage.source = source;
591 cov[file] = storage;
601 break;
61 case 'line':
621 storage = cov[file];
631 storage[line] ++;
641 break;
65 case 'cond':
661 storage = cov[file];
671 storage.condition[line] ++;
681 return express;
69 }
70 }
71
721 BASE._$jscoverage = cov;
731 BASE._$jscmd = jscmd;
741 jscmd(file, 'init', lines, conds, source);
75 })('$file$', $lines$, $conds$, $source$);
76}
77/**
78 * gen coverage head
79 */
80function genCodeCoverage(instrObj) {
81 if (!instrObj) {
820 return '';
83 }
841 var code = [];
851 var filename = instrObj.filename;
86 // Fix windows path
871 filename = filename.replace(/\\/g, '/');
881 var lines = instrObj.lines;
891 var conditions = instrObj.conds;
901 var src = instrObj.source;
911 var jscfArray = jscFunctionBody.toString().split('\n');
921 jscfArray = jscfArray.slice(1, jscfArray.length - 1);
931 var ff = jscfArray.join('\n').replace(/(^|\n) {2}/g, '\n')
94 .replace(/\$(\w+)\$/g, function (m0, m1){
95 switch (m1) {
96 case 'file':
971 return filename;
98 case 'lines':
991 return JSON.stringify(lines);
100 case 'conds':
1011 return JSON.stringify(conditions);
102 case 'source':
1031 return JSON.stringify(src);
104 }
105 });
1061 code.push(ff);
1071 code.push(instrObj.code);
1081 return code.join('\n');
109}
110
111exports.process = function (filename, content) {
112 if (!filename) {
1131 throw new Error('jscoverage.process(filename, content), filename needed!');
114 }
1152 filename = filename.replace(/\\/g, '/');
116 if (!content) {
1171 return '';
118 }
1191 var instrObj;
1201 var ist = new Instrument();
1211 instrObj = ist.process(filename, content);
1221 return genCodeCoverage(instrObj);
123};
124

/Users/fish/workspace/jscoverage/lib/patch.js

100%
9
9
0
LineHitsSource
1/*!
2 * jscoverage: lib/patch.js
3 * Authors : fish <zhengxinlin@gmail.com> (https://github.com/fishbar)
4 * Create : 2014-04-03 15:20:13
5 * CopyRight 2014 (c) Fish And Other Contributors
6 */
7var Module = require('module');
8var path = require('path');
9var fs = require('fs');
10var argv = require('optimist').argv;
11var jscoverage = require('./jscoverage');
12
13var covInject = false;
14var covIgnore = [];
15
16var injectFunctions = {
17 get : '_get',
18 replace : '_replace',
19 call : '_call',
20 reset : '_reset',
21 test: '_test'
22};
23
24exports.getInjectFunctions = function () {
251 return injectFunctions;
26};
27
28exports.enableInject = function (bool) {
291 covInject = bool;
30};
31exports.setCovIgnore = function (ignore) {
322 covIgnore = ignore;
33};
34/**
35 * do mock things here
36 * @covignore
37 */
38(function () {
39 if (Module.prototype.__jsc_patch__) {
40 return;
41 }
42 Module.prototype.__jsc_patch__ = true;
43 var origin_require = Module.prototype.require;
44 Module.prototype.require = function (filename) {
45 var needinject = covInject;
46 var ff = filename;
47 filename = Module._resolveFilename(filename, this);
48 var flagjsc = checkModule(filename);
49 if (typeof filename === 'object') {
50 filename = filename[0];
51 }
52
53 if (!flagjsc) {
54 return origin_require.call(this, filename);
55 }
56
57 var cachedModule = Module._cache[filename];
58 // take care of module cache
59 if (flagjsc && cachedModule && cachedModule.__coveraged__) {
60 return cachedModule.exports;
61 }
62 // console.log('jscoverage:', ff, 'cov', flagjsc, 'inject', needinject);
63 var module = new Module(filename, this);
64 try {
65 module.filename = filename;
66 module.paths = Module._nodeModulePaths(path.dirname(filename));
67 Module._extensions['.js'](module, filename, {
68 flagjsc : flagjsc,
69 needinject : needinject
70 });
71 module.__coveraged__ = flagjsc;
72 module.loaded = true;
73 Module._cache[filename] = module;
74 } catch (err) {
75 delete Module._cache[filename];
76 console.error(err.stack);
77 throw err;
78 }
79 return module.exports;
80 };
81 function stripBOM(content) {
82 // Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
83 // because the buffer-to-string conversion in `fs.readFileSync()`
84 // translates it to FEFF, the UTF-16 BOM.
85 if (content.charCodeAt(0) === 0xFEFF) {
86 content = content.slice(1);
87 }
88 return content;
89 }
90 Module._extensions['.js'] = function (module, filename, status) {
91 var content = fs.readFileSync(filename, 'utf8');
92 var tmpFuncBody;
93 var injectFn = exports.getInjectFunctions();
94 // trim first line when script is a shell script
95 // content = content.replace(/^\#\![^\n]+\n/, '');
96 if (status && status.flagjsc) {
97 content = jscoverage.process(filename, content);
98 }
99 if (status && status.needinject) {
100 tmpFuncBody = injectFunctionBody.toString().replace(/\$\$(\w+)\$\$/g, function (m0, m1) {
101 return injectFunctions[m1];
102 });
103 tmpFuncBody = tmpFuncBody.split(/\n/);
104 content += '\n' + tmpFuncBody.slice(1, tmpFuncBody.length - 1).join('\n');
105 }
106 module._compile(stripBOM(content), filename);
107 };
108})();
109
110function checkModule(module) {
111
112 // native module
113 if (!/\//.test(module)) {
1142 return false;
115 }
116 // modules in node_modules
117 if (/\/node_modules\//.test(module)) {
1181 return false;
119 }
1202 var flagIgnore = false;
1212 covIgnore.forEach(function (v) {
122 if (v.test(module)) {
1231 flagIgnore = true;
124 }
125 });
1262 return !flagIgnore;
127}
128
129/**
130 * do not exec this function
131 * @covignore
132 */
133function injectFunctionBody() {
134 (function (){
135 if (module.exports._i_n_j_e_c_t_e_d_) {
136 return;
137 }
138 if (module.exports.$$call$$ || module.exports.$$get$$ ||
139 module.exports.$$replace$$ || module.exports.$$reset$$) {
140 throw new Error("[jscoverage] jscoverage can not inject function for this module, because the function is exists! using jsc.config({inject:{}})");
141 }
142
143 var __r_e_p_l_a_c_e__ = {};
144 module.exports.$$replace$$ = function (name, obj) {
145 function stringify(obj) {
146 if (obj === null) {
147 return 'null';
148 }
149 if (obj === undefined){
150 return 'undefined';
151 }
152 if (!obj && isNaN(obj)){
153 return 'NaN';
154 }
155 if (typeof obj === 'string') {
156 return '"' + obj.replace(/"/g, '\\"') + '"';
157 }
158 if (typeof obj === 'number') {
159 return obj;
160 }
161 if (obj.constructor === Date) {
162 return 'new Date(' + obj.getTime() + ')';
163 }
164 if (obj.constructor === Function) {
165 return obj.toString();
166 }
167 if (obj.constructor === RegExp) {
168 return obj.toString();
169 }
170 var is_array = obj.constructor === Array ? true : false;
171 var res, i;
172 if (is_array) {
173 res = ['['];
174 for (i = 0; i < obj.length; i++) {
175 res.push(stringify(obj[i]));
176 res.push(',');
177 }
178 if (res[res.length - 1] === ',') {
179 res.pop();
180 }
181 res.push(']');
182 } else {
183 res = ['{'];
184 for (i in obj) {
185 res.push(i + ':' + stringify(obj[i]));
186 res.push(',');
187 }
188 if (res[res.length - 1] === ',')
189 res.pop();
190 res.push('}');
191 }
192 return res.join('');
193 }
194 if (!__r_e_p_l_a_c_e__.hasOwnProperty(name)) {
195 __r_e_p_l_a_c_e__[name] = eval(name);
196 }
197 eval(name + "=" + stringify(obj));
198 };
199 module.exports.$$reset$$ = function (name) {
200 var script;
201 if (name) {
202 script = 'if(__r_e_p_l_a_c_e__.hasOwnProperty("' + name + '"))' + name + ' = __r_e_p_l_a_c_e__["' + name + '"];';
203 } else {
204 script = 'for(var i in __r_e_p_l_a_c_e__){eval( i + " = __r_e_p_l_a_c_e__[\'" + i + "\'];");}';
205 }
206 eval(script);
207 };
208 module.exports.$$call$$ = module.exports.$$test$$ = function (func, args) {
209 var f, o;
210 if (func.match(/\\./)) {
211 func = func.split(".");
212 f = func[func.length - 1];
213 func.pop();
214 o = func.join(".");
215 } else {
216 f = func;
217 o = "this";
218 }
219 return eval(f + ".apply(" + o + "," + JSON.stringify(args) + ")");
220 };
221 module.exports.$$get$$ = function (objstr) {
222 return eval(objstr);
223 };
224 module.exports._i_n_j_e_c_t_e_d_ = true;
225})();
226}
227

/Users/fish/workspace/jscoverage/reporter/detail.js

96%
128
124
4
LineHitsSource
1
2/**
3 * print detail coverage info in console
4 * @param {Object} _$jscoverage [description]
5 * @param {Object} stats [description]
6 * @param {Number} covlevel [description]
7 */
8exports.process = function (_$jscoverage, stats, covlevel) {
91 var file;
101 var tmp;
111 var source;
121 var lines;
131 var allcovered;
14 for (var i in _$jscoverage) {
1510 file = i;
1610 tmp = _$jscoverage[i];
17 if (typeof tmp === 'function' || tmp.length === undefined) {
180 continue;
19 }
2010 source = tmp.source;
2110 allcovered = true;
22 //console.log('[JSCOVERAGE]',file);
2310 console.log('[UNCOVERED CODE]', file);
2410 lines = [];
25 for (var n = 0, len = source.length; n < len ; n++) {
26 if (tmp[n] === 0) {
2740 lines[n] = 1;
2840 allcovered = false;
29 } else {
301147 lines[n] = 0;
31 }
32 }
33 if (allcovered) {
346 console.log(colorful('\t100% covered', 'GREEN'));
35 } else {
364 printCoverageDetail(lines, source);
37 }
38 }
39};
40
41function processLinesMask(lines) {
42 function processLeft3(arr, offset) {
4334 var prev1 = offset - 1;
4434 var prev2 = offset - 2;
4534 var prev3 = offset - 3;
46 if (prev1 < 0) {
470 return;
48 }
4934 arr[prev1] = arr[prev1] === 1 ? arr[prev1] : 2;
50 if (prev2 < 0) {
510 return;
52 }
5334 arr[prev2] = arr[prev2] === 1 ? arr[prev2] : 2;
54 if (prev3 < 0) {
552 return;
56 }
5732 arr[prev3] = arr[prev3] ? arr[prev3] : 3;
58 }
59 function processRight3(arr, offset) {
6033 var len = arr.length;
6133 var next1 = offset;
6233 var next2 = offset + 1;
6333 var next3 = offset + 2;
64 if (next1 >= len || arr[next1] === 1) {
650 return;
66 }
6733 arr[next1] = arr[next1] ? arr[next1] : 2;
68 if (next2 >= len || arr[next2] === 1) {
6912 return;
70 }
7121 arr[next2] = arr[next2] ? arr[next2] : 2;
72 if (next3 >= len || arr[next3] === 1) {
737 return;
74 }
7514 arr[next3] = arr[next3] ? arr[next3] : 3;
76 }
778 var offset = 0;
788 var now;
798 var prev = 0;
80 while (offset < lines.length) {
81763 now = lines[offset];
82763 now = now !== 1 ? 0 : 1;
83 if (now !== prev) {
84 if (now === 1) {
8534 processLeft3(lines, offset);
86 } else if (now === 0) {
8733 processRight3(lines, offset);
88 }
89 }
90763 prev = now;
91763 offset ++;
92 }
938 return lines;
94}
95/**
96 * printCoverageDetail
97 * @param {Array} lines [true] 1 means no coveraged
98 * @return {}
99 */
100function printCoverageDetail(lines, source) {
1014 var len = lines.length;
1024 lines = processLinesMask(lines);
103 //console.log(lines);
104 for (var i = 1; i < len; i++) {
105 if (lines[i] !== 0) {
106 if (lines[i] === 3) {
10721 console.log('......');
108 } else if (lines[i] === 2) {
10968 echo(i, source[i - 1], false);
110 } else {
11140 echo(i, source[i - 1], true);
112 }
113 }
114 }
115 function echo(lineNum, str, bool) {
116108 console.log(colorful(lineNum, 'LINENUM') + '|' + colorful(str, bool ? 'YELLOW' : 'GREEN'));
117 }
118}
119/**
120 * colorful display
121 * @param {} str
122 * @param {} type
123 * @return {}
124 */
125function colorful(str, type) {
126222 var head = '\x1B[', foot = '\x1B[0m';
127222 var color = {
128 LINENUM : 36,
129 GREEN : 32,
130 YELLOW : 33,
131 RED : 31
132 };
133222 return head + color[type] + 'm' + str + foot;
134}

/Users/fish/workspace/jscoverage/reporter/html.js

98%
51
50
1
LineHitsSource
1
2/**
3 * Module dependencies.
4 */
5
6var fs = require('fs');
7var jade = require('jade');
8
9/**
10 * Initialize a new `JsCoverage` reporter.
11 *
12 * @param {Runner} runner
13 * @param {Boolean} output
14 * @api public
15 */
16
17exports.process = function (_$jscoverage, stats, covlevel) {
182 var result = map(_$jscoverage, stats);
191 var file = __dirname + '/templates/coverage.jade';
201 var str = fs.readFileSync(file, 'utf8');
211 var fn = jade.compile(str, { filename: file });
22
231 var html = fn({
24 cov: result,
25 coverageClass: function (n) {
2623 n = n / 100;
27 if (n >= covlevel.high) {
2818 return 'high';
29 }
30 if (n >= covlevel.middle) {
311 return 'medium';
32 }
33 if (n >= covlevel.low) {
344 return 'low';
35 }
360 return 'terrible';
37 }
38 });
39
401 fs.writeFileSync(process.cwd() + '/covreporter.html', html);
411 console.log('[REPORTER]: ', process.cwd() + '/covreporter.html');
42};
43
44/**
45 * Map jscoverage data to a JSON structure
46 * suitable for reporting.
47 *
48 * @param {Object} cov
49 * @return {Object}
50 * @api private
51 */
52
53function map(cov, stats) {
542 var ret = {
55 instrumentation: 'jscoverage',
56 sloc: 0,
57 hits: 0,
58 misses: 0,
59 coverage: 0,
60 files: []
61 };
62
63 for (var filename in cov) {
6422 var data = coverage(filename, cov[filename], stats[filename]);
6521 ret.files.push(data);
6621 ret.hits += data.hits;
6721 ret.misses += data.misses;
6821 ret.sloc += data.sloc;
69 }
70
711 ret.files.sort(function(a, b) {
7231 return a.filename.localeCompare(b.filename);
73 });
74
75 if (ret.sloc > 0) {
761 ret.coverage = (ret.hits / ret.sloc) * 100;
77 }
78
791 return ret;
80};
81
82/**
83 * Map jscoverage data for a single source file
84 * to a JSON structure suitable for reporting.
85 *
86 * @param {String} filename name of the source file
87 * @param {Object} data jscoverage coverage data
88 * @return {Object}
89 * @api private
90 */
91
92function coverage(filename, data, stats) {
9322 var ret = {
94 filename: filename,
95 coverage: stats.coverage * 100,
96 hits: stats.hits,
97 misses: stats.sloc - stats.hits,
98 sloc: stats.sloc,
99 source: {}
100 };
101
10222 data.source.forEach(function(line, num){
1032592 num++;
1042593 ret.source[num] = {
105 source: line,
106 coverage: data[num] === undefined ? '' : data[num],
107 condition: stats.condition[num] ? stats.condition[num] : []
108 };
109 });
110
11121 console.log(ret);
112
11321 return ret;
114}
115

/Users/fish/workspace/jscoverage/test/abc.js

100%
21
21
0
LineHitsSource
1var cde = require('./cde');
2var a = 1;
3var b = 2;
4var c = 3;
5var d;
6var e = a > 1 ? 1 : 2;
7
8var reset = {
9 abc:function () {}
10};
11
12function abc() {
131 var tmp = a + b;
141 var t = 1;
15 // test require ok
161 cde.a();
17 // test switch coverage
181 testSwitch('first');
19 /* @covignore */
20 testSwitch('second');
211 testSwitch();
221 return tmp + c;
23}
24
25function testSwitch(act) {
263 var res = [
27 'a',
28 'b',
29 'c'
30 ];
313 var tmp;
32 switch (act) {
33 case 'first' :
341 tmp = res[0];
351 break;
36 case 'second' :
371 tmp = res[1];
381 break;
39 default:
401 tmp = res.join(',');
41 }
423 return tmp;
43}
44abc();
45exports.abc = abc;
46

/Users/fish/workspace/jscoverage/test/cde.js

100%
10
10
0
LineHitsSource
1function a(){
23 var a = 1;
33 var b = 2;
4 if (a || b > a) {
53 console.log(a);
6 }
73 return a+b;
8}
9exports.a = a;

/Users/fish/workspace/jscoverage/test/example.js

100%
74
74
0
LineHitsSource
1/*!
2 * jscoverage: example.js
3 * Authors : fish <zhengxinlin@gmail.com> (https://github.com/fishbar)
4 * Create : 2014-04-03 15:20:13
5 * CopyRight 2014 (c) Fish And Other Contributors
6 */
7var expect = require('expect.js');
8
9exports.testTryCatch = function (a) {
10 try {
112 return a.run();
12 } catch (e) {
131 return 'catch error';
14 }
15};
16
17exports.testIfElse = function (a, b, c, d) {
18 if (a > 0 && b > 0) {
191 return 'ab';
20 } else if (c || d) {
211 return 'cd';
22 } else {
231 return 'unknow';
24 }
25};
26
27exports.testCondition = function (a, b ,c) {
283 return a || b > c ? '1' : '2';
29};
30
31exports.testWhile = function () {
321 var a = 0;
331 var res = '';
34 while(a < 2) {
352 res += 'a';
362 a ++;
37 }
381 var b = 0;
39 do {
402 res += 'b';
412 b ++;
42 } while (b < 2);
431 return res;
44};
45
46
47describe('example.js', function () {
481 it('test try catch', function () {
491 expect(exports.testTryCatch({})).to.be.match(/catch\ error/);
502 expect(exports.testTryCatch({run: function () {return 'run';}})).to.be('run');
51 });
52
531 it('test if else', function () {
541 expect(exports.testIfElse(1, 0)).to.be('unknow');
551 expect(exports.testIfElse(1, 1)).to.be('ab');
561 expect(exports.testIfElse(0, 0, 1, 0)).to.be('cd');
57 });
58
591 it('test condition', function () {
601 expect(exports.testCondition(1)).to.be('1');
611 expect(exports.testCondition(0, 2, 1)).to.be('1');
621 expect(exports.testCondition(0, 0, 0)).to.be('2');
63 });
64
651 it('test while', function () {
661 expect(exports.testWhile()).to.be('aabb');
67 });
68
691 it('test switch', function () {
701 expect(exports.testSwitch('a')).to.be('a');
711 expect(exports.testSwitch('b')).to.be('b');
721 expect(exports.testSwitch('c')).to.be('c');
731 expect(exports.testSwitch('0')).to.be('d');
74 });
75
761 it('test for', function () {
771 expect(exports.testFor()).to.be(1);
78 });
79
801 it('test binary', function () {
811 expect(exports.testBinary('a')).to.be('a');
821 expect(exports.testBinary(null, 'b')).to.be('b');
831 expect(exports.testBinary(null, null, null, 'b')).to.be('b');
841 expect(exports.testBinary(null, null, 'b')).to.be('b');
85 });
86});
87
88exports.testSwitch = function (a) {
894 var res = null;
90 switch (a) {
91 case 'a':
921 return 'a';
93 case 'b':
941 return 'b';
95 case 'c':
961 res = 'c';
971 break;
98 default:
991 res = 'd';
100 }
1012 return res;
102};
103
104exports.testFor = function () {
1051 var a = 0;
106 for (var i = 0; i < 3; i++) {
107 if (i > 1) {
1081 a ++;
109 }
110 }
1111 return a;
112};
113exports.testBinary = function (a, b, c, d) {
1144 return a || b || c || d;
115};
116// anonymous function
117(function(){
1181 var a = 1;
1191 console.log('this line should covered');
120})();

/Users/fish/workspace/jscoverage/test/patch.js

100%
12
12
0
LineHitsSource
1var patch = require('../lib/patch');
2var expect = require('expect.js');
3var checkModule = patch._get('checkModule');
4
5describe('patch.js', function () {
61 describe('checkModule()', function () {
71 it('should return false when native module', function () {
81 expect(checkModule('fs')).to.be(false);
9 });
101 it('should return false when /node_modules/ module', function () {
111 expect(checkModule('/abc/node_modules/fs')).to.be(false);
12 });
131 it('should return false when native module', function () {
141 expect(checkModule('fs')).to.be(false);
15 });
161 it('should return true when ignore no match', function () {
171 expect(checkModule('/abc/def')).to.be(true);
18 });
191 it('should return true when ignore no match', function () {
201 patch.setCovIgnore([/\/tet\/a.js/]);
211 expect(checkModule('/tet/a.js')).to.be(false);
22 });
23 });
24});

/Users/fish/workspace/jscoverage/test/reporter_detail.js

100%
22
22
0
LineHitsSource
1var expect = require('expect.js');
2var testMod = require('../reporter/detail');
3describe('exports.processLinesMask', function () {
41 it('should be ok when test1', function () {
51 var process = testMod._get('processLinesMask');
61 var input = [0, 0, 0, 1, 1, 0, 0, 1, 0, 0];
71 var result = [3, 2, 2, 1, 1, 2, 2, 1, 2, 2];
81 expect(process(input)).to.be.eql(result);
9 });
101 it('should be ok when test2', function () {
111 var process = testMod._get('processLinesMask');
121 var input = [0, 0, 1, 1, 0, 0, 1, 0, 1];
131 var result = [2, 2, 1, 1, 2, 2, 1, 2, 1];
141 expect(process(input)).to.be.eql(result);
15 });
161 it('should be ok when test3', function () {
171 var process = testMod._get('processLinesMask');
181 var input = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0];
191 var result = [2, 2, 1, 2, 2, 3, 0, 0, 0, 3, 2, 2, 1, 2];
201 expect(process(input)).to.be.eql(result);
21 });
221 it('should be ok when test4', function () {
231 var process = testMod._get('processLinesMask');
241 var input = [0];
251 var result = [0];
261 expect(process(input)).to.be.eql(result);
27 });
28
291 it('should be ok', function () {
301 testMod.process(_$jscoverage, {}, {high: 90, middle: 70, low: 20});
31 });
32});