/**
* @module lib/lcov
*/
const fs = require('fs');
function walkFile(str, cb) {
let data = [];
let item;
['end_of_record'].concat(str.split('\n')).forEach((line) => {
const allparts = line.trim().split(':');
const parts = [allparts.shift(), allparts.join(':')];
let lines, fn;
switch (parts[0].toUpperCase()) {
case 'TN':
item.title = parts[1].trim();
break;
case 'SF':
item.file = parts.slice(1).join(':').trim();
break;
case 'FNF':
item.functions.found = Number(parts[1].trim());
break;
case 'FNH':
item.functions.hit = Number(parts[1].trim());
break;
case 'LF':
item.lines.found = Number(parts[1].trim());
break;
case 'LH':
item.lines.hit = Number(parts[1].trim());
break;
case 'DA':
lines = parts[1].split(',');
item.lines.details.push({
line: Number(lines[0]),
hit: Number(lines[1])
});
break;
case 'FN':
fn = parts[1].split(',');
item.functions.details.push({
name: fn[1],
line: Number(fn[0])
});
break;
case 'FNDA':
fn = parts[1].split(',');
item.functions.details.some((i, k) => {
if (i.name === fn[1] && i.hit === undefined) {
item.functions.details[k].hit = Number(fn[0]);
return true;
}
});
break;
case 'BRDA':
fn = parts[1].split(',');
item.branches.details.push({
line: Number(fn[0]),
block: Number(fn[1]),
branch: Number(fn[2]),
taken: ((fn[3] === '-') ? 0 : Number(fn[3]))
});
break;
case 'BRF':
item.branches.found = Number(parts[1]);
break;
case 'BRH':
item.branches.hit = Number(parts[1]);
break;
}
if (line.indexOf('end_of_record') > -1) {
data.push(item);
item = {
lines: {
found: 0,
hit: 0,
details: []
},
functions: {
hit: 0,
found: 0,
details: []
},
branches: {
hit: 0,
found: 0,
details: []
}
};
}
});
data.shift();
if (data.length) {
cb(undefined, data);
} else {
cb('Failed to parse lcov');
}
}
/**
* will clean a lcov string to strip out any unnecessary content
* @method clean
* @param {String} coverage - the coverage string to clean
* @return {String} - the cleaned version of the coverage string
*/
function clean(coverage) {
return coverage.substr(coverage.indexOf('TN:'), coverage.lastIndexOf('end_of_record') + 'end_of_record'.length);
}
/**
* returns a javascript object that represents the coverage data
* @method parse
* @param {String|Path} file - this can either be a string or a path to a file
* @return {Coverage} - The coverage data structure
*
*/
function parse(file) {
return new Promise(function(resolve, reject) {
if (fs.existsSync(file)) {
fs.readFile(file, 'utf8', (err, str) => {
if (err) {
reject(err);
}
return walkFile(clean(str), function(err, result) {
if (err) return reject(err);
resolve(result);
});
});
} else {
return walkFile(clean(file), function(err, result) {
if (err) return reject(err);
resolve(result);
});
}
});
}
module.exports = {
walkFile,
parse,
clean
};