/* 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 || {}; 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;