@pnpm/exe 11.0.0-alpha.8 → 11.0.0-beta.0

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.
@@ -202,11 +202,13 @@ class Minimatch {
202
202
  isWindows;
203
203
  platform;
204
204
  windowsNoMagicRoot;
205
+ maxGlobstarRecursion;
205
206
  regexp;
206
207
  constructor(pattern, options = {}) {
207
208
  (0, assert_valid_pattern_js_1.assertValidPattern)(pattern);
208
209
  options = options || {};
209
210
  this.options = options;
211
+ this.maxGlobstarRecursion = options.maxGlobstarRecursion ?? 200;
210
212
  this.pattern = pattern;
211
213
  this.platform = options.platform || defaultPlatform;
212
214
  this.isWindows = this.platform === 'win32';
@@ -611,7 +613,8 @@ class Minimatch {
611
613
  // out of pattern, then that's fine, as long as all
612
614
  // the parts match.
613
615
  matchOne(file, pattern, partial = false) {
614
- const options = this.options;
616
+ let fileStartIndex = 0;
617
+ let patternStartIndex = 0;
615
618
  // UNC paths like //?/X:/... can match X:/... and vice versa
616
619
  // Drive letters in absolute drive or unc paths are always compared
617
620
  // case-insensitively.
@@ -640,14 +643,11 @@ class Minimatch {
640
643
  file[fdi],
641
644
  pattern[pdi],
642
645
  ];
646
+ // start matching at the drive letter index of each
643
647
  if (fd.toLowerCase() === pd.toLowerCase()) {
644
648
  pattern[pdi] = fd;
645
- if (pdi > fdi) {
646
- pattern = pattern.slice(pdi);
647
- }
648
- else if (fdi > pdi) {
649
- file = file.slice(fdi);
650
- }
649
+ patternStartIndex = pdi;
650
+ fileStartIndex = fdi;
651
651
  }
652
652
  }
653
653
  }
@@ -657,99 +657,185 @@ class Minimatch {
657
657
  if (optimizationLevel >= 2) {
658
658
  file = this.levelTwoFileOptimize(file);
659
659
  }
660
- this.debug('matchOne', this, { file, pattern });
661
- this.debug('matchOne', file.length, pattern.length);
662
- for (var fi = 0, pi = 0, fl = file.length, pl = pattern.length; fi < fl && pi < pl; fi++, pi++) {
660
+ if (pattern.includes(exports.GLOBSTAR)) {
661
+ return this.#matchGlobstar(file, pattern, partial, fileStartIndex, patternStartIndex);
662
+ }
663
+ return this.#matchOne(file, pattern, partial, fileStartIndex, patternStartIndex);
664
+ }
665
+ #matchGlobstar(file, pattern, partial, fileIndex, patternIndex) {
666
+ // split the pattern into head, tail, and middle of ** delimited parts
667
+ const firstgs = pattern.indexOf(exports.GLOBSTAR, patternIndex);
668
+ const lastgs = pattern.lastIndexOf(exports.GLOBSTAR);
669
+ // split the pattern up into globstar-delimited sections
670
+ // the tail has to be at the end, and the others just have
671
+ // to be found in order from the head.
672
+ const [head, body, tail] = partial ? [
673
+ pattern.slice(patternIndex, firstgs),
674
+ pattern.slice(firstgs + 1),
675
+ [],
676
+ ] : [
677
+ pattern.slice(patternIndex, firstgs),
678
+ pattern.slice(firstgs + 1, lastgs),
679
+ pattern.slice(lastgs + 1),
680
+ ];
681
+ // check the head, from the current file/pattern index.
682
+ if (head.length) {
683
+ const fileHead = file.slice(fileIndex, fileIndex + head.length);
684
+ if (!this.#matchOne(fileHead, head, partial, 0, 0)) {
685
+ return false;
686
+ }
687
+ fileIndex += head.length;
688
+ patternIndex += head.length;
689
+ }
690
+ // now we know the head matches!
691
+ // if the last portion is not empty, it MUST match the end
692
+ // check the tail
693
+ let fileTailMatch = 0;
694
+ if (tail.length) {
695
+ // if head + tail > file, then we cannot possibly match
696
+ if (tail.length + fileIndex > file.length)
697
+ return false;
698
+ // try to match the tail
699
+ let tailStart = file.length - tail.length;
700
+ if (this.#matchOne(file, tail, partial, tailStart, 0)) {
701
+ fileTailMatch = tail.length;
702
+ }
703
+ else {
704
+ // affordance for stuff like a/**/* matching a/b/
705
+ // if the last file portion is '', and there's more to the pattern
706
+ // then try without the '' bit.
707
+ if (file[file.length - 1] !== '' ||
708
+ fileIndex + tail.length === file.length) {
709
+ return false;
710
+ }
711
+ tailStart--;
712
+ if (!this.#matchOne(file, tail, partial, tailStart, 0)) {
713
+ return false;
714
+ }
715
+ fileTailMatch = tail.length + 1;
716
+ }
717
+ }
718
+ // now we know the tail matches!
719
+ // the middle is zero or more portions wrapped in **, possibly
720
+ // containing more ** sections.
721
+ // so a/**/b/**/c/**/d has become **/b/**/c/**
722
+ // if it's empty, it means a/**/b, just verify we have no bad dots
723
+ // if there's no tail, so it ends on /**, then we must have *something*
724
+ // after the head, or it's not a matc
725
+ if (!body.length) {
726
+ let sawSome = !!fileTailMatch;
727
+ for (let i = fileIndex; i < file.length - fileTailMatch; i++) {
728
+ const f = String(file[i]);
729
+ sawSome = true;
730
+ if (f === '.' ||
731
+ f === '..' ||
732
+ (!this.options.dot && f.startsWith('.'))) {
733
+ return false;
734
+ }
735
+ }
736
+ // in partial mode, we just need to get past all file parts
737
+ return partial || sawSome;
738
+ }
739
+ // now we know that there's one or more body sections, which can
740
+ // be matched anywhere from the 0 index (because the head was pruned)
741
+ // through to the length-fileTailMatch index.
742
+ // split the body up into sections, and note the minimum index it can
743
+ // be found at (start with the length of all previous segments)
744
+ // [section, before, after]
745
+ const bodySegments = [[[], 0]];
746
+ let currentBody = bodySegments[0];
747
+ let nonGsParts = 0;
748
+ const nonGsPartsSums = [0];
749
+ for (const b of body) {
750
+ if (b === exports.GLOBSTAR) {
751
+ nonGsPartsSums.push(nonGsParts);
752
+ currentBody = [[], 0];
753
+ bodySegments.push(currentBody);
754
+ }
755
+ else {
756
+ currentBody[0].push(b);
757
+ nonGsParts++;
758
+ }
759
+ }
760
+ let i = bodySegments.length - 1;
761
+ const fileLength = file.length - fileTailMatch;
762
+ for (const b of bodySegments) {
763
+ b[1] = fileLength - (nonGsPartsSums[i--] + b[0].length);
764
+ }
765
+ return !!this.#matchGlobStarBodySections(file, bodySegments, fileIndex, 0, partial, 0, !!fileTailMatch);
766
+ }
767
+ // return false for "nope, not matching"
768
+ // return null for "not matching, cannot keep trying"
769
+ #matchGlobStarBodySections(file,
770
+ // pattern section, last possible position for it
771
+ bodySegments, fileIndex, bodyIndex, partial, globStarDepth, sawTail) {
772
+ // take the first body segment, and walk from fileIndex to its "after"
773
+ // value at the end
774
+ // If it doesn't match at that position, we increment, until we hit
775
+ // that final possible position, and give up.
776
+ // If it does match, then advance and try to rest.
777
+ // If any of them fail we keep walking forward.
778
+ // this is still a bit recursively painful, but it's more constrained
779
+ // than previous implementations, because we never test something that
780
+ // can't possibly be a valid matching condition.
781
+ const bs = bodySegments[bodyIndex];
782
+ if (!bs) {
783
+ // just make sure that there's no bad dots
784
+ for (let i = fileIndex; i < file.length; i++) {
785
+ sawTail = true;
786
+ const f = file[i];
787
+ if (f === '.' ||
788
+ f === '..' ||
789
+ (!this.options.dot && f.startsWith('.'))) {
790
+ return false;
791
+ }
792
+ }
793
+ return sawTail;
794
+ }
795
+ // have a non-globstar body section to test
796
+ const [body, after] = bs;
797
+ while (fileIndex <= after) {
798
+ const m = this.#matchOne(file.slice(0, fileIndex + body.length), body, partial, fileIndex, 0);
799
+ // if limit exceeded, no match. intentional false negative,
800
+ // acceptable break in correctness for security.
801
+ if (m && globStarDepth < this.maxGlobstarRecursion) {
802
+ // match! see if the rest match. if so, we're done!
803
+ const sub = this.#matchGlobStarBodySections(file, bodySegments, fileIndex + body.length, bodyIndex + 1, partial, globStarDepth + 1, sawTail);
804
+ if (sub !== false) {
805
+ return sub;
806
+ }
807
+ }
808
+ const f = file[fileIndex];
809
+ if (f === '.' ||
810
+ f === '..' ||
811
+ (!this.options.dot && f.startsWith('.'))) {
812
+ return false;
813
+ }
814
+ fileIndex++;
815
+ }
816
+ // walked off. no point continuing
817
+ return partial || null;
818
+ }
819
+ #matchOne(file, pattern, partial, fileIndex, patternIndex) {
820
+ let fi;
821
+ let pi;
822
+ let pl;
823
+ let fl;
824
+ for (fi = fileIndex,
825
+ pi = patternIndex,
826
+ fl = file.length,
827
+ pl = pattern.length; fi < fl && pi < pl; fi++, pi++) {
663
828
  this.debug('matchOne loop');
664
- var p = pattern[pi];
665
- var f = file[fi];
829
+ let p = pattern[pi];
830
+ let f = file[fi];
666
831
  this.debug(pattern, p, f);
667
832
  // should be impossible.
668
833
  // some invalid regexp stuff in the set.
669
834
  /* c8 ignore start */
670
- if (p === false) {
835
+ if (p === false || p === exports.GLOBSTAR) {
671
836
  return false;
672
837
  }
673
838
  /* c8 ignore stop */
674
- if (p === exports.GLOBSTAR) {
675
- this.debug('GLOBSTAR', [pattern, p, f]);
676
- // "**"
677
- // a/**/b/**/c would match the following:
678
- // a/b/x/y/z/c
679
- // a/x/y/z/b/c
680
- // a/b/x/b/x/c
681
- // a/b/c
682
- // To do this, take the rest of the pattern after
683
- // the **, and see if it would match the file remainder.
684
- // If so, return success.
685
- // If not, the ** "swallows" a segment, and try again.
686
- // This is recursively awful.
687
- //
688
- // a/**/b/**/c matching a/b/x/y/z/c
689
- // - a matches a
690
- // - doublestar
691
- // - matchOne(b/x/y/z/c, b/**/c)
692
- // - b matches b
693
- // - doublestar
694
- // - matchOne(x/y/z/c, c) -> no
695
- // - matchOne(y/z/c, c) -> no
696
- // - matchOne(z/c, c) -> no
697
- // - matchOne(c, c) yes, hit
698
- var fr = fi;
699
- var pr = pi + 1;
700
- if (pr === pl) {
701
- this.debug('** at the end');
702
- // a ** at the end will just swallow the rest.
703
- // We have found a match.
704
- // however, it will not swallow /.x, unless
705
- // options.dot is set.
706
- // . and .. are *never* matched by **, for explosively
707
- // exponential reasons.
708
- for (; fi < fl; fi++) {
709
- if (file[fi] === '.' ||
710
- file[fi] === '..' ||
711
- (!options.dot && file[fi].charAt(0) === '.'))
712
- return false;
713
- }
714
- return true;
715
- }
716
- // ok, let's see if we can swallow whatever we can.
717
- while (fr < fl) {
718
- var swallowee = file[fr];
719
- this.debug('\nglobstar while', file, fr, pattern, pr, swallowee);
720
- // XXX remove this slice. Just pass the start index.
721
- if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
722
- this.debug('globstar found match!', fr, fl, swallowee);
723
- // found a match.
724
- return true;
725
- }
726
- else {
727
- // can't swallow "." or ".." ever.
728
- // can only swallow ".foo" when explicitly asked.
729
- if (swallowee === '.' ||
730
- swallowee === '..' ||
731
- (!options.dot && swallowee.charAt(0) === '.')) {
732
- this.debug('dot detected!', file, fr, pattern, pr);
733
- break;
734
- }
735
- // ** swallows a segment, and continue.
736
- this.debug('globstar swallow a segment, and continue');
737
- fr++;
738
- }
739
- }
740
- // no match was found.
741
- // However, in partial mode, we can't say this is necessarily over.
742
- /* c8 ignore start */
743
- if (partial) {
744
- // ran out of file
745
- this.debug('\n>>> no match, partial?', file, fr, pattern, pr);
746
- if (fr === fl) {
747
- return true;
748
- }
749
- }
750
- /* c8 ignore stop */
751
- return false;
752
- }
753
839
  // something other than **
754
840
  // non-magic patterns just have to match exactly
755
841
  // patterns with magic have been turned into regexps.