coffee-coverage
Advanced tools
Comparing version 2.0.1 to 3.0.0
# 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 @@ |
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
129371
2023
9
38
+ Addedcoffeescript@^2.0.2
+ Addedcoffeescript@2.7.0(transitive)
- Removedcoffee-script@>=1.6.2
- Removedcoffee-script@1.12.7(transitive)