monocart-coverage-reports 1.1.0 → 2.0.0

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.
@@ -4,11 +4,12 @@ const Util = require('../utils/util.js');
4
4
  const decodeMappings = require('../utils/decode-mappings.js');
5
5
 
6
6
  // position mapping for conversion between offset and line/column
7
- const PositionMapping = require('./position-mapping.js');
7
+ const { Locator } = require('monocart-formatter/node');
8
8
  const findOriginalRange = require('./find-original-range.js');
9
+ const { getJsAstInfo, getCssAstInfo } = require('./ast.js');
9
10
 
10
- const { dedupeCountRanges } = require('./dedupe.js');
11
- const { getSourceType, initSourceMapSourcePath } = require('./source-path.js');
11
+ const { dedupeCountRanges } = require('../utils/dedupe.js');
12
+ const { getSourceType, initSourceMapSourcePath } = require('../utils/source-path.js');
12
13
 
13
14
  const InfoLine = require('./info-line.js');
14
15
  const InfoBranch = require('./info-branch.js');
@@ -16,6 +17,98 @@ const InfoFunction = require('./info-function.js');
16
17
 
17
18
  // ========================================================================================================
18
19
 
20
+ const calculateV8Functions = (functions) => {
21
+
22
+ const v8Functions = {
23
+ total: 0,
24
+ covered: 0
25
+ };
26
+
27
+ functions.forEach((fn) => {
28
+ v8Functions.total += 1;
29
+ if (fn.count > 0) {
30
+ v8Functions.covered += 1;
31
+ }
32
+ });
33
+
34
+ return v8Functions;
35
+ };
36
+
37
+ const calculateV8Branches = (branches) => {
38
+ const v8Branches = {
39
+ total: 0,
40
+ covered: 0
41
+ };
42
+
43
+ branches.forEach((branch) => {
44
+ branch.locations.forEach((location) => {
45
+ v8Branches.total += 1;
46
+ if (location.count > 0) {
47
+ v8Branches.covered += 1;
48
+ }
49
+ });
50
+ });
51
+
52
+ return v8Branches;
53
+ };
54
+
55
+ const calculateV8Lines = (lines, blankCount, commentCount) => {
56
+ const v8Lines = {
57
+ total: 0,
58
+ covered: 0,
59
+ blank: blankCount,
60
+ comment: commentCount
61
+ };
62
+
63
+ lines.forEach((ln) => {
64
+ v8Lines.total += 1;
65
+ // full line covered
66
+ if (ln.covered) {
67
+ v8Lines.covered += 1;
68
+ }
69
+ });
70
+
71
+ return v8Lines;
72
+ };
73
+
74
+ const createEmptyCoverageInfo = () => {
75
+
76
+ // data for v8 UI
77
+ const data = {
78
+ bytes: [],
79
+ functions: [],
80
+ branches: []
81
+ };
82
+
83
+ const branches = [];
84
+
85
+ const functions = [];
86
+
87
+ const lines = [];
88
+ // line 1 based
89
+ const lineMap = new Map();
90
+
91
+ const blankCount = 0;
92
+ const commentCount = 0;
93
+
94
+
95
+ return {
96
+ // v8
97
+ data,
98
+
99
+ // istanbul
100
+ branches,
101
+
102
+ functions,
103
+
104
+ lines,
105
+ lineMap,
106
+ blankCount,
107
+ commentCount
108
+
109
+ };
110
+ };
111
+
19
112
  // istanbul coverage format
20
113
  /**
21
114
  * * `path` - the file path for which coverage is being tracked
@@ -26,243 +119,387 @@ const InfoFunction = require('./info-function.js');
26
119
  * * `f` - hit count for functions
27
120
  * * `b` - hit count for branches
28
121
  */
