compound-ex4
Version:
Compound-ex4 - MVC framework for NodeJS (ExpressJs 4 version), fork compoundjs(https://github.com/1602/compound)
247 lines (219 loc) • 8.91 kB
JavaScript
var Module = require('module');
var path = require('path');
var fs = require('fs');
exports.addCoverage = function addCoverage(code, filename) {
filename = filename.replace(/\\/g, '\\\\');
if (!eligibleForCoverage(filename)) {
return code;
}
if (process.env.COVERAGE && !~filename.indexOf(process.env.COVERAGE)) {
return code;
}
var lines = code.split('\n');
if (lines.length > 0) {
lines[0] = 'if (!__cov["' + filename + '"]) {__cov["' + filename + '"] = { 0: 1}; }' + lines[0];
}
for (var i = 0; i < lines.length; i++) {
if (lines[i].match(/^\s\*\s/)) continue;
var name = '__cov["' + filename + '"][' + i + ']';
var covLine = ' ' + name + ' = (' + name + ' || 0) + 1;';
lines[i] = lines[i]
.replace(/;$/, ';' + covLine)
.replace(/^\s*(return|throw|break|continue)/, covLine + ' $1');
}
return lines.join('\n');
};
var eligibleForCoverage = exports.eligibleForCoverage = function (filename) {
var p = exports.addCoverage.paths;
if (typeof p === 'string') {
return ~filename.indexOf(p);
} else if (p instanceof RegExp) {
return filename.match(p);
} else if (p instanceof Array) {
return p.some(function(x) {
return !!~filename.indexOf(x);
});
}
};
var projectName;
exports.init = function init(subdir, name) {
projectName = name;
if (process.env.NOCOV || global.__cov) return;
if (!subdir) {
subdir = process.cwd();
} else if (typeof subdir === 'string') {
if (!subdir.match(/^\//)) {
subdir = path.join(process.cwd(), subdir);
}
} else if (subdir instanceof Array) {
subdir = subdir.map(function(x) {
return x.match(/^\//) ? x : path.join(process.cwd(), x);
});
}
global.__cov = {};
exports.addCoverage.paths = subdir;
var compile = Module.prototype._compile;
Object.keys(Module._cache).forEach(function (path) {
if (path.match(/node_modules/)) return;
delete Module._cache[path];
});
Module.prototype._compile = function (code, filename) {
if (eligibleForCoverage(filename)) {
code = exports.addCoverage(code, filename);
}
return compile.call(this, code, filename);
};
};
exports.report = function () {
if (process.env.NOCOV) return;
coverageReport();
};
function coverageReport() {
var cwd = process.cwd(),
total_lines = 0,
total_covered = 0,
files = [];
for (file in __cov) {
if (file.search(cwd) === -1 || file.search(cwd + '/node_modules') !== -1) continue;
var shortFileName = file.replace(cwd + '/', '');
var id = shortFileName.replace(/[^a-z]+/gi, '-').replace(/^-|-$/, '');
var code = syntax(fs.readFileSync(file).toString()).split('\n');
var cnt = code.filter(function (line) {
return line.match(/;$/) && !line.match(/^\s\*\s/);
}).length;
var covered = Object.keys(__cov[file]).length;
if (covered > cnt) covered = cnt;
var coveredPercentage = cnt === 0 ? 100 : Math.round((covered / cnt) * 100);
total_covered += covered;
total_lines += cnt;
var html = '<div id="' + id + '" class="file"><div class="row" style="margin-left: 30px;">';
html += '<div class="span5">';
html += '<a href="#' + id +
'" class="filename trigger" name="' + id + '"' +
'>' + shortFileName +
'</a>';
html += '</div><div class="span6">';
var progressClass = 'progress-danger';
if (coveredPercentage > 30) {
progressClass = 'progress-warning';
}
if (coveredPercentage >= 80) {
progressClass = 'progress-success';
}
html += '<div class="progress ' + progressClass + '"> <div class="bar" style="width: ' + coveredPercentage +
'%"><strong>' + coveredPercentage +
'%</strong> [' + covered + '/' + cnt + ']</div></div></div>';
html += '</div>';
html += '<div class="file-contents"><pre><ol>';
code.forEach(function (line, i) {
html += '<li class="' + (__cov[file][i] ? 'covered' : (line.match(/;$/) && !line.match(/ \* /) ? 'uncovered' : '')) + '"><code>' + line + '</code>';
if (__cov[file][i] && i) {
html += '<span class="hits">' + __cov[file][i] + '</span>';
}
html += '</li>';
});
html += '</ol></pre></div></div>';
if (cnt > 1) {
files.push({
lines: cnt,
covered: covered,
id: id,
name: shortFileName,
html: html
});
}
}
var groups = {};
files.sort(byName).forEach(function (f) {
var group = f.name.replace(/\/[^\/]+$/, '');
if (!groups[group]) groups[group] = [];
groups[group].push(f);
});
var groupsListHTML = [];
var html = Object.keys(groups).map(function (g) {
var tltc = 0; // total lines to cover
var tlcd = 0; // total lines covered
var files = groups[g].sort(bySize).map(function (f) {
tltc += f.lines;
tlcd += f.covered;
return f.html;
}).join('\n');
var gid = g.toLowerCase().replace(/[^a-z]/i, '-');
var percentage = Math.round(100 * tlcd / tltc);
groupsListHTML.push('<li><a href="#' + gid + '">./' + g + ' (' + percentage + '%)</a></li>');
return '<div class="group-header row" id="' + gid + '"><div class="group-name span5">./' + g + '</div><div class="span6">' + progressBar(tlcd, tltc) + '</div></div>' + files;
}).join('\n');
function byName(x, y) {
return x.name > y.name ? -1 : 1;
}
function bySize(x, y) {
return y.lines - x.lines;
}
if (!fs.existsSync(cwd + '/coverage')) fs.mkdirSync(cwd + '/coverage');
fs.writeFileSync(cwd + '/coverage/index.html',
fs.readFileSync(path.join(__dirname, '/assets/coverage.html'))
.toString()
.replace('PROJECT', projectName || '')
.replace('PROGRESS', progressBar(total_covered, total_lines))
.replace('CODE', html.replace(/\$'/g, '$\''))
.replace('GROUPS', groupsListHTML.join(''))
);
copy('bootstrap.css');
copy('style.css');
copy('coverage.js');
copy('bootstrap.js');
copy('jquery.min.js');
console.log('====================');
console.log('TOTAL COVERAGE:', Math.round((total_covered / (total_lines)) * 100) + '%');
}
function progressBar(x, total) {
var percentage = total === 0 ? 100 : Math.round((x / total) * 100);
var progressClass = 'progress-danger';
if (percentage > 30) {
progressClass = 'progress-warning';
}
if (percentage >= 80) {
progressClass = 'progress-success';
}
return '<div class="progress ' + progressClass + '"> <div class="bar" style="width: ' + percentage +
'%"><strong>' + percentage +
'%</strong> [' + x + '/' + total + ']</div></div>';
}
function copy(file) {
var source = path.join(__dirname, 'assets', file);
var destination = path.join(process.cwd(), 'coverage', file);
var contents = fs.readFileSync(source).toString();
fs.writeFileSync(destination, contents);
}
function syntax(code) {
var comments = [];
var strings = [];
var res = [];
var all = { 'C': comments, 'S': strings, 'R': res };
var safe = { '<': '<', '>': '>', '&': '&' };
return code
.replace(/[<>&]/g, function (m)
{ return safe[m]; })
.replace(/\/\/.*$/gm, function (m)
{ var l=comments.length; comments.push(m); return '~~~C'+l+'~~~'; })
.replace(/\/\*[\s\S]*?\*\//g, function (m)
{ var l=comments.length; comments.push(m); return '~~~C'+l+'~~~'; })
.replace(/([^\\])((?:'(?:\\'|[^'\n])*')|(?:"(?:\\"|[^"\n])*"))/g, function (m, f, s)
{ var l=strings.length; strings.push(s); return f+'~~~S'+l+'~~~'; })
// .replace(/\/(\\\/|[^\/\n])*\/[gim]{0,3}/g, function(m)
// { var l=res.length; res.push(m); return '~~~R'+l+'~~~'; })
.replace(/(var|function|typeof|new|return|prototype|if|for|in|while|break|do|continue|switch|case)([^a-z0-9\$_])/gi,
'<span class="kwrd">$1</span>$2')
.replace(/(\{|\}|\]|\[|\|)/gi,
'<span class="gly">$1</span>')
.replace(/([a-z\_\$][a-z0-9_]*)[\s]*\(/gi,
'<span class="func">$1</span>(')
.replace(/~~~([CSR])(\d+)~~~/g, replaceCSR)
function replaceCSR(m, t, i) {
var openTag = '<span class="' + t + '">';
var closeTag = '</span>';
return openTag +
all[t][i].replace(/\n/g, closeTag + '\n' + openTag) +
closeTag;
}
}