You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

299 lines
7.7 KiB

2 months ago
/*
Copyright 2012-2015, Yahoo Inc.
Copyrights licensed under the New BSD License. See the accompanying LICENSE
file for terms.
*/
'use strict';
const { ReportBase } = require('istanbul-lib-report');
const NAME_COL = 4;
const PCT_COLS = 7;
const MISSING_COL = 17;
const TAB_SIZE = 1;
const DELIM = ' | ';
function padding(num, ch) {
let str = '';
let i;
ch = ch || ' ';
for (i = 0; i < num; i += 1) {
str += ch;
}
return str;
}
function fill(str, width, right, tabs) {
tabs = tabs || 0;
str = String(str);
const leadingSpaces = tabs * TAB_SIZE;
const remaining = width - leadingSpaces;
const leader = padding(leadingSpaces);
let fmtStr = '';
if (remaining > 0) {
const strlen = str.length;
let fillStr;
if (remaining >= strlen) {
fillStr = padding(remaining - strlen);
} else {
fillStr = '...';
const length = remaining - fillStr.length;
str = str.substring(strlen - length);
right = true;
}
fmtStr = right ? fillStr + str : str + fillStr;
}
return leader + fmtStr;
}
function formatName(name, maxCols, level) {
return fill(name, maxCols, false, level);
}
function formatPct(pct, width) {
return fill(pct, width || PCT_COLS, true, 0);
}
function nodeMissing(node) {
if (node.isSummary()) {
return '';
}
const metrics = node.getCoverageSummary();
const isEmpty = metrics.isEmpty();
const lines = isEmpty ? 0 : metrics.lines.pct;
let coveredLines;
const fileCoverage = node.getFileCoverage();
if (lines === 100) {
const branches = fileCoverage.getBranchCoverageByLine();
coveredLines = Object.entries(branches).map(([key, { coverage }]) => [
key,
coverage === 100
]);
} else {
coveredLines = Object.entries(fileCoverage.getLineCoverage());
}
let newRange = true;
const ranges = coveredLines
.reduce((acum, [line, hit]) => {
if (hit) newRange = true;
else {
line = parseInt(line);
if (newRange) {
acum.push([line]);
newRange = false;
} else acum[acum.length - 1][1] = line;
}
return acum;
}, [])
.map(range => {
const { length } = range;
if (length === 1) return range[0];
return `${range[0]}-${range[1]}`;
});
return [].concat(...ranges).join(',');
}
function nodeName(node) {
return node.getRelativeName() || 'All files';
}
function depthFor(node) {
let ret = 0;
node = node.getParent();
while (node) {
ret += 1;
node = node.getParent();
}
return ret;
}
function nullDepthFor() {
return 0;
}
function findWidth(node, context, nodeExtractor, depthFor = nullDepthFor) {
let last = 0;
function compareWidth(node) {
last = Math.max(
last,
TAB_SIZE * depthFor(node) + nodeExtractor(node).length
);
}
const visitor = {
onSummary: compareWidth,
onDetail: compareWidth
};
node.visit(context.getVisitor(visitor));
return last;
}
function makeLine(nameWidth, missingWidth) {
const name = padding(nameWidth, '-');
const pct = padding(PCT_COLS, '-');
const elements = [];
elements.push(name);
elements.push(pct);
elements.push(padding(PCT_COLS + 1, '-'));
elements.push(pct);
elements.push(pct);
elements.push(padding(missingWidth, '-'));
return elements.join(DELIM.replace(/ /g, '-')) + '-';
}
function tableHeader(maxNameCols, missingWidth) {
const elements = [];
elements.push(formatName('File', maxNameCols, 0));
elements.push(formatPct('% Stmts'));
elements.push(formatPct('% Branch', PCT_COLS + 1));
elements.push(formatPct('% Funcs'));
elements.push(formatPct('% Lines'));
elements.push(formatName('Uncovered Line #s', missingWidth));
return elements.join(DELIM) + ' ';
}
function isFull(metrics) {
return (
metrics.statements.pct === 100 &&
metrics.branches.pct === 100 &&
metrics.functions.pct === 100 &&
metrics.lines.pct === 100
);
}
function tableRow(
node,
context,
colorizer,
maxNameCols,
level,
skipEmpty,
skipFull,
missingWidth
) {
const name = nodeName(node);
const metrics = node.getCoverageSummary();
const isEmpty = metrics.isEmpty();
if (skipEmpty && isEmpty) {
return '';
}
if (skipFull && isFull(metrics)) {
return '';
}
const mm = {
statements: isEmpty ? 0 : metrics.statements.pct,
branches: isEmpty ? 0 : metrics.branches.pct,
functions: isEmpty ? 0 : metrics.functions.pct,
lines: isEmpty ? 0 : metrics.lines.pct
};
const colorize = isEmpty
? function(str) {
return str;
}
: function(str, key) {
return colorizer(str, context.classForPercent(key, mm[key]));
};
const elements = [];
elements.push(colorize(formatName(name, maxNameCols, level), 'statements'));
elements.push(colorize(formatPct(mm.statements), 'statements'));
elements.push(colorize(formatPct(mm.branches, PCT_COLS + 1), 'branches'));
elements.push(colorize(formatPct(mm.functions), 'functions'));
elements.push(colorize(formatPct(mm.lines), 'lines'));
elements.push(
colorizer(
formatName(nodeMissing(node), missingWidth),
mm.lines === 100 ? 'medium' : 'low'
)
);
return elements.join(DELIM) + ' ';
}
class TextReport extends ReportBase {
constructor(opts) {
super(opts);
opts = opts || {};
const { maxCols } = opts;
this.file = opts.file || null;
this.maxCols = maxCols != null ? maxCols : process.stdout.columns || 80;
this.cw = null;
this.skipEmpty = opts.skipEmpty;
this.skipFull = opts.skipFull;
}
onStart(root, context) {
this.cw = context.writer.writeFile(this.file);
this.nameWidth = Math.max(
NAME_COL,
findWidth(root, context, nodeName, depthFor)
);
this.missingWidth = Math.max(
MISSING_COL,
findWidth(root, context, nodeMissing)
);
if (this.maxCols > 0) {
const pct_cols = DELIM.length + 4 * (PCT_COLS + DELIM.length) + 2;
const maxRemaining = this.maxCols - (pct_cols + MISSING_COL);
if (this.nameWidth > maxRemaining) {
this.nameWidth = maxRemaining;
this.missingWidth = MISSING_COL;
} else if (this.nameWidth < maxRemaining) {
const maxRemaining = this.maxCols - (this.nameWidth + pct_cols);
if (this.missingWidth > maxRemaining) {
this.missingWidth = maxRemaining;
}
}
}
const line = makeLine(this.nameWidth, this.missingWidth);
this.cw.println(line);
this.cw.println(tableHeader(this.nameWidth, this.missingWidth));
this.cw.println(line);
}
onSummary(node, context) {
const nodeDepth = depthFor(node);
const row = tableRow(
node,
context,
this.cw.colorize.bind(this.cw),
this.nameWidth,
nodeDepth,
this.skipEmpty,
this.skipFull,
this.missingWidth
);
if (row) {
this.cw.println(row);
}
}
onDetail(node, context) {
return this.onSummary(node, context);
}
onEnd() {
this.cw.println(makeLine(this.nameWidth, this.missingWidth));
this.cw.close();
}
}
module.exports = TextReport;