187 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			187 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | const path = require('path'); | ||
|  | const glob = require('glob'); | ||
|  | const minimatch = require('minimatch'); | ||
|  | const readPkgUp = require('read-pkg-up'); | ||
|  | const requireMainFilename = require('require-main-filename'); | ||
|  | 
 | ||
|  | class TestExclude { | ||
|  |     constructor(opts) { | ||
|  |         Object.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, | ||
|  |                 excludeNodeModules: true, | ||
|  |                 extension: false | ||
|  |             }, | ||
|  |             opts | ||
|  |         ); | ||
|  | 
 | ||
|  |         if (typeof this.include === 'string') { | ||
|  |             this.include = [this.include]; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (typeof this.exclude === 'string') { | ||
|  |             this.exclude = [this.exclude]; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (typeof this.extension === 'string') { | ||
|  |             this.extension = [this.extension]; | ||
|  |         } else if ( | ||
|  |             !Array.isArray(this.extension) || | ||
|  |             this.extension.length === 0 | ||
|  |         ) { | ||
|  |             this.extension = false; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (!this.include && !this.exclude && this.configKey) { | ||
|  |             Object.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([].concat(this.include)); | ||
|  |         } else { | ||
|  |             this.include = false; | ||
|  |         } | ||
|  | 
 | ||
|  |         if ( | ||
|  |             this.excludeNodeModules && | ||
|  |             !this.exclude.includes('**/node_modules/**') | ||
|  |         ) { | ||
|  |             this.exclude = this.exclude.concat('**/node_modules/**'); | ||
|  |         } | ||
|  | 
 | ||
|  |         this.exclude = prepGlobPatterns([].concat(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. | ||
|  |      */ | ||
|  |     handleNegation() { | ||
|  |         const noNeg = e => e.charAt(0) !== '!'; | ||
|  |         const onlyNeg = e => e.charAt(0) === '!'; | ||
|  |         const stripNeg = e => e.slice(1); | ||
|  | 
 | ||
|  |         if (Array.isArray(this.include)) { | ||
|  |             const includeNegated = this.include.filter(onlyNeg).map(stripNeg); | ||
|  |             this.exclude.push(...prepGlobPatterns(includeNegated)); | ||
|  |             this.include = this.include.filter(noNeg); | ||
|  |         } | ||
|  | 
 | ||
|  |         this.excludeNegated = this.exclude.filter(onlyNeg).map(stripNeg); | ||
|  |         this.exclude = this.exclude.filter(noNeg); | ||
|  |         this.excludeNegated = prepGlobPatterns(this.excludeNegated); | ||
|  |     } | ||
|  | 
 | ||
|  |     shouldInstrument(filename, relFile) { | ||
|  |         if ( | ||
|  |             this.extension && | ||
|  |             !this.extension.some(ext => filename.endsWith(ext)) | ||
|  |         ) { | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         let 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 '.\'.
 | ||
|  |         } | ||
|  | 
 | ||
|  |         const dot = { dot: true }; | ||
|  |         const matches = pattern => minimatch(pathToCheck, pattern, dot); | ||
|  |         return ( | ||
|  |             (!this.include || this.include.some(matches)) && | ||
|  |             (!this.exclude.some(matches) || this.excludeNegated.some(matches)) | ||
|  |         ); | ||
|  |     } | ||
|  | 
 | ||
|  |     pkgConf(key, path) { | ||
|  |         const cwd = path || requireMainFilename(require); | ||
|  |         const obj = readPkgUp.sync({ cwd }); | ||
|  | 
 | ||
|  |         if (obj.pkg && obj.pkg[key] && typeof obj.pkg[key] === 'object') { | ||
|  |             this.configFound = true; | ||
|  | 
 | ||
|  |             return obj.pkg[key]; | ||
|  |         } | ||
|  | 
 | ||
|  |         return {}; | ||
|  |     } | ||
|  | 
 | ||
|  |     globSync(cwd = this.cwd) { | ||
|  |         const globPatterns = getExtensionPattern(this.extension || []); | ||
|  |         const globOptions = { cwd, nodir: true, dot: true }; | ||
|  |         /* If we don't have any excludeNegated then we can optimize glob by telling | ||
|  |          * it to not iterate into unwanted directory trees (like node_modules). */ | ||
|  |         if (this.excludeNegated.length === 0) { | ||
|  |             globOptions.ignore = this.exclude; | ||
|  |         } | ||
|  | 
 | ||
|  |         return glob | ||
|  |             .sync(globPatterns, globOptions) | ||
|  |             .filter(file => this.shouldInstrument(path.resolve(cwd, file))); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | function prepGlobPatterns(patterns) { | ||
|  |     return patterns.reduce((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); | ||
|  |     }, []); | ||
|  | } | ||
|  | 
 | ||
|  | function getExtensionPattern(extension) { | ||
|  |     switch (extension.length) { | ||
|  |         case 0: | ||
|  |             return '**'; | ||
|  |         case 1: | ||
|  |             return `**/*${extension[0]}`; | ||
|  |         default: | ||
|  |             return `**/*{${extension.join()}}`; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | const exportFunc = opts => new TestExclude(opts); | ||
|  | 
 | ||
|  | const devConfigs = ['ava', 'babel', 'jest', 'nyc', 'rollup', 'webpack']; | ||
|  | 
 | ||
|  | exportFunc.defaultExclude = [ | ||
|  |     'coverage/**', | ||
|  |     'packages/*/test/**', | ||
|  |     'test/**', | ||
|  |     'test{,-*}.js', | ||
|  |     '**/*{.,-}test.js', | ||
|  |     '**/__tests__/**', | ||
|  |     `**/{${devConfigs.join()}}.config.js` | ||
|  | ]; | ||
|  | 
 | ||
|  | module.exports = exportFunc; |