monocart-reporter 1.7.1 → 1.7.2

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.
Files changed (38) hide show
  1. package/README.md +7 -5
  2. package/lib/cli.js +1 -1
  3. package/lib/common.js +2 -3
  4. package/lib/default/options.js +4 -1
  5. package/lib/generate-data.js +2 -7
  6. package/lib/generate-report.js +10 -11
  7. package/lib/index.d.ts +2 -4
  8. package/lib/index.js +6 -0
  9. package/lib/merge-data.js +7 -6
  10. package/lib/plugins/audit/audit.js +4 -2
  11. package/lib/plugins/comments.js +2 -3
  12. package/lib/plugins/coverage/converter/collect-source-maps.js +194 -0
  13. package/lib/plugins/coverage/converter/converter.js +547 -0
  14. package/lib/plugins/coverage/converter/decode-mappings.js +49 -0
  15. package/lib/plugins/coverage/{v8 → converter}/dedupe.js +8 -1
  16. package/lib/plugins/coverage/converter/find-original-range.js +576 -0
  17. package/lib/plugins/coverage/converter/info-branch.js +30 -0
  18. package/lib/plugins/coverage/converter/info-function.js +29 -0
  19. package/lib/plugins/coverage/converter/info-line.js +20 -0
  20. package/lib/plugins/coverage/converter/position-mapping.js +183 -0
  21. package/lib/plugins/coverage/{coverage-utils.js → converter/source-path.js} +26 -42
  22. package/lib/plugins/coverage/coverage.js +51 -67
  23. package/lib/plugins/coverage/istanbul/istanbul.js +15 -174
  24. package/lib/plugins/coverage/v8/v8.js +22 -13
  25. package/lib/plugins/network/network.js +4 -13
  26. package/lib/plugins/state/client.js +3 -4
  27. package/lib/plugins/state/state.js +6 -3
  28. package/lib/runtime/monocart-code-viewer.js +1 -1
  29. package/lib/runtime/monocart-coverage.js +13 -14
  30. package/lib/runtime/monocart-formatter.js +1 -1
  31. package/lib/runtime/monocart-network.js +1 -1
  32. package/lib/runtime/monocart-reporter.js +1 -1
  33. package/lib/runtime/monocart-v8.js +1 -1
  34. package/lib/runtime/monocart-vendor.js +13 -13
  35. package/lib/utils/util.js +97 -3
  36. package/package.json +4 -5
  37. package/lib/plugins/coverage/v8/position-mapping.js +0 -92
  38. package/lib/plugins/coverage/v8/source-map.js +0 -451
