@@ -10,7 +10,7 @@ const isExtglobType = (c) => types.has(c);
10
10
// entire string, or just a single path portion, to prevent dots
11
11
// and/or traversal patterns, when needed.
12
12
// Exts don't need the ^ or / bit, because the root binds that already.
13
- const startNoTraversal = '(?!\\.\\.?(?:$|/))' ;
13
+ const startNoTraversal = '(?!(?:^|/) \\.\\.?(?:$|/))' ;
14
14
const startNoDot = '(?!\\.)' ;
15
15
// characters that indicate a start of pattern needs the "no dots" bit,
16
16
// because a dot *might* be matched. ( is not in the list, because in
@@ -407,7 +407,8 @@ class AST {
407
407
// - Since the start for a join is eg /(?!\.) and the start for a part
408
408
// is ^(?!\.), we can just prepend (?!\.) to the pattern (either root
409
409
// or start or whatever) and prepend ^ or / at the Regexp construction.
410
- toRegExpSource ( ) {
410
+ toRegExpSource ( allowDot ) {
411
+ const dot = allowDot ?? ! ! this . #options. dot ;
411
412
if ( this . #root === this )
412
413
this . #fillNegs( ) ;
413
414
if ( ! this . type ) {
@@ -416,7 +417,7 @@ class AST {
416
417
. map ( p => {
417
418
const [ re , _ , hasMagic , uflag ] = typeof p === 'string'
418
419
? AST . #parseGlob( p , this . #hasMagic, noEmpty )
419
- : p . toRegExpSource ( ) ;
420
+ : p . toRegExpSource ( allowDot ) ;
420
421
this . #hasMagic = this . #hasMagic || hasMagic ;
421
422
this . #uflag = this . #uflag || uflag ;
422
423
return re ;
@@ -436,14 +437,14 @@ class AST {
436
437
// and prevent that.
437
438
const needNoTrav =
438
439
// dots are allowed, and the pattern starts with [ or .
439
- ( this . #options . dot && aps . has ( src . charAt ( 0 ) ) ) ||
440
+ ( dot && aps . has ( src . charAt ( 0 ) ) ) ||
440
441
// the pattern starts with \., and then [ or .
441
442
( src . startsWith ( '\\.' ) && aps . has ( src . charAt ( 2 ) ) ) ||
442
443
// the pattern starts with \.\., and then [ or .
443
444
( src . startsWith ( '\\.\\.' ) && aps . has ( src . charAt ( 4 ) ) ) ;
444
445
// no need to prevent dots if it can't match a dot, or if a
445
446
// sub-pattern will be preventing it anyway.
446
- const needNoDot = ! this . #options . dot && aps . has ( src . charAt ( 0 ) ) ;
447
+ const needNoDot = ! dot && ! allowDot && aps . has ( src . charAt ( 0 ) ) ;
447
448
start = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : '' ;
448
449
}
449
450
}
@@ -463,23 +464,13 @@ class AST {
463
464
this . #uflag,
464
465
] ;
465
466
}
467
+ // We need to calculate the body *twice* if it's a repeat pattern
468
+ // at the start, once in nodot mode, then again in dot mode, so a
469
+ // pattern like *(?) can match 'x.y'
470
+ const repeated = this . type === '*' || this . type === '+' ;
466
471
// some kind of extglob
467
472
const start = this . type === '!' ? '(?:(?!(?:' : '(?:' ;
468
- const body = this . #parts
469
- . map ( p => {
470
- // extglob ASTs should only contain parent ASTs
471
- /* c8 ignore start */
472
- if ( typeof p === 'string' ) {
473
- throw new Error ( 'string type in extglob ast??' ) ;
474
- }
475
- /* c8 ignore stop */
476
- // can ignore hasMagic, because extglobs are already always magic
477
- const [ re , _ , _hasMagic , uflag ] = p . toRegExpSource ( ) ;
478
- this . #uflag = this . #uflag || uflag ;
479
- return re ;
480
- } )
481
- . filter ( p => ! ( this . isStart ( ) && this . isEnd ( ) ) || ! ! p )
482
- . join ( '|' ) ;
473
+ let body = this . #partsToRegExp( dot ) ;
483
474
if ( this . isStart ( ) && this . isEnd ( ) && ! body && this . type !== '!' ) {
484
475
// invalid extglob, has to at least be *something* present, if it's
485
476
// the entire path portion.
@@ -489,22 +480,37 @@ class AST {
489
480
this . #hasMagic = undefined ;
490
481
return [ s , ( 0 , unescape_js_1 . unescape ) ( this . toString ( ) ) , false , false ] ;
491
482
}
483
+ // XXX abstract out this map method
484
+ let bodyDotAllowed = ! repeated || allowDot || dot || ! startNoDot
485
+ ? ''
486
+ : this . #partsToRegExp( true ) ;
487
+ if ( bodyDotAllowed === body ) {
488
+ bodyDotAllowed = '' ;
489
+ }
490
+ if ( bodyDotAllowed ) {
491
+ body = `(?:${ body } )(?:${ bodyDotAllowed } )*?` ;
492
+ }
492
493
// an empty !() is exactly equivalent to a starNoEmpty
493
494
let final = '' ;
494
495
if ( this . type === '!' && this . #emptyExt) {
495
- final =
496
- ( this . isStart ( ) && ! this . #options. dot ? startNoDot : '' ) + starNoEmpty ;
496
+ final = ( this . isStart ( ) && ! dot ? startNoDot : '' ) + starNoEmpty ;
497
497
}
498
498
else {
499
499
const close = this . type === '!'
500
500
? // !() must match something,but !(x) can match ''
501
501
'))' +
502
- ( this . isStart ( ) && ! this . #options . dot ? startNoDot : '' ) +
502
+ ( this . isStart ( ) && ! dot && ! allowDot ? startNoDot : '' ) +
503
503
star +
504
504
')'
505
505
: this . type === '@'
506
506
? ')'
507
- : `)${ this . type } ` ;
507
+ : this . type === '?'
508
+ ? ')?'
509
+ : this . type === '+' && bodyDotAllowed
510
+ ? ')'
511
+ : this . type === '*' && bodyDotAllowed
512
+ ? `)?`
513
+ : `)${ this . type } ` ;
508
514
final = start + body + close ;
509
515
}
510
516
return [
@@ -514,6 +520,23 @@ class AST {
514
520
this . #uflag,
515
521
] ;
516
522
}
523
+ #partsToRegExp( dot ) {
524
+ return this . #parts
525
+ . map ( p => {
526
+ // extglob ASTs should only contain parent ASTs
527
+ /* c8 ignore start */
528
+ if ( typeof p === 'string' ) {
529
+ throw new Error ( 'string type in extglob ast??' ) ;
530
+ }
531
+ /* c8 ignore stop */
532
+ // can ignore hasMagic, because extglobs are already always magic
533
+ const [ re , _ , _hasMagic , uflag ] = p . toRegExpSource ( dot ) ;
534
+ this . #uflag = this . #uflag || uflag ;
535
+ return re ;
536
+ } )
537
+ . filter ( p => ! ( this . isStart ( ) && this . isEnd ( ) ) || ! ! p )
538
+ . join ( '|' ) ;
539
+ }
517
540
static #parseGlob( glob , hasMagic , noEmpty = false ) {
518
541
let escaping = false ;
519
542
let re = '' ;
0 commit comments