API Docs for: 0.3.0
Show:

File: lib/report/clover.js

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) {
    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;