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

228 lines (203 loc) 7.76 kB
var path = require('path'), util = require('util'), Report = require('./index'), FileWriter = require('../util/file-writer'), TreeSummarizer = require('../util/tree-summarizer'), utils = require('../object-utils'); /** * a `Report` implementation that produces a clover-style XML file. * * Usage * ----- * * var report = require('istanbul').Report.create('clover'); * * @class CloverReport * @module report * @extends Report * @constructor * @param {Object} opts optional * @param {String} [opts.dir] the directory in which to the clover.xml will be written * @param {String} [opts.file] the file name, defaulted to config attribute or 'clover.xml' */ function CloverReport(opts) { Report.call(this); opts = opts || {}; this.projectRoot = process.cwd(); this.dir = opts.dir || this.projectRoot; this.file = opts.file || this.getDefaultConfig().file; this.opts = opts; } CloverReport.TYPE = 'clover'; util.inherits(CloverReport, Report); function asJavaPackage(node) { return node.displayShortName(). replace(/\//g, '.'). replace(/\\/g, '.'). replace(/\.$/, ''); } function asClassName(node) { /*jslint regexp: true */ return node.fullPath().replace(/.*[\\\/]/, ''); } function quote(thing) { return '"' + thing + '"'; } function attr(n, v) { return ' ' + n + '=' + quote(v) + ' '; } function branchCoverageByLine(fileCoverage) { var branchMap = fileCoverage.branchMap, branches = fileCoverage.b, ret = {}; Object.keys(branchMap).forEach(function (k) { var line = branchMap[k].line, branchData = branches[k]; ret[line] = ret[line] || []; ret[line].push.apply(ret[line], branchData); }); Object.keys(ret).forEach(function (k) { var dataArray = ret[k], covered = dataArray.filter(function (item) { return item > 0; }), coverage = covered.length / dataArray.length * 100; ret[k] = { covered: covered.length, total: dataArray.length, coverage: coverage }; }); return ret; } function addClassStats(node, fileCoverage, writer) { fileCoverage = utils.incrementIgnoredTotals(fileCoverage); var metrics = node.metrics, branchByLine = branchCoverageByLine(fileCoverage), fnMap, lines; writer.println('\t\t\t<file' + attr('name', asClassName(node)) + attr('path', node.fullPath()) + '>'); writer.println('\t\t\t\t<metrics' + attr('statements', metrics.lines.total) + attr('coveredstatements', metrics.lines.covered) + attr('conditionals', metrics.branches.total) + attr('coveredconditionals', metrics.branches.covered) + attr('methods', metrics.functions.total) + attr('coveredmethods', metrics.functions.covered) + '/>'); fnMap = fileCoverage.fnMap; lines = fileCoverage.l; Object.keys(lines).forEach(function (k) { var str = '\t\t\t\t<line' + attr('num', k) + attr('count', lines[k]), branchDetail = branchByLine[k]; if (!branchDetail) { str += ' type="stmt" '; } else { str += ' type="cond" ' + attr('truecount', branchDetail.covered) + attr('falsecount', (branchDetail.total - branchDetail.covered)); } writer.println(str + '/>'); }); writer.println('\t\t\t</file>'); } function walk(node, collector, writer, level, projectRoot) { var metrics, totalFiles = 0, totalPackages = 0, totalLines = 0, tempLines = 0; if (level === 0) { metrics = node.metrics; writer.println('<?xml version="1.0" encoding="UTF-8"?>'); writer.println('<coverage' + attr('generated', Date.now()) + 'clover="3.2.0">'); writer.println('\t<project' + attr('timestamp', Date.now()) + attr('name', 'All Files') + '>'); node.children.filter(function (child) { return child.kind === 'dir'; }). forEach(function (child) { totalPackages += 1; child.children.filter(function (child) { return child.kind !== 'dir'; }). forEach(function (child) { Object.keys(collector.fileCoverageFor(child.fullPath()).l).forEach(function (k){ tempLines = k; }); totalLines += Number(tempLines); totalFiles += 1; }); }); writer.println('\t\t<metrics' + attr('statements', metrics.lines.total) + attr('coveredstatements', metrics.lines.covered) + attr('conditionals', metrics.branches.total) + attr('coveredconditionals', metrics.branches.covered) + attr('methods', metrics.functions.total) + attr('coveredmethods', metrics.functions.covered) + attr('elements', metrics.lines.total + metrics.branches.total + metrics.functions.total) + attr('coveredelements', metrics.lines.covered + metrics.branches.covered + metrics.functions.covered) + attr('complexity', 0) + attr('packages', totalPackages) + attr('files', totalFiles) + attr('classes', totalFiles) + attr('loc', totalLines) + attr('ncloc', totalLines) + '/>'); } if (node.packageMetrics) { metrics = node.packageMetrics; writer.println('\t\t<package' + attr('name', asJavaPackage(node)) + '>'); writer.println('\t\t\t<metrics' + attr('statements', metrics.lines.total) + attr('coveredstatements', metrics.lines.covered) + attr('conditionals', metrics.branches.total) + attr('coveredconditionals', metrics.branches.covered) + attr('methods', metrics.functions.total) + attr('coveredmethods', metrics.functions.covered) + '/>'); node.children.filter(function (child) { return child.kind !== 'dir'; }). forEach(function (child) { addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer); }); writer.println('\t\t</package>'); } node.children.filter(function (child) { return child.kind === 'dir'; }). forEach(function (child) { walk(child, collector, writer, level + 1, projectRoot); }); if (level === 0) { writer.println('\t</project>'); writer.println('</coverage>'); } } Report.mix(CloverReport, { synopsis: function () { return 'XML coverage report that can be consumed by the clover tool'; }, getDefaultConfig: function () { return { file: 'clover.xml' }; }, writeReport: function (collector, sync) { var summarizer = new TreeSummarizer(), outputFile = path.join(this.dir, this.file), writer = this.opts.writer || new FileWriter(sync), projectRoot = this.projectRoot, that = this, tree, root; collector.files().forEach(function (key) { summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key))); }); tree = summarizer.getTreeSummary(); root = tree.root; writer.on('done', function () { that.emit('done'); }); writer.writeFile(outputFile, function (contentWriter) { walk(root, collector, contentWriter, 0, projectRoot); writer.done(); }); } }); module.exports = CloverReport;