286 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			286 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | |
|  Copyright 2012-2015, Yahoo Inc.
 | |
|  Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 | |
|  */
 | |
| 'use strict';
 | |
| 
 | |
| const util = require('util');
 | |
| const coverage = require('istanbul-lib-coverage');
 | |
| const Path = require('./path');
 | |
| const tree = require('./tree');
 | |
| const BaseNode = tree.Node;
 | |
| const BaseTree = tree.Tree;
 | |
| 
 | |
| function ReportNode(path, fileCoverage) {
 | |
|     this.path = path;
 | |
|     this.parent = null;
 | |
|     this.fileCoverage = fileCoverage;
 | |
|     this.children = [];
 | |
| }
 | |
| 
 | |
| util.inherits(ReportNode, BaseNode);
 | |
| 
 | |
| ReportNode.prototype.addChild = function(child) {
 | |
|     child.parent = this;
 | |
|     this.children.push(child);
 | |
| };
 | |
| 
 | |
| ReportNode.prototype.asRelative = function(p) {
 | |
|     /* istanbul ignore if */
 | |
|     if (p.substring(0, 1) === '/') {
 | |
|         return p.substring(1);
 | |
|     }
 | |
|     return p;
 | |
| };
 | |
| 
 | |
| ReportNode.prototype.getQualifiedName = function() {
 | |
|     return this.asRelative(this.path.toString());
 | |
| };
 | |
| 
 | |
| ReportNode.prototype.getRelativeName = function() {
 | |
|     const parent = this.getParent();
 | |
|     const myPath = this.path;
 | |
|     let relPath;
 | |
|     let i;
 | |
|     const parentPath = parent ? parent.path : new Path([]);
 | |
|     if (parentPath.ancestorOf(myPath)) {
 | |
|         relPath = new Path(myPath.elements());
 | |
|         for (i = 0; i < parentPath.length; i += 1) {
 | |
|             relPath.shift();
 | |
|         }
 | |
|         return this.asRelative(relPath.toString());
 | |
|     }
 | |
|     return this.asRelative(this.path.toString());
 | |
| };
 | |
| 
 | |
| ReportNode.prototype.getParent = function() {
 | |
|     return this.parent;
 | |
| };
 | |
| 
 | |
| ReportNode.prototype.getChildren = function() {
 | |
|     return this.children;
 | |
| };
 | |
| 
 | |
| ReportNode.prototype.isSummary = function() {
 | |
|     return !this.fileCoverage;
 | |
| };
 | |
| 
 | |
| ReportNode.prototype.getFileCoverage = function() {
 | |
|     return this.fileCoverage;
 | |
| };
 | |
| 
 | |
| ReportNode.prototype.getCoverageSummary = function(filesOnly) {
 | |
|     const cacheProp = 'c_' + (filesOnly ? 'files' : 'full');
 | |
|     let summary;
 | |
| 
 | |
|     if (this.hasOwnProperty(cacheProp)) {
 | |
|         return this[cacheProp];
 | |
|     }
 | |
| 
 | |
|     if (!this.isSummary()) {
 | |
|         summary = this.getFileCoverage().toSummary();
 | |
|     } else {
 | |
|         let count = 0;
 | |
|         summary = coverage.createCoverageSummary();
 | |
|         this.getChildren().forEach(child => {
 | |
|             if (filesOnly && child.isSummary()) {
 | |
|                 return;
 | |
|             }
 | |
|             count += 1;
 | |
|             summary.merge(child.getCoverageSummary(filesOnly));
 | |
|         });
 | |
|         if (count === 0 && filesOnly) {
 | |
|             summary = null;
 | |
|         }
 | |
|     }
 | |
|     this[cacheProp] = summary;
 | |
|     return summary;
 | |
| };
 | |
| 
 | |
| function treeFor(root, childPrefix) {
 | |
|     const tree = new BaseTree();
 | |
|     const maybePrefix = function(node) {
 | |
|         if (childPrefix && !node.isRoot()) {
 | |
|             node.path.unshift(childPrefix);
 | |
|         }
 | |
|     };
 | |
|     tree.getRoot = function() {
 | |
|         return root;
 | |
|     };
 | |
|     const visitor = {
 | |
|         onDetail(node) {
 | |
|             maybePrefix(node);
 | |
|         },
 | |
|         onSummary(node) {
 | |
|             maybePrefix(node);
 | |
|             node.children.sort((a, b) => {
 | |
|                 const astr = a.path.toString();
 | |
|                 const bstr = b.path.toString();
 | |
|                 return astr < bstr
 | |
|                     ? -1
 | |
|                     : astr > bstr
 | |
|                     ? 1
 | |
|                     : /* istanbul ignore next */ 0;
 | |
|             });
 | |
|         }
 | |
|     };
 | |
|     tree.visit(visitor);
 | |
|     return tree;
 | |
| }
 | |
| 
 | |
