Coverage

100%
185
185
0

lib/curiosity.js

100%
20
20
0
LineHitsSource
1/**
2 * Core dependencies.
3 */
4
51var path = require('path');
61var find = require('fine');
7
8/**
9 * Internal dependencies.
10 */
11
121var Runner = require('./curiosity/runner');
131var bundler = require('./curiosity/bundler');
141var Formatter = require('./curiosity/formatter');
15
16/**
17 * Analyze given `files`.
18 *
19 * @param {Array} files
20 * @api public
21 */
22
231exports.analyzeFiles = function(files) {
245 var runner = new Runner(files);
255 var results = runner.run();
265 return bundler.bundle(results);
27};
28
29/**
30 * Lookup & filter production and test files.
31 *
32 * @param {Array} files
33 * @param {Array} ignored
34 * @returns {Object}
35 * @api public
36 */
37
381exports.lookup = function(dirs, ignore) {
396 var lib = [];
406 ignore = (ignore || []).map(function(dir) {
411 return path.normalize(dir);
42 });
43
446 dirs.forEach(function(dir) {
456 var files = find(path.normalize(dir), { ext: '.js', ignore: ignore });
466 lib = lib.concat(files);
47 });
48
496 return lib;
50};
51
52/**
53 * Load given formatter.
54 *
55 * @param {String} name
56 * @returns {Function}
57 * @api public
58 */
59
601exports.loadFormatter = function(name) {
613 return require('./curiosity/formatter/' + name);
62};
63
64/**
65 * Export `Formatter`.
66 */
67
681exports.Formatter = Formatter;
69

lib/curiosity/bundler.js

100%
14
14
0
LineHitsSource
1/**
2 * Bundle an array of stats.
3 *
4 * @param {Array} stats
5 * @returns {Object}
6 * @api public
7 */
8
91exports.bundle = function(stats) {
105 var len = stats.length;
115 var ret = {};
125 var files = [];
135 var total = {};
14
155 stats.forEach(function(stat) {
166 var result = stat.result();
17
186 Object.keys(result).forEach(function(key) {
19186 total[key] = total[key] || { count: 0 };
20186 total[key].count += result[key].count;
21 });
22
236 files.push({ filename: stat.file, result: result });
24 });
25
265 Object.keys(total).forEach(function(key) {
27155 total[key].average = +(total[key].count / len).toFixed(2);
28 });
29
305 return {
31 files: files,
32 total: { files: len, result: total }
33 };
34};
35

lib/curiosity/countable.js

100%
1
1
0
LineHitsSource
11module.exports = {
2 'AssignmentExpression': 1,
3 'VariableDeclarator': 1,
4 'FunctionDeclaration': 1,
5 'FunctionExpression': 1,
6 'ReturnStatement': 1,
7
8 'IfStatement': 2,
9 'ConditionalExpression': 2,
10 'SwitchStatement': 2,
11
12 'DoWhileStatement': 3,
13 'WhileStatement': 3,
14 'ForStatement': 3,
15 'LabelStatement': 3,
16 'ForInStatement': 3,
17 'BreakStatement': 3,
18 'ContinueStatement': 3,
19
20 'ArrayExpression': 4,
21 'ObjectExpression': 4,
22
23 'TryStatement': 5,
24 'ThrowStatement': 5,
25
26 'String': 6,
27 'RegularExpression': 6,
28 'Boolean': 6,
29 'Numeric': 6,
30 'Null': 6,
31 'Identifier': 6,
32 'Semicolon': 6,
33
34 'EmptyStatement': 7,
35 'RequireCall': 7,
36 'FileLine': 7,
37 'Comment': 7,
38 'CallExpression': 7,
39};
40

lib/curiosity/formatter.js

