summarizer.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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 util = require('util');
  7. const coverage = require('istanbul-lib-coverage');
  8. const Path = require('./path');
  9. const tree = require('./tree');
  10. const BaseNode = tree.Node;
  11. const BaseTree = tree.Tree;
  12. function ReportNode(path, fileCoverage) {
  13. this.path = path;
  14. this.parent = null;
  15. this.fileCoverage = fileCoverage;
  16. this.children = [];
  17. }
  18. util.inherits(ReportNode, BaseNode);
  19. ReportNode.prototype.addChild = function(child) {
  20. child.parent = this;
  21. this.children.push(child);
  22. };
  23. ReportNode.prototype.asRelative = function(p) {
  24. /* istanbul ignore if */
  25. if (p.substring(0, 1) === '/') {
  26. return p.substring(1);
  27. }
  28. return p;
  29. };
  30. ReportNode.prototype.getQualifiedName = function() {
  31. return this.asRelative(this.path.toString());
  32. };
  33. ReportNode.prototype.getRelativeName = function() {
  34. const parent = this.getParent();
  35. const myPath = this.path;
  36. let relPath;
  37. let i;
  38. const parentPath = parent ? parent.path : new Path([]);
  39. if (parentPath.ancestorOf(myPath)) {
  40. relPath = new Path(myPath.elements());
  41. for (i = 0; i < parentPath.length; i += 1) {
  42. relPath.shift();
  43. }
  44. return this.asRelative(relPath.toString());
  45. }
  46. return this.asRelative(this.path.toString());
  47. };
  48. ReportNode.prototype.getParent = function() {
  49. return this.parent;
  50. };
  51. ReportNode.prototype.getChildren = function() {
  52. return this.children;
  53. };
  54. ReportNode.prototype.isSummary = function() {
  55. return !this.fileCoverage;
  56. };
  57. ReportNode.prototype.getFileCoverage = function() {
  58. return this.fileCoverage;
  59. };
  60. ReportNode.prototype.getCoverageSummary = function(filesOnly) {
  61. const cacheProp = 'c_' + (filesOnly ? 'files' : 'full');
  62. let summary;
  63. if (this.hasOwnProperty(cacheProp)) {
  64. return this[cacheProp];
  65. }
  66. if (!this.isSummary()) {
  67. summary = this.getFileCoverage().toSummary();
  68. } else {
  69. let count = 0;
  70. summary = coverage.createCoverageSummary();
  71. this.getChildren().forEach(child => {
  72. if (filesOnly && child.isSummary()) {
  73. return;
  74. }
  75. count += 1;
  76. summary.merge(child.getCoverageSummary(filesOnly));
  77. });
  78. if (count === 0 && filesOnly) {
  79. summary = null;
  80. }
  81. }
  82. this[cacheProp] = summary;
  83. return summary;
  84. };
  85. function treeFor(root, childPrefix) {
  86. const tree = new BaseTree();
  87. const maybePrefix = function(node) {
  88. if (childPrefix && !node.isRoot()) {
  89. node.path.unshift(childPrefix);
  90. }
  91. };
  92. tree.getRoot = function() {
  93. return root;
  94. };
  95. const visitor = {
  96. onDetail(node) {
  97. maybePrefix(node);
  98. },
  99. onSummary(node) {
  100. maybePrefix(node);
  101. node.children.sort((a, b) => {
  102. const astr = a.path.toString();
  103. const bstr = b.path.toString();
  104. return astr < bstr
  105. ? -1
  106. : astr > bstr
  107. ? 1
  108. : /* istanbul ignore next */ 0;
  109. });
  110. }
  111. };
  112. tree.visit(visitor);
  113. return tree;
  114. }
  115. function findCommonParent(paths) {
  116. if (paths.length === 0) {
  117. return new Path([]);
  118. }
  119. let common = paths[0];
  120. let i;
  121. for (i = 1; i < paths.length; i += 1) {
  122. common = common.commonPrefixPath(paths[i]);
  123. if (common.length === 0) {
  124. break;
  125. }
  126. }
  127. return common;
  128. }
  129. function toInitialList(coverageMap) {
  130. const ret = [];
  131. coverageMap.files().forEach(filePath => {
  132. const p = new Path(filePath);
  133. const coverage = coverageMap.fileCoverageFor(filePath);
  134. ret.push({
  135. filePath,
  136. path: p,
  137. fileCoverage: coverage
  138. });
  139. });
  140. const commonParent = findCommonParent(ret.map(o => o.path.parent()));
  141. if (commonParent.length > 0) {
  142. ret.forEach(o => {
  143. o.path.splice(0, commonParent.length);
  144. });
  145. }
  146. return {
  147. list: ret,
  148. commonParent
  149. };
  150. }
  151. function toDirParents(list) {
  152. const nodeMap = Object.create(null);
  153. const parentNodeList = [];
  154. list.forEach(o => {
  155. const node = new ReportNode(o.path, o.fileCoverage);
  156. const parentPath = o.path.parent();
  157. let parent = nodeMap[parentPath.toString()];
  158. if (!parent) {
  159. parent = new ReportNode(parentPath);
  160. nodeMap[parentPath.toString()] = parent;
  161. parentNodeList.push(parent);
  162. }
  163. parent.addChild(node);
  164. });
  165. return parentNodeList;
  166. }
  167. function foldIntoParents(nodeList) {
  168. const ret = [];
  169. let i;
  170. let j;
  171. // sort by longest length first
  172. nodeList.sort((a, b) => -1 * Path.compare(a.path, b.path));
  173. for (i = 0; i < nodeList.length; i += 1) {
  174. const first = nodeList[i];
  175. let inserted = false;
  176. for (j = i + 1; j < nodeList.length; j += 1) {
  177. const second = nodeList[j];
  178. if (second.path.ancestorOf(first.path)) {
  179. second.addChild(first);
  180. inserted = true;
  181. break;
  182. }
  183. }
  184. if (!inserted) {
  185. ret.push(first);
  186. }
  187. }
  188. return ret;
  189. }
  190. function createRoot() {
  191. return new ReportNode(new Path([]));
  192. }
  193. function createNestedSummary(coverageMap) {
  194. const flattened = toInitialList(coverageMap);
  195. const dirParents = toDirParents(flattened.list);
  196. const topNodes = foldIntoParents(dirParents);
  197. if (topNodes.length === 0) {
  198. return treeFor(new ReportNode(new Path([])));
  199. }
  200. if (topNodes.length === 1) {
  201. return treeFor(topNodes[0]);
  202. }
  203. const root = createRoot();
  204. topNodes.forEach(node => {
  205. root.addChild(node);
  206. });
  207. return treeFor(root);
  208. }
  209. function createPackageSummary(coverageMap) {
  210. const flattened = toInitialList(coverageMap);
  211. const dirParents = toDirParents(flattened.list);
  212. const common = flattened.commonParent;
  213. let prefix;
  214. let root;
  215. if (dirParents.length === 1) {
  216. root = dirParents[0];
  217. } else {
  218. root = createRoot();
  219. // if one of the dirs is itself the root,
  220. // then we need to create a top-level dir
  221. dirParents.forEach(dp => {
  222. if (dp.path.length === 0) {
  223. prefix = 'root';
  224. }
  225. });
  226. if (prefix && common.length > 0) {
  227. prefix = common.elements()[common.elements().length - 1];
  228. }
  229. dirParents.forEach(node => {
  230. root.addChild(node);
  231. });
  232. }
  233. return treeFor(root, prefix);
  234. }
  235. function createFlatSummary(coverageMap) {
  236. const flattened = toInitialList(coverageMap);
  237. const list = flattened.list;
  238. const root = createRoot();
  239. list.forEach(o => {
  240. const node = new ReportNode(o.path, o.fileCoverage);
  241. root.addChild(node);
  242. });
  243. return treeFor(root);
  244. }
  245. module.exports = {
  246. createNestedSummary,
  247. createPackageSummary,
  248. createFlatSummary
  249. };