@@ -25686,7 +25686,7 @@ const isExtglobType = (c) => types.has(c);
2568625686// entire string, or just a single path portion, to prevent dots
2568725687// and/or traversal patterns, when needed.
2568825688// Exts don't need the ^ or / bit, because the root binds that already.
25689- const startNoTraversal = '(?!\\.\\.?(?:$|/))';
25689+ const startNoTraversal = '(?!(?:^|/) \\.\\.?(?:$|/))';
2569025690const startNoDot = '(?!\\.)';
2569125691// characters that indicate a start of pattern needs the "no dots" bit,
2569225692// because a dot *might* be matched. ( is not in the list, because in
@@ -26083,7 +26083,8 @@ class AST {
2608326083 // - Since the start for a join is eg /(?!\.) and the start for a part
2608426084 // is ^(?!\.), we can just prepend (?!\.) to the pattern (either root
2608526085 // or start or whatever) and prepend ^ or / at the Regexp construction.
26086- toRegExpSource() {
26086+ toRegExpSource(allowDot) {
26087+ const dot = allowDot ?? !!this.#options.dot;
2608726088 if (this.#root === this)
2608826089 this.#fillNegs();
2608926090 if (!this.type) {
@@ -26092,7 +26093,7 @@ class AST {
2609226093 .map(p => {
2609326094 const [re, _, hasMagic, uflag] = typeof p === 'string'
2609426095 ? AST.#parseGlob(p, this.#hasMagic, noEmpty)
26095- : p.toRegExpSource();
26096+ : p.toRegExpSource(allowDot );
2609626097 this.#hasMagic = this.#hasMagic || hasMagic;
2609726098 this.#uflag = this.#uflag || uflag;
2609826099 return re;
@@ -26112,14 +26113,14 @@ class AST {
2611226113 // and prevent that.
2611326114 const needNoTrav =
2611426115 // dots are allowed, and the pattern starts with [ or .
26115- (this.#options. dot && aps.has(src.charAt(0))) ||
26116+ (dot && aps.has(src.charAt(0))) ||
2611626117 // the pattern starts with \., and then [ or .
2611726118 (src.startsWith('\\.') && aps.has(src.charAt(2))) ||
2611826119 // the pattern starts with \.\., and then [ or .
2611926120 (src.startsWith('\\.\\.') && aps.has(src.charAt(4)));
2612026121 // no need to prevent dots if it can't match a dot, or if a
2612126122 // sub-pattern will be preventing it anyway.
26122- const needNoDot = !this.#options. dot && aps.has(src.charAt(0));
26123+ const needNoDot = !dot && !allowDot && aps.has(src.charAt(0));
2612326124 start = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : '';
2612426125 }
2612526126 }
@@ -26139,23 +26140,13 @@ class AST {
2613926140 this.#uflag,
2614026141 ];
2614126142 }
26143+ // We need to calculate the body *twice* if it's a repeat pattern
26144+ // at the start, once in nodot mode, then again in dot mode, so a
26145+ // pattern like *(?) can match 'x.y'
26146+ const repeated = this.type === '*' || this.type === '+';
2614226147 // some kind of extglob
2614326148 const start = this.type === '!' ? '(?:(?!(?:' : '(?:';
26144- const body = this.#parts
26145- .map(p => {
26146- // extglob ASTs should only contain parent ASTs
26147- /* c8 ignore start */
26148- if (typeof p === 'string') {
26149- throw new Error('string type in extglob ast??');
26150- }
26151- /* c8 ignore stop */
26152- // can ignore hasMagic, because extglobs are already always magic
26153- const [re, _, _hasMagic, uflag] = p.toRegExpSource();
26154- this.#uflag = this.#uflag || uflag;
26155- return re;
26156- })
26157- .filter(p => !(this.isStart() && this.isEnd()) || !!p)
26158- .join('|');
26149+ let body = this.#partsToRegExp(dot);
2615926150 if (this.isStart() && this.isEnd() && !body && this.type !== '!') {
2616026151 // invalid extglob, has to at least be *something* present, if it's
2616126152 // the entire path portion.
@@ -26165,22 +26156,37 @@ class AST {
2616526156 this.#hasMagic = undefined;
2616626157 return [s, (0, unescape_js_1.unescape)(this.toString()), false, false];
2616726158 }
26159+ // XXX abstract out this map method
26160+ let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot
26161+ ? ''
26162+ : this.#partsToRegExp(true);
26163+ if (bodyDotAllowed === body) {
26164+ bodyDotAllowed = '';
26165+ }
26166+ if (bodyDotAllowed) {
26167+ body = `(?:${body})(?:${bodyDotAllowed})*?`;
26168+ }
2616826169 // an empty !() is exactly equivalent to a starNoEmpty
2616926170 let final = '';
2617026171 if (this.type === '!' && this.#emptyExt) {
26171- final =
26172- (this.isStart() && !this.#options.dot ? startNoDot : '') + starNoEmpty;
26172+ final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty;
2617326173 }
2617426174 else {
2617526175 const close = this.type === '!'
2617626176 ? // !() must match something,but !(x) can match ''
2617726177 '))' +
26178- (this.isStart() && !this.#options. dot ? startNoDot : '') +
26178+ (this.isStart() && !dot && !allowDot ? startNoDot : '') +
2617926179 star +
2618026180 ')'
2618126181 : this.type === '@'
2618226182 ? ')'
26183- : `)${this.type}`;
26183+ : this.type === '?'
26184+ ? ')?'
26185+ : this.type === '+' && bodyDotAllowed
26186+ ? ')'
26187+ : this.type === '*' && bodyDotAllowed
26188+ ? `)?`
26189+ : `)${this.type}`;
2618426190 final = start + body + close;
2618526191 }
2618626192 return [
@@ -26190,6 +26196,23 @@ class AST {
2619026196 this.#uflag,
2619126197 ];
2619226198 }
26199+ #partsToRegExp(dot) {
26200+ return this.#parts
26201+ .map(p => {
26202+ // extglob ASTs should only contain parent ASTs
26203+ /* c8 ignore start */
26204+ if (typeof p === 'string') {
26205+ throw new Error('string type in extglob ast??');
26206+ }
26207+ /* c8 ignore stop */
26208+ // can ignore hasMagic, because extglobs are already always magic
26209+ const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot);
26210+ this.#uflag = this.#uflag || uflag;
26211+ return re;
26212+ })
26213+ .filter(p => !(this.isStart() && this.isEnd()) || !!p)
26214+ .join('|');
26215+ }
2619326216 static #parseGlob(glob, hasMagic, noEmpty = false) {
2619426217 let escaping = false;
2619526218 let re = '';
0 commit comments