diff options
Diffstat (limited to 'node_modules/istanbul/lib')
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 Binary files differnew file mode 100644 index 000000000..03f704a60 --- /dev/null +++ b/node_modules/istanbul/lib/assets/sort-arrow-sprite.png 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 = ' '; + 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, '&') + .replace(RE_LT, '<') + .replace(RE_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) || ' '); + }); + 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> + </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; + }; + } + }; +}; + |