blob: 8ab2b7d13705d89d91696e12fc042c60cabfc3a7 [file] [log] [blame]
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
mkdirp = require('mkdirp'),
util = require('util'),
fs = require('fs'),
defaults = require('./common/defaults'),
Report = require('./index'),
TreeSummarizer = require('../util/tree-summarizer'),
utils = require('../object-utils'),
PCT_COLS = 9,
MISSING_COL = 15,
TAB_SIZE = 1,
DELIM = ' |',
COL_DELIM = '-|';
/**
* a `Report` implementation that produces text output in a detailed table.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('text');
*
* @class TextReport
* @extends Report
* @module report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file
* @param {String} [opts.file] the filename for the report. When omitted, the report is written to console
* @param {Number} [opts.maxCols] the max column width of the report. By default, the width of the report is adjusted based on the length of the paths
* to be reported.
*/
function TextReport(opts) {
Report.call(this);
opts = opts || {};
this.dir = opts.dir || process.cwd();
this.file = opts.file;
this.summary = opts.summary;
this.maxCols = opts.maxCols || 0;
this.watermarks = opts.watermarks || defaults.watermarks();
}
TextReport.TYPE = 'text';
util.inherits(TextReport, Report);
function padding(num, ch) {
var str = '',
i;
ch = ch || ' ';
for (i = 0; i < num; i += 1) {
str += ch;
}
return str;
}
function fill(str, width, right, tabs, clazz) {
tabs = tabs || 0;
str = String(str);
var leadingSpaces = tabs * TAB_SIZE,
remaining = width - leadingSpaces,
leader = padding(leadingSpaces),
fmtStr = '',
fillStr,
strlen = str.length;
if (remaining > 0) {
if (remaining >= strlen) {
fillStr = padding(remaining - strlen);
fmtStr = right ? fillStr + str : str + fillStr;
} else {
fmtStr = str.substring(strlen - remaining);
fmtStr = '... ' + fmtStr.substring(4);
}
}
fmtStr = defaults.colorize(fmtStr, clazz);
return leader + fmtStr;
}
function formatName(name, maxCols, level, clazz) {
return fill(name, maxCols, false, level, clazz);
}
function formatPct(pct, clazz, width) {
return fill(pct, width || PCT_COLS, true, 0, clazz);
}
function nodeName(node) {
return node.displayShortName() || 'All files';
}
function tableHeader(maxNameCols) {
var elements = [];
elements.push(formatName('File', maxNameCols, 0));
elements.push(formatPct('% Stmts'));
elements.push(formatPct('% Branch'));
elements.push(formatPct('% Funcs'));
elements.push(formatPct('% Lines'));
elements.push(formatPct('Uncovered Lines', undefined, MISSING_COL));
return elements.join(' |') + ' |';
}
function collectMissingLines(kind, linesCovered) {
var missingLines = [];
if (kind !== 'file') {
return [];
}
Object.keys(linesCovered).forEach(function (key) {
if (!linesCovered[key]) {
missingLines.push(key);
}
});
return missingLines;
}
function tableRow(node, maxNameCols, level, watermarks) {
var name = nodeName(node),
statements = node.metrics.statements.pct,
branches = node.metrics.branches.pct,
functions = node.metrics.functions.pct,
lines = node.metrics.lines.pct,
missingLines = collectMissingLines(node.kind, node.metrics.linesCovered),
elements = [];
elements.push(formatName(name, maxNameCols, level, defaults.classFor('statements', node.metrics, watermarks)));
elements.push(formatPct(statements, defaults.classFor('statements', node.metrics, watermarks)));
elements.push(formatPct(branches, defaults.classFor('branches', node.metrics, watermarks)));
elements.push(formatPct(functions, defaults.classFor('functions', node.metrics, watermarks)));
elements.push(formatPct(lines, defaults.classFor('lines', node.metrics, watermarks)));
elements.push(formatPct(missingLines.join(','), 'low', MISSING_COL));
return elements.join(DELIM) + DELIM;
}
function findNameWidth(node, level, last) {
last = last || 0;
level = level || 0;
var idealWidth = TAB_SIZE * level + nodeName(node).length;
if (idealWidth > last) {
last = idealWidth;
}
node.children.forEach(function (child) {
last = findNameWidth(child, level + 1, last);
});
return last;
}
function makeLine(nameWidth) {
var name = padding(nameWidth, '-'),
pct = padding(PCT_COLS, '-'),
elements = [];
elements.push(name);
elements.push(pct);
elements.push(pct);
elements.push(pct);
elements.push(pct);
elements.push(padding(MISSING_COL, '-'));
return elements.join(COL_DELIM) + COL_DELIM;
}
function walk(node, nameWidth, array, level, watermarks) {
var line;
if (level === 0) {
line = makeLine(nameWidth);
array.push(line);
array.push(tableHeader(nameWidth));
array.push(line);
} else {
array.push(tableRow(node, nameWidth, level, watermarks));
}
node.children.forEach(function (child) {
walk(child, nameWidth, array, level + 1, watermarks);
});
if (level === 0) {
array.push(line);
array.push(tableRow(node, nameWidth, level, watermarks));
array.push(line);
}
}
Report.mix(TextReport, {
synopsis: function () {
return 'text report that prints a coverage line for every file, typically to console';
},
getDefaultConfig: function () {
return { file: null, maxCols: 0 };
},
writeReport: function (collector /*, sync */) {
var summarizer = new TreeSummarizer(),
tree,
root,
nameWidth,
statsWidth = 4 * (PCT_COLS + 2) + MISSING_COL,
maxRemaining,
strings = [],
text;
collector.files().forEach(function (key) {
summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(
collector.fileCoverageFor(key)
));
});
tree = summarizer.getTreeSummary();
root = tree.root;
nameWidth = findNameWidth(root);
if (this.maxCols > 0) {
maxRemaining = this.maxCols - statsWidth - 2;
if (nameWidth > maxRemaining) {
nameWidth = maxRemaining;
}
}
walk(root, nameWidth, strings, 0, this.watermarks);
text = strings.join('\n') + '\n';
if (this.file) {
mkdirp.sync(this.dir);
fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8');
} else {
console.log(text);
}
this.emit('done');
}
});
module.exports = TextReport;