100%
15
15
0
LineHitsSource
1/**
2 * Base formatter.
3 *
4 * @param {String} data
5 * @param {WritableStream} out
6 * @constructor
7 */
8
9function Formatter(data, out) {
103 this.data = data;
113 this.out = out;
12}
13
14/**
15 * Print given `msg`.
16 *
17 * @param {String} msg
18 * @param {Number} padding
19 * @api private
20 */
21
221Formatter.prototype.print = function(msg, padding) {
2313 padding = padding || 0;
2413 msg = msg || '';
2513 this.out.write(Array(padding).join(' ') + msg + '\n');
26};
27
28/**
29 * Print with left padding.
30 *
31 * @param {String} msg
32 * @api private
33 */
34
351Formatter.prototype.iprint = function(msg) {
368 this.print(msg, 8);
37};
38
39/**
40 * If `width` is greater than the length of `str`, return a new string of length `width`
41 * with `str` left justified
42 *
43 * @param {String} str
44 * @param {Number} width
45 * @returns {String}
46 * @api private
47 */
48
491Formatter.prototype.ljust = function(str, width) {
509 str += '';
519 var len = Math.max(0, width - str.length);
529 return str + Array(len + 1).join(' ');
53};
54
55/**
56 * Humanize the `key`.
57 *
58 * AssignmentExpression -> Assignment Expressions
59 *
60 * @returns {String}
61 * @api private
62 */
63
641Formatter.prototype.name = function(key) {
651 return key.split(/(?=[A-Z])/).join(' ') + 's';
66};
67
68/**
69 * Primary exports.
70 */
71
721module.exports = Formatter;
73

lib/curiosity/formatter/json.js

100%
7
7
0
LineHitsSource
1/**
2 * Core dependencies.
3 */
4
51var inherits = require('util').inherits;
6
7/**
8 * Internal dependencies.
9 */
10
111var Formattter = require('../formatter');
12
13/**
14 * JSON formatter.
15 *
16 * @constructor
17 */
18
19function Json() {
201 Formattter.apply(this, arguments);
21}
22
23/**
24 * Inherit from `Formattter`.
25 */
26
271inherits(Json, Formattter);
28
29/**
30 * Print the result.
31 *
32 * @api public
33 */
34
351Json.prototype.run = function() {
361 this.print(JSON.stringify(this.data, null, 2));
37};
38
39/**
40 * Primary exports.
41 */
42
431module.exports = Json;
44

lib/curiosity/formatter/summary.js

100%
25
25
0
LineHitsSource
1/**
2 * Core dependencies.
3 */
4
51var inherits = require('util').inherits;
6
7/**
8 * Internal dependencies.
9 */
10
111var Formattter = require('../formatter');
12
13/**
14 * Summary formatter.
15 *
16 * @constructor
17 */
18
19function Summary() {
201 Formattter.apply(this, arguments);
21}
22
23/**
24 * Inherit from `Formattter`.
25 */
26
271inherits(Summary, Formattter);
28
29/**
30 * Print the result.
31 *
32 * @api public
33 */
34
351Summary.prototype.run = function() {
361 var files = this.data.files;
371 var longest = 4;
381 var out = [];
39
401 files.forEach(function(file, i) {
412 var arr = [];
422 arr[0] = file.filename;
432 longest = Math.max(longest, file.filename.length);
442 arr[1] = 0;
452 Object.keys(file.result).forEach(function(key) {
464 arr[1] += file.result[key].count;
47 });
482 out.push(arr);
49 }, this);
50
511 this.print();
52
531 this.iprint(this.ljust('File', longest) + ' Sum of all metrics');
54
551 out.sort(function(a, b) {
561 if (a[1] < b[1]) return 1;
571 if (a[1] > b[1]) return -1;
581 return 0;
59 }).forEach(function(line) {
602 this.iprint(this.ljust(line[0], longest) + ' ' + line[1]);
61 }, this);
62
631 this.print();
64};
65
66/**
67 * Primary exports.
68 */
69
701module.exports = Summary;
71

lib/curiosity/formatter/total.js

