1 var path = require('path'),
2 util = require('util'),
3 Report = require('./index'),
4 FileWriter = require('../util/file-writer'),
5 TreeSummarizer = require('../util/tree-summarizer'),
6 utils = require('../object-utils');
9 * a `Report` implementation that produces a clover-style XML file.
14 * var report = require('istanbul').Report.create('clover');
20 * @param {Object} opts optional
21 * @param {String} [opts.dir] the directory in which to the clover.xml will be written
22 * @param {String} [opts.file] the file name, defaulted to config attribute or 'clover.xml'
24 function CloverReport(opts) {
27 this.projectRoot = process.cwd();
28 this.dir = opts.dir || this.projectRoot;
29 this.file = opts.file || this.getDefaultConfig().file;
33 CloverReport.TYPE = 'clover';
34 util.inherits(CloverReport, Report);
36 function asJavaPackage(node) {
37 return node.displayShortName().
43 function asClassName(node) {
44 /*jslint regexp: true */
45 return node.fullPath().replace(/.*[\\\/]/, '');
48 function quote(thing) {
49 return '"' + thing + '"';
53 return ' ' + n + '=' + quote(v) + ' ';
56 function branchCoverageByLine(fileCoverage) {
57 var branchMap = fileCoverage.branchMap,
58 branches = fileCoverage.b,
60 Object.keys(branchMap).forEach(function (k) {
61 var line = branchMap[k].line,
62 branchData = branches[k];
63 ret[line] = ret[line] || [];
64 ret[line].push.apply(ret[line], branchData);
66 Object.keys(ret).forEach(function (k) {
67 var dataArray = ret[k],
68 covered = dataArray.filter(function (item) { return item > 0; }),
69 coverage = covered.length / dataArray.length * 100;
70 ret[k] = { covered: covered.length, total: dataArray.length, coverage: coverage };
75 function addClassStats(node, fileCoverage, writer) {
76 fileCoverage = utils.incrementIgnoredTotals(fileCoverage);
78 var metrics = node.metrics,
79 branchByLine = branchCoverageByLine(fileCoverage),
83 writer.println('\t\t\t<file' +
84 attr('name', asClassName(node)) +
85 attr('path', node.fullPath()) +
88 writer.println('\t\t\t\t<metrics' +
89 attr('statements', metrics.lines.total) +
90 attr('coveredstatements', metrics.lines.covered) +
91 attr('conditionals', metrics.branches.total) +
92 attr('coveredconditionals', metrics.branches.covered) +
93 attr('methods', metrics.functions.total) +
94 attr('coveredmethods', metrics.functions.covered) +
97 fnMap = fileCoverage.fnMap;
98 lines = fileCoverage.l;
99 Object.keys(lines).forEach(function (k) {
100 var str = '\t\t\t\t<line' +
102 attr('count', lines[k]),
103 branchDetail = branchByLine[k];
106 str += ' type="stmt" ';
108 str += ' type="cond" ' +
109 attr('truecount', branchDetail.covered) +
110 attr('falsecount', (branchDetail.total - branchDetail.covered));
112 writer.println(str + '/>');
115 writer.println('\t\t\t</file>');
118 function walk(node, collector, writer, level, projectRoot) {
125 metrics = node.metrics;
126 writer.println('<?xml version="1.0" encoding="UTF-8"?>');
127 writer.println('<coverage' +
128 attr('generated', Date.now()) +
131 writer.println('\t<project' +
132 attr('timestamp', Date.now()) +
133 attr('name', 'All Files') +
136 node.children.filter(function (child) { return child.kind === 'dir'; }).
137 forEach(function (child) {
139 child.children.filter(function (child) { return child.kind !== 'dir'; }).
140 forEach(function (child) {
141 Object.keys(collector.fileCoverageFor(child.fullPath()).l).forEach(function (k){
144 totalLines += Number(tempLines);
149 writer.println('\t\t<metrics' +
150 attr('statements', metrics.lines.total) +
151 attr('coveredstatements', metrics.lines.covered) +
152 attr('conditionals', metrics.branches.total) +
153 attr('coveredconditionals', metrics.branches.covered) +
154 attr('methods', metrics.functions.total) +
155 attr('coveredmethods', metrics.functions.covered) +
156 attr('elements', metrics.lines.total + metrics.branches.total + metrics.functions.total) +
157 attr('coveredelements', metrics.lines.covered + metrics.branches.covered + metrics.functions.covered) +
158 attr('complexity', 0) +
159 attr('packages', totalPackages) +
160 attr('files', totalFiles) +
161 attr('classes', totalFiles) +
162 attr('loc', totalLines) +
163 attr('ncloc', totalLines) +
166 if (node.packageMetrics) {
167 metrics = node.packageMetrics;
168 writer.println('\t\t<package' +
169 attr('name', asJavaPackage(node)) +
172 writer.println('\t\t\t<metrics' +
173 attr('statements', metrics.lines.total) +
174 attr('coveredstatements', metrics.lines.covered) +
175 attr('conditionals', metrics.branches.total) +
176 attr('coveredconditionals', metrics.branches.covered) +
177 attr('methods', metrics.functions.total) +
178 attr('coveredmethods', metrics.functions.covered) +
181 node.children.filter(function (child) { return child.kind !== 'dir'; }).
182 forEach(function (child) {
183 addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer);
185 writer.println('\t\t</package>');
187 node.children.filter(function (child) { return child.kind === 'dir'; }).
188 forEach(function (child) {
189 walk(child, collector, writer, level + 1, projectRoot);
193 writer.println('\t</project>');
194 writer.println('</coverage>');
198 Report.mix(CloverReport, {
199 synopsis: function () {
200 return 'XML coverage report that can be consumed by the clover tool';
202 getDefaultConfig: function () {
203 return { file: 'clover.xml' };
205 writeReport: function (collector, sync) {
206 var summarizer = new TreeSummarizer(),
207 outputFile = path.join(this.dir, this.file),
208 writer = this.opts.writer || new FileWriter(sync),
209 projectRoot = this.projectRoot,
214 collector.files().forEach(function (key) {
215 summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
217 tree = summarizer.getTreeSummary();
219 writer.on('done', function () { that.emit('done'); });
220 writer.writeFile(outputFile, function (contentWriter) {
221 walk(root, collector, contentWriter, 0, projectRoot);
227 module.exports = CloverReport;