From 2eb13c660ad0a5cc0c9f6601f668a984f07064db Mon Sep 17 00:00:00 2001 From: AndrewFinlay Date: Wed, 10 Apr 2019 09:06:35 +1000 Subject: [PATCH] feat: instrument `--complete-copy` implementation (#1056) With a few caveats: * This will dereference symlinks as it copies them, in some cases this could be a problem i.e. `node_modules/.bin` * This will not copy across empty directories --- index.js | 22 +++++++------ lib/commands/instrument.js | 5 +++ package-lock.json | 17 ++++++++++ package.json | 3 +- test/fixtures/cli/.instrument-nycrc | 3 +- test/nyc-integration.js | 50 ++++++++++++++++++++++++----- 6 files changed, 81 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index 493685948..f38645e6b 100755 --- a/index.js +++ b/index.js @@ -3,9 +3,10 @@ /* global __coverage__ */ const cachingTransform = require('caching-transform') -const util = require('util') +const cpFile = require('cp-file') const findCacheDir = require('find-cache-dir') const fs = require('fs') +const glob = require('glob') const Hash = require('./lib/hash') const libCoverage = require('istanbul-lib-coverage') const libHook = require('istanbul-lib-hook') @@ -19,6 +20,7 @@ const resolveFrom = require('resolve-from') const rimraf = require('rimraf') const SourceMaps = require('./lib/source-maps') const testExclude = require('test-exclude') +const util = require('util') const uuid = require('uuid/v4') const debugLog = util.debuglog('nyc') @@ -155,7 +157,7 @@ NYC.prototype.addAllFiles = function () { this._loadAdditionalModules() this.fakeRequire = true - this.walkAllFiles(this.cwd, relFile => { + this.exclude.globSync(this.cwd).forEach(relFile => { const filename = path.resolve(this.cwd, relFile) this.addFile(filename) const coverage = coverageFinder() @@ -193,7 +195,15 @@ NYC.prototype.instrumentAllFiles = function (input, output, cb) { const stats = fs.lstatSync(input) if (stats.isDirectory()) { inputDir = input - this.walkAllFiles(input, visitor) + + const filesToInstrument = this.exclude.globSync(input) + + if (this.config.completeCopy && output) { + const globOptions = { dot: true, nodir: true, ignore: ['**/.git', '**/.git/**', path.join(output, '**')] } + glob.sync(path.resolve(input, '**'), globOptions) + .forEach(src => cpFile.sync(src, path.join(output, path.relative(input, src)))) + } + filesToInstrument.forEach(visitor) } else { visitor(input) } @@ -203,12 +213,6 @@ NYC.prototype.instrumentAllFiles = function (input, output, cb) { cb() } -NYC.prototype.walkAllFiles = function (dir, visitor) { - this.exclude.globSync(dir).forEach(relFile => { - visitor(relFile) - }) -} - NYC.prototype._transform = function (code, filename) { const extname = path.extname(filename).toLowerCase() const transform = this.transforms[extname] || (() => null) diff --git a/lib/commands/instrument.js b/lib/commands/instrument.js index c22475c24..f744faef6 100644 --- a/lib/commands/instrument.js +++ b/lib/commands/instrument.js @@ -75,6 +75,11 @@ exports.builder = function (yargs) { default: false, type: 'boolean' }) + .option('complete-copy', { + describe: 'should nyc copy all files from input to output as well as instrumented files?', + default: false, + type: 'boolean' + }) .example('$0 instrument ./lib ./output', 'instrument all .js files in ./lib with coverage and output in ./output') } diff --git a/package-lock.json b/package-lock.json index 7bc532b33..e2ac9714c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -852,6 +852,18 @@ } } }, + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + } + }, "cross-spawn": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", @@ -2887,6 +2899,11 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==" }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==" + }, "newline-regex": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/newline-regex/-/newline-regex-0.2.1.tgz", diff --git a/package.json b/package.json index ac3b7eec2..aa617c29d 100644 --- a/package.json +++ b/package.json @@ -69,9 +69,11 @@ "archy": "^1.0.0", "caching-transform": "^3.0.2", "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", "find-cache-dir": "^2.1.0", "find-up": "^3.0.0", "foreground-child": "^1.5.6", + "glob": "^7.1.3", "istanbul-lib-coverage": "^2.0.4", "istanbul-lib-hook": "^2.0.5", "istanbul-lib-instrument": "^3.1.2", @@ -93,7 +95,6 @@ "any-path": "^1.3.0", "chai": "^4.2.0", "coveralls": "^3.0.3", - "glob": "^7.1.3", "is-windows": "^1.0.2", "lodash": "^4.17.11", "newline-regex": "^0.2.1", diff --git a/test/fixtures/cli/.instrument-nycrc b/test/fixtures/cli/.instrument-nycrc index 4063df5c0..dcd61a6b0 100644 --- a/test/fixtures/cli/.instrument-nycrc +++ b/test/fixtures/cli/.instrument-nycrc @@ -1,5 +1,6 @@ { "exclude": [ "**/exclude-me/**" - ] + ], + "complete-copy": true } diff --git a/test/nyc-integration.js b/test/nyc-integration.js index 2198afda7..792fc1293 100644 --- a/test/nyc-integration.js +++ b/test/nyc-integration.js @@ -689,6 +689,31 @@ describe('the nyc cli', function () { files.should.include('ignore.js') files.should.not.include('package.json') files.should.not.include('node_modules') + const includeTarget = path.resolve(fixturesCLI, 'output', 'ignore.js') + fs.readFileSync(includeTarget, 'utf8') + .should.match(/var cov_/) + done() + }) + }) + + it('copies all files from to as well as those that have been instrumented', function (done) { + const args = [bin, 'instrument', '--complete-copy', './nyc-config-js', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.should.include('index.js') + files.should.include('ignore.js') + files.should.include('package.json') + files.should.include('node_modules') + const includeTarget = path.resolve(fixturesCLI, 'output', 'ignore.js') + fs.readFileSync(includeTarget, 'utf8') + .should.match(/var cov_/) done() }) }) @@ -738,19 +763,22 @@ describe('the nyc cli', function () { code.should.equal(0) const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) files.length.should.not.equal(0) - files.should.not.include('exclude-me') - files.should.not.include('node_modules') + files.should.include('exclude-me') + files.should.include('node_modules') files.should.include('index.js') files.should.include('bad.js') const includeTarget = path.resolve(fixturesCLI, 'output', 'index.js') fs.readFileSync(includeTarget, 'utf8') .should.match(/var cov_/) + const excludeTarget = path.resolve(fixturesCLI, 'output', 'exclude-me', 'index.js') + fs.readFileSync(excludeTarget, 'utf8') + .should.not.match(/var cov_/) done() }) }) it('allows a file to be excluded', function (done) { - const args = [bin, 'instrument', '--exclude', 'exclude-me/index.js', './subdir/input-dir', './output'] + const args = [bin, 'instrument', '--complete-copy', '--exclude', 'exclude-me/index.js', './subdir/input-dir', './output'] const proc = spawn(process.execPath, args, { cwd: fixturesCLI, @@ -761,7 +789,10 @@ describe('the nyc cli', function () { code.should.equal(0) const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) files.length.should.not.equal(0) - files.should.not.include('exclude-me') + files.should.include('exclude-me') + const excludeTarget = path.resolve(fixturesCLI, 'output', 'exclude-me', 'index.js') + fs.readFileSync(excludeTarget, 'utf8') + .should.not.match(/var cov_/) done() }) }) @@ -787,7 +818,7 @@ describe('the nyc cli', function () { }) it('allows a file to be excluded from an included directory', function (done) { - const args = [bin, 'instrument', '--exclude', '**/exclude-me.js', '--include', '**/include-me/**', './subdir/input-dir', './output'] + const args = [bin, 'instrument', '--complete-copy', '--exclude', '**/exclude-me.js', '--include', '**/include-me/**', './subdir/input-dir', './output'] const proc = spawn(process.execPath, args, { cwd: fixturesCLI, @@ -802,10 +833,13 @@ describe('the nyc cli', function () { const includeMeFiles = fs.readdirSync(path.resolve(fixturesCLI, 'output', 'include-me')) includeMeFiles.length.should.not.equal(0) includeMeFiles.should.include('include-me.js') - includeMeFiles.should.not.include('exclude-me.js') - const instrumented = path.resolve(fixturesCLI, 'output', 'include-me', 'include-me.js') - fs.readFileSync(instrumented, 'utf8') + includeMeFiles.should.include('exclude-me.js') + const includeTarget = path.resolve(fixturesCLI, 'output', 'include-me', 'include-me.js') + fs.readFileSync(includeTarget, 'utf8') .should.match(/var cov_/) + const excludeTarget = path.resolve(fixturesCLI, 'output', 'exclude-me', 'index.js') + fs.readFileSync(excludeTarget, 'utf8') + .should.not.match(/var cov_/) done() }) })