monocart-coverage-reports 2.5.9 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,666 +1,675 @@
1
- const Util = require('../utils/util.js');
2
-
3
- const getParentFunctionState = (reverseParents) => {
4
- const parentFunction = reverseParents.find((it) => it._state && it._state.isFunction);
5
- if (!parentFunction) {
6
- return;
7
- }
8
- return parentFunction._state;
9
- };
10
-
11
- const getParentCount = (reverseParents, functionCount) => {
12
- // parent count
13
- const parent = reverseParents.find((it) => it._state);
14
- if (parent) {
15
- return parent._state.count;
16
- }
17
- // root function count
18
- return functionCount;
19
- };
20
-
21
- const getFunctionRange = (start, end, coverageInfo) => {
22
-
23
- const {
24
- functionMap, functionNameMap, functionUncoveredRanges
25
- } = coverageInfo;
26
-
27
- // exact matched in functionMap
28
- const range = functionMap.get(start);
29
- if (range) {
30
- return range;
31
- }
32
- // exact matched in functionNameMap
33
- const nameRange = functionNameMap.get(start);
34
- if (nameRange) {
35
- return nameRange;
36
- }
37
-
38
- // find in uncoveredRanges
39
- return Util.findInRanges(start, end, functionUncoveredRanges, 'startOffset', 'endOffset');
40
- };
41
-
42
- const getFunctionBlock = (start, end, functionState) => {
43
- if (!functionState) {
44
- return;
45
- }
46
-
47
- const { range } = functionState;
48
- if (!range) {
49
- return;
50
- }
51
-
52
- const {
53
- blockMap, blockUncoveredRanges, blockCoveredRanges
54
- } = range;
55
-
56
- if (!blockMap) {
57
- return;
58
- }
59
-
60
- const block = blockMap.get(start);
61
- if (block) {
62
- return block;
63
- }
64
-
65
- const uncoveredBlock = Util.findInRanges(start, end, blockUncoveredRanges, 'startOffset', 'endOffset');
66
- if (uncoveredBlock) {
67
- return uncoveredBlock;
68
- }
69
-
70
- // the block is not exact correct, if there is a block wrapped
71
- // [x8]{ var a = b || [x4]c }, b is no block, but it can be found in x8 block
72
- const coveredBlocks = blockCoveredRanges.filter((it) => start >= it.startOffset && end <= it.endOffset);
73
- if (coveredBlocks.length) {
74
- return coveredBlocks.pop();
75
- }
76
-
77
- };
78
-
79
- const addNodeCount = (item) => {
80
-
81
- const { node, reverseParents } = item;
82
-
83
- if (node._state) {
84
- return;
85
- }
86
-
87
- const { start, end } = node;
88
- const functionState = getParentFunctionState(reverseParents);
89
- const block = getFunctionBlock(start, end, functionState);
90
- if (block) {
91
- node._state = {
92
- count: block.count
93
- };
94
- }
95
-
96
- };
97
-
98
- // =======================================================================================
99
-
100
- const createBranchGroup = (type, node, parents, branchMap) => {
101
- const { start, end } = node;
102
- // clone and reverse parents
103
- const reverseParents = [].concat(parents).reverse();
104
- const group = {
105
- type,
106
-
107
- start,
108
- // could be updated if multiple locations
109
- end,
110
-
111
- locations: [],
112
- reverseParents
113
- };
114
-
115
- if (type === 'LogicalExpression') {
116
- // && or ||
117
- group.operator = node.operator;
118
- }
119
-
120
- // could be same start
121
- const branchKey = `${start}_${end}`;
122
-
123
- branchMap.set(branchKey, group);
124
- return group;
125
- };
126
-
127
- const addBranch = (group, node, locationMap) => {
128
- const { start, end } = node;
129
-
130
- const branchKey = `${group.start}_${group.end}`;
131
-
132
- const branchInfo = {
133
- // for get previous group for LogicalExpression
134
- branchKey,
135
- start,
136
- end,
137
- // branch count default to 0
138
- count: 0
139
- };
140
-
141
- group.locations.push(branchInfo);
142
-
143
- // update group end
144
- if (end > group.end) {
145
- group.end = end;
146
- }
147
-
148
- locationMap.set(start, branchInfo);
149
-
150
- return branchInfo;
151
- };
152
-
153
- const addNoneBranch = (group) => {
154
- group.locations.push({
155
- none: true,
156
- count: 0
157
- });
158
- };
159
-
160
- // =======================================================================================
161
-
162
- const updateBlockLocations = (locations) => {
163
- const noBlockList = [];
164
- let blockCount = 0;
165
- locations.forEach((item) => {
166
- if (item.block) {
167
- item.count = item.block.count;
168
- blockCount += item.count;
169
- return;
170
- }
171
- noBlockList.push(item);
172
- });
173
-
174
- return {
175
- noBlockList,
176
- blockCount
177
- };
178
- };
179
-
180
- // const a = tf1 ? 'true' : 'false';
181
- const ConditionalExpression = (group, parentCount) => {
182
- const { noBlockList, blockCount } = updateBlockLocations(group.locations);
183
- if (!noBlockList.length) {
184
- return;
185
- }
186
- let count = parentCount - blockCount;
187
- noBlockList.forEach((item) => {
188
- item.count = count;
189
- count = 0;
190
- });
191
- };
192
-
193
- const IfStatement = (group, parentCount) => {
194
- const { noBlockList, blockCount } = updateBlockLocations(group.locations);
195
- if (!noBlockList.length) {
196
- return;
197
- }
198
- // console.log(parentCount, 'uncovered list', noBlockList.length, group.start);
199
- let count = parentCount - blockCount;
200
- noBlockList.forEach((item) => {
201
- item.count = count;
202
- count = 0;
203
- });
204
-
205
- };
206
-
207
- // const b = tf2 || tf1 || a;
208
- const LogicalExpression = (group, parentCount) => {
209
-
210
- // from left to right
211
- group.locations.forEach((item, i) => {
212
- if (item.block) {
213
- item.count = item.block.count;
214
- } else {
215
- item.count = parentCount;
216
- }
217
- });
218
-
219
- };
220
-
221
- const SwitchStatement = (group, parentCount) => {
222
- const { noBlockList, blockCount } = updateBlockLocations(group.locations);
223
- if (!noBlockList.length) {
224
- return;
225
- }
226
- const count = parentCount - blockCount;
227
- noBlockList.forEach((item) => {
228
- item.count = count;
229
- });
230
- };
231
-
232
- const AssignmentPattern = (group, parentCount) => {
233
- group.locations.forEach((item) => {
234
- item.count = parentCount;
235
- });
236
- };
237
-
238
- // =======================================================================================
239
-
240
- const updateBranchCount = (group) => {
241
-
242
- const {
243
- type, locations, reverseParents
244
- } = group;
245
-
246
- const functionState = getParentFunctionState(reverseParents);
247
-
248
- const functionCount = functionState.count;
249
-
250
- // default is 0, no need continue
251
- if (functionCount === 0) {
252
- return;
253
- }
254
-
255
- // parent is block statement or function
256
- let parentCount = getParentCount(reverseParents, functionCount);
257
- // parent is group range
258
- const groupBlock = getFunctionBlock(group.start, group.end, functionState);
259
- if (groupBlock) {
260
- parentCount = groupBlock.count;
261
- }
262
-
263
- // calculate branches count
264
- locations.forEach((item) => {
265
- const {
266
- start, end, none
267
- } = item;
268
- if (none) {
269
- return;
270
- }
271
- item.block = getFunctionBlock(start, end, functionState);
272
- });
273
-
274
-
275
- const handlers = {
276
- ConditionalExpression,
277
- IfStatement,
278
- LogicalExpression,
279
- SwitchStatement,
280
- AssignmentPattern
281
- };
282
-
283
- const handler = handlers[type];
284
- if (handler) {
285
- handler(group, parentCount);
286
- }
287
-
288
- };
289
-
290
- const generateBranches = (branchMap) => {
291
-
292
- // calculate count for all branches
293
- branchMap.forEach((group) => {
294
- updateBranchCount(group);
295
- });
296
-
297
- // init branches
298
- const branches = [];
299
- branchMap.forEach((group) => {
300
-
301
- // add start/end for none with group start/end
302
- group.locations.forEach((item) => {
303
- if (item.none) {
304
- item.start = group.start;
305
- item.end = group.end;
306
- }
307
- });
308
-
309
- branches.push({
310
- type: group.type,
311
- start: group.start,
312
- end: group.end,
313
- locations: group.locations
314
- });
315
- });
316
-
317
- // sort branches
318
- branches.sort((a, b) => {
319
- return a.start - b.start;
320
- });
321
-
322
- return branches;
323
- };
324
-
325
- // =======================================================================================
326
- // All programs in JavaScript are made of statements and they end with semicolons (;) except block statements which is used to group zero or more statements.
327
- // Statements are just perform some actions but do not produce any value or output whereas expressions return some value.
328
- // Expressions return value, statements do not.
329
- const generateStatements = (statementNodes) => {
330
-
331
- statementNodes.forEach((item) => {
332
- const {
333
- node,
334
- reverseParents
335
- } = item;
336
-
337
- const { start, end } = node;
338
-
339
- item.count = 1;
340
-
341
- // isFunction
342
- if (node._state) {
343
- item.count = node._state.count;
344
- return;
345
- }
346
-
347
- const functionState = getParentFunctionState(reverseParents);
348
- if (!functionState) {
349
- // there is a root range, it impossible not found
350
- return;
351
- }
352
-
353
- // function uncovered
354
- if (functionState.count === 0) {
355
- item.count = 0;
356
- return;
357
- }
358
-
359
- const block = getFunctionBlock(start, end, functionState);
360
- if (block) {
361
- item.count = block.count;
362
- return;
363
- }
364
-
365
- item.count = functionState.count;
366
-
367
- });
368
-
369
- // remove block statement
370
- statementNodes = statementNodes.filter((item) => {
371
- return item.node.type !== 'BlockStatement';
372
- });
373
-
374
- const statements = statementNodes.map((item) => {
375
- const {
376
- node,
377
- count
378
- } = item;
379
- const { start, end } = node;
380
- return {
381
- start,
382
- end,
383
- count
384
- };
385
- });
386
-
387
- return statements;
388
- };
389
-
390
- // =======================================================================================
391
-
392
- const collectNodes = (ast) => {
393
- const functionNodes = [];
394
- const statementNodes = [];
395
- const blockNodes = [];
396
- const branchMap = new Map();
397
- // locationMap for chain LogicalExpression locations
398
- const locationMap = new Map();
399
-
400
- Util.visitAst(ast, {
401
-
402
- VariableDeclarator(node, parents) {
403
-
404
- // =============================================================
405
- // id: { type: 'Identifier', name: '__webpack_modules__' },
406
- const name = node.id && node.id.name;
407
- if (name === '__webpack_modules__' && node.init && node.init.properties) {
408
- // mark all as wrap function
409
- node.init.properties.forEach((it) => {
410
- it.value._wrapKey = it.key && it.key.value;
411
- });
412
- // console.log('==========================', node.type);
413
- // console.log(name, parents.length);
414
- }
415
- // =============================================================
416
-
417
- },
418
-
419
- // ===============================================================================
420
- // statements
421
-
422
- Statement: (node, parents) => {
423
- const reverseParents = [].concat(parents).reverse();
424
- statementNodes.push({
425
- node,
426
- reverseParents
427
- });
428
- },
429
-
430
-
431
- // ===============================================================================
432
- // functions
433
- // Function include FunctionDeclaration, ArrowFunctionExpression, FunctionExpression
434
- Function(node, parents) {
435
- const reverseParents = [].concat(parents).reverse();
436
- functionNodes.push({
437
- node,
438
- reverseParents
439
- });
440
- },
441
-
442
- // ===============================================================================
443
- // branches
444
-
445
- // `for` block, the count not equal to parent
446
- BlockStatement: (node, parents) => {
447
-
448
- // fix branch count: BRDA
449
- const reverseParents = [].concat(parents).reverse();
450
- blockNodes.push({
451
- node,
452
- reverseParents
453
- });
454
-
455
- },
456
-
457
- // default-arg assignment logic.
458
- // function default arguments
459
- AssignmentPattern: (node, parents) => {
460
- const group = createBranchGroup('AssignmentPattern', node, parents, branchMap);
461
- addBranch(group, node, locationMap);
462
- },
463
-
464
- // cond-expr a ternary expression. e.g.: x ? y : z
465
- // Ternary
466
- // var b = a ? 'consequent' : 'alternate';
467
- ConditionalExpression: (node, parents) => {
468
- const { consequent, alternate } = node;
469
- const group = createBranchGroup('ConditionalExpression', node, parents, branchMap);
470
- addBranch(group, consequent, locationMap);
471
- addBranch(group, alternate, locationMap);
472
- },
473
-
474
- // if an if statement; can also be else if.
475
- // An IF statement always has exactly two branches:
476
- // one where the condition is FALSE and one where the condition is TRUE
477
- IfStatement: (node, parents) => {
478
- const { consequent, alternate } = node;
479
- const group = createBranchGroup('IfStatement', node, parents, branchMap);
480
- addBranch(group, consequent, locationMap);
481
-
482
- // console.log('if type', consequent.type);
483
-
484
- if (alternate) {
485
- addBranch(group, alternate, locationMap);
486
- } else {
487
- // add none branch
488
- addNoneBranch(group);
489
- // no need update group end, there is no end
490
- }
491
- },
492
-
493
- // binary-expr a logical expression with a binary operand. e.g.: x && y
494
- // var b = a || b || c;
495
- // do not use BinaryExpression
496
- LogicalExpression: (node, parents) => {
497
- const { left, right } = node;
498
- // console.log(left.start, right.start);
499
-
500
- // could be same branch start
501
- // const da = arguments.length > 1 && typeof arguments[1] !== 'undefined' ? arguments[1] : true;
502
-
503
- let group;
504
- // link to same branch start if LogicalExpression
505
- const prevLocation = locationMap.get(node.start);
506
- if (prevLocation) {
507
- // console.log('link branch ==================', type);
508
- group = branchMap.get(prevLocation.branchKey);
509
- } else {
510
- group = createBranchGroup('LogicalExpression', node, parents, branchMap);
511
- addBranch(group, left, locationMap);
512
- }
513
-
514
- addBranch(group, right, locationMap);
515
-
516
- // console.log(group.locations.map((it) => it.start));
517
-
518
- // sort branch locations
519
- // a || b || c
520
- // first, left a and right c
521
- // then, left a and right b
522
- if (prevLocation) {
523
- const { locations } = group;
524
- locations.sort((a, b) => {
525
- return a.start - b.start;
526
- });
527
- // update group end after sorted
528
- const lastEnd = locations[locations.length - 1].end;
529
- if (lastEnd > group.end) {
530
- group.end = lastEnd;
531
- }
532
- }
533
- },
534
-
535
- // switch a switch statement.
536
- SwitchStatement: (node, parents) => {
537
- const group = createBranchGroup('SwitchStatement', node, parents, branchMap);
538
- const cases = node.cases;
539
- cases.forEach((switchCase) => {
540
- // console.log('switchCase', switchCase.start);
541
- addBranch(group, switchCase, locationMap);
542
- });
543
- }
544
-
545
- });
546
-
547
- return {
548
- functionNodes,
549
- statementNodes,
550
- blockNodes,
551
- branchMap
552
- };
553
- };
554
-
555
- const getRootFunctionState = (ast, coverageInfo) => {
556
- const rootState = {
557
- isFunction: true,
558
- count: 1
559
- };
560
- const rootRange = coverageInfo.rootRange;
561
- if (rootRange) {
562
- rootState.range = rootRange;
563
- rootState.count = rootRange.count;
564
-
565
- // could be not from 0
566
- // 0 881 { startOffset: 77, endOffset: 881,
567
- // const { start, end } = ast;
568
- // console.log(start, end, rootRange);
569
-
570
- }
571
- ast._state = rootState;
572
-
573
- return rootState;
574
- };
575
-
576
- const findFunctionRange = (item, coverageInfo) => {
577
-
578
- const { node, reverseParents } = item;
579
- const { start, end } = node;
580
-
581
- // try function start/end
582
- const functionRange = getFunctionRange(start, end, coverageInfo);
583
- if (functionRange) {
584
- return functionRange;
585
- }
586
-
587
- // try if class MethodDefinition
588
- // 0 is function self
589
- const parent = reverseParents[1];
590
- if (parent && parent.type === 'MethodDefinition') {
591
- return getFunctionRange(parent.start, parent.end, coverageInfo);
592
- }
593
-
594
- };
595
-
596
- const collectAstInfo = (ast, coverageInfo) => {
597
-
598
- const {
599
- functionNodes, statementNodes, blockNodes, branchMap
600
- } = collectNodes(ast);
601
-
602
- // root function state
603
- const rootState = getRootFunctionState(ast, coverageInfo);
604
-
605
- const functions = [];
606
- functionNodes.forEach((item) => {
607
- const { node } = item;
608
- const {
609
- start, end, id
610
- } = node;
611
- const bodyStart = node.body.start;
612
- const bodyEnd = node.body.end;
613
- const functionName = id && id.name;
614
- const isWrap = Boolean(node._wrapKey);
615
-
616
- const functionItem = {
617
- start,
618
- end,
619
- bodyStart,
620
- bodyEnd,
621
- functionName,
622
- count: rootState.count
623
- };
624
- if (isWrap) {
625
- functionItem.wrap = true;
626
- }
627
- functions.push(functionItem);
628
-
629
- const functionState = {
630
- isFunction: true,
631
- count: functionItem.count
632
- };
633
-
634
- const functionRange = findFunctionRange(item, coverageInfo);
635
- if (functionRange) {
636
- if (isWrap) {
637
- functionRange.wrap = true;
638
- }
639
- functionState.range = functionRange;
640
- functionState.count = functionRange.count;
641
- functionItem.count = functionRange.count;
642
- }
643
- node._state = functionState;
644
-
645
- // console.log(item.reverseParents.map((it) => it.type));
646
-
647
- });
648
-
649
- blockNodes.forEach((item) => {
650
- addNodeCount(item);
651
- });
652
-
653
- const branches = generateBranches(branchMap);
654
- const statements = generateStatements(statementNodes);
655
-
656
- return {
657
- functions,
658
- branches,
659
- statements
660
- };
661
-
662
- };
663
-
664
- module.exports = {
665
- collectAstInfo
666
- };
1
+ const Util = require('../utils/util.js');
2
+
3
+ const BranchTypes = {
4
+ ConditionalExpression: 'ConditionalExpression',
5
+ LogicalExpression: 'LogicalExpression',
6
+ IfStatement: 'IfStatement',
7
+ SwitchStatement: 'SwitchStatement',
8
+ AssignmentPattern: 'AssignmentPattern'
9
+ };
10
+
11
+ const getParentFunctionState = (reverseParents) => {
12
+ const parentFunction = reverseParents.find((it) => it._state && it._state.isFunction);
13
+ if (!parentFunction) {
14
+ return;
15
+ }
16
+ return parentFunction._state;
17
+ };
18
+
19
+ const getParentCount = (reverseParents, functionCount) => {
20
+ // parent count
21
+ const parent = reverseParents.find((it) => it._state);
22
+ if (parent) {
23
+ return parent._state.count;
24
+ }
25
+ // root function count
26
+ return functionCount;
27
+ };
28
+
29
+ const getFunctionRange = (start, end, coverageInfo) => {
30
+
31
+ const {
32
+ functionMap, functionNameMap, functionUncoveredRanges
33
+ } = coverageInfo;
34
+
35
+ // exact matched in functionMap
36
+ const range = functionMap.get(start);
37
+ if (range) {
38
+ return range;
39
+ }
40
+ // exact matched in functionNameMap
41
+ const nameRange = functionNameMap.get(start);
42
+ if (nameRange) {
43
+ return nameRange;
44
+ }
45
+
46
+ // find in uncoveredRanges
47
+ return Util.findInRanges(start, end, functionUncoveredRanges, 'startOffset', 'endOffset');
48
+ };
49
+
50
+ const getFunctionBlock = (start, end, functionState) => {
51
+ if (!functionState) {
52
+ return;
53
+ }
54
+
55
+ const { range } = functionState;
56
+ if (!range) {
57
+ return;
58
+ }
59
+
60
+ const {
61
+ blockMap, blockUncoveredRanges, blockCoveredRanges
62
+ } = range;
63
+
64
+ if (!blockMap) {
65
+ return;
66
+ }
67
+
68
+ const block = blockMap.get(start);
69
+ if (block) {
70
+ return block;
71
+ }
72
+
73
+ const uncoveredBlock = Util.findInRanges(start, end, blockUncoveredRanges, 'startOffset', 'endOffset');
74
+ if (uncoveredBlock) {
75
+ return uncoveredBlock;
76
+ }
77
+
78
+ // the block is not exact correct, if there is a block wrapped
79
+ // [x8]{ var a = b || [x4]c }, b is no block, but it can be found in x8 block
80
+ const coveredBlocks = blockCoveredRanges.filter((it) => start >= it.startOffset && end <= it.endOffset);
81
+ if (coveredBlocks.length) {
82
+ return coveredBlocks.pop();
83
+ }
84
+
85
+ };
86
+
87
+ const addNodeCount = (item) => {
88
+
89
+ const { node, reverseParents } = item;
90
+
91
+ if (node._state) {
92
+ return;
93
+ }
94
+
95
+ const { start, end } = node;
96
+ const functionState = getParentFunctionState(reverseParents);
97
+ const block = getFunctionBlock(start, end, functionState);
98
+ if (block) {
99
+ node._state = {
100
+ count: block.count
101
+ };
102
+ }
103
+
104
+ };
105
+
106
+ // =======================================================================================
107
+
108
+ const createBranchGroup = (type, node, parents, branchMap) => {
109
+ const { start, end } = node;
110
+ // clone and reverse parents
111
+ const reverseParents = [].concat(parents).reverse();
112
+ const group = {
113
+ type,
114
+
115
+ start,
116
+ // could be updated if multiple locations
117
+ end,
118
+
119
+ locations: [],
120
+ reverseParents
121
+ };
122
+
123
+ if (type === BranchTypes.LogicalExpression) {
124
+ // && or ||
125
+ group.operator = node.operator;
126
+ }
127
+
128
+ // could be same start
129
+ const branchKey = `${start}_${end}`;
130
+
131
+ branchMap.set(branchKey, group);
132
+ return group;
133
+ };
134
+
135
+ const addBranch = (group, node, locationMap) => {
136
+ const { start, end } = node;
137
+
138
+ const branchKey = `${group.start}_${group.end}`;
139
+
140
+ const branchInfo = {
141
+ // for get previous group for LogicalExpression
142
+ branchKey,
143
+ start,
144
+ end,
145
+ // branch count default to 0
146
+ count: 0
147
+ };
148
+
149
+ group.locations.push(branchInfo);
150
+
151
+ // update group end
152
+ if (end > group.end) {
153
+ group.end = end;
154
+ }
155
+
156
+ locationMap.set(start, branchInfo);
157
+
158
+ return branchInfo;
159
+ };
160
+
161
+ const addNoneBranch = (group) => {
162
+ group.locations.push({
163
+ none: true,
164
+ count: 0
165
+ });
166
+ };
167
+
168
+ // =======================================================================================
169
+
170
+ const updateBlockLocations = (locations) => {
171
+ const noBlockList = [];
172
+ let blockCount = 0;
173
+ locations.forEach((item) => {
174
+ if (item.block) {
175
+ item.count = item.block.count;
176
+ blockCount += item.count;
177
+ return;
178
+ }
179
+ noBlockList.push(item);
180
+ });
181
+
182
+ return {
183
+ noBlockList,
184
+ blockCount
185
+ };
186
+ };
187
+
188
+ // const a = tf1 ? 'true' : 'false';
189
+ const ConditionalExpression = (group, parentCount) => {
190
+ const { noBlockList, blockCount } = updateBlockLocations(group.locations);
191
+ if (!noBlockList.length) {
192
+ return;
193
+ }
194
+ let count = parentCount - blockCount;
195
+ noBlockList.forEach((item) => {
196
+ item.count = count;
197
+ count = 0;
198
+ });
199
+ };
200
+
201
+ const IfStatement = (group, parentCount) => {
202
+ const { noBlockList, blockCount } = updateBlockLocations(group.locations);
203
+ if (!noBlockList.length) {
204
+ return;
205
+ }
206
+ // console.log(parentCount, 'uncovered list', noBlockList.length, group.start);
207
+ let count = parentCount - blockCount;
208
+ noBlockList.forEach((item) => {
209
+ item.count = count;
210
+ count = 0;
211
+ });
212
+
213
+ };
214
+
215
+ // const b = tf2 || tf1 || a;
216
+ const LogicalExpression = (group, parentCount) => {
217
+
218
+ // from left to right
219
+ group.locations.forEach((item, i) => {
220
+ if (item.block) {
221
+ item.count = item.block.count;
222
+ } else {
223
+ item.count = parentCount;
224
+ }
225
+ });
226
+
227
+ };
228
+
229
+ const SwitchStatement = (group, parentCount) => {
230
+ const { noBlockList, blockCount } = updateBlockLocations(group.locations);
231
+ if (!noBlockList.length) {
232
+ return;
233
+ }
234
+ const count = parentCount - blockCount;
235
+ noBlockList.forEach((item) => {
236
+ item.count = count;
237
+ });
238
+ };
239
+
240
+ const AssignmentPattern = (group, parentCount) => {
241
+ group.locations.forEach((item) => {
242
+ item.count = parentCount;
243
+ });
244
+ };
245
+
246
+ // =======================================================================================
247
+
248
+ const updateBranchCount = (group) => {
249
+
250
+ const {
251
+ type, locations, reverseParents
252
+ } = group;
253
+
254
+ const functionState = getParentFunctionState(reverseParents);
255
+
256
+ const functionCount = functionState.count;
257
+
258
+ // default is 0, no need continue
259
+ if (functionCount === 0) {
260
+ return;
261
+ }
262
+
263
+ // parent is block statement or function
264
+ let parentCount = getParentCount(reverseParents, functionCount);
265
+ // parent is group range
266
+ const groupBlock = getFunctionBlock(group.start, group.end, functionState);
267
+ if (groupBlock) {
268
+ parentCount = groupBlock.count;
269
+ }
270
+
271
+ // calculate branches count
272
+ locations.forEach((item) => {
273
+ const {
274
+ start, end, none
275
+ } = item;
276
+ if (none) {
277
+ return;
278
+ }
279
+ item.block = getFunctionBlock(start, end, functionState);
280
+ });
281
+
282
+
283
+ const handlers = {
284
+ ConditionalExpression,
285
+ LogicalExpression,
286
+ IfStatement,
287
+ SwitchStatement,
288
+ AssignmentPattern
289
+ };
290
+
291
+ const handler = handlers[type];
292
+ if (handler) {
293
+ handler(group, parentCount);
294
+ }
295
+
296
+ };
297
+
298
+ const generateBranches = (branchMap) => {
299
+
300
+ // calculate count for all branches
301
+ branchMap.forEach((group) => {
302
+ updateBranchCount(group);
303
+ });
304
+
305
+ // init branches
306
+ const branches = [];
307
+ branchMap.forEach((group) => {
308
+
309
+ // add start/end for none with group start/end
310
+ group.locations.forEach((item) => {
311
+ if (item.none) {
312
+ item.start = group.start;
313
+ item.end = group.end;
314
+ }
315
+ });
316
+
317
+ branches.push({
318
+ type: group.type,
319
+ start: group.start,
320
+ end: group.end,
321
+ locations: group.locations
322
+ });
323
+ });
324
+
325
+ // sort branches
326
+ branches.sort((a, b) => {
327
+ return a.start - b.start;
328
+ });
329
+
330
+ return branches;
331
+ };
332
+
333
+ // =======================================================================================
334
+ // All programs in JavaScript are made of statements and they end with semicolons (;) except block statements which is used to group zero or more statements.
335
+ // Statements are just perform some actions but do not produce any value or output whereas expressions return some value.
336
+ // Expressions return value, statements do not.
337
+ const generateStatements = (statementNodes) => {
338
+
339
+ statementNodes.forEach((item) => {
340
+ const {
341
+ node,
342
+ reverseParents
343
+ } = item;
344
+
345
+ const { start, end } = node;
346
+
347
+ item.count = 1;
348
+
349
+ // isFunction
350
+ if (node._state) {
351
+ item.count = node._state.count;
352
+ return;
353
+ }
354
+
355
+ const functionState = getParentFunctionState(reverseParents);
356
+ if (!functionState) {
357
+ // there is a root range, it impossible not found
358
+ return;
359
+ }
360
+
361
+ // function uncovered
362
+ if (functionState.count === 0) {
363
+ item.count = 0;
364
+ return;
365
+ }
366
+
367
+ const block = getFunctionBlock(start, end, functionState);
368
+ if (block) {
369
+ item.count = block.count;
370
+ return;
371
+ }
372
+
373
+ item.count = functionState.count;
374
+
375
+ });
376
+
377
+ // remove block statement
378
+ statementNodes = statementNodes.filter((item) => {
379
+ return item.node.type !== 'BlockStatement';
380
+ });
381
+
382
+ const statements = statementNodes.map((item) => {
383
+ const {
384
+ node,
385
+ count
386
+ } = item;
387
+ const { start, end } = node;
388
+ return {
389
+ start,
390
+ end,
391
+ count
392
+ };
393
+ });
394
+
395
+ return statements;
396
+ };
397
+
398
+ // =======================================================================================
399
+
400
+ const collectNodes = (ast) => {
401
+ const functionNodes = [];
402
+ const statementNodes = [];
403
+ const blockNodes = [];
404
+ const branchMap = new Map();
405
+ // locationMap for chain LogicalExpression locations
406
+ const locationMap = new Map();
407
+
408
+ Util.visitAst(ast, {
409
+
410
+ VariableDeclarator(node, parents) {
411
+
412
+ // =============================================================
413
+ // id: { type: 'Identifier', name: '__webpack_modules__' },
414
+ const name = node.id && node.id.name;
415
+ if (name === '__webpack_modules__' && node.init && node.init.properties) {
416
+ // mark all as wrap function
417
+ node.init.properties.forEach((it) => {
418
+ it.value._wrapKey = it.key && it.key.value;
419
+ });
420
+ // console.log('==========================', node.type);
421
+ // console.log(name, parents.length);
422
+ }
423
+ // =============================================================
424
+
425
+ },
426
+
427
+ // ===============================================================================
428
+ // statements
429
+
430
+ Statement: (node, parents) => {
431
+ const reverseParents = [].concat(parents).reverse();
432
+ statementNodes.push({
433
+ node,
434
+ reverseParents
435
+ });
436
+ },
437
+
438
+
439
+ // ===============================================================================
440
+ // functions
441
+ // Function include FunctionDeclaration, ArrowFunctionExpression, FunctionExpression
442
+ Function(node, parents) {
443
+ const reverseParents = [].concat(parents).reverse();
444
+ functionNodes.push({
445
+ node,
446
+ reverseParents
447
+ });
448
+ },
449
+
450
+ // ===============================================================================
451
+ // branches
452
+
453
+ // `for` block, the count not equal to parent
454
+ BlockStatement: (node, parents) => {
455
+
456
+ // fix branch count: BRDA
457
+ const reverseParents = [].concat(parents).reverse();
458
+ blockNodes.push({
459
+ node,
460
+ reverseParents
461
+ });
462
+
463
+ },
464
+
465
+ // default-arg assignment logic.
466
+ // function default arguments
467
+ AssignmentPattern: (node, parents) => {
468
+ const group = createBranchGroup(BranchTypes.AssignmentPattern, node, parents, branchMap);
469
+ addBranch(group, node, locationMap);
470
+ },
471
+
472
+ // cond-expr a ternary expression. e.g.: x ? y : z
473
+ // Ternary
474
+ // var b = a ? 'consequent' : 'alternate';
475
+ ConditionalExpression: (node, parents) => {
476
+ const { consequent, alternate } = node;
477
+ const group = createBranchGroup(BranchTypes.ConditionalExpression, node, parents, branchMap);
478
+ addBranch(group, consequent, locationMap);
479
+ addBranch(group, alternate, locationMap);
480
+ },
481
+
482
+ // if an if statement; can also be else if.
483
+ // An IF statement always has exactly two branches:
484
+ // one where the condition is FALSE and one where the condition is TRUE
485
+ IfStatement: (node, parents) => {
486
+ const { consequent, alternate } = node;
487
+ const group = createBranchGroup(BranchTypes.IfStatement, node, parents, branchMap);
488
+ addBranch(group, consequent, locationMap);
489
+
490
+ // console.log('if type', consequent.type);
491
+
492
+ if (alternate) {
493
+ addBranch(group, alternate, locationMap);
494
+ } else {
495
+ // add none branch
496
+ addNoneBranch(group);
497
+ // no need update group end, there is no end
498
+ }
499
+ },
500
+
501
+ // binary-expr a logical expression with a binary operand. e.g.: x && y
502
+ // var b = a || b || c;
503
+ // do not use BinaryExpression
504
+ LogicalExpression: (node, parents) => {
505
+ const { left, right } = node;
506
+ // console.log(left.start, right.start);
507
+
508
+ // could be same branch start
509
+ // const da = arguments.length > 1 && typeof arguments[1] !== 'undefined' ? arguments[1] : true;
510
+
511
+ let group;
512
+ // link to same branch start if LogicalExpression
513
+ const prevLocation = locationMap.get(node.start);
514
+ if (prevLocation) {
515
+ // console.log('link branch ==================', type);
516
+ group = branchMap.get(prevLocation.branchKey);
517
+ } else {
518
+ group = createBranchGroup(BranchTypes.LogicalExpression, node, parents, branchMap);
519
+ addBranch(group, left, locationMap);
520
+ }
521
+
522
+ addBranch(group, right, locationMap);
523
+
524
+ // console.log(group.locations.map((it) => it.start));
525
+
526
+ // sort branch locations
527
+ // a || b || c
528
+ // first, left a and right c
529
+ // then, left a and right b
530
+ if (prevLocation) {
531
+ const { locations } = group;
532
+ locations.sort((a, b) => {
533
+ return a.start - b.start;
534
+ });
535
+ // update group end after sorted
536
+ const lastEnd = locations[locations.length - 1].end;
537
+ if (lastEnd > group.end) {
538
+ group.end = lastEnd;
539
+ }
540
+ }
541
+ },
542
+
543
+ // switch a switch statement.
544
+ SwitchStatement: (node, parents) => {
545
+ const group = createBranchGroup(BranchTypes.SwitchStatement, node, parents, branchMap);
546
+ const cases = node.cases;
547
+ cases.forEach((switchCase) => {
548
+ // console.log('switchCase', switchCase.start);
549
+ addBranch(group, switchCase, locationMap);
550
+ });
551
+ }
552
+
553
+ });
554
+
555
+ return {
556
+ functionNodes,
557
+ statementNodes,
558
+ blockNodes,
559
+ branchMap
560
+ };
561
+ };
562
+
563
+ const getRootFunctionState = (ast, coverageInfo) => {
564
+ const rootState = {
565
+ isFunction: true,
566
+ count: 1
567
+ };
568
+ const rootRange = coverageInfo.rootRange;
569
+ if (rootRange) {
570
+ rootState.range = rootRange;
571
+ rootState.count = rootRange.count;
572
+
573
+ // could be not from 0
574
+ // 0 881 { startOffset: 77, endOffset: 881,
575
+ // const { start, end } = ast;
576
+ // console.log(start, end, rootRange);
577
+
578
+ }
579
+ ast._state = rootState;
580
+
581
+ return rootState;
582
+ };
583
+
584
+ const findFunctionRange = (item, coverageInfo) => {
585
+
586
+ const { node, reverseParents } = item;
587
+ const { start, end } = node;
588
+
589
+ // try function start/end
590
+ const functionRange = getFunctionRange(start, end, coverageInfo);
591
+ if (functionRange) {
592
+ return functionRange;
593
+ }
594
+
595
+ // try if class MethodDefinition
596
+ // 0 is function self
597
+ const parent = reverseParents[1];
598
+ if (parent && parent.type === 'MethodDefinition') {
599
+ return getFunctionRange(parent.start, parent.end, coverageInfo);
600
+ }
601
+
602
+ };
603
+
604
+ const collectAstInfo = (ast, coverageInfo) => {
605
+
606
+ const {
607
+ functionNodes, statementNodes, blockNodes, branchMap
608
+ } = collectNodes(ast);
609
+
610
+ // root function state
611
+ const rootState = getRootFunctionState(ast, coverageInfo);
612
+
613
+ const functions = [];
614
+ functionNodes.forEach((item) => {
615
+ const { node } = item;
616
+ const {
617
+ start, end, id
618
+ } = node;
619
+ const bodyStart = node.body.start;
620
+ const bodyEnd = node.body.end;
621
+ const functionName = id && id.name;
622
+ const isWrap = Boolean(node._wrapKey);
623
+
624
+ const functionItem = {
625
+ start,
626
+ end,
627
+ bodyStart,
628
+ bodyEnd,
629
+ functionName,
630
+ count: rootState.count
631
+ };
632
+ if (isWrap) {
633
+ functionItem.wrap = true;
634
+ }
635
+ functions.push(functionItem);
636
+
637
+ const functionState = {
638
+ isFunction: true,
639
+ count: functionItem.count
640
+ };
641
+
642
+ const functionRange = findFunctionRange(item, coverageInfo);
643
+ if (functionRange) {
644
+ if (isWrap) {
645
+ functionRange.wrap = true;
646
+ }
647
+ functionState.range = functionRange;
648
+ functionState.count = functionRange.count;
649
+ functionItem.count = functionRange.count;
650
+ }
651
+ node._state = functionState;
652
+
653
+ // console.log(item.reverseParents.map((it) => it.type));
654
+
655
+ });
656
+
657
+ blockNodes.forEach((item) => {
658
+ addNodeCount(item);
659
+ });
660
+
661
+ const branches = generateBranches(branchMap);
662
+ const statements = generateStatements(statementNodes);
663
+
664
+ return {
665
+ functions,
666
+ branches,
667
+ statements
668
+ };
669
+
670
+ };
671
+
672
+ module.exports = {
673
+ BranchTypes,
674
+ collectAstInfo
675
+ };