1001 lines
38 KiB
JavaScript
1001 lines
38 KiB
JavaScript
|
import expand from 'brace-expansion';
|
||
|
import { assertValidPattern } from './assert-valid-pattern.js';
|
||
|
import { AST } from './ast.js';
|
||
|
import { escape } from './escape.js';
|
||
|
import { unescape } from './unescape.js';
|
||
|
export const minimatch = (p, pattern, options = {}) => {
|
||
|
assertValidPattern(pattern);
|
||
|
// shortcut: comments match nothing.
|
||
|
if (!options.nocomment && pattern.charAt(0) === '#') {
|
||
|
return false;
|
||
|
}
|
||
|
return new Minimatch(pattern, options).match(p);
|
||
|
};
|
||
|
// Optimized checking for the most common glob patterns.
|
||
|
const starDotExtRE = /^\*+([^+@!?\*\[\(]*)$/;
|
||
|
const starDotExtTest = (ext) => (f) => !f.startsWith('.') && f.endsWith(ext);
|
||
|
const starDotExtTestDot = (ext) => (f) => f.endsWith(ext);
|
||
|
const starDotExtTestNocase = (ext) => {
|
||
|
ext = ext.toLowerCase();
|
||
|
return (f) => !f.startsWith('.') && f.toLowerCase().endsWith(ext);
|
||
|
};
|
||
|
const starDotExtTestNocaseDot = (ext) => {
|
||
|
ext = ext.toLowerCase();
|
||
|
return (f) => f.toLowerCase().endsWith(ext);
|
||
|
};
|
||
|
const starDotStarRE = /^\*+\.\*+$/;
|
||
|
const starDotStarTest = (f) => !f.startsWith('.') && f.includes('.');
|
||
|
const starDotStarTestDot = (f) => f !== '.' && f !== '..' && f.includes('.');
|
||
|
const dotStarRE = /^\.\*+$/;
|
||
|
const dotStarTest = (f) => f !== '.' && f !== '..' && f.startsWith('.');
|
||
|
const starRE = /^\*+$/;
|
||
|
const starTest = (f) => f.length !== 0 && !f.startsWith('.');
|
||
|
const starTestDot = (f) => f.length !== 0 && f !== '.' && f !== '..';
|
||
|
const qmarksRE = /^\?+([^+@!?\*\[\(]*)?$/;
|
||
|
const qmarksTestNocase = ([$0, ext = '']) => {
|
||
|
const noext = qmarksTestNoExt([$0]);
|
||
|
if (!ext)
|
||
|
return noext;
|
||
|
ext = ext.toLowerCase();
|
||
|
return (f) => noext(f) && f.toLowerCase().endsWith(ext);
|
||
|
};
|
||
|
const qmarksTestNocaseDot = ([$0, ext = '']) => {
|
||
|
const noext = qmarksTestNoExtDot([$0]);
|
||
|
if (!ext)
|
||
|
return noext;
|
||
|
ext = ext.toLowerCase();
|
||
|
return (f) => noext(f) && f.toLowerCase().endsWith(ext);
|
||
|
};
|
||
|
const qmarksTestDot = ([$0, ext = '']) => {
|
||
|
const noext = qmarksTestNoExtDot([$0]);
|
||
|
return !ext ? noext : (f) => noext(f) && f.endsWith(ext);
|
||
|
};
|
||
|
const qmarksTest = ([$0, ext = '']) => {
|
||
|
const noext = qmarksTestNoExt([$0]);
|
||
|
return !ext ? noext : (f) => noext(f) && f.endsWith(ext);
|
||
|
};
|
||
|
const qmarksTestNoExt = ([$0]) => {
|
||
|
const len = $0.length;
|
||
|
return (f) => f.length === len && !f.startsWith('.');
|
||
|
};
|
||
|
const qmarksTestNoExtDot = ([$0]) => {
|
||
|
const len = $0.length;
|
||
|
return (f) => f.length === len && f !== '.' && f !== '..';
|
||
|
};
|
||
|
/* c8 ignore start */
|
||
|
const defaultPlatform = (typeof process === 'object' && process
|
||
|
? (typeof process.env === 'object' &&
|
||
|
process.env &&
|
||
|
process.env.__MINIMATCH_TESTING_PLATFORM__) ||
|
||
|
process.platform
|
||
|
: 'posix');
|
||
|
const path = {
|
||
|
win32: { sep: '\\' },
|
||
|
posix: { sep: '/' },
|
||
|
};
|
||
|
/* c8 ignore stop */
|
||
|
export const sep = defaultPlatform === 'win32' ? path.win32.sep : path.posix.sep;
|
||
|
minimatch.sep = sep;
|
||
|
export const GLOBSTAR = Symbol('globstar **');
|
||
|
minimatch.GLOBSTAR = GLOBSTAR;
|
||
|
// any single thing other than /
|
||
|
// don't need to escape / when using new RegExp()
|
||
|
const qmark = '[^/]';
|
||
|
// * => any number of characters
|
||
|
const star = qmark + '*?';
|
||
|
// ** when dots are allowed. Anything goes, except .. and .
|
||
|
// not (^ or / followed by one or two dots followed by $ or /),
|
||
|
// followed by anything, any number of times.
|
||
|
const twoStarDot = '(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?';
|
||
|
// not a ^ or / followed by a dot,
|
||
|
// followed by anything, any number of times.
|
||
|
const twoStarNoDot = '(?:(?!(?:\\/|^)\\.).)*?';
|
||
|
export const filter = (pattern, options = {}) => (p) => minimatch(p, pattern, options);
|
||
|
minimatch.filter = filter;
|
||
|
const ext = (a, b = {}) => Object.assign({}, a, b);
|
||
|
export const defaults = (def) => {
|
||
|
if (!def || typeof def !== 'object' || !Object.keys(def).length) {
|
||
|
return minimatch;
|
||
|
}
|
||
|
const orig = minimatch;
|
||
|
const m = (p, pattern, options = {}) => orig(p, pattern, ext(def, options));
|
||
|
return Object.assign(m, {
|
||
|
Minimatch: class Minimatch extends orig.Minimatch {
|
||
|
constructor(pattern, options = {}) {
|
||
|
super(pattern, ext(def, options));
|
||
|
}
|
||
|
static defaults(options) {
|
||
|
return orig.defaults(ext(def, options)).Minimatch;
|
||
|
}
|
||
|
},
|
||
|
AST: class AST extends orig.AST {
|
||
|
/* c8 ignore start */
|
||
|
constructor(type, parent, options = {}) {
|
||
|
super(type, parent, ext(def, options));
|
||
|
}
|
||
|
/* c8 ignore stop */
|
||
|
static fromGlob(pattern, options = {}) {
|
||
|
return orig.AST.fromGlob(pattern, ext(def, options));
|
||
|
}
|
||
|
},
|
||
|
unescape: (s, options = {}) => orig.unescape(s, ext(def, options)),
|
||
|
escape: (s, options = {}) => orig.escape(s, ext(def, options)),
|
||
|
filter: (pattern, options = {}) => orig.filter(pattern, ext(def, options)),
|
||
|
defaults: (options) => orig.defaults(ext(def, options)),
|
||
|
makeRe: (pattern, options = {}) => orig.makeRe(pattern, ext(def, options)),
|
||
|
braceExpand: (pattern, options = {}) => orig.braceExpand(pattern, ext(def, options)),
|
||
|
match: (list, pattern, options = {}) => orig.match(list, pattern, ext(def, options)),
|
||
|
sep: orig.sep,
|
||
|
GLOBSTAR: GLOBSTAR,
|
||
|
});
|
||
|
};
|
||
|
minimatch.defaults = defaults;
|
||
|
// Brace expansion:
|
||
|
// a{b,c}d -> abd acd
|
||
|
// a{b,}c -> abc ac
|
||
|
// a{0..3}d -> a0d a1d a2d a3d
|
||
|
// a{b,c{d,e}f}g -> abg acdfg acefg
|
||
|
// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg
|
||
|
//
|
||
|
// Invalid sets are not expanded.
|
||
|
// a{2..}b -> a{2..}b
|
||
|
// a{b}c -> a{b}c
|
||
|
export const braceExpand = (pattern, options = {}) => {
|
||
|
assertValidPattern(pattern);
|
||
|
// Thanks to Yeting Li <https://github.com/yetingli> for
|
||
|
// improving this regexp to avoid a ReDOS vulnerability.
|
||
|
if (options.nobrace || !/\{(?:(?!\{).)*\}/.test(pattern)) {
|
||
|
// shortcut. no need to expand.
|
||
|
return [pattern];
|
||
|
}
|
||
|
return expand(pattern);
|
||
|
};
|
||
|
minimatch.braceExpand = braceExpand;
|
||
|
// parse a component of the expanded set.
|
||
|
// At this point, no pattern may contain "/" in it
|
||
|
// so we're going to return a 2d array, where each entry is the full
|
||
|
// pattern, split on '/', and then turned into a regular expression.
|
||
|
// A regexp is made at the end which joins each array with an
|
||
|
// escaped /, and another full one which joins each regexp with |.
|
||
|
//
|
||
|
// Following the lead of Bash 4.1, note that "**" only has special meaning
|
||
|
// when it is the *only* thing in a path portion. Otherwise, any series
|
||
|
// of * is equivalent to a single *. Globstar behavior is enabled by
|
||
|
// default, and can be disabled by setting options.noglobstar.
|
||
|
export const makeRe = (pattern, options = {}) => new Minimatch(pattern, options).makeRe();
|
||
|
minimatch.makeRe = makeRe;
|
||
|
export const match = (list, pattern, options = {}) => {
|
||
|
const mm = new Minimatch(pattern, options);
|
||
|
list = list.filter(f => mm.match(f));
|
||
|
if (mm.options.nonull && !list.length) {
|
||
|
list.push(pattern);
|
||
|
}
|
||
|
return list;
|
||
|
};
|
||
|
minimatch.match = match;
|
||
|
// replace stuff like \* with *
|
||
|
const globMagic = /[?*]|[+@!]\(.*?\)|\[|\]/;
|
||
|
const regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||
|
export class Minimatch {
|
||
|
options;
|
||
|
set;
|
||
|
pattern;
|
||
|
windowsPathsNoEscape;
|
||
|
nonegate;
|
||
|
negate;
|
||
|
comment;
|
||
|
empty;
|
||
|
preserveMultipleSlashes;
|
||
|
partial;
|
||
|
globSet;
|
||
|
globParts;
|
||
|
nocase;
|
||
|
isWindows;
|
||
|
platform;
|
||
|
windowsNoMagicRoot;
|
||
|
regexp;
|
||
|
constructor(pattern, options = {}) {
|
||
|
assertValidPattern(pattern);
|
||
|
options = options || {};
|
||
|
this.options = options;
|
||
|
this.pattern = pattern;
|
||
|
this.platform = options.platform || defaultPlatform;
|
||
|
this.isWindows = this.platform === 'win32';
|
||
|
this.windowsPathsNoEscape =
|
||
|
!!options.windowsPathsNoEscape || options.allowWindowsEscape === false;
|
||
|
if (this.windowsPathsNoEscape) {
|
||
|
this.pattern = this.pattern.replace(/\\/g, '/');
|
||
|
}
|
||
|
this.preserveMultipleSlashes = !!options.preserveMultipleSlashes;
|
||
|
this.regexp = null;
|
||
|
this.negate = false;
|
||
|
this.nonegate = !!options.nonegate;
|
||
|
this.comment = false;
|
||
|
this.empty = false;
|
||
|
this.partial = !!options.partial;
|
||
|
this.nocase = !!this.options.nocase;
|
||
|
this.windowsNoMagicRoot =
|
||
|
options.windowsNoMagicRoot !== undefined
|
||
|
? options.windowsNoMagicRoot
|
||
|
: !!(this.isWindows && this.nocase);
|
||
|
this.globSet = [];
|
||
|
this.globParts = [];
|
||
|
this.set = [];
|
||
|
// make the set of regexps etc.
|
||
|
this.make();
|
||
|
}
|
||
|
hasMagic() {
|
||
|
if (this.options.magicalBraces && this.set.length > 1) {
|
||
|
return true;
|
||
|
}
|
||
|
for (const pattern of this.set) {
|
||
|
for (const part of pattern) {
|
||
|
if (typeof part !== 'string')
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
debug(..._) { }
|
||
|
make() {
|
||
|
const pattern = this.pattern;
|
||
|
const options = this.options;
|
||
|
// empty patterns and comments match nothing.
|
||
|
if (!options.nocomment && pattern.charAt(0) === '#') {
|
||
|
this.comment = true;
|
||
|
return;
|
||
|
}
|
||
|
if (!pattern) {
|
||
|
this.empty = true;
|
||
|
return;
|
||
|
}
|
||
|
// step 1: figure out negation, etc.
|
||
|
this.parseNegate();
|
||
|
// step 2: expand braces
|
||
|
this.globSet = [...new Set(this.braceExpand())];
|
||
|
if (options.debug) {
|
||
|
this.debug = (...args) => console.error(...args);
|
||
|
}
|
||
|
this.debug(this.pattern, this.globSet);
|
||
|
// step 3: now we have a set, so turn each one into a series of
|
||
|
// path-portion matching patterns.
|
||
|
// These will be regexps, except in the case of "**", which is
|
||
|
// set to the GLOBSTAR object for globstar behavior,
|
||
|
// and will not contain any / characters
|
||
|
//
|
||
|
// First, we preprocess to make the glob pattern sets a bit simpler
|
||
|
// and deduped. There are some perf-killing patterns that can cause
|
||
|
// problems with a glob walk, but we can simplify them down a bit.
|
||
|
const rawGlobParts = this.globSet.map(s => this.slashSplit(s));
|
||
|
this.globParts = this.preprocess(rawGlobParts);
|
||
|
this.debug(this.pattern, this.globParts);
|
||
|
// glob --> regexps
|
||
|
let set = this.globParts.map((s, _, __) => {
|
||
|
if (this.isWindows && this.windowsNoMagicRoot) {
|
||
|
// check if it's a drive or unc path.
|
||
|
const isUNC = s[0] === '' &&
|
||
|
s[1] === '' &&
|
||
|
(s[2] === '?' || !globMagic.test(s[2])) &&
|
||
|
!globMagic.test(s[3]);
|
||
|
const isDrive = /^[a-z]:/i.test(s[0]);
|
||
|
if (isUNC) {
|
||
|
return [...s.slice(0, 4), ...s.slice(4).map(ss => this.parse(ss))];
|
||
|
}
|
||
|
else if (isDrive) {
|
||
|
return [s[0], ...s.slice(1).map(ss => this.parse(ss))];
|
||
|
}
|
||
|
}
|
||
|
return s.map(ss => this.parse(ss));
|
||
|
});
|
||
|
this.debug(this.pattern, set);
|
||
|
// filter out everything that didn't compile properly.
|
||
|
this.set = set.filter(s => s.indexOf(false) === -1);
|
||
|
// do not treat the ? in UNC paths as magic
|
||
|
if (this.isWindows) {
|
||
|
for (let i = 0; i < this.set.length; i++) {
|
||
|
const p = this.set[i];
|
||
|
if (p[0] === '' &&
|
||
|
p[1] === '' &&
|
||
|
this.globParts[i][2] === '?' &&
|
||
|
typeof p[3] === 'string' &&
|
||
|
/^[a-z]:$/i.test(p[3])) {
|
||
|
p[2] = '?';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this.debug(this.pattern, this.set);
|
||
|
}
|
||
|
// various transforms to equivalent pattern sets that are
|
||
|
// faster to process in a filesystem walk. The goal is to
|
||
|
// eliminate what we can, and push all ** patterns as far
|
||
|
// to the right as possible, even if it increases the number
|
||
|
// of patterns that we have to process.
|
||
|
preprocess(globParts) {
|
||
|
// if we're not in globstar mode, then turn all ** into *
|
||
|
if (this.options.noglobstar) {
|
||
|
for (let i = 0; i < globParts.length; i++) {
|
||
|
for (let j = 0; j < globParts[i].length; j++) {
|
||
|
if (globParts[i][j] === '**') {
|
||
|
globParts[i][j] = '*';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
const { optimizationLevel = 1 } = this.options;
|
||
|
if (optimizationLevel >= 2) {
|
||
|
// aggressive optimization for the purpose of fs walking
|
||
|
globParts = this.firstPhasePreProcess(globParts);
|
||
|
globParts = this.secondPhasePreProcess(globParts);
|
||
|
}
|
||
|
else if (optimizationLevel >= 1) {
|
||
|
// just basic optimizations to remove some .. parts
|
||
|
globParts = this.levelOneOptimize(globParts);
|
||
|
}
|
||
|
else {
|
||
|
// just collapse multiple ** portions into one
|
||
|
globParts = this.adjascentGlobstarOptimize(globParts);
|
||
|
}
|
||
|
return globParts;
|
||
|
}
|
||
|
// just get rid of adjascent ** portions
|
||
|
adjascentGlobstarOptimize(globParts) {
|
||
|
return globParts.map(parts => {
|
||
|
let gs = -1;
|
||
|
while (-1 !== (gs = parts.indexOf('**', gs + 1))) {
|
||
|
let i = gs;
|
||
|
while (parts[i + 1] === '**') {
|
||
|
i++;
|
||
|
}
|
||
|
if (i !== gs) {
|
||
|
parts.splice(gs, i - gs);
|
||
|
}
|
||
|
}
|
||
|
return parts;
|
||
|
});
|
||
|
}
|
||
|
// get rid of adjascent ** and resolve .. portions
|
||
|
levelOneOptimize(globParts) {
|
||
|
return globParts.map(parts => {
|
||
|
parts = parts.reduce((set, part) => {
|
||
|
const prev = set[set.length - 1];
|
||
|
if (part === '**' && prev === '**') {
|
||
|
return set;
|
||
|
}
|
||
|
if (part === '..') {
|
||
|
if (prev && prev !== '..' && prev !== '.' && prev !== '**') {
|
||
|
set.pop();
|
||
|
return set;
|
||
|
}
|
||
|
}
|
||
|
set.push(part);
|
||
|
return set;
|
||
|
}, []);
|
||
|
return parts.length === 0 ? [''] : parts;
|
||
|
});
|
||
|
}
|
||
|
levelTwoFileOptimize(parts) {
|
||
|
if (!Array.isArray(parts)) {
|
||
|
parts = this.slashSplit(parts);
|
||
|
}
|
||
|
let didSomething = false;
|
||
|
do {
|
||
|
didSomething = false;
|
||
|
// <pre>/<e>/<rest> -> <pre>/<rest>
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
// <pre>/<p>/../<rest> -> <pre>/<rest>
|
||
|
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
|
||
|
// <pre> is 1 or more portions
|
||
|
// <rest> is 1 or more portions
|
||
|
// <p> is any portion other than ., .., '', or **
|
||
|
// <e> 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.
|
||
|
//
|
||
|
// <pre>/**/../<p>/<p>/<rest> -> {<pre>/../<p>/<p>/<rest>,<pre>/**/<p>/<p>/<rest>}
|
||
|
// <pre>/<e>/<rest> -> <pre>/<rest>
|
||
|
// <pre>/<p>/../<rest> -> <pre>/<rest>
|
||
|
// **/**/<rest> -> **/<rest>
|
||
|
//
|
||
|
// **/*/<rest> -> */**/<rest> <== 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;
|
||
|
// <pre>/**/../<p>/<p>/<rest> -> {<pre>/../<p>/<p>/<rest>,<pre>/**/<p>/<p>/<rest>}
|
||
|
for (let parts of globParts) {
|
||
|
let gs = -1;
|
||
|
while (-1 !== (gs = parts.indexOf('**', gs + 1))) {
|
||
|
let gss = gs;
|
||
|
while (parts[gss + 1] === '**') {
|
||
|
// <pre>/**/**/<rest> -> <pre>/**/<rest>
|
||
|
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--;
|
||
|
}
|
||
|
// <pre>/<e>/<rest> -> <pre>/<rest>
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
// <pre>/<p>/../<rest> -> <pre>/<rest>
|
||
|
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
|
||
|
// {<pre>/*/<rest>,<pre>/<p>/<rest>} -> <pre>/*/<rest>
|
||
|
// {<pre>/<rest>,<pre>/<rest>} -> <pre>/<rest>
|
||
|
// {<pre>/**/<rest>,<pre>/<rest>} -> <pre>/**/<rest>
|
||
|
//
|
||
|
// {<pre>/**/<rest>,<pre>/**/<p>/<rest>} -> <pre>/**/<rest>
|
||
|
// ^-- 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 *.<ext> 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
|