minimatch 8.0.4 → 8.0.6

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