c8 7.12.0 → 7.14.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # c8 - native V8 code-coverage
2
2
 
3
- ![ci](https://img.shields.io/github/workflow/status/bcoe/c8/ci?label=ci&logo=github)
3
+ [![ci](https://github.com/bcoe/c8/actions/workflows/ci.yaml/badge.svg)](https://github.com/bcoe/c8/actions/workflows/ci.yaml)
4
4
  ![nycrc config on GitHub](https://img.shields.io/nycrc/bcoe/c8)
5
5
  [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://www.conventionalcommits.org/)
6
6
 
@@ -40,6 +40,7 @@ Here is a list of common options. Run `c8 --help` for the full list and document
40
40
  | `-e`, `--extension` | only files matching these extensions will show coverage | `string \| Array<string>` | [list](https://github.com/istanbuljs/schema/blob/master/default-extension.js) |
41
41
  | `--skip-full` | do not show files with 100% statement, branch, and function coverage | `boolean` | `false` |
42
42
  | `--check-coverage` | check whether coverage is within thresholds provided | `boolean` | `false` |
43
+ | `--per-file` | check thresholds per file | `boolean` | `false` |
43
44
  | `--temp-directory` | directory V8 coverage data is written to and read from | `string` | `process.env.NODE_V8_COVERAGE` |
44
45
  | `--clean` | should temp files be deleted before script execution | `boolean` | `true` |
45
46
 
package/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export declare class Report {
8
8
  include?: string | string[],
9
9
  reporter: string[],
10
10
  reportsDirectory?: string,
11
+ reporterOptions?: Record<string, Record<string, unknown>>
11
12
  tempDirectory?: string,
12
13
  watermarks?: Partial<{
13
14
  statements: Watermark;
@@ -20,7 +21,9 @@ export declare class Report {
20
21
  resolve?: string,
21
22
  all?: boolean,
22
23
  src?: Array<string>,
23
- allowExternal?: boolean
24
+ allowExternal?: boolean,
25
+ skipFull?: boolean,
26
+ excludeNodeModules?: boolean
24
27
  })
25
28
  run(): Promise<void>;
26
29
  }
@@ -34,7 +34,8 @@ exports.outputReport = async function (argv) {
34
34
  allowExternal: argv.allowExternal,
35
35
  src: argv.src,
36
36
  skipFull: argv.skipFull,
37
- excludeNodeModules: argv.excludeNodeModules
37
+ excludeNodeModules: argv.excludeNodeModules,
38
+ mergeAsync: argv.mergeAsync
38
39
  })
39
40
  await report.run()
40
41
  if (argv.checkCoverage) await checkCoverages(argv, report)
package/lib/parse-args.js CHANGED
@@ -152,6 +152,12 @@ function buildYargs (withCommands = false) {
152
152
  describe: 'supplying --allowExternal will cause c8 to allow files from outside of your cwd. This applies both to ' +
153
153
  'files discovered in coverage temp files and also src files discovered if using the --all flag.'
154
154
  })
155
+ .options('merge-async', {
156
+ default: false,
157
+ type: 'boolean',
158
+ describe: 'supplying --merge-async will merge all v8 coverage reports asynchronously and incrementally. ' +
159
+ 'This is to avoid OOM issues with Node.js runtime.'
160
+ })
155
161
  .pkgConf('c8')
156
162
  .demandCommand(1)
157
163
  .check((argv) => {
package/lib/report.js CHANGED
@@ -2,6 +2,12 @@ const Exclude = require('test-exclude')
2
2
  const libCoverage = require('istanbul-lib-coverage')
3
3
  const libReport = require('istanbul-lib-report')
4
4
  const reports = require('istanbul-reports')
5
+ let readFile
6
+ try {
7
+ ;({ readFile } = require('fs/promises'))
8
+ } catch (err) {
9
+ ;({ readFile } = require('fs').promises)
10
+ }
5
11
  const { readdirSync, readFileSync, statSync } = require('fs')
6
12
  const { isAbsolute, resolve, extname } = require('path')
7
13
  const { pathToFileURL, fileURLToPath } = require('url')
@@ -19,6 +25,7 @@ class Report {
19
25
  excludeAfterRemap,
20
26
  include,
21
27
  reporter,
28
+ reporterOptions,
22
29
  reportsDirectory,
23
30
  tempDirectory,
24
31
  watermarks,
@@ -29,9 +36,11 @@ class Report {
29
36
  src,
30
37
  allowExternal = false,
31
38
  skipFull,
32
- excludeNodeModules
39
+ excludeNodeModules,
40
+ mergeAsync
33
41
  }) {
34
42
  this.reporter = reporter
43
+ this.reporterOptions = reporterOptions || {}
35
44
  this.reportsDirectory = reportsDirectory
36
45
  this.tempDirectory = tempDirectory
37
46
  this.watermarks = watermarks
@@ -51,6 +60,7 @@ class Report {
51
60
  this.all = all
52
61
  this.src = this._getSrc(src)
53
62
  this.skipFull = skipFull
63
+ this.mergeAsync = mergeAsync
54
64
  }
55
65
 
56
66
  _getSrc (src) {
@@ -74,7 +84,8 @@ class Report {
74
84
  reports.create(_reporter, {
75
85
  skipEmpty: false,
76
86
  skipFull: this.skipFull,
77
- maxCols: process.stdout.columns || 100
87
+ maxCols: process.stdout.columns || 100,
88
+ ...this.reporterOptions[_reporter]
78
89
  }).execute(context)
79
90
  }
80
91
  }
@@ -87,7 +98,13 @@ class Report {
87
98
  if (this._allCoverageFiles) return this._allCoverageFiles
88
99
 
89
100
  const map = libCoverage.createCoverageMap()
90
- const v8ProcessCov = this._getMergedProcessCov()
101
+ let v8ProcessCov
102
+
103
+ if (this.mergeAsync) {
104
+ v8ProcessCov = await this._getMergedProcessCovAsync()
105
+ } else {
106
+ v8ProcessCov = this._getMergedProcessCov()
107
+ }
91
108
  const resultCountPerPath = new Map()
92
109
  const possibleCjsEsmBridges = new Map()
93
110
 
@@ -185,43 +202,106 @@ class Report {
185
202
  }
186
203
 
187
204
  if (this.all) {
188
- const emptyReports = []
205
+ const emptyReports = this._includeUncoveredFiles(fileIndex)
189
206
  v8ProcessCovs.unshift({
190
207
  result: emptyReports
191
208
  })
192
- const workingDirs = this.src
193
- const { extension } = this.exclude
194
- for (const workingDir of workingDirs) {
195
- this.exclude.globSync(workingDir).forEach((f) => {
196
- const fullPath = resolve(workingDir, f)
197
- if (!fileIndex.has(fullPath)) {
198
- const ext = extname(fullPath)
199
- if (extension.includes(ext)) {
200
- const stat = statSync(fullPath)
201
- const sourceMap = getSourceMapFromFile(fullPath)
202
- if (sourceMap) {
203
- this.sourceMapCache[pathToFileURL(fullPath)] = { data: sourceMap }
204
- }
205
- emptyReports.push({
206
- scriptId: 0,
207
- url: resolve(fullPath),
208
- functions: [{
209
- functionName: '(empty-report)',
210
- ranges: [{
211
- startOffset: 0,
212
- endOffset: stat.size,
213
- count: 0
214
- }],
215
- isBlockCoverage: true
216
- }]
217
- })
218
- }
209
+ }
210
+
211
+ return mergeProcessCovs(v8ProcessCovs)
212
+ }
213
+
214
+ /**
215
+ * Returns the merged V8 process coverage.
216
+ *
217
+ * It asynchronously and incrementally reads and merges individual process coverages
218
+ * generated by Node. This can be used via the `--merge-async` CLI arg. It's intended
219
+ * to be used across a large multi-process test run.
220
+ *
221
+ * @return {ProcessCov} Merged V8 process coverage.
222
+ * @private
223
+ */
224
+ async _getMergedProcessCovAsync () {
225
+ const { mergeProcessCovs } = require('@bcoe/v8-coverage')
226
+ const fileIndex = new Set() // Set<string>
227
+ let mergedCov = null
228
+ for (const file of readdirSync(this.tempDirectory)) {
229
+ try {
230
+ const rawFile = await readFile(
231
+ resolve(this.tempDirectory, file),
232
+ 'utf8'
233
+ )
234
+ let report = JSON.parse(rawFile)
235
+
236
+ if (this._isCoverageObject(report)) {
237
+ if (report['source-map-cache']) {
238
+ Object.assign(this.sourceMapCache, this._normalizeSourceMapCache(report['source-map-cache']))
219
239
  }
220
- })
240
+ report = this._normalizeProcessCov(report, fileIndex)
241
+ if (mergedCov) {
242
+ mergedCov = mergeProcessCovs([mergedCov, report])
243
+ } else {
244
+ mergedCov = mergeProcessCovs([report])
245
+ }
246
+ }
247
+ } catch (err) {
248
+ debuglog(`${err.stack}`)
221
249
  }
222
250
  }
223
251
 
224
- return mergeProcessCovs(v8ProcessCovs)
252
+ if (this.all) {
253
+ const emptyReports = this._includeUncoveredFiles(fileIndex)
254
+ const emptyReport = {
255
+ result: emptyReports
256
+ }
257
+
258
+ mergedCov = mergeProcessCovs([emptyReport, mergedCov])
259
+ }
260
+
261
+ return mergedCov
262
+ }
263
+
264
+ /**
265
+ * Adds empty coverage reports to account for uncovered/untested code.
266
+ * This is only done when the `--all` flag is present.
267
+ *
268
+ * @param {Set} fileIndex list of files that have coverage
269
+ * @returns {Array} list of empty coverage reports
270
+ */
271
+ _includeUncoveredFiles (fileIndex) {
272
+ const emptyReports = []
273
+ const workingDirs = this.src
274
+ const { extension } = this.exclude
275
+ for (const workingDir of workingDirs) {
276
+ this.exclude.globSync(workingDir).forEach((f) => {
277
+ const fullPath = resolve(workingDir, f)
278
+ if (!fileIndex.has(fullPath)) {
279
+ const ext = extname(fullPath)
280
+ if (extension.includes(ext)) {
281
+ const stat = statSync(fullPath)
282
+ const sourceMap = getSourceMapFromFile(fullPath)
283
+ if (sourceMap) {
284
+ this.sourceMapCache[pathToFileURL(fullPath)] = { data: sourceMap }
285
+ }
286
+ emptyReports.push({
287
+ scriptId: 0,
288
+ url: resolve(fullPath),
289
+ functions: [{
290
+ functionName: '(empty-report)',
291
+ ranges: [{
292
+ startOffset: 0,
293
+ endOffset: stat.size,
294
+ count: 0
295
+ }],
296
+ isBlockCoverage: true
297
+ }]
298
+ })
299
+ }
300
+ }
301
+ })
302
+ }
303
+
304
+ return emptyReports
225
305
  }
226
306
 
227
307
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c8",
3
- "version": "7.12.0",
3
+ "version": "7.14.0",
4
4
  "description": "output coverage reports using Node.js' built in coverage",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
@@ -47,7 +47,7 @@
47
47
  "yargs-parser": "^20.2.9"
48
48
  },
49
49
  "devDependencies": {
50
- "@types/node": "^17.0.23",
50
+ "@types/node": "^18.0.0",
51
51
  "chai": "^4.3.6",
52
52
  "chai-jest-snapshot": "^2.0.0",
53
53
  "cross-env": "^7.0.3",