monocart-coverage-reports 2.10.3 → 2.10.5

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.
@@ -0,0 +1,298 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const Util = require('../utils/util.js');
4
+
5
+ const { pathToFileURL, fileURLToPath } = require('url');
6
+
7
+ // const EC = require('eight-colors');
8
+ const { minimatch, convertSourceMap } = require('../packages/monocart-coverage-vendor.js');
9
+
10
+ // ========================================================================================================
11
+
12
+ const resolveAllDirList = (input) => {
13
+ const dirs = Util.toList(input, ',');
14
+ if (!dirs.length) {
15
+ return;
16
+ }
17
+ const dirList = dirs.filter((it) => fs.existsSync(it));
18
+ if (!dirList.length) {
19
+ return;
20
+ }
21
+ return dirList;
22
+ };
23
+
24
+ const resolveAllFilter = (input) => {
25
+ // for function handler
26
+ if (typeof input === 'function') {
27
+ return input;
28
+ }
29
+
30
+ // for single minimatch pattern
31
+ if (input && typeof input === 'string') {
32
+ // string to multiple patterns "{...}"
33
+ const obj = Util.strToObj(input);
34
+ if (obj) {
35
+ input = obj;
36
+ } else {
37
+ return (filePath) => {
38
+ return minimatch(filePath, input);
39
+ };
40
+ }
41
+ }
42
+
43
+ // for patterns
44
+ if (input && typeof input === 'object') {
45
+ const patterns = Object.keys(input);
46
+ return (filePath) => {
47
+ for (const pattern of patterns) {
48
+ if (minimatch(filePath, pattern)) {
49
+ return input[pattern];
50
+ }
51
+ }
52
+ // false if not matched
53
+ };
54
+ }
55
+
56
+ // default
57
+ return () => true;
58
+ };
59
+
60
+ const resolveAllOptions = (input) => {
61
+ if (!input) {
62
+ return;
63
+ }
64
+
65
+ let dir;
66
+ let filter;
67
+ let transformer;
68
+
69
+ if (typeof input === 'string') {
70
+ const obj = Util.strToObj(input);
71
+ if (obj) {
72
+ dir = obj.dir;
73
+ filter = obj.filter;
74
+ transformer = obj.transformer;
75
+ } else {
76
+ dir = input;
77
+ }
78
+ } else if (Array.isArray(input)) {
79
+ dir = input;
80
+ } else {
81
+ dir = input.dir;
82
+ filter = input.filter;
83
+ transformer = input.transformer;
84
+ }
85
+
86
+ const dirList = resolveAllDirList(dir);
87
+ if (!dirList) {
88
+ return;
89
+ }
90
+
91
+ const fileFilter = resolveAllFilter(filter);
92
+ const fileTransformer = typeof transformer === 'function' ? transformer : () => {};
93
+
94
+ return {
95
+ dirList,
96
+ fileFilter,
97
+ fileTransformer
98
+ };
99
+ };
100
+
101
+ const resolveFileType = (fileType, filePath) => {
102
+ if (fileType === 'js' || fileType === 'css') {
103
+ return fileType;
104
+ }
105
+ const extname = path.extname(filePath);
106
+ if (['.css', '.scss', '.sass', '.less'].includes(extname)) {
107
+ return 'css';
108
+ }
109
+ return 'js';
110
+ };
111
+
112
+ const saveUntestedFileSource = async (entryFile, options) => {
113
+ const {
114
+ id,
115
+ url,
116
+ source,
117
+ sourceMap
118
+ } = entryFile;
119
+ // console.log('-', entry.sourcePath);
120
+
121
+ const { cachePath } = Util.getCacheFileInfo('source', id, options.cacheDir);
122
+ if (fs.existsSync(cachePath)) {
123
+ return;
124
+ }
125
+
126
+ // save source and sourceMap to separated json file
127
+ const sourceData = {
128
+ id,
129
+ url,
130
+ source,
131
+ sourceMap
132
+ };
133
+
134
+ // remove comments if not debug
135
+ if (!Util.isDebug()) {
136
+ sourceData.source = convertSourceMap.removeComments(source);
137
+ }
138
+
139
+ // console.log('save untested file', id, url);
140
+
141
+ await Util.saveSourceCacheFile(sourceData, options);
142
+
143
+ };
144
+
145
+ const getUntestedCoverageData = async (emptyList, options, coverageType) => {
146
+
147
+ // save all empty coverage
148
+ const dataId = Util.uid();
149
+ const results = {
150
+ id: dataId
151
+ };
152
+
153
+ if (coverageType === 'istanbul') {
154
+ results.type = 'istanbul';
155
+ results.data = {};
156
+ } else {
157
+ results.type = 'v8';
158
+ results.data = [];
159
+ }
160
+
161
+ const emptyCoverageList = [];
162
+
163
+ // save all empty source and sourcemap
164
+ for (const entryFile of emptyList) {
165
+
166
+ // for raw report: source file
167
+ await saveUntestedFileSource(entryFile, options);
168
+
169
+ const { type, url } = entryFile;
170
+
171
+ if (coverageType === 'istanbul') {
172
+
173
+ // ===============================================
174
+ const item = {
175
+ path: fileURLToPath(url),
176
+ statementMap: {},
177
+ fnMap: {},
178
+ branchMap: {},
179
+ s: {},
180
+ f: {},
181
+ b: {}
182
+ };
183
+ // object
184
+ results.data[item.path] = item;
185
+ emptyCoverageList.push(item);
186
+
187
+ } else {
188
+
189
+ // ===============================================
190
+ if (type === 'js') {
191
+ // empty js
192
+ entryFile.functions = entryFile.functions || [{
193
+ functionName: '',
194
+ ranges: [{
195
+ startOffset: 0,
196
+ endOffset: entryFile.source.length,
197
+ count: 0
198
+ }]
199
+ }];
200
+ } else {
201
+ // empty css
202
+ entryFile.ranges = entryFile.ranges || [];
203
+ }
204
+
205
+ const item = {
206
+ ... entryFile
207
+ };
208
+ delete item.source;
209
+ delete item.sourceMap;
210
+
211
+ // array
212
+ results.data.push(item);
213
+ // will be parsed to AST and converted to V8 coverage
214
+ emptyCoverageList.push(entryFile);
215
+
216
+ }
217
+
218
+ }
219
+
220
+ // for raw report: coverage file
221
+ const { cachePath } = Util.getCacheFileInfo('coverage', dataId, options.cacheDir);
222
+ await Util.writeFile(cachePath, JSON.stringify(results));
223
+
224
+ return emptyCoverageList;
225
+ };
226
+
227
+ const getEmptyCoverages = async (fileList, options, coverageType, fileTransformer) => {
228
+
229
+ const emptyList = [];
230
+
231
+ for (const item of fileList) {
232
+ const {
233
+ filePath, fileType, sourcePath
234
+ } = item;
235
+ const type = resolveFileType(fileType, filePath);
236
+ const url = pathToFileURL(filePath).toString();
237
+ const source = Util.readFileSync(filePath);
238
+ const entryFile = {
239
+ empty: true,
240
+ type,
241
+ url,
242
+ sourcePath,
243
+ // for empty, css supports both source and text
244
+ source
245
+ };
246
+
247
+ await fileTransformer(entryFile, coverageType);
248
+
249
+ // after transformer
250
+ entryFile.id = Util.calculateSha1(entryFile.sourcePath + entryFile.source);
251
+
252
+ emptyList.push(entryFile);
253
+ }
254
+
255
+ // save empty coverage for merging raw reports
256
+ return getUntestedCoverageData(emptyList, options, coverageType);
257
+ };
258
+
259
+ const getUntestedList = (testedMap, options, coverageType = 'v8') => {
260
+ const allOptions = resolveAllOptions(options.all);
261
+ // console.log('allOptions', all, allOptions);
262
+ if (!allOptions) {
263
+ return;
264
+ }
265
+ const {
266
+ dirList, fileFilter, fileTransformer
267
+ } = allOptions;
268
+ const fileList = [];
269
+ dirList.forEach((dir) => {
270
+ Util.forEachFile(dir, [], (fileName, fileDir) => {
271
+ const filePath = path.resolve(fileDir, fileName);
272
+ // return file extname for file type
273
+ const fileType = fileFilter(filePath);
274
+ if (fileType) {
275
+ const sourcePath = Util.relativePath(filePath);
276
+ if (!testedMap.has(sourcePath)) {
277
+ fileList.push({
278
+ filePath,
279
+ fileType,
280
+ sourcePath
281
+ });
282
+ }
283
+ }
284
+ });
285
+ });
286
+ // console.log('fileList', fileList);
287
+ if (!fileList.length) {
288
+ return;
289
+ }
290
+
291
+ return getEmptyCoverages(fileList, options, coverageType, fileTransformer);
292
+
293
+ };
294
+
295
+
296
+ module.exports = {
297
+ getUntestedList
298
+ };
@@ -67,7 +67,8 @@ module.exports = {
67
67
  // options for adding empty coverage for all files
68
68
  // all: {
69
69
  // dir: ['src'],
70
- // filter: (sourcePath) => true
70
+ // filter: (sourcePath) => true,
71
+ // transformer: (entry) => {}
71
72
  // },
72
73
  all: null,
73
74
 
package/lib/generate.js CHANGED
@@ -1,9 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { pathToFileURL, fileURLToPath } = require('url');
4
-
5
- const EC = require('eight-colors');
6
- const { minimatch } = require('./packages/monocart-coverage-vendor.js');
3
+ const { fileURLToPath } = require('url');
7
4
 
8
5
  const Util = require('./utils/util.js');
9
6
  const { convertV8List } = require('./converter/converter.js');
@@ -178,7 +175,7 @@ const getCoverageResults = async (dataList, sourceCache, options) => {
178
175
  // console.log('after merge', v8list.map((it) => it.url));
179
176
 
180
177
  const t2 = Date.now();
181
- const results = convertV8List(v8list, options);
178
+ const results = await convertV8List(v8list, options);
182
179
  Util.logTime('┌ [generate] converted coverage data', t2);
183
180
 
184
181
  const {
@@ -189,7 +186,7 @@ const getCoverageResults = async (dataList, sourceCache, options) => {
189
186
 
190
187
  // istanbul data
191
188
  const t3 = Date.now();
192
- const istanbulData = mergeIstanbulCoverage(dataList);
189
+ const istanbulData = await mergeIstanbulCoverage(dataList, options);
193
190
  const { coverageData, fileSources } = initIstanbulData(istanbulData, options);
194
191
  Util.logTime('┌ [generate] prepared istanbul coverage data', t3);
195
192
  const results = await saveIstanbulReports(coverageData, fileSources, options);
@@ -219,187 +216,6 @@ const generateCoverageReports = async (dataList, sourceCache, options) => {
219
216
  return coverageResults;
220
217
  };
221
218
 
222
- // ========================================================================================================
223
-
224
- const resolveAllDirList = (input) => {
225
- const dirs = Util.toList(input, ',');
226
- if (!dirs.length) {
227
- return;
228
- }
229
- const dirList = dirs.filter((it) => fs.existsSync(it));
230
- if (!dirList.length) {
231
- return;
232
- }
233
- return dirList;
234
- };
235
-
236
- const resolveAllFilter = (input) => {
237
- // for function handler
238
- if (typeof input === 'function') {
239
- return input;
240
- }
241
-
242
- // for single minimatch pattern
243
- if (input && typeof input === 'string') {
244
- // string to multiple patterns "{...}"
245
- const obj = Util.strToObj(input);
246
- if (obj) {
247
- input = obj;
248
- } else {
249
- return (filePath) => {
250
- return minimatch(filePath, input);
251
- };
252
- }
253
- }
254
-
255
- // for patterns
256
- if (input && typeof input === 'object') {
257
- const patterns = Object.keys(input);
258
- return (filePath) => {
259
- for (const pattern of patterns) {
260
- if (minimatch(filePath, pattern)) {
261
- return input[pattern];
262
- }
263
- }
264
- // false if not matched
265
- };
266
- }
267
-
268
- // default
269
- return () => true;
270
- };
271
-
272
- const resolveAllDataType = (type, dataList) => {
273
- if (!type && dataList.length) {
274
- const firstData = dataList[0];
275
- type = firstData.type;
276
- }
277
- const types = {
278
- v8: 'v8',
279
- istanbul: 'istanbul'
280
- };
281
- return types[type] || types.v8;
282
- };
283
-
284
- const resolveAllOptions = (input, dataList) => {
285
- if (!input) {
286
- return;
287
- }
288
-
289
- let type;
290
- let dir;
291
- let filter;
292
-
293
- if (typeof input === 'string') {
294
- const obj = Util.strToObj(input);
295
- if (obj) {
296
- type = obj.type;
297
- dir = obj.dir;
298
- filter = obj.filter;
299
- } else {
300
- dir = input;
301
- }
302
- } else if (Array.isArray(input)) {
303
- dir = input;
304
- } else {
305
- type = input.type;
306
- dir = input.dir;
307
- filter = input.filter;
308
- }
309
-
310
- const dirList = resolveAllDirList(dir);
311
- if (!dirList) {
312
- return;
313
- }
314
-
315
- const dataType = resolveAllDataType(type, dataList);
316
- const fileFilter = resolveAllFilter(filter);
317
-
318
- return {
319
- dirList,
320
- dataType,
321
- fileFilter
322
- };
323
- };
324
-
325
- const resolveFileType = (fileType, filePath) => {
326
- if (fileType === 'js' || fileType === 'css') {
327
- return fileType;
328
- }
329
- const extname = path.extname(filePath);
330
- if (['.css', '.scss', '.sass', '.less'].includes(extname)) {
331
- return 'css';
332
- }
333
- return 'js';
334
- };
335
-
336
- const getV8EmptyData = (fileList) => {
337
- const v8DataList = [];
338
- fileList.forEach((item) => {
339
- const { filePath, fileType } = item;
340
- const type = resolveFileType(fileType, filePath);
341
- const url = pathToFileURL(filePath).toString();
342
- const source = Util.readFileSync(filePath);
343
- const entryFile = {
344
- empty: true,
345
- type,
346
- url,
347
- // for empty, css supports both source and text
348
- source
349
- };
350
- v8DataList.push(entryFile);
351
- });
352
- return v8DataList;
353
- };
354
-
355
- const getIstanbulEmptyData = (fileList) => {
356
- const istanbulData = {};
357
- fileList.forEach((item) => {
358
- const { filePath } = item;
359
- istanbulData[filePath] = {
360
- path: filePath,
361
- statementMap: {},
362
- fnMap: {},
363
- branchMap: {},
364
- s: {},
365
- f: {},
366
- b: {}
367
- };
368
- });
369
- return istanbulData;
370
- };
371
-
372
- const getEmptyData = (all, dataList) => {
373
- const allOptions = resolveAllOptions(all, dataList);
374
- // console.log('allOptions', all, allOptions);
375
- if (!allOptions) {
376
- return;
377
- }
378
- const {
379
- dirList, dataType, fileFilter
380
- } = allOptions;
381
- const fileList = [];
382
- dirList.forEach((dir) => {
383
- Util.forEachFile(dir, [], (fileName, fileDir) => {
384
- const filePath = path.resolve(fileDir, fileName);
385
- const fileType = fileFilter(filePath);
386
- if (fileType) {
387
- fileList.push({
388
- filePath,
389
- fileType
390
- });
391
- }
392
- });
393
- });
394
- // console.log('fileList', fileList);
395
- if (!fileList.length) {
396
- return;
397
- }
398
- if (dataType === 'v8') {
399
- return getV8EmptyData(fileList);
400
- }
401
- return getIstanbulEmptyData(fileList);
402
- };
403
219
 
404
220
  // ========================================================================================================
405
221
 
@@ -448,55 +264,6 @@ const addJsonData = async (mcr, dataList, sourceCache, dir, filename) => {
448
264
  }
449
265
  };
450
266
 
451
- const addEmptyData = async (mcr, dataList, sourceCache) => {
452
-
453
- const { all, cacheDir } = mcr.options;
454
- // add empty coverage for all files
455
- // dataList for data type, before `add` we don't which one is tested, because using hash id
456
- const emptyData = getEmptyData(all, dataList);
457
- if (!emptyData) {
458
- return;
459
- }
460
-
461
- let files = 0;
462
- if (Util.isList(emptyData)) {
463
- files = emptyData.length;
464
- } else {
465
- files = Object.keys(emptyData).length;
466
- }
467
-
468
- Util.logDebug(`added all files with empty coverage: ${EC.yellow(files)}`);
469
- const coverageResults = await mcr.add(emptyData);
470
- // after `add`
471
- if (!coverageResults) {
472
- return;
473
- }
474
-
475
- dataList.push(coverageResults);
476
-
477
- if (coverageResults.type === 'v8') {
478
- // sourceCache
479
- coverageResults.data.forEach((entry) => {
480
- const { id } = entry;
481
- if (sourceCache.has(id)) {
482
- // console.log('+', entry.sourcePath);
483
- return;
484
- }
485
- // console.log('-', entry.sourcePath);
486
- const { cacheName, cachePath } = Util.getCacheFileInfo('source', id, cacheDir);
487
- if (mcr.fileCache.has(cacheName)) {
488
- const json = mcr.fileCache.get(cacheName);
489
- sourceCache.set(json.id, json);
490
- } else {
491
- const content = Util.readFileSync(cachePath);
492
- if (content) {
493
- const json = JSON.parse(content);
494
- sourceCache.set(json.id, json);
495
- }
496
- }
497
- });
498
- }
499
- };
500
267
 
501
268
  const getInputData = async (mcr) => {
502
269
 
@@ -520,8 +287,6 @@ const getInputData = async (mcr) => {
520
287
  }
521
288
  }
522
289
 
523
- await addEmptyData(mcr, dataList, sourceCache);
524
-
525
290
  if (!dataList.length) {
526
291
  const dirs = inputList.map((dir) => Util.relativePath(dir));
527
292
  Util.logError(`Not found coverage data in dir(s): ${dirs.join(', ')}`);
package/lib/index.d.ts CHANGED
@@ -359,8 +359,7 @@ declare namespace MCR {
359
359
  all?: string | string[] | {
360
360
  /** the dir(s) of all files */
361
361
  dir: string | string[];
362
- /** the coverage data type, v8 or istanbul */
363
- type?: "v8" | "istanbul";
362
+
364
363
  /**
365
364
  * the file filter, return the file type, if true, defaults to css if ".css" otherwise is js
366
365
  *
@@ -369,6 +368,14 @@ declare namespace MCR {
369
368
  filter?: string | {
370
369
  [pattern: string]: "js" | "css" | boolean;
371
370
  } | ((filePath: string) => "js" | "css" | boolean);
371
+
372
+ /**
373
+ * the file transformer for source and sourceMap
374
+ * some of untested files like .ts/.jsx/.vue can not be parsed to AST directly by acorn
375
+ * so this is the function which can transform the original source to generated source and sourceMap
376
+ */
377
+ transformer?: (entry: any) => Promise<void>;
378
+
372
379
  };
373
380
 
374
381
  /** (V8 only) {boolean} Enable/Disable ignoring uncovered codes with the special comments: v8 ignore next/next N/start/stop */
@@ -8,6 +8,7 @@ const Util = require('../utils/util.js');
8
8
  const IstanbulSummary = require('./istanbul-summary.js');
9
9
 
10
10
  const { initIstanbulSourcePath } = require('../utils/source-path.js');
11
+ const { getUntestedList } = require('../converter/untested.js');
11
12
 
12
13
  const findHtmlPath = (outputDir) => {
13
14
  const defaultHtml = path.resolve(outputDir, 'index.html');
@@ -121,18 +122,42 @@ const saveIstanbulReports = (coverageData, fileSources, options) => {
121
122
  return coverageResults;
122
123
  };
123
124
 
124
- const mergeIstanbulCoverage = (dataList) => {
125
+ const addUntestedFiles = async (istanbulData, options) => {
126
+ const testedMap = new Map();
127
+ Object.keys(istanbulData).forEach((filePath) => {
128
+ const sourcePath = Util.relativePath(filePath);
129
+ testedMap.set(sourcePath, true);
130
+ });
131
+
132
+ // untested files
133
+ const untestedList = await getUntestedList(testedMap, options, 'istanbul');
134
+ if (!untestedList) {
135
+ return;
136
+ }
137
+
138
+ // console.log('istanbul untestedList', untestedList);
139
+ untestedList.forEach((item) => {
140
+ if (!istanbulData[item.path]) {
141
+ istanbulData[item.path] = item;
142
+ }
143
+ });
144
+
145
+ };
146
+
147
+ const mergeIstanbulCoverage = async (dataList, options) => {
125
148
  const istanbulCoverageList = dataList.map((it) => it.data);
126
149
  const coverageMap = istanbulLibCoverage.createCoverageMap();
127
150
  istanbulCoverageList.forEach((coverage) => {
128
151
  coverageMap.merge(coverage);
129
152
  });
130
153
  const istanbulData = coverageMap.toJSON();
154
+
155
+ await addUntestedFiles(istanbulData, options);
156
+
131
157
  return istanbulData;
132
158
  };
133
159
 
134
160
  const initIstanbulData = (istanbulData, options) => {
135
-
136
161
  const fileSources = options.fileSources || {};
137
162
  const coverageData = initIstanbulSourcePath(istanbulData, fileSources, options);
138
163