1 | 'use strict';
|
2 | const path = require('path');
|
3 | const globby = require('globby');
|
4 | const ignoreByDefault = require('ignore-by-default');
|
5 | const micromatch = require('micromatch');
|
6 | const slash = require('slash');
|
7 |
|
8 | const defaultIgnorePatterns = [...ignoreByDefault.directories(), '**/node_modules'];
|
9 |
|
10 | const buildExtensionPattern = extensions => extensions.length === 1 ? extensions[0] : `{${extensions.join(',')}}`;
|
11 |
|
12 | const normalizePatterns = patterns => {
|
13 |
|
14 | if (process.platform === 'win32') {
|
15 | patterns = patterns.map(pattern => slash(pattern));
|
16 | }
|
17 |
|
18 | return patterns.map(pattern => {
|
19 | if (pattern.startsWith('./')) {
|
20 | return pattern.slice(2);
|
21 | }
|
22 |
|
23 | if (pattern.startsWith('!./')) {
|
24 | return `!${pattern.slice(3)}`;
|
25 | }
|
26 |
|
27 | return pattern;
|
28 | });
|
29 | };
|
30 |
|
31 | function normalizeGlobs(testPatterns, helperPatterns, sourcePatterns, extensions) {
|
32 | if (typeof testPatterns !== 'undefined' && (!Array.isArray(testPatterns) || testPatterns.length === 0)) {
|
33 | throw new Error('The \'files\' configuration must be an array containing glob patterns.');
|
34 | }
|
35 |
|
36 | if (typeof helperPatterns !== 'undefined' && (!Array.isArray(helperPatterns) || helperPatterns.length === 0)) {
|
37 | throw new Error('The \'helpers\' configuration must be an array containing glob patterns.');
|
38 | }
|
39 |
|
40 | if (sourcePatterns && (!Array.isArray(sourcePatterns) || sourcePatterns.length === 0)) {
|
41 | throw new Error('The \'sources\' configuration must be an array containing glob patterns.');
|
42 | }
|
43 |
|
44 | const extensionPattern = buildExtensionPattern(extensions);
|
45 | const defaultTestPatterns = [
|
46 | `**/__tests__/**/*.${extensionPattern}`,
|
47 | `**/*.spec.${extensionPattern}`,
|
48 | `**/*.test.${extensionPattern}`,
|
49 | `**/test-*.${extensionPattern}`,
|
50 | `**/test.${extensionPattern}`,
|
51 | `**/test/**/*.${extensionPattern}`,
|
52 | `**/tests/**/*.${extensionPattern}`
|
53 | ];
|
54 |
|
55 | if (testPatterns) {
|
56 | testPatterns = normalizePatterns(testPatterns);
|
57 |
|
58 | if (testPatterns.every(pattern => pattern.startsWith('!'))) {
|
59 |
|
60 | testPatterns = [...defaultTestPatterns, ...testPatterns];
|
61 | }
|
62 | } else {
|
63 | testPatterns = defaultTestPatterns;
|
64 | }
|
65 |
|
66 | if (helperPatterns) {
|
67 | helperPatterns = normalizePatterns(helperPatterns);
|
68 | } else {
|
69 | helperPatterns = [];
|
70 | }
|
71 |
|
72 | const defaultSourcePatterns = [
|
73 | '**/*.snap',
|
74 | 'ava.config.js',
|
75 | 'package.json',
|
76 | `**/*.${extensionPattern}`
|
77 | ];
|
78 | if (sourcePatterns) {
|
79 | sourcePatterns = normalizePatterns(sourcePatterns);
|
80 |
|
81 | if (sourcePatterns.every(pattern => pattern.startsWith('!'))) {
|
82 |
|
83 | sourcePatterns = [...defaultSourcePatterns, ...sourcePatterns];
|
84 | }
|
85 | } else {
|
86 | sourcePatterns = defaultSourcePatterns;
|
87 | }
|
88 |
|
89 | return {extensions, testPatterns, helperPatterns, sourcePatterns};
|
90 | }
|
91 |
|
92 | exports.normalizeGlobs = normalizeGlobs;
|
93 |
|
94 | const hasExtension = (extensions, file) => extensions.includes(path.extname(file).slice(1));
|
95 |
|
96 | exports.hasExtension = hasExtension;
|
97 |
|
98 | const findFiles = async (cwd, patterns) => {
|
99 | const files = await globby(patterns, {
|
100 | absolute: true,
|
101 | brace: true,
|
102 | case: false,
|
103 | cwd,
|
104 | dot: false,
|
105 | expandDirectories: false,
|
106 | extglob: true,
|
107 | followSymlinkedDirectories: true,
|
108 | gitignore: false,
|
109 | globstar: true,
|
110 | ignore: defaultIgnorePatterns,
|
111 | matchBase: false,
|
112 | onlyFiles: true,
|
113 | stats: false,
|
114 | unique: true
|
115 | });
|
116 |
|
117 |
|
118 |
|
119 | if (process.platform === 'win32') {
|
120 | return files.map(file => path.normalize(file));
|
121 | }
|
122 |
|
123 | return files;
|
124 | };
|
125 |
|
126 | async function findHelpersAndTests({cwd, extensions, testPatterns, helperPatterns}) {
|
127 |
|
128 | const findingTests = findFiles(cwd, testPatterns);
|
129 |
|
130 | const uniqueHelpers = new Set();
|
131 | if (helperPatterns.length > 0) {
|
132 | for (const file of await findFiles(cwd, helperPatterns)) {
|
133 | if (!hasExtension(extensions, file)) {
|
134 | continue;
|
135 | }
|
136 |
|
137 | uniqueHelpers.add(file);
|
138 | }
|
139 | }
|
140 |
|
141 | const tests = [];
|
142 | for (const file of await findingTests) {
|
143 | if (!hasExtension(extensions, file)) {
|
144 | continue;
|
145 | }
|
146 |
|
147 | if (path.basename(file).startsWith('_')) {
|
148 | uniqueHelpers.add(file);
|
149 | } else if (!uniqueHelpers.has(file)) {
|
150 | tests.push(file);
|
151 | }
|
152 | }
|
153 |
|
154 | return {helpers: [...uniqueHelpers], tests};
|
155 | }
|
156 |
|
157 | exports.findHelpersAndTests = findHelpersAndTests;
|
158 |
|
159 | async function findTests({cwd, extensions, testPatterns, helperPatterns}) {
|
160 | const rejectHelpers = helperPatterns.length > 0;
|
161 |
|
162 | const tests = [];
|
163 | for (const file of await findFiles(cwd, testPatterns)) {
|
164 | if (!hasExtension(extensions, file) || path.basename(file).startsWith('_')) {
|
165 | continue;
|
166 | }
|
167 |
|
168 | if (rejectHelpers && matches(normalizeFileForMatching(cwd, file), helperPatterns)) {
|
169 | continue;
|
170 | }
|
171 |
|
172 | tests.push(file);
|
173 | }
|
174 |
|
175 | return {tests};
|
176 | }
|
177 |
|
178 | exports.findTests = findTests;
|
179 |
|
180 | function getChokidarPatterns({sourcePatterns, testPatterns}) {
|
181 | const paths = [];
|
182 | const ignored = defaultIgnorePatterns.map(pattern => `${pattern}/**/*`);
|
183 |
|
184 | for (const pattern of [...sourcePatterns, ...testPatterns]) {
|
185 | if (!pattern.startsWith('!')) {
|
186 | paths.push(pattern);
|
187 | }
|
188 | }
|
189 |
|
190 | return {paths, ignored};
|
191 | }
|
192 |
|
193 | exports.getChokidarPatterns = getChokidarPatterns;
|
194 |
|
195 | const matchingCache = new WeakMap();
|
196 | const processMatchingPatterns = input => {
|
197 | let result = matchingCache.get(input);
|
198 | if (!result) {
|
199 | const ignore = [
|
200 | ...defaultIgnorePatterns,
|
201 |
|
202 | ...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`)
|
203 | ];
|
204 | const patterns = input.filter(pattern => {
|
205 | if (pattern.startsWith('!')) {
|
206 |
|
207 | ignore.push(pattern.slice(1), `${pattern.slice(1)}/**/*`);
|
208 | return false;
|
209 | }
|
210 |
|
211 | return true;
|
212 | });
|
213 |
|
214 | result = {patterns, ignore};
|
215 | matchingCache.set(input, result);
|
216 | }
|
217 |
|
218 | return result;
|
219 | };
|
220 |
|
221 | const matches = (file, patterns) => {
|
222 | let ignore;
|
223 | ({patterns, ignore} = processMatchingPatterns(patterns));
|
224 | return micromatch.some(file, patterns, {ignore});
|
225 | };
|
226 |
|
227 | const NOT_IGNORED = ['**/*'];
|
228 |
|
229 | const normalizeFileForMatching = (cwd, file) => {
|
230 | if (process.platform === 'win32') {
|
231 | cwd = slash(cwd);
|
232 | file = slash(file);
|
233 | }
|
234 |
|
235 | if (!cwd) {
|
236 | return file;
|
237 | }
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | if (!file.startsWith(cwd)) {
|
243 | return file;
|
244 | }
|
245 |
|
246 |
|
247 | return file.slice(cwd.length + 1);
|
248 | };
|
249 |
|
250 | function classify(file, {cwd, extensions, helperPatterns, testPatterns, sourcePatterns}) {
|
251 | let isHelper = false;
|
252 | let isTest = false;
|
253 | let isSource = false;
|
254 |
|
255 | file = normalizeFileForMatching(cwd, file);
|
256 |
|
257 | if (hasExtension(extensions, file)) {
|
258 | if (path.basename(file).startsWith('_')) {
|
259 | isHelper = matches(file, NOT_IGNORED);
|
260 | } else {
|
261 | isHelper = helperPatterns.length > 0 && matches(file, helperPatterns);
|
262 |
|
263 | if (!isHelper) {
|
264 | isTest = testPatterns.length > 0 && matches(file, testPatterns);
|
265 |
|
266 | if (!isTest) {
|
267 |
|
268 |
|
269 | isSource = matches(file, sourcePatterns);
|
270 | }
|
271 | }
|
272 | }
|
273 | } else {
|
274 | isSource = matches(file, sourcePatterns);
|
275 | }
|
276 |
|
277 | return {isHelper, isTest, isSource};
|
278 | }
|
279 |
|
280 | exports.classify = classify;
|