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;