29
- const getFileCoverage = (sourcePath, inputData) => {
122
+ const collectFileCoverage = (item, coverageInfo, state) => {
123
+
124
+ const { sourcePath } = item;
30
125
 
31
126
  const {
32
- lines, functions, branches, ranges
33
- } = inputData;
127
+ // v8
128
+ data,
129
+
130
+ // istanbul
131
+ branches,
34
132
 
35
- // v8 ranges
36
- inputData.ranges = dedupeCountRanges(ranges);
133
+ functions,
37
134
 
38
- // istanbul coverage
39
- const coverage = {
135
+ lines,
136
+ blankCount,
137
+ commentCount
138
+
139
+ } = coverageInfo;
140
+
141
+ // ==========================================
142
+ // v8
143
+ data.bytes = dedupeCountRanges(data.bytes);
144
+ item.data = data;
145
+ item.summary = {
146
+ functions: calculateV8Functions(functions),
147
+ branches: calculateV8Branches(branches),
148
+ lines: calculateV8Lines(lines, blankCount, commentCount)
149
+ };
150
+
151
+ // ==========================================
152
+ // istanbul
153
+ const istanbulCoverage = {
40
154
  path: sourcePath,
41
155
 
42
156
  statementMap: {},
43
- s: {},
44
-
45
157
  fnMap: {},
46
- f: {},
47
-
48
158
  branchMap: {},
159
+
160
+ s: {},
161
+ f: {},
49
162
  b: {}
50
163
  };
51
164
 
52
165
  lines.forEach((line, index) => {
53
- coverage.statementMap[`${index}`] = line.generate();
54
- coverage.s[`${index}`] = line.count;
166
+ istanbulCoverage.statementMap[`${index}`] = line.generate();
167
+ istanbulCoverage.s[`${index}`] = line.count;
55
168
  });
56
169
 
57
170
  functions.forEach((fn, index) => {
58
- coverage.fnMap[`${index}`] = fn.generate();
59
- coverage.f[`${index}`] = fn.count;
171
+ istanbulCoverage.fnMap[`${index}`] = fn.generate();
172
+ istanbulCoverage.f[`${index}`] = fn.count;
60
173
  });
61
174
 
62
175
  branches.forEach((branch, index) => {
63
- coverage.branchMap[`${index}`] = branch.generate();
64
- coverage.b[`${index}`] = [branch.count];
176
+ istanbulCoverage.branchMap[`${index}`] = branch.generate();
177
+ istanbulCoverage.b[`${index}`] = branch.generateCounts();
65
178
  });
66
179
 
67
- return coverage;
180
+ // append to dist file state
181
+ state.coverageData[sourcePath] = istanbulCoverage;
182
+
68
183
  };
69
184
 
70
185
  // ========================================================================================================
71
186
 
72
- const setLineCount = (lineMap, line, count) => {
73
- const lineInfo = lineMap[line];
74
- if (lineInfo) {
75
- lineInfo.count = count;
76
- }
77
- };
187
+ // https://github.com/demurgos/v8-coverage
188
+ /**
189
+ * @ranges is always non-empty. The first range is called the "root range".
190
+ * @isBlockCoverage indicates if the function has block coverage information
191
+ * @false means that there is a single range and its count is the number of times the function was called.
192
+ * @true means that the ranges form a tree of blocks representing how many times each statement or expression inside was executed.
193
+ * It detects skipped or repeated statements. The root range counts the number of function calls.
194
+ *
195
+ * @functionName can be an empty string. This is common for the FunctionCov representing the whole module.
196
+ */
197
+ // if you have a line of code that says `var x= 10; console.log(x);` that's one line and 2 statements.
78
198
 
79
- const setSingleLineCount = (lineMap, sLoc, eLoc, count) => {
80
- // nothing between
81
- if (sLoc.column >= eLoc.column) {
82
- return;
83
- }
199
+ const addJsLineCoverage = (state, range) => {
84
200
 
85
- // sometimes column > length
86
- if (sLoc.column <= sLoc.indent && eLoc.column >= eLoc.length) {
87
- // console.log('single', sLoc.line);
88
- setLineCount(lineMap, sLoc.line, count);
89
- }
201
+ const { locator, coverageInfo } = state;
202
+
203
+ const { data, lineMap } = coverageInfo;
204
+
205
+ const {
206
+ startOffset, endOffset, count
207
+ } = range;
208
+
209
+ // add bytes range
210
+ data.bytes.push({
211
+ // index,
212
+ start: startOffset,
213
+ end: endOffset,
214
+ count
215
+ });
216
+
217
+ const sLoc = locator.offsetToLocation(startOffset);
218
+ const eLoc = locator.offsetToLocation(endOffset);
219
+
220
+ // update lines coverage
221
+ const lines = Util.getRangeLines(sLoc, eLoc);
222
+
223
+ lines.forEach((it) => {
224
+ const line = lineMap.get(it.line);
225
+ if (!line) {
226
+ return;
227
+ }
228
+
229
+ // from outside into inside, uncovered is certain
230
+ // default is covered
231
+ if (it.entire) {
232
+ line.covered = count > 0;
233
+ line.count = count;
234
+ } else {
235
+ if (count > 0) {
236
+ line.count = count;
237
+ } else {
238
+ // not covered if any count = 0
239
+ line.covered = false;
240
+ }
241
+ }
242
+
243
+ // if (!line.history) {
244
+ // line.history = [];
245
+ // }
246
+ // line.history.push(`${it.entire}-${count}`);
247
+
248
+ });
249
+
250
+ // if (state.sourcePath.endsWith('component.js')) {
251
+ // console.log('===========================================', state.sourcePath);
252
+ // console.log(coverageInfo.lines);
253
+ // }
90
254
 
91
255
  };
92
256
 
93
- const updateLinesCount = (lineMap, sLoc, eLoc, count) => {
257
+ const addCssLineCoverage = (state, range) => {
94
258
 
95
- // single line
96
- if (sLoc.line === eLoc.line) {
97
- setSingleLineCount(lineMap, sLoc, eLoc, count);
98
- return;
99
- }
259
+ const { coverageInfo, locator } = state;
100
260
 
101
- const firstELoc = {
102
- ... sLoc,
103
- column: sLoc.length
104
- };
105
- setSingleLineCount(lineMap, sLoc, firstELoc, count);
261
+ const { lineMap, data } = coverageInfo;
262
+
263
+ const {
264
+ start, end, count
265
+ } = range;
266
+
267
+ if (count > 0) {
268
+ const sLoc = locator.offsetToLocation(start);
269
+ const eLoc = locator.offsetToLocation(end);
106
270
 
107
- for (let i = sLoc.line + 1; i < eLoc.line; i++) {
108
- setLineCount(lineMap, i, count);
271
+ // covered css lines
272
+ const lines = Util.getRangeLines(sLoc, eLoc);
273
+ lines.forEach((it) => {
274
+ if (!it.entire) {
275
+ return;
276
+ }
277
+ const line = lineMap.get(it.line);
278
+ if (line) {
279
+ // default is uncovered
280
+ // count always 1, covered is certain
281
+ line.covered = true;
282
+ }
283
+ });
109
284
  }
110
285
 
111
- const lastSLoc = {
112
- ... eLoc,
113
- column: eLoc.indent
114
- };
115
- setSingleLineCount(lineMap, lastSLoc, eLoc, count);
286
+ // add bytes range
287
+ data.bytes.push(range);
288
+
289
+ };
290
+
291
+ // ========================================================================================================
116
292
 
293
+ const updateOffsetToLocation = (locator, loc) => {
294
+ const sLoc = locator.offsetToLocation(loc.start);
295
+ loc.start = {
296
+ line: sLoc.line,
297
+ column: sLoc.column
298
+ };
299
+ const eLoc = locator.offsetToLocation(loc.end);
300
+ loc.end = {
301
+ line: eLoc.line,
302
+ column: eLoc.column
303
+ };
117
304
  };
118
305
 
119
- const initFileCoverage = (positionMapping, count) => {
306
+ const handleFunctionsCoverage = (state) => {
120
307
 
121
- // istanbul
122
- const lines = [];
123
- const functions = [];
124
- const branches = [];
125
- // v8
126
- const ranges = [];
308
+ // functions only for js
309
+ if (!state.js) {
310
+ // use css rules data for functions
311
+ return;
312
+ }
127
313
 
128
- // add all lines
129
- const lineMap = {};
314
+ const {
315
+ locator, coverageInfo, astInfo
316
+ } = state;
130
317
 
131
- const { commentedLines, blankLines } = positionMapping;
132
- positionMapping.lines.forEach((it) => {
133
- // exclude comments and blanks
134
- if (commentedLines.includes(it.line) || blankLines.includes(it.line)) {
135
- return;
136
- }
137
- // line 1-base
138
- const line = it.line + 1;
139
- const lineInfo = new InfoLine(line, it.length, count);
140
- lineMap[line] = lineInfo;
141
- lines.push(lineInfo);
318
+ const v8Functions = coverageInfo.data.functions;
319
+ const istanbulFunctions = coverageInfo.functions;
320
+
321
+ astInfo.functions.forEach((it) => {
322
+ const {
323
+ start, end, functionName, count
324
+ } = it;
325
+
326
+ v8Functions.push({
327
+ start,
328
+ end,
329
+ count
330
+ });
331
+
332
+ const loc = {
333
+ start,
334
+ end
335
+ };
336
+ updateOffsetToLocation(locator, loc);
337
+ const functionInfo = new InfoFunction(loc, count, functionName);
338
+ istanbulFunctions.push(functionInfo);
142
339
  });
143
340
 
144
- return {
145
- lines, functions, branches, ranges, lineMap
146
- };
341
+ // if (state.sourcePath.endsWith('store.js')) {
342
+ // console.log('==========================================================');
343
+ // console.log(astInfo.functions);
344
+ // console.log(functions);
345
+ // }
346
+
147
347
  };
148
348
 
349
+ const handleBranchesCoverage = (state) => {
149
350
 
150
- // https://github.com/demurgos/v8-coverage
151
- /**
152
- * @ranges is always non-empty. The first range is called the "root range".
153
- * @isBlockCoverage indicates if the function has block coverage information
154
- * @false means that there is a single range and its count is the number of times the function was called.
155
- * @true means that the ranges form a tree of blocks representing how many times each statement or expression inside was executed.
156
- * It detects skipped or repeated statements. The root range counts the number of function calls.
157
- *
158
- * @functionName can be an empty string. This is common for the FunctionCov representing the whole module.
159
- */
160
- const addJsCoverage = (coverage, block, range, index, positionMapping) => {
351
+ // functions only for js
352
+ if (!state.js) {
353
+ return;
354
+ }
161
355
 
162
356
  const {
163
- functions, branches, ranges, lineMap
164
- } = coverage;
357
+ locator, coverageInfo, astInfo
358
+ } = state;
165
359
 
166
- const { isBlockCoverage, functionName } = block;
167
- const {
168
- startOffset, endOffset, count
169
- } = range;
360
+ const v8Branches = coverageInfo.data.branches;
361
+ const isTanbulBranches = coverageInfo.branches;
362
+ astInfo.branches.forEach((it) => {
170
363
 
171
- ranges.push({
172
- start: startOffset,
173
- end: endOffset,
174
- count
175
- });
364
+ const branchType = it.type;
365
+ const branchLoc = {
366
+ ... it.loc
367
+ };
368
+ updateOffsetToLocation(locator, branchLoc);
176
369
 
177
- const sLoc = positionMapping.offsetToLocation(startOffset);
178
- const eLoc = positionMapping.offsetToLocation(endOffset);
370
+ // locations
371
+ // [ { start:{line,column}, end:{line,column}, count }, ...]
372
+ const locations = it.locations.map((oLoc) => {
373
+ const newLoc = {
374
+ ... oLoc
375
+ };
376
+ if (newLoc.none) {
179
377
 
180
- // line, column
181
- updateLinesCount(lineMap, sLoc, eLoc, count);
378
+ // the branch count is not very accurate
379
+ v8Branches.push({
380
+ start: it.loc.start,
381
+ end: it.loc.end,
382
+ count: oLoc.count,
383
+ none: true
384
+ });
182
385
 
183
- if (isBlockCoverage) {
184
- if (index === 0) {
185
- // The root range counts the number of function calls
186
- functions.push(new InfoFunction(sLoc, eLoc, count, functionName));
187
- }
386
+ return newLoc;
387
+ }
188
388
 
189
- // index 0 not really a branch, but for covered whole function
190
- branches.push(new InfoBranch(sLoc, eLoc, count));
389
+ // the branch count is not very accurate
390
+ v8Branches.push({
391
+ start: oLoc.start,
392
+ end: oLoc.end,
393
+ count: oLoc.count
394
+ });
191
395
 
192
- } else {
193
- functions.push(new InfoFunction(sLoc, eLoc, count, functionName));
396
+ updateOffsetToLocation(locator, newLoc);
397
+ return newLoc;
398
+ });
399
+
400
+ const branchInfo = new InfoBranch(branchLoc, locations, branchType);
401
+ isTanbulBranches.push(branchInfo);
402
+ });
194
403
 
195
- // possible have branches in the function but no information for it
196
404
 
197
- }
198
405
  };
199
406
 
200
- const addCssCoverage = (coverage, range, positionMapping) => {
201
- const { lineMap } = coverage;
407
+ const handleLinesCoverage = (state) => {
408
+
409
+ const {
410
+ js, locator, coverageInfo
411
+ } = state;
202
412
 
203
- const { start, end } = range;
413
+ const lines = coverageInfo.lines;
414
+ const lineMap = coverageInfo.lineMap;
204
415
 
205
- const sLoc = positionMapping.offsetToLocation(start);
206
- const eLoc = positionMapping.offsetToLocation(end);
416
+ // init lines
417
+ let blankCount = 0;
418
+ let commentCount = 0;
207
419
 
208
- // line, column
209
- updateLinesCount(lineMap, sLoc, eLoc, 1);
420
+ // js: 1 (functions include all uncovered)
421
+ // css: 0 (ranges include all covered)
422
+ const baseLineCount = js ? 1 : 0;
210
423
 
424
+ locator.lines.forEach((it) => {
425
+ // exclude blank and comment
426
+ if (it.blank) {
427
+ blankCount += 1;
428
+ return;
429
+ }
430
+ if (it.comment) {
431
+ commentCount += 1;
432
+ return;
433
+ }
434
+ // line 1-base
435
+ const line = it.line + 1;
436
+ const lineInfo = new InfoLine(line, it.length, baseLineCount);
437
+ lineMap.set(line, lineInfo);
438
+ lines.push(lineInfo);
439
+ });
440
+
441
+ coverageInfo.blankCount = blankCount;
442
+ coverageInfo.commentCount = commentCount;
211
443
  };
212
444
 
213
- // ========================================================================================================
445
+ const handleRangesCoverage = (state) => {
214
446
 
215
- const getDistCoverage = (item, state) => {
216
- const positionMapping = state.positionMapping;
217
- if (item.type === 'js') {
218
- const coverage = initFileCoverage(positionMapping, 1);
219
- item.functions.forEach((block) => {
220
- block.ranges.forEach((range, index) => {
221
- addJsCoverage(coverage, block, range, index, positionMapping);
447
+ const { js, coverageList } = state;
448
+
449
+ if (js) {
450
+ coverageList.forEach((block) => {
451
+ block.ranges.forEach((range) => {
452
+ addJsLineCoverage(state, range);
222
453
  });
223
454
  });
224
- return coverage;
455
+
456
+ } else {
457
+ coverageList.forEach((range) => {
458
+ addCssLineCoverage(state, range);
459
+ });
225
460
  }
226
461
 
227
- const coverage = initFileCoverage(positionMapping, 0);
228
- item.ranges.forEach((range) => {
229
- addCssCoverage(coverage, range, positionMapping);
230
- });
231
- return coverage;
232
462
  };
233
463
 
234
- const unpackDistSource = (item, state) => {
235
- const coverage = getDistCoverage(item, state);
236
- const sourcePath = item.sourcePath;
237
- state.coverageData[sourcePath] = getFileCoverage(sourcePath, coverage);
238
- // after dedupe
239
- item.ranges = coverage.ranges;
464
+ const generateCoverageForDist = (item, state) => {
465
+
466
+ // handle coverage
467
+ handleFunctionsCoverage(state);
468
+ handleBranchesCoverage(state);
469
+ handleLinesCoverage(state);
470
+ handleRangesCoverage(state);
471
+
472
+ collectFileCoverage(item, state.coverageInfo, state);
473
+
240
474
  };
241
475
 
242
476
  // ========================================================================================================
243
477
 
244
- const decodeSourceMappings = async (sourceMap, generatedPositionMapping) => {
478
+ const decodeSourceMappings = async (item, state, originalDecodedMap) => {
479
+
480
+ const generatedLocator = state.locator;
481
+
482
+ const { sources, mappings } = state.sourceMap;
245
483
 
246
- const decodedList = await decodeMappings(sourceMap.mappings);
484
+ const decodedList = await decodeMappings(mappings);
247
485
 
248
- const originalIndexMap = new Map();
249
- sourceMap.sources.forEach((item, i) => {
250
- originalIndexMap.set(i, []);
486
+ sources.forEach((source, i) => {
487
+ originalDecodedMap.set(i, []);
251
488
  });
252
489
 
253
490
  const allDecodedMappings = [];
254
491
  let generatedIndex = 0;
255
492
  decodedList.forEach((segments, generatedLine) => {
256
- let item = null;
493
+ let info = null;
257
494
  segments.forEach((segment) => {
258
495
  const [generatedColumn, sourceIndex, originalLine, originalColumn] = segment;
259
- const generatedOffset = generatedPositionMapping.locationToOffset({
496
+ const generatedOffset = generatedLocator.locationToOffset({
260
497
  // 1-base
261
498
  line: generatedLine + 1,
262
499
  column: generatedColumn
263
500
  });
264
501
 
265
- item = {
502
+ info = {
266
503
  generatedOffset,
267
504
  generatedLine,
268
505
  generatedColumn,
@@ -273,22 +510,22 @@ const decodeSourceMappings = async (sourceMap, generatedPositionMapping) => {
273
510
  originalColumn
274
511
  };
275
512
 
276
- allDecodedMappings.push(item);
513
+ allDecodedMappings.push(info);
277
514
  generatedIndex += 1;
278
515
 
279
516
  if (typeof sourceIndex === 'undefined') {
280
517
  return;
281
518
  }
282
519
 
283
- originalIndexMap.get(sourceIndex).push(item);
520
+ originalDecodedMap.get(sourceIndex).push(info);
284
521
 
285
522
  });
286
523
 
287
524
  // line last one
288
- if (item) {
289
- const line = generatedPositionMapping.getLine(item.generatedLine + 1);
525
+ if (info) {
526
+ const line = generatedLocator.getLine(info.generatedLine + 1);
290
527
  // last column
291
- item.generatedEndOffset = item.generatedOffset + (line.length - item.generatedColumn);
528
+ info.generatedEndOffset = info.generatedOffset + (line.length - info.generatedColumn);
292
529
  }
293
530
 
294
531
  });
@@ -298,16 +535,12 @@ const decodeSourceMappings = async (sourceMap, generatedPositionMapping) => {
298
535
  // return a.generatedOffset - b.generatedOffset;
299
536
  // });
300
537
 
301
- return {
302
- allDecodedMappings,
303
- originalIndexMap
304
- };
305
-
538
+ return allDecodedMappings;
306
539
  };
307
540
 
308
- const getOriginalDecodedMappings = (originalIndexMap, sourceIndex, positionMapping) => {
541
+ const getOriginalDecodedMappings = (originalDecodedMap, sourceIndex, locator) => {
309
542
  // all mappings for the original file sorted
310
- const decodedMappings = originalIndexMap.get(sourceIndex);
543
+ const decodedMappings = originalDecodedMap.get(sourceIndex);
311
544
 
312
545
  if (!decodeMappings) {
313
546
  return [];
@@ -324,7 +557,7 @@ const getOriginalDecodedMappings = (originalIndexMap, sourceIndex, positionMappi
324
557
  // add offset and index
325
558
  decodedMappings.forEach((item, i) => {
326
559
  item.originalIndex = i;
327
- item.originalOffset = positionMapping.locationToOffset({
560
+ item.originalOffset = locator.locationToOffset({
328
561
  line: item.originalLine + 1,
329
562
  column: item.originalColumn
330
563
  });
@@ -333,19 +566,20 @@ const getOriginalDecodedMappings = (originalIndexMap, sourceIndex, positionMappi
333
566
  return decodedMappings;
334
567
  };
335
568
 
336
- const initOriginalList = (sourceMap, originalIndexMap, fileSources, options) => {
569
+ const initOriginalList = (item, state, originalDecodedMap, options) => {
337
570
 
338
571
  // source filter
339
- const { sources, sourcesContent } = sourceMap;
340
-
341
572
  let sourceFilter = options.sourceFilter;
342
573
  if (typeof sourceFilter !== 'function') {
343
574
  sourceFilter = () => true;
344
575
  }
345
576
 
577
+ const fileSources = state.fileSources;
578
+
346
579
  // create original content mappings
347
- const map = new Map();
580
+ const originalMap = new Map();
348
581
 
582
+ const { sources, sourcesContent } = state.sourceMap;
349
583
  sources.forEach((sourcePath, sourceIndex) => {
350
584
 
351
585
  // filter
@@ -363,83 +597,49 @@ const initOriginalList = (sourceMap, originalIndexMap, fileSources, options) =>
363
597
  // keep original formatted content
364
598
  fileSources[sourcePath] = sourceContent;
365
599
 
366
- const positionMapping = new PositionMapping(sourceContent);
600
+ const locator = new Locator(sourceContent);
367
601
 
368
- const decodedMappings = getOriginalDecodedMappings(originalIndexMap, sourceIndex, positionMapping);
602
+ const decodedMappings = getOriginalDecodedMappings(originalDecodedMap, sourceIndex, locator);
369
603
 
370
- const coverage = initFileCoverage(positionMapping, 1);
604
+ // unpacked file always is js
371
605
 
372
606
  const type = getSourceType(sourcePath);
373
607
 
374
608
  const originalState = {
375
- source: sourceContent,
609
+ original: true,
610
+ // only js sourceMap for now
611
+ js: true,
376
612
  type,
377
613
  sourcePath,
378
- positionMapping,
614
+ source: sourceContent,
615
+ locator,
379
616
  decodedMappings,
380
- coverage
617
+ coverageInfo: createEmptyCoverageInfo(),
618
+ astInfo: {
619
+ functions: [],
620
+ branches: []
621
+ }
381
622
  };
382
623
 
383
- map.set(sourceIndex, originalState);
624
+ originalMap.set(sourceIndex, originalState);
384
625
  });
385
626
 
386
- return map;
627
+ return originalMap;
387
628
  };
388
629
 
389
- const unpackSourceMap = async (item, state, options) => {
390
- const sourceMap = item.sourceMap;
391
- const generatedPositionMapping = state.positionMapping;
392
- const distFile = sourceMap.file || path.basename(item.sourcePath);
393
-
394
- // keep original urls
395
- const fileUrls = {};
396
- const sourcePathHandler = options.sourcePath;
397
- initSourceMapSourcePath(sourceMap, fileUrls, sourcePathHandler);
398
-
399
- // decode mappings for each original file
400
- const time_start_decode = Date.now();
401
- const { allDecodedMappings, originalIndexMap } = await decodeSourceMappings(sourceMap, generatedPositionMapping);
402
- // only debug level
403
- Util.logTime(`decode source mappings ${distFile}`, time_start_decode);
404
-
405
- // filter original list and init list
406
- const fileSources = state.fileSources;
407
- const originalMap = initOriginalList(sourceMap, originalIndexMap, fileSources, options);
408
-
409
- originalIndexMap.clear();
630
+ const collectOriginalList = (item, state, originalMap) => {
410
631
 
411
- const generatedState = {
412
- decodedMappings: allDecodedMappings,
413
- positionMapping: generatedPositionMapping
414
- };
415
-
416
- // const time_start_mapping = Date.now();
417
- item.functions.forEach((block) => {
418
- block.ranges.forEach((range, index) => {
419
-
420
- const result = findOriginalRange(range, generatedState, originalMap);
421
- if (!result) {
422
- return;
423
- }
424
-
425
- const { originalRange, originalState } = result;
426
- const { coverage, positionMapping } = originalState;
427
- addJsCoverage(coverage, block, originalRange, index, positionMapping);
428
-
429
- });
430
- });
632
+ const { fileUrls, sourceMap } = state;
633
+ const distFile = sourceMap.file || path.basename(item.sourcePath);
431
634
 
432
635
  // collect original files
433
636
  const sourceList = [];
434
637
  originalMap.forEach((originalState) => {
638
+
435
639
  const {
436
- source, type, sourcePath, coverage
640
+ js, type, sourcePath, source, coverageInfo
437
641
  } = originalState;
438
642
 
439
- // generate coverage
440
- state.coverageData[sourcePath] = getFileCoverage(sourcePath, coverage);
441
- const ranges = coverage.ranges;
442
-
443
643
  // add file item
444
644
  const url = fileUrls[sourcePath] || sourcePath;
445
645
  const id = Util.calculateSha1(url + source);
@@ -447,17 +647,257 @@ const unpackSourceMap = async (item, state, options) => {
447
647
  const sourceItem = {
448
648
  url,
449
649
  id,
650
+ js,
450
651
  type,
451
652
  sourcePath,
452
653
  distFile,
453
- ranges,
454
654
  source
455
655
  };
456
656
 
657
+ // generate coverage, coverageInfo for current file, state for dist file
658
+ collectFileCoverage(sourceItem, coverageInfo, state);
659
+
457
660
  sourceList.push(sourceItem);
458
661
  });
459
662
 
460
- state.sourceList = sourceList;
663
+ return sourceList;
664
+ };
665
+
666
+ // =======================================================================================
667
+
668
+ const handleOriginalFunctionsCoverage = (state, originalMap) => {
669
+
670
+ // functions only for js
671
+ if (!state.js) {
672
+ return;
673
+ }
674
+
675
+ // console.log(state.astInfo.functions);
676
+
677
+ // function count
678
+ state.astInfo.functions.forEach((it) => {
679
+ const {
680
+ start, end, wrap
681
+ } = it;
682
+
683
+ // remove webpack wrap functions for functions count, not for ranges here
684
+ if (wrap) {
685
+ return;
686
+ }
687
+
688
+ // rename to startOffset and endOffset
689
+ const range = {
690
+ startOffset: start,
691
+ endOffset: end
692
+ };
693
+
694
+ const result = findOriginalRange(range, state, originalMap);
695
+ if (result.error) {
696
+ return;
697
+ }
698
+ const { originalRange, originalState } = result;
699
+
700
+ // add back to original ast
701
+ originalState.astInfo.functions.push({
702
+ ... it,
703
+ generatedStart: start,
704
+ generatedEnd: end,
705
+ start: originalRange.startOffset,
706
+ end: originalRange.endOffset
707
+ });
708
+
709
+ // if (originalState.sourcePath.endsWith('store.js')) {
710
+ // console.log('==========================================================');
711
+ // console.log(it, result.startMapping, result.endMapping);
712
+ // }
713
+
714
+ });
715
+
716
+ };
717
+
718
+ const handleOriginalBranchesCoverage = (state, originalMap) => {
719
+
720
+ // branches only for js
721
+ if (!state.js) {
722
+ return;
723
+ }
724
+
725
+ // console.log(state.astInfo.branches);
726
+
727
+ // function count
728
+ state.astInfo.branches.forEach((it) => {
729
+
730
+ const {
731
+ type, loc, locations
732
+ } = it;
733
+
734
+ // rename to startOffset and endOffset
735
+ const bRange = {
736
+ startOffset: loc.start,
737
+ endOffset: loc.end
738
+ };
739
+
740
+ // start
741
+ const result = findOriginalRange(bRange, state, originalMap);
742
+ if (result.error) {
743
+ // not in the original files
744
+ return;
745
+ }
746
+ const { originalRange, originalState } = result;
747
+
748
+ const newBranchLoc = {
749
+ start: originalRange.startOffset,
750
+ end: originalRange.endOffset
751
+ };
752
+
753
+ let hasError;
754
+ const newLocations = locations.map((oLoc) => {
755
+
756
+ const newLoc = {
757
+ ... oLoc
758
+ };
759
+
760
+ if (newLoc.none) {
761
+ return newLoc;
762
+ }
763
+
764
+ const { start, end } = newLoc;
765
+
766
+ const lRange = {
767
+ startOffset: start,
768
+ endOffset: end
769
+ };
770
+ const res = findOriginalRange(lRange, state, originalMap);
771
+ if (res.error) {
772
+ // It should not happen unless it is minify files, the SourceMap has some order problems
773
+
774
+ hasError = true;
775
+
776
+ // console.log('====================================', state.sourcePath, loc);
777
+ // console.log(res.errors);
778
+ // Util.logError(`Not found original branch. start: ${start}, end: ${end}`);
779
+
780
+ return newLoc;
781
+ }
782
+
783
+ const oRage = res.originalRange;
784
+ newLoc.start = oRage.startOffset;
785
+ newLoc.end = oRage.endOffset;
786
+
787
+ return newLoc;
788
+ });
789
+
790
+ if (hasError) {
791
+ return;
792
+ }
793
+
794
+ // add back to original ast
795
+ originalState.astInfo.branches.push({
796
+ type,
797
+ loc: newBranchLoc,
798
+ locations: newLocations
799
+ });
800
+
801
+ });
802
+
803
+ };
804
+
805
+ const handleOriginalRangesCoverage = (state, originalMap) => {
806
+
807
+ const { js, coverageList } = state;
808
+
809
+ // const time_start_mapping = Date.now();
810
+ if (js) {
811
+ // v8 coverage
812
+ coverageList.forEach((block) => {
813
+ block.ranges.forEach((range, index) => {
814
+
815
+ // remove wrap functions for original files
816
+ if (range.wrap) {
817
+ // console.log(range);
818
+ return;
819
+ }
820
+
821
+ const result = findOriginalRange(range, state, originalMap);
822
+ if (result.error) {
823
+ return;
824
+ }
825
+
826
+ const { originalRange, originalState } = result;
827
+ addJsLineCoverage(originalState, originalRange);
828
+
829
+ // if (originalRange.startOffset === 110 && originalRange.endOffset === 271) {
830
+ // console.log('===============================', originalState.sourcePath);
831
+ // console.log(result.startMapping, result.endMapping);
832
+ // }
833
+
834
+ });
835
+ });
836
+
837
+ } else {
838
+ // support css later
839
+ // current css no sourceMap, so never come in
840
+ // coverageList.forEach((range) => {
841
+
842
+ // });
843
+ }
844
+
845
+ };
846
+
847
+ const unpackSourceMap = async (item, state, options) => {
848
+
849
+ const { sourcePath } = item;
850
+ const sourceMap = state.sourceMap;
851
+
852
+ // keep original urls
853
+ const fileUrls = {};
854
+ const sourcePathHandler = options.sourcePath;
855
+ initSourceMapSourcePath(sourceMap, fileUrls, sourcePathHandler);
856
+ state.fileUrls = fileUrls;
857
+
858
+ // ===============================================
859
+ // decode mappings for each original file
860
+ const time_start_decode = Date.now();
861
+ const originalDecodedMap = new Map();
862
+ // for find-original-range
863
+ state.decodedMappings = await decodeSourceMappings(item, state, originalDecodedMap);
864
+ // only debug level
865
+ Util.logTime(`decode source mappings: ${sourcePath}`, time_start_decode);
866
+
867
+ // filter original list and init list
868
+ const originalMap = initOriginalList(item, state, originalDecodedMap, options);
869
+
870
+ originalDecodedMap.clear();
871
+
872
+ // ===============================================
873
+
874
+ // handle functions before handle original state functions
875
+ handleOriginalFunctionsCoverage(state, originalMap);
876
+ handleOriginalBranchesCoverage(state, originalMap);
877
+
878
+ // handle lines info before handle ranges to update line count
879
+ originalMap.forEach((originalState) => {
880
+ handleFunctionsCoverage(originalState);
881
+ handleBranchesCoverage(originalState);
882
+ handleLinesCoverage(originalState);
883
+
884
+ // console.log(originalState.sourcePath, '================================');
885
+ // console.log(originalState.astInfo.functions);
886
+ // console.log(originalState.source.length);
887
+ // console.log(originalState.coverageInfo.functions);
888
+
889
+ // if (originalState.sourcePath.endsWith('demo.js')) {
890
+ // console.log('=================================', originalState.sourcePath);
891
+ // console.log(originalState.coverageInfo.lineMap);
892
+ // }
893
+
894
+ });
895
+
896
+ // handle ranges after lines ready
897
+ handleOriginalRangesCoverage(state, originalMap);
898
+
899
+ // collect coverage for original list
900
+ state.sourceList = collectOriginalList(item, state, originalMap);
461
901
 
462
902
  };
463
903
 
@@ -465,10 +905,10 @@ const unpackSourceMap = async (item, state, options) => {
465
905
 
466
906
  const unpackDistFile = async (item, state, options) => {
467
907
 
468
- if (item.sourceMap) {
908
+ if (state.sourceMap) {
469
909
  if (Util.loggingType === 'debug') {
470
910
  // js self
471
- unpackDistSource(item, state, options);
911
+ generateCoverageForDist(item, state);
472
912
  } else {
473
913
  item.dedupe = true;
474
914
  }
@@ -476,19 +916,13 @@ const unpackDistFile = async (item, state, options) => {
476
916
  // unpack source map
477
917
  await unpackSourceMap(item, state, options);
478
918
 
479
- // remove sourceMap
480
- delete item.sourceMap;
481
-
482
919
  } else {
483
920
 
484
921
  // css/js self
485
- unpackDistSource(item, state, options);
922
+ generateCoverageForDist(item, state);
486
923
 
487
924
  }
488
925
 
489
- // clean after all
490
- delete item.functions;
491
-
492
926
  };
493
927
 
494
928
  // ========================================================================================================
@@ -508,7 +942,6 @@ const dedupeV8List = (v8list) => {
508
942
  }
509
943
  };
510
944
 
511
-
512
945
  const convertV8List = async (v8list, options) => {
513
946
 
514
947
  // global file sources and coverage data
@@ -519,18 +952,68 @@ const convertV8List = async (v8list, options) => {
519
952
  for (const item of v8list) {
520
953
  // console.log([item.id]);
521
954
 
522
- const { source, sourcePath } = item;
955
+ const {
956
+ type, source, sourcePath
957
+ } = item;
523
958
 
524
- const positionMapping = new PositionMapping(source);
959
+ // for source file, type could be ts or vue as extname, but js = true
960
+ const js = type === 'js';
961
+ item.js = js;
525
962
 
526
963
  // append file source
527
964
  fileSources[sourcePath] = source;
528
965
 
966
+ // source mapping
967
+ const locator = new Locator(source);
968
+
969
+ // ============================
970
+ // move sourceMap
971
+ const sourceMap = item.sourceMap;
972
+ if (sourceMap) {
973
+ delete item.sourceMap;
974
+ }
975
+
976
+ // ============================
977
+ const coverageInfo = createEmptyCoverageInfo();
978
+
979
+ // ============================
980
+ // move functions and ranges to coverageList
981
+ let coverageList;
982
+ let astInfo;
983
+ if (js) {
984
+ coverageList = item.functions;
985
+ // remove original functions
986
+ if (Util.loggingType !== 'debug') {
987
+ delete item.functions;
988
+ }
989
+ astInfo = getJsAstInfo(item, coverageList);
990
+ } else {
991
+ astInfo = getCssAstInfo(item, locator);
992
+ // convent css covered ranges to rules ranges and include uncovered ranges
993
+ coverageList = astInfo.coverageList;
994
+ // console.log(coverageList);
995
+
996
+ // remove original ranges
997
+ if (Util.loggingType !== 'debug') {
998
+ delete item.ranges;
999
+ }
1000
+ }
1001
+
1002
+ // ============================
1003
+
529
1004
  // current file and it's sources from sourceMap
530
1005
  const state = {
1006
+ js,
1007
+ type,
1008
+ sourcePath,
1009
+ sourceMap,
1010
+ coverageList,
1011
+ locator,
1012
+ coverageInfo,
1013
+ astInfo,
1014
+ // for istanbul data
531
1015
  fileSources: {},
532
- coverageData: {},
533
- positionMapping
1016
+ coverageData: {}
534
1017
  };
535
1018
 
536
1019
  await unpackDistFile(item, state, options);