monocart-coverage-reports 2.5.9 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/generate.js CHANGED
@@ -1,532 +1,720 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const CG = require('console-grid');
4
- const EC = require('eight-colors');
5
- const Util = require('./utils/util.js');
6
- const {
7
- initIstanbulData, mergeIstanbulCoverage, saveIstanbulReports
8
- } = require('./istanbul/istanbul.js');
9
-
10
- const { mergeV8Coverage, saveV8Report } = require('./v8/v8.js');
11
-
12
- const { convertV8List } = require('./converter/converter.js');
13
-
14
- const { minimatch } = require('./packages/monocart-coverage-vendor.js');
15
- const { pathToFileURL } = require('url');
16
-
17
- // ========================================================================================================
18
-
19
- // maybe v8 or to istanbul reports
20
- const generateV8ListReports = async (v8list, coverageData, fileSources, options) => {
21
-
22
- let report;
23
-
24
- const { v8, istanbul } = options.reportGroup;
25
-
26
- // generate istanbul reports first
27
- if (istanbul) {
28
- report = await saveIstanbulReports(coverageData, fileSources, options);
29
- }
30
-
31
- // generate v8 report
32
- if (v8) {
33
- report = await saveV8Report(v8list, options);
34
- }
35
-
36
- return report;
37
- };
38
-
39
- // ========================================================================================================
40
-
41
- const getReportGroup = (reports, lcov, dataType) => {
42
-
43
- const reportMap = {};
44
-
45
- const reportList = Util.toList(reports, ',');
46
- reportList.forEach((it) => {
47
- if (Util.isList(it)) {
48
- // ["v8"], ["v8", {}]
49
- const id = it[0];
50
- if (typeof id === 'string' && id) {
51
- reportMap[id] = {
52
- ... it[1]
53
- };
54
- }
55
- return;
56
- }
57
- if (typeof it === 'string' && it) {
58
- reportMap[it] = {};
59
- }
60
- });
61
-
62
- // using default report if no reports
63
- if (!Object.keys(reportMap).length) {
64
- const defaultReport = dataType === 'v8' ? 'v8' : 'html';
65
- reportMap[defaultReport] = {};
66
- }
67
-
68
- // add lcovonly report after default report
69
- if (lcov && !reportMap.lcovonly) {
70
- reportMap.lcovonly = {};
71
- }
72
-
73
- const allBuildInReports = {
74
- // v8
75
- 'v8': 'v8',
76
- 'v8-json': 'v8',
77
- 'codecov': 'v8',
78
- 'console-details': 'v8',
79
-
80
- // istanbul
81
- 'clover': 'istanbul',
82
- 'cobertura': 'istanbul',
83
- 'html': 'istanbul',
84
- 'html-spa': 'istanbul',
85
- 'json': 'istanbul',
86
- 'json-summary': 'istanbul',
87
- 'lcov': 'istanbul',
88
- 'lcovonly': 'istanbul',
89
- 'none': 'istanbul',
90
- 'teamcity': 'istanbul',
91
- 'text': 'istanbul',
92
- 'text-lcov': 'istanbul',
93
- 'text-summary': 'istanbul',
94
-
95
- // both
96
- 'console-summary': 'both',
97
- 'raw': 'both'
98
- };
99
-
100
- // group v8 and istanbul
101
- const reportGroup = {};
102
- Object.keys(reportMap).forEach((k) => {
103
- const options = reportMap[k];
104
-
105
- let type = allBuildInReports[k];
106
- if (!type) {
107
- // for custom reporter
108
- type = options.type || 'v8';
109
- }
110
-
111
- let group = reportGroup[type];
112
- if (!group) {
113
- group = {};
114
- reportGroup[type] = group;
115
- }
116
-
117
- group[k] = options;
118
-
119
- });
120
-
121
- // requires a default istanbul report if data is istanbul
122
- if (dataType === 'istanbul' && !reportGroup.istanbul) {
123
- reportGroup.istanbul = {
124
- html: {}
125
- };
126
- }
127
-
128
- return reportGroup;
129
- };
130
-
131
- // ========================================================================================================
132
-
133
- const pFormatter = (v, row, column) => {
134
- if (typeof v === 'number') {
135
- return Util.getColorStrByStatus(Util.PSF(v, 100, 2), row.status);
136
- }
137
- return v;
138
- };
139
-
140
- const nFormatter = (v) => {
141
- if (typeof v === 'number') {
142
- return Util.NF(v);
143
- }
144
- return v;
145
- };
146
-
147
- const handleConsoleSummaryReport = (reportData, reportOptions, options) => {
148
-
149
- const csOptions = {
150
- metrics: [],
151
- ... reportOptions
152
- };
153
-
154
- const {
155
- summary, name, type
156
- } = reportData;
157
-
158
- if (name) {
159
- EC.logCyan(name);
160
- }
161
-
162
- const metrics = Util.getMetrics(type, csOptions.metrics);
163
-
164
- const rows = metrics.map((k) => {
165
- return {
166
- ... summary[k],
167
- name: Util.capitalizeFirstLetter(k)
168
- };
169
- });
170
-
171
- const columns = [{
172
- id: 'name',
173
- name: 'Name'
174
- }, {
175
- id: 'pct',
176
- name: 'Coverage %',
177
- align: 'right',
178
- formatter: pFormatter
179
- }, {
180
- id: 'covered',
181
- name: 'Covered',
182
- align: 'right',
183
- formatter: nFormatter
184
- }, {
185
- id: 'uncovered',
186
- name: 'Uncovered',
187
- align: 'right',
188
- formatter: nFormatter
189
- }, {
190
- id: 'total',
191
- name: 'Total',
192
- align: 'right',
193
- formatter: nFormatter
194
- }];
195
-
196
- CG({
197
- columns,
198
- rows
199
- });
200
- };
201
-
202
- const handleRawReport = (reportData, reportOptions, options) => {
203
- const rawOptions = {
204
- outputDir: 'raw',
205
- ... reportOptions
206
- };
207
-
208
- const cacheDir = options.cacheDir;
209
- const rawDir = path.resolve(options.outputDir, rawOptions.outputDir);
210
- // console.log(rawDir, cacheDir);
211
- if (fs.existsSync(rawDir)) {
212
- Util.logError(`Failed to save raw report because the dir already exists: ${Util.relativePath(rawDir)}`);
213
- return;
214
- }
215
-
216
- const rawParent = path.dirname(rawDir);
217
- if (!fs.existsSync(rawParent)) {
218
- fs.mkdirSync(rawParent, {
219
- recursive: true
220
- });
221
- }
222
-
223
- // just rename the cache folder name
224
- fs.renameSync(cacheDir, rawDir);
225
-
226
- };
227
-
228
-
229
- // ========================================================================================================
230
-
231
- const getCoverageResults = async (dataList, sourceCache, options) => {
232
- // get first and check v8list or istanbul data
233
- const firstData = dataList[0];
234
- const dataType = firstData.type;
235
- // console.log('data type', dataType);
236
-
237
- // init reports
238
- options.reportGroup = getReportGroup(options.reports, options.lcov, dataType);
239
- // console.log('reportGroup', options.reportGroup);
240
-
241
- // v8list
242
- if (dataType === 'v8') {
243
- // merge v8list first
244
- const v8list = await mergeV8Coverage(dataList, sourceCache, options);
245
- // console.log('after merge', v8list.map((it) => it.url));
246
-
247
- // only debug level
248
- const time_start = Date.now();
249
- const results = convertV8List(v8list, options);
250
- const {
251
- v8DataList, coverageData, fileSources
252
- } = results;
253
- Util.logTime(`converted v8 list: ${v8DataList.length}`, time_start);
254
-
255
- return generateV8ListReports(v8DataList, coverageData, fileSources, options);
256
- }
257
-
258
- // istanbul data
259
- const istanbulData = mergeIstanbulCoverage(dataList);
260
- const { coverageData, fileSources } = initIstanbulData(istanbulData, options);
261
- return saveIstanbulReports(coverageData, fileSources, options);
262
- };
263
-
264
- const generateCoverageReports = async (dataList, sourceCache, options) => {
265
- const coverageResults = await getCoverageResults(dataList, sourceCache, options);
266
-
267
- // [ 'type', 'reportPath', 'name', 'watermarks', 'summary', 'files' ]
268
- // console.log(Object.keys(coverageResults));
269
-
270
- const buildInBothReports = {
271
- 'console-summary': handleConsoleSummaryReport,
272
- 'raw': handleRawReport
273
- };
274
-
275
- const bothGroup = options.reportGroup.both;
276
- if (bothGroup) {
277
- const bothReports = Object.keys(bothGroup);
278
- for (const reportName of bothReports) {
279
- const reportOptions = bothGroup[reportName];
280
- const buildInHandler = buildInBothReports[reportName];
281
- if (buildInHandler) {
282
- await buildInHandler(coverageResults, reportOptions, options);
283
- } else {
284
- await Util.runCustomReporter(reportName, coverageResults, reportOptions, options);
285
- }
286
- }
287
- }
288
-
289
- return coverageResults;
290
- };
291
-
292
- // ========================================================================================================
293
-
294
- const resolveAllDirList = (input) => {
295
- const dirs = Util.toList(input, ',');
296
- if (!dirs.length) {
297
- return;
298
- }
299
- const dirList = dirs.filter((it) => fs.existsSync(it));
300
- if (!dirList.length) {
301
- return;
302
- }
303
- return dirList;
304
- };
305
-
306
- const resolveAllFilter = (input) => {
307
- // for function handler
308
- if (typeof input === 'function') {
309
- return input;
310
- }
311
-
312
- // for single minimatch pattern
313
- if (input && typeof input === 'string') {
314
- return (filePath) => {
315
- return minimatch(filePath, input);
316
- };
317
- }
318
-
319
- // for patterns
320
- if (input && typeof input === 'object') {
321
- const patterns = Object.keys(input);
322
- return (filePath) => {
323
- for (const pattern of patterns) {
324
- if (minimatch(filePath, pattern)) {
325
- return input[pattern];
326
- }
327
- }
328
- // false if not matched
329
- };
330
- }
331
-
332
- // default
333
- return () => true;
334
- };
335
-
336
- const resolveAllDataType = (type, dataList) => {
337
- if (!type && dataList.length) {
338
- const firstData = dataList[0];
339
- type = firstData.type;
340
- }
341
- const types = {
342
- v8: 'v8',
343
- istanbul: 'istanbul'
344
- };
345
- return types[type] || types.v8;
346
- };
347
-
348
- const resolveAllOptions = (input, dataList) => {
349
- if (!input) {
350
- return;
351
- }
352
-
353
- let type;
354
- let dir;
355
- let filter;
356
- if (typeof input === 'string' || Array.isArray(input)) {
357
- dir = input;
358
- filter = () => true;
359
- } else {
360
- type = input.type;
361
- dir = input.dir;
362
- filter = input.filter;
363
- }
364
-
365
- const dirList = resolveAllDirList(dir);
366
- if (!dirList) {
367
- return;
368
- }
369
-
370
- const dataType = resolveAllDataType(type, dataList);
371
- const fileFilter = resolveAllFilter(filter);
372
-
373
- return {
374
- dirList,
375
- dataType,
376
- fileFilter
377
- };
378
- };
379
-
380
- const resolveFileType = (fileType, filePath) => {
381
- if (fileType === 'js' || fileType === 'css') {
382
- return fileType;
383
- }
384
- const extname = path.extname(filePath);
385
- if (['.css', '.scss', '.sass', '.less'].includes(extname)) {
386
- return 'css';
387
- }
388
- return 'js';
389
- };
390
-
391
- const getV8EmptyData = (fileList) => {
392
- const v8DataList = [];
393
- fileList.forEach((item) => {
394
- const { filePath, fileType } = item;
395
- const type = resolveFileType(fileType, filePath);
396
- const url = pathToFileURL(filePath).toString();
397
- const source = Util.readFileSync(filePath);
398
- const entryFile = {
399
- empty: true,
400
- type,
401
- url,
402
- // for empty, css supports both source and text
403
- source
404
- };
405
- v8DataList.push(entryFile);
406
- });
407
- return v8DataList;
408
- };
409
-
410
- const getIstanbulEmptyData = (fileList) => {
411
- const istanbulData = {};
412
- fileList.forEach((item) => {
413
- const { filePath } = item;
414
- istanbulData[filePath] = {
415
- path: filePath,
416
- statementMap: {},
417
- fnMap: {},
418
- branchMap: {},
419
- s: {},
420
- f: {},
421
- b: {}
422
- };
423
- });
424
- return istanbulData;
425
- };
426
-
427
- const getEmptyData = (all, dataList) => {
428
- const allOptions = resolveAllOptions(all, dataList);
429
- // console.log('allOptions', all, allOptions);
430
- if (!allOptions) {
431
- return;
432
- }
433
- const {
434
- dirList, dataType, fileFilter
435
- } = allOptions;
436
- const fileList = [];
437
- dirList.forEach((dir) => {
438
- Util.forEachFile(dir, [], (fileName, fileDir) => {
439
- const filePath = path.resolve(fileDir, fileName);
440
- const fileType = fileFilter(filePath);
441
- if (fileType) {
442
- fileList.push({
443
- filePath,
444
- fileType
445
- });
446
- }
447
- });
448
- });
449
- // console.log('fileList', fileList);
450
- if (!fileList.length) {
451
- return;
452
- }
453
- if (dataType === 'v8') {
454
- return getV8EmptyData(fileList);
455
- }
456
- return getIstanbulEmptyData(fileList);
457
- };
458
-
459
- // ========================================================================================================
460
-
461
-
462
- const getInputData = async (inputList, all, cacheDir, addHandler) => {
463
- const dataList = [];
464
- const sourceCache = new Map();
465
-
466
- const addJsonData = async (dir, filename) => {
467
- const isCoverage = filename.startsWith('coverage-');
468
- const isSource = filename.startsWith('source-');
469
- if (isCoverage || isSource) {
470
- const content = await Util.readFile(path.resolve(dir, filename));
471
- if (content) {
472
- const json = JSON.parse(content);
473
- if (isCoverage) {
474
- dataList.push(json);
475
- } else {
476
- sourceCache.set(json.id, json);
477
- }
478
- }
479
- }
480
- };
481
-
482
- for (const dir of inputList) {
483
- const allFiles = fs.readdirSync(dir);
484
- if (!allFiles.length) {
485
- continue;
486
- }
487
- for (const filename of allFiles) {
488
- // only json file
489
- if (filename.endsWith('.json')) {
490
- await addJsonData(dir, filename);
491
- }
492
- }
493
- }
494
-
495
- // add empty coverage for all files
496
- // dataList for data type, before `add` we don't which one is tested, because using hash id
497
- const emptyData = getEmptyData(all, dataList);
498
- if (emptyData) {
499
- const coverageResults = await addHandler(emptyData);
500
- // after `add`
501
- if (coverageResults) {
502
- dataList.push(coverageResults);
503
- if (coverageResults.type === 'v8') {
504
- // sourceCache
505
- coverageResults.data.forEach((entry) => {
506
- const { id } = entry;
507
- if (sourceCache.has(id)) {
508
- // console.log('+', entry.sourcePath);
509
- return;
510
- }
511
- // console.log('-', entry.sourcePath);
512
- const sourcePath = Util.resolveCacheSourcePath(cacheDir, id);
513
- const content = Util.readFileSync(sourcePath);
514
- if (content) {
515
- const json = JSON.parse(content);
516
- sourceCache.set(json.id, json);
517
- }
518
- });
519
- }
520
- }
521
- }
522
-
523
- return {
524
- dataList,
525
- sourceCache
526
- };
527
- };
528
-
529
- module.exports = {
530
- getInputData,
531
- generateCoverageReports
532
- };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const CG = require('console-grid');
4
+ const EC = require('eight-colors');
5
+ const { pathToFileURL } = require('url');
6
+
7
+ const Util = require('./utils/util.js');
8
+ const {
9
+ initIstanbulData, mergeIstanbulCoverage, saveIstanbulReports
10
+ } = require('./istanbul/istanbul.js');
11
+
12
+ const { mergeV8Coverage, saveV8Report } = require('./v8/v8.js');
13
+
14
+ const { convertV8List } = require('./converter/converter.js');
15
+
16
+ const { minimatch } = require('./packages/monocart-coverage-vendor.js');
17
+ const { getGroupedRows } = require('./utils/snapshot.js');
18
+
19
+ // ========================================================================================================
20
+
21
+ // maybe v8 or to istanbul reports
22
+ const generateV8ListReports = async (v8list, coverageData, fileSources, options) => {
23
+
24
+ let report;
25
+
26
+ const { v8, istanbul } = options.reportGroup;
27
+
28
+ // generate istanbul reports first
29
+ if (istanbul) {
30
+ report = await saveIstanbulReports(coverageData, fileSources, options);
31
+ }
32
+
33
+ // generate v8 report
34
+ if (v8) {
35
+ report = await saveV8Report(v8list, options);
36
+ }
37
+
38
+ return report;
39
+ };
40
+
41
+ // ========================================================================================================
42
+
43
+ const getReportGroup = (reports, lcov, dataType) => {
44
+
45
+ const reportMap = {};
46
+
47
+ const reportList = Util.toList(reports, ',');
48
+ reportList.forEach((it) => {
49
+ if (Util.isList(it)) {
50
+ // ["v8"], ["v8", {}]
51
+ const id = it[0];
52
+ if (typeof id === 'string' && id) {
53
+ reportMap[id] = {
54
+ ... it[1]
55
+ };
56
+ }
57
+ return;
58
+ }
59
+ if (typeof it === 'string' && it) {
60
+ reportMap[it] = {};
61
+ }
62
+ });
63
+
64
+ // using default report if no reports
65
+ if (!Object.keys(reportMap).length) {
66
+ const defaultReport = dataType === 'v8' ? 'v8' : 'html';
67
+ reportMap[defaultReport] = {};
68
+ }
69
+
70
+ // add lcovonly report after default report
71
+ if (lcov && !reportMap.lcovonly) {
72
+ reportMap.lcovonly = {};
73
+ }
74
+
75
+ const allBuildInReports = {
76
+ // v8
77
+ 'v8': 'v8',
78
+ 'v8-json': 'v8',
79
+
80
+ // istanbul
81
+ 'clover': 'istanbul',
82
+ 'cobertura': 'istanbul',
83
+ 'html': 'istanbul',
84
+ 'html-spa': 'istanbul',
85
+ 'json': 'istanbul',
86
+ 'json-summary': 'istanbul',
87
+ 'lcov': 'istanbul',
88
+ 'lcovonly': 'istanbul',
89
+ 'none': 'istanbul',
90
+ 'teamcity': 'istanbul',
91
+ 'text': 'istanbul',
92
+ 'text-lcov': 'istanbul',
93
+ 'text-summary': 'istanbul',
94
+
95
+ // both
96
+ 'codecov': 'both',
97
+ 'console-details': 'both',
98
+ 'console-summary': 'both',
99
+ 'raw': 'both'
100
+ };
101
+
102
+ // group v8 and istanbul
103
+ const reportGroup = {};
104
+ Object.keys(reportMap).forEach((k) => {
105
+ const options = reportMap[k];
106
+
107
+ let type = allBuildInReports[k];
108
+ if (!type) {
109
+ // for custom reporter
110
+ type = options.type || 'v8';
111
+ }
112
+
113
+ let group = reportGroup[type];
114
+ if (!group) {
115
+ group = {};
116
+ reportGroup[type] = group;
117
+ }
118
+
119
+ group[k] = options;
120
+
121
+ });
122
+
123
+ // requires a default istanbul report if data is istanbul
124
+ if (dataType === 'istanbul' && !reportGroup.istanbul) {
125
+ reportGroup.istanbul = {
126
+ html: {}
127
+ };
128
+ }
129
+
130
+ return reportGroup;
131
+ };
132
+
133
+ // ========================================================================================================
134
+
135
+ const pFormatter = (v, row, column) => {
136
+ if (typeof v === 'number') {
137
+ return Util.getColorStrByStatus(Util.PSF(v, 100, 2), row.status);
138
+ }
139
+ return v;
140
+ };
141
+
142
+ const nFormatter = (v) => {
143
+ if (typeof v === 'number') {
144
+ return Util.NF(v);
145
+ }
146
+ return v;
147
+ };
148
+
149
+ const getRowData = (rowName, summary, metrics) => {
150
+ const summaryRow = {};
151
+ let lowest = {
152
+ pct: 100,
153
+ status: 'high'
154
+ };
155
+ metrics.map((k) => {
156
+ const s = summary[k];
157
+ if (!s) {
158
+ return;
159
+ }
160
+ const percent = s.pct;
161
+ if (typeof percent !== 'number') {
162
+ return;
163
+ }
164
+ summaryRow[k] = Util.getColorStrByStatus(Util.PSF(percent, 100, 2), s.status);
165
+ if (percent < lowest.pct) {
166
+ lowest = s;
167
+ }
168
+ });
169
+ summaryRow.nameStatus = lowest.status;
170
+ summaryRow.name = rowName;
171
+ return summaryRow;
172
+ };
173
+
174
+ const getUncoveredLines = (file) => {
175
+ const lines = [];
176
+
177
+ const dataLines = file.data.lines;
178
+
179
+ let startLine;
180
+ let endLine;
181
+
182
+ const addLines = () => {
183
+ if (!startLine) {
184
+ return;
185
+ }
186
+ if (endLine) {
187
+ // range
188
+ const link = startLine.color === 'yellow' && endLine.color === 'yellow' ? EC.yellow('-') : EC.red('-');
189
+ lines.push(EC[startLine.color](startLine.line) + link + EC[endLine.color](endLine.line));
190
+ startLine = null;
191
+ endLine = null;
192
+ } else {
193
+ // only start
194
+ lines.push(EC[startLine.color](startLine.line));
195
+ startLine = null;
196
+ }
197
+ };
198
+
199
+ const setLines = (line, color) => {
200
+ if (startLine) {
201
+ endLine = {
202
+ line,
203
+ color
204
+ };
205
+ return;
206
+ }
207
+ startLine = {
208
+ line,
209
+ color
210
+ };
211
+ };
212
+
213
+ Object.keys(dataLines).forEach((line) => {
214
+ const count = dataLines[line];
215
+ if (count === 0) {
216
+ setLines(line, 'red');
217
+ return;
218
+ }
219
+ // 0 < count < 1
220
+ if (typeof count === 'string') {
221
+ setLines(line, 'yellow');
222
+ return;
223
+ }
224
+ // count >= 1
225
+ addLines();
226
+ });
227
+ addLines();
228
+
229
+ return lines.join(',');
230
+ };
231
+
232
+ const getDetailsRows = (files, metrics, cdOptions) => {
233
+
234
+ const skipPercent = cdOptions.skipPercent;
235
+ if (typeof skipPercent === 'number' && skipPercent > 0) {
236
+ files = files.filter((file) => {
237
+ const { summary } = file;
238
+ for (const k of metrics) {
239
+ const percent = summary[k].pct;
240
+ if (typeof percent === 'number' && percent < skipPercent) {
241
+ return true;
242
+ }
243
+ }
244
+ return false;
245
+ });
246
+ }
247
+
248
+ const flatRows = [];
249
+ files.forEach((file) => {
250
+ const { sourcePath, summary } = file;
251
+ const fileRow = getRowData(sourcePath, summary, metrics);
252
+ fileRow.uncoveredLines = getUncoveredLines(file);
253
+ flatRows.push(fileRow);
254
+ });
255
+
256
+ return getGroupedRows(flatRows);
257
+ };
258
+
259
+ const handleCodecovReport = async (reportData, reportOptions, options) => {
260
+ const codecovOptions = {
261
+ outputFile: 'codecov.json',
262
+ ... reportOptions
263
+ };
264
+
265
+ const jsonPath = path.resolve(options.outputDir, codecovOptions.outputFile);
266
+
267
+ // https://docs.codecov.com/docs/codecov-custom-coverage-format
268
+ const coverage = {};
269
+ reportData.files.forEach((item) => {
270
+ const { sourcePath, data } = item;
271
+ coverage[sourcePath] = data.lines;
272
+ });
273
+ const codecovData = {
274
+ coverage
275
+ };
276
+
277
+ await Util.writeFile(jsonPath, JSON.stringify(codecovData));
278
+ return Util.relativePath(jsonPath);
279
+ };
280
+
281
+ const handleConsoleDetailsReport = (reportData, reportOptions, options) => {
282
+ const cdOptions = {
283
+ maxCols: 50,
284
+ skipPercent: 0,
285
+ metrics: [],
286
+ ... reportOptions
287
+ };
288
+
289
+ const {
290
+ type, name, summary, files
291
+ } = reportData;
292
+
293
+ if (name) {
294
+ EC.logCyan(name);
295
+ }
296
+
297
+ const metrics = Util.getMetrics(cdOptions.metrics, type);
298
+ const rows = getDetailsRows(files, metrics, cdOptions);
299
+ const summaryRow = getRowData('Summary', summary, metrics);
300
+ // no rows if skipped all by skipPercent
301
+ if (rows.length) {
302
+ rows.push({
303
+ innerBorder: true
304
+ });
305
+ }
306
+ rows.push(summaryRow);
307
+
308
+ const columns = [{
309
+ id: 'name',
310
+ name: 'Name'
311
+ }, ... metrics.map((m) => {
312
+ return {
313
+ id: m,
314
+ name: Util.capitalizeFirstLetter(m),
315
+ align: 'right'
316
+ };
317
+ }), {
318
+ id: 'uncoveredLines',
319
+ name: 'Uncovered Lines'
320
+ }];
321
+
322
+ return CG({
323
+ options: {
324
+ silent: cdOptions.silent,
325
+ nullPlaceholder: '',
326
+ defaultMaxWidth: cdOptions.maxCols
327
+ },
328
+ columns,
329
+ rows
330
+ });
331
+ };
332
+
333
+ const handleConsoleSummaryReport = (reportData, reportOptions, options) => {
334
+
335
+ const csOptions = {
336
+ metrics: [],
337
+ ... reportOptions
338
+ };
339
+
340
+ const {
341
+ summary, name, type
342
+ } = reportData;
343
+
344
+ if (name) {
345
+ EC.logCyan(name);
346
+ }
347
+
348
+ const metrics = Util.getMetrics(csOptions.metrics, type);
349
+
350
+ const rows = metrics.map((k) => {
351
+ return {
352
+ ... summary[k],
353
+ name: Util.capitalizeFirstLetter(k)
354
+ };
355
+ });
356
+
357
+ const columns = [{
358
+ id: 'name',
359
+ name: 'Name'
360
+ }, {
361
+ id: 'pct',
362
+ name: 'Coverage %',
363
+ align: 'right',
364
+ formatter: pFormatter
365
+ }, {
366
+ id: 'covered',
367
+ name: 'Covered',
368
+ align: 'right',
369
+ formatter: nFormatter
370
+ }, {
371
+ id: 'uncovered',
372
+ name: 'Uncovered',
373
+ align: 'right',
374
+ formatter: nFormatter
375
+ }, {
376
+ id: 'total',
377
+ name: 'Total',
378
+ align: 'right',
379
+ formatter: nFormatter
380
+ }];
381
+
382
+ CG({
383
+ columns,
384
+ rows
385
+ });
386
+ };
387
+
388
+ const handleRawReport = (reportData, reportOptions, options) => {
389
+ const rawOptions = {
390
+ outputDir: 'raw',
391
+ ... reportOptions
392
+ };
393
+
394
+ const cacheDir = options.cacheDir;
395
+ const rawDir = path.resolve(options.outputDir, rawOptions.outputDir);
396
+ // console.log(rawDir, cacheDir);
397
+ if (fs.existsSync(rawDir)) {
398
+ Util.logError(`Failed to save raw report because the dir already exists: ${Util.relativePath(rawDir)}`);
399
+ return;
400
+ }
401
+
402
+ const rawParent = path.dirname(rawDir);
403
+ if (!fs.existsSync(rawParent)) {
404
+ fs.mkdirSync(rawParent, {
405
+ recursive: true
406
+ });
407
+ }
408
+
409
+ // just rename the cache folder name
410
+ fs.renameSync(cacheDir, rawDir);
411
+
412
+ };
413
+
414
+
415
+ // ========================================================================================================
416
+
417
+ const getCoverageResults = async (dataList, sourceCache, options) => {
418
+ // get first and check v8list or istanbul data
419
+ const firstData = dataList[0];
420
+ const dataType = firstData.type;
421
+ // console.log('data type', dataType);
422
+
423
+ // init reports
424
+ options.reportGroup = getReportGroup(options.reports, options.lcov, dataType);
425
+ // console.log('reportGroup', options.reportGroup);
426
+
427
+ // v8list
428
+ if (dataType === 'v8') {
429
+ // merge v8list first
430
+ const v8list = await mergeV8Coverage(dataList, sourceCache, options);
431
+ // console.log('after merge', v8list.map((it) => it.url));
432
+
433
+ // only debug level
434
+ const time_start = Date.now();
435
+ const results = convertV8List(v8list, options);
436
+ const {
437
+ v8DataList, coverageData, fileSources
438
+ } = results;
439
+ Util.logTime(`converted v8 list: ${v8DataList.length}`, time_start);
440
+
441
+ return generateV8ListReports(v8DataList, coverageData, fileSources, options);
442
+ }
443
+
444
+ // istanbul data
445
+ const istanbulData = mergeIstanbulCoverage(dataList);
446
+ const { coverageData, fileSources } = initIstanbulData(istanbulData, options);
447
+ return saveIstanbulReports(coverageData, fileSources, options);
448
+ };
449
+
450
+ const generateCoverageReports = async (dataList, sourceCache, options) => {
451
+ const coverageResults = await getCoverageResults(dataList, sourceCache, options);
452
+
453
+ // [ 'type', 'reportPath', 'name', 'watermarks', 'summary', 'files' ]
454
+ // console.log(Object.keys(coverageResults));
455
+
456
+ const buildInBothReports = {
457
+ 'codecov': handleCodecovReport,
458
+ 'console-details': handleConsoleDetailsReport,
459
+ 'console-summary': handleConsoleSummaryReport,
460
+ 'raw': handleRawReport
461
+ };
462
+
463
+ const bothGroup = options.reportGroup.both;
464
+ if (bothGroup) {
465
+ const bothReports = Object.keys(bothGroup);
466
+ for (const reportName of bothReports) {
467
+ const reportOptions = bothGroup[reportName];
468
+ const buildInHandler = buildInBothReports[reportName];
469
+ if (buildInHandler) {
470
+ await buildInHandler(coverageResults, reportOptions, options);
471
+ } else {
472
+ await Util.runCustomReporter(reportName, coverageResults, reportOptions, options);
473
+ }
474
+ }
475
+ }
476
+
477
+ return coverageResults;
478
+ };
479
+
480
+ // ========================================================================================================
481
+
482
+ const resolveAllDirList = (input) => {
483
+ const dirs = Util.toList(input, ',');
484
+ if (!dirs.length) {
485
+ return;
486
+ }
487
+ const dirList = dirs.filter((it) => fs.existsSync(it));
488
+ if (!dirList.length) {
489
+ return;
490
+ }
491
+ return dirList;
492
+ };
493
+
494
+ const resolveAllFilter = (input) => {
495
+ // for function handler
496
+ if (typeof input === 'function') {
497
+ return input;
498
+ }
499
+
500
+ // for single minimatch pattern
501
+ if (input && typeof input === 'string') {
502
+ return (filePath) => {
503
+ return minimatch(filePath, input);
504
+ };
505
+ }
506
+
507
+ // for patterns
508
+ if (input && typeof input === 'object') {
509
+ const patterns = Object.keys(input);
510
+ return (filePath) => {
511
+ for (const pattern of patterns) {
512
+ if (minimatch(filePath, pattern)) {
513
+ return input[pattern];
514
+ }
515
+ }
516
+ // false if not matched
517
+ };
518
+ }
519
+
520
+ // default
521
+ return () => true;
522
+ };
523
+
524
+ const resolveAllDataType = (type, dataList) => {
525
+ if (!type && dataList.length) {
526
+ const firstData = dataList[0];
527
+ type = firstData.type;
528
+ }
529
+ const types = {
530
+ v8: 'v8',
531
+ istanbul: 'istanbul'
532
+ };
533
+ return types[type] || types.v8;
534
+ };
535
+
536
+ const resolveAllOptions = (input, dataList) => {
537
+ if (!input) {
538
+ return;
539
+ }
540
+
541
+ let type;
542
+ let dir;
543
+ let filter;
544
+ if (typeof input === 'string' || Array.isArray(input)) {
545
+ dir = input;
546
+ filter = () => true;
547
+ } else {
548
+ type = input.type;
549
+ dir = input.dir;
550
+ filter = input.filter;
551
+ }
552
+
553
+ const dirList = resolveAllDirList(dir);
554
+ if (!dirList) {
555
+ return;
556
+ }
557
+
558
+ const dataType = resolveAllDataType(type, dataList);
559
+ const fileFilter = resolveAllFilter(filter);
560
+
561
+ return {
562
+ dirList,
563
+ dataType,
564
+ fileFilter
565
+ };
566
+ };
567
+
568
+ const resolveFileType = (fileType, filePath) => {
569
+ if (fileType === 'js' || fileType === 'css') {
570
+ return fileType;
571
+ }
572
+ const extname = path.extname(filePath);
573
+ if (['.css', '.scss', '.sass', '.less'].includes(extname)) {
574
+ return 'css';
575
+ }
576
+ return 'js';
577
+ };
578
+
579
+ const getV8EmptyData = (fileList) => {
580
+ const v8DataList = [];
581
+ fileList.forEach((item) => {
582
+ const { filePath, fileType } = item;
583
+ const type = resolveFileType(fileType, filePath);
584
+ const url = pathToFileURL(filePath).toString();
585
+ const source = Util.readFileSync(filePath);
586
+ const entryFile = {
587
+ empty: true,
588
+ type,
589
+ url,
590
+ // for empty, css supports both source and text
591
+ source
592
+ };
593
+ v8DataList.push(entryFile);
594
+ });
595
+ return v8DataList;
596
+ };
597
+
598
+ const getIstanbulEmptyData = (fileList) => {
599
+ const istanbulData = {};
600
+ fileList.forEach((item) => {
601
+ const { filePath } = item;
602
+ istanbulData[filePath] = {
603
+ path: filePath,
604
+ statementMap: {},
605
+ fnMap: {},
606
+ branchMap: {},
607
+ s: {},
608
+ f: {},
609
+ b: {}
610
+ };
611
+ });
612
+ return istanbulData;
613
+ };
614
+
615
+ const getEmptyData = (all, dataList) => {
616
+ const allOptions = resolveAllOptions(all, dataList);
617
+ // console.log('allOptions', all, allOptions);
618
+ if (!allOptions) {
619
+ return;
620
+ }
621
+ const {
622
+ dirList, dataType, fileFilter
623
+ } = allOptions;
624
+ const fileList = [];
625
+ dirList.forEach((dir) => {
626
+ Util.forEachFile(dir, [], (fileName, fileDir) => {
627
+ const filePath = path.resolve(fileDir, fileName);
628
+ const fileType = fileFilter(filePath);
629
+ if (fileType) {
630
+ fileList.push({
631
+ filePath,
632
+ fileType
633
+ });
634
+ }
635
+ });
636
+ });
637
+ // console.log('fileList', fileList);
638
+ if (!fileList.length) {
639
+ return;
640
+ }
641
+ if (dataType === 'v8') {
642
+ return getV8EmptyData(fileList);
643
+ }
644
+ return getIstanbulEmptyData(fileList);
645
+ };
646
+
647
+ // ========================================================================================================
648
+
649
+
650
+ const getInputData = async (inputList, all, cacheDir, addHandler) => {
651
+ const dataList = [];
652
+ const sourceCache = new Map();
653
+
654
+ const addJsonData = async (dir, filename) => {
655
+ const isCoverage = filename.startsWith('coverage-');
656
+ const isSource = filename.startsWith('source-');
657
+ if (isCoverage || isSource) {
658
+ const content = await Util.readFile(path.resolve(dir, filename));
659
+ if (content) {
660
+ const json = JSON.parse(content);
661
+ if (isCoverage) {
662
+ dataList.push(json);
663
+ } else {
664
+ sourceCache.set(json.id, json);
665
+ }
666
+ }
667
+ }
668
+ };
669
+
670
+ for (const dir of inputList) {
671
+ const allFiles = fs.readdirSync(dir);
672
+ if (!allFiles.length) {
673
+ continue;
674
+ }
675
+ for (const filename of allFiles) {
676
+ // only json file
677
+ if (filename.endsWith('.json')) {
678
+ await addJsonData(dir, filename);
679
+ }
680
+ }
681
+ }
682
+
683
+ // add empty coverage for all files
684
+ // dataList for data type, before `add` we don't which one is tested, because using hash id
685
+ const emptyData = getEmptyData(all, dataList);
686
+ if (emptyData) {
687
+ const coverageResults = await addHandler(emptyData);
688
+ // after `add`
689
+ if (coverageResults) {
690
+ dataList.push(coverageResults);
691
+ if (coverageResults.type === 'v8') {
692
+ // sourceCache
693
+ coverageResults.data.forEach((entry) => {
694
+ const { id } = entry;
695
+ if (sourceCache.has(id)) {
696
+ // console.log('+', entry.sourcePath);
697
+ return;
698
+ }
699
+ // console.log('-', entry.sourcePath);
700
+ const sourcePath = Util.resolveCacheSourcePath(cacheDir, id);
701
+ const content = Util.readFileSync(sourcePath);
702
+ if (content) {
703
+ const json = JSON.parse(content);
704
+ sourceCache.set(json.id, json);
705
+ }
706
+ });
707
+ }
708
+ }
709
+ }
710
+
711
+ return {
712
+ dataList,
713
+ sourceCache
714
+ };
715
+ };
716
+
717
+ module.exports = {
718
+ getInputData,
719
+ generateCoverageReports
720
+ };