monocart-coverage-reports 2.9.3 → 2.10.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.
package/lib/generate.js CHANGED
@@ -1,38 +1,80 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const CG = require('console-grid');
4
- const EC = require('eight-colors');
5
3
  const { pathToFileURL, fileURLToPath } = require('url');
6
4
 
5
+ const EC = require('eight-colors');
6
+ const { minimatch } = require('./packages/monocart-coverage-vendor.js');
7
+
7
8
  const Util = require('./utils/util.js');
9
+ const { convertV8List } = require('./converter/converter.js');
10
+ const { resolveSourceMap } = require('./converter/collect-source-maps.js');
11
+
12
+ // ========================================================================================================
13
+ // built-in reports
14
+
15
+ // istanbul
8
16
  const {
9
17
  initIstanbulData, mergeIstanbulCoverage, saveIstanbulReports
10
18
  } = require('./istanbul/istanbul.js');
11
19
 
20
+ // v8
12
21
  const { mergeV8Coverage, saveV8Report } = require('./v8/v8.js');
13
22
 
14
- const { convertV8List } = require('./converter/converter.js');
15
- const { resolveSourceMap } = require('./converter/collect-source-maps.js');
23
+ // both
24
+ const { codecovReport } = require('./reports/codecov.js');
25
+ const { codacyReport } = require('./reports/codacy.js');
16
26
 
17
- const { minimatch } = require('minimatch');
18
- const { getGroupedRows } = require('./utils/snapshot.js');
19
- const generateMarkdownGrid = require('./utils/markdown.js');
27
+ const { consoleDetailsReport } = require('./reports/console-details.js');
28
+ const { consoleSummaryReport } = require('./reports/console-summary.js');
20
29
 
21
- // ========================================================================================================
30
+ const { markdownDetailsReport } = require('./reports/markdown-details.js');
31
+ const { markdownSummaryReport } = require('./reports/markdown-summary.js');
22
32
 
