monocart-coverage-reports 2.7.1 → 2.7.3

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.
@@ -129,13 +129,18 @@ const getComparedPosition = (info, direction) => {
129
129
  gt = gt.trimEnd();
130
130
  ot = ot.trimEnd();
131
131
 
132
- // no content after trim
133
- if (!ot.length) {
132
+ // no original content after trim
133
+ if (!ot) {
134
134
  return {
135
135
  pos: direction === 'start' ? oLen : 0
136
136
  };
137
137
  }
138
138
 
139
+ // no generated content after trim
140
+ if (!gt) {
141
+ return;
142
+ }
143
+
139
144
  // same content
140
145
  if (gt === ot) {
141
146
  return {
@@ -273,16 +278,16 @@ const getFixedPosition = (info, direction) => {
273
278
 
274
279
  // ========================================================================================================
275
280
 
276
- const getOriginalEndOffset = (m, rangeInfo) => {
281
+ const getOriginalEndOffset = (m, cache) => {
277
282
  if (!m.originalEndOffset) {
278
- const line = rangeInfo.originalState.locator.getLine(m.originalLine + 1);
283
+ const line = cache.originalState.locator.getLine(m.originalLine + 1);
279
284
  m.originalEndOffset = line ? line.end : m.originalOffset;
280
285
  }
281
286
  return m.originalEndOffset;
282
287
  };
283
288
 
284
- const getOriginalText = (m1, m2, rangeInfo) => {
285
- const originalLocator = rangeInfo.originalState.locator;
289
+ const getOriginalText = (m1, m2, cache) => {
290
+ const originalLocator = cache.originalState.locator;
286
291
 
287
292
  const o1 = m1.originalOffset;
288
293
  const o2 = m2.originalOffset;
@@ -299,7 +304,7 @@ const getOriginalText = (m1, m2, rangeInfo) => {
299
304
  // esbuild fixing two mapping have same original
300
305
  // m1 to end line
301
306
  if (o1 === o2) {
302
- const originalEndOffset = getOriginalEndOffset(m1, rangeInfo);
307
+ const originalEndOffset = getOriginalEndOffset(m1, cache);
303
308
  return {
304
309
  originalOffset: o1,
305
310
  originalText: originalLocator.getSlice(o1, originalEndOffset)
@@ -319,12 +324,12 @@ const getOriginalText = (m1, m2, rangeInfo) => {
319
324
 
320
325
  // if (direction === 'start') {
321
326
  // should be m2 ?
322
- // console.log(direction, rangeInfo.originalState.sourcePath);
327
+ // console.log(direction, cache.originalState.sourcePath);
323
328
  // console.log(m1, m2);
324
329
  // }
325
330
 
326
331
  // should be wrong, ignore m2
327
- const originalEndOffset = getOriginalEndOffset(m1, rangeInfo);
332
+ const originalEndOffset = getOriginalEndOffset(m1, cache);
328
333
  return {
329
334
  originalOffset: o1,
330
335
  originalText: originalLocator.getSlice(o1, originalEndOffset)
@@ -385,10 +390,10 @@ const getGeneratedText = (m1, m2, state, offset) => {
385
390
 
386
391
  // ========================================================================================================
387
392
 
388
- const getFixedOriginalStart = (start, mappings, state, rangeInfo) => {
393
+ const getFixedOriginalStart = (start, mappings, state, cache) => {
389
394
 
390
395
  const [m1, m2] = mappings;
391
- const { originalState, crossStart } = rangeInfo;
396
+ const { originalState, crossStart } = cache;
392
397
 
393
398
  // cross file from start of line
394
399
  if (crossStart) {
@@ -398,7 +403,7 @@ const getFixedOriginalStart = (start, mappings, state, rangeInfo) => {
398
403
  }
399
404
 
400
405
  const direction = 'start';
401
- const { originalText, originalOffset } = getOriginalText(m1, m2, rangeInfo);
406
+ const { originalText, originalOffset } = getOriginalText(m1, m2, cache);
402
407
  const { generatedText, generatedPos } = getGeneratedText(m1, m2, state, start);
403
408
 
404
409
  const info = {
@@ -426,14 +431,14 @@ const getFixedOriginalStart = (start, mappings, state, rangeInfo) => {
426
431
  };
427
432
  };
428
433
 
429
- const getFixedOriginalEnd = (end, mappings, state, rangeInfo) => {
434
+ const getFixedOriginalEnd = (end, mappings, state, cache) => {
430
435
 
431
436
  const [m1, m2] = mappings;
432
- const { originalState, crossEnd } = rangeInfo;
437
+ const { originalState, crossEnd } = cache;
433
438
 
434
439
  // cross file until e1 line end (not file end)
435
440
  if (crossEnd) {
436
- const originalEndOffset = getOriginalEndOffset(m1, rangeInfo);
441
+ const originalEndOffset = getOriginalEndOffset(m1, cache);
437
442
  return {
438
443
  originalEnd: originalEndOffset
439
444
  };
@@ -448,33 +453,8 @@ const getFixedOriginalEnd = (end, mappings, state, rangeInfo) => {
448
453
  };
449
454
  }
450
455
 
451
- // quick check next one if exact match
452
- // most of case is same string
453
- if (m2.generatedOffset === end + 1) {
454
- const gt = state.locator.source[end];
455
- const oi = m2.originalOffset - 1;
456
- const ot = originalState.locator.source[oi];
457
- // it could be ; but it quite far away
458
- if (gt === ot && [';', ','].includes(gt)) {
459
- return {
460
- originalEnd: oi
461
- };
462
- }
463
-
464
- // Not applicable
465
- // console.log('==================================');
466
- // console.log({
467
- // gp: state.sourcePath,
468
- // g: end,
469
- // gt,
470
- // op: originalState.sourcePath,
471
- // o: oi,
472
- // ot
473
- // });
474
- }
475
-
476
456
  const direction = 'end';
477
- const { originalText, originalOffset } = getOriginalText(m1, m2, rangeInfo);
457
+ const { originalText, originalOffset } = getOriginalText(m1, m2, cache);
478
458
  const { generatedText, generatedPos } = getGeneratedText(m1, m2, state, end);
479
459
 
480
460
  const info = {
@@ -506,23 +486,57 @@ const getFixedOriginalEnd = (end, mappings, state, rangeInfo) => {
506
486
 
507
487
  // ========================================================================================================
508
488
 
509
- const getOriginalStart = (start, startMapping, state, rangeInfo) => {
510
- if (Array.isArray(startMapping)) {
511
- return getFixedOriginalStart(start, startMapping, state, rangeInfo);
489
+ const getOriginalStartPosition = (cache, state) => {
490
+
491
+ const { start, startMappings } = cache;
492
+
493
+ if (Array.isArray(startMappings)) {
494
+ return getFixedOriginalStart(start, startMappings, state, cache);
512
495
  }
496
+
513
497
  // Exact match
514
498
  return {
515
- originalStart: startMapping.originalOffset
499
+ originalStart: startMappings.originalOffset
516
500
  };
517
501
  };
518
502
 
519
- const getOriginalEnd = (end, endMapping, state, rangeInfo) => {
520
- if (Array.isArray(endMapping)) {
521
- return getFixedOriginalEnd(end, endMapping, state, rangeInfo);
503
+ const getOriginalExclusiveEnd = (cache, state) => {
504
+ // end, exclusive mappings
505
+ const { end } = cache;
506
+ const { decodedMappings } = state;
507
+ const endMappings = findMapping(decodedMappings, end);
508
+ if (Array.isArray(endMappings)) {
509
+ return getFixedOriginalEnd(end, endMappings, state, cache);
522
510
  }
523
- // Exact match
511
+
512
+ // Exact match end
524
513
  return {
525
- originalEnd: endMapping.originalOffset
514
+ originalEnd: endMappings.originalOffset
515
+ };
516
+ };
517
+
518
+ const getOriginalEndPosition = (cache, state) => {
519
+
520
+ const { endMappings, originalState } = cache;
521
+
522
+ // (end - 1), inclusive
523
+ if (Array.isArray(endMappings)) {
524
+ return getOriginalExclusiveEnd(cache, state);
525
+ }
526
+
527
+ // Exact match (end - 1)
528
+
529
+ // check end char
530
+ const oi = endMappings.originalOffset;
531
+ const originalEndChar = originalState.locator.getSlice(oi, oi + 1);
532
+ // the char should never end with "{" or "("
533
+ if (['{', '('].includes(originalEndChar)) {
534
+ return getOriginalExclusiveEnd(cache, state);
535
+ }
536
+
537
+ // inclusive to exclusive
538
+ return {
539
+ originalEnd: oi + 1
526
540
  };
527
541
  };
528
542
 
@@ -536,9 +550,6 @@ const checkSourceFileIndexes = (startIndexes, endIndexes) => {
536
550
  sourceIndex: s1
537
551
  };
538
552
  }
539
- return {
540
- error: true
541
- };
542
553
  };
543
554
 
544
555
  const checkIndex21 = (s1, s2, e1) => {
@@ -553,9 +564,6 @@ const checkSourceFileIndexes = (startIndexes, endIndexes) => {
553
564
  sourceIndex: e1
554
565
  };
555
566
  }
556
- return {
557
- error: true
558
- };
559
567
  };
560
568
 
561
569
  const checkIndex12 = (s1, e1, e2) => {
@@ -570,9 +578,6 @@ const checkSourceFileIndexes = (startIndexes, endIndexes) => {
570
578
  sourceIndex: s1
571
579
  };
572
580
  }
573
- return {
574
- error: true
575
- };
576
581
  };
577
582
 
578
583
  const checkIndex22 = (s1, s2, e1, e2) => {
@@ -602,10 +607,6 @@ const checkSourceFileIndexes = (startIndexes, endIndexes) => {
602
607
  // both between two mappings: 38,39 ~ 38,39
603
608
  // if (s1 === e1 && s2 === e2) {
604
609
  // }
605
-
606
- return {
607
- error: true
608
- };
609
610
  };
610
611
 
611
612
  // both exact matched
@@ -626,13 +627,20 @@ const checkSourceFileIndexes = (startIndexes, endIndexes) => {
626
627
 
627
628
  };
628
629
 
629
- const getOriginalState = (startMappings, endMappings, originalMap) => {
630
+ const getOriginalState = (start, end, state, originalMap) => {
631
+
632
+ const mappingInfo = getMappingInfo(start, end, state);
633
+ if (mappingInfo.error) {
634
+ return mappingInfo;
635
+ }
636
+
637
+ const { startMappings, endMappings } = mappingInfo;
630
638
 
631
639
  // check source file indexes
632
640
  const startIndexes = [].concat(startMappings).map((it) => it.sourceIndex);
633
641
  const endIndexes = [].concat(endMappings).map((it) => it.sourceIndex);
634
- const rangeInfo = checkSourceFileIndexes(startIndexes, endIndexes);
635
- if (rangeInfo.error) {
642
+ const results = checkSourceFileIndexes(startIndexes, endIndexes);
643
+ if (!results) {
636
644
  return {
637
645
  error: true,
638
646
  errors: [`invalid source indexes: ${EC.yellow(`${startIndexes} ~ ${endIndexes}`)}`]
@@ -641,7 +649,7 @@ const getOriginalState = (startMappings, endMappings, originalMap) => {
641
649
 
642
650
  const {
643
651
  sourceIndex, crossStart, crossEnd
644
- } = rangeInfo;
652
+ } = results;
645
653
 
646
654
  // if (crossStart || crossEnd) {
647
655
  // console.log(EC.magenta('cross file'), EC.yellow(`${startIndexes} ~ ${endIndexes}`));
@@ -655,11 +663,17 @@ const getOriginalState = (startMappings, endMappings, originalMap) => {
655
663
  };
656
664
  }
657
665
 
666
+ // cache for original info
658
667
  return {
659
668
  // for debug
660
669
  // startIndexes,
661
670
  // endIndexes,
662
671
 
672
+ start,
673
+ end,
674
+ startMappings,
675
+ endMappings,
676
+
663
677
  sourceIndex,
664
678
  crossStart,
665
679
  crossEnd,
@@ -667,7 +681,9 @@ const getOriginalState = (startMappings, endMappings, originalMap) => {
667
681
  };
668
682
  };
669
683
 
670
- const getMappingInfo = (decodedMappings, start, end) => {
684
+ const getMappingInfo = (start, end, state) => {
685
+
686
+ const { decodedMappings } = state;
671
687
  // possible no length
672
688
  if (decodedMappings.length < 2) {
673
689
  return {
@@ -677,9 +693,6 @@ const getMappingInfo = (decodedMappings, start, end) => {
677
693
  }
678
694
 
679
695
  // start: inclusive
680
- // end: exclusive
681
- // could be 4 mappings found
682
-
683
696
  const startMappings = findMapping(decodedMappings, start);
684
697
  if (!startMappings) {
685
698
  return {
@@ -688,7 +701,8 @@ const getMappingInfo = (decodedMappings, start, end) => {
688
701
  };
689
702
  }
690
703
 
691
- const endMappings = findMapping(decodedMappings, end);
704
+ // end: exclusive
705
+ const endMappings = findMapping(decodedMappings, end - 1);
692
706
  if (!endMappings) {
693
707
  return {
694
708
  error: true,
@@ -696,6 +710,7 @@ const getMappingInfo = (decodedMappings, start, end) => {
696
710
  };
697
711
  }
698
712
 
713
+ // could be 4 mappings found
699
714
  return {
700
715
  startMappings,
701
716
  endMappings
@@ -704,7 +719,7 @@ const getMappingInfo = (decodedMappings, start, end) => {
704
719
 
705
720
  const findOriginalRange = (start, end, state, originalMap) => {
706
721
 
707
- const { sourcePath, decodedMappings } = state;
722
+ const { sourcePath } = state;
708
723
 
709
724
  const createMappingError = (errors) => {
710
725
  return {
@@ -716,29 +731,17 @@ const findOriginalRange = (start, end, state, originalMap) => {
716
731
  };
717
732
  };
718
733
 
719
- const mappingInfo = getMappingInfo(decodedMappings, start, end);
720
- if (mappingInfo.error) {
721
- return createMappingError(mappingInfo.errors);
722
- }
723
-
724
- const { startMappings, endMappings } = mappingInfo;
725
-
726
- const rangeInfo = getOriginalState(startMappings, endMappings, originalMap);
727
- if (rangeInfo.error) {
728
- return createMappingError(rangeInfo.errors);
729
- }
730
-
731
- const originalStartResult = getOriginalStart(start, startMappings, state, rangeInfo);
732
- if (originalStartResult.error) {
733
- return createMappingError(originalStartResult.errors);
734
- }
735
-
736
- const originalEndResult = getOriginalEnd(end, endMappings, state, rangeInfo);
737
- if (originalEndResult.error) {
738
- return createMappingError(originalEndResult.errors);
734
+ const cache = getOriginalState(start, end, state, originalMap);
735
+ if (cache.error) {
736
+ return createMappingError(cache.errors);
739
737
  }
740
738
 
739
+ const originalStartResult = getOriginalStartPosition(cache, state);
741
740
  const { originalStart } = originalStartResult;
741
+ // could be used for end
742
+ cache.originalStart = originalStart;
743
+
744
+ const originalEndResult = getOriginalEndPosition(cache, state);
742
745
  const { originalEnd } = originalEndResult;
743
746
 
744
747
  // range start > end
@@ -746,7 +749,7 @@ const findOriginalRange = (start, end, state, originalMap) => {
746
749
  return createMappingError([`invalid original start > end: ${EC.yellow(originalStart)} > ${EC.yellow(originalEnd)}`]);
747
750
  }
748
751
 
749
- const { originalState } = rangeInfo;
752
+ const { originalState } = cache;
750
753
 
751
754
  return {
752
755
  start: originalStart,
package/lib/generate.js CHANGED
@@ -2,7 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const CG = require('console-grid');
4
4
  const EC = require('eight-colors');
5
- const { pathToFileURL } = require('url');
5
+ const { pathToFileURL, fileURLToPath } = require('url');
6
6
 
7
7
  const Util = require('./utils/util.js');
8
8
  const {
@@ -12,6 +12,7 @@ const {
12
12
  const { mergeV8Coverage, saveV8Report } = require('./v8/v8.js');
13
13
 
14
14
  const { convertV8List } = require('./converter/converter.js');
15
+ const { resolveSourceMap } = require('./converter/collect-source-maps.js');
15
16
 
16
17
  const { minimatch } = require('./packages/monocart-coverage-vendor.js');
17
18
  const { getGroupedRows } = require('./utils/snapshot.js');
@@ -720,7 +721,135 @@ const getInputData = async (inputList, all, cacheDir, addHandler) => {
720
721
  };
721
722
  };
722
723
 
724
+ // ========================================================================================================
725
+
726
+ const resolveEntrySource = (entry, sourceMapCache, sourceCache) => {
727
+
728
+ const url = entry.url;
729
+
730
+ // source from `source-id.json`
731
+ const sourceData = sourceCache[url];
732
+ if (sourceData) {
733
+ entry.source = sourceData.source;
734
+ return;
735
+ }
736
+
737
+ // source for typescript file from source map cache
738
+ // Note: no runtime code but lineLengths
739
+ const tsExtensionsPattern = /\.([cm]?ts|[tj]sx)($|\?)/;
740
+ if (tsExtensionsPattern.test(url)) {
741
+ const sourcemapData = sourceMapCache[url];
742
+ const lineLengths = sourcemapData && sourcemapData.lineLengths;
743
+
744
+ // for fake source file (can not parse to AST)
745
+ if (lineLengths) {
746
+ let fakeSource = '';
747
+ sourcemapData.lineLengths.forEach((length) => {
748
+ fakeSource += `${''.padEnd(length, '*')}\n`;
749
+ });
750
+ entry.fake = true;
751
+ entry.source = fakeSource;
752
+ return;
753
+ }
754
+
755
+ }
756
+
757
+ // Note: it could be jsx format even extname is `.js`
758
+ const filePath = fileURLToPath(url);
759
+ if (fs.existsSync(filePath)) {
760
+ entry.source = fs.readFileSync(filePath).toString('utf8');
761
+ }
762
+
763
+ };
764
+
765
+ const resolveEntrySourceMap = (entry, sourceMapCache) => {
766
+ // sourcemap data
767
+ const sourcemapData = sourceMapCache[entry.url];
768
+ if (sourcemapData) {
769
+ if (sourcemapData.data) {
770
+ entry.sourceMap = resolveSourceMap(sourcemapData.data, entry.url);
771
+ }
772
+ }
773
+ };
774
+
775
+ const readCoverageData = (filePath, entryFilter, sourceCache) => {
776
+
777
+ const content = fs.readFileSync(filePath).toString('utf-8');
778
+ if (!content) {
779
+ return;
780
+ }
781
+
782
+ const json = JSON.parse(content);
783
+ let coverageData = json.result;
784
+ if (!Util.isList(coverageData)) {
785
+ return;
786
+ }
787
+
788
+ // filter node internal files, should no anonymous for nodejs
789
+ coverageData = coverageData.filter((entry) => entry.url && entry.url.startsWith('file:'));
790
+ coverageData = coverageData.filter(entryFilter);
791
+
792
+ if (!Util.isList(coverageData)) {
793
+ // Util.logDebug('No coverage data after filter');
794
+ return;
795
+ }
796
+
797
+ const sourceMapCache = json['source-map-cache'] || {};
798
+ for (const entry of coverageData) {
799
+ resolveEntrySource(entry, sourceMapCache, sourceCache);
800
+ resolveEntrySourceMap(entry, sourceMapCache);
801
+ }
802
+
803
+ return coverageData;
804
+ };
805
+
806
+ const readFromDir = async (dir, entryFilter, addHandler) => {
807
+
808
+ if (!dir || !fs.existsSync(dir)) {
809
+ Util.logError(`Invalid V8 coverage dir: ${dir}`);
810
+ return;
811
+ }
812
+
813
+ const files = fs.readdirSync(dir);
814
+ if (!files.length) {
815
+ Util.logInfo(`No coverage files in the dir: ${dir}`);
816
+ return;
817
+ }
818
+
819
+ const coverageFiles = [];
820
+ const sourceCache = {};
821
+ files.forEach((filename) => {
822
+ const isCoverage = filename.startsWith('coverage-');
823
+ if (isCoverage) {
824
+ coverageFiles.push(filename);
825
+ return;
826
+ }
827
+ const isSource = filename.startsWith('source-');
828
+ if (isSource) {
829
+ const filePath = path.resolve(dir, filename);
830
+ const content = fs.readFileSync(filePath).toString('utf-8');
831
+ if (content) {
832
+ const json = JSON.parse(content);
833
+ if (json.url) {
834
+ sourceCache[json.url] = json;
835
+ }
836
+ }
837
+ }
838
+ });
839
+
840
+ // filter before adding
841
+ for (const filename of coverageFiles) {
842
+ const filePath = path.resolve(dir, filename);
843
+ const coverageData = readCoverageData(filePath, entryFilter, sourceCache);
844
+ if (coverageData) {
845
+ await addHandler(coverageData);
846
+ }
847
+ }
848
+
849
+ };
850
+
723
851
  module.exports = {
724
852
  getInputData,
853
+ readFromDir,
725
854
  generateCoverageReports
726
855
  };
package/lib/index.d.ts CHANGED
@@ -347,6 +347,12 @@ declare namespace MCR {
347
347
  * @param coverageData {array} V8 format, {object} Istanbul format */
348
348
  add: (coverageData: any[] | any) => Promise<AddedResults>;
349
349
 
350
+ /**
351
+ * add V8 coverage from a dir
352
+ * @param dir node v8 coverage dir
353
+ */
354
+ addFromDir: (dir: string) => Promise<void>;
355
+
350
356
  /** generate report */
351
357
  generate: () => Promise<CoverageResults>;
352
358
 
package/lib/index.js CHANGED
@@ -7,7 +7,9 @@ const defaultOptions = require('./default/options.js');
7
7
  const Util = require('./utils/util.js');
8
8
 
9
9
  const { initV8ListAndSourcemap, getEntryFilter } = require('./v8/v8.js');
10
- const { getInputData, generateCoverageReports } = require('./generate.js');
10
+ const {
11
+ getInputData, readFromDir, generateCoverageReports
12
+ } = require('./generate.js');
11
13
  const { getSnapshot, diffSnapshot } = require('./utils/snapshot.js');
12
14
  const CDPClient = require('./client/cdp-client.js');
13
15
 
@@ -26,12 +28,15 @@ const resolveConfigOptions = async (configPath) => {
26
28
  err = ee;
27
29
  }
28
30
 
29
- if (err || !configOptions) {
31
+ if (err) {
30
32
  Util.logError(`ERROR: failed to load config "${configPath}": ${err && err.message} `);
31
33
  return;
32
34
  }
33
35
 
34
- configOptions = configOptions.default || configOptions;
36
+ // could be multiple level default
37
+ while (configOptions && configOptions.default) {
38
+ configOptions = configOptions.default;
39
+ }
35
40
 
36
41
  return configOptions;
37
42
  };
@@ -113,6 +118,14 @@ class CoverageReport {
113
118
  return results;
114
119
  }
115
120
 
121
+ // add coverage from dir
122
+ addFromDir(dir) {
123
+ const entryFilter = this.getEntryFilter();
124
+ return readFromDir(dir, entryFilter, (coverageData) => {
125
+ return this.add(coverageData);
126
+ });
127
+ }
128
+
116
129
  // generate report
117
130
  async generate() {
118
131
 
@@ -227,9 +240,9 @@ class CoverageReport {
227
240
  'mcr.config.cjs',
228
241
  'mcr.config.mjs',
229
242
  'mcr.config.json',
243
+ 'mcr.config.ts',
230
244
  '.mcrrc.js',
231
- '.mcrrc',
232
- 'mcr.config.ts'
245
+ '.mcrrc'
233
246
  ];
234
247
 
235
248
  const existsList = configList.filter((p) => p && fs.existsSync(p));
@@ -252,7 +265,8 @@ class CoverageReport {
252
265
  return;
253
266
  }
254
267
 
255
- Util.logInfo(`Loaded config: ${EC.cyan(configPath)}`);
268
+ Util.logInfo(`Loaded: ${EC.cyan(configPath)}`);
269
+
256
270
  // init options again
257
271
  this.options = {
258
272
  ... defaultOptions,