UNPKG

istanbul

Version:

Yet another JS code coverage tool that computes statement, line, function and branch coverage with module loader hooks to transparently add coverage when running tests. Supports all JS coverage use cases including unit tests, server side functional tests

235 lines (208 loc) 7.06 kB
/* 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;