aboutsummaryrefslogtreecommitdiff
path: root/node_modules/istanbul/lib
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/istanbul/lib')
-rw-r--r--node_modules/istanbul/lib/assets/base.css213
-rw-r--r--node_modules/istanbul/lib/assets/sort-arrow-sprite.pngbin0 -> 209 bytes
-rw-r--r--node_modules/istanbul/lib/assets/sorter.js158
-rw-r--r--node_modules/istanbul/lib/assets/vendor/prettify.css1
-rw-r--r--node_modules/istanbul/lib/assets/vendor/prettify.js1
-rwxr-xr-xnode_modules/istanbul/lib/cli.js99
-rw-r--r--node_modules/istanbul/lib/collector.js122
-rw-r--r--node_modules/istanbul/lib/command/check-coverage.js195
-rw-r--r--node_modules/istanbul/lib/command/common/run-with-cover.js261
-rw-r--r--node_modules/istanbul/lib/command/cover.js33
-rw-r--r--node_modules/istanbul/lib/command/help.js102
-rw-r--r--node_modules/istanbul/lib/command/index.js33
-rw-r--r--node_modules/istanbul/lib/command/instrument.js265
-rw-r--r--node_modules/istanbul/lib/command/report.js123
-rw-r--r--node_modules/istanbul/lib/command/test.js31
-rw-r--r--node_modules/istanbul/lib/config.js491
-rw-r--r--node_modules/istanbul/lib/hook.js198
-rw-r--r--node_modules/istanbul/lib/instrumenter.js1097
-rw-r--r--node_modules/istanbul/lib/object-utils.js425
-rw-r--r--node_modules/istanbul/lib/register-plugins.js15
-rw-r--r--node_modules/istanbul/lib/report/clover.js227
-rw-r--r--node_modules/istanbul/lib/report/cobertura.js221
-rw-r--r--node_modules/istanbul/lib/report/common/defaults.js51
-rw-r--r--node_modules/istanbul/lib/report/html.js572
-rw-r--r--node_modules/istanbul/lib/report/index.js104
-rw-r--r--node_modules/istanbul/lib/report/json-summary.js75
-rw-r--r--node_modules/istanbul/lib/report/json.js69
-rw-r--r--node_modules/istanbul/lib/report/lcov.js65
-rw-r--r--node_modules/istanbul/lib/report/lcovonly.js103
-rw-r--r--node_modules/istanbul/lib/report/none.js41
-rw-r--r--node_modules/istanbul/lib/report/teamcity.js92
-rw-r--r--node_modules/istanbul/lib/report/templates/foot.txt20
-rw-r--r--node_modules/istanbul/lib/report/templates/head.txt60
-rw-r--r--node_modules/istanbul/lib/report/text-lcov.js50
-rw-r--r--node_modules/istanbul/lib/report/text-summary.js93
-rw-r--r--node_modules/istanbul/lib/report/text.js234
-rw-r--r--node_modules/istanbul/lib/reporter.js111
-rw-r--r--node_modules/istanbul/lib/store/fslookup.js61
-rw-r--r--node_modules/istanbul/lib/store/index.js123
-rw-r--r--node_modules/istanbul/lib/store/memory.js56
-rw-r--r--node_modules/istanbul/lib/store/tmp.js81
-rw-r--r--node_modules/istanbul/lib/util/factory.js88
-rw-r--r--node_modules/istanbul/lib/util/file-matcher.js76
-rw-r--r--node_modules/istanbul/lib/util/file-writer.js154
-rw-r--r--node_modules/istanbul/lib/util/help-formatter.js30
-rw-r--r--node_modules/istanbul/lib/util/input-error.js12
-rw-r--r--node_modules/istanbul/lib/util/insertion-text.js109
-rw-r--r--node_modules/istanbul/lib/util/meta.js13
-rw-r--r--node_modules/istanbul/lib/util/tree-summarizer.js213
-rw-r--r--node_modules/istanbul/lib/util/writer.js92
-rw-r--r--node_modules/istanbul/lib/util/yui-load-hook.js49
51 files changed, 7208 insertions, 0 deletions
diff --git a/node_modules/istanbul/lib/assets/base.css b/node_modules/istanbul/lib/assets/base.css
new file mode 100644
index 000000000..29737bcb0
--- /dev/null
+++ b/node_modules/istanbul/lib/assets/base.css
@@ -0,0 +1,213 @@
+body, html {
+ margin:0; padding: 0;
+ height: 100%;
+}
+body {
+ font-family: Helvetica Neue, Helvetica, Arial;
+ font-size: 14px;
+ color:#333;
+}
+.small { font-size: 12px; }
+*, *:after, *:before {
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing:border-box;
+ box-sizing:border-box;
+ }
+h1 { font-size: 20px; margin: 0;}
+h2 { font-size: 14px; }
+pre {
+ font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ margin: 0;
+ padding: 0;
+ -moz-tab-size: 2;
+ -o-tab-size: 2;
+ tab-size: 2;
+}
+a { color:#0074D9; text-decoration:none; }
+a:hover { text-decoration:underline; }
+.strong { font-weight: bold; }
+.space-top1 { padding: 10px 0 0 0; }
+.pad2y { padding: 20px 0; }
+.pad1y { padding: 10px 0; }
+.pad2x { padding: 0 20px; }
+.pad2 { padding: 20px; }
+.pad1 { padding: 10px; }
+.space-left2 { padding-left:55px; }
+.space-right2 { padding-right:20px; }
+.center { text-align:center; }
+.clearfix { display:block; }
+.clearfix:after {
+ content:'';
+ display:block;
+ height:0;
+ clear:both;
+ visibility:hidden;
+ }
+.fl { float: left; }
+@media only screen and (max-width:640px) {
+ .col3 { width:100%; max-width:100%; }
+ .hide-mobile { display:none!important; }
+}
+
+.quiet {
+ color: #7f7f7f;
+ color: rgba(0,0,0,0.5);
+}
+.quiet a { opacity: 0.7; }
+
+.fraction {
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+ font-size: 10px;
+ color: #555;
+ background: #E8E8E8;
+ padding: 4px 5px;
+ border-radius: 3px;
+ vertical-align: middle;
+}
+
+div.path a:link, div.path a:visited { color: #333; }
+table.coverage {
+ border-collapse: collapse;
+ margin: 10px 0 0 0;
+ padding: 0;
+}
+
+table.coverage td {
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+}
+table.coverage td.line-count {
+ text-align: right;
+ padding: 0 5px 0 20px;
+}
+table.coverage td.line-coverage {
+ text-align: right;
+ padding-right: 10px;
+ min-width:20px;
+}
+
+table.coverage td span.cline-any {
+ display: inline-block;
+ padding: 0 5px;
+ width: 100%;
+}
+.missing-if-branch {
+ display: inline-block;
+ margin-right: 5px;
+ border-radius: 3px;
+ position: relative;
+ padding: 0 4px;
+ background: #333;
+ 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;
+}
+.coverage-summary {
+ border-collapse: collapse;
+ width: 100%;
+}
+.coverage-summary tr { border-bottom: 1px solid #bbb; }
+.keyline-all { border: 1px solid #ddd; }
+.coverage-summary td, .coverage-summary th { padding: 10px; }
+.coverage-summary tbody { border: 1px solid #bbb; }
+.coverage-summary td { border-right: 1px solid #bbb; }
+.coverage-summary td:last-child { border-right: none; }
+.coverage-summary th {
+ text-align: left;
+ font-weight: normal;
+ white-space: nowrap;
+}
+.coverage-summary th.file { border-right: none !important; }
+.coverage-summary th.pct { }
+.coverage-summary th.pic,
+.coverage-summary th.abs,
+.coverage-summary td.pct,
+.coverage-summary td.abs { text-align: right; }
+.coverage-summary td.file { white-space: nowrap; }
+.coverage-summary td.pic { min-width: 120px !important; }
+.coverage-summary tfoot td { }
+
+.coverage-summary .sorter {
+ height: 10px;
+ width: 7px;
+ display: inline-block;
+ margin-left: 0.5em;
+ background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
+}
+.coverage-summary .sorted .sorter {
+ background-position: 0 -20px;
+}
+.coverage-summary .sorted-desc .sorter {
+ background-position: 0 -10px;
+}
+.status-line { height: 10px; }
+/* dark red */
+.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
+.low .chart { border:1px solid #C21F39 }
+/* medium red */
+.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
+/* light red */
+.low, .cline-no { background:#FCE1E5 }
+/* light green */
+.high, .cline-yes { background:rgb(230,245,208) }
+/* medium green */
+.cstat-yes { background:rgb(161,215,106) }
+/* dark green */
+.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
+.high .chart { border:1px solid rgb(77,146,33) }
+/* dark yellow (gold) */
+.medium .chart { border:1px solid #f9cd0b; }
+.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
+/* light yellow */
+.medium { background: #fff4c2; }
+/* light gray */
+span.cline-neutral { background: #eaeaea; }
+
+.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; }
+
+
+.cover-fill, .cover-empty {
+ display:inline-block;
+ height: 12px;
+}
+.chart {
+ line-height: 0;
+}
+.cover-empty {
+ background: white;
+}
+.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; }
+
+.wrapper {
+ min-height: 100%;
+ height: auto !important;
+ height: 100%;
+ margin: 0 auto -48px;
+}
+.footer, .push {
+ height: 48px;
+}
diff --git a/node_modules/istanbul/lib/assets/sort-arrow-sprite.png b/node_modules/istanbul/lib/assets/sort-arrow-sprite.png
new file mode 100644
index 000000000..03f704a60
--- /dev/null
+++ b/node_modules/istanbul/lib/assets/sort-arrow-sprite.png
Binary files differ
diff --git a/node_modules/istanbul/lib/assets/sorter.js b/node_modules/istanbul/lib/assets/sorter.js
new file mode 100644
index 000000000..6c5034e40
--- /dev/null
+++ b/node_modules/istanbul/lib/assets/sorter.js
@@ -0,0 +1,158 @@
+var addSorting = (function () {
+ "use strict";
+ var cols,
+ currentSort = {
+ index: 0,
+ desc: false
+ };
+
+ // returns the summary table element
+ function getTable() { return document.querySelector('.coverage-summary'); }
+ // returns the thead element of the summary table
+ function getTableHeader() { return getTable().querySelector('thead tr'); }
+ // returns the tbody element of the summary table
+ function getTableBody() { return getTable().querySelector('tbody'); }
+ // returns the th element for nth column
+ function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; }
+
+ // loads all columns
+ function loadColumns() {
+ var colNodes = getTableHeader().querySelectorAll('th'),
+ colNode,
+ cols = [],
+ col,
+ i;
+
+ for (i = 0; i < colNodes.length; i += 1) {
+ colNode = colNodes[i];
+ col = {
+ key: colNode.getAttribute('data-col'),
+ sortable: !colNode.getAttribute('data-nosort'),
+ type: colNode.getAttribute('data-type') || 'string'
+ };
+ cols.push(col);
+ if (col.sortable) {
+ col.defaultDescSort = col.type === 'number';
+ colNode.innerHTML = colNode.innerHTML + '<span class="sorter"></span>';
+ }
+ }
+ return cols;
+ }
+ // attaches a data attribute to every tr element with an object
+ // of data values keyed by column name
+ function loadRowData(tableRow) {
+ var tableCols = tableRow.querySelectorAll('td'),
+ colNode,
+ col,
+ data = {},
+ i,
+ val;
+ for (i = 0; i < tableCols.length; i += 1) {
+ colNode = tableCols[i];
+ col = cols[i];
+ val = colNode.getAttribute('data-value');
+ if (col.type === 'number') {
+ val = Number(val);
+ }
+ data[col.key] = val;
+ }
+ return data;
+ }
+ // loads all row data
+ function loadData() {
+ var rows = getTableBody().querySelectorAll('tr'),
+ i;
+
+ for (i = 0; i < rows.length; i += 1) {
+ rows[i].data = loadRowData(rows[i]);
+ }
+ }
+ // sorts the table using the data for the ith column
+ function sortByIndex(index, desc) {
+ var key = cols[index].key,
+ sorter = function (a, b) {
+ a = a.data[key];
+ b = b.data[key];
+ return a < b ? -1 : a > b ? 1 : 0;
+ },
+ finalSorter = sorter,
+ tableBody = document.querySelector('.coverage-summary tbody'),
+ rowNodes = tableBody.querySelectorAll('tr'),
+ rows = [],
+ i;
+
+ if (desc) {
+ finalSorter = function (a, b) {
+ return -1 * sorter(a, b);
+ };
+ }
+
+ for (i = 0; i < rowNodes.length; i += 1) {
+ rows.push(rowNodes[i]);
+ tableBody.removeChild(rowNodes[i]);
+ }
+
+ rows.sort(finalSorter);
+
+ for (i = 0; i < rows.length; i += 1) {
+ tableBody.appendChild(rows[i]);
+ }
+ }
+ // removes sort indicators for current column being sorted
+ function removeSortIndicators() {
+ var col = getNthColumn(currentSort.index),
+ cls = col.className;
+
+ cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
+ col.className = cls;
+ }
+ // adds sort indicators for current column being sorted
+ function addSortIndicators() {
+ getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted';
+ }
+ // adds event listeners for all sorter widgets
+ function enableUI() {
+ var i,
+ el,
+ ithSorter = function ithSorter(i) {
+ var col = cols[i];
+
+ return function () {
+ var desc = col.defaultDescSort;
+
+ if (currentSort.index === i) {
+ desc = !currentSort.desc;
+ }
+ sortByIndex(i, desc);
+ removeSortIndicators();
+ currentSort.index = i;
+ currentSort.desc = desc;
+ addSortIndicators();
+ };
+ };
+ for (i =0 ; i < cols.length; i += 1) {
+ if (cols[i].sortable) {
+ // add the click event handler on the th so users
+ // dont have to click on those tiny arrows
+ el = getNthColumn(i).querySelector('.sorter').parentElement;
+ if (el.addEventListener) {
+ el.addEventListener('click', ithSorter(i));
+ } else {
+ el.attachEvent('onclick', ithSorter(i));
+ }
+ }
+ }
+ }
+ // adds sorting functionality to the UI
+ return function () {
+ if (!getTable()) {
+ return;
+ }
+ cols = loadColumns();
+ loadData(cols);
+ addSortIndicators();
+ enableUI();
+ };
+})();
+
+window.addEventListener('load', addSorting);
diff --git a/node_modules/istanbul/lib/assets/vendor/prettify.css b/node_modules/istanbul/lib/assets/vendor/prettify.css
new file mode 100644
index 000000000..b317a7cda
--- /dev/null
+++ b/node_modules/istanbul/lib/assets/vendor/prettify.css
@@ -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}
diff --git a/node_modules/istanbul/lib/assets/vendor/prettify.js b/node_modules/istanbul/lib/assets/vendor/prettify.js
new file mode 100644
index 000000000..ef51e0386
--- /dev/null
+++ b/node_modules/istanbul/lib/assets/vendor/prettify.js
@@ -0,0 +1 @@
+window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.ignoreCase){ac=true}else{if(/[a-z]/i.test(ae.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,""))){S=true;ac=false;break}}}var Y={b:8,t:9,n:10,v:11,f:12,r:13};function ab(ah){var ag=ah.charCodeAt(0);if(ag!==92){return ag}var af=ah.charAt(1);ag=Y[af];if(ag){return ag}else{if("0"<=af&&af<="7"){return parseInt(ah.substring(1),8)}else{if(af==="u"||af==="x"){return parseInt(ah.substring(2),16)}else{return ah.charCodeAt(1)}}}}function T(af){if(af<32){return(af<16?"\\x0":"\\x")+af.toString(16)}var ag=String.fromCharCode(af);if(ag==="\\"||ag==="-"||ag==="["||ag==="]"){ag="\\"+ag}return ag}function X(am){var aq=am.substring(1,am.length-1).match(new RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));var ak=[];var af=[];var ao=aq[0]==="^";for(var ar=ao?1:0,aj=aq.length;ar<aj;++ar){var ah=aq[ar];if(/\\[bdsw]/i.test(ah)){ak.push(ah)}else{var ag=ab(ah);var al;if(ar+2<aj&&"-"===aq[ar+1]){al=ab(aq[ar+2]);ar+=2}else{al=ag}af.push([ag,al]);if(!(al<65||ag>122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;ar<af.length;++ar){var at=af[ar];if(at[0]<=ap[1]+1){ap[1]=Math.max(ap[1],at[1])}else{ai.push(ap=at)}}var an=["["];if(ao){an.push("^")}an.push.apply(an,ak);for(var ar=0;ar<ai.length;++ar){var at=ai[ar];an.push(T(at[0]));if(at[1]>at[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){an[af]=-1}}}}for(var ak=1;ak<an.length;++ak){if(-1===an[ak]){an[ak]=++ad}}for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am;if(an[am]===undefined){aj[ak]="(?:"}}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){aj[ak]="\\"+an[am]}}}}for(var ak=0,am=0;ak<ah;++ak){if("^"===aj[ak]&&"^"!==aj[ak+1]){aj[ak]=""}}if(al.ignoreCase&&S){for(var ak=0;ak<ah;++ak){var ag=aj[ak];var ai=ag.charAt(0);if(ag.length>=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.global||ae.multiline){throw new Error(""+ae)}aa.push("(?:"+W(ae)+")")}return new RegExp(aa.join("|"),ac?"gi":"g")}function a(V){var U=/(?:^|\s)nocode(?:\s|$)/;var X=[];var T=0;var Z=[];var W=0;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=document.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Y=S&&"pre"===S.substring(0,3);function aa(ab){switch(ab.nodeType){case 1:if(U.test(ab.className)){return}for(var ae=ab.firstChild;ae;ae=ae.nextSibling){aa(ae)}var ad=ab.nodeName;if("BR"===ad||"LI"===ad){X[W]="\n";Z[W<<1]=T++;Z[(W++<<1)|1]=ab}break;case 3:case 4:var ac=ab.nodeValue;if(ac.length){if(!Y){ac=ac.replace(/[ \t\r\n]+/g," ")}else{ac=ac.replace(/\r\n?/g,"\n")}X[W]=ac;Z[W<<1]=T;T+=ac.length;Z[(W++<<1)|1]=ab}break}}aa(V);return{sourceCode:X.join("").replace(/\n$/,""),spans:Z}}function B(S,U,W,T){if(!U){return}var V={sourceCode:U,basePos:S};W(V);T.push.apply(T,V.decorations)}var v=/\S/;function o(S){var V=undefined;for(var U=S.firstChild;U;U=U.nextSibling){var T=U.nodeType;V=(T===1)?(V?S:U):(T===3)?(v.test(U.nodeValue)?S:V):V}return V===S?undefined:V}function g(U,T){var S={};var V;(function(){var ad=U.concat(T);var ah=[];var ag={};for(var ab=0,Z=ad.length;ab<Z;++ab){var Y=ad[ab];var ac=Y[3];if(ac){for(var ae=ac.length;--ae>=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae<aq;++ae){var ag=an[ae];var ap=aj[ag];var ai=void 0;var am;if(typeof ap==="string"){am=false}else{var aa=S[ag.charAt(0)];if(aa){ai=ag.match(aa[1]);ap=aa[0]}else{for(var ao=0;ao<X;++ao){aa=T[ao];ai=ag.match(aa[1]);if(ai){ap=aa[0];break}}if(!ai){ap=F}}am=ap.length>=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y<W.length;++Y){ae(W[Y])}if(ag===(ag|0)){W[0].setAttribute("value",ag)}var aa=ac.createElement("OL");aa.className="linenums";var X=Math.max(0,((ag-1))|0)||0;for(var Y=0,T=W.length;Y<T;++Y){af=W[Y];af.className="L"+((Y+X)%10);if(!af.firstChild){af.appendChild(ac.createTextNode("\xA0"))}aa.appendChild(af)}V.appendChild(aa)}function D(ac){var aj=/\bMSIE\b/.test(navigator.userAgent);var am=/\n/g;var al=ac.sourceCode;var an=al.length;var V=0;var aa=ac.spans;var T=aa.length;var ah=0;var X=ac.decorations;var Y=X.length;var Z=0;X[Y]=an;var ar,aq;for(aq=ar=0;aq<Y;){if(X[aq]!==X[aq+2]){X[ar++]=X[aq++];X[ar++]=X[aq++]}else{aq+=2}}Y=ar;for(aq=ar=0;aq<Y;){var at=X[aq];var ab=X[aq+1];var W=aq+2;while(W+2<=Y&&X[W+1]===ab){W+=2}X[ar++]=at;X[ar++]=ab;aq=W}Y=X.length=ar;var ae=null;while(ah<T){var af=aa[ah];var S=aa[ah+2]||an;var ag=X[Z];var ap=X[Z+2]||an;var W=Math.min(S,ap);var ak=aa[ah+1];var U;if(ak.nodeType!==1&&(U=al.substring(V,W))){if(aj){U=U.replace(am,"\r")}ak.nodeValue=U;var ai=ak.ownerDocument;var ao=ai.createElement("SPAN");ao.className=X[Z+1];var ad=ak.parentNode;ad.replaceChild(ao,ak);ao.appendChild(ak);if(V<S){aa[ah+1]=ak=ai.createTextNode(al.substring(W,S));ad.insertBefore(ak,ao.nextSibling)}}V=W;if(V>=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*</.test(S)?"default-markup":"default-code"}return t[T]}c(K,["default-code"]);c(g([],[[F,/^[^<?]+/],[E,/^<!\w[^>]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa<ac.length;++aa){for(var Z=0,V=ac[aa].length;Z<V;++Z){T.push(ac[aa][Z])}}ac=null;var W=Date;if(!W.now){W={now:function(){return +(new Date)}}}var X=0;var S;var ab=/\blang(?:uage)?-([\w.]+)(?!\S)/;var ae=/\bprettyprint\b/;function U(){var ag=(window.PR_SHOULD_USE_CONTINUATION?W.now()+250:Infinity);for(;X<T.length&&W.now()<ag;X++){var aj=T[X];var ai=aj.className;if(ai.indexOf("prettyprint")>=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X<T.length){setTimeout(U,250)}else{if(ad){ad()}}}U()}window.prettyPrintOne=y;window.prettyPrint=b;window.PR={createSimpleLexer:g,registerLangHandler:c,sourceDecorator:i,PR_ATTRIB_NAME:P,PR_ATTRIB_VALUE:n,PR_COMMENT:j,PR_DECLARATION:E,PR_KEYWORD:z,PR_LITERAL:G,PR_NOCODE:N,PR_PLAIN:F,PR_PUNCTUATION:L,PR_SOURCE:J,PR_STRING:C,PR_TAG:m,PR_TYPE:O}})();PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_DECLARATION,/^<!\w[^>]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^<script\b[^>]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:<!--|-->)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]);
diff --git a/node_modules/istanbul/lib/cli.js b/node_modules/istanbul/lib/cli.js
new file mode 100755
index 000000000..5a8f1bfca
--- /dev/null
+++ b/node_modules/istanbul/lib/cli.js
@@ -0,0 +1,99 @@
+#!/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 async = require('async'),
+ Command = require('./command'),
+ inputError = require('./util/input-error'),
+ exitProcess = 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 exit(ex, code) {
+ // flush output for Node.js Windows pipe bug
+ // https://github.com/joyent/node/issues/6247 is just one bug example
+ // https://github.com/visionmedia/mocha/issues/333 has a good discussion
+ var streams = [process.stdout, process.stderr];
+ async.forEach(streams, function (stream, done) {
+ // submit a write request and wait until it's written
+ stream.write('', done);
+ }, function () {
+ if (ex) {
+ if (typeof ex === 'string') {
+ console.error(ex);
+ exitProcess(1);
+ } else {
+ throw ex; // turn it into an uncaught exception
+ }
+ } else {
+ exitProcess(code);
+ }
+ });
+}
+
+function errHandler (ex) {
+ if (!ex) { return; }
+ if (!ex.inputError) {
+ // exit with exception stack trace
+ exit(ex);
+ } else {
+ //don't print nasty traces but still exit(1)
+ console.error(ex.message);
+ console.error('Try "istanbul help" for usage');
+ exit(null, 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));
+ return;
+ }
+ commandObject.run(commandArgs, errHandler);
+}
+
+function runToCompletion(args) {
+ runCommand(args, errHandler);
+}
+
+/* istanbul ignore if: untestable */
+if (require.main === module) {
+ var args = Array.prototype.slice.call(process.argv, 2);
+ runToCompletion(args);
+}
+
+module.exports = {
+ runToCompletion: runToCompletion
+};
+
diff --git a/node_modules/istanbul/lib/collector.js b/node_modules/istanbul/lib/collector.js
new file mode 100644
index 000000000..f1b6b606e
--- /dev/null
+++ b/node_modules/istanbul/lib/collector.js
@@ -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.
+ */
+"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
+ * @module main
+ * @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; \ No newline at end of file
diff --git a/node_modules/istanbul/lib/command/check-coverage.js b/node_modules/istanbul/lib/command/check-coverage.js
new file mode 100644
index 000000000..5776c7780
--- /dev/null
+++ b/node_modules/istanbul/lib/command/check-coverage.js
@@ -0,0 +1,195 @@
+/*
+ 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'),
+ configuration = require('../config');
+
+function isAbsolute(file) {
+ if (path.isAbsolute) {
+ return path.isAbsolute(file);
+ }
+
+ return path.resolve(file) === path.normalize(file);
+}
+
+function CheckCoverageCommand() {
+ Command.call(this);
+}
+
+function removeFiles(covObj, root, files) {
+ var filesObj = {},
+ obj = {};
+
+ // Create lookup table.
+ files.forEach(function (file) {
+ filesObj[file] = true;
+ });
+
+ Object.keys(covObj).forEach(function (key) {
+ // Exclude keys will always be relative, but covObj keys can be absolute or relative
+ var excludeKey = isAbsolute(key) ? path.relative(root, key) : key;
+ // Also normalize for files that start with `./`, etc.
+ excludeKey = path.normalize(excludeKey);
+ if (filesObj[excludeKey] !== true) {
+ obj[key] = covObj[key];
+ }
+ });
+
+ return obj;
+}
+
+CheckCoverageCommand.TYPE = 'check-coverage';
+util.inherits(CheckCoverageCommand, Command);
+
+Command.mix(CheckCoverageCommand, {
+ synopsis: function () {
+ return "checks overall/per-file 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>', 'global statement coverage threshold'),
+ formatOption('--functions <threshold>', 'global function coverage threshold'),
+ formatOption('--branches <threshold>', 'global branch coverage threshold'),
+ formatOption('--lines <threshold>', 'global 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('Per-file thresholds can be specified via a configuration file.\n');
+ console.error('<include-pattern> is a glob 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 template = {
+ config: path,
+ root: path,
+ statements: Number,
+ lines: Number,
+ branches: Number,
+ functions: Number,
+ verbose: Boolean
+ },
+ opts = nopt(template, { v : '--verbose' }, args, 0),
+ // Translate to config opts.
+ config = configuration.loadFile(opts.config, {
+ verbose: opts.verbose,
+ check: {
+ global: {
+ statements: opts.statements,
+ lines: opts.lines,
+ branches: opts.branches,
+ functions: opts.functions
+ }
+ }
+ }),
+ 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; }
+ if (files.length === 0) {
+ return callback('ERROR: No coverage files found.');
+ }
+ files.forEach(function (file) {
+ var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8'));
+ collector.add(coverageObject);
+ });
+ var thresholds = {
+ global: {
+ statements: config.check.global.statements || 0,
+ branches: config.check.global.branches || 0,
+ lines: config.check.global.lines || 0,
+ functions: config.check.global.functions || 0,
+ excludes: config.check.global.excludes || []
+ },
+ each: {
+ statements: config.check.each.statements || 0,
+ branches: config.check.each.branches || 0,
+ lines: config.check.each.lines || 0,
+ functions: config.check.each.functions || 0,
+ excludes: config.check.each.excludes || []
+ }
+ },
+ rawCoverage = collector.getFinalCoverage(),
+ globalResults = utils.summarizeCoverage(removeFiles(rawCoverage, root, thresholds.global.excludes)),
+ eachResults = removeFiles(rawCoverage, root, thresholds.each.excludes);
+
+ // Summarize per-file results and mutate original results.
+ Object.keys(eachResults).forEach(function (key) {
+ eachResults[key] = utils.summarizeFileCoverage(eachResults[key]);
+ });
+
+ if (config.verbose) {
+ console.log('Compare actuals against thresholds');
+ console.log(JSON.stringify({ global: globalResults, each: eachResults, thresholds: thresholds }, undefined, 4));
+ }
+
+ function check(name, thresholds, actuals) {
+ [
+ "statements",
+ "branches",
+ "lines",
+ "functions"
+ ].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 ' + name + ' threshold (' + -1 * threshold + ')');
+ }
+ } else {
+ if (actual < threshold) {
+ errors.push('ERROR: Coverage for ' + key + ' (' + actual +
+ '%) does not meet ' + name + ' threshold (' + threshold + '%)');
+ }
+ }
+ });
+ }
+
+ check("global", thresholds.global, globalResults);
+
+ Object.keys(eachResults).forEach(function (key) {
+ check("per-file" + " (" + key + ") ", thresholds.each, eachResults[key]);
+ });
+
+ return callback(errors.length === 0 ? null : errors.join("\n"));
+ });
+ }
+});
+
+module.exports = CheckCoverageCommand;
+
+
diff --git a/node_modules/istanbul/lib/command/common/run-with-cover.js b/node_modules/istanbul/lib/command/common/run-with-cover.js
new file mode 100644
index 000000000..d4c5fafe4
--- /dev/null
+++ b/node_modules/istanbul/lib/command/common/run-with-cover.js
@@ -0,0 +1,261 @@
+/*
+ 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'),
+ Reporter = require('../../reporter'),
+ resolve = require('resolve'),
+ configuration = require('../../config');
+
+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 glob patterns e.g. "**/vendor/**"'),
+ formatOption('-i <include-pattern> [-i <include-pattern>]', 'one or more glob patterns e.g. "**/*.js"'),
+ 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('--include-all-sources', 'instrument all unused sources after running tests, defaults to false'),
+ formatOption('--[no-]include-pid', 'include PID in output coverage filename')
+ ].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,
+ 'include-all-sources': Boolean,
+ 'preload-sources': Boolean,
+ i: [ Array, String ],
+ 'include-pid': Boolean
+ },
+ opts = nopt(template, { v : '--verbose' }, args, 0),
+ overrides = {
+ verbose: opts.verbose,
+ instrumentation: {
+ root: opts.root,
+ 'default-excludes': opts['default-excludes'],
+ excludes: opts.x,
+ 'include-all-sources': opts['include-all-sources'],
+ 'preload-sources': opts['preload-sources'],
+ 'include-pid': opts['include-pid']
+ },
+ 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),
+ verbose = config.verbose,
+ cmdAndArgs = opts.argv.remain,
+ preserveComments = opts['preserve-comments'],
+ includePid = opts['include-pid'],
+ cmd,
+ cmdArgs,
+ reportingDir,
+ reporter = new Reporter(config),
+ 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());
+ mkdirp.sync(reportingDir); //ensure we fail early if we cannot do this
+ reporter.dir = reportingDir;
+ reporter.addAll(config.reporting.reports());
+ if (config.reporting.print() !== 'none') {
+ switch (config.reporting.print()) {
+ case 'detail':
+ reporter.add('text');
+ break;
+ case 'both':
+ reporter.add('text');
+ reporter.add('text-summary');
+ break;
+ default:
+ reporter.add('text-summary');
+ break;
+ }
+ }
+
+ excludes.push(path.relative(process.cwd(), path.join(reportingDir, '**', '*')));
+ matcherFor({
+ root: config.instrumentation.root() || process.cwd(),
+ includes: opts.i || config.instrumentation.extensions().map(function (ext) {
+ return '**/*' + ext;
+ }),
+ 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, extensions: config.instrumentation.extensions() },
+ 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'); }
+ callback(ex);
+ return;
+ }
+ }
+ }
+ 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 pidExt = includePid ? ('-' + process.pid) : '',
+ file = path.resolve(reportingDir, 'coverage' + pidExt + '.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
+ if (config.instrumentation.includeAllSources()) {
+ // Files that are not touched by code ran by the test runner is manually instrumented, to
+ // illustrate the missing coverage.
+ matchFn.files.forEach(function (file) {
+ if (!cov[file]) {
+ transformer(fs.readFileSync(file, 'utf-8'), file);
+
+ // When instrumenting the code, istanbul will give each FunctionDeclaration a value of 1 in coverState.s,
+ // presumably to compensate for function hoisting. We need to reset this, as the function was not hoisted,
+ // as it was never loaded.
+ Object.keys(instrumenter.coverState.s).forEach(function (key) {
+ instrumenter.coverState.s[key] = 0;
+ });
+
+ cov[file] = instrumenter.coverState;
+ }
+ });
+ }
+ 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('=============================================================================');
+ }
+ reporter.write(collector, true, callback);
+ });
+ runFn();
+ });
+ } else {
+ runFn();
+ }
+}
+
+module.exports = {
+ run: run,
+ usage: usage
+};
diff --git a/node_modules/istanbul/lib/command/cover.js b/node_modules/istanbul/lib/command/cover.js
new file mode 100644
index 000000000..ee8242917
--- /dev/null
+++ b/node_modules/istanbul/lib/command/cover.js
@@ -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;
+
diff --git a/node_modules/istanbul/lib/command/help.js b/node_modules/istanbul/lib/command/help.js
new file mode 100644
index 000000000..e3f6d76b7
--- /dev/null
+++ b/node_modules/istanbul/lib/command/help.js
@@ -0,0 +1,102 @@
+/*
+ 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('../config'),
+ 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 four sections for instrumentation, reporting, hooks, ' +
+ 'and checking. 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. See the api ' +
+ 'docs for an explanation of each key.'));
+
+ console.error('\n' +
+ formatPara('You only need to specify the keys that you want to override. Your overrides will be merged ' +
+ 'with the default config.'));
+ 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.'));
+ console.error('\n' +
+ formatPara('The `reportConfig` section allows you to configure each report format independently ' +
+ 'and has no command-line equivalent either.'));
+ console.error('\n' +
+ formatPara('The `check` section configures minimum threshold enforcement for coverage results. ' +
+ '`global` applies to all files together and `each` on a per-file basis. A list of files can ' +
+ 'be excluded from enforcement relative to root via the `exclude` property.'));
+ console.error('');
+}
+
+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;
+
+
diff --git a/node_modules/istanbul/lib/command/index.js b/node_modules/istanbul/lib/command/index.js
new file mode 100644
index 000000000..754cf1d0d
--- /dev/null
+++ b/node_modules/istanbul/lib/command/index.js
@@ -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;
+
diff --git a/node_modules/istanbul/lib/command/instrument.js b/node_modules/istanbul/lib/command/instrument.js
new file mode 100644
index 000000000..d08d6b87d
--- /dev/null
+++ b/node_modules/istanbul/lib/command/instrument.js
@@ -0,0 +1,265 @@
+/*
+ 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'),
+ once = require('once'),
+ 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'),
+ configuration = require('../config'),
+ 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, extensions) {
+ var processor = function (name, callback) {
+ var inputFile = path.resolve(inputDir, name),
+ outputFile = path.resolve(outputDir, name),
+ inputFileExtenstion = path.extname(inputFile),
+ isJavaScriptFile = extensions.indexOf(inputFileExtenstion) > -1,
+ oDir = path.dirname(outputFile),
+ readStream, writeStream;
+
+ callback = once(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 glob 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'),
+ formatOption('--es-modules', 'source code uses es import/export module syntax')
+ ].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,
+ 'es-modules': 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'],
+ 'es-modules': opts['es-modules']
+ }
+ },
+ 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 = iOpts.extensions().map(function(ext) {
+ return '**/*' + ext;
+ });
+ }
+
+ instrumenter = new Instrumenter({
+ coverageVariable: iOpts.variable(),
+ embedSource: iOpts.embedSource(),
+ noCompact: !iOpts.compact(),
+ preserveComments: iOpts.preserveComments(),
+ esModules: iOpts.esModules()
+ });
+
+ if (needBaseline) {
+ mkdirp.sync(path.dirname(baselineFile));
+ instrumenter = new BaselineCollector(instrumenter);
+ process.on('exit', function () {
+ console.log('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, iOpts.extensions());
+ });
+ } 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;
+
diff --git a/node_modules/istanbul/lib/command/report.js b/node_modules/istanbul/lib/command/report.js
new file mode 100644
index 000000000..7abc52cfd
--- /dev/null
+++ b/node_modules/istanbul/lib/command/report.js
@@ -0,0 +1,123 @@
+/*
+ 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'),
+ Reporter = require('../reporter'),
+ path = require('path'),
+ fs = require('fs'),
+ Collector = require('../collector'),
+ helpFormatter = require('../util/help-formatter'),
+ formatOption = helpFormatter.formatOption,
+ formatPara = helpFormatter.formatPara,
+ filesFor = require('../util/file-matcher').filesFor,
+ util = require('util'),
+ Command = require('./index'),
+ configuration = require('../config');
+
+function ReportCommand() {
+ Command.call(this);
+}
+
+ReportCommand.TYPE = 'report';
+util.inherits(ReportCommand, Command);
+
+function printDeprecationMessage(pat, fmt) {
+ console.error('**********************************************************************');
+ console.error('DEPRECATION WARNING! You are probably using the old format of the report command');
+ console.error('This will stop working soon, see `istanbul help report` for the new command format');
+ console.error('Assuming you meant: istanbul report --include=' + pat + ' ' + fmt);
+ console.error('**********************************************************************');
+}
+
+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> ... ]\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('--include <glob>', 'The glob pattern to select one or more coverage files, defaults to **/coverage*.json'),
+ formatOption('--verbose, -v', 'verbose mode')
+ ].join('\n\n'));
+
+ console.error('\n');
+ console.error('<format> is one of ');
+ Report.getReportList().forEach(function (name) {
+ console.error(formatOption(name, Report.create(name).synopsis()));
+ });
+ console.error("");
+ console.error(formatPara([
+ 'Default format is lcov unless otherwise specified in the config file.',
+ 'In addition you can tweak the file names for various reports using the config file.',
+ 'Type `istanbul help config` to see what can be tweaked.'
+ ].join(' ')));
+ console.error('\n');
+ },
+
+ run: function (args, callback) {
+
+ var template = {
+ config: path,
+ root: path,
+ dir: path,
+ include: String,
+ verbose: Boolean
+ },
+ opts = nopt(template, { v : '--verbose' }, args, 0),
+ includePattern = opts.include || '**/coverage*.json',
+ root,
+ collector = new Collector(),
+ config = configuration.loadFile(opts.config, {
+ verbose: opts.verbose,
+ reporting: {
+ dir: opts.dir
+ }
+ }),
+ formats = opts.argv.remain,
+ reporter = new Reporter(config);
+
+ // Start: backward compatible processing
+ if (formats.length === 2 &&
+ Report.getReportList().indexOf(formats[1]) < 0) {
+ includePattern = formats[1];
+ formats = [ formats[0] ];
+ printDeprecationMessage(includePattern, formats[0]);
+ }
+ // End: backward compatible processing
+
+ if (formats.length === 0) {
+ formats = config.reporting.reports();
+ }
+ if (formats.length === 0) {
+ formats = [ 'lcov' ];
+ }
+ reporter.addAll(formats);
+
+ 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);
+ });
+ reporter.write(collector, false, function (err) {
+ console.log('Done');
+ return callback(err);
+ });
+ });
+ }
+});
+
+module.exports = ReportCommand;
+
+
diff --git a/node_modules/istanbul/lib/command/test.js b/node_modules/istanbul/lib/command/test.js
new file mode 100644
index 000000000..59305074c
--- /dev/null
+++ b/node_modules/istanbul/lib/command/test.js
@@ -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;
diff --git a/node_modules/istanbul/lib/config.js b/node_modules/istanbul/lib/config.js
new file mode 100644
index 000000000..270193557
--- /dev/null
+++ b/node_modules/istanbul/lib/config.js
@@ -0,0 +1,491 @@
+/*
+ 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'),
+ existsSync = fs.existsSync || path.existsSync,
+ CAMEL_PATTERN = /([a-z])([A-Z])/g,
+ YML_PATTERN = /\.ya?ml$/,
+ yaml = require('js-yaml'),
+ defaults = require('./report/common/defaults');
+
+function defaultConfig(includeBackCompatAttrs) {
+ var ret = {
+ verbose: false,
+ instrumentation: {
+ root: '.',
+ extensions: ['.js'],
+ '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',
+ 'include-all-sources': false,
+ 'include-pid': false,
+ 'es-modules': false
+ },
+ reporting: {
+ print: 'summary',
+ reports: [ 'lcov' ],
+ dir: './coverage'
+ },
+ hooks: {
+ 'hook-run-in-context': false,
+ 'post-require-hook': null,
+ 'handle-sigint': false
+ },
+ check: {
+ global: {
+ statements: 0,
+ lines: 0,
+ branches: 0,
+ functions: 0,
+ excludes: [] // Currently list of files (root + path). For future, extend to patterns.
+ },
+ each: {
+ statements: 0,
+ lines: 0,
+ branches: 0,
+ functions: 0,
+ excludes: []
+ }
+ }
+ };
+ ret.reporting.watermarks = defaults.watermarks();
+ ret.reporting['report-config'] = defaults.defaultReportConfig();
+
+ if (includeBackCompatAttrs) {
+ ret.instrumentation['preload-sources'] = false;
+ }
+
+ 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];
+ };
+ });
+}
+
+/**
+ * Object that returns instrumentation options
+ * @class InstrumentOptions
+ * @module config
+ * @constructor
+ * @param config the instrumentation part of the config object
+ */
+function InstrumentOptions(config) {
+ if (config['preload-sources']) {
+ console.error('The preload-sources option is deprecated, please use include-all-sources instead.');
+ config['include-all-sources'] = config['preload-sources'];
+ }
+ this.config = config;
+}
+
+/**
+ * returns if default excludes should be turned on. Used by the `cover` command.
+ * @method defaultExcludes
+ * @return {Boolean} true if default excludes should be turned on
+ */
+/**
+ * returns if non-JS files should be copied during instrumentation. Used by the
+ * `instrument` command.
+ * @method completeCopy
+ * @return {Boolean} true if non-JS files should be copied
+ */
+/**
+ * returns if the source should be embedded in the instrumented code. Used by the
+ * `instrument` command.
+ * @method embedSource
+ * @return {Boolean} true if the source should be embedded in the instrumented code
+ */
+/**
+ * the coverage variable name to use. Used by the `instrument` command.
+ * @method variable
+ * @return {String} the coverage variable name to use
+ */
+/**
+ * returns if the output should be compact JS. Used by the `instrument` command.
+ * @method compact
+ * @return {Boolean} true if the output should be compact
+ */
+/**
+ * returns if comments should be preserved in the generated JS. Used by the
+ * `cover` and `instrument` commands.
+ * @method preserveComments
+ * @return {Boolean} true if comments should be preserved in the generated JS
+ */
+/**
+ * returns if a zero-coverage baseline file should be written as part of
+ * instrumentation. This allows reporting to display numbers for files that have
+ * no tests. Used by the `instrument` command.
+ * @method saveBaseline
+ * @return {Boolean} true if a baseline coverage file should be written.
+ */
+/**
+ * Sets the baseline coverage filename. Used by the `instrument` command.
+ * @method baselineFile
+ * @return {String} the name of the baseline coverage file.
+ */
+/**
+ * returns if comments the JS to instrument contains es6 Module syntax.
+ * @method esModules
+ * @return {Boolean} true if code contains es6 import/export statements.
+ */
+/**
+ * returns if the coverage filename should include the PID. Used by the `instrument` command.
+ * @method includePid
+ * @return {Boolean} true to include pid in coverage filename.
+ */
+
+
+addMethods(InstrumentOptions,
+ 'extensions', 'defaultExcludes', 'completeCopy',
+ 'embedSource', 'variable', 'compact', 'preserveComments',
+ 'saveBaseline', 'baselineFile', 'esModules',
+ 'includeAllSources', 'includePid');
+
+/**
+ * returns the root directory used by istanbul which is typically the root of the
+ * source tree. Used by the `cover` and `report` commands.
+ * @method root
+ * @return {String} the root directory used by istanbul.
+ */
+InstrumentOptions.prototype.root = function () { return path.resolve(this.config.root); };
+/**
+ * returns an array of glob patterns that should be excluded for instrumentation.
+ * Used by the `instrument` and `cover` commands.
+ * @method excludes
+ * @return {Array} an array of glob patterns that should be excluded for
+ * instrumentation.
+ */
+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;
+};
+
+/**
+ * Object that returns reporting options
+ * @class ReportingOptions
+ * @module config
+ * @constructor
+ * @param config the reporting part of the config object
+ */
+function ReportingOptions(config) {
+ this.config = config;
+}
+
+/**
+ * returns the kind of information to be printed on the console. May be one
+ * of `summary`, `detail`, `both` or `none`. Used by the
+ * `cover` command.
+ * @method print
+ * @return {String} the kind of information to print to the console at the end
+ * of the `cover` command execution.
+ */
+/**
+ * returns a list of reports that should be generated at the end of a run. Used
+ * by the `cover` and `report` commands.
+ * @method reports
+ * @return {Array} an array of reports that should be produced
+ */
+/**
+ * returns the directory under which reports should be generated. Used by the
+ * `cover` and `report` commands.
+ *
+ * @method dir
+ * @return {String} the directory under which reports should be generated.
+ */
+/**
+ * returns an object that has keys that are report format names and values that are objects
+ * containing detailed configuration for each format. Running `istanbul help config`
+ * will give you all the keys per report format that can be overridden.
+ * Used by the `cover` and `report` commands.
+ * @method reportConfig
+ * @return {Object} detailed report configuration per report format.
+ */
+addMethods(ReportingOptions, 'print', 'reports', 'dir', 'reportConfig');
+
+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;
+}
+
+/**
+ * returns the low and high watermarks to be used to designate whether coverage
+ * is `low`, `medium` or `high`. Statements, functions, branches and lines can
+ * have independent watermarks. These are respected by all reports
+ * that color for low, medium and high coverage. See the default configuration for exact syntax
+ * using `istanbul help config`. Used by the `cover` and `report` commands.
+ *
+ * @method watermarks
+ * @return {Object} an object containing low and high watermarks for statements,
+ * branches, functions and lines.
+ */
+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;
+};
+
+/**
+ * Object that returns hook options. Note that istanbul does not provide an
+ * option to hook `require`. This is always done by the `cover` command.
+ * @class HookOptions
+ * @module config
+ * @constructor
+ * @param config the hooks part of the config object
+ */
+function HookOptions(config) {
+ this.config = config;
+}
+
+/**
+ * returns if `vm.runInThisContext` needs to be hooked, in addition to the standard
+ * `require` hooks added by istanbul. This should be true for code that uses
+ * RequireJS for example. Used by the `cover` command.
+ * @method hookRunInContext
+ * @return {Boolean} true if `vm.runInThisContext` needs to be hooked for coverage
+ */
+/**
+ * returns a path to JS file or a dependent module that should be used for
+ * post-processing files after they have been required. See the `yui-istanbul` module for
+ * an example of a post-require hook. This particular hook modifies the yui loader when
+ * that file is required to add istanbul interceptors. Use by the `cover` command
+ *
+ * @method postRequireHook
+ * @return {String} a path to a JS file or the name of a node module that needs
+ * to be used as a `require` post-processor
+ */
+/**
+ * returns if istanbul needs to add a SIGINT (control-c, usually) handler to
+ * save coverage information. Useful for getting code coverage out of processes
+ * that run forever and need a SIGINT to terminate.
+ * @method handleSigint
+ * @return {Boolean} true if SIGINT needs to be hooked to write coverage information
+ */
+
+addMethods(HookOptions, 'hookRunInContext', 'postRequireHook', 'handleSigint');
+
+/**
+ * represents the istanbul configuration and provides sub-objects that can
+ * return instrumentation, reporting and hook options respectively.
+ * Usage
+ * -----
+ *
+ * var configObj = require('istanbul').config.loadFile();
+ *
+ * console.log(configObj.reporting.reports());
+ *
+ * @class Configuration
+ * @module config
+ * @param {Object} obj the base object to use as the configuration
+ * @param {Object} overrides optional - override attributes that are merged into
+ * the base config
+ * @constructor
+ */
+function Configuration(obj, overrides) {
+
+ var config = mergeDefaults(obj, defaultConfig(true));
+ 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.check = config.check; // Pass raw config sub-object.
+}
+
+/**
+ * true if verbose logging is required
+ * @property verbose
+ * @type Boolean
+ */
+/**
+ * instrumentation options
+ * @property instrumentation
+ * @type InstrumentOptions
+ */
+/**
+ * reporting options
+ * @property reporting
+ * @type ReportingOptions
+ */
+/**
+ * hook options
+ * @property hooks
+ * @type HookOptions
+ */
+
+
+function loadFile(file, overrides) {
+ var defaultConfigFile = path.resolve('.istanbul.yml'),
+ configObject;
+
+ if (file) {
+ if (!existsSync(file)) {
+ throw new Error('Invalid configuration file specified:' + file);
+ }
+ } else {
+ if (existsSync(defaultConfigFile)) {
+ file = defaultConfigFile;
+ }
+ }
+
+ if (file) {
+ if (overrides && overrides.verbose === true) {
+ 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);
+}
+
+/**
+ * methods to load the configuration object.
+ * Usage
+ * -----
+ *
+ * var config = require('istanbul').config,
+ * configObj = config.loadFile();
+ *
+ * console.log(configObj.reporting.reports());
+ *
+ * @class Config
+ * @module main
+ * @static
+ */
+module.exports = {
+ /**
+ * loads the specified configuration file with optional overrides. Throws
+ * when a file is specified and it is not found.
+ * @method loadFile
+ * @static
+ * @param {String} file the file to load. If falsy, the default config file, if present, is loaded.
+ * If not a default config is used.
+ * @param {Object} overrides - an object with override keys that are merged into the
+ * config object loaded
+ * @return {Configuration} the config object with overrides applied
+ */
+ loadFile: loadFile,
+ /**
+ * loads the specified configuration object with optional overrides.
+ * @method loadObject
+ * @static
+ * @param {Object} obj the object to use as the base configuration.
+ * @param {Object} overrides - an object with override keys that are merged into the
+ * config object
+ * @return {Configuration} the config object with overrides applied
+ */
+ loadObject: loadObject,
+ /**
+ * returns the default configuration object. Note that this is a plain object
+ * and not a `Configuration` instance.
+ * @method defaultConfig
+ * @static
+ * @return {Object} an object that represents the default config
+ */
+ defaultConfig: defaultConfig
+};
diff --git a/node_modules/istanbul/lib/hook.js b/node_modules/istanbul/lib/hook.js
new file mode 100644
index 000000000..ce238268d
--- /dev/null
+++ b/node_modules/istanbul/lib/hook.js
@@ -0,0 +1,198 @@
+/*
+ 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
+ * @module main
+ */
+var path = require('path'),
+ fs = require('fs'),
+ Module = require('module'),
+ vm = require('vm'),
+ originalLoaders = {},
+ 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 extensions,
+ fn = transformFn(matcher, transformer, options.verbose),
+ postLoadHook = options.postLoadHook &&
+ typeof options.postLoadHook === 'function' ? options.postLoadHook : null;
+
+ extensions = options.extensions || ['.js'];
+
+ extensions.forEach(function(ext){
+ if (!(ext in originalLoaders)) {
+ originalLoaders[ext] = Module._extensions[ext] || Module._extensions['.js'];
+ }
+ Module._extensions[ext] = function (module, filename) {
+ var ret = fn(fs.readFileSync(filename, 'utf8'), filename);
+ if (ret.changed) {
+ module._compile(ret.code, filename);
+ } else {
+ originalLoaders[ext](module, filename);
+ }
+ if (postLoadHook) {
+ postLoadHook(filename);
+ }
+ };
+ });
+}
+/**
+ * unhook `require` to restore it to its original state.
+ * @method unhookRequire
+ * @static
+ */
+function unhookRequire() {
+ Object.keys(originalLoaders).forEach(function(ext) {
+ Module._extensions[ext] = originalLoaders[ext];
+ });
+}
+/**
+ * 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
+};
+
+
diff --git a/node_modules/istanbul/lib/instrumenter.js b/node_modules/istanbul/lib/instrumenter.js
new file mode 100644
index 000000000..61c02cf09
--- /dev/null
+++ b/node_modules/istanbul/lib/instrumenter.js
@@ -0,0 +1,1097 @@
+/*
+ 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 = {
+ // keep in sync with estraverse's VisitorKeys
+ AssignmentExpression: ['left', 'right'],
+ AssignmentPattern: ['left', 'right'],
+ ArrayExpression: ['elements'],
+ ArrayPattern: ['elements'],
+ ArrowFunctionExpression: ['params', 'body'],
+ AwaitExpression: ['argument'], // CAUTION: It's deferred to ES7.
+ BlockStatement: ['body'],
+ BinaryExpression: ['left', 'right'],
+ BreakStatement: ['label'],
+ CallExpression: ['callee', 'arguments'],
+ CatchClause: ['param', 'body'],
+ ClassBody: ['body'],
+ ClassDeclaration: ['id', 'superClass', 'body'],
+ ClassExpression: ['id', 'superClass', 'body'],
+ ComprehensionBlock: ['left', 'right'], // CAUTION: It's deferred to ES7.
+ ComprehensionExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
+ ConditionalExpression: ['test', 'consequent', 'alternate'],
+ ContinueStatement: ['label'],
+ DebuggerStatement: [],
+ DirectiveStatement: [],
+ DoWhileStatement: ['body', 'test'],
+ EmptyStatement: [],
+ ExportAllDeclaration: ['source'],
+ ExportDefaultDeclaration: ['declaration'],
+ ExportNamedDeclaration: ['declaration', 'specifiers', 'source'],
+ ExportSpecifier: ['exported', 'local'],
+ ExpressionStatement: ['expression'],
+ ForStatement: ['init', 'test', 'update', 'body'],
+ ForInStatement: ['left', 'right', 'body'],
+ ForOfStatement: ['left', 'right', 'body'],
+ FunctionDeclaration: ['id', 'params', 'body'],
+ FunctionExpression: ['id', 'params', 'body'],
+ GeneratorExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
+ Identifier: [],
+ IfStatement: ['test', 'consequent', 'alternate'],
+ ImportDeclaration: ['specifiers', 'source'],
+ ImportDefaultSpecifier: ['local'],
+ ImportNamespaceSpecifier: ['local'],
+ ImportSpecifier: ['imported', 'local'],
+ Literal: [],
+ LabeledStatement: ['label', 'body'],
+ LogicalExpression: ['left', 'right'],
+ MetaProperty: ['meta', 'property'],
+ MemberExpression: ['object', 'property'],
+ MethodDefinition: ['key', 'value'],
+ ModuleSpecifier: [],
+ NewExpression: ['callee', 'arguments'],
+ ObjectExpression: ['properties'],
+ ObjectPattern: ['properties'],
+ Program: ['body'],
+ Property: ['key', 'value'],
+ RestElement: [ 'argument' ],
+ ReturnStatement: ['argument'],
+ SequenceExpression: ['expressions'],
+ SpreadElement: ['argument'],
+ Super: [],
+ SwitchStatement: ['discriminant', 'cases'],
+ SwitchCase: ['test', 'consequent'],
+ TaggedTemplateExpression: ['tag', 'quasi'],
+ TemplateElement: [],
+ TemplateLiteral: ['quasis', 'expressions'],
+ ThisExpression: [],
+ ThrowStatement: ['argument'],
+ TryStatement: ['block', 'handler', 'finalizer'],
+ UnaryExpression: ['argument'],
+ UpdateExpression: ['argument'],
+ VariableDeclaration: ['declarations'],
+ VariableDeclarator: ['id', 'init'],
+ WhileStatement: ['test', 'body'],
+ WithStatement: ['object', 'body'],
+ YieldExpression: ['argument']
+ };
+
+ 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] }; },
+ returnStatement: function (expr) { return { type: SYNTAX.ReturnStatement.name, argument: expr }; }
+ };
+
+ 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],
+ // 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;
+
+ if (!SYNTAX[type]) {
+ console.error(node);
+ console.error('Unsupported node type:' + type);
+ return;
+ }
+ children = SYNTAX[type].children;
+ /* 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);
+ }
+ }
+
+ if (node.skipSelf) {
+ return;
+ }
+
+ 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;
+ }
+ } else {
+ assignNode = undefined;
+ }
+ 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.esModules] whether the code to instrument contains uses es
+ * imports or exports.
+ * @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,
+ esModules: false
+ };
+
+ if (this.opts.esModules && !this.opts.noAutoWrap) {
+ this.opts.noAutoWrap = true;
+ if (this.opts.debug) {
+ console.log('Setting noAutoWrap to true as required by esModules');
+ }
+ }
+
+ this.walker = new Walker({
+ ArrowFunctionExpression: [ this.arrowBlockConverter ],
+ ExpressionStatement: this.coverStatement,
+ ExportNamedDeclaration: this.coverExport,
+ BreakStatement: this.coverStatement,
+ ContinueStatement: this.coverStatement,
+ DebuggerStatement: this.coverStatement,
+ ReturnStatement: this.coverStatement,
+ ThrowStatement: this.coverStatement,
+ TryStatement: [ this.paranoidHandlerCheck, 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 ],
+ ForOfStatement: [ 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,
+ MetaProperty: this.coverMetaProperty,
+ }, 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;
+ }
+ try {
+ program = ESP.parse(code, {
+ loc: true,
+ range: true,
+ tokens: this.opts.preserveComments,
+ comment: true,
+ sourceType: this.opts.esModules ? 'module' : 'script'
+ });
+ } catch (e) {
+ console.log('Failed to parse file: ' + filename);
+ throw e;
+ }
+ 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._array.length; i += 1) {
+ generated.map._mappings._array[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).
+ * @method lastFileCoverage
+ * @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.
+ * @method lastSourceMap
+ * @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 ] };
+ }
+ },
+
+ arrowBlockConverter: function (node) {
+ var retStatement;
+ if (node.expression) { // turn expression nodes into a block with a return statement
+ retStatement = astgen.returnStatement(node.body);
+ // ensure the generated return statement is covered
+ retStatement.loc = node.body.loc;
+ node.body = this.convertToBlock(retStatement);
+ node.expression = false;
+ }
+ },
+
+ paranoidHandlerCheck: function (node) {
+ // if someone is using an older esprima on the browser
+ // convert handlers array to single handler attribute
+ // containing its first element
+ /* istanbul ignore next */
+ if (!node.handler && node.handlers) {
+ node.handler = node.handlers[0];
+ }
+ },
+
+ 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;
+ },
+
+ coverMetaProperty: function(node /* , walker */) {
+ node.skipSelf = true;
+ },
+
+ coverStatement: function (node, walker) {
+ var sName,
+ incrStatementCount,
+ parent,
+ 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) {
+ // Called for the side-effect of setting the function's statement count to 1.
+ this.statementName(node.loc, 1);
+ } else {
+ // We let `coverExport` handle ExportNamedDeclarations.
+ parent = walker.parent();
+ if (parent && parent.node.type === SYNTAX.ExportNamedDeclaration.name) {
+ return;
+ }
+
+ 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);
+ }
+ },
+
+ coverExport: function (node, walker) {
+ var sName, incrStatementCount;
+
+ if ( !node.declaration || !node.declaration.declarations ) { return; }
+
+ this.maybeSkipNode(node, 'next');
+
+ sName = this.statementName(node.declaration.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 + ')',
+ clone = function (attr) {
+ var obj = location[attr] || /* istanbul ignore next */ {};
+ return { line: obj.line, column: obj.column };
+ };
+ this.coverState.fnMap[id] = {
+ name: name, line: line,
+ loc: {
+ start: clone('start'),
+ end: clone('end')
+ },
+ 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,
+ makeLoc = function () { return { line: line, column: col }; },
+ bName = this.branchName('if', walker.startLineForNode(node), [
+ { start: makeLoc(), end: makeLoc(), skip: ignoreThen || undefined },
+ { start: makeLoc(), end: makeLoc(), 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'));
diff --git a/node_modules/istanbul/lib/object-utils.js b/node_modules/istanbul/lib/object-utils.js
new file mode 100644
index 000000000..ab88ae0b2
--- /dev/null
+++ b/node_modules/istanbul/lib/object-utils.js
@@ -0,0 +1,425 @@
+/*
+ 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
+ * @module main
+ * @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
+ * linesCovered: lineCoveredCount
+ * }
+ *
+ * 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'
+ },
+ linesCovered: {}
+ };
+ }
+ /**
+ * 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);
+ ret.linesCovered = fileCoverage.l;
+ 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;
+ });
+
+ // keep track of all lines we have coverage for.
+ Object.keys(obj.linesCovered).forEach(function (key) {
+ if (!ret.linesCovered[key]) {
+ ret.linesCovered[key] = obj.linesCovered[key];
+ } else {
+ ret.linesCovered[key] += obj.linesCovered[key];
+ }
+ });
+ }
+ };
+ 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;
+ }
+
+ /**
+ * Creates new file coverage object with incremented hits count
+ * on skipped statements, branches and functions
+ *
+ * @method incrementIgnoredTotals
+ * @static
+ * @param {Object} cov File coverage object
+ * @return {Object} New file coverage object
+ */
+ function incrementIgnoredTotals(cov) {
+ //TODO: This may be slow in the browser and may break in older browsers
+ // Look into using a library that works in Node and the browser
+ var fileCoverage = JSON.parse(JSON.stringify(cov));
+
+ [
+ {mapKey: 'statementMap', hitsKey: 's'},
+ {mapKey: 'branchMap', hitsKey: 'b'},
+ {mapKey: 'fnMap', hitsKey: 'f'}
+ ].forEach(function (keys) {
+ Object.keys(fileCoverage[keys.mapKey])
+ .forEach(function (key) {
+ var map = fileCoverage[keys.mapKey][key];
+ var hits = fileCoverage[keys.hitsKey];
+
+ if (keys.mapKey === 'branchMap') {
+ var locations = map.locations;
+
+ locations.forEach(function (location, index) {
+ if (hits[key][index] === 0 && location.skip) {
+ hits[key][index] = 1;
+ }
+ });
+
+ return;
+ }
+
+ if (hits[key] === 0 && map.skip) {
+ hits[key] = 1;
+ }
+ });
+ });
+
+ return fileCoverage;
+ }
+
+ var exportables = {
+ addDerivedInfo: addDerivedInfo,
+ addDerivedInfoForFile: addDerivedInfoForFile,
+ removeDerivedInfo: removeDerivedInfo,
+ blankSummary: blankSummary,
+ summarizeFileCoverage: summarizeFileCoverage,
+ summarizeCoverage: summarizeCoverage,
+ mergeFileCoverage: mergeFileCoverage,
+ mergeSummaryObjects: mergeSummaryObjects,
+ toYUICoverage: toYUICoverage,
+ incrementIgnoredTotals: incrementIgnoredTotals
+ };
+
+ /* istanbul ignore else: windows */
+ if (isNode) {
+ module.exports = exportables;
+ } else {
+ window.coverageUtils = exportables;
+ }
+}(typeof module !== 'undefined' && typeof module.exports !== 'undefined' && typeof exports !== 'undefined'));
diff --git a/node_modules/istanbul/lib/register-plugins.js b/node_modules/istanbul/lib/register-plugins.js
new file mode 100644
index 000000000..50462bec1
--- /dev/null
+++ b/node_modules/istanbul/lib/register-plugins.js
@@ -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();
+
+
+
diff --git a/node_modules/istanbul/lib/report/clover.js b/node_modules/istanbul/lib/report/clover.js
new file mode 100644
index 000000000..fe73aefcd
--- /dev/null
+++ b/node_modules/istanbul/lib/report/clover.js
@@ -0,0 +1,227 @@
+var path = require('path'),
+ util = require('util'),
+ Report = require('./index'),
+ FileWriter = require('../util/file-writer'),
+ TreeSummarizer = require('../util/tree-summarizer'),
+ utils = require('../object-utils');
+
+/**
+ * a `Report` implementation that produces a clover-style XML file.
+ *
+ * Usage
+ * -----
+ *
+ * var report = require('istanbul').Report.create('clover');
+ *
+ * @class CloverReport
+ * @module report
+ * @extends Report
+ * @constructor
+ * @param {Object} opts optional
+ * @param {String} [opts.dir] the directory in which to the clover.xml will be written
+ * @param {String} [opts.file] the file name, defaulted to config attribute or 'clover.xml'
+ */
+function CloverReport(opts) {
+ Report.call(this);
+ opts = opts || {};
+ this.projectRoot = process.cwd();
+ this.dir = opts.dir || this.projectRoot;
+ this.file = opts.file || this.getDefaultConfig().file;
+ this.opts = opts;
+}
+
+CloverReport.TYPE = 'clover';
+util.inherits(CloverReport, Report);
+
+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) {
+ fileCoverage = utils.incrementIgnoredTotals(fileCoverage);
+
+ 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, {
+ synopsis: function () {
+ return 'XML coverage report that can be consumed by the clover tool';
+ },
+ getDefaultConfig: function () {
+ return { file: 'clover.xml' };
+ },
+ 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,
+ that = this,
+ tree,
+ root;
+
+ collector.files().forEach(function (key) {
+ summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
+ });
+ tree = summarizer.getTreeSummary();
+ root = tree.root;
+ writer.on('done', function () { that.emit('done'); });
+ writer.writeFile(outputFile, function (contentWriter) {
+ walk(root, collector, contentWriter, 0, projectRoot);
+ writer.done();
+ });
+ }
+});
+
+module.exports = CloverReport;
diff --git a/node_modules/istanbul/lib/report/cobertura.js b/node_modules/istanbul/lib/report/cobertura.js
new file mode 100644
index 000000000..d63456c95
--- /dev/null
+++ b/node_modules/istanbul/lib/report/cobertura.js
@@ -0,0 +1,221 @@
+/*
+ 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'),
+ 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
+ * @module report
+ * @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 || this.getDefaultConfig().file;
+ this.opts = opts;
+}
+
+CoberturaReport.TYPE = 'cobertura';
+util.inherits(CoberturaReport, Report);
+
+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) {
+ fileCoverage = utils.incrementIgnoredTotals(fileCoverage);
+
+ 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, {
+ synopsis: function () {
+ return 'XML coverage report that can be consumed by the cobertura tool';
+ },
+ getDefaultConfig: function () {
+ return { file: 'cobertura-coverage.xml' };
+ },
+ 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,
+ that = this,
+ tree,
+ root;
+
+ collector.files().forEach(function (key) {
+ summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
+ });
+ tree = summarizer.getTreeSummary();
+ root = tree.root;
+ writer.on('done', function () { that.emit('done'); });
+ writer.writeFile(outputFile, function (contentWriter) {
+ walk(root, collector, contentWriter, 0, projectRoot);
+ writer.done();
+ });
+ }
+});
+
+module.exports = CoberturaReport;
diff --git a/node_modules/istanbul/lib/report/common/defaults.js b/node_modules/istanbul/lib/report/common/defaults.js
new file mode 100644
index 000000000..a9851c32f
--- /dev/null
+++ b/node_modules/istanbul/lib/report/common/defaults.js
@@ -0,0 +1,51 @@
+/*
+ Copyright (c) 2013, Yahoo! Inc. All rights reserved.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+
+var Report = require('../index');
+var supportsColor = require('supports-color');
+
+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 */
+ var colors = {
+ low: '31;1',
+ medium: '33;1',
+ high: '32;1'
+ };
+
+ if (supportsColor && colors[clazz]) {
+ return '\u001b[' + colors[clazz] + 'm' + str + '\u001b[0m';
+ }
+ return str;
+ },
+
+ defaultReportConfig: function () {
+ var cfg = {};
+ Report.getReportList().forEach(function (type) {
+ var rpt = Report.create(type),
+ c = rpt.getDefaultConfig();
+ if (c) {
+ cfg[type] = c;
+ }
+ });
+ return cfg;
+ }
+};
+
diff --git a/node_modules/istanbul/lib/report/html.js b/node_modules/istanbul/lib/report/html.js
new file mode 100644
index 000000000..1dab26d56
--- /dev/null
+++ b/node_modules/istanbul/lib/report/html.js
@@ -0,0 +1,572 @@
+/*
+ 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').create(),
+ defaults = require('./common/defaults'),
+ path = require('path'),
+ 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'),
+ detailTemplate = handlebars.compile([
+ '<tr>',
+ '<td class="line-count quiet">{{#show_lines}}{{maxLines}}{{/show_lines}}</td>',
+ '<td class="line-coverage quiet">{{#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="pad1">',
+ '<table class="coverage-summary">',
+ '<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}}"><div class="chart">{{#show_picture}}{{metrics.statements.pct}}{{/show_picture}}</div></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}}/{{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}}/{{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}}/{{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}}/{{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 '<div class="cover-fill' + cls + '" style="width: ' + num + '%;"></div>' +
+ '<div class="cover-empty" style="width:' + rest + '%;"></div>';
+ } else {
+ return '';
+ }
+});
+
+handlebars.registerHelper('if_has_ignores', function (metrics, opts) {
+ return (metrics.statements.skipped +
+ metrics.functions.skipped +
+ metrics.branches.skipped) === 0 ? '' : opts.fn(this);
+});
+
+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];
+ if (structuredText[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 '';
+ }
+}
+
+function cleanPath(name) {
+ var SEP = path.sep || '/';
+ return (SEP !== '/') ? name.split(SEP).join('/') : name;
+}
+
+function isEmptySourceStore(sourceStore) {
+ if (!sourceStore) {
+ return true;
+ }
+
+ var cache = sourceStore.sourceCache;
+ return cache && !Object.keys(cache).length;
+}
+
+/**
+ * a `Report` implementation that produces HTML coverage reports.
+ *
+ * Usage
+ * -----
+ *
+ * var report = require('istanbul').Report.create('html');
+ *
+ *
+ * @class HtmlReport
+ * @extends Report
+ * @module 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 = isEmptySourceStore(this.opts.sourceStore) ?
+ Store.create('fslookup') : this.opts.sourceStore;
+ 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, {
+
+ synopsis: function () {
+ return 'Navigable HTML coverage report for every file and directory';
+ },
+
+ 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) + '">' +
+ (cleanPath(nodePath[i].relativeName) || 'all files') + '</a>');
+ }
+ linkPath.reverse();
+ return linkPath.length > 0 ? linkPath.join(' / ') + ' ' +
+ cleanPath(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 = this.getPathHtml(node, linkMapper);
+ templateData.base = {
+ css: linkMapper.asset(node, 'base.css')
+ };
+ templateData.sorter = {
+ js: linkMapper.asset(node, 'sorter.js'),
+ image: linkMapper.asset(node, 'sort-arrow-sprite.png')
+ };
+ 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: cleanPath(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 relativeName = cleanPath(node.relativeName);
+
+ return node.kind === 'dir' ? relativeName + 'index.html' : relativeName + '.html';
+ },
+ ancestorHref: function (node, num) {
+ var href = '',
+ notDot = function(part) {
+ return part !== '.';
+ },
+ separated,
+ levels,
+ i,
+ j;
+
+ for (i = 0; i < num; i += 1) {
+ separated = cleanPath(node.relativeName).split('/').filter(notDot);
+ 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),
+ that = this,
+ tree,
+ copyAssets = function (subdir) {
+ var srcDir = path.resolve(__dirname, '..', 'assets', subdir);
+ fs.readdirSync(srcDir).forEach(function (f) {
+ var resolvedSource = path.resolve(srcDir, 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);
+ }
+ });
+ };
+
+ collector.files().forEach(function (key) {
+ summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
+ });
+ tree = summarizer.getTreeSummary();
+ [ '.', 'vendor'].forEach(function (subdir) {
+ copyAssets(subdir);
+ });
+ writer.on('done', function () { that.emit('done'); });
+ //console.log(JSON.stringify(tree.root, undefined, 4));
+ this.writeFiles(writer, tree.root, dir, collector);
+ writer.done();
+ }
+});
+
+module.exports = HtmlReport;
+
diff --git a/node_modules/istanbul/lib/report/index.js b/node_modules/istanbul/lib/report/index.js
new file mode 100644
index 000000000..13e7effbe
--- /dev/null
+++ b/node_modules/istanbul/lib/report/index.js
@@ -0,0 +1,104 @@
+/*
+ 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,
+ Factory = require('../util/factory'),
+ factory = new Factory('report', __dirname, false);
+/**
+ * An abstraction for producing coverage reports.
+ * This class is both the base class as well as a factory for `Report` implementations.
+ * All reports are event emitters and are expected to emit a `done` event when
+ * the report writing is complete.
+ *
+ * See also the `Reporter` class for easily producing multiple coverage reports
+ * with a single call.
+ *
+ * Usage
+ * -----
+ *
+ * var Report = require('istanbul').Report,
+ * report = Report.create('html'),
+ * collector = new require('istanbul').Collector;
+ *
+ * collector.add(coverageObject);
+ * report.on('done', function () { console.log('done'); });
+ * report.writeReport(collector);
+ *
+ * @class Report
+ * @module report
+ * @main report
+ * @constructor
+ * @protected
+ * @param {Object} options Optional. The options supported by a specific store implementation.
+ */
+function Report(/* options */) {
+ EventEmitter.call(this);
+}
+
+util.inherits(Report, EventEmitter);
+
+//add register, create, mix, loadAll, getReportList 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
+ */
+/**
+ * returns the list of available reports as an array of strings
+ * @method getReportList
+ * @static
+ * @return an array of supported report formats
+ */
+
+var proto = {
+ /**
+ * returns a one-line summary of the report
+ * @method synopsis
+ * @return {String} a description of what the report is about
+ */
+ synopsis: function () {
+ throw new Error('synopsis must be overridden');
+ },
+ /**
+ * returns a config object that has override-able keys settable via config
+ * @method getDefaultConfig
+ * @return {Object|null} an object representing keys that can be overridden via
+ * the istanbul configuration where the values are the defaults used when
+ * not specified. A null return implies no config attributes
+ */
+ getDefaultConfig: function () {
+ return null;
+ },
+ /**
+ * 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');
+ }
+};
+
+Object.keys(proto).forEach(function (k) {
+ Report.prototype[k] = proto[k];
+});
+
+module.exports = Report;
+
+
diff --git a/node_modules/istanbul/lib/report/json-summary.js b/node_modules/istanbul/lib/report/json-summary.js
new file mode 100644
index 000000000..6ab7caae9
--- /dev/null
+++ b/node_modules/istanbul/lib/report/json-summary.js
@@ -0,0 +1,75 @@
+/*
+ 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'),
+ objectUtils = require('../object-utils'),
+ Writer = require('../util/file-writer'),
+ util = require('util'),
+ Report = require('./index');
+/**
+ * a `Report` implementation that produces a coverage JSON object with summary info only.
+ *
+ * Usage
+ * -----
+ *
+ * var report = require('istanbul').Report.create('json-summary');
+ *
+ *
+ * @class JsonSummaryReport
+ * @extends Report
+ * @module report
+ * @constructor
+ * @param {Object} opts optional
+ * @param {String} [opts.dir] the directory in which to write the `coverage-summary.json` file. Defaults to `process.cwd()`
+ */
+function JsonSummaryReport(opts) {
+ this.opts = opts || {};
+ this.opts.dir = this.opts.dir || process.cwd();
+ this.opts.file = this.opts.file || this.getDefaultConfig().file;
+ this.opts.writer = this.opts.writer || null;
+}
+JsonSummaryReport.TYPE = 'json-summary';
+util.inherits(JsonSummaryReport, Report);
+
+Report.mix(JsonSummaryReport, {
+ synopsis: function () {
+ return 'prints a summary coverage object as JSON to a file';
+ },
+ getDefaultConfig: function () {
+ return {
+ file: 'coverage-summary.json'
+ };
+ },
+ writeReport: function (collector, sync) {
+ var outputFile = path.resolve(this.opts.dir, this.opts.file),
+ writer = this.opts.writer || new Writer(sync),
+ that = this;
+
+ var summaries = [],
+ finalSummary;
+ collector.files().forEach(function (file) {
+ summaries.push(objectUtils.summarizeFileCoverage(collector.fileCoverageFor(file)));
+ });
+ finalSummary = objectUtils.mergeSummaryObjects.apply(null, summaries);
+
+ writer.on('done', function () { that.emit('done'); });
+ writer.writeFile(outputFile, function (contentWriter) {
+ contentWriter.println("{");
+ contentWriter.write('"total":');
+ contentWriter.write(JSON.stringify(finalSummary));
+
+ collector.files().forEach(function (key) {
+ contentWriter.println(",");
+ contentWriter.write(JSON.stringify(key));
+ contentWriter.write(":");
+ contentWriter.write(JSON.stringify(objectUtils.summarizeFileCoverage(collector.fileCoverageFor(key))));
+ });
+ contentWriter.println("}");
+ });
+ writer.done();
+ }
+});
+
+module.exports = JsonSummaryReport;
diff --git a/node_modules/istanbul/lib/report/json.js b/node_modules/istanbul/lib/report/json.js
new file mode 100644
index 000000000..2def51ac0
--- /dev/null
+++ b/node_modules/istanbul/lib/report/json.js
@@ -0,0 +1,69 @@
+/*
+ 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
+ * @module 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.file = this.opts.file || this.getDefaultConfig().file;
+ this.opts.writer = this.opts.writer || null;
+}
+JsonReport.TYPE = 'json';
+util.inherits(JsonReport, Report);
+
+Report.mix(JsonReport, {
+ synopsis: function () {
+ return 'prints the coverage object as JSON to a file';
+ },
+ getDefaultConfig: function () {
+ return {
+ file: 'coverage-final.json'
+ };
+ },
+ writeReport: function (collector, sync) {
+ var outputFile = path.resolve(this.opts.dir, this.opts.file),
+ writer = this.opts.writer || new Writer(sync),
+ that = this;
+
+ writer.on('done', function () { that.emit('done'); });
+ 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("}");
+ });
+ writer.done();
+ }
+});
+
+module.exports = JsonReport;
diff --git a/node_modules/istanbul/lib/report/lcov.js b/node_modules/istanbul/lib/report/lcov.js
new file mode 100644
index 000000000..87e01eaab
--- /dev/null
+++ b/node_modules/istanbul/lib/report/lcov.js
@@ -0,0 +1,65 @@
+/*
+ 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'),
+ 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
+ * @module 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';
+util.inherits(LcovReport, Report);
+
+Report.mix(LcovReport, {
+ synopsis: function () {
+ return 'combined lcovonly and html report that generates an lcov.info file as well as HTML';
+ },
+ writeReport: function (collector, sync) {
+ var handler = this.handleDone.bind(this);
+ this.inProgress = 2;
+ this.lcov.on('done', handler);
+ this.html.on('done', handler);
+ this.lcov.writeReport(collector, sync);
+ this.html.writeReport(collector, sync);
+ },
+ handleDone: function () {
+ this.inProgress -= 1;
+ if (this.inProgress === 0) {
+ this.emit('done');
+ }
+ }
+});
+
+module.exports = LcovReport;
diff --git a/node_modules/istanbul/lib/report/lcovonly.js b/node_modules/istanbul/lib/report/lcovonly.js
new file mode 100644
index 000000000..2c1be46d8
--- /dev/null
+++ b/node_modules/istanbul/lib/report/lcovonly.js
@@ -0,0 +1,103 @@
+/*
+ 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
+ * @module 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.file = this.opts.file || this.getDefaultConfig().file;
+ this.opts.writer = this.opts.writer || null;
+}
+LcovOnlyReport.TYPE = 'lcovonly';
+util.inherits(LcovOnlyReport, Report);
+
+Report.mix(LcovOnlyReport, {
+ synopsis: function () {
+ return 'lcov coverage report that can be consumed by the lcov tool';
+ },
+ getDefaultConfig: function () {
+ return { file: 'lcov.info' };
+ },
+ 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, this.opts.file),
+ writer = this.opts.writer || new Writer(sync),
+ that = this;
+ writer.on('done', function () { that.emit('done'); });
+ writer.writeFile(outputFile, function (contentWriter) {
+ collector.files().forEach(function (key) {
+ that.writeFileCoverage(contentWriter, collector.fileCoverageFor(key));
+ });
+ });
+ writer.done();
+ }
+});
+
+module.exports = LcovOnlyReport;
diff --git a/node_modules/istanbul/lib/report/none.js b/node_modules/istanbul/lib/report/none.js
new file mode 100644
index 000000000..0fd5cfca6
--- /dev/null
+++ b/node_modules/istanbul/lib/report/none.js
@@ -0,0 +1,41 @@
+/*
+ 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'),
+ Report = require('./index');
+
+/**
+ * a `Report` implementation that does nothing. Use to specify that no reporting
+ * is needed.
+ *
+ * Usage
+ * -----
+ *
+ * var report = require('istanbul').Report.create('none');
+ *
+ *
+ * @class NoneReport
+ * @extends Report
+ * @module report
+ * @constructor
+ */
+function NoneReport() {
+ Report.call(this);
+}
+
+NoneReport.TYPE = 'none';
+util.inherits(NoneReport, Report);
+
+Report.mix(NoneReport, {
+ synopsis: function () {
+ return 'Does nothing. Useful to override default behavior and suppress reporting entirely';
+ },
+ writeReport: function (/* collector, sync */) {
+ //noop
+ this.emit('done');
+ }
+});
+
+module.exports = NoneReport;
diff --git a/node_modules/istanbul/lib/report/teamcity.js b/node_modules/istanbul/lib/report/teamcity.js
new file mode 100644
index 000000000..f6b90fc95
--- /dev/null
+++ b/node_modules/istanbul/lib/report/teamcity.js
@@ -0,0 +1,92 @@
+/*
+ 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'),
+ 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
+ * @module 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;
+ this.blockName = opts.blockName || this.getDefaultConfig().blockName;
+}
+
+TeamcityReport.TYPE = 'teamcity';
+util.inherits(TeamcityReport, Report);
+
+function lineForKey(value, teamcityVar) {
+ return '##teamcity[buildStatisticValue key=\'' + teamcityVar + '\' value=\'' + value + '\']';
+}
+
+Report.mix(TeamcityReport, {
+ synopsis: function () {
+ return 'report with system messages that can be interpreted with TeamCity';
+ },
+ getDefaultConfig: function () {
+ return { file: null , blockName: 'Code Coverage Summary'};
+ },
+ 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=\''+ this.blockName +'\']');
+
+ //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=\''+ this.blockName +'\']');
+
+ 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);
+ }
+ this.emit('done');
+ }
+});
+
+module.exports = TeamcityReport;
diff --git a/node_modules/istanbul/lib/report/templates/foot.txt b/node_modules/istanbul/lib/report/templates/foot.txt
new file mode 100644
index 000000000..e853251dd
--- /dev/null
+++ b/node_modules/istanbul/lib/report/templates/foot.txt
@@ -0,0 +1,20 @@
+<div class='push'></div><!-- for sticky footer -->
+</div><!-- /wrapper -->
+<div class='footer quiet pad2 space-top1 center small'>
+ Code coverage
+ generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at {{datetime}}
+</div>
+</div>
+{{#if prettify}}
+<script src="{{prettify.js}}"></script>
+<script>
+window.onload = function () {
+ if (typeof prettyPrint === 'function') {
+ prettyPrint();
+ }
+};
+</script>
+{{/if}}
+<script src="{{sorter.js}}"></script>
+</body>
+</html>
diff --git a/node_modules/istanbul/lib/report/templates/head.txt b/node_modules/istanbul/lib/report/templates/head.txt
new file mode 100644
index 000000000..f98094e5f
--- /dev/null
+++ b/node_modules/istanbul/lib/report/templates/head.txt
@@ -0,0 +1,60 @@
+<!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}}
+ <link rel="stylesheet" href="{{base.css}}" />
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <style type='text/css'>
+ .coverage-summary .sorter {
+ background-image: url({{sorter.image}});
+ }
+ </style>
+</head>
+<body>
+<div class='wrapper'>
+ <div class='pad1'>
+ <h1>
+ {{{pathHtml}}}
+ </h1>
+ <div class='clearfix'>
+ {{#with metrics.statements}}
+ <div class='fl pad1y space-right2'>
+ <span class="strong">{{pct}}% </span>
+ <span class="quiet">Statements</span>
+ <span class='fraction'>{{covered}}/{{total}}</span>
+ </div>
+ {{/with}}
+ {{#with metrics.branches}}
+ <div class='fl pad1y space-right2'>
+ <span class="strong">{{pct}}% </span>
+ <span class="quiet">Branches</span>
+ <span class='fraction'>{{covered}}/{{total}}</span>
+ </div>
+ {{/with}}
+ {{#with metrics.functions}}
+ <div class='fl pad1y space-right2'>
+ <span class="strong">{{pct}}% </span>
+ <span class="quiet">Functions</span>
+ <span class='fraction'>{{covered}}/{{total}}</span>
+ </div>
+ {{/with}}
+ {{#with metrics.lines}}
+ <div class='fl pad1y space-right2'>
+ <span class="strong">{{pct}}% </span>
+ <span class="quiet">Lines</span>
+ <span class='fraction'>{{covered}}/{{total}}</span>
+ </div>
+ {{/with}}
+ {{#if_has_ignores metrics}}
+ <div class='fl pad1y'>
+ <span class="strong">{{#show_ignores metrics}}{{/show_ignores}}</span>
+ <span class="quiet">Ignored</span> &nbsp;&nbsp;&nbsp;&nbsp;
+ </div>
+ {{/if_has_ignores}}
+ </div>
+ </div>
+ <div class='status-line {{reportClass}}'></div>
diff --git a/node_modules/istanbul/lib/report/text-lcov.js b/node_modules/istanbul/lib/report/text-lcov.js
new file mode 100644
index 000000000..15e1a48ca
--- /dev/null
+++ b/node_modules/istanbul/lib/report/text-lcov.js
@@ -0,0 +1,50 @@
+var LcovOnly = require('./lcovonly'),
+ util = require('util');
+
+/**
+ * a `Report` implementation that produces an LCOV coverage and prints it
+ * to standard out.
+ *
+ * Usage
+ * -----
+ *
+ * var report = require('istanbul').Report.create('text-lcov');
+ *
+ * @class TextLcov
+ * @module report
+ * @extends LcovOnly
+ * @constructor
+ * @param {Object} opts optional
+ * @param {String} [opts.log] the method used to log to console.
+ */
+function TextLcov(opts) {
+ var that = this;
+
+ LcovOnly.call(this);
+
+ this.opts = opts || {};
+ this.opts.log = this.opts.log || console.log;
+ this.opts.writer = {
+ println: function (ln) {
+ that.opts.log(ln);
+ }
+ };
+}
+
+TextLcov.TYPE = 'text-lcov';
+util.inherits(TextLcov, LcovOnly);
+
+LcovOnly.super_.mix(TextLcov, {
+ writeReport: function (collector) {
+ var that = this,
+ writer = this.opts.writer;
+
+ collector.files().forEach(function (key) {
+ that.writeFileCoverage(writer, collector.fileCoverageFor(key));
+ });
+
+ this.emit('done');
+ }
+});
+
+module.exports = TextLcov;
diff --git a/node_modules/istanbul/lib/report/text-summary.js b/node_modules/istanbul/lib/report/text-summary.js
new file mode 100644
index 000000000..9537cbe20
--- /dev/null
+++ b/node_modules/istanbul/lib/report/text-summary.js
@@ -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'),
+ util = require('util'),
+ 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
+ * @module 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';
+util.inherits(TextSummaryReport, Report);
+
+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, {
+ synopsis: function () {
+ return 'text report that prints a coverage summary across all files, typically to console';
+ },
+ getDefaultConfig: function () {
+ return { file: null };
+ },
+ 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);
+ }
+ this.emit('done');
+ }
+});
+
+module.exports = TextSummaryReport;
diff --git a/node_modules/istanbul/lib/report/text.js b/node_modules/istanbul/lib/report/text.js
new file mode 100644
index 000000000..8ab2b7d13
--- /dev/null
+++ b/node_modules/istanbul/lib/report/text.js
@@ -0,0 +1,234 @@
+/*
+ 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'),
+ util = require('util'),
+ fs = require('fs'),
+ defaults = require('./common/defaults'),
+ Report = require('./index'),
+ TreeSummarizer = require('../util/tree-summarizer'),
+ utils = require('../object-utils'),
+ PCT_COLS = 9,
+ MISSING_COL = 15,
+ TAB_SIZE = 1,
+ 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
+ * @module 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';
+util.inherits(TextReport, Report);
+
+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, width) {
+ return fill(pct, width || 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('% Branch'));
+ elements.push(formatPct('% Funcs'));
+ elements.push(formatPct('% Lines'));
+ elements.push(formatPct('Uncovered Lines', undefined, MISSING_COL));
+ return elements.join(' |') + ' |';
+}
+
+function collectMissingLines(kind, linesCovered) {
+ var missingLines = [];
+
+ if (kind !== 'file') {
+ return [];
+ }
+
+ Object.keys(linesCovered).forEach(function (key) {
+ if (!linesCovered[key]) {
+ missingLines.push(key);
+ }
+ });
+
+ return missingLines;
+}
+
+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,
+ missingLines = collectMissingLines(node.kind, node.metrics.linesCovered),
+ 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)));
+ elements.push(formatPct(missingLines.join(','), 'low', MISSING_COL));
+
+ 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);
+ elements.push(padding(MISSING_COL, '-'));
+ 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, {
+ synopsis: function () {
+ return 'text report that prints a coverage line for every file, typically to console';
+ },
+ getDefaultConfig: function () {
+ return { file: null, maxCols: 0 };
+ },
+ writeReport: function (collector /*, sync */) {
+ var summarizer = new TreeSummarizer(),
+ tree,
+ root,
+ nameWidth,
+ statsWidth = 4 * (PCT_COLS + 2) + MISSING_COL,
+ 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);
+ }
+ this.emit('done');
+ }
+});
+
+module.exports = TextReport;
diff --git a/node_modules/istanbul/lib/reporter.js b/node_modules/istanbul/lib/reporter.js
new file mode 100644
index 000000000..c7000d5b9
--- /dev/null
+++ b/node_modules/istanbul/lib/reporter.js
@@ -0,0 +1,111 @@
+/*
+ Copyright (c) 2014, Yahoo! Inc. All rights reserved.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+var Report = require('./report'),
+ configuration = require('./config'),
+ inputError = require('./util/input-error');
+
+/**
+ * convenience mechanism to write one or more reports ensuring that config
+ * options are respected.
+ * Usage
+ * -----
+ *
+ * var fs = require('fs'),
+ * reporter = new require('istanbul').Reporter(),
+ * collector = new require('istanbul').Collector(),
+ * sync = true;
+ *
+ * collector.add(JSON.parse(fs.readFileSync('coverage.json', 'utf8')));
+ * reporter.add('lcovonly');
+ * reporter.addAll(['clover', 'cobertura']);
+ * reporter.write(collector, sync, function () { console.log('done'); });
+ *
+ * @class Reporter
+ * @param {Configuration} cfg the config object, a falsy value will load the
+ * default configuration instead
+ * @param {String} dir the directory in which to write the reports, may be falsy
+ * to use config or global defaults
+ * @constructor
+ * @module main
+ */
+function Reporter(cfg, dir) {
+ this.config = cfg || configuration.loadFile();
+ this.dir = dir || this.config.reporting.dir();
+ this.reports = {};
+}
+
+Reporter.prototype = {
+ /**
+ * adds a report to be generated. Must be one of the entries returned
+ * by `Report.getReportList()`
+ * @method add
+ * @param {String} fmt the format of the report to generate
+ */
+ add: function (fmt) {
+ if (this.reports[fmt]) { // already added
+ return;
+ }
+ var config = this.config,
+ rptConfig = config.reporting.reportConfig()[fmt] || {};
+ rptConfig.verbose = config.verbose;
+ rptConfig.dir = this.dir;
+ rptConfig.watermarks = config.reporting.watermarks();
+ try {
+ this.reports[fmt] = Report.create(fmt, rptConfig);
+ } catch (ex) {
+ throw inputError.create('Invalid report format [' + fmt + ']');
+ }
+ },
+ /**
+ * adds an array of report formats to be generated
+ * @method addAll
+ * @param {Array} fmts an array of report formats
+ */
+ addAll: function (fmts) {
+ var that = this;
+ fmts.forEach(function (f) {
+ that.add(f);
+ });
+ },
+ /**
+ * writes all reports added and calls the callback when done
+ * @method write
+ * @param {Collector} collector the collector having the coverage data
+ * @param {Boolean} sync true to write reports synchronously
+ * @param {Function} callback the callback to call when done. When `sync`
+ * is true, the callback will be called in the same process tick.
+ */
+ write: function (collector, sync, callback) {
+ var reports = this.reports,
+ verbose = this.config.verbose,
+ handler = this.handleDone.bind(this, callback);
+
+ this.inProgress = Object.keys(reports).length;
+
+ Object.keys(reports).forEach(function (name) {
+ var report = reports[name];
+ if (verbose) {
+ console.error('Write report: ' + name);
+ }
+ report.on('done', handler);
+ report.writeReport(collector, sync);
+ });
+ },
+ /*
+ * handles listening on all reports to be completed before calling the callback
+ * @method handleDone
+ * @private
+ * @param {Function} callback the callback to call when all reports are
+ * written
+ */
+ handleDone: function (callback) {
+ this.inProgress -= 1;
+ if (this.inProgress === 0) {
+ return callback();
+ }
+ }
+};
+
+module.exports = Reporter;
diff --git a/node_modules/istanbul/lib/store/fslookup.js b/node_modules/istanbul/lib/store/fslookup.js
new file mode 100644
index 000000000..b00cc179c
--- /dev/null
+++ b/node_modules/istanbul/lib/store/fslookup.js
@@ -0,0 +1,61 @@
+/*
+ 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
+ * @module 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;
+
diff --git a/node_modules/istanbul/lib/store/index.js b/node_modules/istanbul/lib/store/index.js
new file mode 100644
index 000000000..85ffc4f0a
--- /dev/null
+++ b/node_modules/istanbul/lib/store/index.js
@@ -0,0 +1,123 @@
+/*
+ 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
+ * @module store
+ * @param {Object} options Optional. The options supported by a specific store implementation.
+ * @main store
+ */
+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;
+
+
diff --git a/node_modules/istanbul/lib/store/memory.js b/node_modules/istanbul/lib/store/memory.js
new file mode 100644
index 000000000..ff96fbd32
--- /dev/null
+++ b/node_modules/istanbul/lib/store/memory.js
@@ -0,0 +1,56 @@
+/*
+ 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
+ * @module 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;
diff --git a/node_modules/istanbul/lib/store/tmp.js b/node_modules/istanbul/lib/store/tmp.js
new file mode 100644
index 000000000..31789c88b
--- /dev/null
+++ b/node_modules/istanbul/lib/store/tmp.js
@@ -0,0 +1,81 @@
+/*
+ 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
+ * @module 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 path.join(this.tmp, 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;
diff --git a/node_modules/istanbul/lib/util/factory.js b/node_modules/istanbul/lib/util/factory.js
new file mode 100644
index 000000000..9f3d6f36f
--- /dev/null
+++ b/node_modules/istanbul/lib/util/factory.js
@@ -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;
+
diff --git a/node_modules/istanbul/lib/util/file-matcher.js b/node_modules/istanbul/lib/util/file-matcher.js
new file mode 100644
index 000000000..986064252
--- /dev/null
+++ b/node_modules/istanbul/lib/util/file-matcher.js
@@ -0,0 +1,76 @@
+/*
+ Copyright (c) 2012, Yahoo! Inc. All rights reserved.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+
+var async = require('async'),
+ glob = require('glob'),
+ fs = require('fs'),
+ 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,
+ realpath = options.realpath,
+ 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, nodir: true, ignore: excludes };
+ seq += 1;
+ opts['x' + seq + new Date().getTime()] = true; //cache buster for minimatch cache bug
+ glob(includes.join(' '), opts, function (err, files) {
+ if (err) { return callback(err); }
+ if (relative) { return callback(err, files); }
+
+ if (!realpath) {
+ files = files.map(function (file) { return path.resolve(root, file); });
+ return callback(err, files);
+ }
+
+ var realPathCache = module.constructor._realpathCache || {};
+
+ async.map(files, function (file, done) {
+ fs.realpath(path.resolve(root, file), realPathCache, done);
+ }, callback);
+ });
+}
+
+function matcherFor(options, callback) {
+
+ if (!callback && typeof options === 'function') {
+ callback = options;
+ options = null;
+ }
+ options = options || {};
+ options.relative = false; //force absolute paths
+ options.realpath = true; //force real paths (to match Node.js module 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
+};
+
diff --git a/node_modules/istanbul/lib/util/file-writer.js b/node_modules/istanbul/lib/util/file-writer.js
new file mode 100644
index 000000000..3367dcc83
--- /dev/null
+++ b/node_modules/istanbul/lib/util/file-writer.js
@@ -0,0 +1,154 @@
+/*
+ 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');
+ }
+ }
+});
+/**
+ * a concrete writer implementation that can write files synchronously or
+ * asynchronously based on the constructor argument passed to it.
+ *
+ * Usage
+ * -----
+ *
+ * var sync = true,
+ * fileWriter = new require('istanbul').FileWriter(sync);
+ *
+ * fileWriter.on('done', function () { console.log('done'); });
+ * fileWriter.copyFile('/foo/bar.jpg', '/baz/bar.jpg');
+ * fileWriter.writeFile('/foo/index.html', function (contentWriter) {
+ * contentWriter.println('<html>');
+ * contentWriter.println('</html>');
+ * });
+ * fileWriter.done(); // will emit the `done` event when all files are written
+ *
+ * @class FileWriter
+ * @extends Writer
+ * @module io
+ * @param sync
+ * @constructor
+ */
+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; \ No newline at end of file
diff --git a/node_modules/istanbul/lib/util/help-formatter.js b/node_modules/istanbul/lib/util/help-formatter.js
new file mode 100644
index 000000000..8d9136acf
--- /dev/null
+++ b/node_modules/istanbul/lib/util/help-formatter.js
@@ -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
+}; \ No newline at end of file
diff --git a/node_modules/istanbul/lib/util/input-error.js b/node_modules/istanbul/lib/util/input-error.js
new file mode 100644
index 000000000..488b71a0d
--- /dev/null
+++ b/node_modules/istanbul/lib/util/input-error.js
@@ -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;
+};
+
+
diff --git a/node_modules/istanbul/lib/util/insertion-text.js b/node_modules/istanbul/lib/util/insertion-text.js
new file mode 100644
index 000000000..d257643f2
--- /dev/null
+++ b/node_modules/istanbul/lib/util/insertion-text.js
@@ -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; \ No newline at end of file
diff --git a/node_modules/istanbul/lib/util/meta.js b/node_modules/istanbul/lib/util/meta.js
new file mode 100644
index 000000000..0384459b5
--- /dev/null
+++ b/node_modules/istanbul/lib/util/meta.js
@@ -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
+};
+
diff --git a/node_modules/istanbul/lib/util/tree-summarizer.js b/node_modules/istanbul/lib/util/tree-summarizer.js
new file mode 100644
index 000000000..df350f50e
--- /dev/null
+++ b/node_modules/istanbul/lib/util/tree-summarizer.js
@@ -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) {
+ 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__' + SEP) {
+ 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;
diff --git a/node_modules/istanbul/lib/util/writer.js b/node_modules/istanbul/lib/util/writer.js
new file mode 100644
index 000000000..f5e68293c
--- /dev/null
+++ b/node_modules/istanbul/lib/util/writer.js
@@ -0,0 +1,92 @@
+/*
+ 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 interfaces for writing content
+ * @class ContentWriter
+ * @module io
+ * @main io
+ * @constructor
+ */
+//abstract interface for writing content
+function ContentWriter() {
+}
+
+ContentWriter.prototype = {
+ /**
+ * writes the specified string as-is
+ * @method write
+ * @param {String} str the string to write
+ */
+ write: /* istanbul ignore next: abstract method */ function (/* str */) {
+ throw new Error('write: must be overridden');
+ },
+ /**
+ * writes the specified string with a newline at the end
+ * @method println
+ * @param {String} str the string to write
+ */
+ println: function (str) { this.write(str + '\n'); }
+};
+
+/**
+ * abstract interface for writing files and assets. The caller is expected to
+ * call `done` on the writer after it has finished writing all the required
+ * files. The writer is an event-emitter that emits a `done` event when `done`
+ * is called on it *and* all files have successfully been written.
+ *
+ * @class Writer
+ * @constructor
+ */
+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
+ * @method writeFile
+ * @param {String} file the name of the file to write
+ * @param {Function} 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
+ * @method copyFile
+ * @param {String} source the file to copy, found on the file system
+ * @param {String} 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.
+ * @method done
+ */
+ done: /* istanbul ignore next: abstract method */ function () {
+ throw new Error('done: must be overridden');
+ }
+});
+
+module.exports = {
+ Writer: Writer,
+ ContentWriter: ContentWriter
+};
+
diff --git a/node_modules/istanbul/lib/util/yui-load-hook.js b/node_modules/istanbul/lib/util/yui-load-hook.js
new file mode 100644
index 000000000..9b1365d7a
--- /dev/null
+++ b/node_modules/istanbul/lib/util/yui-load-hook.js
@@ -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;
+ };
+ }
+ };
+};
+