monocart-coverage-reports 2.1.0 → 2.2.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/README.md CHANGED
@@ -11,20 +11,22 @@
11
11
  * [Usage](#usage)
12
12
  * [Default Options](#default-options)
13
13
  * [Available Reports](#available-reports)
14
- * [Multiprocessing Support](#multiprocessing-support)
14
+ * [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)
15
15
  * [onEnd Hook](#onend-hook)
16
+ * [`mcr` CLI](#mcr-cli)
16
17
  * [Compare Reports](#compare-reports)
17
18
  * [Compare Workflows](#compare-workflows)
18
19
  * [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
19
20
  * [Collecting V8 Coverage Data](#collecting-v8-coverage-data)
20
21
  * [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
21
- * [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)
22
+ * [Multiprocessing Support](#multiprocessing-support)
23
+ * [Integration](#integration)
24
+ * [Ignoring Uncovered Codes](#ignoring-uncovered-codes)
25
+ * [Chromium Coverage API](#chromium-coverage-api)
26
+ * [V8 Coverage Data Format](#v8-coverage-data-format)
22
27
  * [How to convert V8 to Istanbul](#how-to-convert-v8-to-istanbul)
23
28
  - [Using `v8-to-istanbul`](#using-v8-to-istanbul)
24
29
  - [How Monocart Works](#how-monocart-works)
25
- * [Ignoring Uncovered Lines](#ignoring-uncovered-lines)
26
- * [Chromium Coverage API](#chromium-coverage-api)
27
- * [V8 Coverage Data Format](#v8-coverage-data-format)
28
30
  * [Istanbul Introduction](#istanbul-introduction)
29
31
  * [Thanks](#thanks)
30
32
 
@@ -49,9 +51,14 @@ console.log(coverageResults.summary);
49
51
 
50
52
  ## Available Reports
51
53
  - `v8` (V8 data only)
52
- - [V8 html](https://cenfun.github.io/monocart-coverage-reports/v8) - Example for browser, build with webpack, and also [Rollup](https://cenfun.github.io/monocart-coverage-reports/v8-rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/v8-esbuild)
53
- - [V8 Node](https://cenfun.github.io/monocart-coverage-reports/v8-node-env) - Example for Node.js using env, and also [V8 API](https://cenfun.github.io/monocart-coverage-reports/v8-node-api), [Inspector](https://cenfun.github.io/monocart-coverage-reports/v8-node-ins) and [CDP](https://cenfun.github.io/monocart-coverage-reports/v8-node-cdp)
54
- - [V8 Minify](https://cenfun.github.io/monocart-coverage-reports/v8-minify)
54
+ - Test in browser
55
+ - Build with webpack: [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [V8 Minify](https://cenfun.github.io/monocart-coverage-reports/v8-minify)
56
+ - Build with [Rollup](https://cenfun.github.io/monocart-coverage-reports/v8-rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/v8-esbuild)
57
+ - Collect with [puppeteer](https://cenfun.github.io/monocart-coverage-reports/puppeteer/)
58
+ - [anonymous](https://cenfun.github.io/monocart-coverage-reports/anonymous/) and [css](https://cenfun.github.io/monocart-coverage-reports/css/)
59
+ - Test in Node.js
60
+ - Collect with [env](https://cenfun.github.io/monocart-coverage-reports/v8-node-env), and also V8 [API](https://cenfun.github.io/monocart-coverage-reports/v8-node-api), [Inspector](https://cenfun.github.io/monocart-coverage-reports/v8-node-ins) and [CDP](https://cenfun.github.io/monocart-coverage-reports/v8-node-cdp)
61
+ - Web server example: [koa](https://cenfun.github.io/monocart-coverage-reports/v8-node-koa/)
55
62
 
56
63
  ![](test/v8.gif)
57
64
 
@@ -100,37 +107,34 @@ const options = {
100
107
  const coverageReport = MCR(options);
101
108
  ```
102
109
 
103
- ## Integration
104
- - [monocart-reporter](https://cenfun.github.io/monocart-reporter/) - Test reporter for [Playwright](https://github.com/microsoft/playwright)
105
- - [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) coverage
106
-
107
-
108
- ## Multiprocessing Support
109
- The data will be added to `[outputDir]/.cache`, and the cache will be removed after reports generated.
110
- - sub process 1
111
- ```js
112
- const MCR = require('monocart-coverage-reports');
113
- const options = require('path-to/same-options.js');
114
- const coverageReport = MCR(options);
115
- await coverageReport.add(coverageData1);
110
+ ## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
111
+ When V8 coverage data collected, it actually contains the data of all entry files, for example:
116
112
  ```
117
-
118
- - sub process 2
119
- ```js
120
- const MCR = require('monocart-coverage-reports');
121
- const options = require('path-to/same-options.js');
122
- const coverageReport = MCR(options);
123
- await coverageReport.add(coverageData2);
113
+ dist/main.js
114
+ dist/vendor.js
115
+ dist/something-else.js
124
116
  ```
125
-
126
- - main process
117
+ We can use `entryFilter` to filter the entry files. For example, we should remove `vendor.js` and `something-else.js` if they are not in our coverage scope.
118
+ ```
119
+ dist/main.js
120
+ ```
121
+ When inline or linked sourcemap exists to the entry file, the source files will be extracted from the sourcemap for the entry file, and the entry file will be removed if `logging` is not `debug`.
122
+ ```
123
+ > src/index.js
124
+ > src/components/app.js
125
+ > node_modules/dependency/dist/dependency.js
126
+ ```
127
+ We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
128
+ ```
129
+ > src/index.js
130
+ > src/components/app.js
131
+ ```
132
+ Example:
127
133
  ```js
128
- // after all sub processes finished
129
- const MCR = require('monocart-coverage-reports');
130
- const options = require('path-to/same-options.js');
131
- const coverageReport = MCR(options);
132
- const coverageResults = await coverageReport.generate();
133
- console.log(coverageResults.summary);
134
+ const coverageOptions = {
135
+ entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
136
+ sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
137
+ };
134
138
  ```
135
139
 
136
140
  ## onEnd Hook
@@ -164,6 +168,44 @@ const coverageOptions = {
164
168
  }
165
169
  ```
166
170
 
171
+ ## `mcr` CLI
172
+ > The CLI will run the program as a [child process](https://nodejs.org/docs/latest/api/child_process.html) with `NODE_V8_COVERAGE=dir` until it exits gracefully, and generate the coverage report with the coverage data from the `dir`.
173
+ - Global mode
174
+ ```sh
175
+ npm i monocart-coverage-reports -g
176
+ mcr "node ./test/test-node-env.js" -r v8,console-summary --lcov
177
+ ```
178
+ - Current working directory mode
179
+ ```sh
180
+ npm i monocart-coverage-reports
181
+ npx mcr "node ./test/test-node-env.js" -r v8,console-summary --lcov
182
+ ```
183
+ - CLI Options
184
+ ```sh
185
+ Usage: mcr [options] <command>
186
+
187
+ CLI to generate coverage reports
188
+
189
+ Arguments:
190
+ command command to execute
191
+
192
+ Options:
193
+ -V, --version output the version number
194
+ -c, --config <path> config path for options
195
+ -o, --outputDir <dir> output dir for reports
196
+ -r, --reports <name[,name]> coverage reports to use
197
+ -n, --name <name> report name for title
198
+ --outputFile <path> output file for v8 report
199
+ --inline inline html for v8 report
200
+ --assetsPath <path> assets path if not inline
201
+ --lcov generate lcov.info file
202
+ -h, --help display help for command
203
+ ```
204
+ - Using configuration file for more [options](#default-options)
205
+ ```sh
206
+ mcr "node ./test/test-node-env.js" -c test/cli-options.js
207
+ ```
208
+
167
209
  ## Compare Reports
168
210
  | | Istanbul | V8 | V8 to Istanbul |
169
211
  | :--------------| :------ | :------ | :---------------------- |
@@ -192,133 +234,103 @@ const coverageOptions = {
192
234
  ## Collecting Istanbul Coverage Data
193
235
  - Instrumenting source code
194
236
  > Before collecting Istanbul coverage data, It requires your source code is instrumented with Istanbul
195
- - Webpack: [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), example: [webpack.config-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-istanbul.js)
196
- - Rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
197
- - Vite: [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul)
237
+ - webpack: [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), example: [webpack.config-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-istanbul.js)
238
+ - rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
239
+ - vite: [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul)
198
240
  - Browser
199
- > Collecting coverage data from `window.__coverage__`, example: [test-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-istanbul.js)
241
+ - Collecting coverage data from `window.__coverage__`, example: [test-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-istanbul.js)
200
242
  - Node.js
201
- > Collecting coverage data from `global.__coverage__`
243
+ - Collecting coverage data from `global.__coverage__`
202
244
 
203
245
  ## Collecting V8 Coverage Data
204
246
  - For source code: enable `sourcemap` and do not compress/minify:
205
- - Webpack: build with `source-map` [devtool](https://webpack.js.org/configuration/devtool/) and `development` [mode](https://webpack.js.org/configuration/mode/), example [webpack.config-v8.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-v8.js)
206
- - Rollup: build with [options](https://rollupjs.org/configuration-options/) `sourcemap: true`
207
- - Vite: build with [options](https://vitejs.dev/config/build-options.html) `sourcemap: true` and `minify: false`
247
+ - [webpack](https://webpack.js.org/configuration/): `devtool: source-map` and `mode: development`, example [webpack.config-v8.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-v8.js)
248
+ - [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true`
249
+ - [vite](https://vitejs.dev/config/build-options.html): `sourcemap: true` and `minify: false`
250
+ - [esbuild](https://esbuild.github.io/api/): `sourcemap: true` and `minify: false`
208
251
  - Browser (Chromium Only)
209
- > Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api), see [example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js)
252
+ > Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api):
253
+ - [Playwright example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js), and [anonymous](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-anonymous.js), [css](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-css.js)
254
+ - [Puppeteer example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-puppeteer.js)
210
255
  - Node.js
211
- > see [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
256
+ - see following [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
212
257
 
213
258
 
214
259
  ## Node.js V8 Coverage Report for Server Side
215
- - Using Node.js env [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
216
- - Before running your Node.js application, set env `NODE_V8_COVERAGE`=`dir`. After the application runs and exits, the coverage data will be saved to the `dir` directory in JSON file format
217
- - Read the json file(s) from the `dir` and generate coverage report
218
- - example:
260
+ Possible solutions:
261
+ - [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
262
+ - Sets Node.js env `NODE_V8_COVERAGE`=`dir` before the program running, the coverage data will be saved to the `dir` after the program exits gracefully.
263
+ - Read the JSON file(s) from the `dir` and generate coverage report.
264
+ - Example:
219
265
  > cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-env` node [./test/test-node-env.js](./test/test-node-env.js) && node [./test/generate-node-report.js](./test/generate-node-report.js)
220
266
 
221
- - Using [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
222
- - Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()` and stopping with `v8.stopCoverage()`.
223
- - example:
267
+ - [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
268
+ - Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()`, it does not require waiting until the program exits gracefully.
269
+ - Example:
224
270
  > cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-api` node [./test/test-node-api.js](./test/test-node-api.js)
225
271
 
226
- - Using [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API (or module [collect-v8-coverage](https://github.com/SimenB/collect-v8-coverage))
272
+ - [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API
227
273
  - Connecting to the V8 inspector and enable V8 coverage.
228
- - Taking coverage data and adding to the report after your application runs.
229
- - example: [./test/test-node-ins.js](./test/test-node-ins.js)
274
+ - Taking coverage data and adding it to the report.
275
+ - Example:
276
+ > node [./test/test-node-ins.js](./test/test-node-ins.js)
230
277
 
231
- - Using [CDP](https://chromedevtools.github.io/devtools-protocol/) API
232
- - Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/)
278
+ - [CDP](https://chromedevtools.github.io/devtools-protocol/) API
279
+ - Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/).
233
280
  - Collecting coverage data with CDP API.
234
- - example: [./test/test-node-cdp.js](./test/test-node-cdp.js)
281
+ - Example:
282
+ > node --inspect=9229 [./test/test-node-cdp.js](./test/test-node-cdp.js)
235
283
 
236
- ## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
237
- When V8 coverage data collected, it actually contains the data of all entry files, for example:
238
- ```
239
- dist/main.js
240
- dist/vendor.js
241
- dist/something-else.js
242
- ```
243
- We can use `entryFilter` to filter the entry files. For example, we should remove `vendor.js` and `something-else.js` if they are not in our coverage scope.
244
- ```
245
- dist/main.js
246
- ```
247
- When inline or linked sourcemap exists to the entry file, the source files will be extracted from the sourcemap for the entry file, and the entry file will be removed if `logging` is not `debug`.
248
- ```
249
- > src/index.js
250
- > src/components/app.js
251
- > node_modules/dependency/dist/dependency.js
252
- ```
253
- We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
254
- ```
255
- > src/index.js
256
- > src/components/app.js
257
- ```
258
- Example:
284
+ - [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started) + CDP + NODE_V8_COVERAGE + V8 API
285
+ - When the program starts a server, it will not exit on its own, thus requiring a manual invocation of the `v8.takeCoverage()` interface to manually collect coverage data. Remote invocation of the `v8.takeCoverage()` interface can be accomplished through the `Runtime.evaluate` of the CDP.
286
+ - Example for [koa](https://github.com/koajs/koa) web server:
287
+ > node [./test/test-node-koa.js](./test/test-node-koa.js)
288
+
289
+ - [Child Process](https://nodejs.org/docs/latest/api/child_process.html) + NODE_V8_COVERAGE
290
+ - see [`mcr` CLI](#mcr-cli)
291
+
292
+ ## Multiprocessing Support
293
+ The data will be added to `[outputDir]/.cache`, and the cache will be removed after reports generated.
294
+ - sub process 1
259
295
  ```js
260
- const coverageOptions = {
261
- entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
262
- sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
263
- };
296
+ const MCR = require('monocart-coverage-reports');
297
+ const options = require('path-to/same-options.js');
298
+ const coverageReport = MCR(options);
299
+ await coverageReport.add(coverageData1);
264
300
  ```
265
301
 
266
- ## How to convert V8 to Istanbul
267
- ### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
268
- It is a popular library which is used to convert V8 coverage format to istanbul's coverage format. Most test frameworks are using it, such as [Jest](https://github.com/jestjs/jest/), [Vitest](https://github.com/vitest-dev/vitest), but it has two major problems:
269
- - 1, The source mapping does not work well if the position is between the two consecutive mappings. for example:
302
+ - sub process 2
270
303
  ```js
271
- const a = tf ? 'true' : 'false';
272
- ^ ^ ^
273
- m1 p m2
304
+ const MCR = require('monocart-coverage-reports');
305
+ const options = require('path-to/same-options.js');
306
+ const coverageReport = MCR(options);
307
+ await coverageReport.add(coverageData2);
274
308
  ```
275
- > `m1` and `m2` are two consecutive mappings, `p` is the position we looking for. However, we can only get the position of the `m1` if we don't fix it to `p`. Especially the generated code is different from the original code, such as minified, compressed or converted, then it becomes very difficult to find the middle position between two mappings.
276
309
 
277
- - 2, The coverage of functions and branches is incorrect. V8 only provided coverage at functions and it's blocks. But if a function is uncovered (count = 0), there is no information for it's blocks and sub-level functions.
278
- And also there are some problems about counting the functions and branches:
310
+ - main process
279
311
  ```js
280
- functions.forEach(block => {
281
- block.ranges.forEach((range, i) => {
282
- if (block.isBlockCoverage) {
283
- // v8-to-istanbul: new CovBranch()
284
- // Problem: not every block is branch, and the first block is actually function.
285
- if (block.functionName && i === 0) {
286
- // v8-to-istanbul: new CovFunction()
287
- // Problem: no anonymous function
288
- }
289
- } else if (block.functionName) {
290
- // v8-to-istanbul: new CovFunction()
291
- // Problem: no anonymous function
292
- }
293
- }
294
- });
295
- // Problem: When the function or parent-level function is uncovered, then its sub-level functions will never be counted.
312
+ // after all sub processes finished
313
+ const MCR = require('monocart-coverage-reports');
314
+ const options = require('path-to/same-options.js');
315
+ const coverageReport = MCR(options);
316
+ const coverageResults = await coverageReport.generate();
317
+ console.log(coverageResults.summary);
296
318
  ```
297
- see source code [v8-to-istanbul.js](https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/v8-to-istanbul.js)
298
319
 
299
- ### How Monocart Works
300
- We have removed `v8-to-istanbul` because of the two major problems and implemented new converter:
301
- - 1, Trying to fix the middle position if not found the exact mapping.
302
- - 2, Finding all functions and branches by parsing the source code [AST](https://github.com/acornjs/acorn), however the V8 cannot provide effective branch coverage information, so the branches is still not perfect.
303
-
304
- | AST | V8 |
305
- | :--------------| :------ |
306
- | AssignmentPattern | 🛇 Not Support |
307
- | ConditionalExpression | ✔ Not Perfect |
308
- | IfStatement | ✔ Not Perfect |
309
- | LogicalExpression | ✔ Not Perfect |
310
- | SwitchStatement | ✔ Not Perfect |
320
+ ## Integration
321
+ - [monocart-reporter](https://cenfun.github.io/monocart-reporter/) - Test reporter for [Playwright](https://github.com/microsoft/playwright)
322
+ - [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) coverage
311
323
 
312
324
  ## Ignoring Uncovered Codes
313
325
  To ignore codes, use the special comment which starts with `v8 ignore `:
314
- - ignoring all until told
326
+ - Ignoring all until stop
315
327
  ```js
316
328
  /* v8 ignore start */
317
329
  function uncovered() {
318
330
  }
319
331
  /* v8 ignore stop */
320
332
  ```
321
- - ignoring the next line or next N lines
333
+ - Ignoring the next line or next N lines
322
334
  ```js
323
335
  /* v8 ignore next */
324
336
  const os = platform === 'wind32' ? 'Windows' : 'Other';
@@ -333,8 +345,8 @@ if (platform === 'linux') {
333
345
 
334
346
  ## Chromium Coverage API
335
347
  - [V8 coverage report](https://v8.dev/blog/javascript-code-coverage) - Native support for JavaScript code coverage to V8. (Chromium only)
336
- - [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
337
348
  - [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
349
+ - [Puppeteer Coverage class](https://pptr.dev/api/puppeteer.coverage)
338
350
  - [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage)
339
351
 
340
352
  ## V8 Coverage Data Format
@@ -380,6 +392,71 @@ export type V8CoverageData = ScriptCoverage[];
380
392
  ```
381
393
  see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
382
394
 
395
+ ### Collect raw v8 coverage data with Puppeteer
396
+ ```js
397
+ await page.coverage.startJSCoverage({
398
+ // provide raw v8 coverage data
399
+ includeRawScriptCoverage: true
400
+ });
401
+
402
+ await page.goto(url);
403
+
404
+ const jsCoverage = await page.coverage.stopJSCoverage();
405
+ const rawV8CoverageData = jsCoverage.map((it) => {
406
+ // Convert to raw v8 coverage format
407
+ return {
408
+ source: it.text,
409
+ ... it.rawScriptCoverage
410
+ };
411
+ }
412
+ ```
413
+ see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
414
+
415
+ ## How to convert V8 to Istanbul
416
+ ### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
417
+ It is a popular library which is used to convert V8 coverage format to istanbul's coverage format. Most test frameworks are using it, such as [Jest](https://github.com/jestjs/jest/), [Vitest](https://github.com/vitest-dev/vitest), but it has two major problems:
418
+ - 1, The source mapping does not work well if the position is between the two consecutive mappings. for example:
419
+ ```js
420
+ const a = tf ? 'true' : 'false';
421
+ ^ ^ ^
422
+ m1 p m2
423
+ ```
424
+ > `m1` and `m2` are two consecutive mappings, `p` is the position we looking for. However, we can only get the position of the `m1` or `m2` if we don't fix it to `p`. Especially the generated code is different from the original code, such as the code was minified, compressed or converted, it is difficult to find the exact position.
425
+
426
+ - 2, The coverage of functions and branches is incorrect. V8 only provided coverage at functions and it's blocks. But if a function is uncovered (count = 0), there is no information for it's blocks and sub-level functions.
427
+ And also there are some problems about counting the functions and branches:
428
+ ```js
429
+ // Problem: When the function or parent-level function is uncovered, then its sub-level functions will never be counted.
430
+ functions.forEach(block => {
431
+ block.ranges.forEach((range, i) => {
432
+ if (block.isBlockCoverage) {
433
+ // v8-to-istanbul: new CovBranch()
434
+ // Problem: not every block is branch, and the first block could be function.
435
+ if (block.functionName && i === 0) {
436
+ // v8-to-istanbul: new CovFunction()
437
+ // Problem: no anonymous function
438
+ }
439
+ } else if (block.functionName) {
440
+ // v8-to-istanbul: new CovFunction()
441
+ // Problem: no anonymous function
442
+ }
443
+ }
444
+ });
445
+ ```
446
+ see source code [v8-to-istanbul.js](https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/v8-to-istanbul.js)
447
+
448
+ ### How Monocart Works
449
+ We implemented new converter instead of v8-to-istanbul:
450
+ - 1, Trying to fix the middle position if not found the exact mapping for the position.
451
+ - 2, Finding all functions and branches by parsing the source code [AST](https://github.com/acornjs/acorn), however the V8 cannot provide effective branch coverage information, so the branches is still not perfect but close to reality.
452
+
453
+ | AST | V8 |
454
+ | :---------------------| :------------- |
455
+ | AssignmentPattern | 🛇 Not Support |
456
+ | ConditionalExpression | ✔ Not Perfect |
457
+ | IfStatement | ✔ Not Perfect |
458
+ | LogicalExpression | ✔ Not Perfect |
459
+ | SwitchStatement | ✔ Not Perfect |
383
460
 
384
461
  ## Istanbul Introduction
385
462
  - [Istanbul coverage report](https://istanbul.js.org/) - Instrumenting source codes and generating coverage reports
package/lib/cli.js ADDED
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const EC = require('eight-colors');
6
+ const { fileURLToPath } = require('url');
7
+
8
+ const {
9
+ foregroundChild,
10
+ program
11
+ } = require('./packages/monocart-coverage-vendor.js');
12
+
13
+ const MCR = require('./index.js');
14
+ const Util = require('./utils/util.js');
15
+ const defaultOptions = require('./default/options.js');
16
+ const version = require('../package.json').version;
17
+
18
+ const cleanDir = (dir) => {
19
+ if (fs.existsSync(dir)) {
20
+ fs.rmSync(dir, {
21
+ recursive: true,
22
+ force: true
23
+ });
24
+ }
25
+ };
26
+
27
+ const initOptions = (cliOptions) => {
28
+ // merge all cli options
29
+ const configPath = cliOptions.config;
30
+ if (configPath) {
31
+ if (fs.existsSync(configPath)) {
32
+ const config = require(path.resolve(configPath));
33
+ Object.assign(cliOptions, config);
34
+ } else {
35
+ EC.logRed(`Not found config: ${configPath}`);
36
+ }
37
+ }
38
+
39
+ // init reports to list with `,`
40
+ const reports = cliOptions.reports;
41
+ if (reports) {
42
+ cliOptions.reports = reports.split(',');
43
+ }
44
+
45
+ // report options
46
+ const options = {
47
+ ... defaultOptions
48
+ };
49
+ Object.keys(cliOptions).forEach((k) => {
50
+ if (Util.hasOwn(options, k)) {
51
+ options[k] = cliOptions[k];
52
+ }
53
+ });
54
+
55
+ return options;
56
+ };
57
+
58
+ const executeCommand = async (command, cliOptions) => {
59
+
60
+ const options = initOptions(cliOptions);
61
+
62
+ console.log('Execute:', EC.cyan(command));
63
+ // console.log(options);
64
+
65
+ const nodeV8CoverageDir = path.resolve(options.outputDir, '.v8-coverage');
66
+ process.env.NODE_V8_COVERAGE = nodeV8CoverageDir;
67
+
68
+ cleanDir(nodeV8CoverageDir);
69
+
70
+ // console.log('nodeV8CoverageDir', nodeV8CoverageDir);
71
+
72
+ const coverageReport = MCR(options);
73
+ coverageReport.cleanCache();
74
+
75
+ // on start hook
76
+ const onStart = cliOptions.onStart;
77
+ if (typeof onStart === 'function') {
78
+ await onStart(coverageReport);
79
+ }
80
+
81
+ foregroundChild(command, {
82
+ shell: true
83
+ }, async () => {
84
+
85
+ if (!fs.existsSync(nodeV8CoverageDir)) {
86
+ EC.logRed(`Not found coverage data: ${Util.relativePath(nodeV8CoverageDir)}`);
87
+ return 1;
88
+ }
89
+
90
+ const files = fs.readdirSync(nodeV8CoverageDir);
91
+ for (const filename of files) {
92
+ const content = fs.readFileSync(path.resolve(nodeV8CoverageDir, filename)).toString('utf-8');
93
+ const json = JSON.parse(content);
94
+ let coverageList = json.result;
95
+
96
+ // filter node internal files
97
+ coverageList = coverageList.filter((entry) => entry.url && entry.url.startsWith('file:'));
98
+
99
+ if (!coverageList.length) {
100
+ continue;
101
+ }
102
+ // attached source content
103
+ coverageList.forEach((entry) => {
104
+ const filePath = fileURLToPath(entry.url);
105
+ if (fs.existsSync(filePath)) {
106
+ entry.source = fs.readFileSync(filePath).toString('utf8');
107
+ } else {
108
+ EC.logRed('not found file', Util.relativePath(filePath));
109
+ }
110
+ });
111
+
112
+ await coverageReport.add(coverageList);
113
+ }
114
+
115
+ await coverageReport.generate();
116
+
117
+ // remove nodeV8CoverageDir
118
+ if (options.logging !== 'debug') {
119
+ cleanDir(nodeV8CoverageDir);
120
+ }
121
+
122
+ return process.exitCode;
123
+ });
124
+
125
+ };
126
+
127
+ program
128
+ .name('mcr')
129
+ .description('CLI to generate coverage reports')
130
+ .version(version)
131
+ .argument('<command>', 'command to execute')
132
+ .option('-c, --config <path>', 'config path for options')
133
+ .option('-o, --outputDir <dir>', 'output dir for reports')
134
+ .option('-r, --reports <name[,name]>', 'coverage reports to use')
135
+ .option('-n, --name <name>', 'report name for title')
136
+
137
+ .option('--outputFile <path>', 'output file for v8 report')
138
+ .option('--inline', 'inline html for v8 report')
139
+ .option('--assetsPath <path>', 'assets path if not inline')
140
+
141
+ .option('--lcov', 'generate lcov.info file')
142
+
143
+ .action((command, cliOptions) => {
144
+ executeCommand(command, cliOptions);
145
+ });
146
+
147
+ program.parse();
@@ -170,7 +170,7 @@ const getJsAstInfo = (item, coverageList) => {
170
170
  try {
171
171
  ast = acorn.parse(source, options);
172
172
  } catch (e) {
173
- Util.logError(`failed to parse source file and fall back to loose: ${item.sourcePath} ${e.message}`);
173
+ Util.logInfo(`failed to parse source file: ${item.sourcePath} ${e.message}`);
174
174
  // https://github.com/acornjs/acorn/tree/master/acorn-loose
175
175
  // It is recommended to always try a parse with the regular acorn parser first,
176
176
  // and only fall back to this parser when that one finds syntax errors.
@@ -774,8 +774,9 @@ const collectOriginalList = (item, state, originalMap, options) => {
774
774
 
775
775
  // add file item
776
776
  const url = fileUrls[sourcePath] || sourcePath;
777
+
777
778
  // add dist for id
778
- const id = Util.calculateSha1(distFile + url + source);
779
+ const id = Util.calculateSha1(distFile + sourcePath + source);
779
780
 
780
781
  const sourceItem = {
781
782
  url,
@@ -975,22 +976,30 @@ const convertV8List = async (v8list, options) => {
975
976
  // merge state
976
977
  Object.assign(fileSources, state.fileSources);
977
978
  Object.assign(coverageData, state.coverageData);
978
- if (state.sourceList) {
979
+
980
+ if (Util.isList(state.sourceList)) {
979
981
  sourceList = sourceList.concat(state.sourceList);
980
982
  }
981
983
 
982
984
  }
983
985
 
984
- // dedupe
985
- dedupeV8List(v8list);
986
-
987
986
  // add all sources
988
987
  if (sourceList.length) {
989
988
  sourceList.forEach((item) => {
989
+
990
+ // exists same id, mark previous item as dedupe
991
+ const prevItem = v8list.find((it) => it.id === item.id);
992
+ if (prevItem) {
993
+ prevItem.dedupe = true;
994
+ }
995
+
990
996
  v8list.push(item);
991
997
  });
992
998
  }
993
999
 
1000
+ // dedupe
1001
+ dedupeV8List(v8list);
1002
+
994
1003
  return {
995
1004
  fileSources,
996
1005
  coverageData