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