| /* |
| 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 |
| }; |
| |