minimatch 9.0.6 → 9.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -193,11 +193,13 @@ export class Minimatch {
193
193
  isWindows;
194
194
  platform;
195
195
  windowsNoMagicRoot;
196
+ maxGlobstarRecursion;
196
197
  regexp;
197
198
  constructor(pattern, options = {}) {
198
199
  assertValidPattern(pattern);
199
200
  options = options || {};
200
201
  this.options = options;
202
+ this.maxGlobstarRecursion = options.maxGlobstarRecursion ?? 200;
201
203
  this.pattern = pattern;
202
204
  this.platform = options.platform || defaultPlatform;
203
205
  this.isWindows = this.platform === 'win32';
@@ -597,7 +599,8 @@ export class Minimatch {
597
599
  // out of pattern, then that's fine, as long as all
598
600
  // the parts match.
599
601
  matchOne(file, pattern, partial = false) {
600
- const options = this.options;
602
+ let fileStartIndex = 0;
603
+ let patternStartIndex = 0;
601
604
  // UNC paths like //?/X:/... can match X:/... and vice versa
602
605
  // Drive letters in absolute drive or unc paths are always compared
603
606
  // case-insensitively.
@@ -618,15 +621,14 @@ export class Minimatch {
618
621
  const fdi = fileUNC ? 3 : fileDrive ? 0 : undefined;
619
622
  const pdi = patternUNC ? 3 : patternDrive ? 0 : undefined;
620
623
  if (typeof fdi === 'number' && typeof pdi === 'number') {
621
- const [fd, pd] = [file[fdi], pattern[pdi]];
624
+ const [fd, pd] = [
625
+ file[fdi],
626
+ pattern[pdi],
627
+ ];
622
628
  if (fd.toLowerCase() === pd.toLowerCase()) {
623
629
  pattern[pdi] = fd;
624
- if (pdi > fdi) {
625
- pattern = pattern.slice(pdi);
626
- }
627
- else if (fdi > pdi) {
628
- file = file.slice(fdi);
629
- }
630
+ patternStartIndex = pdi;
631
+ fileStartIndex = fdi;
630
632
  }
631
633
  }
632
634
  }
@@ -636,102 +638,123 @@ export class Minimatch {
636
638
  if (optimizationLevel >= 2) {
637
639
  file = this.levelTwoFileOptimize(file);
638
640
  }
639
- this.debug('matchOne', this, { file, pattern });
640
- this.debug('matchOne', file.length, pattern.length);
641
- for (var fi = 0, pi = 0, fl = file.length, pl = pattern.length; fi < fl && pi < pl; fi++, pi++) {
642
- this.debug('matchOne loop');
643
- var p = pattern[pi];
644
- var f = file[fi];
645
- this.debug(pattern, p, f);
646
- // should be impossible.
647
- // some invalid regexp stuff in the set.
648
- /* c8 ignore start */
649
- if (p === false) {
641
+ if (pattern.includes(GLOBSTAR)) {
642
+ return this.#matchGlobstar(file, pattern, partial, fileStartIndex, patternStartIndex);
643
+ }
644
+ return this.#matchOne(file, pattern, partial, fileStartIndex, patternStartIndex);
645
+ }
646
+ #matchGlobstar(file, pattern, partial, fileIndex, patternIndex) {
647
+ const firstgs = pattern.indexOf(GLOBSTAR, patternIndex);
648
+ const lastgs = pattern.lastIndexOf(GLOBSTAR);
649
+ const [head, body, tail] = [
650
+ pattern.slice(patternIndex, firstgs),
651
+ pattern.slice(firstgs + 1, lastgs),
652
+ pattern.slice(lastgs + 1),
653
+ ];
654
+ if (head.length) {
655
+ const fileHead = file.slice(fileIndex, fileIndex + head.length);
656
+ if (!this.#matchOne(fileHead, head, partial, 0, 0))
650
657
  return false;
658
+ fileIndex += head.length;
659
+ }
660
+ let fileTailMatch = 0;
661
+ if (tail.length) {
662
+ if (tail.length + fileIndex > file.length)
663
+ return false;
664
+ let tailStart = file.length - tail.length;
665
+ if (this.#matchOne(file, tail, partial, tailStart, 0)) {
666
+ fileTailMatch = tail.length;
651
667
  }
652
- /* c8 ignore stop */
653
- if (p === GLOBSTAR) {
654
- this.debug('GLOBSTAR', [pattern, p, f]);
655
- // "**"
656
- // a/**/b/**/c would match the following:
657
- // a/b/x/y/z/c
658
- // a/x/y/z/b/c
659
- // a/b/x/b/x/c
660
- // a/b/c
661
- // To do this, take the rest of the pattern after
662
- // the **, and see if it would match the file remainder.
663
- // If so, return success.
664
- // If not, the ** "swallows" a segment, and try again.
665
- // This is recursively awful.
666
- //
667
- // a/**/b/**/c matching a/b/x/y/z/c
668
- // - a matches a
669
- // - doublestar
670
- // - matchOne(b/x/y/z/c, b/**/c)
671
- // - b matches b
672
- // - doublestar
673
- // - matchOne(x/y/z/c, c) -> no
674
- // - matchOne(y/z/c, c) -> no
675
- // - matchOne(z/c, c) -> no
676
- // - matchOne(c, c) yes, hit
677
- var fr = fi;
678
- var pr = pi + 1;
679
- if (pr === pl) {
680
- this.debug('** at the end');
681
- // a ** at the end will just swallow the rest.
682
- // We have found a match.
683
- // however, it will not swallow /.x, unless
684
- // options.dot is set.
685
- // . and .. are *never* matched by **, for explosively
686
- // exponential reasons.
687
- for (; fi < fl; fi++) {
688
- if (file[fi] === '.' ||
689
- file[fi] === '..' ||
690
- (!options.dot && file[fi].charAt(0) === '.'))
691
- return false;
692
- }
693
- return true;
668
+ else {
669
+ if (file[file.length - 1] !== '' ||
670
+ fileIndex + tail.length === file.length) {
671
+ return false;
694
672
  }
695
- // ok, let's see if we can swallow whatever we can.
696
- while (fr < fl) {
697
- var swallowee = file[fr];
698
- this.debug('\nglobstar while', file, fr, pattern, pr, swallowee);
699
- // XXX remove this slice. Just pass the start index.
700
- if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
701
- this.debug('globstar found match!', fr, fl, swallowee);
702
- // found a match.
703
- return true;
704
- }
705
- else {
706
- // can't swallow "." or ".." ever.
707
- // can only swallow ".foo" when explicitly asked.
708
- if (swallowee === '.' ||
709
- swallowee === '..' ||
710
- (!options.dot && swallowee.charAt(0) === '.')) {
711
- this.debug('dot detected!', file, fr, pattern, pr);
712
- break;
713
- }
714
- // ** swallows a segment, and continue.
715
- this.debug('globstar swallow a segment, and continue');
716
- fr++;
717
- }
673
+ tailStart--;
674
+ if (!this.#matchOne(file, tail, partial, tailStart, 0))
675
+ return false;
676
+ fileTailMatch = tail.length + 1;
677
+ }
678
+ }
679
+ if (!body.length) {
680
+ let sawSome = !!fileTailMatch;
681
+ for (let i = fileIndex; i < file.length - fileTailMatch; i++) {
682
+ const f = String(file[i]);
683
+ sawSome = true;
684
+ if (f === '.' || f === '..' ||
685
+ (!this.options.dot && f.startsWith('.'))) {
686
+ return false;
718
687
  }
719
- // no match was found.
720
- // However, in partial mode, we can't say this is necessarily over.
721
- /* c8 ignore start */
722
- if (partial) {
723
- // ran out of file
724
- this.debug('\n>>> no match, partial?', file, fr, pattern, pr);
725
- if (fr === fl) {
726
- return true;
727
- }
688
+ }
689
+ return sawSome;
690
+ }
691
+ const bodySegments = [[[], 0]];
692
+ let currentBody = bodySegments[0];
693
+ let nonGsParts = 0;
694
+ const nonGsPartsSums = [0];
695
+ for (const b of body) {
696
+ if (b === GLOBSTAR) {
697
+ nonGsPartsSums.push(nonGsParts);
698
+ currentBody = [[], 0];
699
+ bodySegments.push(currentBody);
700
+ }
701
+ else {
702
+ currentBody[0].push(b);
703
+ nonGsParts++;
704
+ }
705
+ }
706
+ let i = bodySegments.length - 1;
707
+ const fileLength = file.length - fileTailMatch;
708
+ for (const b of bodySegments) {
709
+ b[1] = fileLength - (nonGsPartsSums[i--] + b[0].length);
710
+ }
711
+ return !!this.#matchGlobStarBodySections(file, bodySegments, fileIndex, 0, partial, 0, !!fileTailMatch);
712
+ }
713
+ #matchGlobStarBodySections(file, bodySegments, fileIndex, bodyIndex, partial, globStarDepth, sawTail) {
714
+ const bs = bodySegments[bodyIndex];
715
+ if (!bs) {
716
+ for (let i = fileIndex; i < file.length; i++) {
717
+ sawTail = true;
718
+ const f = file[i];
719
+ if (f === '.' || f === '..' ||
720
+ (!this.options.dot && f.startsWith('.'))) {
721
+ return false;
728
722
  }
729
- /* c8 ignore stop */
723
+ }
724
+ return sawTail;
725
+ }
726
+ const [body, after] = bs;
727
+ while (fileIndex <= after) {
728
+ const m = this.#matchOne(file.slice(0, fileIndex + body.length), body, partial, fileIndex, 0);
729
+ if (m && globStarDepth < this.maxGlobstarRecursion) {
730
+ const sub = this.#matchGlobStarBodySections(file, bodySegments, fileIndex + body.length, bodyIndex + 1, partial, globStarDepth + 1, sawTail);
731
+ if (sub !== false)
732
+ return sub;
733
+ }
734
+ const f = file[fileIndex];
735
+ if (f === '.' || f === '..' ||
736
+ (!this.options.dot && f.startsWith('.'))) {
730
737
  return false;
731
738
  }
732
- // something other than **
733
- // non-magic patterns just have to match exactly
734
- // patterns with magic have been turned into regexps.
739
+ fileIndex++;
740
+ }
741
+ return null;
742
+ }
743
+ #matchOne(file, pattern, partial, fileIndex, patternIndex) {
744
+ let fi;
745
+ let pi;
746
+ let pl;
747
+ let fl;
748
+ for (fi = fileIndex, pi = patternIndex,
749
+ fl = file.length, pl = pattern.length; fi < fl && pi < pl; fi++, pi++) {
750
+ this.debug('matchOne loop');
751
+ let p = pattern[pi];
752
+ let f = file[fi];
753
+ this.debug(pattern, p, f);
754
+ /* c8 ignore start */
755
+ if (p === false || p === GLOBSTAR)
756
+ return false;
757
+ /* c8 ignore stop */
735
758
  let hit;
736
759
  if (typeof p === 'string') {
737
760
  hit = f === p;
@@ -744,38 +767,17 @@ export class Minimatch {
744
767
  if (!hit)
745
768
  return false;
746
769
  }
747
- // Note: ending in / means that we'll get a final ""
748
- // at the end of the pattern. This can only match a
749
- // corresponding "" at the end of the file.
750
- // If the file ends in /, then it can only match a
751
- // a pattern that ends in /, unless the pattern just
752
- // doesn't have any more for it. But, a/b/ should *not*
753
- // match "a/b/*", even though "" matches against the
754
- // [^/]*? pattern, except in partial mode, where it might
755
- // simply not be reached yet.
756
- // However, a/b/ should still satisfy a/*
757
- // now either we fell off the end of the pattern, or we're done.
758
770
  if (fi === fl && pi === pl) {
759
- // ran out of pattern and filename at the same time.
760
- // an exact hit!
761
771
  return true;
762
772
  }
763
773
  else if (fi === fl) {
764
- // ran out of file, but still had pattern left.
765
- // this is ok if we're doing the match as part of
766
- // a glob fs traversal.
767
774
  return partial;
768
775
  }
769
776
  else if (pi === pl) {
770
- // ran out of pattern, still have file left.
771
- // this is only acceptable if we're on the very last
772
- // empty segment of a file with a trailing slash.
773
- // a/* should match a/b/
774
777
  return fi === fl - 1 && file[fi] === '';
775
778
  /* c8 ignore start */
776
779
  }
777
780
  else {
778
- // should be unreachable.
779
781
  throw new Error('wtf?');
780
782
  }
781
783
  /* c8 ignore stop */