Skip to content

Commit 9936e28

Browse files
authored
feat: merge nyc options from multiple files (#205)
Merges NYC settings from - defaults - .nycrc - .nycrc.json - package.json nyc object
1 parent ffb75ea commit 9936e28

File tree

7 files changed

+201
-40
lines changed

7 files changed

+201
-40
lines changed

.circleci/config.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ workflows:
369369
store_artifacts: true
370370
post-steps:
371371
- run: cat examples/exclude-files/.nyc_output/out.json
372+
- run: cat examples/exclude-files/coverage/coverage-final.json
372373
# store the created coverage report folder
373374
# you can click on it in the CircleCI UI
374375
# to see live static HTML site
@@ -380,9 +381,11 @@ workflows:
380381
working_directory: examples/exclude-files
381382
- run:
382383
name: Check code coverage 📈
384+
# we will check the final coverage report
385+
# to make sure it only has files we are interested in
383386
command: |
384387
../../node_modules/.bin/check-coverage main.js
385-
../../node_modules/.bin/only-covered main.js
388+
../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js
386389
working_directory: examples/exclude-files
387390

388391
- cypress/run:

.nycrc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"exclude": [
3+
"support-utils.js",
4+
"task-utils.js"
5+
]
6+
}

cypress/integration/combine-spec.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const { combineNycOptions, defaultNycOptions } = require('../../task-utils')
2+
describe('Combine NYC options', () => {
3+
it('overrides defaults', () => {
4+
const pkgNycOptions = {
5+
extends: '@istanbuljs/nyc-config-typescript',
6+
all: true
7+
}
8+
const combined = combineNycOptions({
9+
pkgNycOptions,
10+
defaultNycOptions
11+
})
12+
cy.wrap(combined).should('deep.equal', {
13+
extends: '@istanbuljs/nyc-config-typescript',
14+
all: true,
15+
'report-dir': './coverage',
16+
reporter: ['lcov', 'clover', 'json'],
17+
extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'],
18+
excludeAfterRemap: true
19+
})
20+
})
21+
22+
it('allows to specify reporter, but changes to array', () => {
23+
const pkgNycOptions = {
24+
reporter: 'text'
25+
}
26+
const combined = combineNycOptions({
27+
pkgNycOptions,
28+
defaultNycOptions
29+
})
30+
cy.wrap(combined).should('deep.equal', {
31+
'report-dir': './coverage',
32+
reporter: ['text'],
33+
extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'],
34+
excludeAfterRemap: true
35+
})
36+
})
37+
38+
it('combines multiple options', () => {
39+
const pkgNycOptions = {
40+
all: true,
41+
extension: '.js'
42+
}
43+
const nycrc = {
44+
include: ['foo.js']
45+
}
46+
const nycrcJson = {
47+
exclude: ['bar.js'],
48+
reporter: ['json']
49+
}
50+
const combined = combineNycOptions({
51+
pkgNycOptions,
52+
nycrc,
53+
nycrcJson,
54+
defaultNycOptions
55+
})
56+
cy.wrap(combined).should('deep.equal', {
57+
all: true,
58+
'report-dir': './coverage',
59+
reporter: ['json'],
60+
extension: ['.js'],
61+
excludeAfterRemap: true,
62+
include: ['foo.js'],
63+
exclude: ['bar.js']
64+
})
65+
})
66+
})

package-lock.json

Lines changed: 12 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"@cypress/webpack-preprocessor": "5.1.2",
5959
"babel-loader": "8.1.0",
6060
"babel-plugin-istanbul": "6.0.0",
61-
"check-code-coverage": "1.0.1",
61+
"check-code-coverage": "1.1.0",
6262
"console-log-div": "0.6.3",
6363
"cypress": "4.4.0",
6464
"express": "4.17.1",
@@ -72,10 +72,5 @@
7272
"typescript": "3.8.3",
7373
"webpack": "4.42.1",
7474
"webpack-cli": "3.3.11"
75-
},
76-
"nyc": {
77-
"exclude": [
78-
"utils.js"
79-
]
8075
}
8176
}

task-utils.js

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,66 @@ const { readFileSync, writeFileSync, existsSync } = require('fs')
77
const { isAbsolute, resolve, join } = require('path')
88
const debug = require('debug')('code-coverage')
99

