123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- /*
- Copyright 2012-2015, Yahoo Inc.
- Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
- */
- "use strict";
- var InsertionText = require('./insertion-text'),
- lt = '\u0001',
- gt = '\u0002',
- RE_LT = /</g,
- RE_GT = />/g,
- RE_AMP = /&/g,
- RE_lt = /\u0001/g,
- RE_gt = /\u0002/g;
- function title(str) {
- return ' title="' + str + '" ';
- }
- function customEscape(text) {
- text = String(text);
- return text.replace(RE_AMP, '&')
- .replace(RE_LT, '<')
- .replace(RE_GT, '>')
- .replace(RE_lt, '<')
- .replace(RE_gt, '>');
- }
- function annotateLines(fileCoverage, structuredText) {
- var lineStats = fileCoverage.getLineCoverage();
- if (!lineStats) {
- return;
- }
- Object.keys(lineStats).forEach(function (lineNumber) {
- var count = lineStats[lineNumber];
- if (structuredText[lineNumber]) {
- structuredText[lineNumber].covered = count > 0 ? 'yes' : 'no';
- structuredText[lineNumber].hits = count;
- }
- });
- }
- 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' && structuredText[startLine]) {
- if (endLine !== startLine) {
- endCol = structuredText[startLine].text.originalLength();
- }
- text = structuredText[startLine].text;
- text.wrap(startCol,
- openSpan,
- startCol < endCol ? 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.decl.start.column,
- endCol = meta.decl.end.column + 1,
- startLine = meta.decl.start.line,
- endLine = meta.decl.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' && structuredText[startLine]) {
- if (endLine !== startLine) {
- endCol = structuredText[startLine].text.originalLength();
- }
- text = structuredText[startLine].text;
- text.wrap(startCol,
- openSpan,
- startCol < endCol ? 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;
- // only highlight if partial branches are missing or if there is a
- // single uncovered branch.
- if (sumCount > 0 || (sumCount === 0 && branchArray.length === 1)) {
- for (i = 0; i < branchArray.length && i < metaArray.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 && structuredText[startLine]) { //skip branches taken
- if (endLine !== startLine) {
- endCol = structuredText[startLine].text.originalLength();
- }
- text = structuredText[startLine].text;
- if (branchMeta[branchName].type === 'if') {
- // '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,
- startCol < endCol ? endCol : text.originalLength(),
- closeSpan);
- }
- }
- }
- }
- });
- }
- function annotateSourceCode(fileCoverage, sourceStore) {
- var codeArray,
- lineCoverageArray;
- try {
- var sourceText = sourceStore.getSource(fileCoverage.path),
- code = sourceText.split(/(?:\r?\n)|\r/),
- count = 0,
- structured = code.map(function (str) {
- count += 1;
- return {
- line: count,
- covered: 'neutral',
- hits: 0,
- text: new InsertionText(str, true)
- };
- });
- structured.unshift({line: 0, covered: null, text: new InsertionText("")});
- 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();
- codeArray = structured.map(function (item) {
- return customEscape(item.text.toString()) || ' ';
- });
- lineCoverageArray = structured.map(function (item) {
- return {
- covered: item.covered,
- hits: item.hits > 0 ? item.hits + 'x' : ' '
- };
- });
- return {
- annotatedCode: codeArray,
- lineCoverage: lineCoverageArray,
- maxLines: structured.length
- };
- } catch (ex) {
- codeArray = [ ex.message ];
- lineCoverageArray = [ { covered: 'no', hits: 0 } ];
- String(ex.stack || '').split(/\r?\n/).forEach(function (line) {
- codeArray.push(line);
- lineCoverageArray.push({ covered: 'no', hits: 0 });
- });
- return {
- annotatedCode: codeArray,
- lineCoverage: lineCoverageArray,
- maxLines: codeArray.length
- };
- }
- }
- module.exports = {
- annotateSourceCode: annotateSourceCode
- };
|