| /* |
| Copyright 2012-2015, Yahoo Inc. |
| Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. |
| */ |
| 'use strict'; |
| |
| var Path = require('./path'), |
| util = require('util'), |
| tree = require('./tree'), |
| coverage = require('istanbul-lib-coverage'), |
| BaseNode = tree.Node, |
| 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() { |
| var parent = this.getParent(), |
| myPath = this.path, |
| relPath, |
| i, |
| 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) { |
| var cacheProp = 'c_' + (filesOnly ? 'files' : 'full'), |
| summary; |
| |
| if (this.hasOwnProperty(cacheProp)) { |
| return this[cacheProp]; |
| } |
| |
| if (!this.isSummary()) { |
| summary = this.getFileCoverage().toSummary(); |
| } else { |
| var count = 0; |
| summary = coverage.createCoverageSummary(); |
| this.getChildren().forEach(function(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) { |
| var tree = new BaseTree(), |
| visitor, |
| maybePrefix = function(node) { |
| if (childPrefix && !node.isRoot()) { |
| node.path.unshift(childPrefix); |
| } |
| }; |
| tree.getRoot = function() { |
| return root; |
| }; |
| visitor = { |
| onDetail: function(node) { |
| maybePrefix(node); |
| }, |
| onSummary: function(node) { |
| maybePrefix(node); |
| node.children.sort(function(a, b) { |
| var astr = a.path.toString(), |
| 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([]); |
| } |
| var common = paths[0], |
| i; |
| |
| for (i = 1; i < paths.length; i += 1) { |
| common = common.commonPrefixPath(paths[i]); |
| if (common.length === 0) { |
| break; |
| } |
| } |
| return common; |
| } |
| |
| function toInitialList(coverageMap) { |
| var ret = [], |
| commonParent; |
| coverageMap.files().forEach(function(filePath) { |
| var p = new Path(filePath), |
| coverage = coverageMap.fileCoverageFor(filePath); |
| ret.push({ |
| filePath: filePath, |
| path: p, |
| fileCoverage: coverage |
| }); |
| }); |
| commonParent = findCommonParent( |
| ret.map(function(o) { |
| return o.path.parent(); |
| }) |
| ); |
| if (commonParent.length > 0) { |
| ret.forEach(function(o) { |
| o.path.splice(0, commonParent.length); |
| }); |
| } |
| return { |
| list: ret, |
| commonParent: commonParent |
| }; |
| } |
| |
| function toDirParents(list) { |
| var nodeMap = Object.create(null), |
| parentNodeList = []; |
| list.forEach(function(o) { |
| var node = new ReportNode(o.path, o.fileCoverage), |
| parentPath = o.path.parent(), |
| 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) { |
| var ret = [], |
| i, |
| j; |
| |
| // sort by longest length first |
| nodeList.sort(function(a, b) { |
| return -1 * Path.compare(a.path, b.path); |
| }); |
| |
| for (i = 0; i < nodeList.length; i += 1) { |
| var first = nodeList[i], |
| inserted = false; |
| |
| for (j = i + 1; j < nodeList.length; j += 1) { |
| var 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) { |
| var flattened = toInitialList(coverageMap), |
| dirParents = toDirParents(flattened.list), |
| topNodes = foldIntoParents(dirParents), |
| root; |
| |
| if (topNodes.length === 0) { |
| return treeFor(new ReportNode(new Path([]))); |
| } |
| |
| if (topNodes.length === 1) { |
| return treeFor(topNodes[0]); |
| } |
| |
| root = createRoot(); |
| topNodes.forEach(function(node) { |
| root.addChild(node); |
| }); |
| return treeFor(root); |
| } |
| |
| function createPackageSummary(coverageMap) { |
| var flattened = toInitialList(coverageMap), |
| dirParents = toDirParents(flattened.list), |
| common = flattened.commonParent, |
| prefix, |
| 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(function(dp) { |
| if (dp.path.length === 0) { |
| prefix = 'root'; |
| } |
| }); |
| if (prefix && common.length > 0) { |
| prefix = common.elements()[common.elements().length - 1]; |
| } |
| dirParents.forEach(function(node) { |
| root.addChild(node); |
| }); |
| } |
| return treeFor(root, prefix); |
| } |
| |
| function createFlatSummary(coverageMap) { |
| var flattened = toInitialList(coverageMap), |
| list = flattened.list, |
| root; |
| |
| root = createRoot(); |
| list.forEach(function(o) { |
| var node = new ReportNode(o.path, o.fileCoverage); |
| root.addChild(node); |
| }); |
| return treeFor(root); |
| } |
| |
| module.exports = { |
| createNestedSummary: createNestedSummary, |
| createPackageSummary: createPackageSummary, |
| createFlatSummary: createFlatSummary |
| }; |