monocart-coverage-reports 2.2.2 → 2.3.0

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.
@@ -109,6 +109,19 @@ const Util = {
109
109
  return false;
110
110
  },
111
111
 
112
+ toList: function(data, separator) {
113
+ if (data instanceof Array) {
114
+ return data;
115
+ }
116
+ if (typeof data === 'string' && (typeof separator === 'string' || separator instanceof RegExp)) {
117
+ return data.split(separator).map((str) => str.trim()).filter((str) => str);
118
+ }
119
+ if (typeof data === 'undefined' || data === null) {
120
+ return [];
121
+ }
122
+ return [data];
123
+ },
124
+
112
125
  forEach: function(rootList, callback) {
113
126
  const isBreak = (res) => {
114
127
  return res === 'break' || res === false;
@@ -14,7 +14,7 @@ const filterRanges = (ranges) => {
14
14
  };
15
15
 
16
16
  // apply directly to css ranges
17
- const dedupeRanges = (ranges) => {
17
+ const dedupeFlatRanges = (ranges) => {
18
18
 
19
19
  ranges = filterRanges(ranges);
20
20
 
@@ -59,11 +59,10 @@ const dedupeRanges = (ranges) => {
59
59
  return ranges;
60
60
  };
61
61
 
62
- // apply to js count ranges
63
- const dedupeCountRanges = (ranges) => {
62
+ const mergeRangesWith = (ranges, comparer, handler) => {
64
63
 
65
- // count ranges format
66
- // { start: 0, end: 6, count: 0 }
64
+ // ranges format
65
+ // { start: 0, end: 6, ... }
67
66
 
68
67
  ranges = filterRanges(ranges);
69
68
 
@@ -77,9 +76,10 @@ const dedupeCountRanges = (ranges) => {
77
76
 
78
77
  // merge count for same range
79
78
  ranges.reduce((lastRange, range) => {
80
- if (range.start === lastRange.start && range.end === lastRange.end) {
79
+ if (comparer(lastRange, range)) {
81
80
  range.dedupe = true;
82
- lastRange.count += range.count;
81
+
82
+ handler(lastRange, range);
83
83
 
84
84
  hasDedupe = true;
85
85
 
@@ -98,8 +98,21 @@ const dedupeCountRanges = (ranges) => {
98
98
  return ranges;
99
99
  };
100
100
 
101
+ // apply to js count ranges
102
+ const dedupeCountRanges = (ranges) => {
103
+ const comparer = (lastRange, range) => {
104
+ return lastRange.start === range.start && lastRange.end === range.end;
105
+ };
106
+ const handler = (lastRange, range) => {
107
+ lastRange.count += range.count;
108
+ };
109
+ return mergeRangesWith(ranges, comparer, handler);
110
+ };
111
+
101
112
 
102
113
  module.exports = {
103
- dedupeRanges,
114
+ sortRanges,
115
+ dedupeFlatRanges,
116
+ mergeRangesWith,
104
117
  dedupeCountRanges
105
118
  };
@@ -39,6 +39,11 @@ const getSourcePath = (url, index = '', type = '') => {
39
39
  url = pathToFileURL(['anonymous', index ? `-${index}` : '', type ? `.${type}` : ''].join('')).toString();
40
40
  }
41
41
 
42
+ // url could be a absolute path
43
+ if (path.isAbsolute(url)) {
44
+ url = pathToFileURL(url).toString();
45
+ }
46
+
42
47
  if (url.startsWith('file:')) {
43
48
  const relPath = Util.relativePath(fileURLToPath(url));
44
49
  return filterPath(relPath);
package/lib/utils/util.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const { writeFile, readFile } = require('fs/promises');
3
3
  const path = require('path');
4
+ const { pathToFileURL } = require('url');
4
5
  const os = require('os');
5
6
  const crypto = require('crypto');
6
7
  const EC = require('eight-colors');
@@ -48,7 +49,7 @@ const Util = {
48
49
  return str;
49
50
  },
50
51
 
51
- calculateSha1(buffer) {
52
+ calculateSha1: (buffer) => {
52
53
  const hash = crypto.createHash('sha1');
53
54
  hash.update(buffer);
54
55
  return hash.digest('hex');
@@ -204,6 +205,34 @@ const Util = {
204
205
  return Util.relativePath(htmlPath);
205
206
  },
206
207
 
208
+ runCustomReporter: async (reportName, reportData, reportOptions, globalOptions) => {
209
+ let CustomReporter;
210
+ let err;
211
+ try {
212
+ CustomReporter = await import(reportName);
213
+ } catch (e) {
214
+ err = e;
215
+ try {
216
+ CustomReporter = await import(pathToFileURL(reportName));
217
+ } catch (ee) {
218
+ err = ee;
219
+ }
220
+ }
221
+
222
+ if (!CustomReporter) {
223
+ Util.logError(err.message);
224
+ return;
225
+ }
226
+
227
+ CustomReporter = CustomReporter.default || CustomReporter;
228
+
229
+ const reporter = new CustomReporter(reportOptions, globalOptions);
230
+
231
+ const results = await reporter.generate(reportData);
232
+
233
+ return results;
234
+ },
235
+
207
236
  getEOL: function(content) {
208
237
  if (!content) {
209
238
  return os.EOL;
package/lib/v8/v8.js CHANGED
@@ -1,12 +1,26 @@
1
1
  const path = require('path');
2
2
  const Util = require('../utils/util.js');
3
3
  const { getV8Summary } = require('./v8-summary.js');
4
- const { dedupeRanges } = require('../utils/dedupe.js');
4
+ const { dedupeFlatRanges } = require('../utils/dedupe.js');
5
5
  const { getSourcePath } = require('../utils/source-path.js');
6
6
  const { mergeScriptCovs } = require('../packages/monocart-coverage-vendor.js');
7
7
  const collectSourceMaps = require('../converter/collect-source-maps.js');
8
8
  const version = require('../../package.json').version;
9
9
 
10
+ const getWrapperSource = (offset, source) => {
11
+ // NO \n because it break line number in mappings
12
+ let startStr = '';
13
+ if (offset >= 4) {
14
+ // fill comments
15
+ const spaces = ''.padEnd(offset - 4, '*');
16
+ startStr = `/*${spaces}*/`;
17
+ } else {
18
+ // fill spaces (should no chance)
19
+ startStr += ''.padEnd(offset, ' ');
20
+ }
21
+ return startStr + source;
22
+ };
23
+
10
24
  const initV8ListAndSourcemap = async (v8list, options) => {
11
25
 
12
26
  // filter list first
@@ -18,7 +32,6 @@ const initV8ListAndSourcemap = async (v8list, options) => {
18
32
  // filter no source or text
19
33
  // init type and css source from text
20
34
  // keep functions
21
-
22
35
  v8list = v8list.filter((item) => {
23
36
  if (typeof item.source === 'string' && item.functions) {
24
37
  item.type = 'js';
@@ -48,38 +61,38 @@ const initV8ListAndSourcemap = async (v8list, options) => {
48
61
  // init id, sourcePath
49
62
  v8list = v8list.map((item, i) => {
50
63
 
51
- const type = item.type;
64
+ let source = item.source || item.text;
65
+
66
+ // fix source with script offset
67
+ let scriptOffset;
68
+ const offset = Util.toNum(item.scriptOffset, true);
69
+ if (offset > 0) {
70
+ scriptOffset = offset;
71
+ source = getWrapperSource(offset, source);
72
+ }
52
73
 
53
74
  const data = {
54
75
  url: item.url,
55
- type
76
+ type: item.type,
77
+ source,
78
+ // script offset >= 0, for vm script
79
+ scriptOffset,
80
+ // Manually Resolve the Sourcemap
81
+ sourceMap: item.sourceMap,
82
+ // empty coverage
83
+ empty: item.empty,
84
+ // match source if using empty coverage
85
+ distFile: item.distFile
56
86
  };
57
87
 
58
- if (type === 'js') {
88
+ // coverage
89
+ if (data.type === 'js') {
59
90
  data.functions = item.functions;
60
- data.source = item.source;
61
- // could be existed
62
- data.sourceMap = item.sourceMap;
63
91
  } else {
64
- // css
65
92
  data.ranges = item.ranges;
66
- data.source = item.text;
67
- }
68
-
69
-
70
- // no functions and ranges
71
- if (item.empty) {
72
- data.empty = true;
73
- }
74
-
75
- const idList = [];
76
- // could be existed
77
- const distFile = item.distFile;
78
- if (distFile) {
79
- data.distFile = distFile;
80
- idList.push(distFile);
81
93
  }
82
94
 
95
+ // resolve source path
83
96
  let sourcePath = getSourcePath(data.url, i + 1, data.type);
84
97
  if (typeof sourcePathHandler === 'function') {
85
98
  const newSourcePath = sourcePathHandler(sourcePath, data.url);
@@ -87,13 +100,10 @@ const initV8ListAndSourcemap = async (v8list, options) => {
87
100
  sourcePath = newSourcePath;
88
101
  }
89
102
  }
90
-
91
103
  data.sourcePath = sourcePath;
92
104
 
93
- idList.push(sourcePath);
94
- idList.push(data.source);
95
-
96
- data.id = Util.calculateSha1(idList.join(''));
105
+ // calculate source id
106
+ data.id = Util.calculateSha1(sourcePath + data.source);
97
107
 
98
108
  return data;
99
109
  });
@@ -103,7 +113,7 @@ const initV8ListAndSourcemap = async (v8list, options) => {
103
113
  const count = await collectSourceMaps(v8list, options);
104
114
  if (count) {
105
115
  // debug level time
106
- Util.logTime(`loaded ${count} sourcemaps`, time_start);
116
+ Util.logTime(`loaded sourcemaps: ${count}`, time_start);
107
117
  }
108
118
 
109
119
  return v8list;
@@ -121,7 +131,7 @@ const mergeCssRanges = (itemList) => {
121
131
  });
122
132
 
123
133
  // ranges: [ {start, end} ]
124
- const ranges = dedupeRanges(concatRanges);
134
+ const ranges = dedupeFlatRanges(concatRanges);
125
135
 
126
136
  resolve(ranges || []);
127
137
  });
@@ -137,13 +147,14 @@ const mergeJsFunctions = (itemList) => {
137
147
  });
138
148
  };
139
149
 
140
- const mergeV8Coverage = async (dataList, options) => {
150
+ const mergeV8Coverage = async (dataList, sourceCache, options) => {
141
151
 
142
152
  let allList = [];
143
153
  dataList.forEach((d) => {
144
154
  allList = allList.concat(d.data);
145
155
  });
146
156
 
157
+ // remove empty items
147
158
  const coverageList = allList.filter((it) => !it.empty);
148
159
 
149
160
  // connect all functions and ranges
@@ -176,6 +187,7 @@ const mergeV8Coverage = async (dataList, options) => {
176
187
  }
177
188
 
178
189
  // first time filter for empty, (not for sources)
190
+ // empty and not in item map
179
191
  const emptyList = allList.filter((it) => it.empty).filter((it) => !itemMap[it.id]);
180
192
 
181
193
  // empty list + merged list
@@ -186,14 +198,12 @@ const mergeV8Coverage = async (dataList, options) => {
186
198
  const {
187
199
  id, type, empty
188
200
  } = item;
189
- const sourcePath = Util.resolveCacheSourcePath(options.cacheDir, id);
190
- const content = await Util.readFile(sourcePath);
191
- if (content) {
192
- const json = JSON.parse(content);
201
+ const json = sourceCache.get(id);
202
+ if (json) {
193
203
  item.source = json.source;
194
204
  item.sourceMap = json.sourceMap;
195
205
  } else {
196
- Util.logError(`failed to read source: ${item.url}`);
206
+ Util.logError(`Not found source data: ${Util.relativePath(item.sourcePath)}`);
197
207
  item.source = '';
198
208
  }
199
209
 
@@ -221,14 +231,13 @@ const mergeV8Coverage = async (dataList, options) => {
221
231
 
222
232
  // ============================================================
223
233
 
224
- const saveCodecovReport = async (reportData, options, codecovOptions) => {
225
- const mergedOptions = {
234
+ const saveCodecovReport = async (reportData, reportOptions, options) => {
235
+ const codecovOptions = {
226
236
  outputFile: 'codecov.json',
227
- ... codecovOptions
237
+ ... reportOptions
228
238
  };
229
239
 
230
- // console.log(mergedOptions);
231
- const jsonPath = path.resolve(options.outputDir, mergedOptions.outputFile);
240
+ const jsonPath = path.resolve(options.outputDir, codecovOptions.outputFile);
232
241
 
233
242
  // https://docs.codecov.com/docs/codecov-custom-coverage-format
234
243
  const coverage = {};
@@ -244,20 +253,32 @@ const saveCodecovReport = async (reportData, options, codecovOptions) => {
244
253
  return Util.relativePath(jsonPath);
245
254
  };
246
255
 
247
- const saveV8JsonReport = async (reportData, options, v8JsonOptions) => {
248
- const mergedOptions = {
256
+ const saveV8JsonReport = async (reportData, reportOptions, options) => {
257
+ const v8JsonOptions = {
249
258
  outputFile: 'coverage-report.json',
250
- ... v8JsonOptions
259
+ ... reportOptions
251
260
  };
252
261
 
253
262
  // console.log(mergedOptions);
254
- const jsonPath = path.resolve(options.outputDir, mergedOptions.outputFile);
263
+ const jsonPath = path.resolve(options.outputDir, v8JsonOptions.outputFile);
255
264
  await Util.writeFile(jsonPath, JSON.stringify(reportData));
256
265
  return Util.relativePath(jsonPath);
257
266
  };
258
267
 
259
268
 
260
- const saveV8HtmlReport = async (reportData, options, v8HtmlOptions) => {
269
+ const saveV8HtmlReport = async (reportData, reportOptions, options) => {
270
+
271
+ // V8 only options, merged with root options
272
+ const v8HtmlOptions = {
273
+ outputFile: options.outputFile,
274
+ inline: options.inline,
275
+ assetsPath: options.assetsPath,
276
+ metrics: options.metrics,
277
+ ... reportOptions
278
+ };
279
+
280
+ // add metrics to data for UI
281
+ reportData.metrics = v8HtmlOptions.metrics;
261
282
 
262
283
  const {
263
284
  outputFile, inline, assetsPath
@@ -314,51 +335,29 @@ const saveV8Report = async (v8list, options) => {
314
335
  files: v8list
315
336
  };
316
337
 
317
- let outputPath;
318
-
319
338
  // v8 reports
320
- const v8Group = options.reportGroup.v8;
321
-
322
- // v8 html
323
- const v8Options = v8Group.v8;
324
- if (v8Options) {
325
-
326
- // V8 only options, merged with root options
327
- const v8HtmlOptions = {
328
- outputFile: options.outputFile,
329
- inline: options.inline,
330
- assetsPath: options.assetsPath,
331
- metrics: options.metrics,
332
- ... v8Options
333
- };
334
-
335
- // add metrics to data
336
- reportData.metrics = v8HtmlOptions.metrics;
337
-
338
- outputPath = await saveV8HtmlReport(reportData, options, v8HtmlOptions);
339
- }
340
-
341
- // v8 json (after html for metrics data)
342
- const v8JsonOptions = v8Group['v8-json'];
343
- if (v8JsonOptions) {
344
- const jsonPath = await saveV8JsonReport(reportData, options, v8JsonOptions);
345
- if (!outputPath) {
346
- outputPath = jsonPath;
347
- }
348
- }
339
+ const buildInV8Reports = {
340
+ 'v8': saveV8HtmlReport,
341
+ 'v8-json': saveV8JsonReport,
342
+ 'codecov': saveCodecovReport
343
+ };
349
344
 
350
- // codecov
351
- const codecovOptions = v8Group.codecov;
352
- if (codecovOptions) {
353
- const jsonPath = await saveCodecovReport(reportData, options, codecovOptions);
354
- if (!outputPath) {
355
- outputPath = jsonPath;
345
+ const outputs = {};
346
+ const v8Group = options.reportGroup.v8;
347
+ const v8Reports = Object.keys(v8Group);
348
+ for (const reportName of v8Reports) {
349
+ const reportOptions = v8Group[reportName];
350
+ const buildInHandler = buildInV8Reports[reportName];
351
+ if (buildInHandler) {
352
+ outputs[reportName] = await buildInHandler(reportData, reportOptions, options);
353
+ } else {
354
+ outputs[reportName] = await Util.runCustomReporter(reportName, reportData, reportOptions, options);
356
355
  }
357
356
  }
358
357
 
359
- // outputPath last one is html
358
+ // outputPath, should be html or json
360
359
  const reportPath = Util.resolveReportPath(options, () => {
361
- return outputPath;
360
+ return outputs.v8 || outputs['v8-json'] || Object.values(outputs).filter((it) => it && typeof it === 'string').shift();
362
361
  });
363
362
 
364
363
  const coverageResults = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monocart-coverage-reports",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "Monocart coverage reports",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -20,16 +20,18 @@
20
20
  "build-docs": "node ./scripts/build-docs.js",
21
21
  "build-test": "node ./scripts/build-test.js",
22
22
  "build": "sf lint && sf b -p && npm run build-test",
23
- "test-node": "npm run test-node-env && npm run test-node-api && npm run test-node-ins && npm run test-node-cdp && npm run test-node-koa",
23
+ "test-node": "npm run test-node-env && npm run test-node-api && npm run test-node-ins && npm run test-node-cdp && npm run test-node-koa && npm run test-vm",
24
24
  "test-node-env": "cross-env NODE_V8_COVERAGE=.temp/v8-coverage-env node ./test/test-node-env.js && node ./test/generate-node-report.js",
25
25
  "test-node-api": "cross-env NODE_V8_COVERAGE=.temp/v8-coverage-api node ./test/test-node-api.js",
26
26
  "test-node-fgc": "node ./test/test-node-fgc.js",
27
27
  "test-node-ins": "node ./test/test-node-ins.js",
28
28
  "test-node-cdp": "node --inspect=9229 ./test/test-node-cdp.js",
29
29
  "test-node-koa": "node ./test/test-node-koa.js",
30
+ "test-vm": "node ./test/test-vm.js",
30
31
  "test-browser": "node ./test/test.js",
31
32
  "test-cli": "npx mcr \"node ./test/test-node-env.js\" -o docs/cli -c test/cli-options.js",
32
- "test": "npm run test-browser && npm run test-node && npm run test-cli && npm run build-docs",
33
+ "test-merge": "node ./test/test-merge.js",
34
+ "test": "npm run test-browser && npm run test-node && npm run test-cli && npm run test-merge && npm run build-docs",
33
35
  "dev": "sf d v8",
34
36
  "open": "node ./scripts/open.js",
35
37
  "patch": "npm run build && sf publish patch -r"
@@ -1,9 +0,0 @@
1
- const { parentPort } = require('worker_threads');
2
- const { decode } = require('../packages/monocart-coverage-vendor.js');
3
-
4
- parentPort.on('message', (message) => {
5
- const result = decode(message);
6
- parentPort.postMessage(result);
7
- });
8
-
9
- parentPort.postMessage('workerReady');
@@ -1,35 +0,0 @@
1
- const path = require('path');
2
- const { Worker } = require('worker_threads');
3
- const Util = require('./util.js');
4
-
5
- // do NOT use isMainThread because it could be executed in worker too. (worker in worker)
6
-
7
- const decodeMappings = (mappings = '') => {
8
- if (typeof text !== 'string') {
9
- mappings = String(mappings);
10
- }
11
-
12
- return new Promise((resolve) => {
13
-
14
- const workerPath = path.resolve(__dirname, 'decode-mappings-worker.js');
15
- const worker = new Worker(workerPath);
16
-
17
- worker.on('message', (message) => {
18
- if (message === 'workerReady') {
19
- worker.postMessage(mappings);
20
- return;
21
- }
22
- resolve(message);
23
- worker.terminate();
24
- });
25
-
26
- worker.on('error', (e) => {
27
- Util.logError(e.message);
28
- resolve([]);
29
- worker.terminate();
30
- });
31
-
32
- });
33
- };
34
-
35
- module.exports = decodeMappings;