228 lines
7.8 KiB
JavaScript
228 lines
7.8 KiB
JavaScript
|
/*
|
||
|
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
|
||
|
};
|
||
|
|