Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / istanbul / lib / report / html.js
1 /*
2  Copyright (c) 2012, Yahoo! Inc.  All rights reserved.
3  Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
4  */
5
6 /*jshint maxlen: 300 */
7 var handlebars = require('handlebars'),
8     defaults = require('./common/defaults'),
9     path = require('path'),
10     fs = require('fs'),
11     util = require('util'),
12     FileWriter = require('../util/file-writer'),
13     Report = require('./index'),
14     Store = require('../store'),
15     InsertionText = require('../util/insertion-text'),
16     TreeSummarizer = require('../util/tree-summarizer'),
17     utils = require('../object-utils'),
18     templateFor = function (name) { return handlebars.compile(fs.readFileSync(path.resolve(__dirname, 'templates', name + '.txt'), 'utf8')); },
19     headerTemplate = templateFor('head'),
20     footerTemplate = templateFor('foot'),
21     pathTemplate = handlebars.compile('<div class="path">{{{html}}}</div>'),
22     detailTemplate = handlebars.compile([
23         '<tr>',
24         '<td class="line-count">{{#show_lines}}{{maxLines}}{{/show_lines}}</td>',
25         '<td class="line-coverage">{{#show_line_execution_counts fileCoverage}}{{maxLines}}{{/show_line_execution_counts}}</td>',
26         '<td class="text"><pre class="prettyprint lang-js">{{#show_code structured}}{{/show_code}}</pre></td>',
27         '</tr>\n'
28     ].join('')),
29     summaryTableHeader = [
30         '<div class="coverage-summary">',
31         '<table>',
32         '<thead>',
33         '<tr>',
34         '   <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>',
35         '   <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>',
36         '   <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>',
37         '   <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>',
38         '   <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>',
39         '   <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>',
40         '   <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>',
41         '   <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>',
42         '   <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>',
43         '   <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>',
44         '</tr>',
45         '</thead>',
46         '<tbody>'
47     ].join('\n'),
48     summaryLineTemplate = handlebars.compile([
49         '<tr>',
50         '<td class="file {{reportClasses.statements}}" data-value="{{file}}"><a href="{{output}}">{{file}}</a></td>',
51         '<td data-value="{{metrics.statements.pct}}" class="pic {{reportClasses.statements}}">{{#show_picture}}{{metrics.statements.pct}}{{/show_picture}}</td>',
52         '<td data-value="{{metrics.statements.pct}}" class="pct {{reportClasses.statements}}">{{metrics.statements.pct}}%</td>',
53         '<td data-value="{{metrics.statements.total}}" class="abs {{reportClasses.statements}}">({{metrics.statements.covered}}&nbsp;/&nbsp;{{metrics.statements.total}})</td>',
54         '<td data-value="{{metrics.branches.pct}}" class="pct {{reportClasses.branches}}">{{metrics.branches.pct}}%</td>',
55         '<td data-value="{{metrics.branches.total}}" class="abs {{reportClasses.branches}}">({{metrics.branches.covered}}&nbsp;/&nbsp;{{metrics.branches.total}})</td>',
56         '<td data-value="{{metrics.functions.pct}}" class="pct {{reportClasses.functions}}">{{metrics.functions.pct}}%</td>',
57         '<td data-value="{{metrics.functions.total}}" class="abs {{reportClasses.functions}}">({{metrics.functions.covered}}&nbsp;/&nbsp;{{metrics.functions.total}})</td>',
58         '<td data-value="{{metrics.lines.pct}}" class="pct {{reportClasses.lines}}">{{metrics.lines.pct}}%</td>',
59         '<td data-value="{{metrics.lines.total}}" class="abs {{reportClasses.lines}}">({{metrics.lines.covered}}&nbsp;/&nbsp;{{metrics.lines.total}})</td>',
60         '</tr>\n'
61     ].join('\n\t')),
62     summaryTableFooter = [
63         '</tbody>',
64         '</table>',
65         '</div>'
66     ].join('\n'),
67     lt = '\u0001',
68     gt = '\u0002',
69     RE_LT = /</g,
70     RE_GT = />/g,
71     RE_AMP = /&/g,
72     RE_lt = /\u0001/g,
73     RE_gt = /\u0002/g;
74
75 handlebars.registerHelper('show_picture', function (opts) {
76     var num = Number(opts.fn(this)),
77         rest,
78         cls = '';
79     if (isFinite(num)) {
80         if (num === 100) {
81             cls = ' cover-full';
82         }
83         num = Math.floor(num);
84         rest = 100 - num;
85         return '<span class="cover-fill' + cls + '" style="width: ' + num + 'px;"></span>' +
86             '<span class="cover-empty" style="width:' + rest + 'px;"></span>';
87     } else {
88         return '';
89     }
90 });
91
92 handlebars.registerHelper('show_ignores', function (metrics) {
93     var statements = metrics.statements.skipped,
94         functions = metrics.functions.skipped,
95         branches = metrics.branches.skipped,
96         result;
97
98     if (statements === 0 && functions === 0 && branches === 0) {
99         return '<span class="ignore-none">none</span>';
100     }
101
102     result = [];
103     if (statements >0) { result.push(statements === 1 ? '1 statement': statements + ' statements'); }
104     if (functions >0) { result.push(functions === 1 ? '1 function' : functions + ' functions'); }
105     if (branches >0) { result.push(branches === 1 ? '1 branch' : branches + ' branches'); }
106
107     return result.join(', ');
108 });
109
110 handlebars.registerHelper('show_lines', function (opts) {
111     var maxLines = Number(opts.fn(this)),
112         i,
113         array = [];
114
115     for (i = 0; i < maxLines; i += 1) {
116         array[i] = i + 1;
117     }
118     return array.join('\n');
119 });
120
121 handlebars.registerHelper('show_line_execution_counts', function (context, opts) {
122     var lines = context.l,
123         maxLines = Number(opts.fn(this)),
124         i,
125         lineNumber,
126         array = [],
127         covered,
128         value = '';
129
130     for (i = 0; i < maxLines; i += 1) {
131         lineNumber = i + 1;
132         value = '&nbsp;';
133         covered = 'neutral';
134         if (lines.hasOwnProperty(lineNumber)) {
135             if (lines[lineNumber] > 0) {
136                 covered = 'yes';
137                 value = lines[lineNumber];
138             } else {
139                 covered = 'no';
140             }
141         }
142         array.push('<span class="cline-any cline-' + covered + '">' + value + '</span>');
143     }
144     return array.join('\n');
145 });
146
147 function customEscape(text) {
148     text = text.toString();
149     return text.replace(RE_AMP, '&amp;')
150         .replace(RE_LT, '&lt;')
151         .replace(RE_GT, '&gt;')
152         .replace(RE_lt, '<')
153         .replace(RE_gt, '>');
154 }
155
156 handlebars.registerHelper('show_code', function (context /*, opts */) {
157     var array = [];
158
159     context.forEach(function (item) {
160         array.push(customEscape(item.text) || '&nbsp;');
161     });
162     return array.join('\n');
163 });
164
165 function title(str) {
166     return ' title="' + str + '" ';
167 }
168
169 function annotateLines(fileCoverage, structuredText) {
170     var lineStats = fileCoverage.l;
171     if (!lineStats) { return; }
172     Object.keys(lineStats).forEach(function (lineNumber) {
173         var count = lineStats[lineNumber];
174         if (structuredText[lineNumber]) {
175           structuredText[lineNumber].covered = count > 0 ? 'yes' : 'no';
176         }
177     });
178     structuredText.forEach(function (item) {
179         if (item.covered === null) {
180             item.covered = 'neutral';
181         }
182     });
183 }
184
185 function annotateStatements(fileCoverage, structuredText) {
186     var statementStats = fileCoverage.s,
187         statementMeta = fileCoverage.statementMap;
188     Object.keys(statementStats).forEach(function (stName) {
189         var count = statementStats[stName],
190             meta = statementMeta[stName],
191             type = count > 0 ? 'yes' : 'no',
192             startCol = meta.start.column,
193             endCol = meta.end.column + 1,
194             startLine = meta.start.line,
195             endLine = meta.end.line,
196             openSpan = lt + 'span class="' + (meta.skip ? 'cstat-skip' : 'cstat-no') + '"' + title('statement not covered') + gt,
197             closeSpan = lt + '/span' + gt,
198             text;
199
200         if (type === 'no') {
201             if (endLine !== startLine) {
202                 endLine = startLine;
203                 endCol = structuredText[startLine].text.originalLength();
204             }
205             text = structuredText[startLine].text;
206             text.wrap(startCol,
207                 openSpan,
208                 startLine === endLine ? endCol : text.originalLength(),
209                 closeSpan);
210         }
211     });
212 }
213
214 function annotateFunctions(fileCoverage, structuredText) {
215
216     var fnStats = fileCoverage.f,
217         fnMeta = fileCoverage.fnMap;
218     if (!fnStats) { return; }
219     Object.keys(fnStats).forEach(function (fName) {
220         var count = fnStats[fName],
221             meta = fnMeta[fName],
222             type = count > 0 ? 'yes' : 'no',
223             startCol = meta.loc.start.column,
224             endCol = meta.loc.end.column + 1,
225             startLine = meta.loc.start.line,
226             endLine = meta.loc.end.line,
227             openSpan = lt + 'span class="' + (meta.skip ? 'fstat-skip' : 'fstat-no') + '"' + title('function not covered') + gt,
228             closeSpan = lt + '/span' + gt,
229             text;
230
231         if (type === 'no') {
232             if (endLine !== startLine) {
233                 endLine = startLine;
234                 endCol = structuredText[startLine].text.originalLength();
235             }
236             text = structuredText[startLine].text;
237             text.wrap(startCol,
238                 openSpan,
239                 startLine === endLine ? endCol : text.originalLength(),
240                 closeSpan);
241         }
242     });
243 }
244
245 function annotateBranches(fileCoverage, structuredText) {
246     var branchStats = fileCoverage.b,
247         branchMeta = fileCoverage.branchMap;
248     if (!branchStats) { return; }
249
250     Object.keys(branchStats).forEach(function (branchName) {
251         var branchArray = branchStats[branchName],
252             sumCount = branchArray.reduce(function (p, n) { return p + n; }, 0),
253             metaArray = branchMeta[branchName].locations,
254             i,
255             count,
256             meta,
257             type,
258             startCol,
259             endCol,
260             startLine,
261             endLine,
262             openSpan,
263             closeSpan,
264             text;
265
266         if (sumCount > 0) { //only highlight if partial branches are missing
267             for (i = 0; i < branchArray.length; i += 1) {
268                 count = branchArray[i];
269                 meta = metaArray[i];
270                 type = count > 0 ? 'yes' : 'no';
271                 startCol = meta.start.column;
272                 endCol = meta.end.column + 1;
273                 startLine = meta.start.line;
274                 endLine = meta.end.line;
275                 openSpan = lt + 'span class="branch-' + i + ' ' + (meta.skip ? 'cbranch-skip' : 'cbranch-no') + '"' + title('branch not covered') + gt;
276                 closeSpan = lt + '/span' + gt;
277
278                 if (count === 0) { //skip branches taken
279                     if (endLine !== startLine) {
280                         endLine = startLine;
281                         endCol = structuredText[startLine].text.originalLength();
282                     }
283                     text = structuredText[startLine].text;
284                     if (branchMeta[branchName].type === 'if') { // and 'if' is a special case since the else branch might not be visible, being non-existent
285                         text.insertAt(startCol, lt + 'span class="' + (meta.skip ? 'skip-if-branch' : 'missing-if-branch') + '"' +
286                             title((i === 0 ? 'if' : 'else') + ' path not taken') + gt +
287                             (i === 0 ? 'I' : 'E')  + lt + '/span' + gt, true, false);
288                     } else {
289                         text.wrap(startCol,
290                             openSpan,
291                             startLine === endLine ? endCol : text.originalLength(),
292                             closeSpan);
293                     }
294                 }
295             }
296         }
297     });
298 }
299
300 function getReportClass(stats, watermark) {
301     var coveragePct = stats.pct,
302         identity  = 1;
303     if (coveragePct * identity === coveragePct) {
304         return coveragePct >= watermark[1] ? 'high' : coveragePct >= watermark[0] ? 'medium' : 'low';
305     } else {
306         return '';
307     }
308 }
309
310 function cleanPath(name) {
311     var SEP = path.sep || '/';
312     return (SEP !== '/') ? name.split(SEP).join('/') : name;
313 }
314
315 function isEmptySourceStore(sourceStore) {
316     if (!sourceStore) {
317         return true;
318     }
319
320     var cache = sourceStore.sourceCache;
321     return cache && !Object.keys(cache).length;
322 }
323
324 /**
325  * a `Report` implementation that produces HTML coverage reports.
326  *
327  * Usage
328  * -----
329  *
330  *      var report = require('istanbul').Report.create('html');
331  *
332  *
333  * @class HtmlReport
334  * @extends Report
335  * @module report
336  * @constructor
337  * @param {Object} opts optional
338  * @param {String} [opts.dir] the directory in which to generate reports. Defaults to `./html-report`
339  */
340 function HtmlReport(opts) {
341     Report.call(this);
342     this.opts = opts || {};
343     this.opts.dir = this.opts.dir || path.resolve(process.cwd(), 'html-report');
344     this.opts.sourceStore = isEmptySourceStore(this.opts.sourceStore) ?
345         Store.create('fslookup') : this.opts.sourceStore;
346     this.opts.linkMapper = this.opts.linkMapper || this.standardLinkMapper();
347     this.opts.writer = this.opts.writer || null;
348     this.opts.templateData = { datetime: Date() };
349     this.opts.watermarks = this.opts.watermarks || defaults.watermarks();
350 }
351
352 HtmlReport.TYPE = 'html';
353 util.inherits(HtmlReport, Report);
354
355 Report.mix(HtmlReport, {
356
357     synopsis: function () {
358         return 'Navigable HTML coverage report for every file and directory';
359     },
360
361     getPathHtml: function (node, linkMapper) {
362         var parent = node.parent,
363             nodePath = [],
364             linkPath = [],
365             i;
366
367         while (parent) {
368             nodePath.push(parent);
369             parent = parent.parent;
370         }
371
372         for (i = 0; i < nodePath.length; i += 1) {
373             linkPath.push('<a href="' + linkMapper.ancestor(node, i + 1) + '">' +
374                 (cleanPath(nodePath[i].relativeName) || 'All files') + '</a>');
375         }
376         linkPath.reverse();
377         return linkPath.length > 0 ? linkPath.join(' &#187; ') + ' &#187; ' +
378             cleanPath(node.displayShortName()) : '';
379     },
380
381     fillTemplate: function (node, templateData) {
382         var opts = this.opts,
383             linkMapper = opts.linkMapper;
384
385         templateData.entity = node.name || 'All files';
386         templateData.metrics = node.metrics;
387         templateData.reportClass = getReportClass(node.metrics.statements, opts.watermarks.statements);
388         templateData.pathHtml = pathTemplate({ html: this.getPathHtml(node, linkMapper) });
389         templateData.base = {
390                 css: linkMapper.asset(node, 'base.css')
391         };
392         templateData.sorter = {
393             js: linkMapper.asset(node, 'sorter.js'),
394             image: linkMapper.asset(node, 'sort-arrow-sprite.png')
395         };
396         templateData.prettify = {
397             js: linkMapper.asset(node, 'prettify.js'),
398             css: linkMapper.asset(node, 'prettify.css')
399         };
400     },
401     writeDetailPage: function (writer, node, fileCoverage) {
402         var opts = this.opts,
403             sourceStore = opts.sourceStore,
404             templateData = opts.templateData,
405             sourceText = fileCoverage.code && Array.isArray(fileCoverage.code) ?
406                 fileCoverage.code.join('\n') + '\n' : sourceStore.get(fileCoverage.path),
407             code = sourceText.split(/(?:\r?\n)|\r/),
408             count = 0,
409             structured = code.map(function (str) { count += 1; return { line: count, covered: null, text: new InsertionText(str, true) }; }),
410             context;
411
412         structured.unshift({ line: 0, covered: null, text: new InsertionText("") });
413
414         this.fillTemplate(node, templateData);
415         writer.write(headerTemplate(templateData));
416         writer.write('<pre><table class="coverage">\n');
417
418         annotateLines(fileCoverage, structured);
419         //note: order is important, since statements typically result in spanning the whole line and doing branches late
420         //causes mismatched tags
421         annotateBranches(fileCoverage, structured);
422         annotateFunctions(fileCoverage, structured);
423         annotateStatements(fileCoverage, structured);
424
425         structured.shift();
426         context = {
427             structured: structured,
428             maxLines: structured.length,
429             fileCoverage: fileCoverage
430         };
431         writer.write(detailTemplate(context));
432         writer.write('</table></pre>\n');
433         writer.write(footerTemplate(templateData));
434     },
435
436     writeIndexPage: function (writer, node) {
437         var linkMapper = this.opts.linkMapper,
438             templateData = this.opts.templateData,
439             children = Array.prototype.slice.apply(node.children),
440             watermarks = this.opts.watermarks;
441
442         children.sort(function (a, b) {
443             return a.name < b.name ? -1 : 1;
444         });
445
446         this.fillTemplate(node, templateData);
447         writer.write(headerTemplate(templateData));
448         writer.write(summaryTableHeader);
449         children.forEach(function (child) {
450             var metrics = child.metrics,
451                 reportClasses = {
452                     statements: getReportClass(metrics.statements, watermarks.statements),
453                     lines: getReportClass(metrics.lines, watermarks.lines),
454                     functions: getReportClass(metrics.functions, watermarks.functions),
455                     branches: getReportClass(metrics.branches, watermarks.branches)
456                 },
457                 data = {
458                     metrics: metrics,
459                     reportClasses: reportClasses,
460                     file: cleanPath(child.displayShortName()),
461                     output: linkMapper.fromParent(child)
462                 };
463             writer.write(summaryLineTemplate(data) + '\n');
464         });
465         writer.write(summaryTableFooter);
466         writer.write(footerTemplate(templateData));
467     },
468
469     writeFiles: function (writer, node, dir, collector) {
470         var that = this,
471             indexFile = path.resolve(dir, 'index.html'),
472             childFile;
473         if (this.opts.verbose) { console.error('Writing ' + indexFile); }
474         writer.writeFile(indexFile, function (contentWriter) {
475             that.writeIndexPage(contentWriter, node);
476         });
477         node.children.forEach(function (child) {
478             if (child.kind === 'dir') {
479                 that.writeFiles(writer, child, path.resolve(dir, child.relativeName), collector);
480             } else {
481                 childFile = path.resolve(dir, child.relativeName + '.html');
482                 if (that.opts.verbose) { console.error('Writing ' + childFile); }
483                 writer.writeFile(childFile, function (contentWriter) {
484                     that.writeDetailPage(contentWriter, child, collector.fileCoverageFor(child.fullPath()));
485                 });
486             }
487         });
488     },
489
490     standardLinkMapper: function () {
491         return {
492             fromParent: function (node) {
493                 var relativeName = cleanPath(node.relativeName);
494                 
495                 return node.kind === 'dir' ? relativeName + 'index.html' : relativeName + '.html';
496             },
497             ancestorHref: function (node, num) {
498                 var href = '',
499                     notDot = function(part) {
500                         return part !== '.';
501                     },
502                     separated,
503                     levels,
504                     i,
505                     j;
506
507                 for (i = 0; i < num; i += 1) {
508                     separated = cleanPath(node.relativeName).split('/').filter(notDot);
509                     levels = separated.length - 1;
510                     for (j = 0; j < levels; j += 1) {
511                         href += '../';
512                     }
513                     node = node.parent;
514                 }
515                 return href;
516             },
517             ancestor: function (node, num) {
518                 return this.ancestorHref(node, num) + 'index.html';
519             },
520             asset: function (node, name) {
521                 var i = 0,
522                     parent = node.parent;
523                 while (parent) { i += 1; parent = parent.parent; }
524                 return this.ancestorHref(node, i) + name;
525             }
526         };
527     },
528
529     writeReport: function (collector, sync) {
530         var opts = this.opts,
531             dir = opts.dir,
532             summarizer = new TreeSummarizer(),
533             writer = opts.writer || new FileWriter(sync),
534             that = this,
535             tree,
536             copyAssets = function (subdir) {
537                 var srcDir = path.resolve(__dirname, '..', 'assets', subdir);
538                 fs.readdirSync(srcDir).forEach(function (f) {
539                     var resolvedSource = path.resolve(srcDir, f),
540                         resolvedDestination = path.resolve(dir, f),
541                         stat = fs.statSync(resolvedSource);
542
543                     if (stat.isFile()) {
544                         if (opts.verbose) {
545                             console.log('Write asset: ' + resolvedDestination);
546                         }
547                         writer.copyFile(resolvedSource, resolvedDestination);
548                     }
549                 });
550             };
551
552         collector.files().forEach(function (key) {
553             summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
554         });
555         tree = summarizer.getTreeSummary();
556         [ '.', 'vendor'].forEach(function (subdir) {
557             copyAssets(subdir);
558         });
559         writer.on('done', function () { that.emit('done'); });
560         //console.log(JSON.stringify(tree.root, undefined, 4));
561         this.writeFiles(writer, tree.root, dir, collector);
562         writer.done();
563     }
564 });
565
566 module.exports = HtmlReport;
567