| function findCommonParent(paths) {
 | |
|     if (paths.length === 0) {
 | |
|         return new Path([]);
 | |
|     }
 | |
|     let common = paths[0];
 | |
|     let i;
 | |
| 
 | |
|     for (i = 1; i < paths.length; i += 1) {
 | |
|         common = common.commonPrefixPath(paths[i]);
 | |
|         if (common.length === 0) {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     return common;
 | |
| }
 | |
| 
 | |
| function toInitialList(coverageMap) {
 | |
|     const ret = [];
 | |
|     coverageMap.files().forEach(filePath => {
 | |
|         const p = new Path(filePath);
 | |
|         const coverage = coverageMap.fileCoverageFor(filePath);
 | |
|         ret.push({
 | |
|             filePath,
 | |
|             path: p,
 | |
|             fileCoverage: coverage
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     const commonParent = findCommonParent(ret.map(o => o.path.parent()));
 | |
|     if (commonParent.length > 0) {
 | |
|         ret.forEach(o => {
 | |
|             o.path.splice(0, commonParent.length);
 | |
|         });
 | |
|     }
 | |
|     return {
 | |
|         list: ret,
 | |
|         commonParent
 | |
|     };
 | |
| }
 | |
| 
 | |
| function toDirParents(list) {
 | |
|     const nodeMap = Object.create(null);
 | |
|     const parentNodeList = [];
 | |
|     list.forEach(o => {
 | |
|         const node = new ReportNode(o.path, o.fileCoverage);
 | |
|         const parentPath = o.path.parent();
 | |
|         let parent = nodeMap[parentPath.toString()];
 | |
| 
 | |
|         if (!parent) {
 | |
|             parent = new ReportNode(parentPath);
 | |
|             nodeMap[parentPath.toString()] = parent;
 | |
|             parentNodeList.push(parent);
 | |
|         }
 | |
|         parent.addChild(node);
 | |
|     });
 | |
|     return parentNodeList;
 | |
| }
 | |
| 
 | |
| function foldIntoParents(nodeList) {
 | |
|     const ret = [];
 | |
|     let i;
 | |
|     let j;
 | |
| 
 | |
|     // sort by longest length first
 | |
|     nodeList.sort((a, b) => -1 * Path.compare(a.path, b.path));
 | |
| 
 | |
|     for (i = 0; i < nodeList.length; i += 1) {
 | |
|         const first = nodeList[i];
 | |
|         let inserted = false;
 | |
| 
 | |
|         for (j = i + 1; j < nodeList.length; j += 1) {
 | |
|             const second = nodeList[j];
 | |
|             if (second.path.ancestorOf(first.path)) {
 | |
|                 second.addChild(first);
 | |
|                 inserted = true;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!inserted) {
 | |
|             ret.push(first);
 | |
|         }
 | |
|     }
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| function createRoot() {
 | |
|     return new ReportNode(new Path([]));
 | |
| }
 | |
| 
 | |
| function createNestedSummary(coverageMap) {
 | |
|     const flattened = toInitialList(coverageMap);
 | |
|     const dirParents = toDirParents(flattened.list);
 | |
|     const topNodes = foldIntoParents(dirParents);
 | |
| 
 | |
|     if (topNodes.length === 0) {
 | |
|         return treeFor(new ReportNode(new Path([])));
 | |
|     }
 | |
| 
 | |
|     if (topNodes.length === 1) {
 | |
|         return treeFor(topNodes[0]);
 | |
|     }
 | |
| 
 | |
|     const root = createRoot();
 | |
|     topNodes.forEach(node => {
 | |
|         root.addChild(node);
 | |
|     });
 | |
|     return treeFor(root);
 | |
| }
 | |
| 
 | |
| function createPackageSummary(coverageMap) {
 | |
|     const flattened = toInitialList(coverageMap);
 | |
|     const dirParents = toDirParents(flattened.list);
 | |
|     const common = flattened.commonParent;
 | |
|     let prefix;
 | |
|     let root;
 | |
| 
 | |
|     if (dirParents.length === 1) {
 | |
|         root = dirParents[0];
 | |
|     } else {
 | |
|         root = createRoot();
 | |
|         // if one of the dirs is itself the root,
 | |
|         // then we need to create a top-level dir
 | |
|         dirParents.forEach(dp => {
 | |
|             if (dp.path.length === 0) {
 | |
|                 prefix = 'root';
 | |
|             }
 | |
|         });
 | |
|         if (prefix && common.length > 0) {
 | |
|             prefix = common.elements()[common.elements().length - 1];
 | |
|         }
 | |
|         dirParents.forEach(node => {
 | |
|             root.addChild(node);
 | |
|         });
 | |
|     }
 | |
|     return treeFor(root, prefix);
 | |
| }
 | |
| 
 | |
| function createFlatSummary(coverageMap) {
 | |
|     const flattened = toInitialList(coverageMap);
 | |
|     const list = flattened.list;
 | |
|     const root = createRoot();
 | |
| 
 | |
|     list.forEach(o => {
 | |
|         const node = new ReportNode(o.path, o.fileCoverage);
 | |
|         root.addChild(node);
 | |
|     });
 | |
|     return treeFor(root);
 | |
| }
 | |
| 
 | |
| module.exports = {
 | |
|     createNestedSummary,
 | |
|     createPackageSummary,
 | |
|     createFlatSummary
 | |
| };
 |