codekingpro/portable-devtools
114k
1import { expand } from 'brace-expansion';
2import { assertValidPattern } from './assert-valid-pattern.js';
3import { AST } from './ast.js';
4import { escape } from './escape.js';
5import { unescape } from './unescape.js';
6export const minimatch = (p, pattern, options = {}) => {
7 assertValidPattern(pattern);
8 // shortcut: comments match nothing.
9 if (!options.nocomment && pattern.charAt(0) === '#') {
10 return false;
11 }
12 return new Minimatch(pattern, options).match(p);
13};
14// Optimized checking for the most common glob patterns.
15const starDotExtRE = /^\*+([^+@!?\*\[\(]*)$/;
16const starDotExtTest = (ext) => (f) => !f.startsWith('.') && f.endsWith(ext);
17const starDotExtTestDot = (ext) => (f) => f.endsWith(ext);
18const starDotExtTestNocase = (ext) => {
19 ext = ext.toLowerCase();
20 return (f) => !f.startsWith('.') && f.toLowerCase().endsWith(ext);
21};
22const starDotExtTestNocaseDot = (ext) => {
23 ext = ext.toLowerCase();
24 return (f) => f.toLowerCase().endsWith(ext);
25};
26const starDotStarRE = /^\*+\.\*+$/;
27const starDotStarTest = (f) => !f.startsWith('.') && f.includes('.');
28const starDotStarTestDot = (f) => f !== '.' && f !== '..' && f.includes('.');
29const dotStarRE = /^\.\*+$/;
30const dotStarTest = (f) => f !== '.' && f !== '..' && f.startsWith('.');
31const starRE = /^\*+$/;
32const starTest = (f) => f.length !== 0 && !f.startsWith('.');
33const starTestDot = (f) => f.length !== 0 && f !== '.' && f !== '..';
34const qmarksRE = /^\?+([^+@!?\*\[\(]*)?$/;
35const qmarksTestNocase = ([$0, ext = '']) => {
36 const noext = qmarksTestNoExt([$0]);
37 if (!ext)
38 return noext;
39 ext = ext.toLowerCase();
40 return (f) => noext(f) && f.toLowerCase().endsWith(ext);
41};
42const qmarksTestNocaseDot = ([$0, ext = '']) => {
43 const noext = qmarksTestNoExtDot([$0]);
44 if (!ext)
45 return noext;
46 ext = ext.toLowerCase();
47 return (f) => noext(f) && f.toLowerCase().endsWith(ext);
48};
49const qmarksTestDot = ([$0, ext = '']) => {
50 const noext = qmarksTestNoExtDot([$0]);
51 return !ext ? noext : (f) => noext(f) && f.endsWith(ext);
52};
53const qmarksTest = ([$0, ext = '']) => {
54 const noext = qmarksTestNoExt([$0]);
55 return !ext ? noext : (f) => noext(f) && f.endsWith(ext);
56};
57const qmarksTestNoExt = ([$0]) => {
58 const len = $0.length;
59 return (f) => f.length === len && !f.startsWith('.');
60};
61const qmarksTestNoExtDot = ([$0]) => {
62 const len = $0.length;
63 return (f) => f.length === len && f !== '.' && f !== '..';
64};
65/* c8 ignore start */
66const defaultPlatform = (typeof process === 'object' && process ?
67 (typeof process.env === 'object' &&
68 process.env &&
69 process.env.__MINIMATCH_TESTING_PLATFORM__) ||
70 process.platform
71 : 'posix');
72const path = {
73 win32: { sep: '\\' },
74 posix: { sep: '/' },
75};
76/* c8 ignore stop */
77export const sep = defaultPlatform === 'win32' ? path.win32.sep : path.posix.sep;
78minimatch.sep = sep;
79export const GLOBSTAR = Symbol('globstar **');
80minimatch.GLOBSTAR = GLOBSTAR;
81// any single thing other than /
82// don't need to escape / when using new RegExp()
83const qmark = '[^/]';
84// * => any number of characters
85const star = qmark + '*?';
86// ** when dots are allowed. Anything goes, except .. and .
87// not (^ or / followed by one or two dots followed by $ or /),
88// followed by anything, any number of times.
89const twoStarDot = '(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?';
90// not a ^ or / followed by a dot,
91// followed by anything, any number of times.
92const twoStarNoDot = '(?:(?!(?:\\/|^)\\.).)*?';
93export const filter = (pattern, options = {}) => (p) => minimatch(p, pattern, options);
94minimatch.filter = filter;
95const ext = (a, b = {}) => Object.assign({}, a, b);
96export const defaults = (def) => {
97 if (!def || typeof def !== 'object' || !Object.keys(def).length) {
98 return minimatch;
99 }
100 const orig = minimatch;
101 const m = (p, pattern, options = {}) => orig(p, pattern, ext(def, options));
102 return Object.assign(m, {
103 Minimatch: class Minimatch extends orig.Minimatch {
104 constructor(pattern, options = {}) {
105 super(pattern, ext(def, options));
106 }
107 static defaults(options) {
108 return orig.defaults(ext(def, options)).Minimatch;
109 }
110 },
111 AST: class AST extends orig.AST {
112 /* c8 ignore start */
113 constructor(type, parent, options = {}) {
114 super(type, parent, ext(def, options));
115 }
116 /* c8 ignore stop */
117 static fromGlob(pattern, options = {}) {
118 return orig.AST.fromGlob(pattern, ext(def, options));
119 }
120 },
121 unescape: (s, options = {}) => orig.unescape(s, ext(def, options)),
122 escape: (s, options = {}) => orig.escape(s, ext(def, options)),
123 filter: (pattern, options = {}) => orig.filter(pattern, ext(def, options)),
124 defaults: (options) => orig.defaults(ext(def, options)),
125 makeRe: (pattern, options = {}) => orig.makeRe(pattern, ext(def, options)),
126 braceExpand: (pattern, options = {}) => orig.braceExpand(pattern, ext(def, options)),
127 match: (list, pattern, options = {}) => orig.match(list, pattern, ext(def, options)),
128 sep: orig.sep,
129 GLOBSTAR: GLOBSTAR,
130 });
131};
132minimatch.defaults = defaults;
133// Brace expansion:
134// a{b,c}d -> abd acd
135// a{b,}c -> abc ac
136// a{0..3}d -> a0d a1d a2d a3d
137// a{b,c{d,e}f}g -> abg acdfg acefg
138// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg
139//
140// Invalid sets are not expanded.
141// a{2..}b -> a{2..}b
142// a{b}c -> a{b}c
143export const braceExpand = (pattern, options = {}) => {
144 assertValidPattern(pattern);
145 // Thanks to Yeting Li <https://github.com/yetingli> for
146 // improving this regexp to avoid a ReDOS vulnerability.
147 if (options.nobrace || !/\{(?:(?!\{).)*\}/.test(pattern)) {
148 // shortcut. no need to expand.
149 return [pattern];
150 }
151 return expand(pattern, { max: options.braceExpandMax });
152};
153minimatch.braceExpand = braceExpand;
154// parse a component of the expanded set.
155// At this point, no pattern may contain "/" in it
156// so we're going to return a 2d array, where each entry is the full
157// pattern, split on '/', and then turned into a regular expression.
158// A regexp is made at the end which joins each array with an
159// escaped /, and another full one which joins each regexp with |.
160//
161// Following the lead of Bash 4.1, note that "**" only has special meaning
162// when it is the *only* thing in a path portion. Otherwise, any series
163// of * is equivalent to a single *. Globstar behavior is enabled by
164// default, and can be disabled by setting options.noglobstar.
165export const makeRe = (pattern, options = {}) => new Minimatch(pattern, options).makeRe();
166minimatch.makeRe = makeRe;
167export const match = (list, pattern, options = {}) => {
168 const mm = new Minimatch(pattern, options);
169 list = list.filter(f => mm.match(f));
170 if (mm.options.nonull && !list.length) {
171 list.push(pattern);
172 }
173 return list;
174};
175minimatch.match = match;
176// replace stuff like \* with *
177const globMagic = /[?*]|[+@!]\(.*?\)|\[|\]/;
178const regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
179export class Minimatch {
180 options;
181 set;
182 pattern;
183 windowsPathsNoEscape;
184 nonegate;
185 negate;
186 comment;
187 empty;
188 preserveMultipleSlashes;
189 partial;
190 globSet;
191 globParts;
192 nocase;
193 isWindows;
194 platform;
195 windowsNoMagicRoot;
196 maxGlobstarRecursion;
197 regexp;
198 constructor(pattern, options = {}) {
199 assertValidPattern(pattern);
200 options = options || {};
201 this.options = options;
202 this.maxGlobstarRecursion = options.maxGlobstarRecursion ?? 200;
203 this.pattern = pattern;
204 this.platform = options.platform || defaultPlatform;
205 this.isWindows = this.platform === 'win32';
206 // avoid the annoying deprecation flag lol
207 const awe = ('allowWindow' + 'sEscape');
208 this.windowsPathsNoEscape =
209 !!options.windowsPathsNoEscape || options[awe] === false;
210 if (this.windowsPathsNoEscape) {
211 this.pattern = this.pattern.replace(/\\/g, '/');
212 }
213 this.preserveMultipleSlashes = !!options.preserveMultipleSlashes;
214 this.regexp = null;
215 this.negate = false;
216 this.nonegate = !!options.nonegate;
217 this.comment = false;
218 this.empty = false;
219 this.partial = !!options.partial;
220 this.nocase = !!this.options.nocase;
221 this.windowsNoMagicRoot =
222 options.windowsNoMagicRoot !== undefined ?
223 options.windowsNoMagicRoot
224 : !!(this.isWindows && this.nocase);
225 this.globSet = [];
226 this.globParts = [];
227 this.set = [];
228 // make the set of regexps etc.
229 this.make();
230 }
231 hasMagic() {
232 if (this.options.magicalBraces && this.set.length > 1) {
233 return true;
234 }
235 for (const pattern of this.set) {
236 for (const part of pattern) {
237 if (typeof part !== 'string')
238 return true;
239 }
240 }
241 return false;
242 }
243 debug(..._) { }
244 make() {
245 const pattern = this.pattern;
246 const options = this.options;
247 // empty patterns and comments match nothing.
248 if (!options.nocomment && pattern.charAt(0) === '#') {
249 this.comment = true;
250 return;
251 }
252 if (!pattern) {
253 this.empty = true;
254 return;
255 }
256 // step 1: figure out negation, etc.
257 this.parseNegate();
258 // step 2: expand braces
259 this.globSet = [...new Set(this.braceExpand())];
260 if (options.debug) {
261 this.debug = (...args) => console.error(...args);
262 }
263 this.debug(this.pattern, this.globSet);
264 // step 3: now we have a set, so turn each one into a series of
265 // path-portion matching patterns.
266 // These will be regexps, except in the case of "**", which is
267 // set to the GLOBSTAR object for globstar behavior,
268 // and will not contain any / characters
269 //
270 // First, we preprocess to make the glob pattern sets a bit simpler
271 // and deduped. There are some perf-killing patterns that can cause
272 // problems with a glob walk, but we can simplify them down a bit.
273 const rawGlobParts = this.globSet.map(s => this.slashSplit(s));
274 this.globParts = this.preprocess(rawGlobParts);
275 this.debug(this.pattern, this.globParts);
276 // glob --> regexps
277 let set = this.globParts.map((s, _, __) => {
278 if (this.isWindows && this.windowsNoMagicRoot) {
279 // check if it's a drive or unc path.
280 const isUNC = s[0] === '' &&
281 s[1] === '' &&
282 (s[2] === '?' || !globMagic.test(s[2])) &&
283 !globMagic.test(s[3]);
284 const isDrive = /^[a-z]:/i.test(s[0]);
285 if (isUNC) {
286 return [
287 ...s.slice(0, 4),
288 ...s.slice(4).map(ss => this.parse(ss)),
289 ];
290 }
291 else if (isDrive) {
292 return [s[0], ...s.slice(1).map(ss => this.parse(ss))];
293 }
294 }
295 return s.map(ss => this.parse(ss));
296 });
297 this.debug(this.pattern, set);
298 // filter out everything that didn't compile properly.
299 this.set = set.filter(s => s.indexOf(false) === -1);
300 // do not treat the ? in UNC paths as magic
301 if (this.isWindows) {
302 for (let i = 0; i < this.set.length; i++) {
303 const p = this.set[i];
304 if (p[0] === '' &&
305 p[1] === '' &&
306 this.globParts[i][2] === '?' &&
307 typeof p[3] === 'string' &&
308 /^[a-z]:$/i.test(p[3])) {
309 p[2] = '?';
310 }
311 }
312 }
313 this.debug(this.pattern, this.set);
314 }
315 // various transforms to equivalent pattern sets that are
316 // faster to process in a filesystem walk. The goal is to
317 // eliminate what we can, and push all ** patterns as far
318 // to the right as possible, even if it increases the number
319 // of patterns that we have to process.
320 preprocess(globParts) {
321 // if we're not in globstar mode, then turn ** into *
322 if (this.options.noglobstar) {
323 for (let i = 0; i < globParts.length; i++) {
324 for (let j = 0; j < globParts[i].length; j++) {
325 if (globParts[i][j] === '**') {
326 globParts[i][j] = '*';
327 }
328 }
329 }
330 }
331 const { optimizationLevel = 1 } = this.options;
332 if (optimizationLevel >= 2) {
333 // aggressive optimization for the purpose of fs walking
334 globParts = this.firstPhasePreProcess(globParts);
335 globParts = this.secondPhasePreProcess(globParts);
336 }
337 else if (optimizationLevel >= 1) {
338 // just basic optimizations to remove some .. parts
339 globParts = this.levelOneOptimize(globParts);
340 }
341 else {
342 // just collapse multiple ** portions into one
343 globParts = this.adjascentGlobstarOptimize(globParts);
344 }
345 return globParts;
346 }
347 // just get rid of adjascent ** portions
348 adjascentGlobstarOptimize(globParts) {
349 return globParts.map(parts => {
350 let gs = -1;
351 while (-1 !== (gs = parts.indexOf('**', gs + 1))) {
352 let i = gs;
353 while (parts[i + 1] === '**') {
354 i++;
355 }
356 if (i !== gs) {
357 parts.splice(gs, i - gs);
358 }
359 }
360 return parts;
361 });
362 }
363 // get rid of adjascent ** and resolve .. portions
364 levelOneOptimize(globParts) {
365 return globParts.map(parts => {
366 parts = parts.reduce((set, part) => {
367 const prev = set[set.length - 1];
368 if (part === '**' && prev === '**') {
369 return set;
370 }
371 if (part === '..') {
372 if (prev && prev !== '..' && prev !== '.' && prev !== '**') {
373 set.pop();
374 return set;
375 }
376 }
377 set.push(part);
378 return set;
379 }, []);
380 return parts.length === 0 ? [''] : parts;
381 });
382 }
383 levelTwoFileOptimize(parts) {
384 if (!Array.isArray(parts)) {
385 parts = this.slashSplit(parts);
386 }
387 let didSomething = false;
388 do {
389 didSomething = false;
390 // <pre>/<e>/<rest> -> <pre>/<rest>
391 if (!this.preserveMultipleSlashes) {
392 for (let i = 1; i < parts.length - 1; i++) {
393 const p = parts[i];
394 // don't squeeze out UNC patterns
395 if (i === 1 && p === '' && parts[0] === '')
396 continue;
397 if (p === '.' || p === '') {
398 didSomething = true;
399 parts.splice(i, 1);
400 i--;
401 }
402 }
403 if (parts[0] === '.' &&
404 parts.length === 2 &&
405 (parts[1] === '.' || parts[1] === '')) {
406 didSomething = true;
407 parts.pop();
408 }
409 }
410 // <pre>/<p>/../<rest> -> <pre>/<rest>
411 let dd = 0;
412 while (-1 !== (dd = parts.indexOf('..', dd + 1))) {
413 const p = parts[dd - 1];
414 if (p && p !== '.' && p !== '..' && p !== '**') {
415 didSomething = true;
416 parts.splice(dd - 1, 2);
417 dd -= 2;
418 }
419 }
420 } while (didSomething);
421 return parts.length === 0 ? [''] : parts;
422 }
423 // First phase: single-pattern processing
424 // <pre> is 1 or more portions
425 // <rest> is 1 or more portions
426 // <p> is any portion other than ., .., '', or **
427 // <e> is . or ''
428 //
429 // **/.. is *brutal* for filesystem walking performance, because
430 // it effectively resets the recursive walk each time it occurs,
431 // and ** cannot be reduced out by a .. pattern part like a regexp
432 // or most strings (other than .., ., and '') can be.
433 //
434 // <pre>/**/../<p>/<p>/<rest> -> {<pre>/../<p>/<p>/<rest>,<pre>/**/<p>/<p>/<rest>}
435 // <pre>/<e>/<rest> -> <pre>/<rest>
436 // <pre>/<p>/../<rest> -> <pre>/<rest>
437 // **/**/<rest> -> **/<rest>
438 //
439 // **/*/<rest> -> */**/<rest> <== not valid because ** doesn't follow
440 // this WOULD be allowed if ** did follow symlinks, or * didn't
441 firstPhasePreProcess(globParts) {
442 let didSomething = false;
443 do {
444 didSomething = false;
445 // <pre>/**/../<p>/<p>/<rest> -> {<pre>/../<p>/<p>/<rest>,<pre>/**/<p>/<p>/<rest>}
446 for (let parts of globParts) {
447 let gs = -1;
448 while (-1 !== (gs = parts.indexOf('**', gs + 1))) {
449 let gss = gs;
450 while (parts[gss + 1] === '**') {
451 // <pre>/**/**/<rest> -> <pre>/**/<rest>
452 gss++;
453 }
454 // eg, if gs is 2 and gss is 4, that means we have 3 **
455 // parts, and can remove 2 of them.
456 if (gss > gs) {
457 parts.splice(gs + 1, gss - gs);
458 }
459 let next = parts[gs + 1];
460 const p = parts[gs + 2];
461 const p2 = parts[gs + 3];
462 if (next !== '..')
463 continue;
464 if (!p ||
465 p === '.' ||
466 p === '..' ||
467 !p2 ||
468 p2 === '.' ||
469 p2 === '..') {
470 continue;
471 }
472 didSomething = true;
473 // edit parts in place, and push the new one
474 parts.splice(gs, 1);
475 const other = parts.slice(0);
476 other[gs] = '**';
477 globParts.push(other);
478 gs--;
479 }
480 // <pre>/<e>/<rest> -> <pre>/<rest>
481 if (!this.preserveMultipleSlashes) {
482 for (let i = 1; i < parts.length - 1; i++) {
483 const p = parts[i];
484 // don't squeeze out UNC patterns
485 if (i === 1 && p === '' && parts[0] === '')
486 continue;
487 if (p === '.' || p === '') {
488 didSomething = true;
489 parts.splice(i, 1);
490 i--;
491 }
492 }
493 if (parts[0] === '.' &&
494 parts.length === 2 &&
495 (parts[1] === '.' || parts[1] === '')) {
496 didSomething = true;
497 parts.pop();
498 }
499 }
500 // <pre>/<p>/../<rest> -> <pre>/<rest>
501 let dd = 0;
502 while (-1 !== (dd = parts.indexOf('..', dd + 1))) {
503 const p = parts[dd - 1];
504 if (p && p !== '.' && p !== '..' && p !== '**') {
505 didSomething = true;
506 const needDot = dd === 1 && parts[dd + 1] === '**';
507 const splin = needDot ? ['.'] : [];
508 parts.splice(dd - 1, 2, ...splin);
509 if (parts.length === 0)
510 parts.push('');
511 dd -= 2;
512 }
513 }
514 }
515 } while (didSomething);
516 return globParts;
517 }
518 // second phase: multi-pattern dedupes
519 // {<pre>/*/<rest>,<pre>/<p>/<rest>} -> <pre>/*/<rest>
520 // {<pre>/<rest>,<pre>/<rest>} -> <pre>/<rest>
521 // {<pre>/**/<rest>,<pre>/<rest>} -> <pre>/**/<rest>
522 //
523 // {<pre>/**/<rest>,<pre>/**/<p>/<rest>} -> <pre>/**/<rest>
524 // ^-- not valid because ** doens't follow symlinks
525 secondPhasePreProcess(globParts) {
526 for (let i = 0; i < globParts.length - 1; i++) {
527 for (let j = i + 1; j < globParts.length; j++) {
528 const matched = this.partsMatch(globParts[i], globParts[j], !this.preserveMultipleSlashes);
529 if (matched) {
530 globParts[i] = [];
531 globParts[j] = matched;
532 break;
533 }
534 }
535 }
536 return globParts.filter(gs => gs.length);
537 }
538 partsMatch(a, b, emptyGSMatch = false) {
539 let ai = 0;
540 let bi = 0;
541 let result = [];
542 let which = '';
543 while (ai < a.length && bi < b.length) {
544 if (a[ai] === b[bi]) {
545 result.push(which === 'b' ? b[bi] : a[ai]);
546 ai++;
547 bi++;
548 }
549 else if (emptyGSMatch && a[ai] === '**' && b[bi] === a[ai + 1]) {
550 result.push(a[ai]);
551 ai++;
552 }
553 else if (emptyGSMatch && b[bi] === '**' && a[ai] === b[bi + 1]) {
554 result.push(b[bi]);
555 bi++;
556 }
557 else if (a[ai] === '*' &&
558 b[bi] &&
559 (this.options.dot || !b[bi].startsWith('.')) &&
560 b[bi] !== '**') {
561 if (which === 'b')
562 return false;
563 which = 'a';
564 result.push(a[ai]);
565 ai++;
566 bi++;
567 }
568 else if (b[bi] === '*' &&
569 a[ai] &&
570 (this.options.dot || !a[ai].startsWith('.')) &&
571 a[ai] !== '**') {
572 if (which === 'a')
573 return false;
574 which = 'b';
575 result.push(b[bi]);
576 ai++;
577 bi++;
578 }
579 else {
580 return false;
581 }
582 }
583 // if we fall out of the loop, it means they two are identical
584 // as long as their lengths match
585 return a.length === b.length && result;
586 }
587 parseNegate() {
588 if (this.nonegate)
589 return;
590 const pattern = this.pattern;
591 let negate = false;
592 let negateOffset = 0;
593 for (let i = 0; i < pattern.length && pattern.charAt(i) === '!'; i++) {
594 negate = !negate;
595 negateOffset++;
596 }
597 if (negateOffset)
598 this.pattern = pattern.slice(negateOffset);
599 this.negate = negate;
600 }
601 // set partial to true to test if, for example,
602 // "/a/b" matches the start of "/*/b/*/d"
603 // Partial means, if you run out of file before you run
604 // out of pattern, then that's fine, as long as all
605 // the parts match.
606 matchOne(file, pattern, partial = false) {
607 let fileStartIndex = 0;
608 let patternStartIndex = 0;
609 // UNC paths like //?/X:/... can match X:/... and vice versa
610 // Drive letters in absolute drive or unc paths are always compared
611 // case-insensitively.
612 if (this.isWindows) {
613 const fileDrive = typeof file[0] === 'string' && /^[a-z]:$/i.test(file[0]);
614 const fileUNC = !fileDrive &&
615 file[0] === '' &&
616 file[1] === '' &&
617 file[2] === '?' &&
618 /^[a-z]:$/i.test(file[3]);
619 const patternDrive = typeof pattern[0] === 'string' && /^[a-z]:$/i.test(pattern[0]);
620 const patternUNC = !patternDrive &&
621 pattern[0] === '' &&
622 pattern[1] === '' &&
623 pattern[2] === '?' &&
624 typeof pattern[3] === 'string' &&
625 /^[a-z]:$/i.test(pattern[3]);
626 const fdi = fileUNC ? 3
627 : fileDrive ? 0
628 : undefined;
629 const pdi = patternUNC ? 3
630 : patternDrive ? 0
631 : undefined;
632 if (typeof fdi === 'number' && typeof pdi === 'number') {
633 const [fd, pd] = [
634 file[fdi],
635 pattern[pdi],
636 ];
637 // start matching at the drive letter index of each
638 if (fd.toLowerCase() === pd.toLowerCase()) {
639 pattern[pdi] = fd;
640 patternStartIndex = pdi;
641 fileStartIndex = fdi;
642 }
643 }
644 }
645 // resolve and reduce . and .. portions in the file as well.
646 // don't need to do the second phase, because it's only one string[]
647 const { optimizationLevel = 1 } = this.options;
648 if (optimizationLevel >= 2) {
649 file = this.levelTwoFileOptimize(file);
650 }
651 if (pattern.includes(GLOBSTAR)) {
652 return this.#matchGlobstar(file, pattern, partial, fileStartIndex, patternStartIndex);
653 }
654 return this.#matchOne(file, pattern, partial, fileStartIndex, patternStartIndex);
655 }
656 #matchGlobstar(file, pattern, partial, fileIndex, patternIndex) {
657 // split the pattern into head, tail, and middle of ** delimited parts
658 const firstgs = pattern.indexOf(GLOBSTAR, patternIndex);
659 const lastgs = pattern.lastIndexOf(GLOBSTAR);
660 // split the pattern up into globstar-delimited sections
661 // the tail has to be at the end, and the others just have
662 // to be found in order from the head.
663 const [head, body, tail] = partial ? [
664 pattern.slice(patternIndex, firstgs),
665 pattern.slice(firstgs + 1),
666 [],
667 ] : [
668 pattern.slice(patternIndex, firstgs),
669 pattern.slice(firstgs + 1, lastgs),
670 pattern.slice(lastgs + 1),
671 ];
672 // check the head, from the current file/pattern index.
673 if (head.length) {
674 const fileHead = file.slice(fileIndex, fileIndex + head.length);
675 if (!this.#matchOne(fileHead, head, partial, 0, 0)) {
676 return false;
677 }
678 fileIndex += head.length;
679 patternIndex += head.length;
680 }
681 // now we know the head matches!
682 // if the last portion is not empty, it MUST match the end
683 // check the tail
684 let fileTailMatch = 0;
685 if (tail.length) {
686 // if head + tail > file, then we cannot possibly match
687 if (tail.length + fileIndex > file.length)
688 return false;
689 // try to match the tail
690 let tailStart = file.length - tail.length;
691 if (this.#matchOne(file, tail, partial, tailStart, 0)) {
692 fileTailMatch = tail.length;
693 }
694 else {
695 // affordance for stuff like a/**/* matching a/b/
696 // if the last file portion is '', and there's more to the pattern
697 // then try without the '' bit.
698 if (file[file.length - 1] !== '' ||
699 fileIndex + tail.length === file.length) {
700 return false;
701 }
702 tailStart--;
703 if (!this.#matchOne(file, tail, partial, tailStart, 0)) {
704 return false;
705 }
706 fileTailMatch = tail.length + 1;
707 }
708 }
709 // now we know the tail matches!
710 // the middle is zero or more portions wrapped in **, possibly
711 // containing more ** sections.
712 // so a/**/b/**/c/**/d has become **/b/**/c/**
713 // if it's empty, it means a/**/b, just verify we have no bad dots
714 // if there's no tail, so it ends on /**, then we must have *something*
715 // after the head, or it's not a matc
716 if (!body.length) {
717 let sawSome = !!fileTailMatch;
718 for (let i = fileIndex; i < file.length - fileTailMatch; i++) {
719 const f = String(file[i]);
720 sawSome = true;
721 if (f === '.' ||
722 f === '..' ||
723 (!this.options.dot && f.startsWith('.'))) {
724 return false;
725 }
726 }
727 // in partial mode, we just need to get past all file parts
728 return partial || sawSome;
729 }
730 // now we know that there's one or more body sections, which can
731 // be matched anywhere from the 0 index (because the head was pruned)
732 // through to the length-fileTailMatch index.
733 // split the body up into sections, and note the minimum index it can
734 // be found at (start with the length of all previous segments)
735 // [section, before, after]
736 const bodySegments = [[[], 0]];
737 let currentBody = bodySegments[0];
738 let nonGsParts = 0;
739 const nonGsPartsSums = [0];
740 for (const b of body) {
741 if (b === GLOBSTAR) {
742 nonGsPartsSums.push(nonGsParts);
743 currentBody = [[], 0];
744 bodySegments.push(currentBody);
745 }
746 else {
747 currentBody[0].push(b);
748 nonGsParts++;
749 }
750 }
751 let i = bodySegments.length - 1;
752 const fileLength = file.length - fileTailMatch;
753 for (const b of bodySegments) {
754 b[1] = fileLength - (nonGsPartsSums[i--] + b[0].length);
755 }
756 return !!this.#matchGlobStarBodySections(file, bodySegments, fileIndex, 0, partial, 0, !!fileTailMatch);
757 }
758 // return false for "nope, not matching"
759 // return null for "not matching, cannot keep trying"
760 #matchGlobStarBodySections(file,
761 // pattern section, last possible position for it
762 bodySegments, fileIndex, bodyIndex, partial, globStarDepth, sawTail) {
763 // take the first body segment, and walk from fileIndex to its "after"
764 // value at the end
765 // If it doesn't match at that position, we increment, until we hit
766 // that final possible position, and give up.
767 // If it does match, then advance and try to rest.
768 // If any of them fail we keep walking forward.
769 // this is still a bit recursively painful, but it's more constrained
770 // than previous implementations, because we never test something that
771 // can't possibly be a valid matching condition.
772 const bs = bodySegments[bodyIndex];
773 if (!bs) {
774 // just make sure that there's no bad dots
775 for (let i = fileIndex; i < file.length; i++) {
776 sawTail = true;
777 const f = file[i];
778 if (f === '.' ||
779 f === '..' ||
780 (!this.options.dot && f.startsWith('.'))) {
781 return false;
782 }
783 }
784 return sawTail;
785 }
786 // have a non-globstar body section to test
787 const [body, after] = bs;
788 while (fileIndex <= after) {
789 const m = this.#matchOne(file.slice(0, fileIndex + body.length), body, partial, fileIndex, 0);
790 // if limit exceeded, no match. intentional false negative,
791 // acceptable break in correctness for security.
792 if (m && globStarDepth < this.maxGlobstarRecursion) {
793 // match! see if the rest match. if so, we're done!
794 const sub = this.#matchGlobStarBodySections(file, bodySegments, fileIndex + body.length, bodyIndex + 1, partial, globStarDepth + 1, sawTail);
795 if (sub !== false) {
796 return sub;
797 }
798 }
799 const f = file[fileIndex];
800 if (f === '.' ||
801 f === '..' ||
802 (!this.options.dot && f.startsWith('.'))) {
803 return false;
804 }
805 fileIndex++;
806 }
807 // walked off. no point continuing
808 return partial || null;
809 }
810 #matchOne(file, pattern, partial, fileIndex, patternIndex) {
811 let fi;
812 let pi;
813 let pl;
814 let fl;
815 for (fi = fileIndex,
816 pi = patternIndex,
817 fl = file.length,
818 pl = pattern.length; fi < fl && pi < pl; fi++, pi++) {
819 this.debug('matchOne loop');
820 let p = pattern[pi];
821 let f = file[fi];
822 this.debug(pattern, p, f);
823 // should be impossible.
824 // some invalid regexp stuff in the set.
825 /* c8 ignore start */
826 if (p === false || p === GLOBSTAR) {
827 return false;
828 }
829 /* c8 ignore stop */
830 // something other than **
831 // non-magic patterns just have to match exactly
832 // patterns with magic have been turned into regexps.
833 let hit;
834 if (typeof p === 'string') {
835 hit = f === p;
836 this.debug('string match', p, f, hit);
837 }
838 else {
839 hit = p.test(f);
840 this.debug('pattern match', p, f, hit);
841 }
842 if (!hit)
843 return false;
844 }
845 // Note: ending in / means that we'll get a final ""
846 // at the end of the pattern. This can only match a
847 // corresponding "" at the end of the file.
848 // If the file ends in /, then it can only match a
849 // a pattern that ends in /, unless the pattern just
850 // doesn't have any more for it. But, a/b/ should *not*
851 // match "a/b/*", even though "" matches against the
852 // [^/]*? pattern, except in partial mode, where it might
853 // simply not be reached yet.
854 // However, a/b/ should still satisfy a/*
855 // now either we fell off the end of the pattern, or we're done.
856 if (fi === fl && pi === pl) {
857 // ran out of pattern and filename at the same time.
858 // an exact hit!
859 return true;
860 }
861 else if (fi === fl) {
862 // ran out of file, but still had pattern left.
863 // this is ok if we're doing the match as part of
864 // a glob fs traversal.
865 return partial;
866 }
867 else if (pi === pl) {
868 // ran out of pattern, still have file left.
869 // this is only acceptable if we're on the very last
870 // empty segment of a file with a trailing slash.
871 // a/* should match a/b/
872 return fi === fl - 1 && file[fi] === '';
873 /* c8 ignore start */
874 }
875 else {
876 // should be unreachable.
877 throw new Error('wtf?');
878 }
879 /* c8 ignore stop */
880 }
881 braceExpand() {
882 return braceExpand(this.pattern, this.options);
883 }
884 parse(pattern) {
885 assertValidPattern(pattern);
886 const options = this.options;
887 // shortcuts
888 if (pattern === '**')
889 return GLOBSTAR;
890 if (pattern === '')
891 return '';
892 // far and away, the most common glob pattern parts are
893 // *, *.*, and *.<ext> Add a fast check method for those.
894 let m;
895 let fastTest = null;
896 if ((m = pattern.match(starRE))) {
897 fastTest = options.dot ? starTestDot : starTest;
898 }
899 else if ((m = pattern.match(starDotExtRE))) {
900 fastTest = (options.nocase ?
901 options.dot ?
902 starDotExtTestNocaseDot
903 : starDotExtTestNocase
904 : options.dot ? starDotExtTestDot
905 : starDotExtTest)(m[1]);
906 }
907 else if ((m = pattern.match(qmarksRE))) {
908 fastTest = (options.nocase ?
909 options.dot ?
910 qmarksTestNocaseDot
911 : qmarksTestNocase
912 : options.dot ? qmarksTestDot
913 : qmarksTest)(m);
914 }
915 else if ((m = pattern.match(starDotStarRE))) {
916 fastTest = options.dot ? starDotStarTestDot : starDotStarTest;
917 }
918 else if ((m = pattern.match(dotStarRE))) {
919 fastTest = dotStarTest;
920 }
921 const re = AST.fromGlob(pattern, this.options).toMMPattern();
922 if (fastTest && typeof re === 'object') {
923 // Avoids overriding in frozen environments
924 Reflect.defineProperty(re, 'test', { value: fastTest });
925 }
926 return re;
927 }
928 makeRe() {
929 if (this.regexp || this.regexp === false)
930 return this.regexp;
931 // at this point, this.set is a 2d array of partial
932 // pattern strings, or "**".
933 //
934 // It's better to use .match(). This function shouldn't
935 // be used, really, but it's pretty convenient sometimes,
936 // when you just want to work with a regex.
937 const set = this.set;
938 if (!set.length) {
939 this.regexp = false;
940 return this.regexp;
941 }
942 const options = this.options;
943 const twoStar = options.noglobstar ? star
944 : options.dot ? twoStarDot
945 : twoStarNoDot;
946 const flags = new Set(options.nocase ? ['i'] : []);
947 // regexpify non-globstar patterns
948 // if ** is only item, then we just do one twoStar
949 // if ** is first, and there are more, prepend (\/|twoStar\/)? to next
950 // if ** is last, append (\/twoStar|) to previous
951 // if ** is in the middle, append (\/|\/twoStar\/) to previous
952 // then filter out GLOBSTAR symbols
953 let re = set
954 .map(pattern => {
955 const pp = pattern.map(p => {
956 if (p instanceof RegExp) {
957 for (const f of p.flags.split(''))
958 flags.add(f);
959 }
960 return (typeof p === 'string' ? regExpEscape(p)
961 : p === GLOBSTAR ? GLOBSTAR
962 : p._src);
963 });
964 pp.forEach((p, i) => {
965 const next = pp[i + 1];
966 const prev = pp[i - 1];
967 if (p !== GLOBSTAR || prev === GLOBSTAR) {
968 return;
969 }
970 if (prev === undefined) {
971 if (next !== undefined && next !== GLOBSTAR) {
972 pp[i + 1] = '(?:\\/|' + twoStar + '\\/)?' + next;
973 }
974 else {
975 pp[i] = twoStar;
976 }
977 }
978 else if (next === undefined) {
979 pp[i - 1] = prev + '(?:\\/|\\/' + twoStar + ')?';
980 }
981 else if (next !== GLOBSTAR) {
982 pp[i - 1] = prev + '(?:\\/|\\/' + twoStar + '\\/)' + next;
983 pp[i + 1] = GLOBSTAR;
984 }
985 });
986 const filtered = pp.filter(p => p !== GLOBSTAR);
987 // For partial matches, we need to make the pattern match
988 // any prefix of the full path. We do this by generating
989 // alternative patterns that match progressively longer prefixes.
990 if (this.partial && filtered.length >= 1) {
991 const prefixes = [];
992 for (let i = 1; i <= filtered.length; i++) {
993 prefixes.push(filtered.slice(0, i).join('/'));
994 }
995 return '(?:' + prefixes.join('|') + ')';
996 }
997 return filtered.join('/');
998 })
999 .join('|');
1000 // need to wrap in parens if we had more than one thing with |,
1001 // otherwise only the first will be anchored to ^ and the last to $
1002 const [open, close] = set.length > 1 ? ['(?:', ')'] : ['', ''];
1003 // must match entire pattern
1004 // ending in a * or ** will make it less strict.
1005 re = '^' + open + re + close + '$';
1006 // In partial mode, '/' should always match as it's a valid prefix for any pattern
1007 if (this.partial) {
1008 re = '^(?:\\/|' + open + re.slice(1, -1) + close + ')$';
1009 }
1010 // can match anything, as long as it's not this.
1011 if (this.negate)
1012 re = '^(?!' + re + ').+$';
1013 try {
1014 this.regexp = new RegExp(re, [...flags].join(''));
1015 /* c8 ignore start */
1016 }
1017 catch (ex) {
1018 // should be impossible
1019 this.regexp = false;
1020 }
1021 /* c8 ignore stop */
1022 return this.regexp;
1023 }
1024 slashSplit(p) {
1025 // if p starts with // on windows, we preserve that
1026 // so that UNC paths aren't broken. Otherwise, any number of
1027 // / characters are coalesced into one, unless
1028 // preserveMultipleSlashes is set to true.
1029 if (this.preserveMultipleSlashes) {
1030 return p.split('/');
1031 }
1032 else if (this.isWindows && /^\/\/[^\/]+/.test(p)) {
1033 // add an extra '' for the one we lose
1034 return ['', ...p.split(/\/+/)];
1035 }
1036 else {
1037 return p.split(/\/+/);
1038 }
1039 }
1040 match(f, partial = this.partial) {
1041 this.debug('match', f, this.pattern);
1042 // short-circuit in the case of busted things.
1043 // comments, etc.
1044 if (this.comment) {
1045 return false;
1046 }
1047 if (this.empty) {
1048 return f === '';
1049 }
1050 if (f === '/' && partial) {
1051 return true;
1052 }
1053 const options = this.options;
1054 // windows: need to use /, not \
1055 if (this.isWindows) {
1056 f = f.split('\\').join('/');
1057 }
1058 // treat the test path as a set of pathparts.
1059 const ff = this.slashSplit(f);
1060 this.debug(this.pattern, 'split', ff);
1061 // just ONE of the pattern sets in this.set needs to match
1062 // in order for it to be valid. If negating, then just one
1063 // match means that we have failed.
1064 // Either way, return on the first hit.
1065 const set = this.set;
1066 this.debug(this.pattern, 'set', set);
1067 // Find the basename of the path by looking for the last non-empty segment
1068 let filename = ff[ff.length - 1];
1069 if (!filename) {
1070 for (let i = ff.length - 2; !filename && i >= 0; i--) {
1071 filename = ff[i];
1072 }
1073 }
1074 for (let i = 0; i < set.length; i++) {
1075 const pattern = set[i];
1076 let file = ff;
1077 if (options.matchBase && pattern.length === 1) {
1078 file = [filename];
1079 }
1080 const hit = this.matchOne(file, pattern, partial);
1081 if (hit) {
1082 if (options.flipNegate) {
1083 return true;
1084 }
1085 return !this.negate;
1086 }
1087 }
1088 // didn't get any hits. this is success if it's a negative
1089 // pattern, failure otherwise.
1090 if (options.flipNegate) {
1091 return false;
1092 }
1093 return this.negate;
1094 }
1095 static defaults(def) {
1096 return minimatch.defaults(def).Minimatch;
1097 }
1098}
1099/* c8 ignore start */
1100export { AST } from './ast.js';
1101export { escape } from './escape.js';
1102export { unescape } from './unescape.js';
1103/* c8 ignore stop */
1104minimatch.AST = AST;
1105minimatch.Minimatch = Minimatch;
1106minimatch.escape = escape;
1107minimatch.unescape = unescape;
1108//# sourceMappingURL=index.js.map