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;
 |