100%
27
27
0
LineHitsSource
1/**
2 * Core dependencies.
3 */
4
51var inherits = require('util').inherits;
6
7/**
8 * Internal dependencies.
9 */
10
111var Formattter = require('../formatter');
121var countable = require('../countable');
13
14/**
15 * Total formatter.
16 *
17 * @constructor
18 */
19
20function Total() {
211 Formattter.apply(this, arguments);
22}
23
24/**
25 * Inherit from `Formattter`.
26 */
27
281inherits(Total, Formattter);
29
30/**
31 * Print the result.
32 *
33 * @api public
34 */
35
361Total.prototype.run = function() {
371 var metrics = this.data.total.result;
381 var lens = {};
391 var group;
40
411 var out = [
42 [ 'Metric', 'Total', 'Average/file' ],
43 [ '------', '-----', '------------' ]
44 ];
45
461 lens.name = out[0][0].length;
471 lens.count = out[0][1].length;
48
491 Object.keys(metrics).forEach(function(key) {
501 var name = this.name(key);
511 lens.name = Math.max(lens.name, name.length);
521 lens.count = Math.max(lens.count, ('' + metrics[key].count).length);
531 out.push([ name, metrics[key].count, metrics[key].average, key ]);
54 }, this);
55
561 this.print();
57
581 for (var i = 0, len = out.length; i < len; i++) {
593 var key = out[i][3];
603 if (typeof group !== 'undefined' && group != countable[key]) this.print();
613 group = countable[key];
62
633 this.iprint([
64 this.ljust(out[i][0], lens.name),
65 this.ljust(out[i][1], lens.count),
66 out[i][2]
67 ].join(' '));
68 }
69
701 this.iprint('----------------------');
711 this.iprint('Files: ' + this.data.total.files);
721 this.print();
73};
74
75/**
76 * Primary export.
77 */
78
791module.exports = Total;
80

lib/curiosity/group.js

100%
11
11
0
LineHitsSource
1/**
2 * Core dependencies.
3 */
4
51var fs = require('fs');
6
7/**
8 * External dependencies.
9 */
10
111var parse = require('esprima').parse;
12
13/**
14 * Group of files.
15 *
16 * @param {Array} files
17 * @constructor
18 */
19
20function Group(files) {
215 this.files = files;
22}
23
24/**
25 * Parse and execute `fn` for every file.
26 *
27 * @param {Function} fn
28 * @param {Object} context
29 * @api public
30 */
31
321Group.prototype.each = function(fn, ctx) {
335 this.files.forEach(function(file) {
346 var data = fs.readFileSync(file, 'utf8');
356 var ast = null;
36
376 try {
386 ast = parse(data, { tokens: true, comment: true });
39 } catch (e) {}
40
4112 if (ast) fn.call(ctx, ast, file, data);
42 });
43};
44
45/**
46 * Primary export.
47 */
48
491module.exports = Group;
50

lib/curiosity/researcher.js

100%
25
25
0
LineHitsSource
1/**
2 * Core dependencies.
3 */
4
51var basename = require('path').basename;
6
7/**
8 * Internal dependencies.
9 */
10
111var countable = require('./countable');
12
13/**
14 * Researcher.
15 *
16 * @param {Stats} stats
17 * @constructor
18 */
19
20function Researcher(stats) {
216 this.stats = stats;
22
236 Object.keys(countable).forEach(function(key) {
24186 this.inc(key, 0);
25 }, this);
26}
27
28/**
29 * Analyze nodes and tokens.
30 *
31 * @param {Object} node or token
32 * @api public
33 */
34
351Researcher.prototype.node = function(node) {
3665 switch (node.type) {
37 case 'AssignmentExpression':
38 case 'ArrayExpression':
39 case 'ConditionalExpression':
40 case 'DoWhileStatement':
41 case 'ForStatement':
42 case 'ForInStatement':
43 case 'FunctionDeclaration':
44 case 'FunctionExpression':
45 case 'IfStatement':
46 case 'ObjectExpression':
47 case 'SwitchStatement':
48 case 'ReturnStatement':
49 case 'ThrowStatement':
50 case 'TryStatement':
51 case 'VariableDeclarator':
52 case 'WhileStatement':
53 case 'LabelStatement':
54 case 'BreakStatement':
55 case 'ContinueStatement':
56 case 'EmptyStatement':
57 case 'Boolean':
58 case 'Identifier':
59 case 'Null':
60 case 'Numeric':
61 case 'RegularExpression':
62 case 'String':
6323 return this.inc(node);
64
65 case 'Line':
66 case 'Block':
673 return this.inc('Comment');
68
69 case 'CallExpression':
703 return this.fncall(node);
71
72 case 'Punctuator':
7317 return this.punctuator(node);
74 }
75};
76
77/**
78 * Analyze a file.
79 *
80 * @param {String} filename
81 * @param {String} file contents
82 * @api public
83 */
84
851Researcher.prototype.file = function(filename, data) {
866 this.inc('FileLine', linesLen(data));
87};
88
89/**
90 * Increment a counter.
91 *
92 * @param {String|Object} type / node
93 * @api private
94 */
95
961Researcher.prototype.inc = function(node, inc) {
97232 this.stats.inc(Object(node) === node ? node.type : node, inc);
98};
99
100/**
101 * Handle semicolons.
102 *
103 * @param {Object} token
104 * @api private
105 */
106
1071Researcher.prototype.punctuator = function(token) {
10826 if (token.value !== ';') return;
1098 this.inc('Semicolon');
110};
111
112/**
113 * Handle function calls and count requires.
114 *
115 * This assumes quite a bit. For instance it simply thinks
116 * that any `require` call is the Node `require`. While
117 * this might not be true, a proper detection would be a little
118 * bit too much for the purposes of this library.
119 *
120 * @param {Object} node
121 * @api private
122 */
123
1241Researcher.prototype.fncall = function(node) {
1253 var callee = node.callee;
126
1273 if (callee.type === 'Identifier' && callee.name === 'require') {
1283 this.inc('RequireCall');
129 }
130
1313 this.inc(node.type);
132};
133
134/**
135 * Split `str` into lines and return its size.
136 *
137 * @param {String} str
138 * @returns {Number}
139 * @api private
140 */
141
142function linesLen(str) {
1436 return str.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/).length - 1;
144}
145
146/**
147 * Primary export.
148 */
149
1501module.exports = Researcher;
151

