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.
298 lines
7.7 KiB
298 lines
7.7 KiB
/* |
|
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;
|
|
|