23
- // maybe v8 or to istanbul reports
24
- const generateV8ListReports = async (v8list, coverageData, fileSources, options) => {
25
- let istanbulReportPath;
26
- // v8 to istanbul reports
27
- if (options.reportGroup.istanbul) {
28
- const istanbulCoverageResults = await saveIstanbulReports(coverageData, fileSources, options);
29
- istanbulReportPath = istanbulCoverageResults.reportPath;
30
- }
33
+ const { rawReport } = require('./reports/raw.js');
31
34
 
32
- // v8 reports and v8 coverage results
33
- // could be no v8 or v8-json, but requires v8 coverage results
34
- const v8CoverageResults = await saveV8Report(v8list, options, istanbulReportPath);
35
- return v8CoverageResults;
35
+ const { customReport } = require('./reports/custom.js');
36
+
37
+ const allBuiltInReports = {
38
+ // v8
39
+ 'v8': 'v8',
40
+ 'v8-json': 'v8',
41
+
42
+ // istanbul
43
+ 'clover': 'istanbul',
44
+ 'cobertura': 'istanbul',
45
+ 'html': 'istanbul',
46
+ 'html-spa': 'istanbul',
47
+ 'json': 'istanbul',
48
+ 'json-summary': 'istanbul',
49
+ 'lcov': 'istanbul',
50
+ 'lcovonly': 'istanbul',
51
+ 'none': 'istanbul',
52
+ 'teamcity': 'istanbul',
53
+ 'text': 'istanbul',
54
+ 'text-lcov': 'istanbul',
55
+ 'text-summary': 'istanbul',
56
+
57
+ // both
58
+ 'codecov': 'both',
59
+ 'codacy': 'both',
60
+ 'console-details': 'both',
61
+ 'console-summary': 'both',
62
+ 'markdown-details': 'both',
63
+ 'markdown-summary': 'both',
64
+ 'raw': 'both'
65
+ };
66
+
67
+ const bothBuiltInReports = {
68
+ 'codecov': codecovReport,
69
+ 'codacy': codacyReport,
70
+
71
+ 'console-details': consoleDetailsReport,
72
+ 'console-summary': consoleSummaryReport,
73
+
74
+ 'markdown-details': markdownDetailsReport,
75
+ 'markdown-summary': markdownSummaryReport,
76
+
77
+ 'raw': rawReport
36
78
  };
37
79
 
38
80
  // ========================================================================================================
@@ -69,469 +111,54 @@ const getReportGroup = (reports, lcov, dataType) => {
69
111
  reportMap.lcovonly = {};
70
112
  }
71
113
 
72
- const allBuildInReports = {
73
- // v8
74
- 'v8': 'v8',
75
- 'v8-json': 'v8',
76
-
77
- // istanbul
78
- 'clover': 'istanbul',
79
- 'cobertura': 'istanbul',
80
- 'html': 'istanbul',
81
- 'html-spa': 'istanbul',
82
- 'json': 'istanbul',
83
- 'json-summary': 'istanbul',
84
- 'lcov': 'istanbul',
85
- 'lcovonly': 'istanbul',
86
- 'none': 'istanbul',
87
- 'teamcity': 'istanbul',
88
- 'text': 'istanbul',
89
- 'text-lcov': 'istanbul',
90
- 'text-summary': 'istanbul',
91
-
92
- // both
93
- 'codecov': 'both',
94
- 'codacy': 'both',
95
- 'console-details': 'both',
96
- 'console-summary': 'both',
97
- 'markdown-details': 'both',
98
- 'markdown-summary': 'both',
99
- 'raw': 'both'
100
- };
101
-
102
114
  // group v8 and istanbul
103
- const reportGroup = {};
115
+ const groupMap = new Map();
104
116
  Object.keys(reportMap).forEach((k) => {
105
117
  const options = reportMap[k];
106
118
 
107
- let type = allBuildInReports[k];
119
+ let type = allBuiltInReports[k];
108
120
  if (!type) {
109
121
  // for custom reporter
110
122
  type = options.type || 'v8';
111
123
  }
112
124
 
113
- let group = reportGroup[type];
125
+ let group = groupMap.get(type);
114
126
  if (!group) {
115
- group = {};
116
- reportGroup[type] = group;
127
+ group = new Map();
128
+ groupMap.set(type, group);
117
129
  }
118
130
 
119
- group[k] = options;
131
+ group.set(k, options);
120
132
 
121
133
  });
122
134
 
123
135
  // requires a default istanbul report if data is istanbul
124
- if (dataType === 'istanbul' && !reportGroup.istanbul) {
125
- reportGroup.istanbul = {
126
- html: {}
127
- };
136
+ if (dataType === 'istanbul' && !groupMap.has('istanbul')) {
137
+ const istanbulGroup = new Map();
138
+ istanbulGroup.set('html', {});
139
+ groupMap.set('istanbul', istanbulGroup);
128
140
  }
129
141
 
130
- return reportGroup;
142
+ return groupMap;
131
143
  };
132
144
 
133
145
  // ========================================================================================================
134
146
 
135
- const nFormatter = (v) => {
136
- if (typeof v === 'number') {
137
- return Util.NF(v);
138
- }
139
- return v;
140
- };
141
-
142
- const getRowData = (rowName, summary, metrics, color) => {
143
- const summaryRow = {};
144
- let lowest = {
145
- pct: 100,
146
- status: 'high'
147
- };
148
- metrics.map((k) => {
149
- const s = summary[k];
150
- if (!s) {
151
- return;
152
- }
153
- const percent = s.pct;
154
- if (typeof percent !== 'number') {
155
- return;
156
- }
157
- summaryRow[k] = Util.getColorStrByStatus(Util.PSF(percent, 100, 2), s.status, color);
158
- if (percent < lowest.pct) {
159
- lowest = s;
160
- }
161
- });
162
- summaryRow.nameStatus = lowest.status;
163
- summaryRow.name = rowName;
164
- return summaryRow;
165
- };
166
-
167
- const getDetailsRows = (files, metrics, cdOptions, color) => {
168
-
169
- const skipPercent = cdOptions.skipPercent;
170
- if (typeof skipPercent === 'number' && skipPercent > 0) {
171
- files = files.filter((file) => {
172
- const { summary } = file;
173
- for (const k of metrics) {
174
- const percent = summary[k].pct;
175
- if (typeof percent === 'number' && percent < skipPercent) {
176
- return true;
177
- }
178
- }
179
- return false;
180
- });
181
- }
182
-
183
- const flatRows = [];
184
- files.forEach((file) => {
185
-
186
- // do NOT add debug file
187
- if (file.debug) {
188
- return;
189
- }
190
-
191
- const { sourcePath, summary } = file;
192
- const fileRow = getRowData(sourcePath, summary, metrics, color);
193
- fileRow.uncoveredLines = Util.getUncoveredLines(file.data.lines, color);
194
- flatRows.push(fileRow);
195
- });
196
-
197
- return getGroupedRows(flatRows);
198
- };
199
-
200
- const handleCodecovReport = async (reportData, reportOptions, options) => {
201
- const codecovOptions = {
202
- outputFile: 'codecov.json',
203
- ... reportOptions
204
- };
205
-
206
- const jsonPath = path.resolve(options.outputDir, codecovOptions.outputFile);
207
-
208
- // https://docs.codecov.com/docs/codecov-custom-coverage-format
209
- const coverage = {};
210
- reportData.files.forEach((item) => {
211
- const { sourcePath, data } = item;
212
- coverage[sourcePath] = data.lines;
213
- });
214
- const codecovData = {
215
- coverage
216
- };
217
-
218
- await Util.writeFile(jsonPath, JSON.stringify(codecovData));
219
- return Util.relativePath(jsonPath);
220
- };
221
-
222
- const handleCodacyReport = async (reportData, reportOptions, options) => {
223
- const codacyOptions = {
224
- outputFile: 'codacy.json',
225
- ... reportOptions
226
- };
227
-
228
- const jsonPath = path.resolve(options.outputDir, codacyOptions.outputFile);
229
-
230
- // https://api.codacy.com/swagger#tocscoveragereport
231
- const fileReports = [];
232
- reportData.files.forEach((item) => {
233
- const { sourcePath, data } = item;
234
-
235
- const coverage = {};
236
- for (const [key, value] of Object.entries(data.lines)) {
237
- if (typeof value === 'number') {
238
- coverage[key] = value;
239
- } else {
240
- // partial coverage not supported?
241
- coverage[key] = 0;
242
- }
243
- }
244
-
245
- fileReports.push({
246
- filename: sourcePath,
247
- coverage
248
- });
249
- });
250
- const codacyData = {
251
- fileReports
252
- };
253
-
254
- await Util.writeFile(jsonPath, JSON.stringify(codacyData));
255
- return Util.relativePath(jsonPath);
256
- };
257
-
258
- const handleConsoleDetailsReport = (reportData, reportOptions, options) => {
259
- const cdOptions = {
260
- maxCols: 50,
261
- skipPercent: 0,
262
- metrics: [],
263
- ... reportOptions
264
- };
265
-
266
- const {
267
- type, name, summary, files
268
- } = reportData;
269
-
270
- if (name) {
271
- Util.logInfo(EC.cyan(name));
272
- }
273
-
274
- const color = 'ansicode';
275
-
276
- const metrics = Util.getMetrics(cdOptions.metrics, type);
277
- const rows = getDetailsRows(files, metrics, cdOptions, color);
278
- const summaryRow = getRowData('Summary', summary, metrics, color);
279
- if (summaryRow.nameStatus) {
280
- summaryRow.name = Util.getColorStrByStatus(summaryRow.name, summaryRow.nameStatus, color);
281
- }
282
- // no rows if skipped all by skipPercent
283
- if (rows.length) {
284
- rows.push({
285
- innerBorder: true
286
- });
287
- }
288
- rows.push(summaryRow);
289
-
290
- const columns = [{
291
- id: 'name',
292
- name: 'Name'
293
- }, ... metrics.map((m) => {
294
- return {
295
- id: m,
296
- name: Util.capitalizeFirstLetter(m),
297
- align: 'right'
298
- };
299
- }), {
300
- id: 'uncoveredLines',
301
- name: 'Uncovered Lines'
302
- }];
303
-
304
- return CG({
305
- options: {
306
- silent: cdOptions.silent,
307
- nullPlaceholder: '',
308
- defaultMaxWidth: cdOptions.maxCols
309
- },
310
- columns,
311
- rows
312
- });
313
- };
314
-
315
- const getSummaryColumns = (color) => {
316
-
317
- const columns = [{
318
- id: 'name',
319
- name: 'Name'
320
- }, {
321
- id: 'pct',
322
- name: 'Coverage %',
323
- align: 'right',
324
- formatter: (v, row, column) => {
325
- if (typeof v === 'number') {
326
- return Util.getColorStrByStatus(Util.PSF(v, 100, 2), row.status, color);
327
- }
328
- return v;
329
- }
330
- }, {
331
- id: 'covered',
332
- name: 'Covered',
333
- align: 'right',
334
- formatter: nFormatter
335
- }, {
336
- id: 'uncovered',
337
- name: 'Uncovered',
338
- align: 'right',
339
- formatter: nFormatter
340
- }, {
341
- id: 'total',
342
- name: 'Total',
343
- align: 'right',
344
- formatter: nFormatter
345
- }];
346
-
347
- return columns;
348
- };
349
-
350
- const handleConsoleSummaryReport = (reportData, reportOptions, options) => {
351
-
352
- const csOptions = {
353
- metrics: [],
354
- ... reportOptions
355
- };
356
-
357
- const {
358
- summary, name, type
359
- } = reportData;
360
-
361
- if (name) {
362
- Util.logInfo(EC.cyan(name));
363
- }
364
-
365
- const metrics = Util.getMetrics(csOptions.metrics, type);
366
-
367
- const rows = metrics.map((k) => {
368
- return {
369
- ... summary[k],
370
- name: Util.capitalizeFirstLetter(k)
371
- };
372
- });
373
-
374
- const columns = getSummaryColumns('ansicode');
375
-
376
- CG({
377
- columns,
378
- rows
379
- });
380
- };
381
-
382
- const handleMarkdownDetailsReport = async (reportData, reportOptions, options) => {
383
- const mdOptions = {
384
- baseUrl: '',
385
- // unicode, html, or empty for no color
386
- color: 'unicode',
387
- skipPercent: 0,
388
- metrics: [],
389
- outputFile: 'coverage-details.md',
390
- ... reportOptions
391
- };
392
-
393
- const color = Util.normalizeColorType(mdOptions.color);
394
- const baseUrl = mdOptions.baseUrl;
395
-
396
- const mdPath = path.resolve(options.outputDir, mdOptions.outputFile);
397
-
398
- const metrics = Util.getMetrics(mdOptions.metrics, reportData.type);
399
-
400
- let files = reportData.files;
401
- const skipPercent = mdOptions.skipPercent;
402
- if (typeof skipPercent === 'number' && skipPercent > 0) {
403
- files = files.filter((file) => {
404
- const { summary } = file;
405
- for (const k of metrics) {
406
- const percent = summary[k].pct;
407
- if (typeof percent === 'number' && percent < skipPercent) {
408
- return true;
409
- }
410
- }
411
- return false;
412
- });
413
- }
414
-
415
- const rows = [];
416
- files.forEach((file) => {
417
-
418
- // do NOT add debug file
419
- if (file.debug) {
420
- return;
421
- }
422
-
423
- const { sourcePath, summary } = file;
424
-
425
- const fileRow = getRowData(sourcePath, summary, metrics, color);
426
- if (fileRow.nameStatus) {
427
- const nameColor = baseUrl && color === 'tex' ? '' : color;
428
- fileRow.name = Util.getColorStrByStatus(fileRow.name, fileRow.nameStatus, nameColor);
429
- }
430
- if (baseUrl) {
431
- fileRow.name = `[${fileRow.name}](${baseUrl + sourcePath})`;
432
- }
433
-
434
- fileRow.uncoveredLines = Util.getUncoveredLines(file.data.lines, color);
435
- rows.push(fileRow);
436
- });
437
-
438
- const summaryRow = getRowData('Summary', reportData.summary, metrics, color);
439
- if (summaryRow.nameStatus) {
440
- summaryRow.name = Util.getColorStrByStatus(summaryRow.name, summaryRow.nameStatus, color);
441
- }
442
- rows.push(summaryRow);
443
-
444
- const columns = [{
445
- id: 'name',
446
- name: 'Name'
447
- }, ... metrics.map((m) => {
448
- return {
449
- id: m,
450
- name: Util.capitalizeFirstLetter(m),
451
- align: 'right'
452
- };
453
- }), {
454
- id: 'uncoveredLines',
455
- name: 'Uncovered Lines'
456
- }];
457
-
458
- const markdownGrid = generateMarkdownGrid({
459
- options: {
460
- name: reportData.name
461
- },
462
- columns,
463
- rows
464
- });
465
-
466
- await Util.writeFile(mdPath, markdownGrid);
467
- return Util.relativePath(mdPath);
468
- };
469
-
470
- const handleMarkdownSummaryReport = async (reportData, reportOptions, options) => {
471
- const msOptions = {
472
- // unicode, html, or empty for no color
473
- color: 'unicode',
474
- metrics: [],
475
- outputFile: 'coverage-summary.md',
476
- ... reportOptions
477
- };
478
-
479
- const color = Util.normalizeColorType(msOptions.color);
480
-
481
- const mdPath = path.resolve(options.outputDir, msOptions.outputFile);
482
-
483
- const metrics = Util.getMetrics(msOptions.metrics, reportData.type);
484
-
485
- const rows = metrics.map((k) => {
486
- return {
487
- ... reportData.summary[k],
488
- name: Util.capitalizeFirstLetter(k)
489
- };
490
- });
491
-
492
- const columns = getSummaryColumns(color);
493
-
494
- const markdownGrid = generateMarkdownGrid({
495
- options: {
496
- name: reportData.name
497
- },
498
- columns,
499
- rows
500
- });
501
-
502
- await Util.writeFile(mdPath, markdownGrid);
503
- return Util.relativePath(mdPath);
504
- };
505
-
506
- const handleRawReport = (reportData, reportOptions, options) => {
507
- const rawOptions = {
508
- outputDir: 'raw',
509
- ... reportOptions
510
- };
511
-
512
- const cacheDir = options.cacheDir;
513
- const rawDir = path.resolve(options.outputDir, rawOptions.outputDir);
514
- // console.log(rawDir, cacheDir);
515
- if (fs.existsSync(rawDir)) {
516
- Util.logError(`Failed to save raw report because the dir already exists: ${Util.relativePath(rawDir)}`);
517
- return;
518
- }
519
-
520
- const rawParent = path.dirname(rawDir);
521
- if (!fs.existsSync(rawParent)) {
522
- fs.mkdirSync(rawParent, {
523
- recursive: true
524
- });
147
+ // maybe v8 or to istanbul reports
148
+ const generateV8ListReports = async (v8list, coverageData, fileSources, options) => {
149
+ let istanbulReportPath;
150
+ // v8 to istanbul reports
151
+ if (options.reportGroup.has('istanbul')) {
152
+ const istanbulCoverageResults = await saveIstanbulReports(coverageData, fileSources, options);
153
+ istanbulReportPath = istanbulCoverageResults.reportPath;
525
154
  }
526
155
 
527
- // just rename the cache folder name
528
- fs.renameSync(cacheDir, rawDir);
529
-
156
+ // v8 reports and v8 coverage results
157
+ // could be no v8 or v8-json, but requires v8 coverage results
158
+ const v8CoverageResults = await saveV8Report(v8list, options, istanbulReportPath);
159
+ return v8CoverageResults;
530
160
  };
531
161
 
532
-
533
- // ========================================================================================================
534
-
535
162
  const getCoverageResults = async (dataList, sourceCache, options) => {
536
163
  // get first and check v8list or istanbul data
537
164
  const firstData = dataList[0];
@@ -575,27 +202,15 @@ const generateCoverageReports = async (dataList, sourceCache, options) => {
575
202
  // [ 'type', 'reportPath', 'name', 'watermarks', 'summary', 'files' ]
576
203
  // console.log(Object.keys(coverageResults));
577
204
 
578
- const buildInBothReports = {
579
- 'codecov': handleCodecovReport,
580
- 'codacy': handleCodacyReport,
581
- 'console-details': handleConsoleDetailsReport,
582
- 'console-summary': handleConsoleSummaryReport,
583
- 'markdown-details': handleMarkdownDetailsReport,
584
- 'markdown-summary': handleMarkdownSummaryReport,
585
- 'raw': handleRawReport
586
- };
587
-
588
- const bothGroup = options.reportGroup.both;
589
- if (bothGroup) {
590
- const bothReports = Object.keys(bothGroup);
591
- for (const reportName of bothReports) {
592
- const reportOptions = bothGroup[reportName];
593
- const buildInHandler = buildInBothReports[reportName];
205
+ if (options.reportGroup.has('both')) {
206
+ const bothGroup = options.reportGroup.get('both');
207
+ for (const [reportName, reportOptions] of bothGroup) {
208
+ const builtInHandler = bothBuiltInReports[reportName];
594
209
  const t1 = Date.now();
595
- if (buildInHandler) {
596
- await buildInHandler(coverageResults, reportOptions, options);
210
+ if (builtInHandler) {
211
+ await builtInHandler(coverageResults, reportOptions, options);
597
212
  } else {
598
- await Util.runCustomReporter(reportName, coverageResults, reportOptions, options);
213
+ await customReport(reportName, coverageResults, reportOptions, options);
599
214
  }
600
215
  Util.logTime(`┌ [generate] saved report: ${reportName}`, t1);
601
216
  }
package/lib/index.d.ts CHANGED
@@ -129,6 +129,9 @@ declare namespace MCR {
129
129
  maxCols?: number;
130
130
  skipPercent?: number;
131
131
  metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">;
132
+ filter?: string | {
133
+ [pattern: string]: boolean;
134
+ } | ((file: CoverageFile) => boolean);
132
135
  }] |
133
136
  ['markdown-summary'] | ['markdown-summary', {
134
137
  color: 'unicode' | 'html' | 'tex' | string;
@@ -141,8 +144,12 @@ declare namespace MCR {
141
144
  ['markdown-details'] | ['markdown-details', {
142
145
  baseUrl?: string;
143
146
  color: 'unicode' | 'html' | 'tex' | string;
147
+ maxCols?: number;
144
148
  skipPercent?: number;
145
149
  metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">;
150
+ filter?: string | {
151
+ [pattern: string]: boolean;
152
+ } | ((file: CoverageFile) => boolean);
146
153
  /**
147
154
  * defaults to `coverage-details.md`
148
155
  */
@@ -190,14 +197,23 @@ declare namespace MCR {
190
197
  start: number;
191
198
  end: number;
192
199
  count: number;
200
+ /** ignored by special comment which starts with `v8 ignore` */
193
201
  ignored?: boolean;
202
+ /**
203
+ * branch only, for example:
204
+ * there is only `if` branch but no `else` branch, then `none` will be true, it shows `else path uncovered`
205
+ */
194
206
  none?: boolean;
207
+ /** function only, function name */
208
+ name?: boolean;
195
209
  }
196
210
 
197
211
  export interface IgnoredRange {
198
212
  start: number;
199
213
  end: number;
214
+ /** ignore type: `next` or `range` */
200
215
  type: string;
216
+ /** n lines for `next` type */
201
217
  n?: number;
202
218
  }
203
219
 
@@ -246,6 +262,7 @@ declare namespace MCR {
246
262
  export type CoverageResults = {
247
263
  type: "v8" | "istanbul";
248
264
  reportPath: string;
265
+ version: string;
249
266
  name: string;
250
267
  watermarks: Watermarks;
251
268
  summary: CoverageSummary;
@@ -315,7 +332,11 @@ declare namespace MCR {
315
332
  *
316
333
  * {object} Replace key with value.
317
334
  * */
318
- sourcePath?: ((filePath: string, info: any) => string) | {
335
+ sourcePath?: ((filePath: string, info: {
336
+ /** the related dist file of current source file */
337
+ distFile?: string;
338
+ [key: string]: any;
339
+ }) => string) | {
319
340
  [key: string]: string;
320
341
  };
321
342