@@ -0,0 +1,547 @@
1
+ const path = require('path');
2
+
3
+ const Util = require('../../../utils/util.js');
4
+
5
+ // position mapping for conversion between offset and line/column
6
+ const PositionMapping = require('./position-mapping.js');
7
+
8
+ const decodeMappings = require('./decode-mappings.js');
9
+ const findOriginalRange = require('./find-original-range.js');
10
+
11
+ const { dedupeCountRanges } = require('./dedupe.js');
12
+ const { getSourceType, initSourceMapSourcePath } = require('./source-path.js');
13
+
14
+ const InfoLine = require('./info-line.js');
15
+ const InfoBranch = require('./info-branch.js');
16
+ const InfoFunction = require('./info-function.js');
17
+
18
+ // ========================================================================================================
19
+
20
+ // istanbul coverage format
21
+ /**
22
+ * * `path` - the file path for which coverage is being tracked
23
+ * * `statementMap` - map of statement locations keyed by statement index
24
+ * * `fnMap` - map of function metadata keyed by function index
25
+ * * `branchMap` - map of branch metadata keyed by branch index
26
+ * * `s` - hit counts for statements
27
+ * * `f` - hit count for functions
28
+ * * `b` - hit count for branches
29
+ */
30
+ const getFileCoverage = (sourcePath, inputData) => {
31
+
32
+ const {
33
+ lines, functions, branches, ranges
34
+ } = inputData;
35
+
36
+ // v8 ranges
37
+ inputData.ranges = dedupeCountRanges(ranges);
38
+
39
+ // istanbul coverage
40
+ const coverage = {
41
+ path: sourcePath,
42
+
43
+ statementMap: {},
44
+ s: {},
45
+
46
+ fnMap: {},
47
+ f: {},
48
+
49
+ branchMap: {},
50
+ b: {}
51
+ };
52
+
53
+ lines.forEach((line, index) => {
54
+ coverage.statementMap[`${index}`] = line.generate();
55
+ coverage.s[`${index}`] = line.count;
56
+ });
57
+
58
+ functions.forEach((fn, index) => {
59
+ coverage.fnMap[`${index}`] = fn.generate();
60
+ coverage.f[`${index}`] = fn.count;
61
+ });
62
+
63
+ branches.forEach((branch, index) => {
64
+ coverage.branchMap[`${index}`] = branch.generate();
65
+ coverage.b[`${index}`] = [branch.count];
66
+ });
67
+
68
+ return coverage;
69
+ };
70
+
71
+ // ========================================================================================================
72
+
73
+ const setLineCount = (lineMap, line, count) => {
74
+ const lineInfo = lineMap[line];
75
+ if (lineInfo) {
76
+ lineInfo.count = count;
77
+ }
78
+ };
79
+
80
+ const setSingleLineCount = (lineMap, sLoc, eLoc, count) => {
81
+ // nothing between
82
+ if (sLoc.column >= eLoc.column) {
83
+ return;
84
+ }
85
+
86
+ // sometimes column > length
87
+ if (sLoc.column <= sLoc.indent && eLoc.column >= eLoc.length) {
88
+ // console.log('single', sLoc.line);
89
+ setLineCount(lineMap, sLoc.line, count);
90
+ }
91
+
92
+ };
93
+
94
+ const updateLinesCount = (lineMap, sLoc, eLoc, count) => {
95
+
96
+ // single line
97
+ if (sLoc.line === eLoc.line) {
98
+ setSingleLineCount(lineMap, sLoc, eLoc, count);
99
+ return;
100
+ }
101
+
102
+ const firstELoc = {
103
+ ... sLoc,
104
+ column: sLoc.length
105
+ };
106
+ setSingleLineCount(lineMap, sLoc, firstELoc, count);
107
+
108
+ for (let i = sLoc.line + 1; i < eLoc.line; i++) {
109
+ setLineCount(lineMap, i, count);
110
+ }
111
+
112
+ const lastSLoc = {
113
+ ... eLoc,
114
+ column: eLoc.indent
115
+ };
116
+ setSingleLineCount(lineMap, lastSLoc, eLoc, count);
117
+
118
+ };
119
+
120
+ const initFileCoverage = (positionMapping) => {
121
+
122
+ // istanbul
123
+ const lines = [];
124
+ const functions = [];
125
+ const branches = [];
126
+ // v8
127
+ const ranges = [];
128
+
129
+ // add all lines
130
+ const lineMap = {};
131
+
132
+ const { commentedLines, blankLines } = positionMapping;
133
+ positionMapping.lines.forEach((it) => {
134
+ // exclude comments and blanks
135
+ if (commentedLines.includes(it.line) || blankLines.includes(it.line)) {
136
+ return;
137
+ }
138
+ // line 1-base
139
+ const line = it.line + 1;
140
+ const lineInfo = new InfoLine(line, it.length);
141
+ lineMap[line] = lineInfo;
142
+ lines.push(lineInfo);
143
+ });
144
+
145
+ return {
146
+ lines, functions, branches, ranges, lineMap
147
+ };
148
+ };
149
+
150
+
151
+ // https://github.com/demurgos/v8-coverage
152
+ /**
153
+ * @ranges is always non-empty. The first range is called the "root range".
154
+ * @isBlockCoverage indicates if the function has block coverage information
155
+ * @false means that there is a single range and its count is the number of times the function was called.
156
+ * @true means that the ranges form a tree of blocks representing how many times each statement or expression inside was executed.
157
+ * It detects skipped or repeated statements. The root range counts the number of function calls.
158
+ *
159
+ * @functionName can be an empty string. This is common for the FunctionCov representing the whole module.
160
+ */
161
+ const addCoverage = (coverage, block, range, index, positionMapping) => {
162
+
163
+ const {
164
+ functions, branches, ranges, lineMap
165
+ } = coverage;
166
+
167
+ const { isBlockCoverage, functionName } = block;
168
+ const {
169
+ startOffset, endOffset, count
170
+ } = range;
171
+
172
+ ranges.push({
173
+ start: startOffset,
174
+ end: endOffset,
175
+ count
176
+ });
177
+
178
+ const sLoc = positionMapping.offsetToLocation(startOffset);
179
+ const eLoc = positionMapping.offsetToLocation(endOffset);
180
+
181
+ // line, column
182
+ updateLinesCount(lineMap, sLoc, eLoc, count);
183
+
184
+ if (isBlockCoverage) {
185
+ if (index === 0) {
186
+ // The root range counts the number of function calls
187
+ functions.push(new InfoFunction(sLoc, eLoc, count, functionName));
188
+ }
189
+
190
+ // index 0 not really a branch, but for covered whole function
191
+ branches.push(new InfoBranch(sLoc, eLoc, count));
192
+
193
+ } else {
194
+ functions.push(new InfoFunction(sLoc, eLoc, count, functionName));
195
+
196
+ // possible have branches in the function but no information for it
197
+
198
+ }
199
+ };
200
+
201
+ // ========================================================================================================
202
+
203
+ const unpackDistSource = (item, state) => {
204
+
205
+ const positionMapping = state.positionMapping;
206
+ const coverage = initFileCoverage(positionMapping);
207
+
208
+ item.functions.forEach((block) => {
209
+ block.ranges.forEach((range, index) => {
210
+ addCoverage(coverage, block, range, index, positionMapping);
211
+ });
212
+ });
213
+
214
+ const sourcePath = item.sourcePath;
215
+ state.coverageData[sourcePath] = getFileCoverage(sourcePath, coverage);
216
+ item.ranges = coverage.ranges;
217
+
218
+ };
219
+
220
+ // ========================================================================================================
221
+
222
+ const getOriginalDecodedMappings = (originalIndexMap, sourceIndex, positionMapping) => {
223
+ // all mappings for the original file sorted
224
+ const decodedMappings = originalIndexMap.get(sourceIndex);
225
+
226
+ if (!decodeMappings) {
227
+ return [];
228
+ }
229
+
230
+ // sort by original line/column
231
+ decodedMappings.sort((a, b) => {
232
+ if (a.originalLine === b.originalLine) {
233
+ return a.originalColumn - b.originalColumn;
234
+ }
235
+ return a.originalLine - b.originalLine;
236
+ });
237
+
238
+ // add offset and index
239
+ decodedMappings.forEach((item, i) => {
240
+ item.originalIndex = i;
241
+ item.originalOffset = positionMapping.locationToOffset({
242
+ line: item.originalLine + 1,
243
+ column: item.originalColumn
244
+ });
245
+ });
246
+
247
+ return decodedMappings;
248
+ };
249
+
250
+ const initOriginalList = (sourceMap, originalIndexMap, fileSources, options) => {
251
+
252
+ // source filter
253
+ const { sources, sourcesContent } = sourceMap;
254
+
255
+ let sourceFilter = options.sourceFilter;
256
+ if (typeof sourceFilter !== 'function') {
257
+ sourceFilter = () => true;
258
+ }
259
+
260
+ // create original content mappings
261
+ const map = new Map();
262
+
263
+ sources.forEach((sourcePath, sourceIndex) => {
264
+
265
+ // filter
266
+ if (!sourceFilter(sourcePath)) {
267
+ return;
268
+ }
269
+
270
+ // console.log(`add source: ${k}`);
271
+ const sourceContent = sourcesContent[sourceIndex];
272
+ if (typeof sourceContent !== 'string') {
273
+ Util.logError(`not found source content: ${sourcePath}`);
274
+ return;
275
+ }
276
+
277
+ // keep original formatted content
278
+ fileSources[sourcePath] = sourceContent;
279
+
280
+ const positionMapping = new PositionMapping(sourceContent);
281
+
282
+ const decodedMappings = getOriginalDecodedMappings(originalIndexMap, sourceIndex, positionMapping);
283
+
284
+ const coverage = initFileCoverage(positionMapping);
285
+
286
+ const type = getSourceType(sourcePath);
287
+
288
+ const originalState = {
289
+ source: sourceContent,
290
+ type,
291
+ sourcePath,
292
+ positionMapping,
293
+ decodedMappings,
294
+ coverage
295
+ };
296
+
297
+ map.set(sourceIndex, originalState);
298
+ });
299
+
300
+ return map;
301
+ };
302
+
303
+ const decodeSourceMappings = async (sourceMap, generatedPositionMapping) => {
304
+
305
+ const decodedList = await decodeMappings(sourceMap.mappings);
306
+
307
+ const originalIndexMap = new Map();
308
+ sourceMap.sources.forEach((item, i) => {
309
+ originalIndexMap.set(i, []);
310
+ });
311
+
312
+ const allDecodedMappings = [];
313
+ let generatedIndex = 0;
314
+ decodedList.forEach((segments, generatedLine) => {
315
+ let item = null;
316
+ segments.forEach((segment) => {
317
+ const [generatedColumn, sourceIndex, originalLine, originalColumn] = segment;
318
+ const generatedOffset = generatedPositionMapping.locationToOffset({
319
+ // 1-base
320
+ line: generatedLine + 1,
321
+ column: generatedColumn
322
+ });
323
+
324
+ item = {
325
+ generatedOffset,
326
+ generatedLine,
327
+ generatedColumn,
328
+ generatedIndex,
329
+
330
+ sourceIndex,
331
+ originalLine,
332
+ originalColumn
333
+ };
334
+
335
+ allDecodedMappings.push(item);
336
+ generatedIndex += 1;
337
+
338
+ if (typeof sourceIndex === 'undefined') {
339
+ return;
340
+ }
341
+
342
+ originalIndexMap.get(sourceIndex).push(item);
343
+
344
+ });
345
+
346
+ // line last one
347
+ if (item) {
348
+ const line = generatedPositionMapping.getLine(item.generatedLine + 1);
349
+ // last column
350
+ item.generatedEndOffset = item.generatedOffset + (line.length - item.generatedColumn);
351
+ }
352
+
353
+ });
354
+
355
+ // defaults to sort by generated offset, not need sort
356
+ // allDecodedMappings.sort((a, b) => {
357
+ // return a.generatedOffset - b.generatedOffset;
358
+ // });
359
+
360
+ return {
361
+ allDecodedMappings,
362
+ originalIndexMap
363
+ };
364
+
365
+ };
366
+
367
+ const unpackSourceMap = async (item, state, options) => {
368
+ const sourceMap = item.sourceMap;
369
+ const generatedPositionMapping = state.positionMapping;
370
+ const distFile = sourceMap.file || path.basename(item.sourcePath);
371
+
372
+ // keep original urls
373
+ const fileUrls = {};
374
+ initSourceMapSourcePath(sourceMap, fileUrls, options.sourcePath);
375
+
376
+ // decode mappings for each original file
377
+ const time_start_decode = Date.now();
378
+ const { allDecodedMappings, originalIndexMap } = await decodeSourceMappings(sourceMap, generatedPositionMapping);
379
+ // only debug level
380
+ Util.logTime(`decode source mappings ${distFile}`, time_start_decode);
381
+
382
+ // filter original list and init list
383
+ const fileSources = state.fileSources;
384
+ const originalMap = initOriginalList(sourceMap, originalIndexMap, fileSources, options);
385
+
386
+ originalIndexMap.clear();
387
+
388
+ const generatedState = {
389
+ decodedMappings: allDecodedMappings,
390
+ positionMapping: generatedPositionMapping
391
+ };
392
+
393
+ // const time_start_mapping = Date.now();
394
+ item.functions.forEach((block) => {
395
+ block.ranges.forEach((range, index) => {
396
+
397
+ const result = findOriginalRange(range, generatedState, originalMap);
398
+ if (!result) {
399
+ return;
400
+ }
401
+
402
+ const { originalRange, originalState } = result;
403
+ const { coverage, positionMapping } = originalState;
404
+ addCoverage(coverage, block, originalRange, index, positionMapping);
405
+
406
+ });
407
+ });
408
+
409
+ // collect original files
410
+ const sourceList = [];
411
+ originalMap.forEach((originalState) => {
412
+ const {
413
+ source, type, sourcePath, coverage
414
+ } = originalState;
415
+
416
+ // generate coverage
417
+ state.coverageData[sourcePath] = getFileCoverage(sourcePath, coverage);
418
+ const ranges = coverage.ranges;
419
+
420
+ // add file item
421
+ const url = fileUrls[sourcePath] || sourcePath;
422
+ const id = Util.calculateSha1(url + source);
423
+
424
+ const sourceItem = {
425
+ url,
426
+ id,
427
+ type,
428
+ sourcePath,
429
+ distFile,
430
+ ranges,
431
+ source
432
+ };
433
+
434
+ sourceList.push(sourceItem);
435
+ });
436
+
437
+ state.sourceList = sourceList;
438
+
439
+ };
440
+
441
+ // ========================================================================================================
442
+
443
+ const unpackDistFile = async (item, state, options) => {
444
+
445
+ if (item.sourceMap) {
446
+ if (Util.loggingType === 'debug') {
447
+ // js self
448
+ unpackDistSource(item, state, options);
449
+ } else {
450
+ item.dedupe = true;
451
+ }
452
+
453
+ // unpack source map
454
+ await unpackSourceMap(item, state, options);
455
+
456
+ // remove sourceMap
457
+ delete item.sourceMap;
458
+
459
+ } else {
460
+
461
+ // js self
462
+ unpackDistSource(item, state, options);
463
+
464
+ }
465
+
466
+ // clean after all
467
+ delete item.functions;
468
+
469
+ };
470
+
471
+ // ========================================================================================================
472
+
473
+ const dedupeV8List = (v8list) => {
474
+ const indexes = [];
475
+ v8list.forEach((item, i) => {
476
+ if (item.dedupe) {
477
+ indexes.push(i);
478
+ }
479
+ });
480
+ if (indexes.length) {
481
+ indexes.reverse();
482
+ indexes.forEach((i) => {
483
+ v8list.splice(i, 1);
484
+ });
485
+ }
486
+ };
487
+
488
+
489
+ const convertV8List = async (v8list, options) => {
490
+
491
+ // TODO css to istanbul
492
+ const jsList = v8list.filter((it) => it.type === 'js');
493
+
494
+
495
+ // global file sources and coverage data
496
+ const fileSources = {};
497
+ const coverageData = {};
498
+ let sourceList = [];
499
+
500
+ for (const item of jsList) {
501
+ // console.log([item.id]);
502
+
503
+ const { source, sourcePath } = item;
504
+
505
+ const positionMapping = new PositionMapping(source);
506
+
507
+ // append file source
508
+ fileSources[sourcePath] = source;
509
+
510
+ // current file and it's sources from sourceMap
511
+ const state = {
512
+ fileSources: {},
513
+ coverageData: {},
514
+ positionMapping
515
+ };
516
+
517
+ await unpackDistFile(item, state, options);
518
+
519
+ // merge state
520
+ Object.assign(fileSources, state.fileSources);
521
+ Object.assign(coverageData, state.coverageData);
522
+ if (state.sourceList) {
523
+ sourceList = sourceList.concat(state.sourceList);
524
+ }
525
+
526
+ }
527
+
528
+ // dedupe
529
+ dedupeV8List(v8list);
530
+
531
+ // add all sources
532
+ if (sourceList.length) {
533
+ sourceList.forEach((item) => {
534
+ v8list.push(item);
535
+ });
536
+ }
537
+
538
+ return {
539
+ fileSources,
540
+ coverageData
541
+ };
542
+
543
+ };
544
+
545
+ module.exports = {
546
+ convertV8List
547
+ };
@@ -0,0 +1,49 @@
1
+ const {
2
+ Worker, isMainThread, parentPort
3
+ } = require('worker_threads');
4
+
5
+ if (isMainThread) {
6
+
7
+ const decodeMappings = (mappings = '') => {
8
+ if (typeof text !== 'string') {
9
+ mappings = String(mappings);
10
+ }
11
+
12
+ return new Promise((resolve) => {
13
+
14
+ const worker = new Worker(__filename);
15
+
16
+ worker.on('message', (message) => {
17
+ if (message === 'workerReady') {
18
+ worker.postMessage(mappings);
19
+ return;
20
+ }
21
+ resolve(message);
22
+ worker.terminate();
23
+ });
24
+
25
+ worker.on('error', (err) => {
26
+ resolve({
27
+ error: err
28
+ });
29
+ worker.terminate();
30
+ });
31
+
32
+ });
33
+ };
34
+
35
+ module.exports = decodeMappings;
36
+
37
+ } else {
38
+
39
+ const { decode } = require('../../../runtime/monocart-coverage.js');
40
+
41
+ parentPort.on('message', (message) => {
42
+ const result = decode(message);
43
+ parentPort.postMessage(result);
44
+ });
45
+
46
+ parentPort.postMessage('workerReady');
47
+
48
+ }
49
+
@@ -1,5 +1,12 @@
1
1
 
2
- const { sortRanges } = require('../coverage-utils.js');
2
+ const sortRanges = (ranges) => {
3
+ ranges.sort((a, b) => {
4
+ if (a.start === b.start) {
5
+ return a.end - b.end;
6
+ }
7
+ return a.start - b.start;
8
+ });
9
+ };
3
10
 
4
11
  const filterRanges = (ranges) => {
5
12
  // remove start = end