UNPKG

5.21 kBJavaScriptView Raw
1'use strict';
2
3const path = require('path');
4const { promisify } = require('util');
5const glob = promisify(require('glob'));
6const minimatch = require('minimatch');
7const { defaults } = require('@istanbuljs/schema');
8const isOutsideDir = require('./is-outside-dir');
9
10class TestExclude {
11 constructor(opts = {}) {
12 Object.assign(
13 this,
14 {relativePath: true},
15 defaults.testExclude
16 );
17
18 for (const [name, value] of Object.entries(opts)) {
19 if (value !== undefined) {
20 this[name] = value;
21 }
22 }
23
24 if (typeof this.include === 'string') {
25 this.include = [this.include];
26 }
27
28 if (typeof this.exclude === 'string') {
29 this.exclude = [this.exclude];
30 }
31
32 if (typeof this.extension === 'string') {
33 this.extension = [this.extension];
34 } else if (this.extension.length === 0) {
35 this.extension = false;
36 }
37
38 if (this.include && this.include.length > 0) {
39 this.include = prepGlobPatterns([].concat(this.include));
40 } else {
41 this.include = false;
42 }
43
44 if (
45 this.excludeNodeModules &&
46 !this.exclude.includes('**/node_modules/**')
47 ) {
48 this.exclude = this.exclude.concat('**/node_modules/**');
49 }
50
51 this.exclude = prepGlobPatterns([].concat(this.exclude));
52
53 this.handleNegation();
54 }
55
56 /* handle the special case of negative globs
57 * (!**foo/bar); we create a new this.excludeNegated set
58 * of rules, which is applied after excludes and we
59 * move excluded include rules into this.excludes.
60 */
61 handleNegation() {
62 const noNeg = e => e.charAt(0) !== '!';
63 const onlyNeg = e => e.charAt(0) === '!';
64 const stripNeg = e => e.slice(1);
65
66 if (Array.isArray(this.include)) {
67 const includeNegated = this.include.filter(onlyNeg).map(stripNeg);
68 this.exclude.push(...prepGlobPatterns(includeNegated));
69 this.include = this.include.filter(noNeg);
70 }
71
72 this.excludeNegated = this.exclude.filter(onlyNeg).map(stripNeg);
73 this.exclude = this.exclude.filter(noNeg);
74 this.excludeNegated = prepGlobPatterns(this.excludeNegated);
75 }
76
77 shouldInstrument(filename, relFile) {
78 if (
79 this.extension &&
80 !this.extension.some(ext => filename.endsWith(ext))
81 ) {
82 return false;
83 }
84
85 let pathToCheck = filename;
86
87 if (this.relativePath) {
88 relFile = relFile || path.relative(this.cwd, filename);
89
90 // Don't instrument files that are outside of the current working directory.
91 if (isOutsideDir(this.cwd, filename)) {
92 return false;
93 }
94
95 pathToCheck = relFile.replace(/^\.[\\/]/, ''); // remove leading './' or '.\'.
96 }
97
98 const dot = { dot: true };
99 const matches = pattern => minimatch(pathToCheck, pattern, dot);
100 return (
101 (!this.include || this.include.some(matches)) &&
102 (!this.exclude.some(matches) || this.excludeNegated.some(matches))
103 );
104 }
105
106 globSync(cwd = this.cwd) {
107 const globPatterns = getExtensionPattern(this.extension || []);
108 const globOptions = { cwd, nodir: true, dot: true };
109 /* If we don't have any excludeNegated then we can optimize glob by telling
110 * it to not iterate into unwanted directory trees (like node_modules). */
111 if (this.excludeNegated.length === 0) {
112 globOptions.ignore = this.exclude;
113 }
114
115 return glob
116 .sync(globPatterns, globOptions)
117 .filter(file => this.shouldInstrument(path.resolve(cwd, file)));
118 }
119
120 async glob(cwd = this.cwd) {
121 const globPatterns = getExtensionPattern(this.extension || []);
122 const globOptions = { cwd, nodir: true, dot: true };
123 /* If we don't have any excludeNegated then we can optimize glob by telling
124 * it to not iterate into unwanted directory trees (like node_modules). */
125 if (this.excludeNegated.length === 0) {
126 globOptions.ignore = this.exclude;
127 }
128
129 const list = await glob(globPatterns, globOptions);
130 return list.filter(file => this.shouldInstrument(path.resolve(cwd, file)));
131 }
132}
133
134function prepGlobPatterns(patterns) {
135 return patterns.reduce((result, pattern) => {
136 // Allow gitignore style of directory exclusion
137 if (!/\/\*\*$/.test(pattern)) {
138 result = result.concat(pattern.replace(/\/$/, '') + '/**');
139 }
140
141 // Any rules of the form **/foo.js, should also match foo.js.
142 if (/^\*\*\//.test(pattern)) {
143 result = result.concat(pattern.replace(/^\*\*\//, ''));
144 }
145
146 return result.concat(pattern);
147 }, []);
148}
149
150function getExtensionPattern(extension) {
151 switch (extension.length) {
152 case 0:
153 return '**';
154 case 1:
155 return `**/*${extension[0]}`;
156 default:
157 return `**/*{${extension.join()}}`;
158 }
159}
160
161module.exports = TestExclude;