Socket
Socket
Sign inDemoInstall

coffee-coverage

Package Overview
Dependencies
9
Maintainers
12
Versions
36
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.0.1 to 3.0.0

5

CHANGELOG.md
# coffee-coverage Changelog
### v3.0.0
- This version works with CoffeeScript v2
### v2.0.0

@@ -75,3 +78,3 @@ - Add support for [nyc](https://github.com/istanbuljs/nyc).

- Force coffee-script to disable chaining of if/else if statements during compile. This
- Force coffeescript to disable chaining of if/else if statements during compile. This
fix is required for coffee-script 1.6.3 and higher.

@@ -78,0 +81,0 @@

8

docs/development.md
Overview
--------
The 5 mile high view of coffee-coverage is; first we use
[coffee-script to parse the input and generate an abstract syntax tree (AST)](https://github.com/benbria/coffee-coverage/blob/c7566d50493ad98953640ccc5e7dc0080576d08a/src/coffeeCoverage.coffee#L319).
[coffeescript to parse the input and generate an abstract syntax tree (AST)](https://github.com/benbria/coffee-coverage/blob/c7566d50493ad98953640ccc5e7dc0080576d08a/src/coffeeCoverage.coffee#L319).
Then we visit every node in the AST with an
[instrumentor](https://github.com/benbria/coffee-coverage/tree/master/src/instrumentors) which, generally, adds some
extra nodes into the resulting coffee-script. Finally we
extra nodes into the resulting coffeescript. Finally we
[compile the resulting AST](https://github.com/benbria/coffee-coverage/blob/c7566d50493ad98953640ccc5e7dc0080576d08a/src/coffeeCoverage.coffee#L350)

@@ -12,3 +12,3 @@ out into JavaScript.

All the information about where a particular fragment of source came from is based on the [`locationData`](https://github.com/jashkenas/coffeescript/blob/98dd1bf8e80aa7974422a5fdef3075a9e7329d00/src/helpers.coffee#L98)
found in the coffee-script node.
found in the coffeescript node.

@@ -31,3 +31,3 @@ You'll notice that instrumentors have a

If we're running `istanbul cover` to generate coverage for a project with mixed JS and coffee-script content, then
If we're running `istanbul cover` to generate coverage for a project with mixed JS and coffeescript content, then
things get a little more exciting. The problem is that

@@ -34,0 +34,0 @@ [Istanbul generates a unique variable name](https://github.com/gotwarlost/istanbul/blob/c87ada03cb485e4f9110224899b68d8dc27e4bf3/lib/command/common/run-with-cover.js#L158)

@@ -12,3 +12,3 @@ Codeship and Coveralls

Assuming you have a coffee-script project with tests cases stored in /test, and you are using
Assuming you have a coffeescript project with tests cases stored in /test, and you are using
mocha to run your unit tests, `cd` to your project and run:

@@ -20,3 +20,3 @@

--compilers coffee:coffee-script/register
--compilers coffee:coffeescript/register
--require coffee-coverage/register-istanbul

@@ -23,0 +23,0 @@ --recursive

@@ -15,3 +15,3 @@ Running with [Istanbul](https://github.com/gotwarlost/istanbul)

Assuming you have a coffee-script project with tests cases stored in /test, and you are using
Assuming you have a coffeescript project with tests cases stored in /test, and you are using
mocha to run your unit tests, `cd` to your project and run:

@@ -21,3 +21,3 @@

mocha --recursive \
--compilers coffee:coffee-script/register \
--compilers coffee:coffeescript/register \
--require coffee-coverage/register-istanbul \

@@ -54,3 +54,3 @@ test

./node_modules/.bin/istanbul cover -x 'lib/**' ./node_modules/.bin/_mocha -- \
--compilers coffee:coffee-script/register \
--compilers coffee:coffeescript/register \
--require coffee-coverage/register-istanbul \

@@ -68,3 +68,3 @@ --recursive \

--compilers coffee:coffee-script/register
--compilers coffee:coffeescript/register
--require coffee-coverage/register-istanbul

@@ -71,0 +71,0 @@ --recursive

@@ -16,3 +16,3 @@ Running with [JSCoverage](http://siliconforks.com/jscoverage/)

Assuming you have a coffee-script project with tests cases stored in /test, and you are using
Assuming you have a coffeescript project with tests cases stored in /test, and you are using
mocha to run your unit tests, `cd` to your project and run:

@@ -45,3 +45,3 @@

--compilers coffee:coffee-script/register
--compilers coffee:coffeescript/register
--recursive

@@ -81,3 +81,3 @@

coffeeCoverage --initfile ./lib/init.js --exclude test --path abbr ./src ./lib
# Compile the test directory with regular coffee-script
# Compile the test directory with regular coffeescript
coffee -o ./lib/test ./src/test

@@ -84,0 +84,0 @@

@@ -15,3 +15,3 @@ Running with [nyc](https://github.com/istanbuljs/nyc)

Assuming you have a coffee-script project with tests cases stored in /test, and you are using
Assuming you have a coffeescript project with tests cases stored in /test, and you are using
mocha to run your unit tests, `cd` to your project and run:

@@ -21,3 +21,3 @@

./node_modules/.bin/nyc --reporter lcov ./node_modules/.bin/mocha --recursive \
--compilers coffee:coffee-script/register \
--compilers coffee:coffeescript/register \
--require coffee-coverage/register-istanbul \

@@ -49,3 +49,3 @@ test

--compilers coffee:coffee-script/register
--compilers coffee:coffeescript/register
--require coffee-coverage/register-istanbul

@@ -52,0 +52,0 @@ --recursive

@@ -12,3 +12,3 @@ Travis-CI and Coveralls

Assuming you have a coffee-script project with tests cases stored in /test, and you are using
Assuming you have a coffeescript project with tests cases stored in /test, and you are using
mocha to run your unit tests, `cd` to your project and run:

@@ -20,3 +20,3 @@

--compilers coffee:coffee-script/register
--compilers coffee:coffeescript/register
--require coffee-coverage/register-istanbul

@@ -23,0 +23,0 @@ --recursive

@@ -1,7 +0,11 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
var CoverageError, EXTENSIONS, INSTRUMENTORS, NodeWrapper, SkipVisitor, _, assert, coffeeScript, events, excludeFile, factoryDefaults, fs, getInstrumentorClass, mkdirs, path, ref, statFile, util,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
//### CoffeeCoverage
// JSCoverage-style instrumentation for CoffeeScript files.
// By Jason Walton, Benbria
var CoverageError, EXTENSIONS, INSTRUMENTORS, NodeWrapper, SkipVisitor, _, assert, coffeeScript, events, excludeFile, factoryDefaults, fs, getInstrumentorClass, mkdirs, path, statFile, util;
assert = require('assert');

@@ -17,3 +21,3 @@

coffeeScript = require('coffee-script');
coffeeScript = require('coffeescript');

@@ -24,5 +28,5 @@ _ = require('lodash');

ref = require('./utils/helpers'), mkdirs = ref.mkdirs, statFile = ref.statFile, excludeFile = ref.excludeFile;
({mkdirs, statFile, excludeFile} = require('./utils/helpers'));
EXTENSIONS = require('./constants').EXTENSIONS;
({EXTENSIONS} = require('./constants'));

@@ -36,16 +40,13 @@ SkipVisitor = require('./SkipVisitor');

CoverageError = (function(superClass) {
extend(CoverageError, superClass);
function CoverageError(message) {
CoverageError = class CoverageError extends Error {
constructor(message) {
super();
this.message = message;
this.name = "CoverageError";
Error.call(this);
Error.captureStackTrace(this, arguments.callee);
Error.captureStackTrace(this, CoverageError);
}
return CoverageError;
};
})(Error);
// Default options.
factoryDefaults = {

@@ -62,3 +63,3 @@ exclude: [],

if (!instrumentor) {
throw new Error("Invalid instrumentor " + instrumentorName + ". Valid options are: " + (Object.keys(INSTRUMENTORS).join(', ')));
throw new Error(`Invalid instrumentor ${instrumentorName}. Valid options are: ${Object.keys(INSTRUMENTORS).join(', ')}`);
}

@@ -68,203 +69,294 @@ return instrumentor;

exports.CoverageInstrumentor = (function(superClass) {
exports.CoverageInstrumentor = (function() {
var getEffectiveOptions, validateSrcDest, writeToFile;
extend(CoverageInstrumentor, superClass);
//### CoverageInstrumentor
function CoverageInstrumentor(options) {
if (options == null) {
options = {};
// Instruments .coffee files to provide code-coverage data.
class CoverageInstrumentor extends events.EventEmitter {
//### Create a new CoverageInstrumentor
// For a list of available options see `@instrument`.
constructor(options = {}) {
super();
this.defaultOptions = _.defaults({}, options, factoryDefaults);
_.defaults(this.defaultOptions, getInstrumentorClass(this.defaultOptions.instrumentor).getDefaultOptions());
}
this.defaultOptions = _.defaults({}, options, factoryDefaults);
_.defaults(this.defaultOptions, getInstrumentorClass(this.defaultOptions.instrumentor).getDefaultOptions());
}
writeToFile = function(outFile, content) {
return fs.writeFileSync(outFile, content);
};
//### Instrument a file or directory.
validateSrcDest = function(source, out) {
var outStat, sourceStat;
sourceStat = statFile(source);
outStat = out ? statFile(out) : null;
if (!sourceStat) {
throw new CoverageError("Source file " + source + " does not exist.");
}
if (outStat) {
if (sourceStat.isFile() && outStat.isDirectory()) {
throw new CoverageError("Refusing to overwrite directory " + out + " with file.");
// This calls @instrumentFile or @instrumentDirectory, depending on whether "source" is
// a file or directory respectively.
// * `options.coverageVar` gives the name of the global variable to use to store
// coverage data in. This defaults to '_$jscoverage' to be compatible with
// JSCoverage.
// * `options.recursive` controls whether or not this will descend recursively into
// subdirectories. This defaults to true.
// * `options.exclude` is an array of files to ignore. instrumentDirectory will
// not instrument a file if it is in this list, nor will it recursively traverse
// into a directory if it is in this list. This defaults to [].
// Note that this field is case sensitive!
// * `options.basePath` if provided, then all excludes will be evaluated relative
// to this base path. For example, if `options.exclude` is `['a/b']`, and
// `options.basePath` is "/Users/jwalton/myproject", then this will prevent
// coffeeCoverage from traversing "/Users/jwalton/myproject/a/b". `basePath`
// will also be stripped from the front of any files when generating names.
// * `options.initFileStream` is a stream to which all global initialization will be
// written to via `initFileStream.write(data)`.
// * `options.log` should be a `{debug(), info(), warn(), error()}` object, where each is a function
// that takes multiple parameters and logs them (similar to `console.log()`.)
// * `options.instrumentor` is the name of the instrumentor to use (see `INSTURMENTORS`.)
// All options passed in will be passed along to the instrumentor implementation, so
// instrumentor-specific options may be added to `options` as well.
// Throws CoverageError if there is a problem with the `source` or `out` parameters.
instrument(source, out, options = {}) {
var sourceStat;
({sourceStat} = validateSrcDest(source, out));
if (sourceStat.isFile()) {
return this.instrumentFile(source, out, options);
} else if (sourceStat.isDirectory()) {
return this.instrumentDirectory(source, out, options);
} else {
throw new CoverageError(`Can't instrument ${source}.`);
}
if (sourceStat.isDirectory() && outStat.isFile()) {
throw new CoverageError("Refusing to overwrite file " + out + " with directory.");
}
}
return {
sourceStat: sourceStat,
outStat: outStat
};
};
CoverageInstrumentor.prototype.instrument = function(source, out, options) {
var sourceStat;
if (options == null) {
options = {};
}
sourceStat = validateSrcDest(source, out).sourceStat;
if (sourceStat.isFile()) {
return this.instrumentFile(source, out, options);
} else if (sourceStat.isDirectory()) {
return this.instrumentDirectory(source, out, options);
} else {
throw new CoverageError("Can't instrument " + source + ".");
}
};
// Return the output file name for a given input file name.
CoverageInstrumentor.prototype.getOutputFileName = function(fileName) {
var coffee_extension, ext, outFile;
if (fileName == null) {
return null;
}
outFile = fileName;
for (coffee_extension in EXTENSIONS) {
ext = EXTENSIONS[coffee_extension];
if (_.endsWith(fileName.toLowerCase(), coffee_extension)) {
outFile = fileName.slice(0, +(-(coffee_extension.length + 1)) + 1 || 9e9) + ext.js_extension;
break;
// e.g. `getOutputFileName('foo.coffee') # => 'foo.js'`
getOutputFileName(fileName) {
var coffee_extension, ext, outFile;
if (fileName == null) {
return null;
}
outFile = fileName;
for (coffee_extension in EXTENSIONS) {
ext = EXTENSIONS[coffee_extension];
if (_.endsWith(fileName.toLowerCase(), coffee_extension)) {
outFile = fileName.slice(0, +(-(coffee_extension.length + 1)) + 1 || 9e9) + ext.js_extension;
break;
}
}
return outFile;
}
return outFile;
};
getEffectiveOptions = function(options, defaultOptions) {
if (options == null) {
options = {};
}
return _.defaults({}, options, defaultOptions);
};
//### Instrument a directory.
CoverageInstrumentor.prototype.instrumentDirectory = function(sourceDirectory, outDirectory, options) {
var answer, coffee_extension, effectiveOptions, file, inst, j, len, outDirectoryStat, outFile, outputDirectoryExists, processed, ref1, ref2, ref3, sourceDirectoryMode, sourceDirectoryStat, sourceFile, sourceStat;
if (options == null) {
options = {};
}
sourceDirectory = path.resolve(sourceDirectory);
sourceDirectoryStat = statFile(sourceDirectory);
if (!sourceDirectoryStat) {
return null;
}
this.emit("instrumentingDirectory", sourceDirectory, outDirectory);
effectiveOptions = getEffectiveOptions(options, this.defaultOptions);
effectiveOptions.basePath = effectiveOptions.basePath ? path.resolve(effectiveOptions.basePath) : sourceDirectory;
answer = {
lines: 0
};
validateSrcDest(sourceDirectory, outDirectory);
if (!_.endsWith(sourceDirectory, path.sep)) {
sourceDirectory += path.sep;
}
sourceDirectoryMode = sourceDirectoryStat.mode;
if (outDirectory) {
if (!_.endsWith(outDirectory, path.sep)) {
outDirectory += path.sep;
// This finds all .coffee files in the specified `sourceDirectory`, and writes instrumented
// files into `outDirectory`. `outDirectory` will be created if it does not already exist.
// For a list of available options see `@instrument`.
// Emits an "instrumentingDirectory" event before doing any work, with the names of the source
// and out directories. The directory names are guaranteed to end in path.sep. Emits a
// "skip" event for any files which are skipped because they are in the `options.exclude` list.
// Throws CoverageError if there is a problem with the `sourceDirectory` or `outDirectory`
// parameters.
// Returns an object consisting of:
// - `lines` - the total number of instrumented lines.
// Returns null if `sourceDirectory` does not exist.
instrumentDirectory(sourceDirectory, outDirectory, options = {}) {
var answer, coffee_extension, effectiveOptions, file, inst, j, len, outDirectoryStat, outFile, outputDirectoryExists, processed, ref, ref1, ref2, sourceDirectoryMode, sourceDirectoryStat, sourceFile, sourceStat;
// Turn the source directory into an absolute path
sourceDirectory = path.resolve(sourceDirectory);
sourceDirectoryStat = statFile(sourceDirectory);
if (!sourceDirectoryStat) {
return null;
}
outDirectoryStat = statFile(outDirectory);
outputDirectoryExists = !!outDirectoryStat;
}
ref1 = fs.readdirSync(sourceDirectory);
for (j = 0, len = ref1.length; j < len; j++) {
file = ref1[j];
sourceFile = sourceDirectory + file;
if (excludeFile(sourceFile, effectiveOptions)) {
this.emit("skip", sourceDirectory + file);
continue;
this.emit("instrumentingDirectory", sourceDirectory, outDirectory);
effectiveOptions = getEffectiveOptions(options, this.defaultOptions);
effectiveOptions.basePath = effectiveOptions.basePath ? path.resolve(effectiveOptions.basePath) : sourceDirectory;
answer = {
lines: 0
};
validateSrcDest(sourceDirectory, outDirectory);
if (!_.endsWith(sourceDirectory, path.sep)) {
sourceDirectory += path.sep;
}
sourceStat = statFile(sourceFile);
if (!sourceStat) {
continue;
sourceDirectoryMode = sourceDirectoryStat.mode;
if (outDirectory) {
if (!_.endsWith(outDirectory, path.sep)) {
outDirectory += path.sep;
}
// Check to see if the output directory exists
outDirectoryStat = statFile(outDirectory);
outputDirectoryExists = !!outDirectoryStat;
}
outFile = outDirectory ? outDirectory + file : null;
if (effectiveOptions.recursive && sourceStat.isDirectory()) {
inst = this.instrumentDirectory(sourceFile, outFile, effectiveOptions);
answer.lines += (ref2 = inst != null ? inst.lines : void 0) != null ? ref2 : 0;
} else {
processed = false;
for (coffee_extension in EXTENSIONS) {
if (coffee_extension === '._coffee') {
continue;
}
if (_.endsWith(file.toLowerCase(), coffee_extension) && sourceStat.isFile()) {
if ((outDirectory != null) && !outputDirectoryExists) {
mkdirs(outDirectory, sourceDirectoryMode);
outputDirectoryExists = true;
ref = fs.readdirSync(sourceDirectory);
// Instrument every file in the directory
for (j = 0, len = ref.length; j < len; j++) {
file = ref[j];
sourceFile = sourceDirectory + file;
if (excludeFile(sourceFile, effectiveOptions)) {
this.emit("skip", sourceDirectory + file);
continue;
}
sourceStat = statFile(sourceFile);
if (!sourceStat) {
// Can happen if file or folder is deleted while we're instrumenting.
// Also, see https://github.com/benbria/coffee-coverage/issues/54.
continue;
}
outFile = outDirectory ? outDirectory + file : null;
if (effectiveOptions.recursive && sourceStat.isDirectory()) {
inst = this.instrumentDirectory(sourceFile, outFile, effectiveOptions);
answer.lines += (ref1 = inst != null ? inst.lines : void 0) != null ? ref1 : 0;
} else {
processed = false;
for (coffee_extension in EXTENSIONS) {
// TODO: Make this work for streamline files.
if (coffee_extension === '._coffee') {
continue;
}
outFile = this.getOutputFileName(outFile);
inst = this.instrumentFile(sourceFile, outFile, effectiveOptions);
answer.lines += (ref3 = inst != null ? inst.lines : void 0) != null ? ref3 : 0;
processed = true;
break;
if (_.endsWith(file.toLowerCase(), coffee_extension) && sourceStat.isFile()) {
// lazy-create the output directory.
if ((outDirectory != null) && !outputDirectoryExists) {
mkdirs(outDirectory, sourceDirectoryMode);
outputDirectoryExists = true;
}
// Replace the ".(lit)coffee(.md)" extension with a ".js" extension
outFile = this.getOutputFileName(outFile);
inst = this.instrumentFile(sourceFile, outFile, effectiveOptions);
answer.lines += (ref2 = inst != null ? inst.lines : void 0) != null ? ref2 : 0;
processed = true;
break;
}
}
}
}
return answer;
}
return answer;
};
CoverageInstrumentor.prototype.instrumentFile = function(sourceFile, outFile, options) {
var answer, data, effectiveOptions;
if (outFile == null) {
outFile = null;
//### Instrument a .coffee file.
// Same as `@instrumentCoffee` but takes a file name instead of file data.
// Emits an "instrumentingFile" event with the name of the input and output file.
// * `outFile` is optional; if present then the compiled JavaScript will be written out to this
// file.
// * `options.fileName` is the fileName to use in the generated instrumentation.
// For other options, see `@instrumentCoffee` and `@instrument`.
// Throws CoverageError if there is a problem with the `sourceFile` or `outFile` parameters.
instrumentFile(sourceFile, outFile = null, options = {}) {
var answer, data, effectiveOptions;
this.emit("instrumentingFile", sourceFile, outFile);
effectiveOptions = getEffectiveOptions(options, this.defaultOptions);
validateSrcDest(sourceFile, outFile);
data = fs.readFileSync(sourceFile, 'utf8');
answer = this.instrumentCoffee(path.resolve(sourceFile), data, effectiveOptions);
if (outFile) {
writeToFile(outFile, answer.init + answer.js);
}
return answer;
}
if (options == null) {
options = {};
//### Instrument a .coffee file.
// Parameters:
// * `fileName` is the name of the file. This should be an absolute path.
// * `source` is the contents of the coffee file.
// * `options.fileName` - if rpresent, this will be the filename passed to the instrumentor.
// Otherwise the absolute path will be passed.
// * If `options.usedFileNameMap` is present, it must be an object. This method will add a
// mapping from the absolute file path to the short filename in usedFileNameMap. If the name
// of the file is already in usedFileNameMap then this method will generate a unique name.
// If `options.usedFileNames` is present, it must be an array - this is the deprecated version
// of `usedFileNameMap`.
// * If `options.initFileStream` is present, then all global initialization will be written
// to `initFileStream.write()`, in addition to being returned.
// Returns an object consisting of:
// * `init` - the intialization JavaScript code.
// * `js` - the compiled JavaScript, instrumented to collect coverage data.
// * `lines` - the total number of instrumented lines.
instrumentCoffee(fileName, source, options = {}) {
var effectiveOptions, instrumentor, instrumentorConstructor, ref, ref1, result;
effectiveOptions = getEffectiveOptions(options, this.defaultOptions);
if ((ref = effectiveOptions.log) != null) {
ref.info(`Instrumenting ${fileName}`);
}
instrumentorConstructor = getInstrumentorClass(effectiveOptions.instrumentor);
instrumentor = new instrumentorConstructor(fileName, source, effectiveOptions);
result = exports._runInstrumentor(instrumentor, fileName, source, effectiveOptions);
if ((ref1 = effectiveOptions.initFileStream) != null) {
ref1.write(result.init);
}
return result;
}
this.emit("instrumentingFile", sourceFile, outFile);
effectiveOptions = getEffectiveOptions(options, this.defaultOptions);
validateSrcDest(sourceFile, outFile);
data = fs.readFileSync(sourceFile, 'utf8');
answer = this.instrumentCoffee(path.resolve(sourceFile), data, effectiveOptions);
if (outFile) {
writeToFile(outFile, answer.init + answer.js);
}
return answer;
};
CoverageInstrumentor.prototype.instrumentCoffee = function(fileName, source, options) {
var effectiveOptions, instrumentor, instrumentorConstructor, ref1, ref2, result;
if (options == null) {
options = {};
// Write a string to a file.
writeToFile = function(outFile, content) {
return fs.writeFileSync(outFile, content);
};
// Some basic valication of source and out files.
validateSrcDest = function(source, out) {
var outStat, sourceStat;
sourceStat = statFile(source);
outStat = out ? statFile(out) : null;
if (!sourceStat) {
throw new CoverageError(`Source file ${source} does not exist.`);
}
effectiveOptions = getEffectiveOptions(options, this.defaultOptions);
if ((ref1 = effectiveOptions.log) != null) {
ref1.info("Instrumenting " + fileName);
if (outStat) {
if (sourceStat.isFile() && outStat.isDirectory()) {
throw new CoverageError(`Refusing to overwrite directory ${out} with file.`);
}
if (sourceStat.isDirectory() && outStat.isFile()) {
throw new CoverageError(`Refusing to overwrite file ${out} with directory.`);
}
}
instrumentorConstructor = getInstrumentorClass(effectiveOptions.instrumentor);
instrumentor = new instrumentorConstructor(fileName, source, effectiveOptions);
result = exports._runInstrumentor(instrumentor, fileName, source, effectiveOptions);
if ((ref2 = effectiveOptions.initFileStream) != null) {
ref2.write(result.init);
}
return result;
return {sourceStat, outStat};
};
getEffectiveOptions = function(options = {}, defaultOptions) {
return _.defaults({}, options, defaultOptions);
};
return CoverageInstrumentor;
})(events.EventEmitter);
})();
exports._runInstrumentor = function(instrumentor, fileName, source, options) {
var answer, ast, coffeeOptions, err, init, js, ref1, ref2, runVisitor, token, tokens, wrappedAST;
if (options == null) {
options = {};
}
// Runs an instrumentor on some source code.
// * `instrumentor` an instance of an instrumentor class to run on.
// * `fileName` the absolute path of the source file.
// * `source` a string containing the sourcecode the instrument.
// * `options.bare` true if we should compile bare coffeescript (no enclosing function).
// * `options.log` log object.
exports._runInstrumentor = function(instrumentor, fileName, source, options = {}) {
var answer, ast, coffeeOptions, err, init, js, ref, ref1, runVisitor, token, tokens, wrappedAST;
assert(instrumentor, "instrumentor");
try {
if ((ref1 = options.log) != null) {
if (typeof ref1.debug === "function") {
ref1.debug("Instrumenting " + fileName);
// Compile coffee to nodes.
if ((ref = options.log) != null) {
if (typeof ref.debug === "function") {
ref.debug(`Instrumenting ${fileName}`);
}
}
coffeeOptions = {
bare: (ref2 = options.bare) != null ? ref2 : false,
bare: (ref1 = options.bare) != null ? ref1 : false,
literate: /\.(litcoffee|coffee\.md)$/.test(fileName)
};
tokens = coffeeScript.tokens(source, coffeeOptions);
// collect referenced variables
coffeeOptions.referencedVars = _.uniq((function() {

@@ -281,17 +373,19 @@ var j, len, results;

})());
// convert tokens to ast
ast = coffeeScript.nodes(tokens);
} catch (error) {
err = error;
throw new CoverageError("Could not parse " + fileName + ": " + err.stack);
throw new CoverageError(`Could not parse ${fileName}: ${err.stack}`);
}
runVisitor = function(visitor, nodeWrapper) {
var __, i, indent, j, len, name, ref3, ref4, ref5;
if ((ref3 = nodeWrapper.node.coffeeCoverage) != null ? ref3.generated : void 0) {
var __, i, indent, j, len, name, ref2, ref3, ref4;
// Ignore code that we generated.
if ((ref2 = nodeWrapper.node.coffeeCoverage) != null ? ref2.generated : void 0) {
return;
}
if (((ref4 = options.log) != null ? ref4.debug : void 0) != null) {
if (((ref3 = options.log) != null ? ref3.debug : void 0) != null) {
indent = ((function() {
var j, ref5, results;
var j, ref4, results;
results = [];
for (i = j = 0, ref5 = nodeWrapper.depth; 0 <= ref5 ? j < ref5 : j > ref5; i = 0 <= ref5 ? ++j : --j) {
for (i = j = 0, ref4 = nodeWrapper.depth; 0 <= ref4 ? j < ref4 : j > ref4; i = 0 <= ref4 ? ++j : --j) {
results.push(" ");

@@ -301,4 +395,9 @@ }

})()).join('');
options.log.debug(indent + "Examining " + (nodeWrapper.toString()));
options.log.debug(`${indent}Examining ${nodeWrapper.toString()}`);
}
if (nodeWrapper.node.comments) {
if (typeof visitor["visitComment"] === "function") {
visitor["visitComment"](nodeWrapper);
}
}
if (nodeWrapper.isStatement) {

@@ -309,9 +408,9 @@ if (typeof visitor["visitStatement"] === "function") {

}
if (typeof visitor[name = "visit" + nodeWrapper.type] === "function") {
if (typeof visitor[name = `visit${nodeWrapper.type}`] === "function") {
visitor[name](nodeWrapper);
}
if (nodeWrapper.isSwitchCases) {
ref5 = nodeWrapper.node;
for (i = j = 0, len = ref5.length; j < len; i = ++j) {
__ = ref5[i];
ref4 = nodeWrapper.node;
for (i = j = 0, len = ref4.length; j < len; i = ++j) {
__ = ref4[i];
nodeWrapper.forEachChildOfType(i, function(child) {

@@ -322,2 +421,3 @@ return runVisitor(visitor, child);

}
// Recurse into child nodes
return nodeWrapper.forEachChild(function(child) {

@@ -332,8 +432,8 @@ return runVisitor(visitor, child);

try {
// Compile the instrumented CoffeeScript and write it to the JS file.
js = ast.compile(coffeeOptions);
} catch (error) {
err = error;
/* !pragma coverage-skip-block */
throw new CoverageError("Could not compile " + fileName + " after instrumenting: " + err.stack);
throw new CoverageError(`Could not compile ${fileName} after instrumenting: ${err.stack}`);
}

@@ -340,0 +440,0 @@ answer = {

@@ -1,5 +0,8 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
var CoverageInstrumentor, DEFAULT_INSTRUMENTOR, INSTRUMENTORS, _, executableName, fs, mkdirs, parseArgs, path, ref, ref1, stripLeadingDotOrSlash, version;
//!/usr/bin/env coffee
// Implements functionality for the CLI command
var CoverageInstrumentor, DEFAULT_INSTRUMENTOR, INSTRUMENTORS, _, executableName, fs, mkdirs, parseArgs, path, stripLeadingDotOrSlash, version;
fs = require('fs');

@@ -11,7 +14,7 @@

ref = require('./index'), CoverageInstrumentor = ref.CoverageInstrumentor, version = ref.version;
({CoverageInstrumentor, version} = require('./index'));
INSTRUMENTORS = require('./coffeeCoverage').INSTRUMENTORS;
({INSTRUMENTORS} = require('./coffeeCoverage'));
ref1 = require('./utils/helpers'), stripLeadingDotOrSlash = ref1.stripLeadingDotOrSlash, mkdirs = ref1.mkdirs;
({stripLeadingDotOrSlash, mkdirs} = require('./utils/helpers'));

@@ -41,3 +44,3 @@ DEFAULT_INSTRUMENTOR = 'jscoverage';

parser.addArgument(['-c', '--coverageVar'], {
help: "Set the name to use in the instrumented code for the coverage variable. Defaults to\n'" + coverageVarDefault + "'.",
help: `Set the name to use in the instrumented code for the coverage variable. Defaults to\n'${coverageVarDefault}'.`,
metavar: "name",

@@ -47,9 +50,9 @@ defaultValue: coverageVarDefault

parser.addArgument(['-t', '--inst'], {
help: "Set the type of instrumentation to generate. Valid options are:\n" + (Object.keys(INSTRUMENTORS).map(function(t) {
help: `Set the type of instrumentation to generate. Valid options are:\n${Object.keys(INSTRUMENTORS).map(function(t) {
if (t === DEFAULT_INSTRUMENTOR) {
return t + " (default)";
return `${t} (default)`;
} else {
return t;
}
}).join(', ')),
}).join(', ')}`,
metavar: "type",

@@ -60,3 +63,3 @@ defaultValue: DEFAULT_INSTRUMENTOR

parser.addArgument(['-e', '--exclude'], {
help: "Comma delimited set of file names to exclude. Any file or directory which is in\nthis list will be ignored. Note that this field is case sensitive. Defaults to\n'" + excludeDefault + "'.",
help: `Comma delimited set of file names to exclude. Any file or directory which is in\nthis list will be ignored. Note that this field is case sensitive. Defaults to\n'${excludeDefault}'.`,
metavar: "filenames",

@@ -88,5 +91,6 @@ defaultValue: excludeDefault

parser.printUsage();
console.error(executableName + ": error: Invalid coverage type " + options.inst + ".\nMust be one of " + (Object.keys(INSTRUMENTORS).join(', ')));
console.error(`${executableName}: error: Invalid coverage type ${options.inst}.\nMust be one of ${Object.keys(INSTRUMENTORS).join(', ')}`);
process.exit(1);
}
// Split exclude into an array.
if (options.exclude) {

@@ -101,3 +105,3 @@ options.exclude = options.exclude.split(",");

exports.main = function(args) {
var coverageInstrumentor, err, options, ref2, result;
var coverageInstrumentor, err, options, ref, result;
try {

@@ -114,11 +118,12 @@ options = parseArgs(args.slice(2));

coverageInstrumentor.on("instrumentingDirectory", function(sourceDir, outDir) {
return console.log("Instrumenting directory: " + (stripLeadingDotOrSlash(sourceDir)) + " to " + (stripLeadingDotOrSlash(outDir)));
return console.log(`Instrumenting directory: ${stripLeadingDotOrSlash(sourceDir)} to ${stripLeadingDotOrSlash(outDir)}`);
});
coverageInstrumentor.on("instrumentingFile", function(sourceFile, outFile) {
return console.log(" " + (stripLeadingDotOrSlash(sourceFile)) + " to " + (stripLeadingDotOrSlash(outFile)));
return console.log(` ${stripLeadingDotOrSlash(sourceFile)} to ${stripLeadingDotOrSlash(outFile)}`);
});
coverageInstrumentor.on("skip", function(file) {
return console.log(" Skipping: " + (stripLeadingDotOrSlash(file)));
return console.log(` Skipping: ${stripLeadingDotOrSlash(file)}`);
});
}
// Change initFile into a output stream
if (options.initfile) {

@@ -133,9 +138,9 @@ mkdirs(path.dirname(options.initfile));

result = coverageInstrumentor.instrument(options.src, options.dest, options);
if ((ref2 = options.initFileStream) != null) {
ref2.end();
if ((ref = options.initFileStream) != null) {
ref.end();
}
if (!result) {
return console.error(options.src + " does not exist.");
return console.error(`${options.src} does not exist.`);
} else {
return console.log("Instrumented " + result.lines + " lines.");
return console.log(`Instrumented ${result.lines} lines.`);
}

@@ -145,3 +150,3 @@ } catch (error) {

if (err.constructor.name === "CoverageError") {
console.error("Error: " + err.message);
console.error(`Error: ${err.message}`);
return process.exit(1);

@@ -148,0 +153,0 @@ } else {

@@ -1,4 +0,4 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
var CompiledCache, EXTENSIONS, _, fs, getRelativeFilename, mkdirs, path, ref;
var CompiledCache, EXTENSIONS, _, fs, getRelativeFilename, mkdirs, path;

@@ -11,19 +11,16 @@ fs = require('fs');

EXTENSIONS = require('./constants').EXTENSIONS;
({EXTENSIONS} = require('./constants'));
ref = require('./utils/helpers'), mkdirs = ref.mkdirs, getRelativeFilename = ref.getRelativeFilename;
({mkdirs, getRelativeFilename} = require('./utils/helpers'));
module.exports = CompiledCache = (function() {
function CompiledCache(basePath, cacheDir1, ext) {
module.exports = CompiledCache = class CompiledCache {
constructor(basePath, cacheDir1, ext = '_covered') {
this.basePath = basePath;
this.cacheDir = cacheDir1;
this.ext = ext != null ? ext : '_covered';
this.ext = ext;
}
CompiledCache.prototype._getCacheFileName = function(fileName, options) {
var cacheFile, newExt, ref1, relativeFile;
if (options == null) {
options = {};
}
newExt = (ref1 = options.ext) != null ? ref1 : this.ext;
_getCacheFileName(fileName, options = {}) {
var cacheFile, newExt, ref, relativeFile;
newExt = (ref = options.ext) != null ? ref : this.ext;
relativeFile = getRelativeFilename(this.basePath, fileName);

@@ -33,9 +30,6 @@ cacheFile = path.resolve(this.cacheDir, relativeFile);

return cacheFile;
};
}
CompiledCache.prototype.get = function(fileName, compileFn) {
get(fileName, compileFn = null) {
var answer, cacheFileName, cacheStat, fileStat;
if (compileFn == null) {
compileFn = null;
}
if (!this.cacheDir) {

@@ -60,9 +54,6 @@ return typeof compileFn === "function" ? compileFn() : void 0;

return answer;
};
}
CompiledCache.prototype.put = function(fileName, contents, options) {
put(fileName, contents, options = {}) {
var cacheDir, cacheFileName;
if (options == null) {
options = {};
}
if (!this.cacheDir || !contents) {

@@ -77,8 +68,6 @@ return;

});
};
}
return CompiledCache;
};
})();
}).call(this);

@@ -1,3 +0,4 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
// Extensions we know how to process.
exports.EXTENSIONS = {

@@ -4,0 +5,0 @@ ".coffee": {

@@ -1,2 +0,2 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {

@@ -9,4 +9,5 @@ exports.register = require('./register');

// Add 'version', 'author', and 'contributors' to our exports
require('pkginfo')(module, 'version', 'author', 'contributors');
}).call(this);

@@ -1,5 +0,59 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
var Istanbul, NodeWrapper, _, assert, compareLocations, fileToLines, findInCode, minLocation, nodeToLocation, ref, toQuotedString;
// This is an instrumentor which provides [Istanbul](https://github.com/gotwarlost/istanbul) style
// instrumentation. This will add a JSON report object to the source code. The report object is a
// hash where keys are file names (absolute paths), and values are coverage data for that file
// (the result of `json.stringify(collector.fileCoverageFor(filename))`) Each coverage data
// consists of:
// * `path` - The path to the file. This is an absolute path.
// * `s` - Hash of statement counts, where keys as statement IDs.
// * `b` - Hash of branch counts, where keys are branch IDs and values are arrays of counts.
// For an if statement, the value would have two counts; one for the if, and one for the
// else. Switch statements would have an array of values for each case.
// * `f` - Hash of function counts, where keys are function IDs.
// * `fnMap` - Hash of functions where keys are function IDs, and values are `{name, line, loc}`,
// where `name` is the name of the function, `line` is the line the function is declared on,
// and `loc` is the `Location` of the function declaration (just the declaration, not the entire
// function body.)
// * `statementMap` - Hash where keys are statement IDs, and values are `Location` objects for each
// statement. The `Location` for a function definition is really an assignment, and should
// include the entire function.
// * `branchMap` - Hash where keys are branch IDs, and values are `{line, type, locations}` objects.
// `line` is the line the branch starts on. `type` is the type of the branch (e.g. "if", "switch").
// `locations` is an array of `Location` objects, one for each possible outcome of the branch.
// Note for an `if` statement where there is no `else` clause, there will still be two `locations`
// generated. Istanbul does *not* generate coverage for the `default` case of a switch statement
// if `default` is not explicitly present in the source code.
// `locations` for an if statement are always 0-length and located at the start of the `if` (even
// the location for the "else"). For a `switch` statement, `locations` start at the start of the
// `case` statement and go to the end of the line before the next case statement (note Istanbul
// does nothing clever here if a `case` is missing a `break`.)
// ## Location Objects
// Location objects are a `{start: {line, column}, end: {line, column}, skip}` object that describes
// the start and end of a piece of code. Note that `line` is 1-based, but `column` is 0-based.
// `skip` is optional - if true it instructs Istanbul to ignore if this location has no executions.
// An `### istanbul ignore next ###` before a statement would cause that statement's location
// in the `staementMap` to be marked `skip: true`. For an `if` or a `switch`, this should also
// cause all desendant statments to be marked `skip`, as well as all locations in the `branchMap`.
// An `### istanbul ignore if ###` should cause the loction for the `if` in the `branchMap` to be
// marked `skip`, along with all statements inside the `if`. Similar for
// `### istanbul ignore else ###`.
// An `### istanbul ignore next ###` before a `when` in a `switch` should cause the appropriate
// entry in the `branchMap` to be marked skip, and all statements inside the `when`.
// (coffeescript doesn't allow block comments at top scope inside a switch. Might not be
// able to do this.)
// An `### istanbul ignore next ###` before a function declaration should cause the function (not
// the location) in the `fnMap` to be marked `skip`, the statement for the function delcaration and
// all statements in the function to be marked `skip` in the `statementMap`.
var Istanbul, NodeWrapper, _, assert, compareLocations, fileToLines, findInCode, minLocation, nodeToLocation, toQuotedString;
assert = require('assert');

@@ -11,8 +65,9 @@

toQuotedString = require('../utils/helpers').toQuotedString;
({toQuotedString} = require('../utils/helpers'));
ref = require('../utils/codeUtils'), compareLocations = ref.compareLocations, fileToLines = ref.fileToLines, minLocation = ref.minLocation;
({compareLocations, fileToLines, minLocation} = require('../utils/codeUtils'));
nodeToLocation = function(node) {
var answer, ref1;
var answer, ref;
// Istanbul uses 1-based lines, but 0-based columns
answer = {

@@ -28,3 +83,3 @@ start: {

};
if (((ref1 = node.coffeeCoverage) != null ? ref1.skip : void 0) || (typeof node.isMarked === "function" ? node.isMarked('skip') : void 0)) {
if (((ref = node.coffeeCoverage) != null ? ref.skip : void 0) || (typeof node.isMarked === "function" ? node.isMarked('skip') : void 0)) {
answer.skip = true;

@@ -35,12 +90,11 @@ }

findInCode = function(code, str, options) {
var column, currentCol, currentLine, end, ref1, ref2, start;
if (options == null) {
options = {};
}
start = (ref1 = options.start) != null ? ref1 : {
// Find a string in the source code, and return a `{line, column}`.
// Line is 1-based and column is 0-based.
findInCode = function(code, str, options = {}) {
var column, currentCol, currentLine, end, ref, ref1, start;
start = (ref = options.start) != null ? ref : {
line: 1,
column: 0
};
end = (ref2 = options.end) != null ? ref2 : {
end = (ref1 = options.end) != null ? ref1 : {
line: code.length + 1,

@@ -55,7 +109,7 @@ column: 0

line: currentLine,
column: column
column
}, end) < 1) {
return {
line: currentLine,
column: column
column
};

@@ -69,13 +123,15 @@ }

module.exports = Istanbul = (function() {
Istanbul.getDefaultOptions = function() {
var ref1;
module.exports = Istanbul = class Istanbul {
// Return default options for this instrumentor.
static getDefaultOptions() {
var ref;
return {
coverageVar: (ref1 = module.exports.findIstanbulVariable()) != null ? ref1 : '__coverage__'
coverageVar: (ref = module.exports.findIstanbulVariable()) != null ? ref : '__coverage__'
};
};
}
Istanbul.findIstanbulVariable = function() {
// Find the runtime Istanbul variable, if it exists. Otherwise, fall back to a sensible default.
static findIstanbulVariable() {
var coverageVar, coverageVars;
coverageVar = "$$cov_" + (Date.now()) + "$$";
coverageVar = `$$cov_${Date.now()}$$`;
if (global[coverageVar] == null) {

@@ -88,2 +144,3 @@ coverageVars = Object.keys(global).filter(function(key) {

} else {
// Needs to be undefined and not `null`, because `_.defaults()` treats them differently.
coverageVar = void 0;

@@ -93,11 +150,10 @@ }

return coverageVar;
};
}
function Istanbul(fileName, source, options) {
// `options` is a `{log, coverageVar}` object.
constructor(fileName, source, options = {}) {
this.fileName = fileName;
this.source = source;
if (options == null) {
options = {};
}
this.log = options.log, this.coverageVar = options.coverageVar;
({log: this.log, coverageVar: this.coverageVar} = options);
options = _.defaults({}, options, Istanbul.getDefaultOptions());

@@ -111,27 +167,30 @@ this.sourceLines = fileToLines(this.source);

this.anonId = 1;
this._prefix = this.coverageVar + "[" + this.quotedFileName + "]";
this._prefix = `${this.coverageVar}[${this.quotedFileName}]`;
}
/* !pragma coverage-skip-next */
Istanbul.prototype._warn = function(message, options) {
var lineNumber, ref1, str;
if (options == null) {
options = {};
}
_warn(message, options = {}) {
var lineNumber, ref, str;
str = message;
str += "\n file: " + this.fileName;
str += `\n file: ${this.fileName}`;
if (options.node) {
str += "\n node: " + (options.node.toString());
str += `\n node: ${options.node.toString()}`;
}
if ((options.line != null) || options.node) {
lineNumber = options.line != null ? options.line : options.node.locationData.first_line + 1;
str += "\n source: " + this.sourceLines[lineNumber - 1];
str += `\n source: ${this.sourceLines[lineNumber - 1]}`;
}
return (ref1 = this.log) != null ? ref1.warn(str) : void 0;
};
return (ref = this.log) != null ? ref.warn(str) : void 0;
}
Istanbul.prototype.visitStatement = function(node) {
var location, statementId;
// Called on each non-comment statement within a Block. If a `visitXXX` exists for the
// specific node type, it will also be called after `visitStatement`.
visitStatement(node) {
var grandParentType, location, ref, ref1, ref2, ref3, statementId;
grandParentType = (ref = node.parent) != null ? (ref1 = ref.parent) != null ? (ref2 = ref1.node) != null ? (ref3 = ref2.constructor) != null ? ref3.name : void 0 : void 0 : void 0 : void 0;
if (grandParentType === "StringWithInterpolations" && !node.parent.parent.skipped) {
node.parent.parent.skipped = true;
return;
}
// Ignore nodes marked 'noCoverage'
if (node.isMarked('noCoverage')) {

@@ -146,7 +205,10 @@ return;

this.statementMap.push(location);
node.insertBefore(this._prefix + ".s[" + statementId + "]++");
node.insertBefore(`${this._prefix}.s[${statementId}]++`);
return this.instrumentedLineCount++;
};
}
Istanbul.prototype._findEndOfIf = function(ifNode) {
// coffeescript will put the end of an 'If' statement as being right before the start of
// the 'else' (which is probably a bug.) Istanbul expects the end to be the end of the last
// line in the else (and for chained ifs, Istanbul expects the end of the very last else.)
_findEndOfIf(ifNode) {
var elseBody, elseChild;

@@ -165,6 +227,7 @@ assert(ifNode.type === 'If');

}
};
}
Istanbul.prototype.visitIf = function(node) {
var body, bodyPresent, branchId, elseBody, elseBodyPresent, elseLocation, ifLocation, ref1;
visitIf(node) {
var body, bodyPresent, branchId, elseBody, elseBodyPresent, elseLocation, ifLocation, ref;
// Ignore nodes marked 'noCoverage'
if (node.isMarked('noCoverage')) {

@@ -174,2 +237,3 @@ return;

branchId = this.branchMap.length + 1;
// Make a 0-length `Location` object.
ifLocation = nodeToLocation(node);

@@ -189,5 +253,10 @@ ifLocation.end.line = ifLocation.start.line;

if (node.node.isChain) {
if ((ref1 = this.log) != null) {
if (typeof ref1.debug === "function") {
ref1.debug(" Disabling chaining for if statement");
// Chaining is where coffee compiles something into `... else if ...`
// instead of '... else {if ...}`. Chaining produces nicer looking coder
// with fewer indents, but it also produces code that's harder to instrument
// (because we can't add code between the `else` and the `if`), so we turn it off.
if ((ref = this.log) != null) {
if (typeof ref.debug === "function") {
ref.debug(" Disabling chaining for if statement");
}

@@ -204,13 +273,37 @@ }

if (bodyPresent && !elseBodyPresent) {
node.insertAtStart('body', this._prefix + ".b[" + branchId + "][0]++");
node.parent.insertAfter(this._prefix + ".b[" + branchId + "][1]++");
// This is kind of a weird case:
// fn = (x) ->
// return if x then 10
// return 20
// You might think that second `return` statement was unreachable, but it will actually
// be hit if `x` is fasley. We can't add anything to the `elseBody` here, though
// without making the the second return statement unreachable. The solution is
// to add the instrumentation for the "else" case after the `return`.
node.insertAtStart('body', `${this._prefix}.b[${branchId}][0]++`);
node.parent.insertAfter(`${this._prefix}.b[${branchId}][1]++`);
this.instrumentedLineCount += 2;
} else if (elseBodyPresent && !bodyPresent) {
node.insertAtStart('elseBody', this._prefix + ".b[" + branchId + "][1]++");
node.parent.insertAfter(this._prefix + ".b[" + branchId + "][0]++");
// This is the even weirder case:
// fn = (x) ->
// return if x then else 10
// return 20
node.insertAtStart('elseBody', `${this._prefix}.b[${branchId}][1]++`);
node.parent.insertAfter(`${this._prefix}.b[${branchId}][0]++`);
this.instrumentedLineCount += 2;
} else if (!elseBodyPresent && !bodyPresent) {
this._warn("If statement could not be instrumented", {
node: node
});
// Yes, you can do this:
// fn = (x) ->
// return if x then else
// return 20
// but it's a bit stupid, so if you do it, we're just going to ignore this
// statement.
this._warn("If statement could not be instrumented", {node});
ifLocation.skip = true;

@@ -226,4 +319,4 @@ elseLocation.skip = true;

}
node.insertAtStart('body', this._prefix + ".b[" + branchId + "][0]++");
node.insertAtStart('elseBody', this._prefix + ".b[" + branchId + "][1]++");
node.insertAtStart('body', `${this._prefix}.b[${branchId}][0]++`);
node.insertAtStart('elseBody', `${this._prefix}.b[${branchId}][1]++`);
this.instrumentedLineCount += 2;

@@ -237,6 +330,7 @@ }

});
};
}
Istanbul.prototype.visitSwitch = function(node) {
visitSwitch(node) {
var branchId, loc, locations;
// Ignore nodes marked 'noCoverage'
if (node.isMarked('noCoverage')) {

@@ -247,34 +341,33 @@ return;

locations = [];
locations = node.node.cases.map((function(_this) {
return function(arg) {
var answer, block, blockLocation, conditions, ref1, start, startColumn;
conditions = arg[0], block = arg[1];
start = minLocation(_.flatten([conditions], true).map(function(condition) {
return nodeToLocation(condition).start;
}));
blockLocation = nodeToLocation(block);
if ((startColumn = (ref1 = _this.sourceLines[start.line - 1]) != null ? ref1.indexOf('when') : void 0) > -1) {
start.column = startColumn;
} else {
/* !pragma coverage-skip-block */
_this._warn("Couldn't find 'when'", {
node: node,
line: start.line
});
start.column -= 5;
if (start.column < 0) {
start.column = 0;
}
locations = node.node.cases.map(([conditions, block]) => {
var answer, blockLocation, ref, start, startColumn;
start = minLocation(_.flatten([conditions], true).map(function(condition) {
return nodeToLocation(condition).start;
}));
blockLocation = nodeToLocation(block);
// start.column is the start of the condition, but we want the start of the
// `when`.
if ((startColumn = (ref = this.sourceLines[start.line - 1]) != null ? ref.indexOf('when') : void 0) > -1) {
start.column = startColumn;
} else {
/* !pragma coverage-skip-block */
this._warn("Couldn't find 'when'", {
node,
line: start.line
});
// Intelligent guess
start.column -= 5;
if (start.column < 0) {
start.column = 0;
}
answer = {
start: start,
end: blockLocation.end
};
if (node.isMarked('skip') || blockLocation.skip) {
answer.skip = true;
}
return answer;
}
answer = {
start,
end: blockLocation.end
};
})(this));
if (node.isMarked('skip') || blockLocation.skip) {
answer.skip = true;
}
return answer;
});
if (node.node.otherwise != null) {

@@ -286,27 +379,23 @@ locations.push(nodeToLocation(node.node.otherwise));

line: loc.start.line,
loc: loc,
loc,
type: 'switch',
locations: locations
locations
});
node.node.cases.forEach((function(_this) {
return function(arg, index) {
var block, caseNode, conditions;
conditions = arg[0], block = arg[1];
caseNode = new NodeWrapper(block, node, 'cases', index, node.depth + 1);
assert.equal(caseNode.type, 'Block');
return caseNode.insertAtStart('expressions', _this._prefix + ".b[" + branchId + "][" + index + "]++");
};
})(this));
return node.forEachChildOfType('otherwise', (function(_this) {
return function(otherwise) {
var index;
index = node.node.cases.length;
assert.equal(otherwise.type, 'Block');
return otherwise.insertAtStart('expressions', _this._prefix + ".b[" + branchId + "][" + index + "]++");
};
})(this));
};
node.node.cases.forEach(([conditions, block], index) => {
var caseNode;
caseNode = new NodeWrapper(block, node, 'cases', index, node.depth + 1);
assert.equal(caseNode.type, 'Block');
return caseNode.insertAtStart('expressions', `${this._prefix}.b[${branchId}][${index}]++`);
});
return node.forEachChildOfType('otherwise', (otherwise) => {
var index;
index = node.node.cases.length;
assert.equal(otherwise.type, 'Block');
return otherwise.insertAtStart('expressions', `${this._prefix}.b[${branchId}][${index}]++`);
});
}
Istanbul.prototype.visitCode = function(node) {
var arrow, end, endOfFn, fnMapEntry, functionId, isAssign, lastParam, name, paramCount, ref1, ref2, ref3, ref4, ref5, start;
visitCode(node) {
var arrow, end, endOfFn, fnMapEntry, functionId, isAssign, lastParam, name, paramCount, ref, ref1, ref2, ref3, ref4, start;
// Ignore nodes marked 'noCoverage'
if (node.isMarked('noCoverage')) {

@@ -316,5 +405,7 @@ return;

functionId = this.fnMap.length + 1;
paramCount = (ref1 = (ref2 = node.node.params) != null ? ref2.length : void 0) != null ? ref1 : 0;
isAssign = node.parent.type === 'Assign' && (((ref3 = node.parent.node.variable) != null ? (ref4 = ref3.base) != null ? ref4.value : void 0 : void 0) != null);
name = isAssign ? node.parent.node.variable.base.value : "(anonymous_" + (this.anonId++) + ")";
paramCount = (ref = (ref1 = node.node.params) != null ? ref1.length : void 0) != null ? ref : 0;
isAssign = node.parent.type === 'Assign' && (((ref2 = node.parent.node.variable) != null ? (ref3 = ref2.base) != null ? ref3.value : void 0 : void 0) != null);
// Figure out the name of this funciton
name = isAssign ? node.parent.node.variable.base.value : `(anonymous_${this.anonId++})`;
// Find the start and end of the function declaration.
start = isAssign ? nodeToLocation(node.parent).start : nodeToLocation(node).start;

@@ -324,2 +415,3 @@ if (paramCount > 0) {

end = nodeToLocation(lastParam).end;
// Coffee-script doesn't tell us where the `->` is, so we have to find it
arrow = node.node.bound ? '=>' : '->';

@@ -337,8 +429,8 @@ endOfFn = findInCode(this.sourceLines, arrow, {

} else {
/* !pragma coverage-skip-block */
this._warn("Couldn't find '->' or '=>'", {
node: node,
node,
line: start.line
});
// Educated guess
end.column += 4;

@@ -348,22 +440,21 @@ }

end = nodeToLocation(node).start;
// Fix off-by-one error
end.column++;
}
fnMapEntry = {
name: name,
name,
line: start.line,
loc: nodeToLocation(node),
decl: {
start: start,
end: end
}
decl: {start, end}
};
if ((ref5 = node.node.coffeeCoverage) != null ? ref5.skip : void 0) {
if ((ref4 = node.node.coffeeCoverage) != null ? ref4.skip : void 0) {
fnMapEntry.skip = true;
}
this.fnMap.push(fnMapEntry);
return node.insertAtStart('body', this._prefix + ".f[" + functionId + "]++");
};
return node.insertAtStart('body', `${this._prefix}.f[${functionId}]++`);
}
Istanbul.prototype.visitClass = function(node) {
var functionId, loc, ref1;
visitClass(node) {
var functionId, loc, ref;
// Ignore nodes marked 'noCoverage'
if (node.isMarked('noCoverage')) {

@@ -380,3 +471,3 @@ return;

this.fnMap.push({
name: (ref1 = node.node.determineName()) != null ? ref1 : '_Class',
name: (ref = node.node.determineName()) != null ? ref : '_Class',
line: loc.start.line,

@@ -386,6 +477,6 @@ loc: nodeToLocation(node),

});
return node.insertAtStart('body', this._prefix + ".f[" + functionId + "]++");
};
return node.insertAtStart('body', `${this._prefix}.f[${functionId}]++`);
}
Istanbul.prototype.getInitString = function() {
getInitString() {
var init, initData;

@@ -407,5 +498,5 @@ initData = {

initData.b[id + 1] = (function() {
var i, ref1, results;
var i, ref, results;
results = [];
for (i = 0, ref1 = branch.locations.length; 0 <= ref1 ? i < ref1 : i > ref1; 0 <= ref1 ? i++ : i--) {
for (i = 0, ref = branch.locations.length; 0 <= ref ? i < ref : i > ref; 0 <= ref ? i++ : i--) {
results.push(0);

@@ -421,13 +512,11 @@ }

});
return init = "if (typeof " + this.coverageVar + " === 'undefined') " + this.coverageVar + " = {};\n(function(_export) {\n if (typeof _export." + this.coverageVar + " === 'undefined') {\n _export." + this.coverageVar + " = " + this.coverageVar + ";\n }\n})(typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : this);\nif (! " + this._prefix + ") { " + this._prefix + " = " + (JSON.stringify(initData)) + " }";
};
return init = `if (typeof ${this.coverageVar} === 'undefined') ${this.coverageVar} = {};\n(function(_export) {\n if (typeof _export.${this.coverageVar} === 'undefined') {\n _export.${this.coverageVar} = ${this.coverageVar};\n }\n})(typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : this);\nif (! ${this._prefix}) { ${this._prefix} = ${JSON.stringify(initData)} }`;
}
Istanbul.prototype.getInstrumentedLineCount = function() {
getInstrumentedLineCount() {
return this.instrumentedLineCount;
};
}
return Istanbul;
};
})();
}).call(this);

@@ -1,6 +0,13 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
var JSCoverage, _, fileToLines, generateUniqueName, getRelativeFilename, path, ref, stripLeadingDotOrSlash, toQuotedString,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
// This is an instrumentor which provides [JSCoverage](http://siliconforks.com/jscoverage/) style
// instrumentation. This will add a `_$jscoverage` variable to the source code, which is
// a hash where keys are file names, and values are sparse arrays where keys are line number and
// values are the count that the given line was executed. In addition,
// `_$jscoverage['filename'].source` will be an array containing a copy of the original source code
// split into lines.
var JSCoverage, _, fileToLines, generateUniqueName, getRelativeFilename, path, stripLeadingDotOrSlash, toQuotedString,
indexOf = [].indexOf;
path = require('path');

@@ -10,6 +17,7 @@

ref = require('../utils/helpers'), toQuotedString = ref.toQuotedString, stripLeadingDotOrSlash = ref.stripLeadingDotOrSlash, getRelativeFilename = ref.getRelativeFilename;
({toQuotedString, stripLeadingDotOrSlash, getRelativeFilename} = require('../utils/helpers'));
fileToLines = require('../utils/codeUtils').fileToLines;
({fileToLines} = require('../utils/codeUtils'));
// Generate a unique file name
generateUniqueName = function(usedNames, desiredName) {

@@ -29,4 +37,5 @@ var answer, suffix;

module.exports = JSCoverage = (function() {
JSCoverage.getDefaultOptions = function() {
module.exports = JSCoverage = class JSCoverage {
// Return default options for this instrumentor.
static getDefaultOptions() {
return {

@@ -37,47 +46,59 @@ path: 'bare',

};
};
}
function JSCoverage(fileName, source, options) {
var ref1, relativeFileName;
// `options` is a `{log, coverageVar, basePath, path, usedFileNameMap}` object.
// * `options.path` should be one of:
// * 'relative' - file names will have the `basePath` stripped from them.
// * 'abbr' - an abbreviated file name will be constructed, with each parent in the path
// replaced by the first character in its name.
// * 'bare' (default) - Path names will be omitted. Only the base file name will be used.
// * If `options.usedFileNameMap` is present, it must be an object. This method will add a
// mapping from the absolute file path to the short filename in usedFileNameMap. If the name
// of the file is already in usedFileNameMap then this method will generate a unique name.
// `options.usedFileNames` is the deprecated array version of `usedFileNameMap`.
constructor(fileName, source, options = {}) {
var ref, relativeFileName;
this.fileName = fileName;
this.source = source;
if (options == null) {
options = {};
}
this.log = options.log, this.coverageVar = options.coverageVar;
({log: this.log, coverageVar: this.coverageVar} = options);
options = _.defaults({}, options, JSCoverage.getDefaultOptions());
this.instrumentedLines = [];
relativeFileName = getRelativeFilename(options.basePath, this.fileName);
this.shortFileName = ((ref1 = options.usedFileNameMap) != null ? ref1[this.fileName] : void 0) || (function(_this) {
return function() {
var shortFileName, usedFileNames;
shortFileName = (function() {
switch (options.path) {
case 'relative':
return stripLeadingDotOrSlash(relativeFileName);
case 'abbr':
return this._abbreviatedPath(stripLeadingDotOrSlash(relativeFileName));
default:
return path.basename(relativeFileName);
}
}).call(_this);
if (options.usedFileNames != null) {
if (indexOf.call(options.usedFileNames, shortFileName) >= 0) {
shortFileName = generateUniqueName(options.usedFileNames, shortFileName);
}
options.usedFileNames.push(shortFileName);
} else if (options.usedFileNameMap != null) {
usedFileNames = _.values(options.usedFileNameMap);
if (indexOf.call(usedFileNames, shortFileName) >= 0) {
shortFileName = generateUniqueName(usedFileNames, shortFileName);
}
options.usedFileNameMap[_this.fileName] = shortFileName;
this.shortFileName = ((ref = options.usedFileNameMap) != null ? ref[this.fileName] : void 0) || (() => {
var shortFileName, usedFileNames;
shortFileName = (function() {
switch (options.path) {
case 'relative':
return stripLeadingDotOrSlash(relativeFileName);
case 'abbr':
return this._abbreviatedPath(stripLeadingDotOrSlash(relativeFileName));
default:
return path.basename(relativeFileName);
}
return shortFileName;
};
})(this)();
}).call(this);
// Generate a unique fileName if required.
if (options.usedFileNames != null) {
// `usedFileNames` is deprecated, but prefer it over `userFileNameMap`, since
// `usedFileNameMap` will always be present thanks to the defaults.
if (indexOf.call(options.usedFileNames, shortFileName) >= 0) {
shortFileName = generateUniqueName(options.usedFileNames, shortFileName);
}
options.usedFileNames.push(shortFileName);
} else if (options.usedFileNameMap != null) {
usedFileNames = _.values(options.usedFileNameMap);
if (indexOf.call(usedFileNames, shortFileName) >= 0) {
shortFileName = generateUniqueName(usedFileNames, shortFileName);
}
options.usedFileNameMap[this.fileName] = shortFileName;
}
return shortFileName;
})();
this.quotedFileName = toQuotedString(this.shortFileName);
}
JSCoverage.prototype._abbreviatedPath = function(pathName) {
// Converts a path like "./foo/bar/baz" to "./f/b/baz"
_abbreviatedPath(pathName) {
var answer, filename, i, len, needTrailingSlash, pathElement, splitPath;

@@ -110,6 +131,9 @@ needTrailingSlash = false;

return answer;
};
}
JSCoverage.prototype.visitStatement = function(node) {
var line, ref1, ref2;
// Called on each non-comment statement within a Block. If a `visitXXX` exists for the
// specific node type, it will also be called after `visitStatement`.
visitStatement(node) {
var line, ref, ref1;
// Don't instrument skipped lines.
if (node.isMarked('skip') || node.isMarked('noCoverage')) {

@@ -120,20 +144,39 @@ return;

if (indexOf.call(this.instrumentedLines, line) >= 0) {
return (ref1 = this.log) != null ? typeof ref1.debug === "function" ? ref1.debug("Skipping " + (node.toString())) : void 0 : void 0;
// Never instrument the same line twice. This can happen in a situation like:
// if x then console.log "foo"
// Here the "if" statement can be instrumented, but we could also instrument the
// "console.log" statement on the same line.
// Note that we also run into a weird situation here:
// x = if y then {name: "foo"} \
// else {name: "bar"}
// Because here we're going to instrument the inside of the "else" block,
// but not the inside of the "if" block, which is OK, but a bit weird.
return (ref = this.log) != null ? typeof ref.debug === "function" ? ref.debug(`Skipping ${node.toString()}`) : void 0 : void 0;
} else {
if ((ref2 = this.log) != null) {
if (typeof ref2.debug === "function") {
ref2.debug("Instrumenting " + (node.toString()));
if ((ref1 = this.log) != null) {
if (typeof ref1.debug === "function") {
ref1.debug(`Instrumenting ${node.toString()}`);
}
}
this.instrumentedLines.push(line);
return node.insertBefore(this.coverageVar + "[" + this.quotedFileName + "][" + line + "]++");
return node.insertBefore(`${this.coverageVar}[${this.quotedFileName}][${line}]++`);
}
};
}
JSCoverage.prototype.visitIf = function(node) {
var ref1;
visitIf(node) {
var ref;
if (node.node.isChain) {
if ((ref1 = this.log) != null) {
if (typeof ref1.debug === "function") {
ref1.debug(" Disabling chaining for if statement");
// Chaining is where coffee compiles something into `... else if ...`
// instead of '... else {if ...}`. Chaining produces nicer looking coder
// with fewer indents, but it also produces code that's harder to instrument
// (because we can't add code between the `else` and the `if`), so we turn it off.
if ((ref = this.log) != null) {
if (typeof ref.debug === "function") {
ref.debug(" Disabling chaining for if statement");
}

@@ -143,14 +186,15 @@ }

}
};
}
JSCoverage.prototype.getInitString = function() {
var fileToInstrumentLines, i, index, init, j, len, len1, line, lineNumber, ref1;
init = "if (typeof " + this.coverageVar + " === 'undefined') " + this.coverageVar + " = {};\n(function(_export) {\n if (typeof _export." + this.coverageVar + " === 'undefined') {\n _export." + this.coverageVar + " = " + this.coverageVar + ";\n }\n})(typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : this);\nif (! " + this.coverageVar + "[" + this.quotedFileName + "]) {\n " + this.coverageVar + "[" + this.quotedFileName + "] = [];\n";
ref1 = this.instrumentedLines;
for (i = 0, len = ref1.length; i < len; i++) {
lineNumber = ref1[i];
init += " " + this.coverageVar + "[" + this.quotedFileName + "][" + lineNumber + "] = 0;\n";
getInitString() {
var fileToInstrumentLines, i, index, init, j, len, len1, line, lineNumber, ref;
init = `if (typeof ${this.coverageVar} === 'undefined') ${this.coverageVar} = {};\n(function(_export) {\n if (typeof _export.${this.coverageVar} === 'undefined') {\n _export.${this.coverageVar} = ${this.coverageVar};\n }\n})(typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : this);\nif (! ${this.coverageVar}[${this.quotedFileName}]) {\n ${this.coverageVar}[${this.quotedFileName}] = [];\n`;
ref = this.instrumentedLines;
for (i = 0, len = ref.length; i < len; i++) {
lineNumber = ref[i];
init += ` ${this.coverageVar}[${this.quotedFileName}][${lineNumber}] = 0;\n`;
}
init += "}\n\n";
init += this.coverageVar + "[" + this.quotedFileName + "].source = [";
// Write the original source code into the ".source" array.
init += `${this.coverageVar}[${this.quotedFileName}].source = [`;
fileToInstrumentLines = fileToLines(this.source);

@@ -165,12 +209,10 @@ for (index = j = 0, len1 = fileToInstrumentLines.length; j < len1; index = ++j) {

return init += "];\n\n";
};
}
JSCoverage.prototype.getInstrumentedLineCount = function() {
getInstrumentedLineCount() {
return this.instrumentedLines.length;
};
}
return JSCoverage;
};
})();
}).call(this);

@@ -1,2 +0,2 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {

@@ -7,9 +7,26 @@ var NodeWrapper, _, assert, coffeeScript, compile, forNodeAndChildren;

coffeeScript = require('coffee-script');
coffeeScript = require('coffeescript');
_ = require('lodash');
module.exports = NodeWrapper = (function() {
function NodeWrapper(node1, parent, childName1, childIndex1, depth) {
var ref, ref1;
// Wraps a `node` returned from coffeescript's `nodes()` method.
// Properties:
// * `node` - The original coffeescript node.
// * `parent` - A `NodeWrapper` object for the parent of the coffeescript node.
// * `childName` - A coffeescript node has multiple named children. This is the name of the
// attribute which contains this node in `@parent.node`. Note that `@parent.node[childName]`
// may be a single Node or it may be an array of nodes, depending on the implementation of the
// specific node type.
// * `childIndex` - Where `@parent.node[childName]` is an array, this is the index of `@node`
// in `@parent.node[childName]`. Note that inserting new nodes will obviously invalidate this
// value, so this is more of a "hint" than a hard and fast truism.
// * `depth` - The depth in the AST from the root node.
// * `type` - Copy of @node.constructor.name.
// * `locationData` - Copy of @node.locationData.
// * `isStatement` - true if this node is a statement.
module.exports = NodeWrapper = class NodeWrapper {
constructor(node1, parent, childName1, childIndex1, depth = 0) {
var ref, ref1, ref2;
this.node = node1;

@@ -19,9 +36,16 @@ this.parent = parent;

this.childIndex = childIndex1;
this.depth = depth != null ? depth : 0;
this.depth = depth;
assert(this.node);
this.locationData = this.node.locationData;
this.type = ((ref = this.node.constructor) != null ? ref.name : void 0) || null;
this.isStatement = (this.parent != null) && this.type !== 'Comment' && this.parent.type === 'Block' && this.childName === 'expressions';
// TODO: Is this too naive? coffeescript nodes have a `isStatement(o)` function, which
// really only cares about `o.level`. Should we be working out the level and calling
// this function instead of trying to figure this out ourselves?
this.isStatement = (this.parent != null) && this.parent.type === 'Block' && this.childName === 'expressions';
// Note we exclude 'Value' nodes. When you parse a Class, you'll get Value nodes wrapping
// each contiguous block of function assignments, and we don't want to treat these as
// statements. I can't think of another case where you have a Value as a direct child
// of an expression.
if (this.isStatement && this.type === 'Value' && ((ref1 = this.parent.parent) != null ? ref1.type : void 0) === 'Class') {
this.isStatement = false;
this.isStatement = ((ref2 = this.node.base.constructor) != null ? ref2.name : void 0) === "Call";
}

@@ -31,13 +55,15 @@ this.isSwitchCases = this.childName === 'cases' && this.type === 'Array';

NodeWrapper.prototype.forEachChild = function(fn) {
// Run `fn(node)` for each child of this node. Child nodes will be automatically wrapped in a
// `NodeWrapper`.
forEachChild(fn) {
if (this.node.children != null) {
return this.node.children.forEach((function(_this) {
return function(childName) {
return _this.forEachChildOfType(childName, fn);
};
})(this));
return this.node.children.forEach((childName) => {
return this.forEachChildOfType(childName, fn);
});
}
};
}
NodeWrapper.prototype.forEachChildOfType = function(childName, fn) {
// Like `forEachChild`, but only
forEachChildOfType(childName, fn) {
var child, childNodes, children, index, results, wrappedChild;

@@ -59,9 +85,7 @@ children = this.node[childName];

}
};
}
NodeWrapper.prototype.markAll = function(varName, value) {
// Mark this node and all descendants with the given flag.
markAll(varName, value = true) {
var markCoffeeNode;
if (value == null) {
value = true;
}
markCoffeeNode = function(coffeeNode) {

@@ -75,9 +99,7 @@ if (coffeeNode.coffeeCoverage == null) {

return markCoffeeNode(this.node);
};
}
NodeWrapper.prototype.mark = function(varName, value) {
// Mark a node with a flag.
mark(varName, value = true) {
var base;
if (value == null) {
value = true;
}
if ((base = this.node).coffeeCoverage == null) {

@@ -87,17 +109,13 @@ base.coffeeCoverage = {};

return this.node.coffeeCoverage[varName] = value;
};
}
NodeWrapper.prototype.isMarked = function(varName, value) {
isMarked(varName, value = true) {
var ref;
if (value == null) {
value = true;
}
return ((ref = this.node.coffeeCoverage) != null ? ref[varName] : void 0) === value;
};
}
NodeWrapper.prototype.child = function(name, index) {
// Returns a NodeWrapper for the given child. This only works if the child is not an array
// (e.g. `Block.expressions`)
child(name, index = null) {
var child;
if (index == null) {
index = null;
}
child = this.node[name];

@@ -117,5 +135,7 @@ if (!child) {

}
};
}
NodeWrapper.prototype._fixChildIndex = function() {
// `@childIndex` is a hint, since nodes can move around. This updateds @childIndex if
// necessary.
_fixChildIndex() {
var childIndex;

@@ -133,5 +153,7 @@ if (!_.isArray(this.parent.node[this.childName])) {

}
};
}
NodeWrapper.prototype.next = function() {
// Returns this node's next sibling, or null if this node has no next sibling.
next() {
var nextNode, ref;

@@ -148,22 +170,35 @@ if ((ref = this.parent.type) !== 'Block' && ref !== 'Obj') {

}
};
}
NodeWrapper.prototype._insertBeforeIndex = function(childName, index, csSource) {
_insertBeforeIndex(childName, index, csSource) {
var compiled;
assert(_.isArray(this.node[childName]), (this.toString()) + " -> " + childName);
assert(_.isArray(this.node[childName]), `${this.toString()} -> ${childName}`);
compiled = compile(csSource, this.node);
return this.node[childName].splice(index, 0, compiled);
};
}
NodeWrapper.prototype.insertBefore = function(csSource) {
// Insert a new node before this node (only works if this node is in an array-based attribute,
// like `Block.expressions`.)
// Note that generated nodes will have the `node.coffeeCoverage.generated` flag set,
// and will be skipped when instrumenting code.
insertBefore(csSource) {
this._fixChildIndex();
return this.parent._insertBeforeIndex(this.childName, this.childIndex, csSource);
};
}
NodeWrapper.prototype.insertAfter = function(csSource) {
insertAfter(csSource) {
this._fixChildIndex();
return this.parent._insertBeforeIndex(this.childName, this.childIndex + 1, csSource);
};
}
NodeWrapper.prototype.insertAtStart = function(childName, csSource) {
// Insert a chunk of code at the start of a child of this node. E.g. if this is a Block,
// then `insertAtStart('expressions', 'console.log "foo"'')` would add a `console.log`
// statement to the start of the Block's expressions list.
// Note that generated nodes will have the `node.coffeeCoverage.generated` flag set,
// and will be skipped when instrumenting code.
insertAtStart(childName, csSource) {
var child, ref;

@@ -180,25 +215,24 @@ child = this.node[childName];

} else if (!child) {
// This will generate a 'Block'
return this.node[childName] = compile(csSource, this.node);
} else {
throw new Error("Don't know how to insert statement into " + this.type + "." + childName + ": " + this.type[childName]);
throw new Error(`Don't know how to insert statement into ${this.type}.${childName}: ${this.type[childName]}`);
}
};
}
NodeWrapper.prototype.toString = function() {
toString() {
var answer, ref;
answer = '';
if (this.childName) {
answer += this.childName + "[" + this.childIndex + "]:";
answer += `${this.childName}[${this.childIndex}]:`;
}
answer += this.type;
if (this.node.locationData != null) {
answer += " (" + (((ref = this.node.locationData) != null ? ref.first_line : void 0) + 1) + ":" + (this.node.locationData.first_column + 1) + ")";
answer += ` (${((ref = this.node.locationData) != null ? ref.first_line : void 0) + 1}:${this.node.locationData.first_column + 1})`;
}
return answer;
};
}
return NodeWrapper;
};
})();
forNodeAndChildren = function(node, fn) {

@@ -214,4 +248,7 @@ fn(node);

forNodeAndChildren(compiled, function(n) {
// Fix up location data for each instrumented line. Make these all 0-length,
// so we don't have to rewrite the location data for all the non-generated
// nodes in the tree.
n.locationData = {
first_line: line - 1,
first_line: line - 1, // -1 because `line` is 1-based
first_column: 0,

@@ -221,2 +258,4 @@ last_line: line - 1,

};
// Mark each node as coffee-coverage generated, so we won't try to instrument our
// instrumented lines.
if (n.coffeeCoverage == null) {

@@ -223,0 +262,0 @@ n.coffeeCoverage = {};

@@ -1,4 +0,4 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
var CompiledCache, StringStream, _, coffeeCoverage, excludeFile, fs, getRelativeFilename, mkdirs, path, ref;
var CompiledCache, StringStream, _, coffeeCoverage, excludeFile, fs, getRelativeFilename, mkdirs, path;

@@ -15,22 +15,65 @@ path = require('path');

StringStream = (function() {
function StringStream() {
StringStream = class StringStream {
constructor() {
this.data = "";
}
StringStream.prototype.write = function(data) {
write(data) {
return this.data += data;
};
}
return StringStream;
};
})();
({mkdirs, excludeFile, getRelativeFilename} = require('./utils/helpers'));
ref = require('./utils/helpers'), mkdirs = ref.mkdirs, excludeFile = ref.excludeFile, getRelativeFilename = ref.getRelativeFilename;
// Register coffeeCoverage to automatically process '.coffee', '.litcoffee', '.coffee.md' and '._coffee' files.
module.exports = function(options) {
// Note if you're using this in conjunction with
// [streamlinejs](https://github.com/Sage/streamlinejs), you *must* call this function
// after calling `streamline.register()`, otherwise by the time we get the source the
// file will already have been compiled.
// Parameters:
// * `options.instrumentor` is the name of the instrumentor to use (see `INSTURMENTORS`.)
// All options passed in will be passed along to the instrumentor implementation, so
// instrumentor-specific options may be added to `options` as well.
// * `options.coverageVar` gives the name of the global variable to use to store
// coverage data in. The default coverage variable depends on the `options.instrumentor` used.
// * `options.exclude` is an array of files and directories to ignore. For example, ['/test'] would
// ignore all files in the test folder. Defaults to [].
// * `options.basePath` the root folder for your project. If provided, then all excludes will be
// evaluated relative to this base path. For example, if `options.exclude` is `['/a/b']`, and
// `options.basePath` is "/Users/jwalton/myproject", then this will prevent
// coffeeCoverage from traversing "/Users/jwalton/myproject/a/b". Some instrumentor
// implementations may strip the `basePath` for readability.
// * `options.initAll` - If true, then coffeeCoverage will recursively walk through all
// subdirectories of `options.basePath` and gather line number information for all CoffeeScript
// files found. This way even files which are not `require`d at any point during your test will
// still be instrumented and reported on.
// * `options.writeOnExit` - A file to write a JSON coverage file to on completion. This will
// stringify the variable set in `options.coverageVar` and write it to disk.
// * `options.streamlinejs` - (deprecated) Enable support for streamlinejs < 1.x. You can either pass `true`
// here, or a set of options to pass on to
// [transform](https://github.com/Sage/streamlinejs/blob/e10906d6cd/lib/callbacks/transform.md).
// * `options.postProcessors` - New way of compiling source after it has been coffee compiled and instrumented. Can apply
// something like the streamline compiler. This puts all the power in the consumer's hands and allows for more
// flexibility. Pass an array of objects of the form `{ext: '._coffee', fn: (compiled, fileName) -> }`. The `fn` will
// be passed the coffee compiled/instrumented source, and the full path to the file.
// * `options.cachePath` - A folder to write instrumented code to. Subsequent runs will load
// instrumented code from the cache if the source files haven't changed. This is recommended
// when using `options.streamlinejs`.
// e.g. `coffeeCoverage.register {path: 'abbr', basePath: "#{__dirname}/.." }`
module.exports = function(options = {}) {
var compiledCache, coverage, defaults, err, instrumentFile, instrumentorClass, module, origStreamineCoffeeHandler, replaceHandler, streamlineTransform;
if (options == null) {
options = {};
}
// Clone options so we don't modify the original.
options = _.defaults({}, options, {

@@ -43,9 +86,10 @@ instrumentor: 'jscoverage',

writeOnExit: null,
streamlinejs: false,
streamlinejs: false, // deprecated
postProcessors: [],
cachePath: null
});
// Add default options from the instrumentor.
instrumentorClass = coffeeCoverage.INSTRUMENTORS[options.instrumentor];
if (!instrumentorClass) {
throw new Error(("Unknown instrumentor: " + options.instrumentor + ". ") + ("Valid options are " + (Object.keys(coffeeCoverage.INSTRUMENTORS))));
throw new Error(`Unknown instrumentor: ${options.instrumentor}. ` + `Valid options are ${Object.keys(coffeeCoverage.INSTRUMENTORS)}`);
}

@@ -66,2 +110,3 @@ if (instrumentorClass.getDefaultOptions != null) {

if (options.basePath && options.initAll) {
// Recursively instrument everything in the base path to generate intialization data.
options.initFileStream = new StringStream();

@@ -94,2 +139,4 @@ coverage.instrumentDirectory(options.basePath, null, options);

replaceHandler(".coffee.md");
// legacy option for `streamlinejs` < 1.x.
// NOTE: deprecated. Use `options.postProcessors` instead.
if (options.streamlinejs) {

@@ -122,9 +169,7 @@ console.warn("\noptions.streamlinejs is deprecated. Please use options.postProcessors\n");

}
// setup any custom post processors
if (_.isArray(options.postProcessors) && options.postProcessors.length) {
options.postProcessors.forEach(function(processorOpts) {
options.postProcessors.forEach(function(processorOpts = {}) {
var ext, fn, originalHandler;
if (processorOpts == null) {
processorOpts = {};
}
ext = processorOpts.ext, fn = processorOpts.fn;
({ext, fn} = processorOpts);
if (!(_.isString(ext) && _.isFunction(fn))) {

@@ -152,3 +197,3 @@ return;

return process.on('exit', function() {
var dirName, ref1;
var dirName, ref;
try {

@@ -160,3 +205,3 @@ dirName = path.dirname(options.writeOnExit);

err = error;
return console.error("Failed to write coverage data", (ref1 = err.stack) != null ? ref1 : err);
return console.error("Failed to write coverage data", (ref = err.stack) != null ? ref : err);
}

@@ -163,0 +208,0 @@ });

@@ -1,3 +0,6 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
// Visitor which looks for pragma directives for skipping coverage, and marks coffeescript nodes
// to be skipped.
var NodeWrapper, PRAGMAS, PRAGMA_PREFIX, SkipVisitor, _;

@@ -13,67 +16,129 @@

{
// '!pragma coverage-skip-next', 'istanbul ignore next'
// Mark the next node and all descendants as `skip`.
regex: /^!pragma\s+coverage-skip-next$/,
istanbulRegex: /^istanbul\s+ignore\s+next$/,
fn: function(self, node, match, options) {
var next;
if (options == null) {
options = {};
fn: function(self,
node,
match,
options = {}) {
var origNode;
origNode = node;
if (node.type === "Value") {
if (node.parent.type === "Assign" || node.parent.type === "Switch" || node.parent.type === "Class") {
node = node.parent;
} else {
node = node.parent.parent;
}
} else if (node.type !== "If") {
node = node.parent;
}
next = self._getNext(node, match);
return next.markAll('skip', true);
if (!node) {
throw new Error(`Pragma '${match[0]}' at ${self._toLocString(origNode)} has no next statement`);
}
return node.markAll('skip',
true);
}
}, {
},
{
// '!pragma coverage-skip-block'
regex: /^!pragma\s+coverage-skip-block$/,
fn: function(self, node, match, options) {
var ifBody, ifNode, parent, ref;
if (options == null) {
options = {};
}
parent = node.parent;
parent.markAll('skip', true);
if (parent.type !== 'Block') {
/* !pragma coverage-skip-block */
throw new Error(("Pragma '" + match[0] + "' at " + (this._toLocString(node)) + " is not ") + "child of a Block (how did you even do this!?)");
}
if (((ref = parent.parent) != null ? ref.type : void 0) === 'If') {
fn: function(self,
node,
match,
options = {}) {
var ifBody,
ifNode,
parent;
parent = node.parent.parent.parent;
parent.markAll('skip',
true);
if (parent.parent.type === 'If') {
ifBody = parent;
ifNode = parent.parent;
if (ifBody.childName === 'body') {
return ifNode.mark('skipIf', true);
return ifNode.mark('skipIf',
true);
} else {
return ifNode.mark('skipElse', true);
return ifNode.mark('skipElse',
true);
}
}
}
}, {
},
{
// '!pragma no-coverage-next'
// Mark the next node and all descendants as `noCoverage`.
regex: /^!pragma\s+no-coverage-next$/,
fn: function(self, node, match, options) {
var next;
if (options == null) {
options = {};
fn: function(self,
node,
match,
options = {}) {
if (node.type === "Value") {
if (node.parent.type === "Assign" || node.parent.type === "Switch" || node.parent.type === "Class") {
node = node.parent;
} else {
node = node.parent.parent;
}
} else if (node.type !== "If") {
node = node.parent;
}
next = self._getNext(node, match);
return next.markAll('noCoverage', true);
return node.markAll('noCoverage',
true);
}
}, {
},
{
// 'istanbul ignore if'
// Must be before an `If` statement. Mark the `If` as `skipIf`, and mark all children in
// the `body` as `skip`.
istanbulRegex: /^istanbul\s+ignore\s+if$/,
fn: function(self, node, match, options) {
var ifNode, ref;
if (options == null) {
options = {};
fn: function(self,
node,
match,
options = {}) {
var ifNode,
ref,
ref1;
if (node.type === "IdentifierLiteral") {
return;
}
ifNode = self._getNext(node, match, 'If');
ifNode.mark('skipIf', true);
return (ref = ifNode.child('body')) != null ? ref.markAll('skip', true) : void 0;
if (node.type === "Value" && ((ref = node.node.base.constructor) != null ? ref.name : void 0) === "PassthroughLiteral") {
throw new Error(`Pragma '${match[0]}' at ${self._toLocString(node)} has no next statement`);
}
ifNode = self.getIfNode(node,
match);
ifNode.mark('skipIf',
true);
return (ref1 = ifNode.child('body')) != null ? ref1.markAll('skip',
true) : void 0;
}
}, {
},
{
// 'istanbul ignore next'
// Must be before an `If` statement. Mark the `If` as `skipElse`, and mark all children in
// the `elseBody` as `skip`.
istanbulRegex: /^istanbul\s+ignore\s+else$/,
fn: function(self, node, match, options) {
var ifNode, ref;
if (options == null) {
options = {};
fn: function(self,
node,
match,
options = {}) {
var ifNode,
ref,
ref1;
if (node.type === "IdentifierLiteral") {
return;
}
ifNode = self._getNext(node, match, 'If');
ifNode.mark('skipElse', true);
return (ref = ifNode.child('elseBody')) != null ? ref.markAll('skip', true) : void 0;
if (node.type === "Value" && ((ref = node.node.base.constructor) != null ? ref.name : void 0) === "PassthroughLiteral") {
throw new Error(`Pragma '${match[0]}' at ${self._toLocString(node)} has no next statement`);
}
ifNode = self.getIfNode(node,
match);
ifNode.mark('skipElse',
true);
return (ref1 = ifNode.child('elseBody')) != null ? ref1.markAll('skip',
true) : void 0;
}

@@ -83,62 +148,55 @@ }

module.exports = SkipVisitor = (function() {
function SkipVisitor(fileName) {
module.exports = SkipVisitor = class SkipVisitor {
constructor(fileName) {
this.fileName = fileName;
}
SkipVisitor.prototype.visitComment = function(node) {
var comment, found, ref, ref1;
comment = (ref = (ref1 = node.node.comment) != null ? ref1.trim().toLowerCase() : void 0) != null ? ref : '';
found = false;
visitComment(node) {
var comment, ref;
if (node.node.comments.visited) {
return;
}
comment = (ref = node.node.comments[0].content.trim().toLowerCase()) != null ? ref : '';
if (_.startsWith(comment, PRAGMA_PREFIX)) {
return PRAGMAS.filter(function(pragma) {
PRAGMAS.filter(function(pragma) {
return pragma.regex != null;
}).forEach((function(_this) {
return function(pragma) {
var match;
if (match = comment.match(pragma.regex)) {
return pragma.fn(_this, node, match, _this.options);
}
};
})(this));
}).forEach((pragma) => {
var match;
if (match = comment.match(pragma.regex)) {
return pragma.fn(this, node, match, this.options);
}
});
} else if (_.startsWith(comment, 'istanbul')) {
return PRAGMAS.filter(function(pragma) {
PRAGMAS.filter(function(pragma) {
return pragma.istanbulRegex != null;
}).forEach((function(_this) {
return function(pragma) {
var match;
if (match = comment.match(pragma.istanbulRegex)) {
return pragma.fn(_this, node, match, _this.options);
}
};
})(this));
}).forEach((pragma) => {
var match;
if (match = comment.match(pragma.istanbulRegex)) {
return pragma.fn(this, node, match, this.options);
}
});
}
};
return node.node.comments.visited = true;
}
SkipVisitor.prototype._toLocString = function(node) {
return this.fileName + " (" + (node.locationData.first_line + 1) + ":" + (node.locationData.first_column + 1) + ")";
};
_toLocString(node) {
return `${this.fileName} (${node.locationData.first_line + 1}:${node.locationData.first_column + 1})`;
}
SkipVisitor.prototype._getNext = function(node, match, type) {
var next;
if (type == null) {
type = null;
getIfNode(node, match) {
var ref, ref1, ref2, ref3, ref4;
if (node.type === "If") {
return node;
}
next = node.next();
while ((next != null ? next.type : void 0) === 'Comment') {
next = next.next();
if (((ref = node.parent) != null ? (ref1 = ref.parent) != null ? ref1.type : void 0 : void 0) === "If") {
return node.parent.parent;
}
if (next == null) {
throw new Error("Pragma '" + match[0] + "' at " + (this._toLocString(node)) + " has no next statement");
if (((ref2 = node.parent) != null ? (ref3 = ref2.parent) != null ? (ref4 = ref3.parent) != null ? ref4.type : void 0 : void 0 : void 0) === "If") {
return node.parent.parent.parent;
}
if ((type != null) && next.type !== type) {
throw new Error("Statement after pragma '" + match[0] + "' at " + (this._toLocString(node)) + " is not of type " + type);
}
return next;
};
throw new Error(`Statement after pragma '${match[0]}' at ${this._toLocString(node)} is not of type If`);
}
return SkipVisitor;
};
})();
}).call(this);

@@ -1,3 +0,5 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
// Takes the contents of a file and returns an array of lines.
// `source` is a string containing an entire file.
exports.fileToLines = function(source) {

@@ -9,2 +11,3 @@ var dataWithFixedLfs;

// Where `a` and `b` are `{line, column}` objects, return -1 if a < b, 0 if a == b, 1 is a > b.
exports.compareLocations = function(a, b) {

@@ -24,2 +27,3 @@ if (a.line < b.line) {

// Given an array of `{line, column}` objects, returns the one that occurs earliest in the document.
exports.minLocation = function(locations) {

@@ -26,0 +30,0 @@ var min;

@@ -1,5 +0,5 @@

// Generated by CoffeeScript 1.12.4
// Generated by CoffeeScript 2.0.2
(function() {
var EXTENSIONS, _, assert, fs, minimatch, path, statFile,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
indexOf = [].indexOf;

@@ -14,3 +14,3 @@ assert = require('assert');

EXTENSIONS = require('../constants').EXTENSIONS;
({EXTENSIONS} = require('../constants'));

@@ -23,2 +23,3 @@ minimatch = require('minimatch');

// Get details about a file. Returns a fs.Stats object, or null if the file does not exist.
exports.statFile = statFile = function(file) {

@@ -31,4 +32,8 @@ if (!fs.existsSync(file)) {

// Creates the directory supplied by `dirPath`, creating any intermediate directories as
// required. For example, `mkdirs('a/b/c')` might create the directory 'a', then 'a/b', then
// 'a/b/c'.
exports.mkdirs = function(dirPath, mode) {
var currentPath, i, len, pathElement, pathElements, stat;
// Short-circuit if path already exists
if (!statFile(dirPath)) {

@@ -45,5 +50,6 @@ pathElements = dirPath.split(path.sep);

if (stat && !stat.isDirectory()) {
throw new CoverageError("Can't create directory " + currentPath + ": file already exists.");
throw new CoverageError(`Can't create directory ${currentPath}: file already exists.`);
}
if (!stat) {
// Create the directory
fs.mkdirSync(currentPath, mode);

@@ -57,2 +63,4 @@ }

// Return the relative path for the file from the basePath. Returns file name
// if the file is not relative to basePath.
exports.getRelativeFilename = function(basePath, fileName) {

@@ -65,2 +73,6 @@ if ((basePath != null) && _.startsWith(fileName, basePath)) {

// Return true if we should exclude a file.
// `fileName` should be a resolved path (e.g. /users/jwalton/projects/foo/src/blah.coffee)
exports.excludeFile = function(fileName, options) {

@@ -79,4 +91,6 @@ var basePath, component, components, exclude, excludePath, excluded, i, j, k, len, len1, len2, ref, relativeFilename, resolvedFileName;

if (relativeFilename === fileName) {
// Only instrument files that are inside the project.
excluded = true;
}
// For each exclude value try to use it as a pattern to exclude files
exclude.map(function(pattern) {

@@ -97,3 +111,4 @@ if (minimatch(relativeFilename, pattern)) {

excludePath = exclude[j];
if (_.startsWith("/" + relativeFilename, excludePath) || _.startsWith(relativeFilename, excludePath)) {
// Allow `exlude` paths to start with /s or not.
if (_.startsWith(`/${relativeFilename}`, excludePath) || _.startsWith(relativeFilename, excludePath)) {
excluded = true;

@@ -118,2 +133,4 @@ }

// Takes in a string, and returns a quoted string with any \s and "s in the string escaped to be
// JS friendly.
exports.toQuotedString = function(string) {

@@ -120,0 +137,0 @@ var answer;

@@ -15,3 +15,3 @@ {

],
"version": "2.0.1",
"version": "3.0.0",
"author": "Benbria (http://www.benbria.com/)",

@@ -26,3 +26,4 @@ "contributors": [

"Dmitry Petrov (https://github.com/can3p)",
"Devon Govett (https://github.com/devongovett)"
"Devon Govett (https://github.com/devongovett)",
"Emanuele Tamponi (https://glitch.com/@etamponi)"
],

@@ -43,3 +44,3 @@ "license": "MIT",

"argparse": "^1.0.2",
"coffee-script": ">=1.6.2",
"coffeescript": "^2.0.2",
"lodash": "^4.14.0",

@@ -64,6 +65,3 @@ "minimatch": "^3.0.2",

"distclean": "npm run clean && rm -rf node_modules"
},
"engines": {
"node": ">=0.8.0"
}
}

@@ -18,3 +18,3 @@ Istanbul and JSCoverage-style instrumentation for CoffeeScript files.

* Native coffee-script instrumentation - [not based on source maps](./docs/comparison-to-ibrik.md)
* Native coffeescript instrumentation - [not based on source maps](./docs/comparison-to-ibrik.md)
* Conditional instrumentation with [pragmas](./docs/pragmas.md)

@@ -38,3 +38,3 @@ * Support for [nyc](./docs/HOWTO-nyc.md) style instrumentation

$ npm install --save-dev istanbul
$ mocha --recursive --compilers coffee:coffee-script/register --require coffee-coverage/register-istanbul test
$ mocha --recursive --compilers coffee:coffeescript/register --require coffee-coverage/register-istanbul test

@@ -41,0 +41,0 @@ $ ./node_modules/.bin/istanbul report

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc