You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					187 lines
				
				5.6 KiB
			
		
		
			
		
	
	
					187 lines
				
				5.6 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								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;
							 |