jsc
Version:
Javascript complexity tool
164 lines (142 loc) • 4.7 kB
JavaScript
var fs = require('fs');
var check = require('check-types');
var _ = require('lodash');
var Vector = require('gauss').Vector;
var removeMatchingPrefixes = require('./utils').removeMatchingPrefixes;
var json = /\.json$/i;
function getComplexityInfo() {
var info = 'LOC - lines of code (logical, lower is better)\n' +
'Cyclomatic - McCabe complexity (lower is better)\n\thttp://en.wikipedia.org/wiki/Cyclomatic_complexity\n' +
'Halstead difficulty (lower is better)\n\thttp://en.wikipedia.org/wiki/Halstead_complexity_measures\n' +
'maintainability (higher is better)\n\thttp://jscomplexity.org/complexity\n';
return info;
}
function toNumber(value) {
return +value;
}
// output complexity report chart to file
function writeComplexityChart(metrics, filename) {
check.verifyArray(metrics, 'expected array of metrics');
// transform array of arrays to object
var o = {};
metrics.slice(1).forEach(function (measured) {
var filename = measured[0];
var loc = measured[1];
var cyclomatic = measured[2];
var halstead = measured[3];
o[filename] = {
loc: toNumber(loc),
cyclomatic: toNumber(cyclomatic),
halstead: toNumber(halstead)
};
});
check.verifyString(filename, 'output filename ' + filename + ' should be a string');
log.debug('output report filename', filename);
var data = JSON.stringify(o, null, 2);
fs.writeFileSync(filename, data + '\n', 'utf-8');
log.info('Saved metrics to', filename);
}
var Table = require('cli-table');
function makeTable(titles, rows, colorful, complexityLimit) {
console.assert(Array.isArray(titles), 'column titles should be an array, not', titles);
console.assert(Array.isArray(rows), 'rows should be an array, not', rows);
complexityLimit = complexityLimit || 1000;
console.assert(complexityLimit > 0, 'invalid complexity limit', complexityLimit);
var table;
if (colorful) {
table = new Table({
head: titles
});
} else {
table = new Table({
head: titles,
style: {
compact: true,
'padding-left': 1,
'padding-right': 1
},
chars: {
'top': '-',
'top-mid': '+',
'top-left': '+',
'top-right': '+',
'bottom': '-',
'bottom-mid': '+',
'bottom-left': '+',
'bottom-right': '+',
'left': '|',
'left-mid': '+',
'mid': '-',
'mid-mid': '+',
'right': '|',
'right-mid': '+'
}
});
}
var complexityColumn = 2;
if (colorful) {
rows.forEach(function (row, index) {
var complexity = row[complexityColumn];
if (complexity > complexityLimit) {
var redRow = row.map(function(cell) {
return String(cell).bold.red;
});
rows[index] = redRow;
}
});
}
rows.forEach(function(row) {
table.push(row);
});
return table;
}
function writeReportTables(options) {
options = options || {};
console.assert(Array.isArray(options.metrics), 'metrics should be an array, not', options.metrics);
console.assert(options.metrics.length >= 1, 'invalid complexity length', options.metrics.length);
if (options.metrics.length === 1) {
log.log('nothing to report, empty complexity array');
return;
}
var info = getComplexityInfo();
check.verifyString(info, 'complexity info should be a string, not ' + info);
if (options.minimal) {
info = '';
}
var titles = options.metrics[0];
var rows = options.metrics.slice(1);
if (options.filename) {
(function () {
var table = makeTable(titles, rows, false);
console.assert(table, 'could not make plain table');
var reportFilename = options.filename.replace(json, '.txt');
var text = table.toString() + '\n' + info;
fs.writeFileSync(reportFilename, text, 'utf-8');
log.info('Saved report text', reportFilename);
}());
}
(function () {
log.debug('making table, colors?', options.colors, 'complexity limit', options.limit);
// grab values BEFORE they are obscured by terminal colors
// make sure values are numbers
var complexities = _.pluck(rows, 2).map(toNumber);
var halsteadComplexities = _.pluck(rows, 3).map(toNumber);
var rowsNoPrefix = removeMatchingPrefixes(rows);
var table = makeTable(titles, rowsNoPrefix, options.colors, options.limit);
console.assert(table, 'could not make table, colors?', options.colors);
var text = table.toString();
if (info) {
text += '\n' + info;
}
console.log(text);
var set = new Vector(complexities);
console.log('Cyclomatic: min', set.min(), 'mean', set.mean().toFixed(1), 'max', set.max());
set = new Vector(halsteadComplexities);
console.log('Halstead: min', set.min(), 'mean', set.mean().toFixed(1), 'max', set.max());
}());
}
module.exports = {
writeComplexityChart: writeComplexityChart,
writeReportTables: writeReportTables,
getComplexityInfo: getComplexityInfo
};