IstanbulJS - レポート改善 ver.2 - ソースコード修正
IstanbulJS - レポート改善 ver.2 - ソースコード修正:
IstanbulJS - レポート改善 - ソースコード修正 (以降、「前記事」と記載)を基に、別のルールを用いた改善案。
予め 前記事の修正 を実施しておくこと。
本修正で行うハイライトのルールを以下のように定義する。
定義部の開始位置から関数の終了位置までをハイライトする。定義部の開始位置は 前記事の定義 と同様である。
ハイライトのアルゴリズムを、上述のルールに沿うよう修正する。
�� istanbul-reports@2.0.1
�� TypeScriptを使わない場合のパスは
修正を実施後に再度生成した各レポートが、上述のルールに従っているか検証する。
�� 上述のルールに従ってハイライトされている。
�� 上述のルールに従ってハイライトされている。
�� 上述のルールに従ってハイライトされている。
�� 上述のルールに従ってハイライトされている。
本記事では、IstanbulJSが生成するHTMLレポートのハイライト範囲を 前記事 と異なるルールで修正するコードを示した。
概要
IstanbulJS - レポート改善 - ソースコード修正 (以降、「前記事」と記載)を基に、別のルールを用いた改善案。
前提
予め 前記事の修正 を実施しておくこと。
ルール
本修正で行うハイライトのルールを以下のように定義する。
関数
定義部の開始位置から関数の終了位置までをハイライトする。定義部の開始位置は 前記事の定義 と同様である。
修正ファイル
annotator.js
目的
ハイライトのアルゴリズムを、上述のルールに沿うよう修正する。
パッケージ
�� istanbul-reports@2.0.1
コード
�� TypeScriptを使わない場合のパスは node_modules/istanbul-reports/lib/html/annotator.js
である。node_modules/nyc/node_modules/istanbul-reports/lib/html/annotator.js
/* 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; var statements = []; Object.keys(statementStats).forEach(function (stName) { var count = statementStats[stName], meta = statementMeta[stName], type = count > 0 ? 'yes' : 'no', startLine = meta.start.line, startCol = meta.start.column, endFirstCol = meta.end.column, endLastCol = endFirstCol, endLine = meta.end.line, openSpan = lt + 'span class="' + (meta.skip ? 'cstat-skip' : 'cstat-no') + ' start-line-' + startLine + ' start-column-' + startCol + '"' + title('statement not covered') + gt, closeSpan = lt + '/span' + gt, i, text, lines = []; if (type === 'no' && structuredText[startLine]) { if (endLine !== startLine) { endFirstCol = structuredText[startLine].text.originalLength(); } text = structuredText[startLine].text lines.push({ insertionText: text, args: [ startCol, openSpan, startCol < endLastCol ? endFirstCol : text.originalLength(), closeSpan ] }); for (i = startLine + 1; i < endLine; i++) { text = structuredText[i].text lines.push({ insertionText: text, args: [ text.startPos, openSpan, text.originalLength(), closeSpan ] }); } if (endLine !== startLine) { text = structuredText[endLine].text lines.push({ insertionText: text, args: [ text.startPos, openSpan, endLastCol, closeSpan ] }); } statements.push({ startPos: meta.start, lines: lines }); } }); // reverse sort statements.sort(function (b, a) { return a.startPos.line === b.startPos.line ? a.startPos.column - b.startPos.column : a.startPos.line - b.startPos.line; }); statements.map(function (statement) { statement.lines.map(function (line) { var insertionText = line.insertionText; var args = line.args; insertionText.wrap.apply(insertionText, args); }) }); } function annotateFunctions(fileCoverage, structuredText) { var fnStats = fileCoverage.f, fnMeta = fileCoverage.fnMap; var functions = []; if (!fnStats) { return; } Object.keys(fnStats).forEach(function (fName) { var count = fnStats[fName], meta = fnMeta[fName], type = count > 0 ? 'yes' : 'no', declStartCol = meta.decl.start.column, declEndCol = meta.decl.end.column, declStartLine = meta.decl.start.line, declEndLine = meta.decl.end.line, locStartCol = meta.loc.start.column, locEndCol = meta.loc.end.column, locStartLine = meta.loc.start.line, locEndLine = meta.loc.end.line, openSpan = lt + 'span class="' + (meta.skip ? 'fstat-skip' : 'fstat-no') + ' start-line-' + declStartLine + ' start-column-' + declStartCol + '"' + title('function not covered') + gt, closeSpan = lt + '/span' + gt, i, text, lines = []; if (type === 'no' && structuredText[declStartLine]) { text = structuredText[declStartLine].text; lines.push({ insertionText: text, args: [ declStartCol, openSpan, locEndLine === declStartLine ? locEndCol : text.originalLength(), closeSpan ] }); for (i = declStartLine + 1; i < locEndLine; i++) { text = structuredText[i].text; lines.push({ insertionText: text, args: [ text.startPos, openSpan, text.originalLength(), closeSpan ] }); } if (locEndLine !== declStartLine) { text = structuredText[locEndLine].text; lines.push({ insertionText: text, args: [ text.startPos, openSpan, locEndCol, closeSpan ] }); } functions.push({ startPos: meta.decl.start, lines: lines }); } }); // reverse sort functions.sort(function (b, a) { return a.startPos.line === b.startPos.line ? a.startPos.column - b.startPos.column : a.startPos.line - b.startPos.line; }); functions.map(function (fn) { fn.lines.map(function (line) { var insertionText = line.insertionText; var args = line.args; insertionText.wrap.apply(insertionText, args); }) }); } 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], i, count, meta, type, startCol, endCol, startLine, endLine, offset, 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.locations.length; i += 1) { count = branchArray[i]; meta = metaArray.locations[i]; type = count > 0 ? 'yes' : 'no'; startCol = meta.start.column; endCol = meta.end.column; startLine = meta.start.line; endLine = meta.end.line; openSpan = lt + 'span class="branch-' + i + ' ' + (meta.skip ? 'cbranch-skip' : 'cbranch-no') + ' start-line-' + startLine + ' start-column-' + startCol + '"' + title('branch not covered') + gt; closeSpan = lt + '/span' + gt; if (count === 0 && structuredText[startLine]) { //skip branches taken if (branchMeta[branchName].type === 'if') { // 'if' is a special case // since the else branch might not be visible, being non-existent text = structuredText[metaArray.loc.start.line].text; text.insertAt(metaArray.loc.start.column, 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 if (branchMeta[branchName].type === 'switch') { text = structuredText[startLine].text; offset = text.offsets.reduce(function (accumulator, currentValue) { return accumulator + currentValue.len; }, 0); endCol = text.text.substr(startCol + offset).match(/^\s*(?:case\s+[^\[\('"`:]*[\[\(]?\s*['"`]?(?:\s*:|.*?[\)\]]\s*:|.*?[^\\]['"`]\s*:)|default\s*:)/)[0].length + startCol; text.wrap(startCol, openSpan, endCol, closeSpan); } else { if (endLine !== startLine) { endCol = structuredText[startLine].text.originalLength(); } text = structuredText[startLine].text; 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); annotateBranches(fileCoverage, structured); annotateStatements(fileCoverage, structured); annotateFunctions(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 };
コメント
コメントを投稿