10+
function combineNycOptions({
11+
pkgNycOptions,
12+
nycrc,
13+
nycrcJson,
14+
defaultNycOptions
15+
}) {
16+
// last option wins
17+
const nycOptions = Object.assign(
18+
{},
19+
defaultNycOptions,
20+
nycrc,
21+
nycrcJson,
22+
pkgNycOptions
23+
)
24+
25+
if (typeof nycOptions.reporter === 'string') {
26+
nycOptions.reporter = [nycOptions.reporter]
27+
}
28+
if (typeof nycOptions.extension === 'string') {
29+
nycOptions.extension = [nycOptions.extension]
30+
}
31+
32+
return nycOptions
33+
}
34+
35+
const defaultNycOptions = {
36+
'report-dir': './coverage',
37+
reporter: ['lcov', 'clover', 'json'],
38+
extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'],
39+
excludeAfterRemap: true
40+
}
41+
42+
function readNycOptions(workingDirectory) {
43+
const pkgFilename = join(workingDirectory, 'package.json')
44+
const pkg = existsSync(pkgFilename)
45+
? JSON.parse(readFileSync(pkgFilename, 'utf8'))
46+
: {}
47+
const pkgNycOptions = pkg.nyc || {}
48+
49+
const nycrcFilename = join(workingDirectory, '.nycrc')
50+
const nycrc = existsSync(nycrcFilename)
51+
? JSON.parse(readFileSync(nycrcFilename, 'utf8'))
52+
: {}
53+
54+
const nycrcJsonFilename = join(workingDirectory, '.nycrc.json')
55+
const nycrcJson = existsSync(nycrcJsonFilename)
56+
? JSON.parse(readFileSync(nycrcJsonFilename, 'utf8'))
57+
: {}
58+
59+
const nycOptions = combineNycOptions({
60+
pkgNycOptions,
61+
nycrc,
62+
nycrcJson,
63+
defaultNycOptions
64+
})
65+
debug('combined NYC options %o', nycOptions)
66+
67+
return nycOptions
68+
}
69+
1070
function checkAllPathsNotFound(nycFilename) {
1171
const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8'))
1272

@@ -198,5 +258,8 @@ module.exports = {
198258
showNycInfo,
199259
resolveRelativePaths,
200260
checkAllPathsNotFound,
201-
tryFindingLocalFiles
261+
tryFindingLocalFiles,
262+
readNycOptions,
263+
combineNycOptions,
264+
defaultNycOptions
202265
}

task.js

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const {
77
showNycInfo,
88
resolveRelativePaths,
99
checkAllPathsNotFound,
10-
tryFindingLocalFiles
10+
tryFindingLocalFiles,
11+
readNycOptions
1112
} = require('./task-utils')
1213
const { fixSourcePaths } = require('./support-utils')
1314
const NYC = require('nyc')
@@ -28,7 +29,6 @@ const pkgFilename = join(processWorkingDirectory, 'package.json')
2829
const pkg = existsSync(pkgFilename)
2930
? JSON.parse(readFileSync(pkgFilename, 'utf8'))
3031
: {}
31-
const nycOptions = pkg.nyc || {}
3232
const scripts = pkg.scripts || {}
3333
const DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME = 'coverage:report'
3434
const customNycReportScript = scripts[DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME]
@@ -42,6 +42,37 @@ function saveCoverage(coverage) {
4242
writeFileSync(nycFilename, JSON.stringify(coverage, null, 2))
4343
}
4444

45+
function maybePrintFinalCoverageFiles(folder) {
46+
const jsonReportFilename = join(folder, 'coverage-final.json')
47+
if (!existsSync) {
48+
debug('Did not find final coverage file %s', jsonReportFilename)
49+
return
50+
}
51+
52+
debug('Final coverage in %s', jsonReportFilename)
53+
const finalCoverage = JSON.parse(readFileSync(jsonReportFilename, 'utf8'))
54+
Object.keys(finalCoverage).forEach(key => {
55+
const s = finalCoverage[key].s || {}
56+
const statements = Object.keys(s)
57+
const totalStatements = statements.length
58+
let coveredStatements = 0
59+
statements.forEach(statementKey => {
60+
if (s[statementKey]) {
61+
coveredStatements += 1
62+
}
63+
})
64+
65+
const allCovered = coveredStatements === totalStatements
66+
debug(
67+
'%s %s statements covered %d/%d',
68+
allCovered ? '✅' : '⚠️',
69+
key,
70+
coveredStatements,
71+
totalStatements
72+
)
73+
})
74+
}
75+
4576
const tasks = {
4677
/**
4778
* Clears accumulated code coverage information.
@@ -122,41 +153,29 @@ const tasks = {
122153
})
123154
}
124155

125-
const reportFolder = nycOptions['report-dir'] || './coverage'
126-
const reportDir = resolve(reportFolder)
127-
const reporter = nycOptions['reporter'] || ['lcov', 'clover', 'json']
128-
129-
// TODO we could look at how NYC is parsing its CLI arguments
130-
// I am mostly worried about additional NYC options that are stored in
131-
// package.json and .nycrc resource files.
132-
// for now let's just camel case all options
133156
// https://github.com/istanbuljs/nyc#common-configuration-options
134-
const nycReportOptions = {
135-
reportDir,
136-
tempDir: coverageFolder,
137-
reporter: [].concat(reporter), // make sure this is a list
138-
include: nycOptions.include,
139-
exclude: nycOptions.exclude,
140-
// from working with TypeScript code seems we need these settings too
141-
excludeAfterRemap: true,
142-
extension: nycOptions.extension || [
143-
'.js',
144-
'.cjs',
145-
'.mjs',
146-
'.ts',
147-
'.tsx',
148-
'.jsx'
149-
],
150-
all: nycOptions.all
157+
const nycReportOptions = readNycOptions(processWorkingDirectory)
158+
159+
// override a couple of options
160+
nycReportOptions.tempDir = coverageFolder
161+
if (nycReportOptions['report-dir']) {
162+
nycReportOptions['report-dir'] = resolve(nycReportOptions['report-dir'])
151163
}
152164

153165
debug('calling NYC reporter with options %o', nycReportOptions)
154166
debug('current working directory is %s', process.cwd())
155167
const nyc = new NYC(nycReportOptions)
156168

157169
const returnReportFolder = () => {
158-
debug('after reporting, returning the report folder name %s', reportDir)
159-
return reportDir
170+
const reportFolder = nycReportOptions['report-dir']
171+
debug(
172+
'after reporting, returning the report folder name %s',
173+
reportFolder
174+
)
175+
176+
maybePrintFinalCoverageFiles(reportFolder)
177+
178+
return reportFolder
160179
}
161180
return nyc.report().then(returnReportFolder)
162181
}

0 commit comments

Comments
 (0)