Skip to content

Commit 7783284

Browse files
AndrewFinlaycoreyfarrell
authored andcommitted
feat: Allow nyc instrument to instrument code in place (#1149)
This change adds the `--in-place` switch to nyc instrument If `--in-place` is specified, the output directory will be set to equal the input directory for the instrument command. This has the effect of replacing any file in the input directory that should be instrumented, with its instrumented counterpart. The command will throw an error if the --delete option is specified. The only way to instrument in place is with the --in-place switch, setting the input and output directories to be the same will not work If `--in-place` is set the instrument command ignores any output directory specified with the command If `--in-place` is set the instrument command will disable the `--complete-copy` switch as it is unnecessary.
1 parent c358ce1 commit 7783284

File tree

6 files changed

+104
-30
lines changed

6 files changed

+104
-30
lines changed

docs/instrument.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@ For example, `nyc instrument . ./output` will produce instrumented versions of a
2020

2121
The `--delete` option will remove the existing output directory before instrumenting.
2222

23+
The `--in-place` option will allow you to run the instrument command.
24+
2325
The `--complete-copy` option will copy all remaining files from the `input` directory to the `output` directory.
2426
When using `--complete-copy` nyc will not copy the contents of a `.git` folder to the output directory.
2527

2628
**Note:** `--complete-copy` will dereference any symlinks during the copy process, this may stop scripts running properly from the output directory.
2729

2830
## Streaming instrumentation
2931

30-
`nyc instrument` will stream instrumented source directly to `stdout`, that can then be piped to another process.
31-
You can use this behaviour to create a server that can instrument files on request.
32+
`nyc instrument <input>` will stream instrumented source directly to `stdout` and that output can then be piped to another process.
33+
You can use this behaviour to create a server that dynamically instruments files on request.
3234
The following example shows streaming instrumentation middleware capable of instrumenting files on request.
3335

3436
```javascript

lib/commands/instrument.js

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,32 +31,37 @@ exports.builder = function (yargs) {
3131
.option('source-map', {
3232
default: true,
3333
type: 'boolean',
34-
description: 'should nyc detect and handle source maps?'
34+
describe: 'should nyc detect and handle source maps?'
3535
})
3636
.option('produce-source-map', {
3737
default: false,
3838
type: 'boolean',
39-
description: "should nyc's instrumenter produce source maps?"
39+
describe: "should nyc's instrumenter produce source maps?"
4040
})
4141
.option('compact', {
4242
default: true,
4343
type: 'boolean',
44-
description: 'should the output be compacted?'
44+
describe: 'should the output be compacted?'
4545
})
4646
.option('preserve-comments', {
4747
default: true,
4848
type: 'boolean',
49-
description: 'should comments be preserved in the output?'
49+
describe: 'should comments be preserved in the output?'
5050
})
5151
.option('instrument', {
5252
default: true,
5353
type: 'boolean',
54-
description: 'should nyc handle instrumentation?'
54+
describe: 'should nyc handle instrumentation?'
55+
})
56+
.option('in-place', {
57+
default: false,
58+
type: 'boolean',
59+
describe: 'should nyc run the instrumentation in place?'
5560
})
5661
.option('exit-on-error', {
5762
default: false,
5863
type: 'boolean',
59-
description: 'should nyc exit when an instrumentation failure occurs?'
64+
describe: 'should nyc exit when an instrumentation failure occurs?'
6065
})
6166
.option('include', {
6267
alias: 'n',
@@ -92,26 +97,30 @@ exports.builder = function (yargs) {
9297
.example('$0 instrument ./lib ./output', 'instrument all .js files in ./lib with coverage and output in ./output')
9398
}
9499

100+
const errorExit = msg => {
101+
console.error(`nyc instrument failed: ${msg}`)
102+
process.exitCode = 1
103+
}
104+
95105
exports.handler = function (argv) {
96-
if (argv.output && (path.resolve(argv.cwd, argv.input) === path.resolve(argv.cwd, argv.output))) {
97-
console.error(`nyc instrument failed: cannot instrument files in place, <input> must differ from <output>`)
98-
process.exitCode = 1
99-
return
106+
if (argv.output && !argv.inPlace && (path.resolve(argv.cwd, argv.input) === path.resolve(argv.cwd, argv.output))) {
107+
return errorExit(`cannot instrument files in place, <input> must differ from <output>. Set '--in-place' to force`)
100108
}
101109

102110
if (path.relative(argv.cwd, path.resolve(argv.cwd, argv.input)).startsWith('..')) {
103-
console.error(`nyc instrument failed: cannot instrument files outside of project root directory`)
104-
process.exitCode = 1
105-
return
111+
return errorExit('cannot instrument files outside project root directory')
112+
}
113+
114+
if (argv.delete && argv.inPlace) {
115+
return errorExit(`cannot use '--delete' when instrumenting files in place`)
106116
}
107117

108118
if (argv.delete && argv.output && argv.output.length !== 0) {
109119
const relPath = path.relative(process.cwd(), path.resolve(argv.output))
110120
if (relPath !== '' && !relPath.startsWith('..')) {
111121
rimraf.sync(argv.output)
112122
} else {
113-
console.error(`nyc instrument failed: attempt to delete '${process.cwd()}' or containing directory.`)
114-
process.exit(1)
123+
return errorExit(`attempt to delete '${process.cwd()}' or containing directory.`)
115124
}
116125
}
117126

@@ -120,12 +129,11 @@ exports.handler = function (argv) {
120129
? './lib/instrumenters/istanbul'
121130
: './lib/instrumenters/noop'
122131

123-
const nyc = new NYC(argv)
132+
if (argv.inPlace) {
133+
argv.output = argv.input
134+
argv.completeCopy = false
135+
}
124136

125-
nyc.instrumentAllFiles(argv.input, argv.output, err => {
126-
if (err) {
127-
console.error(err.message)
128-
process.exitCode = 1
129-
}
130-
})
137+
const nyc = new NYC(argv)
138+
nyc.instrumentAllFiles(argv.input, argv.output, err => err ? errorExit(err.message) : null)
131139
}

tap-snapshots/test-nyc-integration.js-TAP.test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ All files | 0 | 0 | 0 | 0 |
103103
test.js | 0 | 0 | 0 | 0 |
104104
cli/fakebin | 0 | 100 | 100 | 0 |
105105
npm-template.js | 0 | 100 | 100 | 0 | 2,3,4,7,9
106+
cli/instrument-inplace | 0 | 100 | 0 | 0 |
107+
file1.js | 0 | 100 | 0 | 0 | 2,5
108+
file2.js | 0 | 100 | 0 | 0 | 2,5
106109
cli/nyc-config-js | 0 | 0 | 100 | 0 |
107110
ignore.js | 0 | 100 | 100 | 0 | 1
108111
index.js | 0 | 0 | 100 | 0 | 1,3,5,7,8,9,10
@@ -126,7 +129,7 @@ exports[`test/nyc-integration.js TAP --ignore-class-method skips methods that ma
126129
---------------------------------|---------|----------|---------|---------|-------------------------
127130
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
128131
---------------------------------|---------|----------|---------|---------|-------------------------
129-
All files | 1.49 | 0 | 5.56 | 1.96 |
132+
All files | 1.45 | 0 | 5 | 1.89 |
130133
cli | 2.08 | 0 | 5.56 | 3.13 |
131134
args.js | 0 | 100 | 100 | 0 | 1
132135
by-arg2.js | 0 | 0 | 100 | 0 | 1,2,3,4,5,7
@@ -144,6 +147,9 @@ All files | 1.49 | 0 | 5.56 | 1.96 |
144147
skip-full.js | 0 | 100 | 100 | 0 | 1,2
145148
cli/fakebin | 0 | 100 | 100 | 0 |
146149
npm-template.js | 0 | 100 | 100 | 0 | 2,3,4,7,9
150+
cli/instrument-inplace | 0 | 100 | 0 | 0 |
151+
file1.js | 0 | 100 | 0 | 0 | 2,5
152+
file2.js | 0 | 100 | 0 | 0 | 2,5
147153
cli/nyc-config-js | 0 | 0 | 100 | 0 |
148154
ignore.js | 0 | 100 | 100 | 0 | 1
149155
index.js | 0 | 0 | 100 | 0 | 1,3,5,7,8,9,10
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function test() {
2+
return 'file1'
3+
}
4+
5+
module.exports = test
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function test() {
2+
return 'file2'
3+
}
4+
5+
module.exports = test

test/nyc-integration-old.js

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const fixturesCLI = path.resolve(__dirname, './fixtures/cli')
77
const fakebin = path.resolve(fixturesCLI, 'fakebin')
88
const fs = require('fs')
99
const isWindows = require('is-windows')()
10+
const cpFile = require('cp-file')
1011
const rimraf = require('rimraf')
1112
const makeDir = require('make-dir')
1213
const { spawn } = require('child_process')
@@ -325,7 +326,7 @@ describe('the nyc cli', function () {
325326
})
326327

327328
it('aborts if trying to write files in place', function (done) {
328-
const args = [bin, 'instrument', '--delete', './', './']
329+
const args = [bin, 'instrument', './', './']
329330

330331
const proc = spawn(process.execPath, args, {
331332
cwd: fixturesCLI,
@@ -339,7 +340,54 @@ describe('the nyc cli', function () {
339340

340341
proc.on('close', function (code) {
341342
code.should.equal(1)
342-
stderr.should.include('nyc instrument failed: cannot instrument files in place')
343+
stderr.should.include('cannot instrument files in place')
344+
done()
345+
})
346+
})
347+
348+
it('can write files in place with --in-place switch', function (done) {
349+
const args = [bin, 'instrument', '--in-place', '--include', '*/file1.js', './test-instrument-inplace']
350+
351+
const sourceDir = path.resolve(fixturesCLI, 'instrument-inplace')
352+
const destDir = path.resolve(fixturesCLI, 'test-instrument-inplace')
353+
makeDir.sync(destDir)
354+
cpFile.sync(path.join(sourceDir, 'file1.js'), path.join(destDir, 'file1.js'))
355+
cpFile.sync(path.join(sourceDir, 'file2.js'), path.join(destDir, 'file2.js'))
356+
357+
const proc = spawn(process.execPath, args, {
358+
cwd: fixturesCLI,
359+
env: env
360+
})
361+
362+
proc.on('close', function (code) {
363+
code.should.equal(0)
364+
const file1 = path.resolve(destDir, 'file1.js')
365+
fs.readFileSync(file1, 'utf8')
366+
.should.match(/var cov_/)
367+
const file2 = path.resolve(destDir, 'file2.js')
368+
fs.readFileSync(file2, 'utf8')
369+
.should.not.match(/var cov_/)
370+
rimraf.sync(destDir)
371+
done()
372+
})
373+
})
374+
375+
it('aborts if trying to delete while writing files in place', function (done) {
376+
const args = [bin, 'instrument', '--in-place', '--delete', '--include', 'file1.js', './instrument-inplace']
377+
378+
const proc = spawn(process.execPath, args, {
379+
cwd: fixturesCLI,
380+
env: env
381+
})
382+
383+
let stderr = ''
384+
proc.stderr.on('data', function (chunk) {
385+
stderr += chunk
386+
})
387+
388+
proc.on('close', function (code) {
389+
code.should.equal(1)
390+
stderr.should.include(`cannot use '--delete' when instrumenting files in place`)
343391
done()
344392
})
345393
})
@@ -359,7 +407,7 @@ describe('the nyc cli', function () {
359407

360408
proc.on('close', function (code) {
361409
code.should.equal(1)
362-
stderr.should.include('nyc instrument failed: cannot instrument files outside of project root directory')
410+
stderr.should.include('cannot instrument files outside project root directory')
363411
done()
364412
})
365413
})
@@ -421,7 +469,7 @@ describe('the nyc cli', function () {
421469

422470
proc.on('close', function (code) {
423471
code.should.equal(1)
424-
stderr.should.include('nyc instrument failed: attempt to delete')
472+
stderr.should.include('attempt to delete')
425473
done()
426474
})
427475
})
@@ -441,7 +489,7 @@ describe('the nyc cli', function () {
441489

442490
proc.on('close', function (code) {
443491
code.should.equal(1)
444-
stderr.should.include('nyc instrument failed: attempt to delete')
492+
stderr.should.include('attempt to delete')
445493
done()
446494
})
447495
})

0 commit comments

Comments
 (0)