blanket
Version:
seamless js code coverage
237 lines (214 loc) • 9.45 kB
JavaScript
var extend = require("xtend"),
path = require('path'),
join = path.join;
var blanketNode = function (userOptions,cli){
var fs = require("fs"),
path = require("path"),
configPath = process.cwd() + '/package.json',
existsSync = fs.existsSync || path.existsSync,
file = existsSync(configPath) ? JSON.parse((fs.readFileSync(configPath, 'utf8')||{})) : null,
packageConfigs;
if (file){
var scripts = file.scripts,
config = file.config;
if (scripts && scripts.blanket){
console.warn("BLANKET-" + path + ": `scripts[\"blanket\"]` is deprecated. Please migrate to `config[\"blanket\"]`.\n");
packageConfigs = scripts.blanket;
} else if (config && config.blanket){
packageConfigs = config.blanket;
}
}
var blanketConfigs = packageConfigs ? extend(packageConfigs,userOptions) : userOptions,
blanket = require("./blanket").blanket,
oldLoader = require.extensions['.js'],
newLoader;
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
if (blanketConfigs){
var newOptions={
'filter': 'src' // Default filter to src
};
Object.keys(blanketConfigs).forEach(function (option) {
var optionValue = blanketConfigs[option];
if(option === "data-cover-only" || option === "pattern"){
newOptions.filter = optionValue;
}
if(option === "data-cover-never"){
newOptions.antifilter = optionValue;
}
if (option === "data-cover-loader" || option === "loader"){
newOptions.loader = optionValue;
}
if (option === "data-cover-timeout"){
newOptions.timeout = optionValue;
}
if (option === "onlyCwd" && !!optionValue){
newOptions.cwdRegex = new RegExp("^" + escapeRegExp(process.cwd()), "i");
}
if (option === "data-cover-customVariable"){
newOptions.customVariable = optionValue;
}
if (option === "data-cover-ecmaVersion"){
newOptions.ecmaVersion = parseInt(optionValue,10);
}
if (option === "data-cover-flags"){
newOptions.order = !optionValue.unordered;
newOptions.ignoreScriptError = !!optionValue.ignoreError;
newOptions.autoStart = !!optionValue.autoStart;
newOptions.branchTracking = !!optionValue.branchTracking;
newOptions.debug = !!optionValue.debug;
newOptions.engineOnly = !!optionValue.engineOnly;
}
if (option === "data-cover-reporter-options"){
newOptions.reporter_options = optionValue;
}
});
blanket.options(newOptions);
} else {
// If no config is specified, default filter to src.
blanket.options('filter', 'src');
}
//helper functions
blanket.normalizeBackslashes = function (str) {
return str.replace(/\\/g, '/');
};
blanket.restoreNormalLoader = function () {
if (!blanket.options("engineOnly")){
newLoader = require.extensions['.js'];
require.extensions['.js'] = oldLoader;
}
};
blanket.restoreBlanketLoader = function () {
if (!blanket.options("engineOnly")){
require.extensions['.js'] = newLoader;
}
};
//you can pass in a string, a regex, or an array of files
blanket.matchPattern = function (filename,pattern){
var cwdRegex = blanket.options("cwdRegex");
if (cwdRegex && !cwdRegex.test(filename)){
return false;
}
if (typeof pattern === 'string'){
if (pattern.indexOf("[") === 0){
//treat as array
var pattenArr = pattern.slice(1,pattern.length-1).split(",");
return pattenArr.some(function(elem){
return blanket.matchPattern(filename,blanket.normalizeBackslashes(elem.slice(1,-1)));
});
}else if ( pattern.indexOf("//") === 0){
var ex = pattern.slice(2,pattern.lastIndexOf('/'));
var mods = pattern.slice(pattern.lastIndexOf('/')+1);
var regex = new RegExp(ex,mods);
return regex.test(filename);
}else{
return filename.indexOf(blanket.normalizeBackslashes(pattern)) > -1;
}
}else if ( pattern instanceof Array ){
return pattern.some(function(elem){
return filename.indexOf(blanket.normalizeBackslashes(elem)) > -1;
});
}else if (pattern instanceof RegExp){
return pattern.test(filename);
}else if (typeof pattern === 'function'){
return pattern(filename);
}else{
throw new Error("Bad file instrument indicator. Must be a string, regex, function, or array.");
}
};
if (!blanket.options("engineOnly")){
//instrument js files
require.extensions['.js'] = function(localModule, filename) {
var pattern = blanket.options("filter"),
reporter_options = blanket.options("reporter_options"),
originalFilename = filename,
inputFilename = filename;
filename = blanket.normalizeBackslashes(filename);
//we check the never matches first
var antipattern = _blanket.options("antifilter");
if (typeof antipattern !== "undefined" &&
blanket.matchPattern(filename.replace(/\.js$/,""),antipattern)
){
oldLoader(localModule,filename);
if (_blanket.options("debug")) {console.log("BLANKET-File will never be instrumented:"+filename);}
}else if (blanket.matchPattern(filename,pattern)){
if (_blanket.options("debug")) {console.log("BLANKET-Attempting instrument of:"+filename);}
var content = fs.readFileSync(filename, 'utf8');
if (reporter_options && reporter_options.shortnames){
inputFilename = filename.replace(path.dirname(filename),"");
} else if (reporter_options && reporter_options.relativepath) {
inputFilename = filename.replace(process.cwd(),"");
}
if (reporter_options && reporter_options.basepath){
inputFilename = filename.replace(reporter_options.basepath + '/',"");
}
blanket.instrument({
inputFile: content,
inputFileName: blanket.normalizeBackslashes(inputFilename)
},function(instrumented){
var baseDirPath = blanket.normalizeBackslashes(path.dirname(filename))+'/.';
try{
instrumented = instrumented.replace(/require\s*\(\s*("|')\./g,'require($1'+baseDirPath);
localModule._compile(instrumented, originalFilename);
}
catch(err){
if (_blanket.options("ignoreScriptError")){
//we can continue like normal if
//we're ignoring script errors,
//but otherwise we don't want
//to completeLoad or the error might be
//missed.
if (_blanket.options("debug")) {console.log("BLANKET-There was an error loading the file:"+filename);}
oldLoader(localModule,filename);
}else{
var e = new Error("BLANKET-Error parsing instrumented code: "+err);
e.error = err;
throw e;
}
}
});
}else{
oldLoader(localModule, originalFilename);
}
};
}
//if a loader is specified, use it
var loaderOption = blanket.options("loader");
if (loaderOption){
if (Array.isArray(loaderOption)) {
loaderOption.forEach(function(loader) {
require(loader)(blanket);
});
} else {
require(loaderOption)(blanket);
}
}
newLoader = require.extensions['.js'];
return blanket;
};
if ((process.env && process.env.BLANKET_COV===1) ||
(process.ENV && process.ENV.BLANKET_COV)){
module.exports = blanketNode({engineOnly:true},false);
}else{
var args = process.argv;
var blanketRequired = false;
for (var i = 0; i < args.length; i++) {
if (['-r', '--require'].indexOf(args[i]) >= 0 &&
args[i + 1] === 'blanket') {
blanketRequired = true;
}
}
if (['node', 'iojs', 'nodejs', 'jx'].indexOf(path.basename(args[0])) > -1 &&
args[1].indexOf(join('node_modules','mocha','bin')) > -1 &&
blanketRequired){
//using mocha cli
module.exports = blanketNode(null,true);
}else{
//not mocha cli
module.exports = function(options){
//we don't want to expose the cli option.
return blanketNode(options,false);
};
}
}