| /* |
| 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 |
| }; |