| const assign = require('object-assign') |
| const arrify = require('arrify') |
| const micromatch = require('micromatch') |
| const path = require('path') |
| const readPkgUp = require('read-pkg-up') |
| const requireMainFilename = require('require-main-filename') |
| |
| function TestExclude (opts) { |
| assign(this, { |
| cwd: process.cwd(), |
| include: false, |
| relativePath: true, |
| configKey: null, // the key to load config from in package.json. |
| configPath: null, // optionally override requireMainFilename. |
| configFound: false |
| }, opts) |
| |
| if (typeof this.include === 'string') this.include = [this.include] |
| if (typeof this.exclude === 'string') this.exclude = [this.exclude] |
| |
| if (!this.include && !this.exclude && this.configKey) { |
| assign(this, this.pkgConf(this.configKey, this.configPath)) |
| } |
| |
| if (!this.exclude || !Array.isArray(this.exclude)) { |
| this.exclude = exportFunc.defaultExclude |
| } |
| |
| if (this.include && this.include.length > 0) { |
| this.include = prepGlobPatterns(arrify(this.include)) |
| } else { |
| this.include = false |
| } |
| |
| if (this.exclude.indexOf('**/node_modules/**') === -1) { |
| this.exclude.push('**/node_modules/**') |
| } |
| |
| this.exclude = prepGlobPatterns( |
| [].concat(arrify(this.exclude)) |
| ) |
| |
| this.handleNegation() |
| } |
| |
| // handle the special case of negative globs |
| // (!**foo/bar); we create a new this.excludeNegated set |
| // of rules, which is applied after excludes and we |
| // move excluded include rules into this.excludes. |
| TestExclude.prototype.handleNegation = function () { |
| if (Array.isArray(this.include)) { |
| const includeNegated = this.include.filter(function (e) { |
| return e.charAt(0) === '!' |
| }).map(function (e) { |
| return e.slice(1) |
| }) |
| this.exclude.push.apply(this.exclude, prepGlobPatterns(includeNegated)) |
| this.include = this.include.filter(function (e) { |
| return e.charAt(0) !== '!' |
| }) |
| } |
| |
| this.excludeNegated = this.exclude.filter(function (e) { |
| return e.charAt(0) === '!' |
| }).map(function (e) { |
| return e.slice(1) |
| }) |
| this.exclude = this.exclude.filter(function (e) { |
| return e.charAt(0) !== '!' |
| }) |
| this.excludeNegated = prepGlobPatterns(this.excludeNegated) |
| } |
| |
| TestExclude.prototype.shouldInstrument = function (filename, relFile) { |
| var pathToCheck = filename |
| |
| if (this.relativePath) { |
| relFile = relFile || path.relative(this.cwd, filename) |
| |
| // Don't instrument files that are outside of the current working directory. |
| if (/^\.\./.test(path.relative(this.cwd, filename))) return false |
| |
| pathToCheck = relFile.replace(/^\.[\\/]/, '') // remove leading './' or '.\'. |
| } |
| |
| return ( |
| !this.include || |
| micromatch.any(pathToCheck, this.include, {dotfiles: true})) && |
| (!micromatch.any(pathToCheck, this.exclude, {dotfiles: true}) || |
| micromatch.any(pathToCheck, this.excludeNegated, {dotfiles: true})) |
| } |
| |
| TestExclude.prototype.pkgConf = function (key, path) { |
| const obj = readPkgUp.sync({ |
| cwd: path || requireMainFilename(require) |
| }) |
| |
| if (obj.pkg && obj.pkg[key] && typeof obj.pkg[key] === 'object') { |
| this.configFound = true |
| return obj.pkg[key] |
| } else { |
| return {} |
| } |
| } |
| |
| function prepGlobPatterns (patterns) { |
| return patterns.reduce(function (result, pattern) { |
| // Allow gitignore style of directory exclusion |
| if (!/\/\*\*$/.test(pattern)) { |
| result = result.concat(pattern.replace(/\/$/, '') + '/**') |
| } |
| |
| // Any rules of the form **/foo.js, should also match foo.js. |
| if (/^\*\*\//.test(pattern)) { |
| result = result.concat(pattern.replace(/^\*\*\//, '')) |
| } |
| |
| return result.concat(pattern) |
| }, []) |
| } |
| |
| var exportFunc = function (opts) { |
| return new TestExclude(opts) |
| } |
| |
| exportFunc.defaultExclude = [ |
| 'coverage/**', |
| 'test/**', |
| 'test{,-*}.js', |
| '**/*.test.js', |
| '**/__tests__/**', |
| '**/node_modules/**' |
| ] |
| |
| module.exports = exportFunc |