1
1
// parse a single path portion
2
2
3
- import { MinimatchOptions , MMRegExp } from './index.js'
4
3
import { parseClass } from './brace-expressions.js'
4
+ import { MinimatchOptions , MMRegExp } from './index.js'
5
5
import { unescape } from './unescape.js'
6
6
7
7
// classes [] are handled by the parseClass method
@@ -50,7 +50,7 @@ const isExtglobType = (c: string): c is ExtglobType =>
50
50
// entire string, or just a single path portion, to prevent dots
51
51
// and/or traversal patterns, when needed.
52
52
// Exts don't need the ^ or / bit, because the root binds that already.
53
- const startNoTraversal = '(?!\\.\\.?(?:$|/))'
53
+ const startNoTraversal = '(?!(?:^|/) \\.\\.?(?:$|/))'
54
54
const startNoDot = '(?!\\.)'
55
55
56
56
// characters that indicate a start of pattern needs the "no dots" bit,
@@ -467,12 +467,10 @@ export class AST {
467
467
// - Since the start for a join is eg /(?!\.) and the start for a part
468
468
// is ^(?!\.), we can just prepend (?!\.) to the pattern (either root
469
469
// or start or whatever) and prepend ^ or / at the Regexp construction.
470
- toRegExpSource ( ) : [
471
- re : string ,
472
- body : string ,
473
- hasMagic : boolean ,
474
- uflag : boolean
475
- ] {
470
+ toRegExpSource (
471
+ allowDot ?: boolean
472
+ ) : [ re : string , body : string , hasMagic : boolean , uflag : boolean ] {
473
+ const dot = allowDot ?? ! ! this . #options. dot
476
474
if ( this . #root === this ) this . #fillNegs( )
477
475
if ( ! this . type ) {
478
476
const noEmpty = this . isStart ( ) && this . isEnd ( )
@@ -481,7 +479,7 @@ export class AST {
481
479
const [ re , _ , hasMagic , uflag ] =
482
480
typeof p === 'string'
483
481
? AST . #parseGlob( p , this . #hasMagic, noEmpty )
484
- : p . toRegExpSource ( )
482
+ : p . toRegExpSource ( allowDot )
485
483
this . #hasMagic = this . #hasMagic || hasMagic
486
484
this . #uflag = this . #uflag || uflag
487
485
return re
@@ -504,14 +502,14 @@ export class AST {
504
502
// and prevent that.
505
503
const needNoTrav =
506
504
// dots are allowed, and the pattern starts with [ or .
507
- ( this . #options . dot && aps . has ( src . charAt ( 0 ) ) ) ||
505
+ ( dot && aps . has ( src . charAt ( 0 ) ) ) ||
508
506
// the pattern starts with \., and then [ or .
509
507
( src . startsWith ( '\\.' ) && aps . has ( src . charAt ( 2 ) ) ) ||
510
508
// the pattern starts with \.\., and then [ or .
511
509
( src . startsWith ( '\\.\\.' ) && aps . has ( src . charAt ( 4 ) ) )
512
510
// no need to prevent dots if it can't match a dot, or if a
513
511
// sub-pattern will be preventing it anyway.
514
- const needNoDot = ! this . #options . dot && aps . has ( src . charAt ( 0 ) )
512
+ const needNoDot = ! dot && ! allowDot && aps . has ( src . charAt ( 0 ) )
515
513
516
514
start = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : ''
517
515
}
@@ -536,23 +534,15 @@ export class AST {
536
534
]
537
535
}
538
536
537
+ // We need to calculate the body *twice* if it's a repeat pattern
538
+ // at the start, once in nodot mode, then again in dot mode, so a
539
+ // pattern like *(?) can match 'x.y'
540
+
541
+ const repeated = this . type === '*' || this . type === '+'
539
542
// some kind of extglob
540
543
const start = this . type === '!' ? '(?:(?!(?:' : '(?:'
541
- const body = this . #parts
542
- . map ( p => {
543
- // extglob ASTs should only contain parent ASTs
544
- /* c8 ignore start */
545
- if ( typeof p === 'string' ) {
546
- throw new Error ( 'string type in extglob ast??' )
547
- }
548
- /* c8 ignore stop */
549
- // can ignore hasMagic, because extglobs are already always magic
550
- const [ re , _ , _hasMagic , uflag ] = p . toRegExpSource ( )
551
- this . #uflag = this . #uflag || uflag
552
- return re
553
- } )
554
- . filter ( p => ! ( this . isStart ( ) && this . isEnd ( ) ) || ! ! p )
555
- . join ( '|' )
544
+ let body = this . #partsToRegExp( dot )
545
+
556
546
if ( this . isStart ( ) && this . isEnd ( ) && ! body && this . type !== '!' ) {
557
547
// invalid extglob, has to at least be *something* present, if it's
558
548
// the entire path portion.
@@ -562,21 +552,39 @@ export class AST {
562
552
this . #hasMagic = undefined
563
553
return [ s , unescape ( this . toString ( ) ) , false , false ]
564
554
}
555
+
556
+ // XXX abstract out this map method
557
+ let bodyDotAllowed =
558
+ ! repeated || allowDot || dot || ! startNoDot
559
+ ? ''
560
+ : this . #partsToRegExp( true )
561
+ if ( bodyDotAllowed === body ) {
562
+ bodyDotAllowed = ''
563
+ }
564
+ if ( bodyDotAllowed ) {
565
+ body = `(?:${ body } )(?:${ bodyDotAllowed } )*?`
566
+ }
567
+
565
568
// an empty !() is exactly equivalent to a starNoEmpty
566
569
let final = ''
567
570
if ( this . type === '!' && this . #emptyExt) {
568
- final =
569
- ( this . isStart ( ) && ! this . #options. dot ? startNoDot : '' ) + starNoEmpty
571
+ final = ( this . isStart ( ) && ! dot ? startNoDot : '' ) + starNoEmpty
570
572
} else {
571
573
const close =
572
574
this . type === '!'
573
575
? // !() must match something,but !(x) can match ''
574
576
'))' +
575
- ( this . isStart ( ) && ! this . #options . dot ? startNoDot : '' ) +
577
+ ( this . isStart ( ) && ! dot && ! allowDot ? startNoDot : '' ) +
576
578
star +
577
579
')'
578
580
: this . type === '@'
579
581
? ')'
582
+ : this . type === '?'
583
+ ? ')?'
584
+ : this . type === '+' && bodyDotAllowed
585
+ ? ')'
586
+ : this . type === '*' && bodyDotAllowed
587
+ ? `)?`
580
588
: `)${ this . type } `
581
589
final = start + body + close
582
590
}
@@ -588,6 +596,24 @@ export class AST {
588
596
]
589
597
}
590
598
599
+ #partsToRegExp( dot : boolean ) {
600
+ return this . #parts
601
+ . map ( p => {
602
+ // extglob ASTs should only contain parent ASTs
603
+ /* c8 ignore start */
604
+ if ( typeof p === 'string' ) {
605
+ throw new Error ( 'string type in extglob ast??' )
606
+ }
607
+ /* c8 ignore stop */
608
+ // can ignore hasMagic, because extglobs are already always magic
609
+ const [ re , _ , _hasMagic , uflag ] = p . toRegExpSource ( dot )
610
+ this . #uflag = this . #uflag || uflag
611
+ return re
612
+ } )
613
+ . filter ( p => ! ( this . isStart ( ) && this . isEnd ( ) ) || ! ! p )
614
+ . join ( '|' )
615
+ }
616
+
591
617
static #parseGlob(
592
618
glob : string ,
593
619
hasMagic : boolean | undefined ,
0 commit comments