lib/curiosity/runner.js

100%
21
21
0
LineHitsSource
1/**
2 * Internal dependencies.
3 */
4
51var Group = require('./group');
61var Stats = require('./stats');
71var Researcher = require('./researcher');
8
9/**
10 * Runner.
11 *
12 * @param {Array} files
13 * @constructor
14 */
15
16function Runner(files) {
175 this.files = files;
18}
19
20/**
21 * Iterate over all files and collect
22 * the needed statistics
23 *
24 * @api public
25 */
26
271Runner.prototype.run = function() {
285 var group = new Group(this.files);
295 var results = [];
30
315 group.each(function(ast, file, data) {
326 var stats = new Stats(file);
336 var researcher = new Researcher(stats);
346 researcher.file(file, data);
35
366 (function walk(node) {
3789 var key = null;
3889 var child = null;
39
40154 if (node.type) researcher.node(node);
41
4289 Object.keys(node).forEach(function(key) {
43196 child = node[key];
44279 if (Object(child) === child) walk(child);
45 });
46 })(ast);
47
486 results.push(stats);
49 });
50
515 return results;
52};
53
54/**
55 * Primary export.
56 */
57
581module.exports = Runner;
59

lib/curiosity/stats.js

100%
19
19
0
LineHitsSource
1/**
2 * Stats.
3 *
4 * @param {String} file
5 * @constructor
6 */
7
8function Stats(file) {
96 this.file = file;
106 this.counters = {};
116 this.keys = [];
12}
13
14/**
15 * Increment a counter.
16 *
17 * @param {String} type
18 * @param {Number}
19 * @api public
20 */
21
221Stats.prototype.inc = function(type, inc) {
23232 inc = typeof inc === 'undefined' ? 1 : inc;
24232 this.counters[type] = this.counters[type] || 0;
25232 this.counters[type] += inc;
26232 this.key(type);
27};
28
29/**
30 * Return the stored data.
31 *
32 * @returns {Object}
33 * @api public
34 */
35
361Stats.prototype.result = function() {
376 var ret = {};
38
396 this.keys.forEach(function(key) {
40186 ret[key] = { count: this.counter(key) };
41 }, this);
42
436 return ret;
44};
45
46/**
47 * Register a new key.
48 *
49 * @param {String} key
50 * @api private
51 */
52
531Stats.prototype.key = function(key) {
54278 if (~this.keys.indexOf(key)) return;
55186 this.keys.push(key);
56};
57
58/**
59 * Return counter for `type`.
60 *
61 * @param {String} type
62 * @returns {Number}
63 * @api private
64 */
65
661Stats.prototype.counter = function(type) {
67186 return this.counters[type] || 0;
68};
69
70/**
71 * Primary export.
72 */
73
741module.exports = Stats;
75