UNPKG

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