1 | 'use strict';
|
2 | const path = require('path');
|
3 | const globby = require('globby');
|
4 | const ignoreByDefault = require('ignore-by-default');
|
5 | const picomatch = require('picomatch');
|
6 | const slash = require('slash');
|
7 | const providerManager = require('./provider-manager');
|
8 |
|
9 | const defaultIgnorePatterns = [...ignoreByDefault.directories(), '**/node_modules'];
|
10 | const defaultPicomatchIgnorePatterns = [
|
11 | ...defaultIgnorePatterns,
|
12 |
|
13 | ...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`)
|
14 | ];
|
15 |
|
16 | const defaultMatchNoIgnore = picomatch(defaultPicomatchIgnorePatterns);
|
17 |
|
18 | const defaultIgnoredByWatcherPatterns = [
|
19 | '**/*.snap.md',
|
20 | 'ava.config.js',
|
21 | 'ava.config.cjs'
|
22 | ];
|
23 |
|
24 | const buildExtensionPattern = extensions => extensions.length === 1 ? extensions[0] : `{${extensions.join(',')}}`;
|
25 |
|
26 | function normalizePattern(pattern) {
|
27 |
|
28 | if (process.platform === 'win32') {
|
29 | pattern = slash(pattern);
|
30 | }
|
31 |
|
32 | if (pattern.startsWith('./')) {
|
33 | return pattern.slice(2);
|
34 | }
|
35 |
|
36 | if (pattern.startsWith('!./')) {
|
37 | return `!${pattern.slice(3)}`;
|
38 | }
|
39 |
|
40 | return pattern;
|
41 | }
|
42 |
|
43 | exports.normalizePattern = normalizePattern;
|
44 |
|
45 | function normalizePatterns(patterns) {
|
46 | return patterns.map(pattern => normalizePattern(pattern));
|
47 | }
|
48 |
|
49 | exports.normalizePatterns = normalizePatterns;
|
50 |
|
51 | function normalizeGlobs({extensions, files: filePatterns, ignoredByWatcher: ignoredByWatcherPatterns, providers}) {
|
52 | if (filePatterns !== undefined && (!Array.isArray(filePatterns) || filePatterns.length === 0)) {
|
53 | throw new Error('The ’files’ configuration must be an array containing glob patterns.');
|
54 | }
|
55 |
|
56 | if (ignoredByWatcherPatterns !== undefined && (!Array.isArray(ignoredByWatcherPatterns) || ignoredByWatcherPatterns.length === 0)) {
|
57 | throw new Error('The ’ignoredByWatcher’ configuration must be an array containing glob patterns.');
|
58 | }
|
59 |
|
60 | const extensionPattern = buildExtensionPattern(extensions);
|
61 | const defaultTestPatterns = [
|
62 | `test.${extensionPattern}`,
|
63 | `{src,source}/test.${extensionPattern}`,
|
64 | `**/__tests__/**/*.${extensionPattern}`,
|
65 | `**/*.spec.${extensionPattern}`,
|
66 | `**/*.test.${extensionPattern}`,
|
67 | `**/test-*.${extensionPattern}`,
|
68 | `**/test/**/*.${extensionPattern}`,
|
69 | `**/tests/**/*.${extensionPattern}`,
|
70 | '!**/__tests__/**/__{helper,fixture}?(s)__/**/*',
|
71 | '!**/test?(s)/**/{helper,fixture}?(s)/**/*'
|
72 | ];
|
73 |
|
74 | if (filePatterns) {
|
75 | filePatterns = normalizePatterns(filePatterns);
|
76 |
|
77 | if (filePatterns.every(pattern => pattern.startsWith('!'))) {
|
78 |
|
79 | filePatterns = [...defaultTestPatterns, ...filePatterns];
|
80 | }
|
81 | } else {
|
82 | filePatterns = defaultTestPatterns;
|
83 | }
|
84 |
|
85 | if (ignoredByWatcherPatterns) {
|
86 | ignoredByWatcherPatterns = [...defaultIgnoredByWatcherPatterns, ...normalizePatterns(ignoredByWatcherPatterns)];
|
87 | } else {
|
88 | ignoredByWatcherPatterns = [...defaultIgnoredByWatcherPatterns];
|
89 | }
|
90 |
|
91 | for (const {level, main} of providers) {
|
92 | if (level >= providerManager.levels.pathRewrites) {
|
93 | ({filePatterns, ignoredByWatcherPatterns} = main.updateGlobs({filePatterns, ignoredByWatcherPatterns}));
|
94 | }
|
95 | }
|
96 |
|
97 | return {extensions, filePatterns, ignoredByWatcherPatterns};
|
98 | }
|
99 |
|
100 | exports.normalizeGlobs = normalizeGlobs;
|
101 |
|
102 | const hasExtension = (extensions, file) => extensions.includes(path.extname(file).slice(1));
|
103 |
|
104 | exports.hasExtension = hasExtension;
|
105 |
|
106 | const globFiles = async (cwd, patterns) => {
|
107 | const files = await globby(patterns, {
|
108 |
|
109 |
|
110 | absolute: false,
|
111 | braceExpansion: true,
|
112 | caseSensitiveMatch: false,
|
113 | cwd,
|
114 | dot: false,
|
115 | expandDirectories: false,
|
116 | extglob: true,
|
117 | followSymbolicLinks: true,
|
118 | gitignore: false,
|
119 | globstar: true,
|
120 | ignore: defaultIgnorePatterns,
|
121 | baseNameMatch: false,
|
122 | onlyFiles: true,
|
123 | stats: false,
|
124 | unique: true
|
125 | });
|
126 |
|
127 |
|
128 |
|
129 | return files.map(file => path.join(cwd, file));
|
130 | };
|
131 |
|
132 | async function findFiles({cwd, extensions, filePatterns}) {
|
133 | return (await globFiles(cwd, filePatterns)).filter(file => hasExtension(extensions, file));
|
134 | }
|
135 |
|
136 | exports.findFiles = findFiles;
|
137 |
|
138 | async function findTests({cwd, extensions, filePatterns}) {
|
139 | return (await findFiles({cwd, extensions, filePatterns})).filter(file => !path.basename(file).startsWith('_'));
|
140 | }
|
141 |
|
142 | exports.findTests = findTests;
|
143 |
|
144 | function getChokidarIgnorePatterns({ignoredByWatcherPatterns}) {
|
145 | return [
|
146 | ...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`),
|
147 | ...ignoredByWatcherPatterns.filter(pattern => !pattern.startsWith('!'))
|
148 | ];
|
149 | }
|
150 |
|
151 | exports.getChokidarIgnorePatterns = getChokidarIgnorePatterns;
|
152 |
|
153 | const matchingCache = new WeakMap();
|
154 | const processMatchingPatterns = input => {
|
155 | let result = matchingCache.get(input);
|
156 | if (!result) {
|
157 | const ignore = [...defaultPicomatchIgnorePatterns];
|
158 | const patterns = input.filter(pattern => {
|
159 | if (pattern.startsWith('!')) {
|
160 |
|
161 | ignore.push(pattern.slice(1), `${pattern.slice(1)}/**/*`);
|
162 | return false;
|
163 | }
|
164 |
|
165 | return true;
|
166 | });
|
167 |
|
168 | result = {
|
169 | match: picomatch(patterns, {ignore}),
|
170 | matchNoIgnore: picomatch(patterns)
|
171 | };
|
172 | matchingCache.set(input, result);
|
173 | }
|
174 |
|
175 | return result;
|
176 | };
|
177 |
|
178 | function matches(file, patterns) {
|
179 | const {match} = processMatchingPatterns(patterns);
|
180 | return match(file);
|
181 | }
|
182 |
|
183 | exports.matches = matches;
|
184 |
|
185 | const matchesIgnorePatterns = (file, patterns) => {
|
186 | const {matchNoIgnore} = processMatchingPatterns(patterns);
|
187 | return matchNoIgnore(file) || defaultMatchNoIgnore(file);
|
188 | };
|
189 |
|
190 | function normalizeFileForMatching(cwd, file) {
|
191 | if (process.platform === 'win32') {
|
192 | cwd = slash(cwd);
|
193 | file = slash(file);
|
194 | }
|
195 |
|
196 | if (!cwd) {
|
197 | return file;
|
198 | }
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | if (!file.startsWith(cwd)) {
|
204 | return file;
|
205 | }
|
206 |
|
207 |
|
208 | return file.slice(cwd.length + 1);
|
209 | }
|
210 |
|
211 | exports.normalizeFileForMatching = normalizeFileForMatching;
|
212 |
|
213 | function isHelperish(file) {
|
214 |
|
215 | if (path.basename(file).startsWith('_')) {
|
216 | return true;
|
217 | }
|
218 |
|
219 |
|
220 |
|
221 |
|
222 | if (path.isAbsolute(file)) {
|
223 | return false;
|
224 | }
|
225 |
|
226 |
|
227 |
|
228 | return path.dirname(file).split('/').some(dir => /^_(?:$|[^_])/.test(dir));
|
229 | }
|
230 |
|
231 | exports.isHelperish = isHelperish;
|
232 |
|
233 | function classify(file, {cwd, extensions, filePatterns, ignoredByWatcherPatterns}) {
|
234 | file = normalizeFileForMatching(cwd, file);
|
235 | return {
|
236 | isIgnoredByWatcher: matchesIgnorePatterns(file, ignoredByWatcherPatterns),
|
237 | isTest: hasExtension(extensions, file) && !isHelperish(file) && filePatterns.length > 0 && matches(file, filePatterns)
|
238 | };
|
239 | }
|
240 |
|
241 | exports.classify = classify;
|
242 |
|
243 | function applyTestFileFilter({cwd, filter, testFiles}) {
|
244 | return testFiles.filter(file => matches(normalizeFileForMatching(cwd, file), filter));
|
245 | }
|
246 |
|
247 | exports.applyTestFileFilter = applyTestFileFilter;
|