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