UNPKG

7.36 kBJavaScriptView Raw
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
7const coverage = require('istanbul-lib-coverage');
8const Path = require('./path');
9const { BaseNode, BaseTree } = require('./tree');
10
11class ReportNode extends BaseNode {
12 constructor(path, fileCoverage) {
13 super();
14
15 this.path = path;
16 this.parent = null;
17 this.fileCoverage = fileCoverage;
18 this.children = [];
19 }
20
21 static createRoot(children) {
22 const root = new ReportNode(new Path([]));
23
24 children.forEach(child => {
25 root.addChild(child);
26 });
27
28 return root;
29 }
30
31 addChild(child) {
32 child.parent = this;
33 this.children.push(child);
34 }
35
36 asRelative(p) {
37 if (p.substring(0, 1) === '/') {
38 return p.substring(1);
39 }
40 return p;
41 }
42
43 getQualifiedName() {
44 return this.asRelative(this.path.toString());
45 }
46
47 getRelativeName() {
48 const parent = this.getParent();
49 const myPath = this.path;
50 let relPath;
51 let i;
52 const parentPath = parent ? parent.path : new Path([]);
53 if (parentPath.ancestorOf(myPath)) {
54 relPath = new Path(myPath.elements());
55 for (i = 0; i < parentPath.length; i += 1) {
56 relPath.shift();
57 }
58 return this.asRelative(relPath.toString());
59 }
60 return this.asRelative(this.path.toString());
61 }
62
63 getParent() {
64 return this.parent;
65 }
66
67 getChildren() {
68 return this.children;
69 }
70
71 isSummary() {
72 return !this.fileCoverage;
73 }
74
75 getFileCoverage() {
76 return this.fileCoverage;
77 }
78
79 getCoverageSummary(filesOnly) {
80 const cacheProp = `c_${filesOnly ? 'files' : 'full'}`;
81 let summary;
82
83 if (Object.prototype.hasOwnProperty.call(this, cacheProp)) {
84 return this[cacheProp];
85 }
86
87 if (!this.isSummary()) {
88 summary = this.getFileCoverage().toSummary();
89 } else {
90 let count = 0;
91 summary = coverage.createCoverageSummary();
92 this.getChildren().forEach(child => {
93 if (filesOnly && child.isSummary()) {
94 return;
95 }
96 count += 1;
97 summary.merge(child.getCoverageSummary(filesOnly));
98 });
99 if (count === 0 && filesOnly) {
100 summary = null;
101 }
102 }
103 this[cacheProp] = summary;
104 return summary;
105 }
106}
107
108class ReportTree extends BaseTree {
109 constructor(root, childPrefix) {
110 super(root);
111
112 const maybePrefix = node => {
113 if (childPrefix && !node.isRoot()) {
114 node.path.unshift(childPrefix);
115 }
116 };
117 this.visit({
118 onDetail: maybePrefix,
119 onSummary(node) {
120 maybePrefix(node);
121 node.children.sort((a, b) => {
122 const astr = a.path.toString();
123 const bstr = b.path.toString();
124 return astr < bstr
125 ? -1
126 : astr > bstr
127 ? 1
128 : /* istanbul ignore next */ 0;
129 });
130 }
131 });
132 }
133}
134
135function findCommonParent(paths) {
136 return paths.reduce(
137 (common, path) => common.commonPrefixPath(path),
138 paths[0] || new Path([])
139 );
140}
141
142function findOrCreateParent(parentPath, nodeMap, created = () => {}) {
143 let parent = nodeMap[parentPath.toString()];
144
145 if (!parent) {
146 parent = new ReportNode(parentPath);
147 nodeMap[parentPath.toString()] = parent;
148 created(parentPath, parent);
149 }
150
151 return parent;
152}
153
154function toDirParents(list) {
155 const nodeMap = Object.create(null);
156 list.forEach(o => {
157 const parent = findOrCreateParent(o.path.parent(), nodeMap);
158 parent.addChild(new ReportNode(o.path, o.fileCoverage));
159 });
160
161 return Object.values(nodeMap);
162}
163
164function addAllPaths(topPaths, nodeMap, path, node) {
165 const parent = findOrCreateParent(
166 path.parent(),
167 nodeMap,
168 (parentPath, parent) => {
169 if (parentPath.hasParent()) {
170 addAllPaths(topPaths, nodeMap, parentPath, parent);
171 } else {
172 topPaths.push(parent);
173 }
174 }
175 );
176
177 parent.addChild(node);
178}
179
180function foldIntoOneDir(node, parent) {
181 const { children } = node;
182 if (children.length === 1 && !children[0].fileCoverage) {
183 children[0].parent = parent;
184 return foldIntoOneDir(children[0], parent);
185 }
186 node.children = children.map(child => foldIntoOneDir(child, node));
187 return node;
188}
189
190function pkgSummaryPrefix(dirParents, commonParent) {
191 if (!dirParents.some(dp => dp.path.length === 0)) {
192 return;
193 }
194
195 if (commonParent.length === 0) {
196 return 'root';
197 }
198
199 return commonParent.name();
200}
201
202class SummarizerFactory {
203 constructor(coverageMap, defaultSummarizer = 'pkg') {
204 this._coverageMap = coverageMap;
205 this._defaultSummarizer = defaultSummarizer;
206 this._initialList = coverageMap.files().map(filePath => ({
207 filePath,
208 path: new Path(filePath),
209 fileCoverage: coverageMap.fileCoverageFor(filePath)
210 }));
211 this._commonParent = findCommonParent(
212 this._initialList.map(o => o.path.parent())
213 );
214 if (this._commonParent.length > 0) {
215 this._initialList.forEach(o => {
216 o.path.splice(0, this._commonParent.length);
217 });
218 }
219 }
220
221 get defaultSummarizer() {
222 return this[this._defaultSummarizer];
223 }
224
225 get flat() {
226 if (!this._flat) {
227 this._flat = new ReportTree(
228 ReportNode.createRoot(
229 this._initialList.map(
230 node => new ReportNode(node.path, node.fileCoverage)
231 )
232 )
233 );
234 }
235
236 return this._flat;
237 }
238
239 _createPkg() {
240 const dirParents = toDirParents(this._initialList);
241 if (dirParents.length === 1) {
242 return new ReportTree(dirParents[0]);
243 }
244
245 return new ReportTree(
246 ReportNode.createRoot(dirParents),
247 pkgSummaryPrefix(dirParents, this._commonParent)
248 );
249 }
250
251 get pkg() {
252 if (!this._pkg) {
253 this._pkg = this._createPkg();
254 }
255
256 return this._pkg;
257 }
258
259 _createNested() {
260 const nodeMap = Object.create(null);
261 const topPaths = [];
262 this._initialList.forEach(o => {
263 const node = new ReportNode(o.path, o.fileCoverage);
264 addAllPaths(topPaths, nodeMap, o.path, node);
265 });
266
267 const topNodes = topPaths.map(node => foldIntoOneDir(node));
268 if (topNodes.length === 1) {
269 return new ReportTree(topNodes[0]);
270 }
271
272 return new ReportTree(ReportNode.createRoot(topNodes));
273 }
274
275 get nested() {
276 if (!this._nested) {
277 this._nested = this._createNested();
278 }
279
280 return this._nested;
281 }
282}
283
284module.exports = SummarizerFactory;