Add code coverage modules

This commit is contained in:
Timothy Warren 2014-10-24 10:28:45 -04:00
parent 41ef3ebeec
commit 48f3597b19
1399 changed files with 483712 additions and 0 deletions

1
node_modules/.bin/grunt-istanbul generated vendored Symbolic link
View File

@ -0,0 +1 @@
../grunt-istanbul/bin/grunt-istanbul

22
node_modules/grunt-contrib-clean/LICENSE-MIT generated vendored Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2014 Tim Branyen, contributors
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

104
node_modules/grunt-contrib-clean/README.md generated vendored Normal file
View File

@ -0,0 +1,104 @@
# grunt-contrib-clean v0.6.0 [![Build Status: Linux](https://travis-ci.org/gruntjs/grunt-contrib-clean.png?branch=master)](https://travis-ci.org/gruntjs/grunt-contrib-clean) <a href="https://ci.appveyor.com/project/gruntjs/grunt-contrib-clean"><img src="https://ci.appveyor.com/api/projects/status/li28411ceq3n833d/branch/master" alt="Build Status: Windows" height="18" /></a>
> Clean files and folders.
## Getting Started
This plugin requires Grunt `~0.4.0`
If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
```shell
npm install grunt-contrib-clean --save-dev
```
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
```js
grunt.loadNpmTasks('grunt-contrib-clean');
```
*This plugin was designed to work with Grunt 0.4.x. If you're still using grunt v0.3.x it's strongly recommended that [you upgrade](http://gruntjs.com/upgrading-from-0.3-to-0.4), but in case you can't please use [v0.3.2](https://github.com/gruntjs/grunt-contrib-clean/tree/grunt-0.3-stable).*
## Clean task
_Run this task with the `grunt clean` command._
Task targets, files and options may be specified according to the grunt [Configuring tasks](http://gruntjs.com/configuring-tasks) guide.
*Due to the destructive nature of this task, always be cautious of the paths you clean.*
### Options
#### force
Type: `Boolean`
Default: false
This overrides this task from blocking deletion of folders outside current working dir (CWD). Use with caution.
#### no-write
Type: `Boolean`
Default: false
Will log messages of what would happen if the task was ran but doesn't actually delete the files.
### Usage Examples
There are three formats you can use to run this task.
#### Short
```js
clean: ["path/to/dir/one", "path/to/dir/two"]
```
#### Medium (specific targets with global options)
```js
clean: {
build: ["path/to/dir/one", "path/to/dir/two"],
release: ["path/to/another/dir/one", "path/to/another/dir/two"]
},
```
#### Long (specific targets with per target options)
```js
clean: {
build: {
src: ["path/to/dir/one", "path/to/dir/two"]
}
}
```
##### Skipping Files
```js
// Deletes all .js files, but skips min.js files
clean: {
js: ["path/to/dir/*.js", "!path/to/dir/*.min.js"]
}
```
"Compact" and "Files Array" formats support a few [additional properties](http://gruntjs.com/configuring-tasks#files)
which help you deal with hidden files, process dynamic mappings and so on.
## Release History
* 2014-07-27v0.6.0Less verbose output. README updates.
* 2013-07-15v0.5.0Use rimraf directly, version 2.2.1 to fix issue on Windows. Add no-write option to mimic grunt.file.delete behavior.
* 2013-04-16v0.4.1Check if file exists to avoid trying to delete a non-existent file.
* 2013-02-15v0.4.0First official release for Grunt 0.4.0.
* 2013-01-18v0.4.0rc6Updating grunt/gruntplugin dependencies to rc6. Changing in-development grunt/gruntplugin dependency versions from tilde version ranges to specific versions.
* 2013-01-09v0.4.0rc5Updating to work with grunt v0.4.0rc5. Switching to this.filesSrc api.
* 2012-12-07v0.4.0aConversion to grunt v0.4 conventions. Remove node v0.6 and grunt v0.3 support. Add force option to bypass CWD check.
* 2012-09-23v0.3.0Options no longer accepted from global config key.
* 2012-09-10v0.2.0Refactored from grunt-contrib into individual repo.
---
Task submitted by [Tim Branyen](http://tbranyen.com/)
*This file was generated on Sun Jul 27 2014 17:36:23.*

View File

@ -0,0 +1 @@
../rimraf/bin.js

View File

@ -0,0 +1,6 @@
# Authors sorted by whether or not they're me.
Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)
Wayne Larsen <wayne@larsen.st> (http://github.com/wvl)
ritch <skawful@gmail.com>
Marcel Laverdet
Yosef Dinerstein <yosefd@microsoft.com>

View File

@ -0,0 +1,23 @@
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,30 @@
`rm -rf` for node.
Install with `npm install rimraf`, or just drop rimraf.js somewhere.
## API
`rimraf(f, callback)`
The callback will be called with an error if there is one. Certain
errors are handled for you:
* Windows: `EBUSY` and `ENOTEMPTY` - rimraf will back off a maximum of
`opts.maxBusyTries` times before giving up.
* `ENOENT` - If the file doesn't exist, rimraf will return
successfully, since your desired outcome is already the case.
## rimraf.sync
It can remove stuff synchronously, too. But that's not so good. Use
the async API. It's better.
## CLI
If installed with `npm install rimraf -g` it can be used as a global
command `rimraf <path>` which is useful for cross platform support.
## mkdirp
If you need to create a directory recursively, check out
[mkdirp](https://github.com/substack/node-mkdirp).

33
node_modules/grunt-contrib-clean/node_modules/rimraf/bin.js generated vendored Executable file
View File

@ -0,0 +1,33 @@
#!/usr/bin/env node
var rimraf = require('./')
var help = false
var dashdash = false
var args = process.argv.slice(2).filter(function(arg) {
if (dashdash)
return !!arg
else if (arg === '--')
dashdash = true
else if (arg.match(/^(-+|\/)(h(elp)?|\?)$/))
help = true
else
return !!arg
});
if (help || args.length === 0) {
// If they didn't ask for help, then this is not a "success"
var log = help ? console.log : console.error
log('Usage: rimraf <path>')
log('')
log(' Deletes all files and folders at "path" recursively.')
log('')
log('Options:')
log('')
log(' -h, --help Display this usage info')
process.exit(help ? 0 : 1)
} else {
args.forEach(function(arg) {
rimraf.sync(arg)
})
}

View File

@ -0,0 +1,73 @@
{
"name": "rimraf",
"version": "2.2.8",
"main": "rimraf.js",
"description": "A deep deletion module for node (like `rm -rf`)",
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me",
"url": "http://blog.izs.me/"
},
"license": {
"type": "MIT",
"url": "https://github.com/isaacs/rimraf/raw/master/LICENSE"
},
"repository": {
"type": "git",
"url": "git://github.com/isaacs/rimraf.git"
},
"scripts": {
"test": "cd test && bash run.sh"
},
"bin": {
"rimraf": "./bin.js"
},
"contributors": [
{
"name": "Isaac Z. Schlueter",
"email": "i@izs.me",
"url": "http://blog.izs.me"
},
{
"name": "Wayne Larsen",
"email": "wayne@larsen.st",
"url": "http://github.com/wvl"
},
{
"name": "ritch",
"email": "skawful@gmail.com"
},
{
"name": "Marcel Laverdet"
},
{
"name": "Yosef Dinerstein",
"email": "yosefd@microsoft.com"
}
],
"bugs": {
"url": "https://github.com/isaacs/rimraf/issues"
},
"homepage": "https://github.com/isaacs/rimraf",
"_id": "rimraf@2.2.8",
"_shasum": "e439be2aaee327321952730f99a8929e4fc50582",
"_from": "rimraf@>=2.2.1 <2.3.0",
"_npmVersion": "1.4.10",
"_npmUser": {
"name": "isaacs",
"email": "i@izs.me"
},
"maintainers": [
{
"name": "isaacs",
"email": "i@izs.me"
}
],
"dist": {
"shasum": "e439be2aaee327321952730f99a8929e4fc50582",
"tarball": "http://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
"readme": "ERROR: No README data found!"
}

View File

@ -0,0 +1,248 @@
module.exports = rimraf
rimraf.sync = rimrafSync
var assert = require("assert")
var path = require("path")
var fs = require("fs")
// for EMFILE handling
var timeout = 0
exports.EMFILE_MAX = 1000
exports.BUSYTRIES_MAX = 3
var isWindows = (process.platform === "win32")
function defaults (options) {
var methods = [
'unlink',
'chmod',
'stat',
'rmdir',
'readdir'
]
methods.forEach(function(m) {
options[m] = options[m] || fs[m]
m = m + 'Sync'
options[m] = options[m] || fs[m]
})
}
function rimraf (p, options, cb) {
if (typeof options === 'function') {
cb = options
options = {}
}
assert(p)
assert(options)
assert(typeof cb === 'function')
defaults(options)
if (!cb) throw new Error("No callback passed to rimraf()")
var busyTries = 0
rimraf_(p, options, function CB (er) {
if (er) {
if (isWindows && (er.code === "EBUSY" || er.code === "ENOTEMPTY") &&
busyTries < exports.BUSYTRIES_MAX) {
busyTries ++
var time = busyTries * 100
// try again, with the same exact callback as this one.
return setTimeout(function () {
rimraf_(p, options, CB)
}, time)
}
// this one won't happen if graceful-fs is used.
if (er.code === "EMFILE" && timeout < exports.EMFILE_MAX) {
return setTimeout(function () {
rimraf_(p, options, CB)
}, timeout ++)
}
// already gone
if (er.code === "ENOENT") er = null
}
timeout = 0
cb(er)
})
}
// Two possible strategies.
// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR
// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR
//
// Both result in an extra syscall when you guess wrong. However, there
// are likely far more normal files in the world than directories. This
// is based on the assumption that a the average number of files per
// directory is >= 1.
//
// If anyone ever complains about this, then I guess the strategy could
// be made configurable somehow. But until then, YAGNI.
function rimraf_ (p, options, cb) {
assert(p)
assert(options)
assert(typeof cb === 'function')
options.unlink(p, function (er) {
if (er) {
if (er.code === "ENOENT")
return cb(null)
if (er.code === "EPERM")
return (isWindows)
? fixWinEPERM(p, options, er, cb)
: rmdir(p, options, er, cb)
if (er.code === "EISDIR")
return rmdir(p, options, er, cb)
}
return cb(er)
})
}
function fixWinEPERM (p, options, er, cb) {
assert(p)
assert(options)
assert(typeof cb === 'function')
if (er)
assert(er instanceof Error)
options.chmod(p, 666, function (er2) {
if (er2)
cb(er2.code === "ENOENT" ? null : er)
else
options.stat(p, function(er3, stats) {
if (er3)
cb(er3.code === "ENOENT" ? null : er)
else if (stats.isDirectory())
rmdir(p, options, er, cb)
else
options.unlink(p, cb)
})
})
}
function fixWinEPERMSync (p, options, er) {
assert(p)
assert(options)
if (er)
assert(er instanceof Error)
try {
options.chmodSync(p, 666)
} catch (er2) {
if (er2.code === "ENOENT")
return
else
throw er
}
try {
var stats = options.statSync(p)
} catch (er3) {
if (er3.code === "ENOENT")
return
else
throw er
}
if (stats.isDirectory())
rmdirSync(p, options, er)
else
options.unlinkSync(p)
}
function rmdir (p, options, originalEr, cb) {
assert(p)
assert(options)
if (originalEr)
assert(originalEr instanceof Error)
assert(typeof cb === 'function')
// try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS)
// if we guessed wrong, and it's not a directory, then
// raise the original error.
options.rmdir(p, function (er) {
if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM"))
rmkids(p, options, cb)
else if (er && er.code === "ENOTDIR")
cb(originalEr)
else
cb(er)
})
}
function rmkids(p, options, cb) {
assert(p)
assert(options)
assert(typeof cb === 'function')
options.readdir(p, function (er, files) {
if (er)
return cb(er)
var n = files.length
if (n === 0)
return options.rmdir(p, cb)
var errState
files.forEach(function (f) {
rimraf(path.join(p, f), options, function (er) {
if (errState)
return
if (er)
return cb(errState = er)
if (--n === 0)
options.rmdir(p, cb)
})
})
})
}
// this looks simpler, and is strictly *faster*, but will
// tie up the JavaScript thread and fail on excessively
// deep directory trees.
function rimrafSync (p, options) {
options = options || {}
defaults(options)
assert(p)
assert(options)
try {
options.unlinkSync(p)
} catch (er) {
if (er.code === "ENOENT")
return
if (er.code === "EPERM")
return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er)
if (er.code !== "EISDIR")
throw er
rmdirSync(p, options, er)
}
}
function rmdirSync (p, options, originalEr) {
assert(p)
assert(options)
if (originalEr)
assert(originalEr instanceof Error)
try {
options.rmdirSync(p)
} catch (er) {
if (er.code === "ENOENT")
return
if (er.code === "ENOTDIR")
throw originalEr
if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")
rmkidsSync(p, options)
}
}
function rmkidsSync (p, options) {
assert(p)
assert(options)
options.readdirSync(p).forEach(function (f) {
rimrafSync(path.join(p, f), options)
})
options.rmdirSync(p, options)
}

View File

@ -0,0 +1,16 @@
#!/bin/bash
set -e
code=0
for i in test-*.js; do
echo -n $i ...
bash setup.sh
node $i
if [ -d target ]; then
echo "fail"
code=1
else
echo "pass"
fi
done
rm -rf target
exit $code

View File

@ -0,0 +1,47 @@
#!/bin/bash
set -e
files=10
folders=2
depth=4
target="$PWD/target"
rm -rf target
fill () {
local depth=$1
local files=$2
local folders=$3
local target=$4
if ! [ -d $target ]; then
mkdir -p $target
fi
local f
f=$files
while [ $f -gt 0 ]; do
touch "$target/f-$depth-$f"
let f--
done
let depth--
if [ $depth -le 0 ]; then
return 0
fi
f=$folders
while [ $f -gt 0 ]; do
mkdir "$target/folder-$depth-$f"
fill $depth $files $folders "$target/d-$depth-$f"
let f--
done
}
fill $depth $files $folders $target
# sanity assert
[ -d $target ]

View File

@ -0,0 +1,5 @@
var rimraf = require("../rimraf")
, path = require("path")
rimraf(path.join(__dirname, "target"), function (er) {
if (er) throw er
})

View File

@ -0,0 +1,3 @@
var rimraf = require("../rimraf")
, path = require("path")
rimraf.sync(path.join(__dirname, "target"))

88
node_modules/grunt-contrib-clean/package.json generated vendored Normal file
View File

@ -0,0 +1,88 @@
{
"name": "grunt-contrib-clean",
"description": "Clean files and folders.",
"version": "0.6.0",
"homepage": "https://github.com/gruntjs/grunt-contrib-clean",
"author": {
"name": "Grunt Team",
"url": "http://gruntjs.com/"
},
"repository": {
"type": "git",
"url": "git://github.com/gruntjs/grunt-contrib-clean.git"
},
"bugs": {
"url": "https://github.com/gruntjs/grunt-contrib-clean/issues"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/gruntjs/grunt-contrib-clean/blob/master/LICENSE-MIT"
}
],
"engines": {
"node": ">= 0.8.0"
},
"scripts": {
"test": "grunt test"
},
"dependencies": {
"rimraf": "~2.2.1"
},
"devDependencies": {
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-nodeunit": "~0.3.3",
"grunt-contrib-internal": "~0.4.10",
"grunt": "~0.4.0"
},
"peerDependencies": {
"grunt": "~0.4.0"
},
"keywords": [
"gruntplugin"
],
"files": [
"tasks",
"LICENSE-MIT"
],
"_id": "grunt-contrib-clean@0.6.0",
"dist": {
"shasum": "f532dba4b8212674c7c013e146bda6638b9048f6",
"tarball": "http://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.6.0.tgz"
},
"_from": "grunt-contrib-clean@*",
"_npmVersion": "1.3.26",
"_npmUser": {
"name": "vladikoff",
"email": "vlad@vladikoff.com"
},
"maintainers": [
{
"name": "tkellen",
"email": "tyler@sleekcode.net"
},
{
"name": "cowboy",
"email": "cowboy@rj3.net"
},
{
"name": "shama",
"email": "kyle@dontkry.com"
},
{
"name": "vladikoff",
"email": "vlad@vladikoff.com"
},
{
"name": "sindresorhus",
"email": "sindresorhus@gmail.com"
},
{
"name": "jmeas",
"email": "jellyes2@gmail.com"
}
],
"directories": {},
"_shasum": "f532dba4b8212674c7c013e146bda6638b9048f6",
"_resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.6.0.tgz"
}

59
node_modules/grunt-contrib-clean/tasks/clean.js generated vendored Normal file
View File

@ -0,0 +1,59 @@
/*
* grunt-contrib-clean
* http://gruntjs.com/
*
* Copyright (c) 2014 Tim Branyen, contributors
* Licensed under the MIT license.
*/
'use strict';
var rimraf = require('rimraf');
module.exports = function(grunt) {
function clean(filepath, options) {
if (!grunt.file.exists(filepath)) {
return false;
}
// Only delete cwd or outside cwd if --force enabled. Be careful, people!
if (!options.force) {
if (grunt.file.isPathCwd(filepath)) {
grunt.verbose.error();
grunt.fail.warn('Cannot delete the current working directory.');
return false;
} else if (!grunt.file.isPathInCwd(filepath)) {
grunt.verbose.error();
grunt.fail.warn('Cannot delete files outside the current working directory.');
return false;
}
}
try {
// Actually delete. Or not.
if (!options['no-write']) {
rimraf.sync(filepath);
}
grunt.verbose.writeln((options['no-write'] ? 'Not actually cleaning ' : 'Cleaning ') + filepath + '...');
} catch (e) {
grunt.log.error();
grunt.fail.warn('Unable to delete "' + filepath + '" file (' + e.message + ').', e);
}
}
grunt.registerMultiTask('clean', 'Clean files and folders.', function() {
// Merge task-specific and/or target-specific options with these defaults.
var options = this.options({
force: grunt.option('force') === true,
'no-write': grunt.option('no-write') === true,
});
// Clean specified files / dirs.
this.filesSrc.forEach(function(filepath) {
clean(filepath, options);
});
grunt.log.ok(this.filesSrc.length + ' ' + grunt.util.pluralize(this.filesSrc.length, 'path/paths') + ' cleaned.');
});
};

5
node_modules/grunt-istanbul/.npmignore generated vendored Normal file
View File

@ -0,0 +1,5 @@
/.settings/
/node_modules/
/build/
/test/
.project

12
node_modules/grunt-istanbul/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,12 @@
language: node_js
node_js:
- 0.11
- 0.10
branches:
except:
- gh-pages
before_script:
- npm install -g grunt-cli

72
node_modules/grunt-istanbul/Gruntfile.js generated vendored Normal file
View File

@ -0,0 +1,72 @@
module.exports = function(grunt) {
'use strict';
var dateFormat = require('dateformat');
var tests = 'test/**/*_test.js';
var tasks = 'tasks/**/*.js';
var reportDir = 'build/reports/' + dateFormat(new Date(), 'yyyymmdd-HHMMss');
grunt.initConfig({
clean : [ 'build' ],
nodeunit : {
files : [ tests ]
},
watch : {
files : [ tasks, tests ],
tasks : 'default'
},
jshint : {
files : [ 'Gruntfile.js', tasks, tests ],
options : {
curly : true,
eqeqeq : true,
immed : true,
latedef : true,
newcap : true,
noarg : true,
sub : true,
undef : true,
boss : true,
eqnull : true,
node : true
},
globals : {}
},
instrument : {
files : tasks,
options : {
lazy : true,
basePath : 'build/instrument/'
}
},
reloadTasks : {
rootPath : 'build/instrument/tasks'
},
storeCoverage : {
options : {
dir : reportDir
}
},
makeReport : {
src : 'build/reports/**/*.json',
options : {
type : ['lcov', 'html'],
dir : reportDir,
print : 'detail'
}
}
});
// Load local tasks.
grunt.loadTasks('tasks');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-nodeunit');
grunt.registerTask('test', 'nodeunit');
grunt.registerTask('default', [ 'jshint', 'test' ]);
grunt.registerTask('cover', [ 'clean', 'instrument', 'reloadTasks', 'test',
'storeCoverage', 'makeReport' ]);
};

22
node_modules/grunt-istanbul/LICENSE-MIT generated vendored Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2012 taichi
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

104
node_modules/grunt-istanbul/README.md generated vendored Normal file
View File

@ -0,0 +1,104 @@
# grunt-istanbul [![Build Status](https://travis-ci.org/taichi/grunt-istanbul.png)](https://travis-ci.org/taichi/grunt-istanbul)
JavaScript codecoverage tool for Grunt
## Getting Started
This plugin requires Grunt ~0.4.1
Install this grunt plugin next to your project's [Gruntfile.js][getting_started] with: `npm install grunt-istanbul`
Then add this line to your project's `Gruntfile.js` gruntfile:
```javascript
grunt.loadNpmTasks('grunt-istanbul');
```
[grunt]: https://github.com/cowboy/grunt
[getting_started]: https://github.com/cowboy/grunt/blob/master/docs/getting_started.md
## Documentation
To use this grunt-istanbul plugin, register a grunt task to run the following:
1. Instrument your source code
2. Run your test suite against your instrumented source code
3. Store your coverage results
4. Make the report
For step 2, an environment variable can be used to determine which path to use for loading
the source code to run the tests against. For example, when you normally run your tests you
want them to point directly at your source code. But when you run your instanbul code coverage
task you want your tests to point at your instrumented source code. The `grunt-env` plugin
can be used for setting an environment variable in a grunt task. Here's an example solution
that solves this problem using `grunt-env` and `grunt-mocha-test`:
```javascript
// in Gruntfile.js
module.exports = function (grunt) {
grunt.initConfig({
env: {
coverage: {
APP_DIR_FOR_CODE_COVERAGE: '../test/coverage/instrument/app/'
}
},
instrument: {
files: 'app/*.js',
options: {
lazy: true,
basePath: 'test/coverage/instrument/'
}
},
mochaTest: {
options: {
reporter: 'spec'
},
src: ['test/*.js']
},
storeCoverage: {
options: {
dir: 'test/coverage/reports'
}
},
makeReport: {
src: 'test/coverage/reports/**/*.json',
options: {
type: 'lcov',
dir: 'test/coverage/reports',
print: 'detail'
}
}
});
grunt.registerTask('coverage', ['env:coverage', 'instrument', 'mochaTest',
'storeCoverage', 'makeReport']);
};
// require_helper.js
module.exports = function (path) {
return require((process.env.APP_DIR_FOR_CODE_COVERAGE || '../app/') + path);
};
// using requireHelper in a test
var requireHelper = require('../require_helper');
var formValidator = requireHelper('form_validator');
```
Also, checkout the example Gruntfile.js in this repo (note that you do not need to implement the
`reloadTasks` task in this example):
[Gruntfile.js](https://github.com/taichi/grunt-istanbul/blob/master/Gruntfile.js#69)
### more examples
* [Testing and Code Coverage With Node.js Apps](http://www.gregjopa.com/2014/02/testing-and-code-coverage-with-node-js-apps/)
* [gregjopa/express-app-testing-demo](https://github.com/gregjopa/express-app-testing-demo)
## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt][grunt].
## Release History
_(Nothing yet)_
## License
Copyright (c) 2014 taichi
Licensed under the MIT license.

2
node_modules/grunt-istanbul/bin/grunt-istanbul generated vendored Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env node
require('grunt').npmTasks('grunt-istanbul').cli();

1
node_modules/grunt-istanbul/node_modules/.bin/istanbul generated vendored Symbolic link
View File

@ -0,0 +1 @@
../istanbul/lib/cli.js

View File

@ -0,0 +1,139 @@
Changelog
---------
<table>
<tr>
<td>v0.2.16</td><td>Make YUI links https-always since relative links break local
filesystem use-case
</td>
</tr>
<tr>
<td>v0.2.15</td><td>make link protocols relative so they don't break on https connections
(thanks to @yasyf)
</td>
</tr>
<tr>
<td>v0.2.14</td><td>Fix hook to deal with non-string/ missing filenames
(thanks to @jason0x43), update dependencies
</td>
</tr>
<tr>
<td>v0.2.13</td><td>Add `--preload-sources` option to `cover` command to make
code not required by tests to appear in the coverage report.
</td>
</tr>
<tr>
<td>v0.2.12</td><td>Text summary as valid markdown, thanks to @smikes</td>
</tr>
<tr>
<td>v0.2.11</td><td>Allow source map generation, thanks to @jason0x43</td>
</tr>
<tr>
<td>v0.2.10</td><td>Add flag to handle sigints and dump coverage, thanks to @samccone</td>
</tr>
<tr>
<td>v0.2.9</td><td>Fix #202</td>
</tr>
<tr>
<tr>
<td>v0.2.8</td><td>Upgrade esprima</td>
</tr>
<tr>
<td>v0.2.7</td><td><ul>
<li>Upgrade esprima</li>
<li>Misc jshint fixes</li>
</ul></td>
</tr>
<tr>
<td>v0.2.6</td><td><ul>
<li>Revert bad commit for tree summarizer</li>
</ul></td>
</tr>
<tr>
<td>v0.2.5</td><td><ul>
<li>Add clover report, thanks to @bixdeng, @mpderbec</li>
<li>Fix cobertura report bug for relative paths, thanks to @jxiaodev</li>
<li>Run self-coverage on tests always</li>
<li>Fix tree summarizer when relative paths are involved, thanks to @Swatinem</li>
</ul></td>
</tr>
<tr>
<td>v0.2.4</td><td><ul>
<li>Fix line-split algo to handle Mac lin separators, thanks to @asifrc</li>
<li>Update README for quick intro to ignoring code for coverage, thanks to @gergelyke</li>
</ul></td>
</tr>
<tr>
<td>v0.2.3</td><td><ul>
<li>Add YAML config file. `istanbul help config` has more details</li>
<li>Support custom reporting thresholds using the `watermarks` section of the config file</li>
</ul></td>
</tr>
<tr><td>v0.2.2</td><td>update escodegen, handlebars and resolve dependency versions</td></tr>
<tr>
<td>v0.2.1</td><td><ul>
<li>Add ability to skip branches and other hard-to-test code using comments.
See <a href="https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md">the doc</a> for more details</li>
<li>Turn `util.error` into `console.error` for node 0.11 compatibility, thanks to @pornel</li>
</ul></td>
</tr>
<tr><td>v0.2.0</td><td><ul>
<li>Add --preserve-comments to instrumenter options, thanks to @arikon</li>
<li>Support 'use strict;' in file scope, thanks to @pornel</li>
</ul>
Up minor version due to the new way in which the global object is accessed.
This _should_ be backwards-compatible but has not been tested in the wild.
</td></tr>
<tr><td>v0.1.46</td><td>Fix #114</td></tr>
<tr><td>v0.1.45</td><td>Add teamcity reporter, thanks to @chrisgladd</td></tr>
<tr><td>v0.1.44</td><td>Fix inconsistency in processing empty switch with latest esprima, up deps</td></tr>
<tr><td>v0.1.43</td><td>Add colors to text report thanks to @runk</td></tr>
<tr><td>v0.1.42</td><td>fix #78: embed source regression introduced in v0.1.38. Fix broken test for this</td></tr>
<tr><td>v0.1.41</td><td>add json report to dump coverage object for certain use cases</td></tr>
<tr><td>v0.1.40</td><td>forward sourceStore from lcov to html report, pull request by @vojtajina</td></tr>
<tr><td>v0.1.39</td><td>add <source> tag to cobertura report, pull request by @jhansche</td></tr>
<tr><td>v0.1.38</td><td><ul>
<li>factor out AST instrumentation into own instrumentASTSync method</li>
<li>always set function declaration coverage stats to 1 since every such declaration is "executed" exactly one time by the compiler</li>
</ul></td></tr>
<tr><td>v0.1.37</td><td>--complete-copy flag contrib from @kami, correct strict mode semantics for instrumented functions</td></tr>
<tr><td>v0.1.36</td><td>real quiet when --print=none specified, add repo URL to package.json, add contributors</td></tr>
<tr><td>v0.1.35</td><td>accept cobertura contrib from @nbrownus, fix #52</td></tr>
<tr><td>v0.1.34</td><td>fix async reporting, update dependencies, accept html cleanup contrib from @mathiasbynens</td></tr>
<tr><td>v0.1.33</td><td>initialize global coverage object before running tests to workaround mocha leak detection</td></tr>
<tr><td>v0.1.32</td><td>Fix for null nodes in array expressions, add @unindented as contributor</td></tr>
<tr><td>v0.1.31</td><td>Misc internal fixes and test changes</td></tr>
<tr><td>v0.1.30</td><td>Write standard blurbs ("writing coverage object..." etc.) to stderr rather than stdout</td></tr>
<tr><td>v0.1.29</td><td>Allow --post-require-hook to be a module that can be `require`-d</td></tr>
<tr><td>v0.1.28</td><td>Add --post-require-hook switch to support use-cases similar to the YUI loader</td></tr>
<tr><td>v0.1.27</td><td>Add --hook-run-in-context switch to support RequireJS modules. Thanks to @millermedeiros for the pull request</td></tr>
<tr><td>v0.1.26</td><td>Add support for minimum uncovered unit for check-coverage. Fixes #25</td></tr>
<tr><td>v0.1.25</td><td>Allow for relative paths in the YUI loader hook</td></tr>
<tr><td>v0.1.24</td><td>Add lcov summaries. Fixes issue #20</td></tr>
<tr><td>v0.1.23</td><td>Add ability to save a baseline coverage file for the instrument command. Fixes issue #19</td></tr>
<tr><td>v0.1.22</td><td>Add signature attribute to cobertura method tags to fix NPE by the Hudson publisher</td></tr>
<tr><td>v0.1.21</td><td>Add cobertura XML report format; exprimental for now</td></tr>
<tr><td>v0.1.20</td><td>Fix HTML/ lcov report interface to be more customizable for middleware needs</td></tr>
<tr><td>v0.1.19</td><td>make all hooking non-destructive in that already loaded modules are never reloaded. Add self-test mode so that already loaded istanbul modules can be unloaded prior to hooking.</td></tr>
<tr><td>v0.1.18</td><td>Add option to hook in non-destructive mode; i.e. the require cache is not unloaded when hooking</td></tr>
<tr><td>v0.1.17</td><td>Export some more objects; undocumented for now</td></tr>
<tr><td>v0.1.16</td><td>Fix npm keywords for istanbul which expects an array of strings but was being fed a single string with keywords instead</td></tr>
<tr><td>v0.1.15</td><td>Add the 'check-coverage' command so that Istanbul can be used as a posttest script to enforce minimum coverage</td></tr>
<tr><td>v0.1.14</td><td>Expose the experimental YUI load hook in the interface</td></tr>
<tr><td>v0.1.13</td><td>Internal jshint cleanup, no features or fixes</td></tr>
<tr><td>v0.1.12</td><td>Give npm the README that was getting inadvertently excluded</td></tr>
<tr><td>v0.1.11</td><td>Merge pull request #14 for HTML tweaks. Thanks @davglass. Add @davglass and @nowamasa as contributors in `package.json`</td></tr>
<tr><td>v0.1.10</td><td>Fix to issue #12. Do not install `uncaughtException` handler and pass input error back to CLI using a callback as opposed to throwing.</td></tr>
<tr><td>v0.1.9</td><td>Attempt to create reporting directory again just before writing coverage in addition to initial creation</td></tr>
<tr><td>v0.1.8</td><td>Fix issue #11.</td></tr>
<tr><td>v0.1.7</td><td>Add text summary and detailed reporting available as --print [summary|detail|both|none]. summary is the default if nothing specified.</td></tr>
<tr><td>v0.1.6</td><td>Handle backslashes in the file path correctly in emitted code. Fixes #9. Thanks to @nowamasa for bug report and fix</td></tr>
<tr><td>v0.1.5</td><td>make object-utils.js work on a browser as-is</td></tr>
<tr><td>v0.1.4</td><td>partial fix for issue #4; add titles to missing coverage spans, remove negative margin for missing if/else indicators</td></tr>
<tr><td>v0.1.3</td><td>Set the environment variable running_under_istanbul to 1 when that is the case. This allows test runners that use istanbul as a library to back off on using it when set.</td></tr>
<tr><td>v0.1.2</td><td>HTML reporting cosmetics. Reports now show syntax-colored JS using `prettify`. Summary tables no longer wrap in awkward places.</td></tr>
<tr><td>v0.1.1</td><td>Fixes issue #1. HTML reports use sources embedded inside the file coverage objects if found rather than reading from the filesystem</td></tr>
<tr><td>v0.1.0</td><td>Initial version</td></tr>
</td></tr>
</table>

View File

@ -0,0 +1,24 @@
Copyright 2012 Yahoo! Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Yahoo! Inc. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,224 @@
Istanbul - a JS code coverage tool written in JS
================================================
[![Build Status](https://secure.travis-ci.org/gotwarlost/istanbul.png)](http://travis-ci.org/gotwarlost/istanbul)
[![Dependency Status](https://gemnasium.com/gotwarlost/istanbul.png)](https://gemnasium.com/gotwarlost/istanbul)
[![Coverage Status](https://img.shields.io/coveralls/gotwarlost/istanbul.svg)](https://coveralls.io/r/gotwarlost/istanbul?branch=master)
[![NPM](https://nodei.co/npm/istanbul.png?downloads=true)](https://nodei.co/npm/istanbul/)
Features
--------
* All-javascript instrumentation library that tracks **statement, branch,
and function coverage** and reverse-engineers **line coverage** with 100% fidelity.
* **Module loader hooks** to instrument code on the fly
* **Command line tools** to run node unit tests "with coverage turned on" and no cooperation
whatsoever from the test runner
* **HTML**, **LCOV**, **Cobertura**, **TeamCity**, and **Clover** reporting.
* Ability to use as **middleware** when serving JS files that need to be tested on the browser.
* Can be used on the **command line** as well as a **library**
* Based on the awesome `esprima` parser and the equally awesome `escodegen` code generator
* Well-tested on node 0.4.x, 0.6.x, 0.8.x and the browser (instrumentation library only)
Installing
----------
$ npm install -g istanbul
Getting started
---------------
The best way to see it in action is to run node unit tests. Say you have a test
script `test.js` that runs all tests for your node project without coverage.
Simply:
$ cd /path/to/your/source/root
$ istanbul cover test.js
and this should produce a `coverage.json`, `lcov.info` and `lcov-report/*html` under `./coverage`
Sample of code coverage reports produced by this tool (for this tool!):
* [HTML reports](http://gotwarlost.github.com/istanbul/public/coverage/lcov-report/index.html)
* [Standard LCOV reports](http://gotwarlost.github.com/istanbul/public/coverage/std-lcov/index.html) (using `genhtml` on the lcov trace file)
Use cases
---------
Supports the following use cases and more
* transparent coverage of nodejs unit tests
* ability to use in an <code>npm test</code> script for conditional coverage
* instrumentation of files in batch mode for browser tests (using yeti for example)
* Server side code coverage for nodejs by embedding it as custom middleware
Ignoring code for coverage
--------------------------
* Skip an `if` or `else` path with `/* istanbul ignore if */` or `/* istanbul ignore else */` respectively.
* For all other cases, skip the next 'thing' in the source with: `/* istanbul ignore next */`
See [ignoring-code-for-coverage.md](ignoring-code-for-coverage.md) for the spec.
The command line
----------------
$ istanbul help
gives you detailed help on all commands.
Usage: istanbul help <command>
Available commands are:
check-coverage
checks overall coverage against thresholds from coverage JSON
files. Exits 1 if thresholds are not met, 0 otherwise
cover transparently adds coverage information to a node command. Saves
coverage.json and reports at the end of execution
help shows help
instrument
instruments a file or a directory tree and writes the
instrumented code to the desired output location
report writes reports for coverage JSON objects produced in a previous
run
test cover a node command only when npm_config_coverage is set. Use in
an `npm test` script for conditional coverage
Command names can be abbreviated as long as the abbreviation is unambiguous
The `cover` command
-------------------
$ istanbul cover my-test-script.js -- my test args
# note the -- between the command name and the arguments to be passed
The `cover` command can be used to get a coverage object and reports for any arbitrary
node script. By default, coverage information is written under `./coverage` - this
can be changed using command-line options.
The `cover` command can also be passed an optional `--handle-sigint` flag to enable writing reports when a user triggers a manual SIGINT of the process that is being covered. This can be useful when you are generating coverage for a long lived process.
The `test` command
-------------------
The `test` command has almost the same behavior as the `cover` command, except that
it skips coverage unless the `npm_config_coverage` environment variable is set.
This helps you set up conditional coverage for tests. In this case you would
have a `package.json` that looks as follows.
{
"name": "my-awesome-lib",
"version": "1.0",
"script": {
"test": "istanbul test my-test-file.js"
}
}
Then:
$ npm test # will run tests without coverage
And:
$ npm test --coverage # will run tests with coverage
**Note**: This needs `node 0.6` or better to work. `npm` for `node 0.4.x` does
not support the `--coverage` flag.
The `instrument` command
------------------------
Instruments a single JS file or an entire directory tree and produces an output directory tree with instrumented code. This should not be required for running node unit tests but is useful for tests to be run on the browser (using `yeti` for example).
The `report` command
-------------------
Writes reports using `coverage*.json` files as the source of coverage information. Reports are available in the following formats:
* html - produces a bunch of HTML files with annotated source code
* lcovonly - produces an lcov.info file
* lcov - produces html + lcov files. This is the default format
* cobertura - produces a cobertura-coverage.xml file for easy Hudson integration
* text-summary - produces a compact text summary of coverage, typically to console
* text - produces a detailed text table with coverage for all files
* teamcity - produces service messages to report code coverage to TeamCity
* clover - produces a clover.xml file to integrate with Atlassian Clover
Additional report formats may be plugged in at the library level.
The `check-coverage` command
----------------------------
Checks the coverage of statements, functions, branches, and lines against the
provided thresholds. Postive thresholds are taken to be the minimum percentage
required and negative numbers are taken to be the number of uncovered entities
allowed.
Library usage
-------------
All the features of istanbul can be accessed as a library using its [public API](http://gotwarlost.github.com/istanbul/public/apidocs/index.html)
Changelog
---------
Changelog has been moved [here](https://github.com/gotwarlost/istanbul/blob/master/CHANGELOG.md).
License
-------
istanbul is licensed under the [BSD License](http://github.com/gotwarlost/istanbul/raw/master/LICENSE).
Third-party libraries
---------------------
The following third-party libraries are used by this module:
* abbrev: https://github.com/isaacs/abbrev-js - to handle command abbreviations
* async: https://github.com/caolan/async - for parallel instrumentation of files
* escodegen: https://github.com/Constellation/escodegen - for JS code generation
* esprima: https://github.com/ariya/esprima - for JS parsing
* fileset: https://github.com/mklabs/node-fileset - for loading and matching path expressions
* handlebars: https://github.com/wycats/handlebars.js/ - for report template expansion
* js-yaml: https://github.com/nodeca/js-yaml - for YAML config file load
* mkdirp: https://github.com/substack/node-mkdirp - to create output directories
* nodeunit: https://github.com/caolan/nodeunit - dev dependency for unit tests
* nopt: https://github.com/isaacs/nopt - for option parsing
* resolve: https://github.com/substack/node-resolve - for resolving a post-require hook module name into its main file.
* rimraf - https://github.com/isaacs/rimraf - dev dependency for unit tests
* which: https://github.com/isaacs/node-which - to resolve a node command to a file for the `cover` command
* wordwrap: https://github.com/substack/node-wordwrap - for prettier help
* prettify: http://code.google.com/p/google-code-prettify/ - for syntax colored HTML reports. Files checked in under `lib/vendor/`
Inspired by
-----------
* YUI test coverage - https://github.com/yui/yuitest - the grand-daddy of JS coverage tools. Istanbul has been specifically designed to offer an alternative to this library with an easy migration path.
* cover: https://github.com/itay/node-cover - the inspiration for the `cover` command, modeled after the `run` command in that tool. The coverage methodology used by istanbul is quite different, however
Shout out to
------------
* [mfncooper](https://github.com/mfncooper) - for great brainstorming discussions
* [reid](https://github.com/reid), [davglass](https://github.com/davglass), the YUI dudes, for interesting conversations, encouragement, support and gentle pressure to get it done :)
Why the funky name?
-------------------
Since all the good ones are taken. Comes from the loose association of ideas across coverage, carpet-area coverage, the country that makes good carpets and so on...

View File

@ -0,0 +1,114 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
/**
* provides access to the key libraries in istanbul so you can write
* your own tools using `istanbul` as a library.
*
* @module istanbul
*/
/*jslint nomen: true */
var path = require('path'),
Store = require('./lib/store'),
Report = require('./lib/report'),
meta = require('./lib/util/meta');
//register our standard plaugins
require('./lib/register-plugins');
/**
* the top-level API for `istanbul`.
*
* Usage
* -----
*
* var istanbul = require('istanbul');
*
*
* @class API
*/
module.exports = {
/**
* the Instrumenter class.
* @property {Instrumenter} Instrumenter
* @static
*/
Instrumenter: require('./lib/instrumenter'),
/**
* the Store class.
* @property {Store} Store
* @static
*/
Store: Store,
/**
* the Collector class
* @property {Collector} Collector
* @static
*/
Collector: require('./lib/collector'),
/**
* the hook module
* @property {Hook} hook
* @static
*/
hook: require('./lib/hook'),
/**
* the Report class
* @property {Report} Report
* @static
*/
Report: Report,
/**
* utility for processing coverage objects
* @property {ObjectUtils} utils
* @static
*/
utils: require('./lib/object-utils'),
/**
* asynchronously returns a function that can match filesystem paths.
* The function returned in the callback may be passed directly as a `matcher`
* to the functions in the `hook` module.
*
* When no options are passed, the match function is one that matches all JS
* files under the current working directory except ones under `node_modules`
*
* Match patterns are `ant`-style patterns processed using the `fileset` library.
* Examples not provided due to limitations in putting asterisks inside
* jsdoc comments. Please refer to tests under `test/other/test-matcher.js`
* for examples.
*
* @method matcherFor
* @static
* @param {Object} options Optional. Lookup options.
* @param {String} [options.root] the root of the filesystem tree under
* which to match files. Defaults to `process.cwd()`
* @param {Array} [options.includes] an array of include patterns to match.
* Defaults to all JS files under the root.
* @param {Array} [options.excludes] and array of exclude patterns. File paths
* matching these patterns will be excluded by the returned matcher.
* Defaults to files under `node_modules` found anywhere under root.
* @param {Function(err, matchFunction)} callback The callback that is
* called with two arguments. The first is an `Error` object in case
* of errors or a falsy value if there were no errors. The second
* is a function that may be use as a matcher.
*/
matcherFor: require('./lib/util/file-matcher').matcherFor,
/**
* the version of the library
* @property {String} VERSION
* @static
*/
VERSION: meta.VERSION,
//undocumented
_yuiLoadHook: require('./lib/util/yui-load-hook'),
//undocumented
TreeSummarizer: require('./lib/util/tree-summarizer'),
//undocumented
assetsDir: path.resolve(__dirname, 'lib', 'vendor')
};

73
node_modules/grunt-istanbul/node_modules/istanbul/lib/cli.js generated vendored Executable file
View File

@ -0,0 +1,73 @@
#!/usr/bin/env node
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Command = require('./command'),
inputError = require('./util/input-error'),
exit = process.exit; //hold a reference to original process.exit so that we are not affected even when a test changes it
require('./register-plugins');
function findCommandPosition(args) {
var i;
for (i = 0; i < args.length; i += 1) {
if (args[i].charAt(0) !== '-') {
return i;
}
}
return -1;
}
function errHandler (ex) {
if (!ex) { return; }
if (!ex.inputError) {
throw ex; // turn it into an uncaught exception
} else {
//don't print nasty traces but still exit(1)
console.error(ex.message);
console.error('Try "istanbul help" for usage');
exit(1);
}
}
function runCommand(args, callback) {
var pos = findCommandPosition(args),
command,
commandArgs,
commandObject;
if (pos < 0) {
return callback(inputError.create('Need a command to run'));
}
commandArgs = args.slice(0, pos);
command = args[pos];
commandArgs.push.apply(commandArgs, args.slice(pos + 1));
try {
commandObject = Command.create(command);
} catch (ex) {
errHandler(inputError.create(ex.message));
}
commandObject.run(commandArgs, errHandler);
}
function runToCompletion(args) {
runCommand(args, errHandler);
}
if (require.main === module) {
var args = Array.prototype.slice.call(process.argv, 2);
runToCompletion(args);
}
module.exports = {
runToCompletion: runToCompletion
};

View File

@ -0,0 +1,121 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
"use strict";
var MemoryStore = require('./store/memory'),
utils = require('./object-utils');
/**
* a mechanism to merge multiple coverage objects into one. Handles the use case
* of overlapping coverage information for the same files in multiple coverage
* objects and does not double-count in this situation. For example, if
* you pass the same coverage object multiple times, the final merged object will be
* no different that any of the objects passed in (except for execution counts).
*
* The `Collector` is built for scale to handle thousands of coverage objects.
* By default, all processing is done in memory since the common use-case is of
* one or a few coverage objects. You can work around memory
* issues by passing in a `Store` implementation that stores temporary computations
* on disk (the `tmp` store, for example).
*
* The `getFinalCoverage` method returns an object with merged coverage information
* and is provided as a convenience for implementors working with coverage information
* that can fit into memory. Reporters, in the interest of generality, should *not* use this method for
* creating reports.
*
* Usage
* -----
*
* var collector = new require('istanbul').Collector();
*
* files.forEach(function (f) {
* //each coverage object can have overlapping information about multiple files
* collector.add(JSON.parse(fs.readFileSync(f, 'utf8')));
* });
*
* collector.files().forEach(function(file) {
* var fileCoverage = collector.fileCoverageFor(file);
* console.log('Coverage for ' + file + ' is:' + JSON.stringify(fileCoverage));
* });
*
* // convenience method: do not use this when dealing with a large number of files
* var finalCoverage = collector.getFinalCoverage();
*
* @class Collector
* @constructor
* @param {Object} options Optional. Configuration options.
* @param {Store} options.store - an implementation of `Store` to use for temporary
* calculations.
*/
function Collector(options) {
options = options || {};
this.store = options.store || new MemoryStore();
}
Collector.prototype = {
/**
* adds a coverage object to the collector.
*
* @method add
* @param {Object} coverage the coverage object.
* @param {String} testName Optional. The name of the test used to produce the object.
* This is currently not used.
*/
add: function (coverage /*, testName */) {
var store = this.store;
Object.keys(coverage).forEach(function (key) {
var fileCoverage = coverage[key];
if (store.hasKey(key)) {
store.setObject(key, utils.mergeFileCoverage(fileCoverage, store.getObject(key)));
} else {
store.setObject(key, fileCoverage);
}
});
},
/**
* returns a list of unique file paths for which coverage information has been added.
* @method files
* @return {Array} an array of file paths for which coverage information is present.
*/
files: function () {
return this.store.keys();
},
/**
* return file coverage information for a single file
* @method fileCoverageFor
* @param {String} fileName the path for the file for which coverage information is
* required. Must be one of the values returned in the `files()` method.
* @return {Object} the coverage information for the specified file.
*/
fileCoverageFor: function (fileName) {
var ret = this.store.getObject(fileName);
utils.addDerivedInfoForFile(ret);
return ret;
},
/**
* returns file coverage information for all files. This has the same format as
* any of the objects passed in to the `add` method. The number of keys in this
* object will be a superset of all keys found in the objects passed to `add()`
* @method getFinalCoverage
* @return {Object} the merged coverage information
*/
getFinalCoverage: function () {
var ret = {},
that = this;
this.files().forEach(function (file) {
ret[file] = that.fileCoverageFor(file);
});
return ret;
},
/**
* disposes this collector and reclaims temporary resources used in the
* computation. Calls `dispose()` on the underlying store.
* @method dispose
*/
dispose: function () {
this.store.dispose();
}
};
module.exports = Collector;

View File

@ -0,0 +1,115 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var nopt = require('nopt'),
path = require('path'),
fs = require('fs'),
Collector = require('../collector'),
formatOption = require('../util/help-formatter').formatOption,
util = require('util'),
utils = require('../object-utils'),
filesFor = require('../util/file-matcher').filesFor,
Command = require('./index');
function CheckCoverageCommand() {
Command.call(this);
}
CheckCoverageCommand.TYPE = 'check-coverage';
util.inherits(CheckCoverageCommand, Command);
Command.mix(CheckCoverageCommand, {
synopsis: function () {
return "checks overall coverage against thresholds from coverage JSON files. Exits 1 if thresholds are not met, 0 otherwise";
},
usage: function () {
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [<include-pattern>]\n\nOptions are:\n\n' +
[
formatOption('--statements <threshold>', 'statement coverage threshold'),
formatOption('--functions <threshold>', 'function coverage threshold'),
formatOption('--branches <threshold>', 'branch coverage threshold'),
formatOption('--lines <threshold>', 'line coverage threshold')
].join('\n\n') + '\n');
console.error('\n\n');
console.error('Thresholds, when specified as a positive number are taken to be the minimum percentage required.');
console.error('When a threshold is specified as a negative number it represents the maximum number of uncovered entities allowed.\n');
console.error('For example, --statements 90 implies minimum statement coverage is 90%.');
console.error(' --statements -10 implies that no more than 10 uncovered statements are allowed\n');
console.error('<include-pattern> is a fileset pattern that can be used to select one or more coverage files ' +
'for merge. This defaults to "**/coverage*.json"');
console.error('\n');
},
run: function (args, callback) {
var config = {
root: path,
dir: path,
statements: Number,
lines: Number,
branches: Number,
functions: Number,
verbose: Boolean
},
opts = nopt(config, { v : '--verbose' }, args, 0),
includePattern = '**/coverage*.json',
root,
collector = new Collector(),
errors = [];
if (opts.argv.remain.length > 0) {
includePattern = opts.argv.remain[0];
}
root = opts.root || process.cwd();
filesFor({
root: root,
includes: [ includePattern ]
}, function (err, files) {
if (err) { throw err; }
files.forEach(function (file) {
var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8'));
collector.add(coverageObject);
});
var thresholds = {
statements: opts.statements || 0,
branches: opts.branches || 0,
lines: opts.lines || 0,
functions: opts.functions || 0
},
actuals = utils.summarizeCoverage(collector.getFinalCoverage());
if (opts.verbose) {
console.log('Compare actuals against thresholds');
console.log(JSON.stringify({ actuals: actuals, thresholds: thresholds }, undefined, 4));
}
Object.keys(thresholds).forEach(function (key) {
var actual = actuals[key].pct,
actualUncovered = actuals[key].total - actuals[key].covered,
threshold = thresholds[key];
if (threshold < 0) {
if (threshold * -1 < actualUncovered) {
errors.push('ERROR: Uncovered count for ' + key + ' (' + actualUncovered + ') exceeds threshold (' + -1 * threshold + ')');
}
} else {
if (actual < threshold) {
errors.push('ERROR: Coverage for ' + key + ' (' + actual + '%) does not meet threshold (' + threshold + '%)');
}
}
});
return callback(errors.length === 0 ? null : errors.join("\n"));
});
}
});
module.exports = CheckCoverageCommand;

View File

@ -0,0 +1,250 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Module = require('module'),
path = require('path'),
fs = require('fs'),
nopt = require('nopt'),
which = require('which'),
mkdirp = require('mkdirp'),
existsSync = fs.existsSync || path.existsSync,
inputError = require('../../util/input-error'),
matcherFor = require('../../util/file-matcher').matcherFor,
Instrumenter = require('../../instrumenter'),
Collector = require('../../collector'),
formatOption = require('../../util/help-formatter').formatOption,
hook = require('../../hook'),
Report = require('../../report'),
resolve = require('resolve'),
configuration = require('../../configuration');
function usage(arg0, command) {
console.error('\nUsage: ' + arg0 + ' ' + command + ' [<options>] <executable-js-file-or-command> [-- <arguments-to-jsfile>]\n\nOptions are:\n\n'
+ [
formatOption('--config <path-to-config>', 'the configuration file to use, defaults to .istanbul.yml'),
formatOption('--root <path> ', 'the root path to look for files to instrument, defaults to .'),
formatOption('-x <exclude-pattern> [-x <exclude-pattern>]', 'one or more fileset patterns e.g. "**/vendor/**"'),
formatOption('--[no-]default-excludes', 'apply default excludes [ **/node_modules/**, **/test/**, **/tests/** ], defaults to true'),
formatOption('--hook-run-in-context', 'hook vm.runInThisContext in addition to require (supports RequireJS), defaults to false'),
formatOption('--post-require-hook <file> | <module>', 'JS module that exports a function for post-require processing'),
formatOption('--report <format> [--report <format>] ', 'report format, defaults to lcov (= lcov.info + HTML)'),
formatOption('--dir <report-dir>', 'report directory, defaults to ./coverage'),
formatOption('--print <type>', 'type of report to print to console, one of summary (default), detail, both or none'),
formatOption('--verbose, -v', 'verbose mode'),
formatOption('--[no-]preserve-comments', 'remove / preserve comments in the output, defaults to false'),
formatOption('--[no-]preload-sources', '`require` all sources before running tests, defaults to false')
].join('\n\n') + '\n');
console.error('\n');
}
function run(args, commandName, enableHooks, callback) {
var template = {
config: path,
root: path,
x: [ Array, String ],
report: [Array, String ],
dir: path,
verbose: Boolean,
yui: Boolean,
'default-excludes': Boolean,
print: String,
'self-test': Boolean,
'hook-run-in-context': Boolean,
'post-require-hook': String,
'preserve-comments': Boolean,
'preload-sources': Boolean
},
opts = nopt(template, { v : '--verbose' }, args, 0),
overrides = {
verbose: opts.verbose,
instrumentation: {
root: opts.root,
'default-excludes': opts['default-excludes'],
excludes: opts.x,
'preload-sources': opts['preload-sources']
},
reporting: {
reports: opts.report,
print: opts.print,
dir: opts.dir
},
hooks: {
'hook-run-in-context': opts['hook-run-in-context'],
'post-require-hook': opts['post-require-hook'],
'handle-sigint': opts['handle-sigint']
}
},
config = configuration.loadFile(opts.config, overrides),
watermarks = config.reporting.watermarks(),
reportOpts,
verbose = config.verbose,
cmdAndArgs = opts.argv.remain,
preserveComments = opts['preserve-comments'],
cmd,
cmdArgs,
reportingDir,
reports = [],
runFn,
excludes;
if (cmdAndArgs.length === 0) {
return callback(inputError.create('Need a filename argument for the ' + commandName + ' command!'));
}
cmd = cmdAndArgs.shift();
cmdArgs = cmdAndArgs;
if (!existsSync(cmd)) {
try {
cmd = which.sync(cmd);
} catch (ex) {
return callback(inputError.create('Unable to resolve file [' + cmd + ']'));
}
} else {
cmd = path.resolve(cmd);
}
runFn = function () {
process.argv = ["node", cmd].concat(cmdArgs);
if (verbose) {
console.log('Running: ' + process.argv.join(' '));
}
process.env.running_under_istanbul=1;
Module.runMain(cmd, null, true);
};
excludes = config.instrumentation.excludes(true);
if (enableHooks) {
reportingDir = path.resolve(config.reporting.dir());
reportOpts = { dir: reportingDir, watermarks: watermarks };
mkdirp.sync(reportingDir); //ensure we fail early if we cannot do this
reports.push.apply(reports, config.reporting.reports().map(function (r) {
return Report.create(r, reportOpts);
}));
if (config.reporting.print() !== 'none') {
switch (config.reporting.print()) {
case 'detail':
reports.push(Report.create('text', reportOpts));
break;
case 'both':
reports.push(Report.create('text', reportOpts));
reports.push(Report.create('text-summary', reportOpts));
break;
default:
reports.push(Report.create('text-summary', reportOpts));
break;
}
}
excludes.push(path.relative(process.cwd(), path.join(reportingDir, '**', '*')));
matcherFor({
root: config.instrumentation.root() || process.cwd(),
includes: [ '**/*.js' ],
excludes: excludes
},
function (err, matchFn) {
if (err) { return callback(err); }
var coverageVar = '$$cov_' + new Date().getTime() + '$$',
instrumenter = new Instrumenter({ coverageVariable: coverageVar , preserveComments: preserveComments}),
transformer = instrumenter.instrumentSync.bind(instrumenter),
hookOpts = { verbose: verbose },
postRequireHook = config.hooks.postRequireHook(),
postLoadHookFile;
if (postRequireHook) {
postLoadHookFile = path.resolve(postRequireHook);
} else if (opts.yui) { //EXPERIMENTAL code: do not rely on this in anyway until the docs say it is allowed
postLoadHookFile = path.resolve(__dirname, '../../util/yui-load-hook');
}
if (postRequireHook) {
if (!existsSync(postLoadHookFile)) { //assume it is a module name and resolve it
try {
postLoadHookFile = resolve.sync(postRequireHook, { basedir: process.cwd() });
} catch (ex) {
if (verbose) { console.error('Unable to resolve [' + postRequireHook + '] as a node module'); }
}
}
}
if (postLoadHookFile) {
if (verbose) { console.error('Use post-load-hook: ' + postLoadHookFile); }
hookOpts.postLoadHook = require(postLoadHookFile)(matchFn, transformer, verbose);
}
if (opts['self-test']) {
hook.unloadRequireCache(matchFn);
}
// runInThisContext is used by RequireJS [issue #23]
if (config.hooks.hookRunInContext()) {
hook.hookRunInThisContext(matchFn, transformer, hookOpts);
}
hook.hookRequire(matchFn, transformer, hookOpts);
//initialize the global variable to stop mocha from complaining about leaks
global[coverageVar] = {};
// enable passing --handle-sigint to write reports on SIGINT.
// This allows a user to manually kill a process while
// still getting the istanbul report.
if (config.hooks.handleSigint()) {
process.once('SIGINT', process.exit);
}
process.once('exit', function () {
var file = path.resolve(reportingDir, 'coverage.json'),
collector,
cov;
if (typeof global[coverageVar] === 'undefined' || Object.keys(global[coverageVar]).length === 0) {
console.error('No coverage information was collected, exit without writing coverage information');
return;
} else {
cov = global[coverageVar];
}
//important: there is no event loop at this point
//everything that happens in this exit handler MUST be synchronous
mkdirp.sync(reportingDir); //yes, do this again since some test runners could clean the dir initially created
if (config.reporting.print() !== 'none') {
console.error('=============================================================================');
console.error('Writing coverage object [' + file + ']');
}
fs.writeFileSync(file, JSON.stringify(cov), 'utf8');
collector = new Collector();
collector.add(cov);
if (config.reporting.print() !== 'none') {
console.error('Writing coverage reports at [' + reportingDir + ']');
console.error('=============================================================================');
}
reports.forEach(function (report) {
report.writeReport(collector, true);
});
return callback();
});
if (config.instrumentation.preloadSources()) {
matchFn.files.forEach(function (file) {
if (verbose) { console.error('Preload ' + file); }
try {
require(file);
} catch (ex) {
if (verbose) {
console.error('Unable to preload ' + file);
}
// swallow
}
});
}
runFn();
});
} else {
runFn();
}
}
module.exports = {
run: run,
usage: usage
};

View File

@ -0,0 +1,33 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var runWithCover = require('./common/run-with-cover'),
util = require('util'),
Command = require('./index');
function CoverCommand() {
Command.call(this);
}
CoverCommand.TYPE = 'cover';
util.inherits(CoverCommand, Command);
Command.mix(CoverCommand, {
synopsis: function () {
return "transparently adds coverage information to a node command. Saves coverage.json and reports at the end of execution";
},
usage: function () {
runWithCover.usage(this.toolName(), this.type());
},
run: function (args, callback) {
runWithCover.run(args, this.type(), true, callback);
}
});
module.exports = CoverCommand;

View File

@ -0,0 +1,90 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Command = require('./index.js'),
util = require('util'),
formatOption = require('../util/help-formatter').formatOption,
VERSION = require('../../index').VERSION,
configuration = require('../configuration'),
yaml = require('js-yaml'),
formatPara = require('../util/help-formatter').formatPara;
function showConfigHelp(toolName) {
console.error('\nConfiguring ' + toolName);
console.error('====================');
console.error('\n' +
formatPara(toolName + ' can be configured globally using a .istanbul.yml YAML file ' +
'at the root of your source tree. Every command also accepts a --config=<config-file> argument to ' +
'customize its location per command. The alternate config file can be in YAML, JSON or node.js ' +
'(exporting the config object).'));
console.error('\n' +
formatPara('The config file currently has three sections for instrumentation, reporting and hooks. ' +
'Note that certain commands (like `cover`) use information from multiple sections.'));
console.error('\n' +
formatPara('Keys in the config file usually correspond to command line parameters with the same name. ' +
'The verbose option for every command shows you the exact configuration used'));
console.error('\nThe default configuration is as follows:\n');
console.error(yaml.safeDump(configuration.defaultConfig(), { indent: 4, flowLevel: 3 }));
console.error('\n' +
formatPara('The `watermarks` section does not have a command line equivalent. It allows you to set up ' +
'low and high watermark percentages for reporting. These are honored by all reporters that colorize ' +
'their output based on low/ medium/ high coverage'));
}
function HelpCommand() {
Command.call(this);
}
HelpCommand.TYPE = 'help';
util.inherits(HelpCommand, Command);
Command.mix(HelpCommand, {
synopsis: function () {
return "shows help";
},
usage: function () {
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' config | <command>\n');
console.error('`config` provides help with istanbul configuration\n');
console.error('Available commands are:\n');
var commandObj;
Command.getCommandList().forEach(function (cmd) {
commandObj = Command.create(cmd);
console.error(formatOption(cmd, commandObj.synopsis()));
console.error("\n");
});
console.error("Command names can be abbreviated as long as the abbreviation is unambiguous");
console.error(this.toolName() + ' version:' + VERSION);
console.error("\n");
},
run: function (args, callback) {
var command;
if (args.length === 0) {
this.usage();
} else {
if (args[0] === 'config') {
showConfigHelp(this.toolName());
} else {
try {
command = Command.create(args[0]);
command.usage('istanbul', Command.resolveCommandName(args[0]));
} catch (ex) {
console.error('Invalid command: ' + args[0]);
this.usage();
}
}
}
return callback();
}
});
module.exports = HelpCommand;

View File

@ -0,0 +1,33 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Factory = require('../util/factory'),
factory = new Factory('command', __dirname, true);
function Command() {}
// add register, create, mix, loadAll, getCommandList, resolveCommandName to the Command object
factory.bindClassMethods(Command);
Command.prototype = {
toolName: function () {
return require('../util/meta').NAME;
},
type: function () {
return this.constructor.TYPE;
},
synopsis: /* istanbul ignore next: base method */ function () {
return "the developer has not written a one-line summary of the " + this.type() + " command";
},
usage: /* istanbul ignore next: base method */ function () {
console.error("the developer has not provided a usage for the " + this.type() + " command");
},
run: /* istanbul ignore next: abstract method */ function (args, callback) {
return callback(new Error("run: must be overridden for the " + this.type() + " command"));
}
};
module.exports = Command;

View File

@ -0,0 +1,259 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
mkdirp = require('mkdirp'),
async = require('async'),
fs = require('fs'),
filesFor = require('../util/file-matcher').filesFor,
nopt = require('nopt'),
Instrumenter = require('../instrumenter'),
inputError = require('../util/input-error'),
formatOption = require('../util/help-formatter').formatOption,
util = require('util'),
Command = require('./index'),
Collector = require('../collector'),
flowControl = require('../util/flow-control'),
configuration = require('../configuration'),
verbose;
/**
* Chunk file size to use when reading non JavaScript files in memory
* and copying them over when using complete-copy flag.
*/
var READ_FILE_CHUNK_SIZE = 64 * 1024;
function BaselineCollector(instrumenter) {
this.instrumenter = instrumenter;
this.collector = new Collector();
this.instrument = instrumenter.instrument.bind(this.instrumenter);
var origInstrumentSync = instrumenter.instrumentSync;
this.instrumentSync = function () {
var args = Array.prototype.slice.call(arguments),
ret = origInstrumentSync.apply(this.instrumenter, args),
baseline = this.instrumenter.lastFileCoverage(),
coverage = {};
coverage[baseline.path] = baseline;
this.collector.add(coverage);
return ret;
};
//monkey patch the instrumenter to call our version instead
instrumenter.instrumentSync = this.instrumentSync.bind(this);
}
BaselineCollector.prototype = {
getCoverage: function () {
return this.collector.getFinalCoverage();
}
};
function processFiles(instrumenter, inputDir, outputDir, relativeNames) {
var processor = function (name, callback) {
var inputFile = path.resolve(inputDir, name),
outputFile = path.resolve(outputDir, name),
inputFileExtenstion = path.extname(inputFile),
isJavaScriptFile = (inputFileExtenstion === '.js'),
oDir = path.dirname(outputFile),
readStream, writeStream;
callback = flowControl.callOnce(callback);
mkdirp.sync(oDir);
if (fs.statSync(inputFile).isDirectory()) {
return callback(null, name);
}
if (isJavaScriptFile) {
fs.readFile(inputFile, 'utf8', function (err, data) {
if (err) { return callback(err, name); }
instrumenter.instrument(data, inputFile, function (iErr, instrumented) {
if (iErr) { return callback(iErr, name); }
fs.writeFile(outputFile, instrumented, 'utf8', function (err) {
return callback(err, name);
});
});
});
}
else {
// non JavaScript file, copy it as is
readStream = fs.createReadStream(inputFile, {'bufferSize': READ_FILE_CHUNK_SIZE});
writeStream = fs.createWriteStream(outputFile);
readStream.on('error', callback);
writeStream.on('error', callback);
readStream.pipe(writeStream);
readStream.on('end', function() {
callback(null, name);
});
}
},
q = async.queue(processor, 10),
errors = [],
count = 0,
startTime = new Date().getTime();
q.push(relativeNames, function (err, name) {
var inputFile, outputFile;
if (err) {
errors.push({ file: name, error: err.message || err.toString() });
inputFile = path.resolve(inputDir, name);
outputFile = path.resolve(outputDir, name);
fs.writeFileSync(outputFile, fs.readFileSync(inputFile));
}
if (verbose) {
console.log('Processed: ' + name);
} else {
if (count % 100 === 0) { process.stdout.write('.'); }
}
count += 1;
});
q.drain = function () {
var endTime = new Date().getTime();
console.log('\nProcessed [' + count + '] files in ' + Math.floor((endTime - startTime) / 1000) + ' secs');
if (errors.length > 0) {
console.log('The following ' + errors.length + ' file(s) had errors and were copied as-is');
console.log(errors);
}
};
}
function InstrumentCommand() {
Command.call(this);
}
InstrumentCommand.TYPE = 'instrument';
util.inherits(InstrumentCommand, Command);
Command.mix(InstrumentCommand, {
synopsis: function synopsis() {
return "instruments a file or a directory tree and writes the instrumented code to the desired output location";
},
usage: function () {
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> <file-or-directory>\n\nOptions are:\n\n' +
[
formatOption('--config <path-to-config>', 'the configuration file to use, defaults to .istanbul.yml'),
formatOption('--output <file-or-dir>', 'The output file or directory. This is required when the input is a directory, ' +
'defaults to standard output when input is a file'),
formatOption('-x <exclude-pattern> [-x <exclude-pattern>]', 'one or more fileset patterns (e.g. "**/vendor/**" to ignore all files ' +
'under a vendor directory). Also see the --default-excludes option'),
formatOption('--variable <global-coverage-variable-name>', 'change the variable name of the global coverage variable from the ' +
'default value of `__coverage__` to something else'),
formatOption('--embed-source', 'embed source code into the coverage object, defaults to false'),
formatOption('--[no-]compact', 'produce [non]compact output, defaults to compact'),
formatOption('--[no-]preserve-comments', 'remove / preserve comments in the output, defaults to false'),
formatOption('--[no-]complete-copy', 'also copy non-javascript files to the ouput directory as is, defaults to false'),
formatOption('--save-baseline', 'produce a baseline coverage.json file out of all files instrumented'),
formatOption('--baseline-file <file>', 'filename of baseline file, defaults to coverage/coverage-baseline.json')
].join('\n\n') + '\n');
console.error('\n');
},
run: function (args, callback) {
var template = {
config: path,
output: path,
x: [Array, String],
variable: String,
compact: Boolean,
'complete-copy': Boolean,
verbose: Boolean,
'save-baseline': Boolean,
'baseline-file': path,
'embed-source': Boolean,
'preserve-comments': Boolean
},
opts = nopt(template, { v : '--verbose' }, args, 0),
overrides = {
verbose: opts.verbose,
instrumentation: {
variable: opts.variable,
compact: opts.compact,
'embed-source': opts['embed-source'],
'preserve-comments': opts['preserve-comments'],
excludes: opts.x,
'complete-copy': opts['complete-copy'],
'save-baseline': opts['save-baseline'],
'baseline-file': opts['baseline-file']
}
},
config = configuration.loadFile(opts.config, overrides),
iOpts = config.instrumentation,
cmdArgs = opts.argv.remain,
file,
stats,
stream,
includes,
instrumenter,
needBaseline = iOpts.saveBaseline(),
baselineFile = path.resolve(iOpts.baselineFile()),
output = opts.output;
verbose = config.verbose;
if (cmdArgs.length !== 1) {
return callback(inputError.create('Need exactly one filename/ dirname argument for the instrument command!'));
}
if (iOpts.completeCopy()) {
includes = ['**/*'];
}
else {
includes = ['**/*.js'];
}
instrumenter = new Instrumenter({
coverageVariable: iOpts.variable(),
embedSource: iOpts.embedSource(),
noCompact: !iOpts.compact(),
preserveComments: iOpts.preserveComments()
});
if (needBaseline) {
mkdirp.sync(path.dirname(baselineFile));
instrumenter = new BaselineCollector(instrumenter);
process.on('exit', function () {
util.puts('Saving baseline coverage at: ' + baselineFile);
fs.writeFileSync(baselineFile, JSON.stringify(instrumenter.getCoverage()), 'utf8');
});
}
file = path.resolve(cmdArgs[0]);
stats = fs.statSync(file);
if (stats.isDirectory()) {
if (!output) { return callback(inputError.create('Need an output directory [-o <dir>] when input is a directory!')); }
if (output === file) { return callback(inputError.create('Cannot instrument into the same directory/ file as input!')); }
mkdirp.sync(output);
filesFor({
root: file,
includes: includes,
excludes: opts.x || iOpts.excludes(false), // backwards-compat, *sigh*
relative: true
}, function (err, files) {
if (err) { return callback(err); }
processFiles(instrumenter, file, output, files);
});
} else {
if (output) {
stream = fs.createWriteStream(output);
} else {
stream = process.stdout;
}
stream.write(instrumenter.instrumentSync(fs.readFileSync(file, 'utf8'), file));
if (stream !== process.stdout) {
stream.end();
}
}
}
});
module.exports = InstrumentCommand;

View File

@ -0,0 +1,110 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var nopt = require('nopt'),
Report = require('../report'),
path = require('path'),
fs = require('fs'),
Collector = require('../collector'),
inputError = require('../util/input-error'),
formatOption = require('../util/help-formatter').formatOption,
filesFor = require('../util/file-matcher').filesFor,
util = require('util'),
Command = require('./index'),
configuration = require('../configuration');
function ReportCommand() {
Command.call(this);
}
ReportCommand.TYPE = 'report';
util.inherits(ReportCommand, Command);
Command.mix(ReportCommand, {
synopsis: function () {
return "writes reports for coverage JSON objects produced in a previous run";
},
usage: function () {
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [ <format> [<include-pattern>] ]\n\nOptions are:\n\n' +
[
formatOption('--config <path-to-config>', 'the configuration file to use, defaults to .istanbul.yml'),
formatOption('--root <input-directory>', 'The input root directory for finding coverage files'),
formatOption('--dir <report-directory>', 'The output directory where files will be written. This defaults to ./coverage/'),
formatOption('--verbose, -v', 'verbose mode')
].join('\n\n') + '\n');
console.error('\n');
console.error('<format> is one of html, lcovonly, lcov (html + lcovonly), cobertura, text-summary, text, ' +
'teamcity, or clover. Default is lcov');
console.error('<include-pattern> is a fileset pattern that can be used to select one or more coverage files ' +
'for merged reporting. This defaults to "**/coverage*.json"');
console.error('\n');
},
run: function (args, callback) {
var template = {
config: path,
root: path,
dir: path,
verbose: Boolean
},
opts = nopt(template, { v : '--verbose' }, args, 0),
fmtAndArgs = opts.argv.remain,
fmt = 'lcov',
includePattern = '**/coverage*.json',
reporter,
root,
collector = new Collector(),
config = configuration.loadFile(opts.config, {
verbose: opts.verbose,
reporting: {
dir: opts.dir
}
}),
reportOpts = {
verbose: config.verbose,
dir: config.reporting.dir(),
watermarks: config.reporting.watermarks()
};
if (fmtAndArgs.length > 0) {
fmt = fmtAndArgs[0];
}
if (fmtAndArgs.length > 1) {
includePattern = fmtAndArgs[1];
}
try {
reporter = Report.create(fmt, reportOpts);
} catch (ex) {
return callback(inputError.create('Invalid report format [' + fmt + ']'));
}
root = opts.root || process.cwd();
filesFor({
root: root,
includes: [ includePattern ]
}, function (err, files) {
if (err) { throw err; }
files.forEach(function (file) {
var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8'));
collector.add(coverageObject);
});
console.log('Using reporter [' + fmt + ']');
reporter.writeReport(collector);
console.log('Done');
return callback();
});
}
});
module.exports = ReportCommand;

View File

@ -0,0 +1,31 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var runWithCover = require('./common/run-with-cover'),
util = require('util'),
Command = require('./index');
function TestCommand() {
Command.call(this);
}
TestCommand.TYPE = 'test';
util.inherits(TestCommand, Command);
Command.mix(TestCommand, {
synopsis: function () {
return "cover a node command only when npm_config_coverage is set. Use in an `npm test` script for conditional coverage";
},
usage: function () {
runWithCover.usage(this.toolName(), this.type());
},
run: function (args, callback) {
runWithCover.run(args, this.type(), !!process.env.npm_config_coverage, callback);
}
});
module.exports = TestCommand;

View File

@ -0,0 +1,223 @@
/*
Copyright (c) 2013, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
fs = require('fs'),
CAMEL_PATTERN = /([a-z])([A-Z])/g,
YML_PATTERN = /\.ya?ml$/,
yaml = require('js-yaml'),
defaults = require('./report/common/defaults');
function defaultConfig() {
var ret = {
verbose: false,
instrumentation: {
root: '.',
'default-excludes': true,
excludes: [],
'embed-source': false,
variable: '__coverage__',
compact: true,
'preserve-comments': false,
'complete-copy': false,
'save-baseline': false,
'baseline-file': './coverage/coverage-baseline.json',
'preload-sources': false
},
reporting: {
print: 'summary',
reports: [ 'lcov' ],
dir: './coverage'
},
hooks: {
'hook-run-in-context': false,
'post-require-hook': null,
'handle-sigint': false
}
};
ret.reporting.watermarks = defaults.watermarks();
return ret;
}
function dasherize(word) {
return word.replace(CAMEL_PATTERN, function (match, lch, uch) {
return lch + '-' + uch.toLowerCase();
});
}
function isScalar(v) {
if (v === null) { return true; }
return v !== undefined && !Array.isArray(v) && typeof v !== 'object';
}
function isObject(v) {
return typeof v === 'object' && v !== null && !Array.isArray(v);
}
function mergeObjects(explicit, template) {
var ret = {};
Object.keys(template).forEach(function (k) {
var v1 = template[k],
v2 = explicit[k];
if (Array.isArray(v1)) {
ret[k] = Array.isArray(v2) && v2.length > 0 ? v2 : v1;
} else if (isObject(v1)) {
v2 = isObject(v2) ? v2 : {};
ret[k] = mergeObjects(v2, v1);
} else {
ret[k] = isScalar(v2) ? v2 : v1;
}
});
return ret;
}
function mergeDefaults(explicit, implicit) {
return mergeObjects(explicit || {}, implicit);
}
function addMethods() {
var args = Array.prototype.slice.call(arguments),
cons = args.shift();
args.forEach(function (arg) {
var method = arg,
property = dasherize(arg);
cons.prototype[method] = function () {
return this.config[property];
};
});
}
function InstrumentOptions(config) {
this.config = config;
}
addMethods(InstrumentOptions,
'defaultExcludes', 'completeCopy',
'embedSource', 'variable', 'compact', 'preserveComments',
'saveBaseline', 'baselineFile',
'preloadSources');
InstrumentOptions.prototype.root = function () { return path.resolve(this.config.root); };
InstrumentOptions.prototype.excludes = function (excludeTests) {
var defs;
if (this.defaultExcludes()) {
defs = [ '**/node_modules/**' ];
if (excludeTests) {
defs = defs.concat(['**/test/**', '**/tests/**']);
}
return defs.concat(this.config.excludes);
}
return this.config.excludes;
};
function ReportingOptions(config) {
this.config = config;
}
addMethods(ReportingOptions, 'print', 'reports', 'dir');
function isInvalidMark(v, key) {
var prefix = 'Watermark for [' + key + '] :';
if (v.length !== 2) {
return prefix + 'must be an array of length 2';
}
v[0] = Number(v[0]);
v[1] = Number(v[1]);
if (isNaN(v[0]) || isNaN(v[1])) {
return prefix + 'must have valid numbers';
}
if (v[0] < 0 || v[1] < 0) {
return prefix + 'must be positive numbers';
}
if (v[1] > 100) {
return prefix + 'cannot exceed 100';
}
if (v[1] <= v[0]) {
return prefix + 'low must be less than high';
}
return null;
}
ReportingOptions.prototype.watermarks = function () {
var v = this.config.watermarks,
defs = defaults.watermarks(),
ret = {};
Object.keys(defs).forEach(function (k) {
var mark = v[k], //it will already be a non-zero length array because of the way the merge works
message = isInvalidMark(mark, k);
if (message) {
console.error(message);
ret[k] = defs[k];
} else {
ret[k] = mark;
}
});
return ret;
};
function HookOptions(config) {
this.config = config;
}
addMethods(HookOptions, 'hookRunInContext', 'postRequireHook', 'handleSigint');
function Configuration(obj, overrides) {
var config = mergeDefaults(obj, defaultConfig());
if (isObject(overrides)) {
config = mergeDefaults(overrides, config);
}
if (config.verbose) {
console.error('Using configuration');
console.error('-------------------');
console.error(yaml.safeDump(config, { indent: 4, flowLevel: 3 }));
console.error('-------------------\n');
}
this.verbose = config.verbose;
this.instrumentation = new InstrumentOptions(config.instrumentation);
this.reporting = new ReportingOptions(config.reporting);
this.hooks = new HookOptions(config.hooks);
//this.thresholds = new ThresholdOptions(config.thresholds);
}
function loadFile(file, overrides) {
var defaultConfigFile = path.resolve('.istanbul.yml'),
configObject;
if (file) {
if (!fs.existsSync(file)) {
throw new Error('Invalid configuration file specified:' + file);
}
} else {
if (fs.existsSync(defaultConfigFile)) {
file = defaultConfigFile;
}
}
if (file) {
console.error('Loading config: ' + file);
configObject = file.match(YML_PATTERN) ?
yaml.safeLoad(fs.readFileSync(file, 'utf8'), { filename: file }) :
require(path.resolve(file));
}
return new Configuration(configObject, overrides);
}
function loadObject(obj, overrides) {
return new Configuration(obj, overrides);
}
module.exports = {
loadFile: loadFile,
loadObject: loadObject,
defaultConfig: defaultConfig
};

View File

@ -0,0 +1,187 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
/**
* provides a mechanism to transform code in the scope of `require` or `vm.createScript`.
* This mechanism is general and relies on a user-supplied `matcher` function that determines when transformations should be
* performed and a user-supplied `transformer` function that performs the actual transform.
* Instrumenting code for coverage is one specific example of useful hooking.
*
* Note that both the `matcher` and `transformer` must execute synchronously.
*
* For the common case of matching filesystem paths based on inclusion/ exclusion patterns, use the `matcherFor`
* function in the istanbul API to get a matcher.
*
* It is up to the transformer to perform processing with side-effects, such as caching, storing the original
* source code to disk in case of dynamically generated scripts etc. The `Store` class can help you with this.
*
* Usage
* -----
*
* var hook = require('istanbul').hook,
* myMatcher = function (file) { return file.match(/foo/); },
* myTransformer = function (code, file) { return 'console.log("' + file + '");' + code; };
*
* hook.hookRequire(myMatcher, myTransformer);
*
* var foo = require('foo'); //will now print foo's module path to console
*
* @class Hook
*/
var path = require('path'),
fs = require('fs'),
Module = require('module'),
vm = require('vm'),
originalLoader = Module._extensions['.js'],
originalCreateScript = vm.createScript,
originalRunInThisContext = vm.runInThisContext;
function transformFn(matcher, transformer, verbose) {
return function (code, filename) {
var shouldHook = typeof filename === 'string' && matcher(path.resolve(filename)),
transformed,
changed = false;
if (shouldHook) {
if (verbose) {
console.error('Module load hook: transform [' + filename + ']');
}
try {
transformed = transformer(code, filename);
changed = true;
} catch (ex) {
console.error('Transformation error; return original code');
console.error(ex);
transformed = code;
}
} else {
transformed = code;
}
return { code: transformed, changed: changed };
};
}
function unloadRequireCache(matcher) {
if (matcher && typeof require !== 'undefined' && require && require.cache) {
Object.keys(require.cache).forEach(function (filename) {
if (matcher(filename)) {
delete require.cache[filename];
}
});
}
}
/**
* hooks `require` to return transformed code to the node module loader.
* Exceptions in the transform result in the original code being used instead.
* @method hookRequire
* @static
* @param matcher {Function(filePath)} a function that is called with the absolute path to the file being
* `require`-d. Should return a truthy value when transformations need to be applied to the code, a falsy value otherwise
* @param transformer {Function(code, filePath)} a function called with the original code and the associated path of the file
* from where the code was loaded. Should return the transformed code.
* @param options {Object} options Optional.
* @param {Boolean} [options.verbose] write a line to standard error every time the transformer is called
* @param {Function} [options.postLoadHook] a function that is called with the name of the file being
* required. This is called after the require is processed irrespective of whether it was transformed.
*/
function hookRequire(matcher, transformer, options) {
options = options || {};
var fn = transformFn(matcher, transformer, options.verbose),
postLoadHook = options.postLoadHook &&
typeof options.postLoadHook === 'function' ? options.postLoadHook : null;
Module._extensions['.js'] = function (module, filename) {
var ret = fn(fs.readFileSync(filename, 'utf8'), filename);
if (ret.changed) {
module._compile(ret.code, filename);
} else {
originalLoader(module, filename);
}
if (postLoadHook) {
postLoadHook(filename);
}
};
}
/**
* unhook `require` to restore it to its original state.
* @method unhookRequire
* @static
*/
function unhookRequire() {
Module._extensions['.js'] = originalLoader;
}
/**
* hooks `vm.createScript` to return transformed code out of which a `Script` object will be created.
* Exceptions in the transform result in the original code being used instead.
* @method hookCreateScript
* @static
* @param matcher {Function(filePath)} a function that is called with the filename passed to `vm.createScript`
* Should return a truthy value when transformations need to be applied to the code, a falsy value otherwise
* @param transformer {Function(code, filePath)} a function called with the original code and the filename passed to
* `vm.createScript`. Should return the transformed code.
* @param options {Object} options Optional.
* @param {Boolean} [options.verbose] write a line to standard error every time the transformer is called
*/
function hookCreateScript(matcher, transformer, opts) {
opts = opts || {};
var fn = transformFn(matcher, transformer, opts.verbose);
vm.createScript = function (code, file) {
var ret = fn(code, file);
return originalCreateScript(ret.code, file);
};
}
/**
* unhooks vm.createScript, restoring it to its original state.
* @method unhookCreateScript
* @static
*/
function unhookCreateScript() {
vm.createScript = originalCreateScript;
}
/**
* hooks `vm.runInThisContext` to return transformed code.
* @method hookRunInThisContext
* @static
* @param matcher {Function(filePath)} a function that is called with the filename passed to `vm.createScript`
* Should return a truthy value when transformations need to be applied to the code, a falsy value otherwise
* @param transformer {Function(code, filePath)} a function called with the original code and the filename passed to
* `vm.createScript`. Should return the transformed code.
* @param options {Object} options Optional.
* @param {Boolean} [options.verbose] write a line to standard error every time the transformer is called
*/
function hookRunInThisContext(matcher, transformer, opts) {
opts = opts || {};
var fn = transformFn(matcher, transformer, opts.verbose);
vm.runInThisContext = function (code, file) {
var ret = fn(code, file);
return originalRunInThisContext(ret.code, file);
};
}
/**
* unhooks vm.runInThisContext, restoring it to its original state.
* @method unhookRunInThisContext
* @static
*/
function unhookRunInThisContext() {
vm.runInThisContext = originalRunInThisContext;
}
module.exports = {
hookRequire: hookRequire,
unhookRequire: unhookRequire,
hookCreateScript: hookCreateScript,
unhookCreateScript: unhookCreateScript,
hookRunInThisContext : hookRunInThisContext,
unhookRunInThisContext : unhookRunInThisContext,
unloadRequireCache: unloadRequireCache
};

View File

@ -0,0 +1,966 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
/*global esprima, escodegen, window */
(function (isNode) {
"use strict";
var SYNTAX,
nodeType,
ESP = isNode ? require('esprima') : esprima,
ESPGEN = isNode ? require('escodegen') : escodegen, //TODO - package as dependency
crypto = isNode ? require('crypto') : null,
LEADER_WRAP = '(function () { ',
TRAILER_WRAP = '\n}());',
COMMENT_RE = /^\s*istanbul\s+ignore\s+(if|else|next)(?=\W|$)/,
astgen,
preconditions,
cond,
isArray = Array.isArray;
/* istanbul ignore if: untestable */
if (!isArray) {
isArray = function (thing) { return thing && Object.prototype.toString.call(thing) === '[object Array]'; };
}
if (!isNode) {
preconditions = {
'Could not find esprima': ESP,
'Could not find escodegen': ESPGEN,
'JSON object not in scope': JSON,
'Array does not implement push': [].push,
'Array does not implement unshift': [].unshift
};
/* istanbul ignore next: untestable */
for (cond in preconditions) {
if (preconditions.hasOwnProperty(cond)) {
if (!preconditions[cond]) { throw new Error(cond); }
}
}
}
function generateTrackerVar(filename, omitSuffix) {
var hash, suffix;
if (crypto !== null) {
hash = crypto.createHash('md5');
hash.update(filename);
suffix = hash.digest('base64');
//trim trailing equal signs, turn identifier unsafe chars to safe ones + => _ and / => $
suffix = suffix.replace(new RegExp('=', 'g'), '')
.replace(new RegExp('\\+', 'g'), '_')
.replace(new RegExp('/', 'g'), '$');
} else {
window.__cov_seq = window.__cov_seq || 0;
window.__cov_seq += 1;
suffix = window.__cov_seq;
}
return '__cov_' + (omitSuffix ? '' : suffix);
}
function pushAll(ary, thing) {
if (!isArray(thing)) {
thing = [ thing ];
}
Array.prototype.push.apply(ary, thing);
}
SYNTAX = {
ArrayExpression: [ 'elements' ],
AssignmentExpression: ['left', 'right'],
BinaryExpression: ['left', 'right' ],
BlockStatement: [ 'body' ],
BreakStatement: [ 'label' ],
CallExpression: [ 'callee', 'arguments'],
CatchClause: ['param', 'body'],
ConditionalExpression: [ 'test', 'consequent', 'alternate' ],
ContinueStatement: [ 'label' ],
DebuggerStatement: [ ],
DoWhileStatement: [ 'test', 'body' ],
EmptyStatement: [],
ExpressionStatement: [ 'expression'],
ForInStatement: [ 'left', 'right', 'body' ],
ForStatement: ['init', 'test', 'update', 'body' ],
FunctionDeclaration: ['id', 'params', 'body'],
FunctionExpression: ['id', 'params', 'defaults', 'body'],
Identifier: [],
IfStatement: ['test', 'consequent', 'alternate'],
LabeledStatement: ['label', 'body'],
Literal: [],
LogicalExpression: [ 'left', 'right' ],
MemberExpression: ['object', 'property'],
NewExpression: ['callee', 'arguments'],
ObjectExpression: [ 'properties' ],
Program: [ 'body' ],
Property: [ 'key', 'value'],
ReturnStatement: ['argument'],
SequenceExpression: ['expressions'],
SwitchCase: [ 'test', 'consequent' ],
SwitchStatement: ['discriminant', 'cases' ],
ThisExpression: [],
ThrowStatement: ['argument'],
TryStatement: [ 'block', 'handlers', 'finalizer' ],
UnaryExpression: ['argument'],
UpdateExpression: [ 'argument' ],
VariableDeclaration: [ 'declarations' ],
VariableDeclarator: [ 'id', 'init' ],
WhileStatement: [ 'test', 'body' ],
WithStatement: [ 'object', 'body' ]
};
for (nodeType in SYNTAX) {
/* istanbul ignore else: has own property */
if (SYNTAX.hasOwnProperty(nodeType)) {
SYNTAX[nodeType] = { name: nodeType, children: SYNTAX[nodeType] };
}
}
astgen = {
variable: function (name) { return { type: SYNTAX.Identifier.name, name: name }; },
stringLiteral: function (str) { return { type: SYNTAX.Literal.name, value: String(str) }; },
numericLiteral: function (num) { return { type: SYNTAX.Literal.name, value: Number(num) }; },
statement: function (contents) { return { type: SYNTAX.ExpressionStatement.name, expression: contents }; },
dot: function (obj, field) { return { type: SYNTAX.MemberExpression.name, computed: false, object: obj, property: field }; },
subscript: function (obj, sub) { return { type: SYNTAX.MemberExpression.name, computed: true, object: obj, property: sub }; },
postIncrement: function (obj) { return { type: SYNTAX.UpdateExpression.name, operator: '++', prefix: false, argument: obj }; },
sequence: function (one, two) { return { type: SYNTAX.SequenceExpression.name, expressions: [one, two] }; }
};
function Walker(walkMap, preprocessor, scope, debug) {
this.walkMap = walkMap;
this.preprocessor = preprocessor;
this.scope = scope;
this.debug = debug;
if (this.debug) {
this.level = 0;
this.seq = true;
}
}
function defaultWalker(node, walker) {
var type = node.type,
preprocessor,
postprocessor,
children = SYNTAX[type].children,
// don't run generated nodes thru custom walks otherwise we will attempt to instrument the instrumentation code :)
applyCustomWalker = !!node.loc || node.type === SYNTAX.Program.name,
walkerFn = applyCustomWalker ? walker.walkMap[type] : null,
i,
j,
walkFnIndex,
childType,
childNode,
ret,
childArray,
childElement,
pathElement,
assignNode,
isLast;
/* istanbul ignore if: guard */
if (node.walking) { throw new Error('Infinite regress: Custom walkers may NOT call walker.apply(node)'); }
node.walking = true;
ret = walker.apply(node, walker.preprocessor);
preprocessor = ret.preprocessor;
if (preprocessor) {
delete ret.preprocessor;
ret = walker.apply(node, preprocessor);
}
if (isArray(walkerFn)) {
for (walkFnIndex = 0; walkFnIndex < walkerFn.length; walkFnIndex += 1) {
isLast = walkFnIndex === walkerFn.length - 1;
ret = walker.apply(ret, walkerFn[walkFnIndex]);
/*istanbul ignore next: paranoid check */
if (ret.type !== type && !isLast) {
throw new Error('Only the last walker is allowed to change the node type: [type was: ' + type + ' ]');
}
}
} else {
if (walkerFn) {
ret = walker.apply(node, walkerFn);
}
}
for (i = 0; i < children.length; i += 1) {
childType = children[i];
childNode = node[childType];
if (childNode && !childNode.skipWalk) {
pathElement = { node: node, property: childType };
if (isArray(childNode)) {
childArray = [];
for (j = 0; j < childNode.length; j += 1) {
childElement = childNode[j];
pathElement.index = j;
if (childElement) {
assignNode = walker.apply(childElement, null, pathElement);
if (isArray(assignNode.prepend)) {
pushAll(childArray, assignNode.prepend);
delete assignNode.prepend;
}
}
pushAll(childArray, assignNode);
}
node[childType] = childArray;
} else {
assignNode = walker.apply(childNode, null, pathElement);
/*istanbul ignore if: paranoid check */
if (isArray(assignNode.prepend)) {
throw new Error('Internal error: attempt to prepend statements in disallowed (non-array) context');
/* if this should be allowed, this is how to solve it
tmpNode = { type: 'BlockStatement', body: [] };
pushAll(tmpNode.body, assignNode.prepend);
pushAll(tmpNode.body, assignNode);
node[childType] = tmpNode;
delete assignNode.prepend;
*/
} else {
node[childType] = assignNode;
}
}
}
}
postprocessor = ret.postprocessor;
if (postprocessor) {
delete ret.postprocessor;
ret = walker.apply(ret, postprocessor);
}
delete node.walking;
return ret;
}
Walker.prototype = {
startWalk: function (node) {
this.path = [];
this.apply(node);
},
apply: function (node, walkFn, pathElement) {
var ret, i, seq, prefix;
walkFn = walkFn || defaultWalker;
if (this.debug) {
this.seq += 1;
this.level += 1;
seq = this.seq;
prefix = '';
for (i = 0; i < this.level; i += 1) { prefix += ' '; }
console.log(prefix + 'Enter (' + seq + '):' + node.type);
}
if (pathElement) { this.path.push(pathElement); }
ret = walkFn.call(this.scope, node, this);
if (pathElement) { this.path.pop(); }
if (this.debug) {
this.level -= 1;
console.log(prefix + 'Return (' + seq + '):' + node.type);
}
return ret || node;
},
startLineForNode: function (node) {
return node && node.loc && node.loc.start ? node.loc.start.line : /* istanbul ignore next: guard */ null;
},
ancestor: function (n) {
return this.path.length > n - 1 ? this.path[this.path.length - n] : /* istanbul ignore next: guard */ null;
},
parent: function () {
return this.ancestor(1);
},
isLabeled: function () {
var el = this.parent();
return el && el.node.type === SYNTAX.LabeledStatement.name;
}
};
/**
* mechanism to instrument code for coverage. It uses the `esprima` and
* `escodegen` libraries for JS parsing and code generation respectively.
*
* Works on `node` as well as the browser.
*
* Usage on nodejs
* ---------------
*
* var instrumenter = new require('istanbul').Instrumenter(),
* changed = instrumenter.instrumentSync('function meaningOfLife() { return 42; }', 'filename.js');
*
* Usage in a browser
* ------------------
*
* Load `esprima.js`, `escodegen.js` and `instrumenter.js` (this file) using `script` tags or other means.
*
* Create an instrumenter object as:
*
* var instrumenter = new Instrumenter(),
* changed = instrumenter.instrumentSync('function meaningOfLife() { return 42; }', 'filename.js');
*
* Aside from demonstration purposes, it is unclear why you would want to instrument code in a browser.
*
* @class Instrumenter
* @constructor
* @param {Object} options Optional. Configuration options.
* @param {String} [options.coverageVariable] the global variable name to use for
* tracking coverage. Defaults to `__coverage__`
* @param {Boolean} [options.embedSource] whether to embed the source code of every
* file as an array in the file coverage object for that file. Defaults to `false`
* @param {Boolean} [options.preserveComments] whether comments should be preserved in the output. Defaults to `false`
* @param {Boolean} [options.noCompact] emit readable code when set. Defaults to `false`
* @param {Boolean} [options.noAutoWrap] do not automatically wrap the source in
* an anonymous function before covering it. By default, code is wrapped in
* an anonymous function before it is parsed. This is done because
* some nodejs libraries have `return` statements outside of
* a function which is technically invalid Javascript and causes the parser to fail.
* This construct, however, works correctly in node since module loading
* is done in the context of an anonymous function.
*
* Note that the semantics of the code *returned* by the instrumenter does not change in any way.
* The function wrapper is "unwrapped" before the instrumented code is generated.
* @param {Object} [options.codeGenerationOptions] an object that is directly passed to the `escodegen`
* library as configuration for code generation. The `noCompact` setting is not honored when this
* option is specified
* @param {Boolean} [options.debug] assist in debugging. Currently, the only effect of
* setting this option is a pretty-print of the coverage variable. Defaults to `false`
* @param {Boolean} [options.walkDebug] assist in debugging of the AST walker used by this class.
*
*/
function Instrumenter(options) {
this.opts = options || {
debug: false,
walkDebug: false,
coverageVariable: '__coverage__',
codeGenerationOptions: undefined,
noAutoWrap: false,
noCompact: false,
embedSource: false,
preserveComments: false
};
this.walker = new Walker({
ExpressionStatement: this.coverStatement,
BreakStatement: this.coverStatement,
ContinueStatement: this.coverStatement,
DebuggerStatement: this.coverStatement,
ReturnStatement: this.coverStatement,
ThrowStatement: this.coverStatement,
TryStatement: this.coverStatement,
VariableDeclaration: this.coverStatement,
IfStatement: [ this.ifBlockConverter, this.coverStatement, this.ifBranchInjector ],
ForStatement: [ this.skipInit, this.loopBlockConverter, this.coverStatement ],
ForInStatement: [ this.skipLeft, this.loopBlockConverter, this.coverStatement ],
WhileStatement: [ this.loopBlockConverter, this.coverStatement ],
DoWhileStatement: [ this.loopBlockConverter, this.coverStatement ],
SwitchStatement: [ this.coverStatement, this.switchBranchInjector ],
SwitchCase: [ this.switchCaseInjector ],
WithStatement: [ this.withBlockConverter, this.coverStatement ],
FunctionDeclaration: [ this.coverFunction, this.coverStatement ],
FunctionExpression: this.coverFunction,
LabeledStatement: this.coverStatement,
ConditionalExpression: this.conditionalBranchInjector,
LogicalExpression: this.logicalExpressionBranchInjector,
ObjectExpression: this.maybeAddType
}, this.extractCurrentHint, this, this.opts.walkDebug);
//unit testing purposes only
if (this.opts.backdoor && this.opts.backdoor.omitTrackerSuffix) {
this.omitTrackerSuffix = true;
}
}
Instrumenter.prototype = {
/**
* synchronous instrumentation method. Throws when illegal code is passed to it
* @method instrumentSync
* @param {String} code the code to be instrumented as a String
* @param {String} filename Optional. The name of the file from which
* the code was read. A temporary filename is generated when not specified.
* Not specifying a filename is only useful for unit tests and demonstrations
* of this library.
*/
instrumentSync: function (code, filename) {
var program;
//protect from users accidentally passing in a Buffer object instead
if (typeof code !== 'string') { throw new Error('Code must be string'); }
if (code.charAt(0) === '#') { //shebang, 'comment' it out, won't affect syntax tree locations for things we care about
code = '//' + code;
}
if (!this.opts.noAutoWrap) {
code = LEADER_WRAP + code + TRAILER_WRAP;
}
program = ESP.parse(code, {
loc: true,
range: true,
tokens: this.opts.preserveComments,
comment: true
});
if (this.opts.preserveComments) {
program = ESPGEN.attachComments(program, program.comments, program.tokens);
}
if (!this.opts.noAutoWrap) {
program = {
type: SYNTAX.Program.name,
body: program.body[0].expression.callee.body.body,
comments: program.comments
};
}
return this.instrumentASTSync(program, filename, code);
},
filterHints: function (comments) {
var ret = [],
i,
comment,
groups;
if (!(comments && isArray(comments))) {
return ret;
}
for (i = 0; i < comments.length; i += 1) {
comment = comments[i];
/* istanbul ignore else: paranoid check */
if (comment && comment.value && comment.range && isArray(comment.range)) {
groups = String(comment.value).match(COMMENT_RE);
if (groups) {
ret.push({ type: groups[1], start: comment.range[0], end: comment.range[1] });
}
}
}
return ret;
},
extractCurrentHint: function (node) {
if (!node.range) { return; }
var i = this.currentState.lastHintPosition + 1,
hints = this.currentState.hints,
nodeStart = node.range[0],
hint;
this.currentState.currentHint = null;
while (i < hints.length) {
hint = hints[i];
if (hint.end < nodeStart) {
this.currentState.currentHint = hint;
this.currentState.lastHintPosition = i;
i += 1;
} else {
break;
}
}
},
/**
* synchronous instrumentation method that instruments an AST instead.
* @method instrumentASTSync
* @param {String} program the AST to be instrumented
* @param {String} filename Optional. The name of the file from which
* the code was read. A temporary filename is generated when not specified.
* Not specifying a filename is only useful for unit tests and demonstrations
* of this library.
* @param {String} originalCode the original code corresponding to the AST,
* used for embedding the source into the coverage object
*/
instrumentASTSync: function (program, filename, originalCode) {
var usingStrict = false,
codegenOptions,
generated,
preamble,
lineCount,
i;
filename = filename || String(new Date().getTime()) + '.js';
this.sourceMap = null;
this.coverState = {
path: filename,
s: {},
b: {},
f: {},
fnMap: {},
statementMap: {},
branchMap: {}
};
this.currentState = {
trackerVar: generateTrackerVar(filename, this.omitTrackerSuffix),
func: 0,
branch: 0,
variable: 0,
statement: 0,
hints: this.filterHints(program.comments),
currentHint: null,
lastHintPosition: -1,
ignoring: 0
};
if (program.body && program.body.length > 0 && this.isUseStrictExpression(program.body[0])) {
//nuke it
program.body.shift();
//and add it back at code generation time
usingStrict = true;
}
this.walker.startWalk(program);
codegenOptions = this.opts.codeGenerationOptions || { format: { compact: !this.opts.noCompact }};
codegenOptions.comment = this.opts.preserveComments;
//console.log(JSON.stringify(program, undefined, 2));
generated = ESPGEN.generate(program, codegenOptions);
preamble = this.getPreamble(originalCode || '', usingStrict);
if (generated.map && generated.code) {
lineCount = preamble.split(/\r\n|\r|\n/).length;
// offset all the generated line numbers by the number of lines in the preamble
for (i = 0; i < generated.map._mappings.length; i += 1) {
generated.map._mappings[i].generatedLine += lineCount;
}
this.sourceMap = generated.map;
generated = generated.code;
}
return preamble + '\n' + generated + '\n';
},
/**
* Callback based instrumentation. Note that this still executes synchronously in the same process tick
* and calls back immediately. It only provides the options for callback style error handling as
* opposed to a `try-catch` style and nothing more. Implemented as a wrapper over `instrumentSync`
*
* @method instrument
* @param {String} code the code to be instrumented as a String
* @param {String} filename Optional. The name of the file from which
* the code was read. A temporary filename is generated when not specified.
* Not specifying a filename is only useful for unit tests and demonstrations
* of this library.
* @param {Function(err, instrumentedCode)} callback - the callback function
*/
instrument: function (code, filename, callback) {
if (!callback && typeof filename === 'function') {
callback = filename;
filename = null;
}
try {
callback(null, this.instrumentSync(code, filename));
} catch (ex) {
callback(ex);
}
},
/**
* returns the file coverage object for the code that was instrumented
* just before calling this method. Note that this represents a
* "zero-coverage" object which is not even representative of the code
* being loaded in node or a browser (which would increase the statement
* counts for mainline code).
* @return {Object} a "zero-coverage" file coverage object for the code last instrumented
* by this instrumenter
*/
lastFileCoverage: function () {
return this.coverState;
},
/**
* returns the source map object for the code that was instrumented
* just before calling this method.
* @return {Object} a source map object for the code last instrumented
* by this instrumenter
*/
lastSourceMap: function () {
return this.sourceMap;
},
fixColumnPositions: function (coverState) {
var offset = LEADER_WRAP.length,
fixer = function (loc) {
if (loc.start.line === 1) {
loc.start.column -= offset;
}
if (loc.end.line === 1) {
loc.end.column -= offset;
}
},
k,
obj,
i,
locations;
obj = coverState.statementMap;
for (k in obj) {
/* istanbul ignore else: has own property */
if (obj.hasOwnProperty(k)) { fixer(obj[k]); }
}
obj = coverState.fnMap;
for (k in obj) {
/* istanbul ignore else: has own property */
if (obj.hasOwnProperty(k)) { fixer(obj[k].loc); }
}
obj = coverState.branchMap;
for (k in obj) {
/* istanbul ignore else: has own property */
if (obj.hasOwnProperty(k)) {
locations = obj[k].locations;
for (i = 0; i < locations.length; i += 1) {
fixer(locations[i]);
}
}
}
},
getPreamble: function (sourceCode, emitUseStrict) {
var varName = this.opts.coverageVariable || '__coverage__',
file = this.coverState.path.replace(/\\/g, '\\\\'),
tracker = this.currentState.trackerVar,
coverState,
strictLine = emitUseStrict ? '"use strict";' : '',
// return replacements using the function to ensure that the replacement is
// treated like a dumb string and not as a string with RE replacement patterns
replacer = function (s) {
return function () { return s; };
},
code;
if (!this.opts.noAutoWrap) {
this.fixColumnPositions(this.coverState);
}
if (this.opts.embedSource) {
this.coverState.code = sourceCode.split(/(?:\r?\n)|\r/);
}
coverState = this.opts.debug ? JSON.stringify(this.coverState, undefined, 4) : JSON.stringify(this.coverState);
code = [
"%STRICT%",
"var %VAR% = (Function('return this'))();",
"if (!%VAR%.%GLOBAL%) { %VAR%.%GLOBAL% = {}; }",
"%VAR% = %VAR%.%GLOBAL%;",
"if (!(%VAR%['%FILE%'])) {",
" %VAR%['%FILE%'] = %OBJECT%;",
"}",
"%VAR% = %VAR%['%FILE%'];"
].join("\n")
.replace(/%STRICT%/g, replacer(strictLine))
.replace(/%VAR%/g, replacer(tracker))
.replace(/%GLOBAL%/g, replacer(varName))
.replace(/%FILE%/g, replacer(file))
.replace(/%OBJECT%/g, replacer(coverState));
return code;
},
startIgnore: function () {
this.currentState.ignoring += 1;
},
endIgnore: function () {
this.currentState.ignoring -= 1;
},
convertToBlock: function (node) {
if (!node) {
return { type: 'BlockStatement', body: [] };
} else if (node.type === 'BlockStatement') {
return node;
} else {
return { type: 'BlockStatement', body: [ node ] };
}
},
ifBlockConverter: function (node) {
node.consequent = this.convertToBlock(node.consequent);
node.alternate = this.convertToBlock(node.alternate);
},
loopBlockConverter: function (node) {
node.body = this.convertToBlock(node.body);
},
withBlockConverter: function (node) {
node.body = this.convertToBlock(node.body);
},
statementName: function (location, initValue) {
var sName,
ignoring = !!this.currentState.ignoring;
location.skip = ignoring || undefined;
initValue = initValue || 0;
this.currentState.statement += 1;
sName = this.currentState.statement;
this.coverState.statementMap[sName] = location;
this.coverState.s[sName] = initValue;
return sName;
},
skipInit: function (node /*, walker */) {
if (node.init) {
node.init.skipWalk = true;
}
},
skipLeft: function (node /*, walker */) {
node.left.skipWalk = true;
},
isUseStrictExpression: function (node) {
return node && node.type === SYNTAX.ExpressionStatement.name &&
node.expression && node.expression.type === SYNTAX.Literal.name &&
node.expression.value === 'use strict';
},
maybeSkipNode: function (node, type) {
var alreadyIgnoring = !!this.currentState.ignoring,
hint = this.currentState.currentHint,
ignoreThis = !alreadyIgnoring && hint && hint.type === type;
if (ignoreThis) {
this.startIgnore();
node.postprocessor = this.endIgnore;
return true;
}
return false;
},
coverStatement: function (node, walker) {
var sName,
incrStatementCount,
grandParent;
this.maybeSkipNode(node, 'next');
if (this.isUseStrictExpression(node)) {
grandParent = walker.ancestor(2);
/* istanbul ignore else: difficult to test */
if (grandParent) {
if ((grandParent.node.type === SYNTAX.FunctionExpression.name ||
grandParent.node.type === SYNTAX.FunctionDeclaration.name) &&
walker.parent().node.body[0] === node) {
return;
}
}
}
if (node.type === SYNTAX.FunctionDeclaration.name) {
sName = this.statementName(node.loc, 1);
} else {
sName = this.statementName(node.loc);
incrStatementCount = astgen.statement(
astgen.postIncrement(
astgen.subscript(
astgen.dot(astgen.variable(this.currentState.trackerVar), astgen.variable('s')),
astgen.stringLiteral(sName)
)
)
);
this.splice(incrStatementCount, node, walker);
}
},
splice: function (statements, node, walker) {
var targetNode = walker.isLabeled() ? walker.parent().node : node;
targetNode.prepend = targetNode.prepend || [];
pushAll(targetNode.prepend, statements);
},
functionName: function (node, line, location) {
this.currentState.func += 1;
var id = this.currentState.func,
ignoring = !!this.currentState.ignoring,
name = node.id ? node.id.name : '(anonymous_' + id + ')';
this.coverState.fnMap[id] = { name: name, line: line, loc: location, skip: ignoring || undefined };
this.coverState.f[id] = 0;
return id;
},
coverFunction: function (node, walker) {
var id,
body = node.body,
blockBody = body.body,
popped;
this.maybeSkipNode(node, 'next');
id = this.functionName(node, walker.startLineForNode(node), {
start: node.loc.start,
end: { line: node.body.loc.start.line, column: node.body.loc.start.column }
});
if (blockBody.length > 0 && this.isUseStrictExpression(blockBody[0])) {
popped = blockBody.shift();
}
blockBody.unshift(
astgen.statement(
astgen.postIncrement(
astgen.subscript(
astgen.dot(astgen.variable(this.currentState.trackerVar), astgen.variable('f')),
astgen.stringLiteral(id)
)
)
)
);
if (popped) {
blockBody.unshift(popped);
}
},
branchName: function (type, startLine, pathLocations) {
var bName,
paths = [],
locations = [],
i,
ignoring = !!this.currentState.ignoring;
this.currentState.branch += 1;
bName = this.currentState.branch;
for (i = 0; i < pathLocations.length; i += 1) {
pathLocations[i].skip = pathLocations[i].skip || ignoring || undefined;
locations.push(pathLocations[i]);
paths.push(0);
}
this.coverState.b[bName] = paths;
this.coverState.branchMap[bName] = { line: startLine, type: type, locations: locations };
return bName;
},
branchIncrementExprAst: function (varName, branchIndex, down) {
var ret = astgen.postIncrement(
astgen.subscript(
astgen.subscript(
astgen.dot(astgen.variable(this.currentState.trackerVar), astgen.variable('b')),
astgen.stringLiteral(varName)
),
astgen.numericLiteral(branchIndex)
),
down
);
return ret;
},
locationsForNodes: function (nodes) {
var ret = [],
i;
for (i = 0; i < nodes.length; i += 1) {
ret.push(nodes[i].loc);
}
return ret;
},
ifBranchInjector: function (node, walker) {
var alreadyIgnoring = !!this.currentState.ignoring,
hint = this.currentState.currentHint,
ignoreThen = !alreadyIgnoring && hint && hint.type === 'if',
ignoreElse = !alreadyIgnoring && hint && hint.type === 'else',
line = node.loc.start.line,
col = node.loc.start.column,
start = { line: line, column: col },
end = { line: line, column: col },
bName = this.branchName('if', walker.startLineForNode(node), [
{ start: start, end: end, skip: ignoreThen || undefined },
{ start: start, end: end, skip: ignoreElse || undefined }
]),
thenBody = node.consequent.body,
elseBody = node.alternate.body,
child;
thenBody.unshift(astgen.statement(this.branchIncrementExprAst(bName, 0)));
elseBody.unshift(astgen.statement(this.branchIncrementExprAst(bName, 1)));
if (ignoreThen) { child = node.consequent; child.preprocessor = this.startIgnore; child.postprocessor = this.endIgnore; }
if (ignoreElse) { child = node.alternate; child.preprocessor = this.startIgnore; child.postprocessor = this.endIgnore; }
},
branchLocationFor: function (name, index) {
return this.coverState.branchMap[name].locations[index];
},
switchBranchInjector: function (node, walker) {
var cases = node.cases,
bName,
i;
if (!(cases && cases.length > 0)) {
return;
}
bName = this.branchName('switch', walker.startLineForNode(node), this.locationsForNodes(cases));
for (i = 0; i < cases.length; i += 1) {
cases[i].branchLocation = this.branchLocationFor(bName, i);
cases[i].consequent.unshift(astgen.statement(this.branchIncrementExprAst(bName, i)));
}
},
switchCaseInjector: function (node) {
var location = node.branchLocation;
delete node.branchLocation;
if (this.maybeSkipNode(node, 'next')) {
location.skip = true;
}
},
conditionalBranchInjector: function (node, walker) {
var bName = this.branchName('cond-expr', walker.startLineForNode(node), this.locationsForNodes([ node.consequent, node.alternate ])),
ast1 = this.branchIncrementExprAst(bName, 0),
ast2 = this.branchIncrementExprAst(bName, 1);
node.consequent.preprocessor = this.maybeAddSkip(this.branchLocationFor(bName, 0));
node.alternate.preprocessor = this.maybeAddSkip(this.branchLocationFor(bName, 1));
node.consequent = astgen.sequence(ast1, node.consequent);
node.alternate = astgen.sequence(ast2, node.alternate);
},
maybeAddSkip: function (branchLocation) {
return function (node) {
var alreadyIgnoring = !!this.currentState.ignoring,
hint = this.currentState.currentHint,
ignoreThis = !alreadyIgnoring && hint && hint.type === 'next';
if (ignoreThis) {
this.startIgnore();
node.postprocessor = this.endIgnore;
}
if (ignoreThis || alreadyIgnoring) {
branchLocation.skip = true;
}
};
},
logicalExpressionBranchInjector: function (node, walker) {
var parent = walker.parent(),
leaves = [],
bName,
tuple,
i;
this.maybeSkipNode(node, 'next');
if (parent && parent.node.type === SYNTAX.LogicalExpression.name) {
//already covered
return;
}
this.findLeaves(node, leaves);
bName = this.branchName('binary-expr',
walker.startLineForNode(node),
this.locationsForNodes(leaves.map(function (item) { return item.node; }))
);
for (i = 0; i < leaves.length; i += 1) {
tuple = leaves[i];
tuple.parent[tuple.property] = astgen.sequence(this.branchIncrementExprAst(bName, i), tuple.node);
tuple.node.preprocessor = this.maybeAddSkip(this.branchLocationFor(bName, i));
}
},
findLeaves: function (node, accumulator, parent, property) {
if (node.type === SYNTAX.LogicalExpression.name) {
this.findLeaves(node.left, accumulator, node, 'left');
this.findLeaves(node.right, accumulator, node, 'right');
} else {
accumulator.push({ node: node, parent: parent, property: property });
}
},
maybeAddType: function (node /*, walker */) {
var props = node.properties,
i,
child;
for (i = 0; i < props.length; i += 1) {
child = props[i];
if (!child.type) {
child.type = SYNTAX.Property.name;
}
}
}
};
if (isNode) {
module.exports = Instrumenter;
} else {
window.Instrumenter = Instrumenter;
}
}(typeof module !== 'undefined' && typeof module.exports !== 'undefined' && typeof exports !== 'undefined'));

View File

@ -0,0 +1,367 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
/**
* utility methods to process coverage objects. A coverage object has the following
* format.
*
* {
* "/path/to/file1.js": { file1 coverage },
* "/path/to/file2.js": { file2 coverage }
* }
*
* The internals of the file coverage object are intentionally not documented since
* it is not a public interface.
*
* *Note:* When a method of this module has the word `File` in it, it will accept
* one of the sub-objects of the main coverage object as an argument. Other
* methods accept the higher level coverage object with multiple keys.
*
* Works on `node` as well as the browser.
*
* Usage on nodejs
* ---------------
*
* var objectUtils = require('istanbul').utils;
*
* Usage in a browser
* ------------------
*
* Load this file using a `script` tag or other means. This will set `window.coverageUtils`
* to this module's exports.
*
* @class ObjectUtils
* @static
*/
(function (isNode) {
/**
* adds line coverage information to a file coverage object, reverse-engineering
* it from statement coverage. The object passed in is updated in place.
*
* Note that if line coverage information is already present in the object,
* it is not recomputed.
*
* @method addDerivedInfoForFile
* @static
* @param {Object} fileCoverage the coverage object for a single file
*/
function addDerivedInfoForFile(fileCoverage) {
var statementMap = fileCoverage.statementMap,
statements = fileCoverage.s,
lineMap;
if (!fileCoverage.l) {
fileCoverage.l = lineMap = {};
Object.keys(statements).forEach(function (st) {
var line = statementMap[st].start.line,
count = statements[st],
prevVal = lineMap[line];
if (count === 0 && statementMap[st].skip) { count = 1; }
if (typeof prevVal === 'undefined' || prevVal < count) {
lineMap[line] = count;
}
});
}
}
/**
* adds line coverage information to all file coverage objects.
*
* @method addDerivedInfo
* @static
* @param {Object} coverage the coverage object
*/
function addDerivedInfo(coverage) {
Object.keys(coverage).forEach(function (k) {
addDerivedInfoForFile(coverage[k]);
});
}
/**
* removes line coverage information from all file coverage objects
* @method removeDerivedInfo
* @static
* @param {Object} coverage the coverage object
*/
function removeDerivedInfo(coverage) {
Object.keys(coverage).forEach(function (k) {
delete coverage[k].l;
});
}
function percent(covered, total) {
var tmp;
if (total > 0) {
tmp = 1000 * 100 * covered / total + 5;
return Math.floor(tmp / 10) / 100;
} else {
return 100.00;
}
}
function computeSimpleTotals(fileCoverage, property, mapProperty) {
var stats = fileCoverage[property],
map = mapProperty ? fileCoverage[mapProperty] : null,
ret = { total: 0, covered: 0, skipped: 0 };
Object.keys(stats).forEach(function (key) {
var covered = !!stats[key],
skipped = map && map[key].skip;
ret.total += 1;
if (covered || skipped) {
ret.covered += 1;
}
if (!covered && skipped) {
ret.skipped += 1;
}
});
ret.pct = percent(ret.covered, ret.total);
return ret;
}
function computeBranchTotals(fileCoverage) {
var stats = fileCoverage.b,
branchMap = fileCoverage.branchMap,
ret = { total: 0, covered: 0, skipped: 0 };
Object.keys(stats).forEach(function (key) {
var branches = stats[key],
map = branchMap[key],
covered,
skipped,
i;
for (i = 0; i < branches.length; i += 1) {
covered = branches[i] > 0;
skipped = map.locations && map.locations[i] && map.locations[i].skip;
if (covered || skipped) {
ret.covered += 1;
}
if (!covered && skipped) {
ret.skipped += 1;
}
}
ret.total += branches.length;
});
ret.pct = percent(ret.covered, ret.total);
return ret;
}
/**
* returns a blank summary metrics object. A metrics object has the following
* format.
*
* {
* lines: lineMetrics,
* statements: statementMetrics,
* functions: functionMetrics,
* branches: branchMetrics
* }
*
* Each individual metric object looks as follows:
*
* {
* total: n,
* covered: m,
* pct: percent
* }
*
* @method blankSummary
* @static
* @return {Object} a blank metrics object
*/
function blankSummary() {
return {
lines: {
total: 0,
covered: 0,
skipped: 0,
pct: 'Unknown'
},
statements: {
total: 0,
covered: 0,
skipped: 0,
pct: 'Unknown'
},
functions: {
total: 0,
covered: 0,
skipped: 0,
pct: 'Unknown'
},
branches: {
total: 0,
covered: 0,
skipped: 0,
pct: 'Unknown'
}
};
}
/**
* returns the summary metrics given the coverage object for a single file. See `blankSummary()`
* to understand the format of the returned object.
*
* @method summarizeFileCoverage
* @static
* @param {Object} fileCoverage the coverage object for a single file.
* @return {Object} the summary metrics for the file
*/
function summarizeFileCoverage(fileCoverage) {
var ret = blankSummary();
addDerivedInfoForFile(fileCoverage);
ret.lines = computeSimpleTotals(fileCoverage, 'l');
ret.functions = computeSimpleTotals(fileCoverage, 'f', 'fnMap');
ret.statements = computeSimpleTotals(fileCoverage, 's', 'statementMap');
ret.branches = computeBranchTotals(fileCoverage);
return ret;
}
/**
* merges two instances of file coverage objects *for the same file*
* such that the execution counts are correct.
*
* @method mergeFileCoverage
* @static
* @param {Object} first the first file coverage object for a given file
* @param {Object} second the second file coverage object for the same file
* @return {Object} an object that is a result of merging the two. Note that
* the input objects are not changed in any way.
*/
function mergeFileCoverage(first, second) {
var ret = JSON.parse(JSON.stringify(first)),
i;
delete ret.l; //remove derived info
Object.keys(second.s).forEach(function (k) {
ret.s[k] += second.s[k];
});
Object.keys(second.f).forEach(function (k) {
ret.f[k] += second.f[k];
});
Object.keys(second.b).forEach(function (k) {
var retArray = ret.b[k],
secondArray = second.b[k];
for (i = 0; i < retArray.length; i += 1) {
retArray[i] += secondArray[i];
}
});
return ret;
}
/**
* merges multiple summary metrics objects by summing up the `totals` and
* `covered` fields and recomputing the percentages. This function is generic
* and can accept any number of arguments.
*
* @method mergeSummaryObjects
* @static
* @param {Object} summary... multiple summary metrics objects
* @return {Object} the merged summary metrics
*/
function mergeSummaryObjects() {
var ret = blankSummary(),
args = Array.prototype.slice.call(arguments),
keys = ['lines', 'statements', 'branches', 'functions'],
increment = function (obj) {
if (obj) {
keys.forEach(function (key) {
ret[key].total += obj[key].total;
ret[key].covered += obj[key].covered;
ret[key].skipped += obj[key].skipped;
});
}
};
args.forEach(function (arg) {
increment(arg);
});
keys.forEach(function (key) {
ret[key].pct = percent(ret[key].covered, ret[key].total);
});
return ret;
}
/**
* returns the coverage summary for a single coverage object. This is
* wrapper over `summarizeFileCoverage` and `mergeSummaryObjects` for
* the common case of a single coverage object
* @method summarizeCoverage
* @static
* @param {Object} coverage the coverage object
* @return {Object} summary coverage metrics across all files in the coverage object
*/
function summarizeCoverage(coverage) {
var fileSummary = [];
Object.keys(coverage).forEach(function (key) {
fileSummary.push(summarizeFileCoverage(coverage[key]));
});
return mergeSummaryObjects.apply(null, fileSummary);
}
/**
* makes the coverage object generated by this library yuitest_coverage compatible.
* Note that this transformation is lossy since the returned object will not have
* statement and branch coverage.
*
* @method toYUICoverage
* @static
* @param {Object} coverage The `istanbul` coverage object
* @return {Object} a coverage object in `yuitest_coverage` format.
*/
function toYUICoverage(coverage) {
var ret = {};
addDerivedInfo(coverage);
Object.keys(coverage).forEach(function (k) {
var fileCoverage = coverage[k],
lines = fileCoverage.l,
functions = fileCoverage.f,
fnMap = fileCoverage.fnMap,
o;
o = ret[k] = {
lines: {},
calledLines: 0,
coveredLines: 0,
functions: {},
calledFunctions: 0,
coveredFunctions: 0
};
Object.keys(lines).forEach(function (k) {
o.lines[k] = lines[k];
o.coveredLines += 1;
if (lines[k] > 0) {
o.calledLines += 1;
}
});
Object.keys(functions).forEach(function (k) {
var name = fnMap[k].name + ':' + fnMap[k].line;
o.functions[name] = functions[k];
o.coveredFunctions += 1;
if (functions[k] > 0) {
o.calledFunctions += 1;
}
});
});
return ret;
}
var exportables = {
addDerivedInfo: addDerivedInfo,
addDerivedInfoForFile: addDerivedInfoForFile,
removeDerivedInfo: removeDerivedInfo,
blankSummary: blankSummary,
summarizeFileCoverage: summarizeFileCoverage,
summarizeCoverage: summarizeCoverage,
mergeFileCoverage: mergeFileCoverage,
mergeSummaryObjects: mergeSummaryObjects,
toYUICoverage: toYUICoverage
};
/* istanbul ignore else: windows */
if (isNode) {
module.exports = exportables;
} else {
window.coverageUtils = exportables;
}
}(typeof module !== 'undefined' && typeof module.exports !== 'undefined' && typeof exports !== 'undefined'));

View File

@ -0,0 +1,15 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Store = require('./store'),
Report = require('./report'),
Command = require('./command');
Store.loadAll();
Report.loadAll();
Command.loadAll();

View File

@ -0,0 +1,198 @@
var path = require('path'),
Report = require('./index'),
FileWriter = require('../util/file-writer'),
TreeSummarizer = require('../util/tree-summarizer'),
utils = require('../object-utils');
function CloverReport(opts) {
Report.call(this);
opts = opts || {};
this.projectRoot = process.cwd();
this.dir = opts.dir || this.projectRoot;
this.file = opts.file || 'clover.xml';
this.opts = opts;
}
CloverReport.TYPE = 'clover';
function asJavaPackage(node) {
return node.displayShortName().
replace(/\//g, '.').
replace(/\\/g, '.').
replace(/\.$/, '');
}
function asClassName(node) {
/*jslint regexp: true */
return node.fullPath().replace(/.*[\\\/]/, '');
}
function quote(thing) {
return '"' + thing + '"';
}
function attr(n, v) {
return ' ' + n + '=' + quote(v) + ' ';
}
function branchCoverageByLine(fileCoverage) {
var branchMap = fileCoverage.branchMap,
branches = fileCoverage.b,
ret = {};
Object.keys(branchMap).forEach(function (k) {
var line = branchMap[k].line,
branchData = branches[k];
ret[line] = ret[line] || [];
ret[line].push.apply(ret[line], branchData);
});
Object.keys(ret).forEach(function (k) {
var dataArray = ret[k],
covered = dataArray.filter(function (item) { return item > 0; }),
coverage = covered.length / dataArray.length * 100;
ret[k] = { covered: covered.length, total: dataArray.length, coverage: coverage };
});
return ret;
}
function addClassStats(node, fileCoverage, writer) {
var metrics = node.metrics,
branchByLine = branchCoverageByLine(fileCoverage),
fnMap,
lines;
writer.println('\t\t\t<file' +
attr('name', asClassName(node)) +
attr('path', node.fullPath()) +
'>');
writer.println('\t\t\t\t<metrics' +
attr('statements', metrics.lines.total) +
attr('coveredstatements', metrics.lines.covered) +
attr('conditionals', metrics.branches.total) +
attr('coveredconditionals', metrics.branches.covered) +
attr('methods', metrics.functions.total) +
attr('coveredmethods', metrics.functions.covered) +
'/>');
fnMap = fileCoverage.fnMap;
lines = fileCoverage.l;
Object.keys(lines).forEach(function (k) {
var str = '\t\t\t\t<line' +
attr('num', k) +
attr('count', lines[k]),
branchDetail = branchByLine[k];
if (!branchDetail) {
str += ' type="stmt" ';
} else {
str += ' type="cond" ' +
attr('truecount', branchDetail.covered) +
attr('falsecount', (branchDetail.total - branchDetail.covered));
}
writer.println(str + '/>');
});
writer.println('\t\t\t</file>');
}
function walk(node, collector, writer, level, projectRoot) {
var metrics,
totalFiles = 0,
totalPackages = 0,
totalLines = 0,
tempLines = 0;
if (level === 0) {
metrics = node.metrics;
writer.println('<?xml version="1.0" encoding="UTF-8"?>');
writer.println('<coverage' +
attr('generated', Date.now()) +
'clover="3.2.0">');
writer.println('\t<project' +
attr('timestamp', Date.now()) +
attr('name', 'All Files') +
'>');
node.children.filter(function (child) { return child.kind === 'dir'; }).
forEach(function (child) {
totalPackages += 1;
child.children.filter(function (child) { return child.kind !== 'dir'; }).
forEach(function (child) {
Object.keys(collector.fileCoverageFor(child.fullPath()).l).forEach(function (k){
tempLines = k;
});
totalLines += Number(tempLines);
totalFiles += 1;
});
});
writer.println('\t\t<metrics' +
attr('statements', metrics.lines.total) +
attr('coveredstatements', metrics.lines.covered) +
attr('conditionals', metrics.branches.total) +
attr('coveredconditionals', metrics.branches.covered) +
attr('methods', metrics.functions.total) +
attr('coveredmethods', metrics.functions.covered) +
attr('elements', metrics.lines.total + metrics.branches.total + metrics.functions.total) +
attr('coveredelements', metrics.lines.covered + metrics.branches.covered + metrics.functions.covered) +
attr('complexity', 0) +
attr('packages', totalPackages) +
attr('files', totalFiles) +
attr('classes', totalFiles) +
attr('loc', totalLines) +
attr('ncloc', totalLines) +
'/>');
}
if (node.packageMetrics) {
metrics = node.packageMetrics;
writer.println('\t\t<package' +
attr('name', asJavaPackage(node)) +
'>');
writer.println('\t\t\t<metrics' +
attr('statements', metrics.lines.total) +
attr('coveredstatements', metrics.lines.covered) +
attr('conditionals', metrics.branches.total) +
attr('coveredconditionals', metrics.branches.covered) +
attr('methods', metrics.functions.total) +
attr('coveredmethods', metrics.functions.covered) +
'/>');
node.children.filter(function (child) { return child.kind !== 'dir'; }).
forEach(function (child) {
addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer);
});
writer.println('\t\t</package>');
}
node.children.filter(function (child) { return child.kind === 'dir'; }).
forEach(function (child) {
walk(child, collector, writer, level + 1, projectRoot);
});
if (level === 0) {
writer.println('\t</project>');
writer.println('</coverage>');
}
}
Report.mix(CloverReport, {
writeReport: function (collector, sync) {
var summarizer = new TreeSummarizer(),
outputFile = path.join(this.dir, this.file),
writer = this.opts.writer || new FileWriter(sync),
projectRoot = this.projectRoot,
tree,
root;
collector.files().forEach(function (key) {
summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
});
tree = summarizer.getTreeSummary();
root = tree.root;
writer.writeFile(outputFile, function (contentWriter) {
walk(root, collector, contentWriter, 0, projectRoot);
});
}
});
module.exports = CloverReport;

View File

@ -0,0 +1,207 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
Report = require('./index'),
FileWriter = require('../util/file-writer'),
TreeSummarizer = require('../util/tree-summarizer'),
utils = require('../object-utils');
/**
* a `Report` implementation that produces a cobertura-style XML file that conforms to the
* http://cobertura.sourceforge.net/xml/coverage-04.dtd DTD.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('cobertura');
*
* @class CoberturaReport
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the cobertura-coverage.xml will be written
*/
function CoberturaReport(opts) {
Report.call(this);
opts = opts || {};
this.projectRoot = process.cwd();
this.dir = opts.dir || this.projectRoot;
this.file = opts.file || 'cobertura-coverage.xml';
this.opts = opts;
}
CoberturaReport.TYPE = 'cobertura';
function asJavaPackage(node) {
return node.displayShortName().
replace(/\//g, '.').
replace(/\\/g, '.').
replace(/\.$/, '');
}
function asClassName(node) {
/*jslint regexp: true */
return node.fullPath().replace(/.*[\\\/]/, '');
}
function quote(thing) {
return '"' + thing + '"';
}
function attr(n, v) {
return ' ' + n + '=' + quote(v) + ' ';
}
function branchCoverageByLine(fileCoverage) {
var branchMap = fileCoverage.branchMap,
branches = fileCoverage.b,
ret = {};
Object.keys(branchMap).forEach(function (k) {
var line = branchMap[k].line,
branchData = branches[k];
ret[line] = ret[line] || [];
ret[line].push.apply(ret[line], branchData);
});
Object.keys(ret).forEach(function (k) {
var dataArray = ret[k],
covered = dataArray.filter(function (item) { return item > 0; }),
coverage = covered.length / dataArray.length * 100;
ret[k] = { covered: covered.length, total: dataArray.length, coverage: coverage };
});
return ret;
}
function addClassStats(node, fileCoverage, writer, projectRoot) {
var metrics = node.metrics,
branchByLine = branchCoverageByLine(fileCoverage),
fnMap,
lines;
writer.println('\t\t<class' +
attr('name', asClassName(node)) +
attr('filename', path.relative(projectRoot, node.fullPath())) +
attr('line-rate', metrics.lines.pct / 100.0) +
attr('branch-rate', metrics.branches.pct / 100.0) +
'>');
writer.println('\t\t<methods>');
fnMap = fileCoverage.fnMap;
Object.keys(fnMap).forEach(function (k) {
var name = fnMap[k].name,
hits = fileCoverage.f[k];
writer.println(
'\t\t\t<method' +
attr('name', name) +
attr('hits', hits) +
attr('signature', '()V') + //fake out a no-args void return
'>'
);
//Add the function definition line and hits so that jenkins cobertura plugin records method hits
writer.println(
'\t\t\t\t<lines>' +
'<line' +
attr('number', fnMap[k].line) +
attr('hits', fileCoverage.f[k]) +
'/>' +
'</lines>'
);
writer.println('\t\t\t</method>');
});
writer.println('\t\t</methods>');
writer.println('\t\t<lines>');
lines = fileCoverage.l;
Object.keys(lines).forEach(function (k) {
var str = '\t\t\t<line' +
attr('number', k) +
attr('hits', lines[k]),
branchDetail = branchByLine[k];
if (!branchDetail) {
str += attr('branch', false);
} else {
str += attr('branch', true) +
attr('condition-coverage', branchDetail.coverage +
'% (' + branchDetail.covered + '/' + branchDetail.total + ')');
}
writer.println(str + '/>');
});
writer.println('\t\t</lines>');
writer.println('\t\t</class>');
}
function walk(node, collector, writer, level, projectRoot) {
var metrics;
if (level === 0) {
metrics = node.metrics;
writer.println('<?xml version="1.0" ?>');
writer.println('<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">');
writer.println('<coverage' +
attr('lines-valid', metrics.lines.total) +
attr('lines-covered', metrics.lines.covered) +
attr('line-rate', metrics.lines.pct / 100.0) +
attr('branches-valid', metrics.branches.total) +
attr('branches-covered', metrics.branches.covered) +
attr('branch-rate', metrics.branches.pct / 100.0) +
attr('timestamp', Date.now()) +
'complexity="0" version="0.1">');
writer.println('<sources>');
writer.println('\t<source>' + projectRoot + '</source>');
writer.println('</sources>');
writer.println('<packages>');
}
if (node.packageMetrics) {
metrics = node.packageMetrics;
writer.println('\t<package' +
attr('name', asJavaPackage(node)) +
attr('line-rate', metrics.lines.pct / 100.0) +
attr('branch-rate', metrics.branches.pct / 100.0) +
'>');
writer.println('\t<classes>');
node.children.filter(function (child) { return child.kind !== 'dir'; }).
forEach(function (child) {
addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer, projectRoot);
});
writer.println('\t</classes>');
writer.println('\t</package>');
}
node.children.filter(function (child) { return child.kind === 'dir'; }).
forEach(function (child) {
walk(child, collector, writer, level + 1, projectRoot);
});
if (level === 0) {
writer.println('</packages>');
writer.println('</coverage>');
}
}
Report.mix(CoberturaReport, {
writeReport: function (collector, sync) {
var summarizer = new TreeSummarizer(),
outputFile = path.join(this.dir, this.file),
writer = this.opts.writer || new FileWriter(sync),
projectRoot = this.projectRoot,
tree,
root;
collector.files().forEach(function (key) {
summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
});
tree = summarizer.getTreeSummary();
root = tree.root;
writer.writeFile(outputFile, function (contentWriter) {
walk(root, collector, contentWriter, 0, projectRoot);
});
}
});
module.exports = CoberturaReport;

View File

@ -0,0 +1,33 @@
/*
Copyright (c) 2013, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
module.exports = {
watermarks: function () {
return {
statements: [ 50, 80 ],
lines: [ 50, 80 ],
functions: [ 50, 80],
branches: [ 50, 80 ]
};
},
classFor: function (type, metrics, watermarks) {
var mark = watermarks[type],
value = metrics[type].pct;
return value >= mark[1] ? 'high' : value >= mark[0] ? 'medium' : 'low';
},
colorize: function (str, clazz) {
/* istanbul ignore if: untestable in batch mode */
if (process.stdout.isTTY) {
switch (clazz) {
case 'low' : str = '\033[91m' + str + '\033[0m'; break;
case 'medium': str = '\033[93m' + str + '\033[0m'; break;
case 'high': str = '\033[92m' + str + '\033[0m'; break;
}
}
return str;
}
};

View File

@ -0,0 +1,538 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
/*jshint maxlen: 300 */
var handlebars = require('handlebars'),
defaults = require('./common/defaults'),
path = require('path'),
SEP = path.sep || '/',
fs = require('fs'),
util = require('util'),
FileWriter = require('../util/file-writer'),
Report = require('./index'),
Store = require('../store'),
InsertionText = require('../util/insertion-text'),
TreeSummarizer = require('../util/tree-summarizer'),
utils = require('../object-utils'),
templateFor = function (name) { return handlebars.compile(fs.readFileSync(path.resolve(__dirname, 'templates', name + '.txt'), 'utf8')); },
headerTemplate = templateFor('head'),
footerTemplate = templateFor('foot'),
pathTemplate = handlebars.compile('<div class="path">{{{html}}}</div>'),
detailTemplate = handlebars.compile([
'<tr>',
'<td class="line-count">{{#show_lines}}{{maxLines}}{{/show_lines}}</td>',
'<td class="line-coverage">{{#show_line_execution_counts fileCoverage}}{{maxLines}}{{/show_line_execution_counts}}</td>',
'<td class="text"><pre class="prettyprint lang-js">{{#show_code structured}}{{/show_code}}</pre></td>',
'</tr>\n'
].join('')),
summaryTableHeader = [
'<div class="coverage-summary">',
'<table>',
'<thead>',
'<tr>',
' <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>',
' <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>',
' <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>',
' <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>',
' <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>',
' <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>',
' <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>',
' <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>',
' <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>',
' <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>',
'</tr>',
'</thead>',
'<tbody>'
].join('\n'),
summaryLineTemplate = handlebars.compile([
'<tr>',
'<td class="file {{reportClasses.statements}}" data-value="{{file}}"><a href="{{output}}">{{file}}</a></td>',
'<td data-value="{{metrics.statements.pct}}" class="pic {{reportClasses.statements}}">{{#show_picture}}{{metrics.statements.pct}}{{/show_picture}}</td>',
'<td data-value="{{metrics.statements.pct}}" class="pct {{reportClasses.statements}}">{{metrics.statements.pct}}%</td>',
'<td data-value="{{metrics.statements.total}}" class="abs {{reportClasses.statements}}">({{metrics.statements.covered}}&nbsp;/&nbsp;{{metrics.statements.total}})</td>',
'<td data-value="{{metrics.branches.pct}}" class="pct {{reportClasses.branches}}">{{metrics.branches.pct}}%</td>',
'<td data-value="{{metrics.branches.total}}" class="abs {{reportClasses.branches}}">({{metrics.branches.covered}}&nbsp;/&nbsp;{{metrics.branches.total}})</td>',
'<td data-value="{{metrics.functions.pct}}" class="pct {{reportClasses.functions}}">{{metrics.functions.pct}}%</td>',
'<td data-value="{{metrics.functions.total}}" class="abs {{reportClasses.functions}}">({{metrics.functions.covered}}&nbsp;/&nbsp;{{metrics.functions.total}})</td>',
'<td data-value="{{metrics.lines.pct}}" class="pct {{reportClasses.lines}}">{{metrics.lines.pct}}%</td>',
'<td data-value="{{metrics.lines.total}}" class="abs {{reportClasses.lines}}">({{metrics.lines.covered}}&nbsp;/&nbsp;{{metrics.lines.total}})</td>',
'</tr>\n'
].join('\n\t')),
summaryTableFooter = [
'</tbody>',
'</table>',
'</div>'
].join('\n'),
lt = '\u0001',
gt = '\u0002',
RE_LT = /</g,
RE_GT = />/g,
RE_AMP = /&/g,
RE_lt = /\u0001/g,
RE_gt = /\u0002/g;
handlebars.registerHelper('show_picture', function (opts) {
var num = Number(opts.fn(this)),
rest,
cls = '';
if (isFinite(num)) {
if (num === 100) {
cls = ' cover-full';
}
num = Math.floor(num);
rest = 100 - num;
return '<span class="cover-fill' + cls + '" style="width: ' + num + 'px;"></span>' +
'<span class="cover-empty" style="width:' + rest + 'px;"></span>';
} else {
return '';
}
});
handlebars.registerHelper('show_ignores', function (metrics) {
var statements = metrics.statements.skipped,
functions = metrics.functions.skipped,
branches = metrics.branches.skipped,
result;
if (statements === 0 && functions === 0 && branches === 0) {
return '<span class="ignore-none">none</span>';
}
result = [];
if (statements >0) { result.push(statements === 1 ? '1 statement': statements + ' statements'); }
if (functions >0) { result.push(functions === 1 ? '1 function' : functions + ' functions'); }
if (branches >0) { result.push(branches === 1 ? '1 branch' : branches + ' branches'); }
return result.join(', ');
});
handlebars.registerHelper('show_lines', function (opts) {
var maxLines = Number(opts.fn(this)),
i,
array = [];
for (i = 0; i < maxLines; i += 1) {
array[i] = i + 1;
}
return array.join('\n');
});
handlebars.registerHelper('show_line_execution_counts', function (context, opts) {
var lines = context.l,
maxLines = Number(opts.fn(this)),
i,
lineNumber,
array = [],
covered,
value = '';
for (i = 0; i < maxLines; i += 1) {
lineNumber = i + 1;
value = '&nbsp;';
covered = 'neutral';
if (lines.hasOwnProperty(lineNumber)) {
if (lines[lineNumber] > 0) {
covered = 'yes';
value = lines[lineNumber];
} else {
covered = 'no';
}
}
array.push('<span class="cline-any cline-' + covered + '">' + value + '</span>');
}
return array.join('\n');
});
function customEscape(text) {
text = text.toString();
return text.replace(RE_AMP, '&amp;')
.replace(RE_LT, '&lt;')
.replace(RE_GT, '&gt;')
.replace(RE_lt, '<')
.replace(RE_gt, '>');
}
handlebars.registerHelper('show_code', function (context /*, opts */) {
var array = [];
context.forEach(function (item) {
array.push(customEscape(item.text) || '&nbsp;');
});
return array.join('\n');
});
function title(str) {
return ' title="' + str + '" ';
}
function annotateLines(fileCoverage, structuredText) {
var lineStats = fileCoverage.l;
if (!lineStats) { return; }
Object.keys(lineStats).forEach(function (lineNumber) {
var count = lineStats[lineNumber];
structuredText[lineNumber].covered = count > 0 ? 'yes' : 'no';
});
structuredText.forEach(function (item) {
if (item.covered === null) {
item.covered = 'neutral';
}
});
}
function annotateStatements(fileCoverage, structuredText) {
var statementStats = fileCoverage.s,
statementMeta = fileCoverage.statementMap;
Object.keys(statementStats).forEach(function (stName) {
var count = statementStats[stName],
meta = statementMeta[stName],
type = count > 0 ? 'yes' : 'no',
startCol = meta.start.column,
endCol = meta.end.column + 1,
startLine = meta.start.line,
endLine = meta.end.line,
openSpan = lt + 'span class="' + (meta.skip ? 'cstat-skip' : 'cstat-no') + '"' + title('statement not covered') + gt,
closeSpan = lt + '/span' + gt,
text;
if (type === 'no') {
if (endLine !== startLine) {
endLine = startLine;
endCol = structuredText[startLine].text.originalLength();
}
text = structuredText[startLine].text;
text.wrap(startCol,
openSpan,
startLine === endLine ? endCol : text.originalLength(),
closeSpan);
}
});
}
function annotateFunctions(fileCoverage, structuredText) {
var fnStats = fileCoverage.f,
fnMeta = fileCoverage.fnMap;
if (!fnStats) { return; }
Object.keys(fnStats).forEach(function (fName) {
var count = fnStats[fName],
meta = fnMeta[fName],
type = count > 0 ? 'yes' : 'no',
startCol = meta.loc.start.column,
endCol = meta.loc.end.column + 1,
startLine = meta.loc.start.line,
endLine = meta.loc.end.line,
openSpan = lt + 'span class="' + (meta.skip ? 'fstat-skip' : 'fstat-no') + '"' + title('function not covered') + gt,
closeSpan = lt + '/span' + gt,
text;
if (type === 'no') {
if (endLine !== startLine) {
endLine = startLine;
endCol = structuredText[startLine].text.originalLength();
}
text = structuredText[startLine].text;
text.wrap(startCol,
openSpan,
startLine === endLine ? endCol : text.originalLength(),
closeSpan);
}
});
}
function annotateBranches(fileCoverage, structuredText) {
var branchStats = fileCoverage.b,
branchMeta = fileCoverage.branchMap;
if (!branchStats) { return; }
Object.keys(branchStats).forEach(function (branchName) {
var branchArray = branchStats[branchName],
sumCount = branchArray.reduce(function (p, n) { return p + n; }, 0),
metaArray = branchMeta[branchName].locations,
i,
count,
meta,
type,
startCol,
endCol,
startLine,
endLine,
openSpan,
closeSpan,
text;
if (sumCount > 0) { //only highlight if partial branches are missing
for (i = 0; i < branchArray.length; i += 1) {
count = branchArray[i];
meta = metaArray[i];
type = count > 0 ? 'yes' : 'no';
startCol = meta.start.column;
endCol = meta.end.column + 1;
startLine = meta.start.line;
endLine = meta.end.line;
openSpan = lt + 'span class="branch-' + i + ' ' + (meta.skip ? 'cbranch-skip' : 'cbranch-no') + '"' + title('branch not covered') + gt;
closeSpan = lt + '/span' + gt;
if (count === 0) { //skip branches taken
if (endLine !== startLine) {
endLine = startLine;
endCol = structuredText[startLine].text.originalLength();
}
text = structuredText[startLine].text;
if (branchMeta[branchName].type === 'if') { // and 'if' is a special case since the else branch might not be visible, being non-existent
text.insertAt(startCol, lt + 'span class="' + (meta.skip ? 'skip-if-branch' : 'missing-if-branch') + '"' +
title((i === 0 ? 'if' : 'else') + ' path not taken') + gt +
(i === 0 ? 'I' : 'E') + lt + '/span' + gt, true, false);
} else {
text.wrap(startCol,
openSpan,
startLine === endLine ? endCol : text.originalLength(),
closeSpan);
}
}
}
}
});
}
function getReportClass(stats, watermark) {
var coveragePct = stats.pct,
identity = 1;
if (coveragePct * identity === coveragePct) {
return coveragePct >= watermark[1] ? 'high' : coveragePct >= watermark[0] ? 'medium' : 'low';
} else {
return '';
}
}
/**
* a `Report` implementation that produces HTML coverage reports.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('html');
*
*
* @class HtmlReport
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to generate reports. Defaults to `./html-report`
*/
function HtmlReport(opts) {
Report.call(this);
this.opts = opts || {};
this.opts.dir = this.opts.dir || path.resolve(process.cwd(), 'html-report');
this.opts.sourceStore = this.opts.sourceStore || Store.create('fslookup');
this.opts.linkMapper = this.opts.linkMapper || this.standardLinkMapper();
this.opts.writer = this.opts.writer || null;
this.opts.templateData = { datetime: Date() };
this.opts.watermarks = this.opts.watermarks || defaults.watermarks();
}
HtmlReport.TYPE = 'html';
util.inherits(HtmlReport, Report);
Report.mix(HtmlReport, {
getPathHtml: function (node, linkMapper) {
var parent = node.parent,
nodePath = [],
linkPath = [],
i;
while (parent) {
nodePath.push(parent);
parent = parent.parent;
}
for (i = 0; i < nodePath.length; i += 1) {
linkPath.push('<a href="' + linkMapper.ancestor(node, i + 1) + '">' +
(nodePath[i].relativeName || 'All files') + '</a>');
}
linkPath.reverse();
return linkPath.length > 0 ? linkPath.join(' &#187; ') + ' &#187; ' +
node.displayShortName() : '';
},
fillTemplate: function (node, templateData) {
var opts = this.opts,
linkMapper = opts.linkMapper;
templateData.entity = node.name || 'All files';
templateData.metrics = node.metrics;
templateData.reportClass = getReportClass(node.metrics.statements, opts.watermarks.statements);
templateData.pathHtml = pathTemplate({ html: this.getPathHtml(node, linkMapper) });
templateData.prettify = {
js: linkMapper.asset(node, 'prettify.js'),
css: linkMapper.asset(node, 'prettify.css')
};
},
writeDetailPage: function (writer, node, fileCoverage) {
var opts = this.opts,
sourceStore = opts.sourceStore,
templateData = opts.templateData,
sourceText = fileCoverage.code && Array.isArray(fileCoverage.code) ?
fileCoverage.code.join('\n') + '\n' : sourceStore.get(fileCoverage.path),
code = sourceText.split(/(?:\r?\n)|\r/),
count = 0,
structured = code.map(function (str) { count += 1; return { line: count, covered: null, text: new InsertionText(str, true) }; }),
context;
structured.unshift({ line: 0, covered: null, text: new InsertionText("") });
this.fillTemplate(node, templateData);
writer.write(headerTemplate(templateData));
writer.write('<pre><table class="coverage">\n');
annotateLines(fileCoverage, structured);
//note: order is important, since statements typically result in spanning the whole line and doing branches late
//causes mismatched tags
annotateBranches(fileCoverage, structured);
annotateFunctions(fileCoverage, structured);
annotateStatements(fileCoverage, structured);
structured.shift();
context = {
structured: structured,
maxLines: structured.length,
fileCoverage: fileCoverage
};
writer.write(detailTemplate(context));
writer.write('</table></pre>\n');
writer.write(footerTemplate(templateData));
},
writeIndexPage: function (writer, node) {
var linkMapper = this.opts.linkMapper,
templateData = this.opts.templateData,
children = Array.prototype.slice.apply(node.children),
watermarks = this.opts.watermarks;
children.sort(function (a, b) {
return a.name < b.name ? -1 : 1;
});
this.fillTemplate(node, templateData);
writer.write(headerTemplate(templateData));
writer.write(summaryTableHeader);
children.forEach(function (child) {
var metrics = child.metrics,
reportClasses = {
statements: getReportClass(metrics.statements, watermarks.statements),
lines: getReportClass(metrics.lines, watermarks.lines),
functions: getReportClass(metrics.functions, watermarks.functions),
branches: getReportClass(metrics.branches, watermarks.branches)
},
data = {
metrics: metrics,
reportClasses: reportClasses,
file: child.displayShortName(),
output: linkMapper.fromParent(child)
};
writer.write(summaryLineTemplate(data) + '\n');
});
writer.write(summaryTableFooter);
writer.write(footerTemplate(templateData));
},
writeFiles: function (writer, node, dir, collector) {
var that = this,
indexFile = path.resolve(dir, 'index.html'),
childFile;
if (this.opts.verbose) { console.error('Writing ' + indexFile); }
writer.writeFile(indexFile, function (contentWriter) {
that.writeIndexPage(contentWriter, node);
});
node.children.forEach(function (child) {
if (child.kind === 'dir') {
that.writeFiles(writer, child, path.resolve(dir, child.relativeName), collector);
} else {
childFile = path.resolve(dir, child.relativeName + '.html');
if (that.opts.verbose) { console.error('Writing ' + childFile); }
writer.writeFile(childFile, function (contentWriter) {
that.writeDetailPage(contentWriter, child, collector.fileCoverageFor(child.fullPath()));
});
}
});
},
standardLinkMapper: function () {
return {
fromParent: function (node) {
var i = 0,
relativeName = node.relativeName,
ch;
if (SEP !== '/') {
relativeName = '';
for (i = 0; i < node.relativeName.length; i += 1) {
ch = node.relativeName.charAt(i);
if (ch === SEP) {
relativeName += '/';
} else {
relativeName += ch;
}
}
}
return node.kind === 'dir' ? relativeName + 'index.html' : relativeName + '.html';
},
ancestorHref: function (node, num) {
var href = '',
separated,
levels,
i,
j;
for (i = 0; i < num; i += 1) {
separated = node.relativeName.split(SEP);
levels = separated.length - 1;
for (j = 0; j < levels; j += 1) {
href += '../';
}
node = node.parent;
}
return href;
},
ancestor: function (node, num) {
return this.ancestorHref(node, num) + 'index.html';
},
asset: function (node, name) {
var i = 0,
parent = node.parent;
while (parent) { i += 1; parent = parent.parent; }
return this.ancestorHref(node, i) + name;
}
};
},
writeReport: function (collector, sync) {
var opts = this.opts,
dir = opts.dir,
summarizer = new TreeSummarizer(),
writer = opts.writer || new FileWriter(sync),
tree;
collector.files().forEach(function (key) {
summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
});
tree = summarizer.getTreeSummary();
fs.readdirSync(path.resolve(__dirname, '..', 'vendor')).forEach(function (f) {
var resolvedSource = path.resolve(__dirname, '..', 'vendor', f),
resolvedDestination = path.resolve(dir, f),
stat = fs.statSync(resolvedSource);
if (stat.isFile()) {
if (opts.verbose) {
console.log('Write asset: ' + resolvedDestination);
}
writer.copyFile(resolvedSource, resolvedDestination);
}
});
//console.log(JSON.stringify(tree.root, undefined, 4));
this.writeFiles(writer, tree.root, dir, collector);
}
});
module.exports = HtmlReport;

View File

@ -0,0 +1,60 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Factory = require('../util/factory'),
factory = new Factory('report', __dirname, false);
/**
* abstract report class for producing coverage reports.
*
* Usage
* -----
*
* var Report = require('istanbul').Report,
* report = Report.create('html'),
* collector = new require('istanbul').Collector;
*
* collector.add(coverageObject);
* report.writeReport(collector);
*
* @class Report
* @constructor
* @protected
* @param {Object} options Optional. The options supported by a specific store implementation.
*/
function Report(/* options */) {}
//add register, create, mix, loadAll, getStoreList as class methods
factory.bindClassMethods(Report);
/**
* registers a new report implementation.
* @method register
* @static
* @param {Function} constructor the constructor function for the report. This function must have a
* `TYPE` property of type String, that will be used in `Report.create()`
*/
/**
* returns a report implementation of the specified type.
* @method create
* @static
* @param {String} type the type of report to create
* @param {Object} opts Optional. Options specific to the report implementation
* @return {Report} a new store of the specified type
*/
Report.prototype = {
/**
* writes the report for a set of coverage objects added to a collector.
* @method writeReport
* @param {Collector} collector the collector for getting the set of files and coverage
* @param {Boolean} sync true if reports must be written synchronously, false if they can be written using asynchronous means (e.g. stream.write)
*/
writeReport: function (/* collector, sync */) {
throw new Error('writeReport: must be overridden');
}
};
module.exports = Report;

View File

@ -0,0 +1,55 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
Writer = require('../util/file-writer'),
util = require('util'),
Report = require('./index');
/**
* a `Report` implementation that produces a coverage JSON object.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('json');
*
*
* @class JsonReport
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to write the `coverage-final.json` file. Defaults to `process.cwd()`
*/
function JsonReport(opts) {
this.opts = opts || {};
this.opts.dir = this.opts.dir || process.cwd();
this.opts.writer = this.opts.writer || null;
}
JsonReport.TYPE = 'json';
util.inherits(JsonReport, Report);
Report.mix(JsonReport, {
writeReport: function (collector, sync) {
var outputFile = path.resolve(this.opts.dir, 'coverage-final.json'),
writer = this.opts.writer || new Writer(sync);
writer.writeFile(outputFile, function (contentWriter) {
var first = true;
contentWriter.println("{");
collector.files().forEach(function (key) {
if (first) {
first = false;
} else {
contentWriter.println(",");
}
contentWriter.write(JSON.stringify(key));
contentWriter.write(":");
contentWriter.write(JSON.stringify(collector.fileCoverageFor(key)));
});
contentWriter.println("}");
});
}
});
module.exports = JsonReport;

View File

@ -0,0 +1,49 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
mkdirp = require('mkdirp'),
Report = require('./index'),
LcovOnlyReport = require('./lcovonly'),
HtmlReport = require('./html');
/**
* a `Report` implementation that produces an LCOV coverage file and an associated HTML report from coverage objects.
* The name and behavior of this report is designed to ease migration for projects that currently use `yuitest_coverage`
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('lcov');
*
*
* @class LcovReport
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the `lcov.info` file.
* HTML files are written in a subdirectory called `lcov-report`. Defaults to `process.cwd()`
*/
function LcovReport(opts) {
Report.call(this);
opts = opts || {};
var baseDir = path.resolve(opts.dir || process.cwd()),
htmlDir = path.resolve(baseDir, 'lcov-report');
mkdirp.sync(baseDir);
this.lcov = new LcovOnlyReport({ dir: baseDir, watermarks: opts.watermarks });
this.html = new HtmlReport({ dir: htmlDir, watermarks: opts.watermarks, sourceStore: opts.sourceStore});
}
LcovReport.TYPE = 'lcov';
Report.mix(LcovReport, {
writeReport: function (collector, sync) {
this.lcov.writeReport(collector, sync);
this.html.writeReport(collector, sync);
}
});
module.exports = LcovReport;

View File

@ -0,0 +1,93 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
Writer = require('../util/file-writer'),
util = require('util'),
Report = require('./index'),
utils = require('../object-utils');
/**
* a `Report` implementation that produces an LCOV coverage file from coverage objects.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('lcovonly');
*
*
* @class LcovOnlyReport
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the `lcov.info` file. Defaults to `process.cwd()`
*/
function LcovOnlyReport(opts) {
this.opts = opts || {};
this.opts.dir = this.opts.dir || process.cwd();
this.opts.writer = this.opts.writer || null;
}
LcovOnlyReport.TYPE = 'lcovonly';
util.inherits(LcovOnlyReport, Report);
Report.mix(LcovOnlyReport, {
writeFileCoverage: function (writer, fc) {
var functions = fc.f,
functionMap = fc.fnMap,
lines = fc.l,
branches = fc.b,
branchMap = fc.branchMap,
summary = utils.summarizeFileCoverage(fc);
writer.println('TN:'); //no test name
writer.println('SF:' + fc.path);
Object.keys(functions).forEach(function (key) {
var meta = functionMap[key];
writer.println('FN:' + [ meta.line, meta.name ].join(','));
});
writer.println('FNF:' + summary.functions.total);
writer.println('FNH:' + summary.functions.covered);
Object.keys(functions).forEach(function (key) {
var stats = functions[key],
meta = functionMap[key];
writer.println('FNDA:' + [ stats, meta.name ].join(','));
});
Object.keys(lines).forEach(function (key) {
var stat = lines[key];
writer.println('DA:' + [ key, stat ].join(','));
});
writer.println('LF:' + summary.lines.total);
writer.println('LH:' + summary.lines.covered);
Object.keys(branches).forEach(function (key) {
var branchArray = branches[key],
meta = branchMap[key],
line = meta.line,
i = 0;
branchArray.forEach(function (b) {
writer.println('BRDA:' + [line, key, i, b].join(','));
i += 1;
});
});
writer.println('BRF:' + summary.branches.total);
writer.println('BRH:' + summary.branches.covered);
writer.println('end_of_record');
},
writeReport: function (collector, sync) {
var outputFile = path.resolve(this.opts.dir, 'lcov.info'),
writer = this.opts.writer || new Writer(sync),
that = this;
writer.writeFile(outputFile, function (contentWriter) {
collector.files().forEach(function (key) {
that.writeFileCoverage(contentWriter, collector.fileCoverageFor(key));
});
});
}
});
module.exports = LcovOnlyReport;

View File

@ -0,0 +1,20 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Report = require('./index');
function NoneReport() {
Report.call(this);
}
NoneReport.TYPE = 'none';
Report.mix(NoneReport, {
writeReport: function (/* collector, sync */) {
//noop
}
});
module.exports = NoneReport;

View File

@ -0,0 +1,82 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
mkdirp = require('mkdirp'),
fs = require('fs'),
utils = require('../object-utils'),
Report = require('./index');
/**
* a `Report` implementation that produces system messages interpretable by TeamCity.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('teamcity');
*
* @class TeamcityReport
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file
* @param {String} [opts.file] the filename for the report. When omitted, the report is written to console
*/
function TeamcityReport(opts) {
Report.call(this);
opts = opts || {};
this.dir = opts.dir || process.cwd();
this.file = opts.file;
}
TeamcityReport.TYPE = 'teamcity';
function lineForKey(value, teamcityVar) {
return '##teamcity[buildStatisticValue key=\'' + teamcityVar + '\' value=\'' + value + '\']';
}
Report.mix(TeamcityReport, {
writeReport: function (collector /*, sync */) {
var summaries = [],
finalSummary,
lines = [],
text;
collector.files().forEach(function (file) {
summaries.push(utils.summarizeFileCoverage(collector.fileCoverageFor(file)));
});
finalSummary = utils.mergeSummaryObjects.apply(null, summaries);
lines.push('');
lines.push('##teamcity[blockOpened name=\'Code Coverage Summary\']');
//Statements Covered
lines.push(lineForKey(finalSummary.statements.pct, 'CodeCoverageB'));
//Methods Covered
lines.push(lineForKey(finalSummary.functions.covered, 'CodeCoverageAbsMCovered'));
lines.push(lineForKey(finalSummary.functions.total, 'CodeCoverageAbsMTotal'));
lines.push(lineForKey(finalSummary.functions.pct, 'CodeCoverageM'));
//Lines Covered
lines.push(lineForKey(finalSummary.lines.covered, 'CodeCoverageAbsLCovered'));
lines.push(lineForKey(finalSummary.lines.total, 'CodeCoverageAbsLTotal'));
lines.push(lineForKey(finalSummary.lines.pct, 'CodeCoverageL'));
lines.push('##teamcity[blockClosed name=\'Code Coverage Summary\']');
text = lines.join('\n');
if (this.file) {
mkdirp.sync(this.dir);
console.log(path.join(this.dir, this.file));
fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8');
} else {
console.log(text);
}
}
});
module.exports = TeamcityReport;

View File

@ -0,0 +1,102 @@
</div>
<div class="footer">
<div class="meta">Generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at {{datetime}}</div>
</div>
{{#if prettify}}
<script src="{{prettify.js}}"></script>
{{/if}}
<script src="https://yui-s.yahooapis.com/3.6.0/build/yui/yui-min.js"></script>
<script>
YUI().use('datatable', function (Y) {
var formatters = {
pct: function (o) {
o.className += o.record.get('classes')[o.column.key];
try {
return o.value.toFixed(2) + '%';
} catch (ex) { return o.value + '%'; }
},
html: function (o) {
o.className += o.record.get('classes')[o.column.key];
return o.record.get(o.column.key + '_html');
}
},
defaultFormatter = function (o) {
o.className += o.record.get('classes')[o.column.key];
return o.value;
};
function getColumns(theadNode) {
var colNodes = theadNode.all('tr th'),
cols = [],
col;
colNodes.each(function (colNode) {
col = {
key: colNode.getAttribute('data-col'),
label: colNode.get('innerHTML') || ' ',
sortable: !colNode.getAttribute('data-nosort'),
className: colNode.getAttribute('class'),
type: colNode.getAttribute('data-type'),
allowHTML: colNode.getAttribute('data-html') === 'true' || colNode.getAttribute('data-fmt') === 'html'
};
col.formatter = formatters[colNode.getAttribute('data-fmt')] || defaultFormatter;
cols.push(col);
});
return cols;
}
function getRowData(trNode, cols) {
var tdNodes = trNode.all('td'),
i,
row = { classes: {} },
node,
name;
for (i = 0; i < cols.length; i += 1) {
name = cols[i].key;
node = tdNodes.item(i);
row[name] = node.getAttribute('data-value') || node.get('innerHTML');
row[name + '_html'] = node.get('innerHTML');
row.classes[name] = node.getAttribute('class');
//Y.log('Name: ' + name + '; Value: ' + row[name]);
if (cols[i].type === 'number') { row[name] = row[name] * 1; }
}
//Y.log(row);
return row;
}
function getData(tbodyNode, cols) {
var data = [];
tbodyNode.all('tr').each(function (trNode) {
data.push(getRowData(trNode, cols));
});
return data;
}
function replaceTable(node) {
if (!node) { return; }
var cols = getColumns(node.one('thead')),
data = getData(node.one('tbody'), cols),
table,
parent = node.get('parentNode');
table = new Y.DataTable({
columns: cols,
data: data,
sortBy: 'file'
});
parent.set('innerHTML', '');
table.render(parent);
}
Y.on('domready', function () {
replaceTable(Y.one('div.coverage-summary table'));
if (typeof prettyPrint === 'function') {
prettyPrint();
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,217 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for {{entity}}</title>
<meta charset="utf-8">
{{#if prettify}}
<link rel="stylesheet" href="{{prettify.css}}">
{{/if}}
<style>
body, html {
margin:0; padding: 0;
}
body {
font-family: Helvetica Neue, Helvetica,Arial;
font-size: 10pt;
}
div.header, div.footer {
background: #eee;
padding: 1em;
}
div.header {
z-index: 100;
position: fixed;
top: 0;
border-bottom: 1px solid #666;
width: 100%;
}
div.footer {
border-top: 1px solid #666;
}
div.body {
margin-top: 10em;
}
div.meta {
font-size: 90%;
text-align: center;
}
h1, h2, h3 {
font-weight: normal;
}
h1 {
font-size: 12pt;
}
h2 {
font-size: 10pt;
}
pre {
font-family: Consolas, Menlo, Monaco, monospace;
margin: 0;
padding: 0;
line-height: 14px;
font-size: 14px;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
}
div.path { font-size: 110%; }
div.path a:link, div.path a:visited { color: #000; }
table.coverage { border-collapse: collapse; margin:0; padding: 0 }
table.coverage td {
margin: 0;
padding: 0;
color: #111;
vertical-align: top;
}
table.coverage td.line-count {
width: 50px;
text-align: right;
padding-right: 5px;
}
table.coverage td.line-coverage {
color: #777 !important;
text-align: right;
border-left: 1px solid #666;
border-right: 1px solid #666;
}
table.coverage td.text {
}
table.coverage td span.cline-any {
display: inline-block;
padding: 0 5px;
width: 40px;
}
table.coverage td span.cline-neutral {
background: #eee;
}
table.coverage td span.cline-yes {
background: #b5d592;
color: #999;
}
table.coverage td span.cline-no {
background: #fc8c84;
}
.cstat-yes { color: #111; }
.cstat-no { background: #fc8c84; color: #111; }
.fstat-no { background: #ffc520; color: #111 !important; }
.cbranch-no { background: yellow !important; color: #111; }
.cstat-skip { background: #ddd; color: #111; }
.fstat-skip { background: #ddd; color: #111 !important; }
.cbranch-skip { background: #ddd !important; color: #111; }
.missing-if-branch {
display: inline-block;
margin-right: 10px;
position: relative;
padding: 0 4px;
background: black;
color: yellow;
}
.skip-if-branch {
display: none;
margin-right: 10px;
position: relative;
padding: 0 4px;
background: #ccc;
color: white;
}
.missing-if-branch .typ, .skip-if-branch .typ {
color: inherit !important;
}
.entity, .metric { font-weight: bold; }
.metric { display: inline-block; border: 1px solid #333; padding: 0.3em; background: white; }
.metric small { font-size: 80%; font-weight: normal; color: #666; }
div.coverage-summary table { border-collapse: collapse; margin: 3em; font-size: 110%; }
div.coverage-summary td, div.coverage-summary table th { margin: 0; padding: 0.25em 1em; border-top: 1px solid #666; border-bottom: 1px solid #666; }
div.coverage-summary th { text-align: left; border: 1px solid #666; background: #eee; font-weight: normal; }
div.coverage-summary th.file { border-right: none !important; }
div.coverage-summary th.pic { border-left: none !important; text-align: right; }
div.coverage-summary th.pct { border-right: none !important; }
div.coverage-summary th.abs { border-left: none !important; text-align: right; }
div.coverage-summary td.pct { text-align: right; border-left: 1px solid #666; }
div.coverage-summary td.abs { text-align: right; font-size: 90%; color: #444; border-right: 1px solid #666; }
div.coverage-summary td.file { text-align: right; border-left: 1px solid #666; white-space: nowrap; }
div.coverage-summary td.pic { min-width: 120px !important; }
div.coverage-summary a:link { text-decoration: none; color: #000; }
div.coverage-summary a:visited { text-decoration: none; color: #333; }
div.coverage-summary a:hover { text-decoration: underline; }
div.coverage-summary tfoot td { border-top: 1px solid #666; }
div.coverage-summary .yui3-datatable-sort-indicator, div.coverage-summary .dummy-sort-indicator {
height: 10px;
width: 7px;
display: inline-block;
margin-left: 0.5em;
}
div.coverage-summary .yui3-datatable-sort-indicator {
background: url("https://yui-s.yahooapis.com/3.6.0/build/datatable-sort/assets/skins/sam/sort-arrow-sprite.png") no-repeat scroll 0 0 transparent;
}
div.coverage-summary .yui3-datatable-sorted .yui3-datatable-sort-indicator {
background-position: 0 -20px;
}
div.coverage-summary .yui3-datatable-sorted-desc .yui3-datatable-sort-indicator {
background-position: 0 -10px;
}
.high { background: #b5d592 !important; }
.medium { background: #ffe87c !important; }
.low { background: #fc8c84 !important; }
span.cover-fill, span.cover-empty {
display:inline-block;
border:1px solid #444;
background: white;
height: 12px;
}
span.cover-fill {
background: #ccc;
border-right: 1px solid #444;
}
span.cover-empty {
background: white;
border-left: none;
}
span.cover-full {
border-right: none !important;
}
pre.prettyprint {
border: none !important;
padding: 0 !important;
margin: 0 !important;
}
.com { color: #999 !important; }
.ignore-none { color: #999; font-weight: normal; }
</style>
</head>
<body>
<div class="header {{reportClass}}">
<h1>Code coverage report for <span class="entity">{{entity}}</span></h1>
<h2>
{{#with metrics.statements}}
Statements: <span class="metric">{{pct}}% <small>({{covered}} / {{total}})</small></span> &nbsp;&nbsp;&nbsp;&nbsp;
{{/with}}
{{#with metrics.branches}}
Branches: <span class="metric">{{pct}}% <small>({{covered}} / {{total}})</small></span> &nbsp;&nbsp;&nbsp;&nbsp;
{{/with}}
{{#with metrics.functions}}
Functions: <span class="metric">{{pct}}% <small>({{covered}} / {{total}})</small></span> &nbsp;&nbsp;&nbsp;&nbsp;
{{/with}}
{{#with metrics.lines}}
Lines: <span class="metric">{{pct}}% <small>({{covered}} / {{total}})</small></span> &nbsp;&nbsp;&nbsp;&nbsp;
{{/with}}
Ignored: <span class="metric">{{#show_ignores metrics}}{{/show_ignores}}</span> &nbsp;&nbsp;&nbsp;&nbsp;
</h2>
{{{pathHtml}}}
</div>
<div class="body">

View File

@ -0,0 +1,83 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
mkdirp = require('mkdirp'),
defaults = require('./common/defaults'),
fs = require('fs'),
utils = require('../object-utils'),
Report = require('./index');
/**
* a `Report` implementation that produces text output for overall coverage in summary format.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('text-summary');
*
* @class TextSummaryReport
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file
* @param {String} [opts.file] the filename for the report. When omitted, the report is written to console
*/
function TextSummaryReport(opts) {
Report.call(this);
opts = opts || {};
this.dir = opts.dir || process.cwd();
this.file = opts.file;
this.watermarks = opts.watermarks || defaults.watermarks();
}
TextSummaryReport.TYPE = 'text-summary';
function lineForKey(summary, key, watermarks) {
var metrics = summary[key],
skipped,
result,
clazz = defaults.classFor(key, summary, watermarks);
key = key.substring(0, 1).toUpperCase() + key.substring(1);
if (key.length < 12) { key += ' '.substring(0, 12 - key.length); }
result = [ key , ':', metrics.pct + '%', '(', metrics.covered + '/' + metrics.total, ')'].join(' ');
skipped = metrics.skipped;
if (skipped > 0) {
result += ', ' + skipped + ' ignored';
}
return defaults.colorize(result, clazz);
}
Report.mix(TextSummaryReport, {
writeReport: function (collector /*, sync */) {
var summaries = [],
finalSummary,
lines = [],
watermarks = this.watermarks,
text;
collector.files().forEach(function (file) {
summaries.push(utils.summarizeFileCoverage(collector.fileCoverageFor(file)));
});
finalSummary = utils.mergeSummaryObjects.apply(null, summaries);
lines.push('');
lines.push('=============================== Coverage summary ===============================');
lines.push.apply(lines, [
lineForKey(finalSummary, 'statements', watermarks),
lineForKey(finalSummary, 'branches', watermarks),
lineForKey(finalSummary, 'functions', watermarks),
lineForKey(finalSummary, 'lines', watermarks)
]);
lines.push('================================================================================');
text = lines.join('\n');
if (this.file) {
mkdirp.sync(this.dir);
fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8');
} else {
console.log(text);
}
}
});
module.exports = TextSummaryReport;

View File

@ -0,0 +1,202 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
mkdirp = require('mkdirp'),
fs = require('fs'),
defaults = require('./common/defaults'),
Report = require('./index'),
TreeSummarizer = require('../util/tree-summarizer'),
utils = require('../object-utils'),
PCT_COLS = 10,
TAB_SIZE = 3,
DELIM = ' |',
COL_DELIM = '-|';
/**
* a `Report` implementation that produces text output in a detailed table.
*
* Usage
* -----
*
* var report = require('istanbul').Report.create('text');
*
* @class TextReport
* @extends Report
* @constructor
* @param {Object} opts optional
* @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file
* @param {String} [opts.file] the filename for the report. When omitted, the report is written to console
* @param {Number} [opts.maxcols] the max column width of the report. By default, the width of the report is adjusted based on the length of the paths
* to be reported.
*/
function TextReport(opts) {
Report.call(this);
opts = opts || {};
this.dir = opts.dir || process.cwd();
this.file = opts.file;
this.summary = opts.summary;
this.maxCols = opts.maxCols || 0;
this.watermarks = opts.watermarks || defaults.watermarks();
}
TextReport.TYPE = 'text';
function padding(num, ch) {
var str = '',
i;
ch = ch || ' ';
for (i = 0; i < num; i += 1) {
str += ch;
}
return str;
}
function fill(str, width, right, tabs, clazz) {
tabs = tabs || 0;
str = String(str);
var leadingSpaces = tabs * TAB_SIZE,
remaining = width - leadingSpaces,
leader = padding(leadingSpaces),
fmtStr = '',
fillStr,
strlen = str.length;
if (remaining > 0) {
if (remaining >= strlen) {
fillStr = padding(remaining - strlen);
fmtStr = right ? fillStr + str : str + fillStr;
} else {
fmtStr = str.substring(strlen - remaining);
fmtStr = '... ' + fmtStr.substring(4);
}
}
fmtStr = defaults.colorize(fmtStr, clazz);
return leader + fmtStr;
}
function formatName(name, maxCols, level, clazz) {
return fill(name, maxCols, false, level, clazz);
}
function formatPct(pct, clazz) {
return fill(pct, PCT_COLS, true, 0, clazz);
}
function nodeName(node) {
return node.displayShortName() || 'All files';
}
function tableHeader(maxNameCols) {
var elements = [];
elements.push(formatName('File', maxNameCols, 0));
elements.push(formatPct('% Stmts'));
elements.push(formatPct('% Branches'));
elements.push(formatPct('% Funcs'));
elements.push(formatPct('% Lines'));
return elements.join(' |') + ' |';
}
function tableRow(node, maxNameCols, level, watermarks) {
var name = nodeName(node),
statements = node.metrics.statements.pct,
branches = node.metrics.branches.pct,
functions = node.metrics.functions.pct,
lines = node.metrics.lines.pct,
elements = [];
elements.push(formatName(name, maxNameCols, level, defaults.classFor('statements', node.metrics, watermarks)));
elements.push(formatPct(statements, defaults.classFor('statements', node.metrics, watermarks)));
elements.push(formatPct(branches, defaults.classFor('branches', node.metrics, watermarks)));
elements.push(formatPct(functions, defaults.classFor('functions', node.metrics, watermarks)));
elements.push(formatPct(lines, defaults.classFor('lines', node.metrics, watermarks)));
return elements.join(DELIM) + DELIM;
}
function findNameWidth(node, level, last) {
last = last || 0;
level = level || 0;
var idealWidth = TAB_SIZE * level + nodeName(node).length;
if (idealWidth > last) {
last = idealWidth;
}
node.children.forEach(function (child) {
last = findNameWidth(child, level + 1, last);
});
return last;
}
function makeLine(nameWidth) {
var name = padding(nameWidth, '-'),
pct = padding(PCT_COLS, '-'),
elements = [];
elements.push(name);
elements.push(pct);
elements.push(pct);
elements.push(pct);
elements.push(pct);
return elements.join(COL_DELIM) + COL_DELIM;
}
function walk(node, nameWidth, array, level, watermarks) {
var line;
if (level === 0) {
line = makeLine(nameWidth);
array.push(line);
array.push(tableHeader(nameWidth));
array.push(line);
} else {
array.push(tableRow(node, nameWidth, level, watermarks));
}
node.children.forEach(function (child) {
walk(child, nameWidth, array, level + 1, watermarks);
});
if (level === 0) {
array.push(line);
array.push(tableRow(node, nameWidth, level, watermarks));
array.push(line);
}
}
Report.mix(TextReport, {
writeReport: function (collector /*, sync */) {
var summarizer = new TreeSummarizer(),
tree,
root,
nameWidth,
statsWidth = 4 * ( PCT_COLS + 2),
maxRemaining,
strings = [],
text;
collector.files().forEach(function (key) {
summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
});
tree = summarizer.getTreeSummary();
root = tree.root;
nameWidth = findNameWidth(root);
if (this.maxCols > 0) {
maxRemaining = this.maxCols - statsWidth - 2;
if (nameWidth > maxRemaining) {
nameWidth = maxRemaining;
}
}
walk(root, nameWidth, strings, 0, this.watermarks);
text = strings.join('\n') + '\n';
if (this.file) {
mkdirp.sync(this.dir);
fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8');
} else {
console.log(text);
}
}
});
module.exports = TextReport;

View File

@ -0,0 +1,60 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
fs = require('fs'),
Store = require('./index');
/**
* a `Store` implementation that doesn't actually store anything. It assumes that keys
* are absolute file paths, and contents are contents of those files.
* Thus, `set` for this store is no-op, `get` returns the
* contents of the filename that the key represents, `hasKey` returns true if the key
* supplied is a valid file path and `keys` always returns an empty array.
*
* Usage
* -----
*
* var store = require('istanbul').Store.create('fslookup');
*
*
* @class LookupStore
* @extends Store
* @constructor
*/
function LookupStore(opts) {
Store.call(this, opts);
}
LookupStore.TYPE = 'fslookup';
util.inherits(LookupStore, Store);
Store.mix(LookupStore, {
keys: function () {
return [];
},
get: function (key) {
return fs.readFileSync(key, 'utf8');
},
hasKey: function (key) {
var stats;
try {
stats = fs.statSync(key);
return stats.isFile();
} catch (ex) {
return false;
}
},
set: function (key /*, contents */) {
if (!this.hasKey(key)) {
throw new Error('Attempt to set contents for non-existent file [' + key + '] on a fslookup store');
}
return key;
}
});
module.exports = LookupStore;

View File

@ -0,0 +1,122 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var Factory = require('../util/factory'),
factory = new Factory('store', __dirname, false);
/**
* An abstraction for keeping track of content against some keys (e.g.
* original source, instrumented source, coverage objects against file names).
* This class is both the base class as well as a factory for `Store` implementations.
*
* Usage
* -----
*
* var Store = require('istanbul').Store,
* store = Store.create('memory');
*
* //basic use
* store.set('foo', 'foo-content');
* var content = store.get('foo');
*
* //keys and values
* store.keys().forEach(function (key) {
* console.log(key + ':\n' + store.get(key);
* });
* if (store.hasKey('bar') { console.log(store.get('bar'); }
*
*
* //syntactic sugar
* store.setObject('foo', { foo: true });
* console.log(store.getObject('foo').foo);
*
* store.dispose();
*
* @class Store
* @constructor
* @protected
* @param {Object} options Optional. The options supported by a specific store implementation.
*/
function Store(/* options */) {}
//add register, create, mix, loadAll, getStoreList as class methods
factory.bindClassMethods(Store);
/**
* registers a new store implementation.
* @method register
* @static
* @param {Function} constructor the constructor function for the store. This function must have a
* `TYPE` property of type String, that will be used in `Store.create()`
*/
/**
* returns a store implementation of the specified type.
* @method create
* @static
* @param {String} type the type of store to create
* @param {Object} opts Optional. Options specific to the store implementation
* @return {Store} a new store of the specified type
*/
Store.prototype = {
/**
* sets some content associated with a specific key. The manner in which
* duplicate keys are handled for multiple `set()` calls with the same
* key is implementation-specific.
*
* @method set
* @param {String} key the key for the content
* @param {String} contents the contents for the key
*/
set: function (/* key, contents */) { throw new Error("set: must be overridden"); },
/**
* returns the content associated to a specific key or throws if the key
* was not `set`
* @method get
* @param {String} key the key for which to get the content
* @return {String} the content for the specified key
*/
get: function (/* key */) { throw new Error("get: must be overridden"); },
/**
* returns a list of all known keys
* @method keys
* @return {Array} an array of seen keys
*/
keys: function () { throw new Error("keys: must be overridden"); },
/**
* returns true if the key is one for which a `get()` call would work.
* @method hasKey
* @param {String} key
* @return true if the key is valid for this store, false otherwise
*/
hasKey: function (/* key */) { throw new Error("hasKey: must be overridden"); },
/**
* lifecycle method to dispose temporary resources associated with the store
* @method dispose
*/
dispose: function () {},
/**
* sugar method to return an object associated with a specific key. Throws
* if the content set against the key was not a valid JSON string.
* @method getObject
* @param {String} key the key for which to return the associated object
* @return {Object} the object corresponding to the key
*/
getObject: function (key) {
return JSON.parse(this.get(key));
},
/**
* sugar method to set an object against a specific key.
* @method setObject
* @param {String} key the key for the object
* @param {Object} object the object to be stored
*/
setObject: function (key, object) {
return this.set(key, JSON.stringify(object));
}
};
module.exports = Store;

View File

@ -0,0 +1,55 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
Store = require('./index');
/**
* a `Store` implementation using an in-memory object.
*
* Usage
* -----
*
* var store = require('istanbul').Store.create('memory');
*
*
* @class MemoryStore
* @extends Store
* @constructor
*/
function MemoryStore() {
Store.call(this);
this.map = {};
}
MemoryStore.TYPE = 'memory';
util.inherits(MemoryStore, Store);
Store.mix(MemoryStore, {
set: function (key, contents) {
this.map[key] = contents;
},
get: function (key) {
if (!this.hasKey(key)) {
throw new Error('Unable to find entry for [' + key + ']');
}
return this.map[key];
},
hasKey: function (key) {
return this.map.hasOwnProperty(key);
},
keys: function () {
return Object.keys(this.map);
},
dispose: function () {
this.map = {};
}
});
module.exports = MemoryStore;

View File

@ -0,0 +1,80 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
path = require('path'),
os = require('os'),
fs = require('fs'),
mkdirp = require('mkdirp'),
Store = require('./index');
function makeTempDir() {
var dir = path.join(os.tmpDir ? os.tmpDir() : /* istanbul ignore next */ (process.env.TMPDIR || '/tmp'), 'ts' + new Date().getTime());
mkdirp.sync(dir);
return dir;
}
/**
* a `Store` implementation using temporary files.
*
* Usage
* -----
*
* var store = require('istanbul').Store.create('tmp');
*
*
* @class TmpStore
* @extends Store
* @param {Object} opts Optional.
* @param {String} [opts.tmp] a pre-existing directory to use as the `tmp` directory. When not specified, a random directory
* is created under `os.tmpDir()`
* @constructor
*/
function TmpStore(opts) {
opts = opts || {};
this.tmp = opts.tmp || makeTempDir();
this.map = {};
this.seq = 0;
this.prefix = 't' + new Date().getTime() + '-';
}
TmpStore.TYPE = 'tmp';
util.inherits(TmpStore, Store);
Store.mix(TmpStore, {
generateTmpFileName: function () {
this.seq += 1;
return this.prefix + this.seq + '.tmp';
},
set: function (key, contents) {
var tmpFile = this.generateTmpFileName();
fs.writeFileSync(tmpFile, contents, 'utf8');
this.map[key] = tmpFile;
},
get: function (key) {
var tmpFile = this.map[key];
if (!tmpFile) { throw new Error('Unable to find tmp entry for [' + tmpFile + ']'); }
return fs.readFileSync(tmpFile, 'utf8');
},
hasKey: function (key) {
return !!this.map[key];
},
keys: function () {
return Object.keys(this.map);
},
dispose: function () {
var map = this.map;
Object.keys(map).forEach(function (key) {
fs.unlinkSync(map[key]);
});
this.map = {};
}
});
module.exports = TmpStore;

View File

@ -0,0 +1,88 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
path = require('path'),
fs = require('fs'),
abbrev = require('abbrev');
function Factory(kind, dir, allowAbbreviations) {
this.kind = kind;
this.dir = dir;
this.allowAbbreviations = allowAbbreviations;
this.classMap = {};
this.abbreviations = null;
}
Factory.prototype = {
knownTypes: function () {
var keys = Object.keys(this.classMap);
keys.sort();
return keys;
},
resolve: function (abbreviatedType) {
if (!this.abbreviations) {
this.abbreviations = abbrev(this.knownTypes());
}
return this.abbreviations[abbreviatedType];
},
register: function (constructor) {
var type = constructor.TYPE;
if (!type) { throw new Error('Could not register ' + this.kind + ' constructor [no TYPE property]: ' + util.inspect(constructor)); }
this.classMap[type] = constructor;
this.abbreviations = null;
},
create: function (type, opts) {
var allowAbbrev = this.allowAbbreviations,
realType = allowAbbrev ? this.resolve(type) : type,
Cons;
Cons = realType ? this.classMap[realType] : null;
if (!Cons) { throw new Error('Invalid ' + this.kind + ' [' + type + '], allowed values are ' + this.knownTypes().join(', ')); }
return new Cons(opts);
},
loadStandard: function (dir) {
var that = this;
fs.readdirSync(dir).forEach(function (file) {
if (file !== 'index.js' && file.indexOf('.js') === file.length - 3) {
try {
that.register(require(path.resolve(dir, file)));
} catch (ex) {
console.error(ex.message);
console.error(ex.stack);
throw new Error('Could not register ' + that.kind + ' from file ' + file);
}
}
});
},
bindClassMethods: function (Cons) {
var tmpKind = this.kind.charAt(0).toUpperCase() + this.kind.substring(1), //ucfirst
allowAbbrev = this.allowAbbreviations;
Cons.mix = Factory.mix;
Cons.register = this.register.bind(this);
Cons.create = this.create.bind(this);
Cons.loadAll = this.loadStandard.bind(this, this.dir);
Cons['get' + tmpKind + 'List'] = this.knownTypes.bind(this);
if (allowAbbrev) {
Cons['resolve' + tmpKind + 'Name'] = this.resolve.bind(this);
}
}
};
Factory.mix = function (cons, proto) {
Object.keys(proto).forEach(function (key) {
cons.prototype[key] = proto[key];
});
};
module.exports = Factory;

View File

@ -0,0 +1,64 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var fileset = require('fileset'),
path = require('path'),
seq = 0;
function filesFor(options, callback) {
if (!callback && typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
var root = options.root,
includes = options.includes,
excludes = options.excludes,
relative = options.relative,
opts;
root = root || process.cwd();
includes = includes && Array.isArray(includes) ? includes : [ '**/*.js' ];
excludes = excludes && Array.isArray(excludes) ? excludes : [ '**/node_modules/**' ];
opts = { cwd: root };
seq += 1;
opts['x' + seq + new Date().getTime()] = true; //cache buster for minimatch cache bug
fileset(includes.join(' '), excludes.join(' '), opts, function (err, files) {
if (err) { return callback(err); }
if (!relative) {
files = files.map(function (file) { return path.resolve(root, file); });
}
callback(err, files);
});
}
function matcherFor(options, callback) {
if (!callback && typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
options.relative = false; //force absolute paths
filesFor(options, function (err, files) {
var fileMap = {},
matchFn;
if (err) { return callback(err); }
files.forEach(function (file) { fileMap[file] = true; });
matchFn = function (file) { return fileMap[file]; };
matchFn.files = Object.keys(fileMap);
return callback(null, matchFn);
});
}
module.exports = {
filesFor: filesFor,
matcherFor: matcherFor
};

View File

@ -0,0 +1,131 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
util = require('util'),
fs = require('fs'),
async = require('async'),
mkdirp = require('mkdirp'),
writer = require('./writer'),
Writer = writer.Writer,
ContentWriter = writer.ContentWriter;
function extend(cons, proto) {
Object.keys(proto).forEach(function (k) {
cons.prototype[k] = proto[k];
});
}
function BufferedContentWriter() {
ContentWriter.call(this);
this.content = '';
}
util.inherits(BufferedContentWriter, ContentWriter);
extend(BufferedContentWriter, {
write: function (str) {
this.content += str;
},
getContent: function () {
return this.content;
}
});
function StreamContentWriter(stream) {
ContentWriter.call(this);
this.stream = stream;
}
util.inherits(StreamContentWriter, ContentWriter);
extend(StreamContentWriter, {
write: function (str) {
this.stream.write(str);
}
});
function SyncFileWriter() {
Writer.call(this);
}
util.inherits(SyncFileWriter, Writer);
extend(SyncFileWriter, {
writeFile: function (file, callback) {
mkdirp.sync(path.dirname(file));
var cw = new BufferedContentWriter();
callback(cw);
fs.writeFileSync(file, cw.getContent(), 'utf8');
},
done: function () {
this.emit('done'); //everything already done
}
});
function AsyncFileWriter() {
this.queue = async.queue(this.processFile.bind(this), 20);
this.openFileMap = {};
}
util.inherits(AsyncFileWriter, Writer);
extend(AsyncFileWriter, {
writeFile: function (file, callback) {
this.openFileMap[file] = true;
this.queue.push({ file: file, callback: callback });
},
processFile: function (task, cb) {
var file = task.file,
userCallback = task.callback,
that = this,
stream,
contentWriter;
mkdirp.sync(path.dirname(file));
stream = fs.createWriteStream(file);
stream.on('close', function () {
delete that.openFileMap[file];
cb();
that.checkDone();
});
stream.on('error', function (err) { that.emit('error', err); });
contentWriter = new StreamContentWriter(stream);
userCallback(contentWriter);
stream.end();
},
done: function () {
this.doneCalled = true;
this.checkDone();
},
checkDone: function () {
if (!this.doneCalled) { return; }
if (Object.keys(this.openFileMap).length === 0) {
this.emit('done');
}
}
});
function FileWriter(sync) {
Writer.call(this);
var that = this;
this.delegate = sync ? new SyncFileWriter() : new AsyncFileWriter();
this.delegate.on('error', function (err) { that.emit('error', err); });
this.delegate.on('done', function () { that.emit('done'); });
}
util.inherits(FileWriter, Writer);
extend(FileWriter, {
copyFile: function (source, dest) {
mkdirp.sync(path.dirname(dest));
fs.writeFileSync(dest, fs.readFileSync(source));
},
writeFile: function (file, callback) {
this.delegate.writeFile(file, callback);
},
done: function () {
this.delegate.done();
}
});
module.exports = FileWriter;

View File

@ -0,0 +1,26 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
/**
* Wrap a function and make sure it's called only once.
*
* @param {Function} func Function to wrap.
* @return {Function} Wrapped function.
*/
function callOnce(func) {
var called = false;
return function wrapped() {
if (!called) {
called = true;
func.apply(null, arguments);
}
};
}
module.exports = {
'callOnce': callOnce
};

View File

@ -0,0 +1,30 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var OPT_PREFIX = " ",
OPT_START = OPT_PREFIX.length,
TEXT_START = 14,
STOP = 80,
wrap = require('wordwrap')(TEXT_START, STOP),
paraWrap = require('wordwrap')(1, STOP);
function formatPara(text) {
return paraWrap(text);
}
function formatOption(option, helpText) {
var formattedText = wrap(helpText);
if (option.length > TEXT_START - OPT_START - 2) {
return OPT_PREFIX + option + '\n' + formattedText;
} else {
return OPT_PREFIX + option + formattedText.substring((OPT_PREFIX + option).length);
}
}
module.exports = {
formatPara: formatPara,
formatOption: formatOption
};

View File

@ -0,0 +1,12 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
module.exports.create = function (message) {
var err = new Error(message);
err.inputError = true;
return err;
};

View File

@ -0,0 +1,109 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
function InsertionText(text, consumeBlanks) {
this.text = text;
this.origLength = text.length;
this.offsets = [];
this.consumeBlanks = consumeBlanks;
this.startPos = this.findFirstNonBlank();
this.endPos = this.findLastNonBlank();
}
var WHITE_RE = /[ \f\n\r\t\v\u00A0\u2028\u2029]/;
InsertionText.prototype = {
findFirstNonBlank: function () {
var pos = -1,
text = this.text,
len = text.length,
i;
for (i = 0; i < len; i += 1) {
if (!text.charAt(i).match(WHITE_RE)) {
pos = i;
break;
}
}
return pos;
},
findLastNonBlank: function () {
var text = this.text,
len = text.length,
pos = text.length + 1,
i;
for (i = len - 1; i >= 0; i -= 1) {
if (!text.charAt(i).match(WHITE_RE)) {
pos = i;
break;
}
}
return pos;
},
originalLength: function () {
return this.origLength;
},
insertAt: function (col, str, insertBefore, consumeBlanks) {
consumeBlanks = typeof consumeBlanks === 'undefined' ? this.consumeBlanks : consumeBlanks;
col = col > this.originalLength() ? this.originalLength() : col;
col = col < 0 ? 0 : col;
if (consumeBlanks) {
if (col <= this.startPos) {
col = 0;
}
if (col > this.endPos) {
col = this.origLength;
}
}
var len = str.length,
offset = this.findOffset(col, len, insertBefore),
realPos = col + offset,
text = this.text;
this.text = text.substring(0, realPos) + str + text.substring(realPos);
return this;
},
findOffset: function (pos, len, insertBefore) {
var offsets = this.offsets,
offsetObj,
cumulativeOffset = 0,
i;
for (i = 0; i < offsets.length; i += 1) {
offsetObj = offsets[i];
if (offsetObj.pos < pos || (offsetObj.pos === pos && !insertBefore)) {
cumulativeOffset += offsetObj.len;
}
if (offsetObj.pos >= pos) {
break;
}
}
if (offsetObj && offsetObj.pos === pos) {
offsetObj.len += len;
} else {
offsets.splice(i, 0, { pos: pos, len: len });
}
return cumulativeOffset;
},
wrap: function (startPos, startText, endPos, endText, consumeBlanks) {
this.insertAt(startPos, startText, true, consumeBlanks);
this.insertAt(endPos, endText, false, consumeBlanks);
return this;
},
wrapLine: function (startText, endText) {
this.wrap(0, startText, this.originalLength(), endText);
},
toString: function () {
return this.text;
}
};
module.exports = InsertionText;

View File

@ -0,0 +1,13 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
fs = require('fs'),
pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'), 'utf8'));
module.exports = {
NAME: pkg.name,
VERSION: pkg.version
};

View File

@ -0,0 +1,213 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var path = require('path'),
SEP = path.sep || '/',
utils = require('../object-utils');
function commonArrayPrefix(first, second) {
var len = first.length < second.length ? first.length : second.length,
i,
ret = [];
for (i = 0; i < len; i += 1) {
if (first[i] === second[i]) {
ret.push(first[i]);
} else {
break;
}
}
return ret;
}
function findCommonArrayPrefix(args) {
if (args.length === 0) {
return [];
}
var separated = args.map(function (arg) { return arg.split(SEP); }),
ret = separated.pop();
if (separated.length === 0) {
return ret.slice(0, ret.length - 1);
} else {
return separated.reduce(commonArrayPrefix, ret);
}
}
function Node(fullName, kind, metrics) {
this.name = fullName;
this.fullName = fullName;
this.kind = kind;
this.metrics = metrics || null;
this.parent = null;
this.children = [];
}
Node.prototype = {
displayShortName: function () {
return this.relativeName;
},
fullPath: function () {
return this.fullName;
},
addChild: function (child) {
this.children.push(child);
child.parent = this;
},
toJSON: function () {
return {
name: this.name,
relativeName: this.relativeName,
fullName: this.fullName,
kind: this.kind,
metrics: this.metrics,
parent: this.parent === null ? null : this.parent.name,
children: this.children.map(function (node) { return node.toJSON(); })
};
}
};
function TreeSummary(summaryMap, commonPrefix) {
this.prefix = commonPrefix;
this.convertToTree(summaryMap, commonPrefix);
}
TreeSummary.prototype = {
getNode: function (shortName) {
return this.map[shortName];
},
convertToTree: function (summaryMap, arrayPrefix) {
var nodes = [],
rootPath = arrayPrefix.join(SEP) + SEP,
root = new Node(rootPath, 'dir'),
tmp,
tmpChildren,
seen = {},
filesUnderRoot = false;
seen[rootPath] = root;
Object.keys(summaryMap).forEach(function (key) {
var metrics = summaryMap[key],
node,
parentPath,
parent;
node = new Node(key, 'file', metrics);
seen[key] = node;
nodes.push(node);
parentPath = path.dirname(key) + SEP;
if (parentPath === SEP + SEP) {
parentPath = SEP + '__root__' + SEP;
}
parent = seen[parentPath];
if (!parent) {
parent = new Node(parentPath, 'dir');
root.addChild(parent);
seen[parentPath] = parent;
}
parent.addChild(node);
if (parent === root) { filesUnderRoot = true; }
});
if (filesUnderRoot && arrayPrefix.length > 0) {
arrayPrefix.pop(); //start at one level above
tmp = root;
tmpChildren = tmp.children;
tmp.children = [];
root = new Node(arrayPrefix.join(SEP) + SEP, 'dir');
root.addChild(tmp);
tmpChildren.forEach(function (child) {
if (child.kind === 'dir') {
root.addChild(child);
} else {
tmp.addChild(child);
}
});
}
this.fixupNodes(root, arrayPrefix.join(SEP) + SEP);
this.calculateMetrics(root);
this.root = root;
this.map = {};
this.indexAndSortTree(root, this.map);
},
fixupNodes: function (node, prefix, parent) {
var that = this;
if (node.name.indexOf(prefix) === 0) {
node.name = node.name.substring(prefix.length);
}
if (node.name.charAt(0) === SEP) {
node.name = node.name.substring(1);
}
if (parent) {
if (parent.name !== '__root__/') {
node.relativeName = node.name.substring(parent.name.length);
} else {
node.relativeName = node.name;
}
} else {
node.relativeName = node.name.substring(prefix.length);
}
node.children.forEach(function (child) {
that.fixupNodes(child, prefix, node);
});
},
calculateMetrics: function (entry) {
var that = this,
fileChildren;
if (entry.kind !== 'dir') {return; }
entry.children.forEach(function (child) {
that.calculateMetrics(child);
});
entry.metrics = utils.mergeSummaryObjects.apply(
null,
entry.children.map(function (child) { return child.metrics; })
);
// calclulate "java-style" package metrics where there is no hierarchy
// across packages
fileChildren = entry.children.filter(function (n) { return n.kind !== 'dir'; });
if (fileChildren.length > 0) {
entry.packageMetrics = utils.mergeSummaryObjects.apply(
null,
fileChildren.map(function (child) { return child.metrics; })
);
} else {
entry.packageMetrics = null;
}
},
indexAndSortTree: function (node, map) {
var that = this;
map[node.name] = node;
node.children.sort(function (a, b) {
a = a.relativeName;
b = b.relativeName;
return a < b ? -1 : a > b ? 1 : 0;
});
node.children.forEach(function (child) {
that.indexAndSortTree(child, map);
});
},
toJSON: function () {
return {
prefix: this.prefix,
root: this.root.toJSON()
};
}
};
function TreeSummarizer() {
this.summaryMap = {};
}
TreeSummarizer.prototype = {
addFileCoverageSummary: function (filePath, metrics) {
this.summaryMap[filePath] = metrics;
},
getTreeSummary: function () {
var commonArrayPrefix = findCommonArrayPrefix(Object.keys(this.summaryMap));
return new TreeSummary(this.summaryMap, commonArrayPrefix);
}
};
module.exports = TreeSummarizer;

View File

@ -0,0 +1,64 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
var util = require('util'),
EventEmitter = require('events').EventEmitter;
function extend(cons, proto) {
Object.keys(proto).forEach(function (k) {
cons.prototype[k] = proto[k];
});
}
//abstract interface for writing content
function ContentWriter() {
}
ContentWriter.prototype = {
write: /* istanbul ignore next: abstract method */ function (/* str */) {
throw new Error('write: must be overridden');
},
println: function (str) { this.write(str); this.write('\n'); }
};
//abstract interface for writing files and assets
function Writer() {
EventEmitter.call(this);
}
util.inherits(Writer, EventEmitter);
extend(Writer, {
/**
* allows writing content to a file using a callback that is passed a content writer
* @param file the name of the file to write
* @param callback the callback that is called as `callback(contentWriter)`
*/
writeFile: /* istanbul ignore next: abstract method */ function (/* file, callback */) {
throw new Error('writeFile: must be overridden');
},
/**
* copies a file from source to destination
* @param source the file to copy, found on the file system
* @param dest the destination path
*/
copyFile: /* istanbul ignore next: abstract method */ function (/* source, dest */) {
throw new Error('copyFile: must be overridden');
},
/**
* marker method to indicate that the caller is done with this writer object
* The writer is expected to emit a `done` event only after this method is called
* and it is truly done.
*/
done: /* istanbul ignore next: abstract method */ function () {
throw new Error('done: must be overridden');
}
});
module.exports = {
Writer: Writer,
ContentWriter: ContentWriter
};

View File

@ -0,0 +1,49 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
//EXPERIMENTAL code: do not rely on this in anyway until the docs say it is allowed
var path = require('path'),
yuiRegexp = /yui-nodejs\.js$/;
module.exports = function (matchFn, transformFn, verbose) {
return function (file) {
if (!file.match(yuiRegexp)) {
return;
}
var YMain = require(file),
YUI,
loaderFn,
origGet;
if (YMain.YUI) {
YUI = YMain.YUI;
loaderFn = YUI.Env && YUI.Env.mods && YUI.Env.mods['loader-base'] ? YUI.Env.mods['loader-base'].fn : null;
if (!loaderFn) { return; }
if (verbose) { console.log('Applying YUI load post-hook'); }
YUI.Env.mods['loader-base'].fn = function (Y) {
loaderFn.call(null, Y);
origGet = Y.Get._exec;
Y.Get._exec = function (data, url, cb) {
if (matchFn(url) || matchFn(path.resolve(url))) { //allow for relative paths as well
if (verbose) {
console.log('Transforming [' + url + ']');
}
try {
data = transformFn(data, url);
} catch (ex) {
console.error('Error transforming: ' + url + ' return original code');
console.error(ex.message || ex);
if (ex.stack) { console.error(ex.stack); }
}
}
return origGet.call(Y, data, url, cb);
};
return Y;
};
}
};
};

View File

@ -0,0 +1 @@
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
../escodegen/bin/escodegen.js

View File

@ -0,0 +1 @@
../escodegen/bin/esgenerate.js

View File

@ -0,0 +1 @@
../esprima/bin/esparse.js

View File

@ -0,0 +1 @@
../esprima/bin/esvalidate.js

View File

@ -0,0 +1 @@
../handlebars/bin/handlebars

View File

@ -0,0 +1 @@
../js-yaml/bin/js-yaml.js

View File

@ -0,0 +1 @@
../mkdirp/bin/cmd.js

View File

@ -0,0 +1 @@
../nopt/bin/nopt.js

View File

@ -0,0 +1 @@
../which/bin/which

View File

@ -0,0 +1,3 @@
To get started, <a
href="http://www.clahub.com/agreements/isaacs/abbrev-js">sign the
Contributor License Agreement</a>.

View File

@ -0,0 +1,23 @@
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,23 @@
# abbrev-js
Just like [ruby's Abbrev](http://apidock.com/ruby/Abbrev).
Usage:
var abbrev = require("abbrev");
abbrev("foo", "fool", "folding", "flop");
// returns:
{ fl: 'flop'
, flo: 'flop'
, flop: 'flop'
, fol: 'folding'
, fold: 'folding'
, foldi: 'folding'
, foldin: 'folding'
, folding: 'folding'
, foo: 'foo'
, fool: 'fool'
}
This is handy for command-line scripts, or other cases where you want to be able to accept shorthands.

View File

@ -0,0 +1,62 @@
module.exports = exports = abbrev.abbrev = abbrev
abbrev.monkeyPatch = monkeyPatch
function monkeyPatch () {
Object.defineProperty(Array.prototype, 'abbrev', {
value: function () { return abbrev(this) },
enumerable: false, configurable: true, writable: true
})
Object.defineProperty(Object.prototype, 'abbrev', {
value: function () { return abbrev(Object.keys(this)) },
enumerable: false, configurable: true, writable: true
})
}
function abbrev (list) {
if (arguments.length !== 1 || !Array.isArray(list)) {
list = Array.prototype.slice.call(arguments, 0)
}
for (var i = 0, l = list.length, args = [] ; i < l ; i ++) {
args[i] = typeof list[i] === "string" ? list[i] : String(list[i])
}
// sort them lexicographically, so that they're next to their nearest kin
args = args.sort(lexSort)
// walk through each, seeing how much it has in common with the next and previous
var abbrevs = {}
, prev = ""
for (var i = 0, l = args.length ; i < l ; i ++) {
var current = args[i]
, next = args[i + 1] || ""
, nextMatches = true
, prevMatches = true
if (current === next) continue
for (var j = 0, cl = current.length ; j < cl ; j ++) {
var curChar = current.charAt(j)
nextMatches = nextMatches && curChar === next.charAt(j)
prevMatches = prevMatches && curChar === prev.charAt(j)
if (!nextMatches && !prevMatches) {
j ++
break
}
}
prev = current
if (j === cl) {
abbrevs[current] = current
continue
}
for (var a = current.substr(0, j) ; j <= cl ; j ++) {
abbrevs[a] = current
a += current.charAt(j)
}
}
return abbrevs
}
function lexSort (a, b) {
return a === b ? 0 : a > b ? 1 : -1
}

View File

@ -0,0 +1,46 @@
{
"name": "abbrev",
"version": "1.0.5",
"description": "Like ruby's abbrev module, but in js",
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me"
},
"main": "abbrev.js",
"scripts": {
"test": "node test.js"
},
"repository": {
"type": "git",
"url": "http://github.com/isaacs/abbrev-js"
},
"license": {
"type": "MIT",
"url": "https://github.com/isaacs/abbrev-js/raw/master/LICENSE"
},
"bugs": {
"url": "https://github.com/isaacs/abbrev-js/issues"
},
"homepage": "https://github.com/isaacs/abbrev-js",
"_id": "abbrev@1.0.5",
"_shasum": "5d8257bd9ebe435e698b2fa431afde4fe7b10b03",
"_from": "abbrev@>=1.0.0 <1.1.0",
"_npmVersion": "1.4.7",
"_npmUser": {
"name": "isaacs",
"email": "i@izs.me"
},
"maintainers": [
{
"name": "isaacs",
"email": "i@izs.me"
}
],
"dist": {
"shasum": "5d8257bd9ebe435e698b2fa431afde4fe7b10b03",
"tarball": "http://registry.npmjs.org/abbrev/-/abbrev-1.0.5.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.5.tgz",
"readme": "ERROR: No README data found!"
}

View File

@ -0,0 +1,47 @@
var abbrev = require('./abbrev.js')
var assert = require("assert")
var util = require("util")
console.log("TAP Version 13")
var count = 0
function test (list, expect) {
count++
var actual = abbrev(list)
assert.deepEqual(actual, expect,
"abbrev("+util.inspect(list)+") === " + util.inspect(expect) + "\n"+
"actual: "+util.inspect(actual))
actual = abbrev.apply(exports, list)
assert.deepEqual(abbrev.apply(exports, list), expect,
"abbrev("+list.map(JSON.stringify).join(",")+") === " + util.inspect(expect) + "\n"+
"actual: "+util.inspect(actual))
console.log('ok - ' + list.join(' '))
}
test([ "ruby", "ruby", "rules", "rules", "rules" ],
{ rub: 'ruby'
, ruby: 'ruby'
, rul: 'rules'
, rule: 'rules'
, rules: 'rules'
})
test(["fool", "foom", "pool", "pope"],
{ fool: 'fool'
, foom: 'foom'
, poo: 'pool'
, pool: 'pool'
, pop: 'pope'
, pope: 'pope'
})
test(["a", "ab", "abc", "abcd", "abcde", "acde"],
{ a: 'a'
, ab: 'ab'
, abc: 'abc'
, abcd: 'abcd'
, abcde: 'abcde'
, ac: 'acde'
, acd: 'acde'
, acde: 'acde'
})
console.log("0..%d", count)

View File

@ -0,0 +1,20 @@
{
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 4,
"eqnull": true,
"latedef": true,
"noarg": true,
"noempty": true,
"quotmark": "single",
"undef": true,
"unused": "vars",
"strict": true,
"trailing": true,
"validthis": true,
"onevar": true,
"node": true
}

View File

@ -0,0 +1,19 @@
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,27 @@
Copyright (c) 2009-2011, Mozilla Foundation and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the names of the Mozilla Foundation nor the names of project
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,111 @@
### Escodegen [![Build Status](https://secure.travis-ci.org/Constellation/escodegen.svg)](http://travis-ci.org/Constellation/escodegen) [![Build Status](https://drone.io/github.com/Constellation/escodegen/status.png)](https://drone.io/github.com/Constellation/escodegen/latest)
Escodegen ([escodegen](http://github.com/Constellation/escodegen)) is
[ECMAScript](http://www.ecma-international.org/publications/standards/Ecma-262.htm)
(also popularly known as [JavaScript](http://en.wikipedia.org/wiki/JavaScript>JavaScript))
code generator from [Parser API](https://developer.mozilla.org/en/SpiderMonkey/Parser_API) AST.
See [online generator demo](http://constellation.github.com/escodegen/demo/index.html).
### Install
Escodegen can be used in a web browser:
<script src="escodegen.browser.js"></script>
escodegen.browser.js is found in tagged-revision. See Tags on GitHub.
Or in a Node.js application via the package manager:
npm install escodegen
### Usage
A simple example: the program
escodegen.generate({
type: 'BinaryExpression',
operator: '+',
left: { type: 'Literal', value: 40 },
right: { type: 'Literal', value: 2 }
});
produces the string `'40 + 2'`
See the [API page](https://github.com/Constellation/escodegen/wiki/API) for
options. To run the tests, execute `npm test` in the root directory.
### Building browser bundle / minified browser bundle
At first, executing `npm install` to install the all dev dependencies.
After that,
npm run-script build
will generate `escodegen.browser.js`, it is used on the browser environment.
And,
npm run-script build-min
will generate minified `escodegen.browser.min.js`.
### License
#### Escodegen
Copyright (C) 2012 [Yusuke Suzuki](http://github.com/Constellation)
(twitter: [@Constellation](http://twitter.com/Constellation)) and other contributors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#### source-map
SourceNodeMocks has a limited interface of mozilla/source-map SourceNode implementations.
Copyright (c) 2009-2011, Mozilla Foundation and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the names of the Mozilla Foundation nor the names of project
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,45 @@
#!/usr/bin/env node
/*
Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint sloppy:true node:true */
var fs = require('fs'),
path = require('path'),
root = path.join(path.dirname(fs.realpathSync(__filename)), '..'),
esprima = require('esprima'),
escodegen = require(root),
files = process.argv.splice(2);
if (files.length === 0) {
console.log('Usage:');
console.log(' escodegen file.js');
process.exit(1);
}
files.forEach(function (filename) {
var content = fs.readFileSync(filename, 'utf-8');
console.log(escodegen.generate(esprima.parse(content)));
});
/* vim: set sw=4 ts=4 et tw=80 : */

View File

@ -0,0 +1,45 @@
#!/usr/bin/env node
/*
Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint sloppy:true node:true */
var fs = require('fs'),
path = require('path'),
root = path.join(path.dirname(fs.realpathSync(__filename)), '..'),
esprima = require('esprima'),
escodegen = require(root),
files = process.argv.splice(2);
if (files.length === 0) {
console.log('Usage:');
console.log(' esgenerate file.json');
process.exit(1);
}
files.forEach(function (filename) {
var content = fs.readFileSync(filename, 'utf-8');
console.log(escodegen.generate(JSON.parse(content)));
});
/* vim: set sw=4 ts=4 et tw=80 : */

View File

@ -0,0 +1,58 @@
{
"name": "escodegen",
"description": "ECMAScript code generator",
"homepage": "http://github.com/Constellation/escodegen",
"main": "escodegen.js",
"bin": {
"esgenerate": "./bin/esgenerate.js",
"escodegen": "./bin/escodegen.js"
},
"version": "1.3.3",
"engines": {
"node": ">=0.10.0"
},
"maintainers": [
{
"name": "Yusuke Suzuki",
"email": "utatane.tea@gmail.com",
"web": "http://github.com/Constellation"
}
],
"repository": {
"type": "git",
"url": "http://github.com/Constellation/escodegen.git"
},
"dependencies": {
"esutils": "~1.0.0",
"estraverse": "~1.5.0",
"esprima": "~1.1.1"
},
"optionalDependencies": {},
"devDependencies": {
"esprima-moz": "*",
"semver": "*",
"chai": "~1.7.2",
"gulp": "~3.5.0",
"gulp-mocha": "~0.4.1",
"gulp-eslint": "~0.1.2",
"jshint-stylish": "~0.1.5",
"gulp-jshint": "~1.4.0",
"commonjs-everywhere": "~0.9.6",
"bluebird": "~1.2.0",
"bower-registry-client": "~0.2.0"
},
"licenses": [
{
"type": "BSD",
"url": "http://github.com/Constellation/escodegen/raw/master/LICENSE.BSD"
}
],
"scripts": {
"test": "gulp travis",
"unit-test": "gulp test",
"lint": "gulp lint",
"release": "node tools/release.js",
"build-min": "./node_modules/.bin/cjsify -ma path: tools/entry-point.js > escodegen.browser.min.js",
"build": "./node_modules/.bin/cjsify -a path: tools/entry-point.js > escodegen.browser.js"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
/*
Copyright (C) 2014 Yusuke Suzuki <utatane.tea@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
'use strict';
var gulp = require('gulp');
var mocha = require('gulp-mocha');
var jshint = require('gulp-jshint');
var eslint = require('gulp-eslint');
var TEST = [ 'test/*.js' ];
var LINT = [
'gulpfile.js',
'escodegen.js'
];
var ESLINT_OPTION = {
'rulesdir': 'tools/rules/',
'rules': {
'push-with-multiple-arguments': 2,
'quotes': 0,
'eqeqeq': 0,
'no-use-before-define': 0,
'no-shadow': 0
},
'env': {
'node': true
}
};
gulp.task('test', function () {
return gulp.src(TEST).pipe(mocha({ reporter: 'spec' }));
});
gulp.task('lint', function () {
return gulp.src(LINT)
.pipe(jshint('.jshintrc'))
.pipe(jshint.reporter(require('jshint-stylish')))
.pipe(jshint.reporter('fail'))
.pipe(eslint(ESLINT_OPTION))
.pipe(eslint.formatEach('compact', process.stderr));
});
gulp.task('travis', [ 'lint', 'test' ]);
gulp.task('default', [ 'travis' ]);

View File

@ -0,0 +1 @@
../esprima/bin/esparse.js

View File

@ -0,0 +1 @@
../esprima/bin/esvalidate.js

Some files were not shown because too many files have changed in this diff Show More