// -> /if (!this.preserveMultipleSlashes) { for (let i = 1; i < parts.length - 1; i++) { const p = parts[i]; // don't squeeze out UNC patterns if (i === 1 && p === '' && parts[0] === '') continue; if (p === '.' || p === '') { didSomething = true; parts.splice(i, 1); i--; } } if (parts[0] === '.' && parts.length === 2 && (parts[1] === '.' || parts[1] === '')) { didSomething = true; parts.pop(); } } // //../
-> /let dd = 0; while (-1 !== (dd = parts.indexOf('..', dd + 1))) { const p = parts[dd - 1]; if (p && p !== '.' && p !== '..' && p !== '**') { didSomething = true; parts.splice(dd - 1, 2); dd -= 2; } } } while (didSomething); return parts.length === 0 ? [''] : parts; } // First phase: single-pattern processing // is 1 or more portions //is 1 or more portions // is any portion other than ., .., '', or ** //
is . or '' // // **/.. is *brutal* for filesystem walking performance, because // it effectively resets the recursive walk each time it occurs, // and ** cannot be reduced out by a .. pattern part like a regexp // or most strings (other than .., ., and '') can be. // // /**/..//
/
-> { /..//
/
, /**//
/
} // // -> /// //../
-> /// **/**/ -> **/ // // **/*/ -> */**/ <== not valid because ** doesn't follow // this WOULD be allowed if ** did follow symlinks, or * didn't firstPhasePreProcess(globParts) { let didSomething = false; do { didSomething = false; // /**/..//
/
-> { /..//
/
, /**//
/
} for (let parts of globParts) { let gs = -1; while (-1 !== (gs = parts.indexOf('**', gs + 1))) { let gss = gs; while (parts[gss + 1] === '**') { // /**/**/-> /**/gss++; } // eg, if gs is 2 and gss is 4, that means we have 3 ** // parts, and can remove 2 of them. if (gss > gs) { parts.splice(gs + 1, gss - gs); } let next = parts[gs + 1]; const p = parts[gs + 2]; const p2 = parts[gs + 3]; if (next !== '..') continue; if (!p || p === '.' || p === '..' || !p2 || p2 === '.' || p2 === '..') { continue; } didSomething = true; // edit parts in place, and push the new one parts.splice(gs, 1); const other = parts.slice(0); other[gs] = '**'; globParts.push(other); gs--; } // // -> /if (!this.preserveMultipleSlashes) { for (let i = 1; i < parts.length - 1; i++) { const p = parts[i]; // don't squeeze out UNC patterns if (i === 1 && p === '' && parts[0] === '') continue; if (p === '.' || p === '') { didSomething = true; parts.splice(i, 1); i--; } } if (parts[0] === '.' && parts.length === 2 && (parts[1] === '.' || parts[1] === '')) { didSomething = true; parts.pop(); } } // //../
-> /let dd = 0; while (-1 !== (dd = parts.indexOf('..', dd + 1))) { const p = parts[dd - 1]; if (p && p !== '.' && p !== '..' && p !== '**') { didSomething = true; const needDot = dd === 1 && parts[dd + 1] === '**'; const splin = needDot ? ['.'] : []; parts.splice(dd - 1, 2, ...splin); if (parts.length === 0) parts.push(''); dd -= 2; } } } } while (didSomething); return globParts; } // second phase: multi-pattern dedupes // { /*/, //
} -> /*/// { /, /} -> /// { /**/, /} -> /**/// // { /**/, /**//
} -> /**/// ^-- not valid because ** doens't follow symlinks secondPhasePreProcess(globParts) { for (let i = 0; i < globParts.length - 1; i++) { for (let j = i + 1; j < globParts.length; j++) { const matched = this.partsMatch(globParts[i], globParts[j], !this.preserveMultipleSlashes); if (matched) { globParts[i] = []; globParts[j] = matched; break; } } } return globParts.filter(gs => gs.length); } partsMatch(a, b, emptyGSMatch = false) { let ai = 0; let bi = 0; let result = []; let which = ''; while (ai < a.length && bi < b.length) { if (a[ai] === b[bi]) { result.push(which === 'b' ? b[bi] : a[ai]); ai++; bi++; } else if (emptyGSMatch && a[ai] === '**' && b[bi] === a[ai + 1]) { result.push(a[ai]); ai++; } else if (emptyGSMatch && b[bi] === '**' && a[ai] === b[bi + 1]) { result.push(b[bi]); bi++; } else if (a[ai] === '*' && b[bi] && (this.options.dot || !b[bi].startsWith('.')) && b[bi] !== '**') { if (which === 'b') return false; which = 'a'; result.push(a[ai]); ai++; bi++; } else if (b[bi] === '*' && a[ai] && (this.options.dot || !a[ai].startsWith('.')) && a[ai] !== '**') { if (which === 'a') return false; which = 'b'; result.push(b[bi]); ai++; bi++; } else { return false; } } // if we fall out of the loop, it means they two are identical // as long as their lengths match return a.length === b.length && result; } parseNegate() { if (this.nonegate) return; const pattern = this.pattern; let negate = false; let negateOffset = 0; for (let i = 0; i < pattern.length && pattern.charAt(i) === '!'; i++) { negate = !negate; negateOffset++; } if (negateOffset) this.pattern = pattern.slice(negateOffset); this.negate = negate; } // set partial to true to test if, for example, // "/a/b" matches the start of "/*/b/*/d" // Partial means, if you run out of file before you run // out of pattern, then that's fine, as long as all // the parts match. matchOne(file, pattern, partial = false) { const options = this.options; // UNC paths like //?/X:/... can match X:/... and vice versa // Drive letters in absolute drive or unc paths are always compared // case-insensitively. if (this.isWindows) { const fileDrive = typeof file[0] === 'string' && /^[a-z]:$/i.test(file[0]); const fileUNC = !fileDrive && file[0] === '' && file[1] === '' && file[2] === '?' && /^[a-z]:$/i.test(file[3]); const patternDrive = typeof pattern[0] === 'string' && /^[a-z]:$/i.test(pattern[0]); const patternUNC = !patternDrive && pattern[0] === '' && pattern[1] === '' && pattern[2] === '?' && typeof pattern[3] === 'string' && /^[a-z]:$/i.test(pattern[3]); const fdi = fileUNC ? 3 : fileDrive ? 0 : undefined; const pdi = patternUNC ? 3 : patternDrive ? 0 : undefined; if (typeof fdi === 'number' && typeof pdi === 'number') { const [fd, pd] = [file[fdi], pattern[pdi]]; if (fd.toLowerCase() === pd.toLowerCase()) { pattern[pdi] = fd; if (pdi > fdi) { pattern = pattern.slice(pdi); } else if (fdi > pdi) { file = file.slice(fdi); } } } } // resolve and reduce . and .. portions in the file as well. // dont' need to do the second phase, because it's only one string[] const { optimizationLevel = 1 } = this.options; if (optimizationLevel >= 2) { file = this.levelTwoFileOptimize(file); } this.debug('matchOne', this, { file, pattern }); this.debug('matchOne', file.length, pattern.length); for (var fi = 0, pi = 0, fl = file.length, pl = pattern.length; fi < fl && pi < pl; fi++, pi++) { this.debug('matchOne loop'); var p = pattern[pi]; var f = file[fi]; this.debug(pattern, p, f); // should be impossible. // some invalid regexp stuff in the set. /* c8 ignore start */ if (p === false) { return false; } /* c8 ignore stop */ if (p === GLOBSTAR) { this.debug('GLOBSTAR', [pattern, p, f]); // "**" // a/**/b/**/c would match the following: // a/b/x/y/z/c // a/x/y/z/b/c // a/b/x/b/x/c // a/b/c // To do this, take the rest of the pattern after // the **, and see if it would match the file remainder. // If so, return success. // If not, the ** "swallows" a segment, and try again. // This is recursively awful. // // a/**/b/**/c matching a/b/x/y/z/c // - a matches a // - doublestar // - matchOne(b/x/y/z/c, b/**/c) // - b matches b // - doublestar // - matchOne(x/y/z/c, c) -> no // - matchOne(y/z/c, c) -> no // - matchOne(z/c, c) -> no // - matchOne(c, c) yes, hit var fr = fi; var pr = pi + 1; if (pr === pl) { this.debug('** at the end'); // a ** at the end will just swallow the rest. // We have found a match. // however, it will not swallow /.x, unless // options.dot is set. // . and .. are *never* matched by **, for explosively // exponential reasons. for (; fi < fl; fi++) { if (file[fi] === '.' || file[fi] === '..' || (!options.dot && file[fi].charAt(0) === '.')) return false; } return true; } // ok, let's see if we can swallow whatever we can. while (fr < fl) { var swallowee = file[fr]; this.debug('\nglobstar while', file, fr, pattern, pr, swallowee); // XXX remove this slice. Just pass the start index. if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { this.debug('globstar found match!', fr, fl, swallowee); // found a match. return true; } else { // can't swallow "." or ".." ever. // can only swallow ".foo" when explicitly asked. if (swallowee === '.' || swallowee === '..' || (!options.dot && swallowee.charAt(0) === '.')) { this.debug('dot detected!', file, fr, pattern, pr); break; } // ** swallows a segment, and continue. this.debug('globstar swallow a segment, and continue'); fr++; } } // no match was found. // However, in partial mode, we can't say this is necessarily over. /* c8 ignore start */ if (partial) { // ran out of file this.debug('\n>>> no match, partial?', file, fr, pattern, pr); if (fr === fl) { return true; } } /* c8 ignore stop */ return false; } // something other than ** // non-magic patterns just have to match exactly // patterns with magic have been turned into regexps. let hit; if (typeof p === 'string') { hit = f === p; this.debug('string match', p, f, hit); } else { hit = p.test(f); this.debug('pattern match', p, f, hit); } if (!hit) return false; } // Note: ending in / means that we'll get a final "" // at the end of the pattern. This can only match a // corresponding "" at the end of the file. // If the file ends in /, then it can only match a // a pattern that ends in /, unless the pattern just // doesn't have any more for it. But, a/b/ should *not* // match "a/b/*", even though "" matches against the // [^/]*? pattern, except in partial mode, where it might // simply not be reached yet. // However, a/b/ should still satisfy a/* // now either we fell off the end of the pattern, or we're done. if (fi === fl && pi === pl) { // ran out of pattern and filename at the same time. // an exact hit! return true; } else if (fi === fl) { // ran out of file, but still had pattern left. // this is ok if we're doing the match as part of // a glob fs traversal. return partial; } else if (pi === pl) { // ran out of pattern, still have file left. // this is only acceptable if we're on the very last // empty segment of a file with a trailing slash. // a/* should match a/b/ return fi === fl - 1 && file[fi] === ''; /* c8 ignore start */ } else { // should be unreachable. throw new Error('wtf?'); } /* c8 ignore stop */ } braceExpand() { return braceExpand(this.pattern, this.options); } parse(pattern) { assertValidPattern(pattern); const options = this.options; // shortcuts if (pattern === '**') return GLOBSTAR; if (pattern === '') return ''; // far and away, the most common glob pattern parts are // *, *.*, and *. Add a fast check method for those. let m; let fastTest = null; if ((m = pattern.match(starRE))) { fastTest = options.dot ? starTestDot : starTest; } else if ((m = pattern.match(starDotExtRE))) { fastTest = (options.nocase ? options.dot ? starDotExtTestNocaseDot : starDotExtTestNocase : options.dot ? starDotExtTestDot : starDotExtTest)(m[1]); } else if ((m = pattern.match(qmarksRE))) { fastTest = (options.nocase ? options.dot ? qmarksTestNocaseDot : qmarksTestNocase : options.dot ? qmarksTestDot : qmarksTest)(m); } else if ((m = pattern.match(starDotStarRE))) { fastTest = options.dot ? starDotStarTestDot : starDotStarTest; } else if ((m = pattern.match(dotStarRE))) { fastTest = dotStarTest; } const re = AST.fromGlob(pattern, this.options).toMMPattern(); if (fastTest && typeof re === 'object') { // Avoids overriding in frozen environments Reflect.defineProperty(re, 'test', { value: fastTest }); } return re; } makeRe() { if (this.regexp || this.regexp === false) return this.regexp; // at this point, this.set is a 2d array of partial // pattern strings, or "**". // // It's better to use .match(). This function shouldn't // be used, really, but it's pretty convenient sometimes, // when you just want to work with a regex. const set = this.set; if (!set.length) { this.regexp = false; return this.regexp; } const options = this.options; const twoStar = options.noglobstar ? star : options.dot ? twoStarDot : twoStarNoDot; const flags = new Set(options.nocase ? ['i'] : []); // regexpify non-globstar patterns // if ** is only item, then we just do one twoStar // if ** is first, and there are more, prepend (\/|twoStar\/)? to next // if ** is last, append (\/twoStar|) to previous // if ** is in the middle, append (\/|\/twoStar\/) to previous // then filter out GLOBSTAR symbols let re = set .map(pattern => { const pp = pattern.map(p => { if (p instanceof RegExp) { for (const f of p.flags.split('')) flags.add(f); } return typeof p === 'string' ? regExpEscape(p) : p === GLOBSTAR ? GLOBSTAR : p._src; }); pp.forEach((p, i) => { const next = pp[i + 1]; const prev = pp[i - 1]; if (p !== GLOBSTAR || prev === GLOBSTAR) { return; } if (prev === undefined) { if (next !== undefined && next !== GLOBSTAR) { pp[i + 1] = '(?:\\/|' + twoStar + '\\/)?' + next; } else { pp[i] = twoStar; } } else if (next === undefined) { pp[i - 1] = prev + '(?:\\/|' + twoStar + ')?'; } else if (next !== GLOBSTAR) { pp[i - 1] = prev + '(?:\\/|\\/' + twoStar + '\\/)' + next; pp[i + 1] = GLOBSTAR; } }); return pp.filter(p => p !== GLOBSTAR).join('/'); }) .join('|'); // need to wrap in parens if we had more than one thing with |, // otherwise only the first will be anchored to ^ and the last to $ const [open, close] = set.length > 1 ? ['(?:', ')'] : ['', '']; // must match entire pattern // ending in a * or ** will make it less strict. re = '^' + open + re + close + '$'; // can match anything, as long as it's not this. if (this.negate) re = '^(?!' + re + ').+$'; try { this.regexp = new RegExp(re, [...flags].join('')); /* c8 ignore start */ } catch (ex) { // should be impossible this.regexp = false; } /* c8 ignore stop */ return this.regexp; } slashSplit(p) { // if p starts with // on windows, we preserve that // so that UNC paths aren't broken. Otherwise, any number of // / characters are coalesced into one, unless // preserveMultipleSlashes is set to true. if (this.preserveMultipleSlashes) { return p.split('/'); } else if (this.isWindows && /^\/\/[^\/]+/.test(p)) { // add an extra '' for the one we lose return ['', ...p.split(/\/+/)]; } else { return p.split(/\/+/); } } match(f, partial = this.partial) { this.debug('match', f, this.pattern); // short-circuit in the case of busted things. // comments, etc. if (this.comment) { return false; } if (this.empty) { return f === ''; } if (f === '/' && partial) { return true; } const options = this.options; // windows: need to use /, not \ if (this.isWindows) { f = f.split('\\').join('/'); } // treat the test path as a set of pathparts. const ff = this.slashSplit(f); this.debug(this.pattern, 'split', ff); // just ONE of the pattern sets in this.set needs to match // in order for it to be valid. If negating, then just one // match means that we have failed. // Either way, return on the first hit. const set = this.set; this.debug(this.pattern, 'set', set); // Find the basename of the path by looking for the last non-empty segment let filename = ff[ff.length - 1]; if (!filename) { for (let i = ff.length - 2; !filename && i >= 0; i--) { filename = ff[i]; } } for (let i = 0; i < set.length; i++) { const pattern = set[i]; let file = ff; if (options.matchBase && pattern.length === 1) { file = [filename]; } const hit = this.matchOne(file, pattern, partial); if (hit) { if (options.flipNegate) { return true; } return !this.negate; } } // didn't get any hits. this is success if it's a negative // pattern, failure otherwise. if (options.flipNegate) { return false; } return this.negate; } static defaults(def) { return minimatch.defaults(def).Minimatch; } } /* c8 ignore start */ export { AST } from './ast.js'; export { escape } from './escape.js'; export { unescape } from './unescape.js'; /* c8 ignore stop */ minimatch.AST = AST; minimatch.Minimatch = Minimatch; minimatch.escape = escape; minimatch.unescape = unescape; //# sourceMappingURL=index.js.map