index.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /*
  2. Copyright 2012-2015, Yahoo Inc.
  3. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
  4. */
  5. 'use strict';
  6. const PCT_COLS = 9;
  7. const MISSING_COL = 18;
  8. const TAB_SIZE = 1;
  9. const DELIM = ' |';
  10. const COL_DELIM = '-|';
  11. function padding(num, ch) {
  12. let str = '';
  13. let i;
  14. ch = ch || ' ';
  15. for (i = 0; i < num; i += 1) {
  16. str += ch;
  17. }
  18. return str;
  19. }
  20. function fill(str, width, right, tabs) {
  21. tabs = tabs || 0;
  22. str = String(str);
  23. const leadingSpaces = tabs * TAB_SIZE;
  24. const remaining = width - leadingSpaces;
  25. const leader = padding(leadingSpaces);
  26. let fmtStr = '';
  27. let fillStr;
  28. const strlen = str.length;
  29. if (remaining > 0) {
  30. if (remaining >= strlen) {
  31. fillStr = padding(remaining - strlen);
  32. fmtStr = right ? fillStr + str : str + fillStr;
  33. } else {
  34. fmtStr = str.substring(strlen - remaining);
  35. fmtStr = '... ' + fmtStr.substring(4);
  36. }
  37. }
  38. return leader + fmtStr;
  39. }
  40. function formatName(name, maxCols, level) {
  41. return fill(name, maxCols, false, level);
  42. }
  43. function formatPct(pct, width) {
  44. return fill(pct, width || PCT_COLS, true, 0);
  45. }
  46. function nodeName(node) {
  47. return node.getRelativeName() || 'All files';
  48. }
  49. function depthFor(node) {
  50. let ret = 0;
  51. node = node.getParent();
  52. while (node) {
  53. ret += 1;
  54. node = node.getParent();
  55. }
  56. return ret;
  57. }
  58. function findNameWidth(node, context) {
  59. let last = 0;
  60. const compareWidth = function(node) {
  61. const depth = depthFor(node);
  62. const idealWidth = TAB_SIZE * depth + nodeName(node).length;
  63. if (idealWidth > last) {
  64. last = idealWidth;
  65. }
  66. };
  67. const visitor = {
  68. onSummary(node) {
  69. compareWidth(node);
  70. },
  71. onDetail(node) {
  72. compareWidth(node);
  73. }
  74. };
  75. node.visit(context.getVisitor(visitor));
  76. return last;
  77. }
  78. function makeLine(nameWidth) {
  79. const name = padding(nameWidth, '-');
  80. const pct = padding(PCT_COLS, '-');
  81. const elements = [];
  82. elements.push(name);
  83. elements.push(pct);
  84. elements.push(pct);
  85. elements.push(pct);
  86. elements.push(pct);
  87. elements.push(padding(MISSING_COL, '-'));
  88. return elements.join(COL_DELIM) + COL_DELIM;
  89. }
  90. function tableHeader(maxNameCols) {
  91. const elements = [];
  92. elements.push(formatName('File', maxNameCols, 0));
  93. elements.push(formatPct('% Stmts'));
  94. elements.push(formatPct('% Branch'));
  95. elements.push(formatPct('% Funcs'));
  96. elements.push(formatPct('% Lines'));
  97. elements.push(formatPct('Uncovered Line #s', MISSING_COL));
  98. return elements.join(' |') + ' |';
  99. }
  100. function missingLines(node, colorizer) {
  101. const missingLines = node.isSummary()
  102. ? []
  103. : node.getFileCoverage().getUncoveredLines();
  104. return colorizer(formatPct(missingLines.join(','), MISSING_COL), 'low');
  105. }
  106. function missingBranches(node, colorizer) {
  107. const branches = node.isSummary()
  108. ? {}
  109. : node.getFileCoverage().getBranchCoverageByLine();
  110. const missingLines = Object.keys(branches)
  111. .filter(key => branches[key].coverage < 100)
  112. .map(key => key);
  113. return colorizer(formatPct(missingLines.join(','), MISSING_COL), 'medium');
  114. }
  115. function isFull(metrics) {
  116. return (
  117. metrics.statements.pct === 100 &&
  118. metrics.branches.pct === 100 &&
  119. metrics.functions.pct === 100 &&
  120. metrics.lines.pct === 100
  121. );
  122. }
  123. function tableRow(
  124. node,
  125. context,
  126. colorizer,
  127. maxNameCols,
  128. level,
  129. skipEmpty,
  130. skipFull
  131. ) {
  132. const name = nodeName(node);
  133. const metrics = node.getCoverageSummary();
  134. const isEmpty = metrics.isEmpty();
  135. if (skipEmpty && isEmpty) {
  136. return '';
  137. }
  138. if (skipFull && isFull(metrics)) {
  139. return '';
  140. }
  141. const mm = {
  142. statements: isEmpty ? 0 : metrics.statements.pct,
  143. branches: isEmpty ? 0 : metrics.branches.pct,
  144. functions: isEmpty ? 0 : metrics.functions.pct,
  145. lines: isEmpty ? 0 : metrics.lines.pct
  146. };
  147. const colorize = isEmpty
  148. ? function(str) {
  149. return str;
  150. }
  151. : function(str, key) {
  152. return colorizer(str, context.classForPercent(key, mm[key]));
  153. };
  154. const elements = [];
  155. elements.push(colorize(formatName(name, maxNameCols, level), 'statements'));
  156. elements.push(colorize(formatPct(mm.statements), 'statements'));
  157. elements.push(colorize(formatPct(mm.branches), 'branches'));
  158. elements.push(colorize(formatPct(mm.functions), 'functions'));
  159. elements.push(colorize(formatPct(mm.lines), 'lines'));
  160. if (mm.lines === 100) {
  161. elements.push(missingBranches(node, colorizer));
  162. } else {
  163. elements.push(missingLines(node, colorizer));
  164. }
  165. return elements.join(DELIM) + DELIM;
  166. }
  167. function TextReport(opts) {
  168. opts = opts || {};
  169. this.file = opts.file || null;
  170. this.maxCols = opts.maxCols || 0;
  171. this.cw = null;
  172. this.skipEmpty = opts.skipEmpty;
  173. this.skipFull = opts.skipFull;
  174. }
  175. TextReport.prototype.onStart = function(root, context) {
  176. const statsWidth = 4 * (PCT_COLS + 2) + MISSING_COL;
  177. this.cw = context.writer.writeFile(this.file);
  178. this.nameWidth = findNameWidth(root, context);
  179. if (this.maxCols > 0) {
  180. const maxRemaining = this.maxCols - statsWidth - 2;
  181. if (this.nameWidth > maxRemaining) {
  182. this.nameWidth = maxRemaining;
  183. }
  184. }
  185. const line = makeLine(this.nameWidth);
  186. this.cw.println(line);
  187. this.cw.println(tableHeader(this.nameWidth));
  188. this.cw.println(line);
  189. };
  190. TextReport.prototype.onSummary = function(node, context) {
  191. const nodeDepth = depthFor(node);
  192. const row = tableRow(
  193. node,
  194. context,
  195. this.cw.colorize.bind(this.cw),
  196. this.nameWidth,
  197. nodeDepth,
  198. this.skipEmpty,
  199. this.skipFull
  200. );
  201. if (row) {
  202. this.cw.println(row);
  203. }
  204. };
  205. TextReport.prototype.onDetail = function(node, context) {
  206. return this.onSummary(node, context);
  207. };
  208. TextReport.prototype.onEnd = function() {
  209. this.cw.println(makeLine(this.nameWidth));
  210. this.cw.close();
  211. };
  212. module.exports = TextReport;