@@ -157,7 +157,9 @@ minimatch.match = (list, pattern, options = {}) => {
157157
158158// replace stuff like \* with *
159159const globUnescape = s => s . replace ( / \\ ( .) / g, '$1' )
160+ const charUnescape = s => s . replace ( / \\ ( [ ^ - \] ] ) / g, '$1' )
160161const regExpEscape = s => s . replace ( / [ - [ \] { } ( ) * + ? . , \\ ^ $ | # \s ] / g, '\\$&' )
162+ const braExpEscape = s => s . replace ( / [ [ \] \\ ] / g, '\\$&' )
161163
162164class Minimatch {
163165 constructor ( pattern , options ) {
@@ -425,7 +427,7 @@ class Minimatch {
425427 if ( pattern === '' ) return ''
426428
427429 let re = ''
428- let hasMagic = ! ! options . nocase
430+ let hasMagic = false
429431 let escaping = false
430432 // ? => one single character
431433 const patternListStack = [ ]
@@ -438,11 +440,23 @@ class Minimatch {
438440 let pl
439441 let sp
440442 // . and .. never match anything that doesn't start with .,
441- // even when options.dot is set.
442- const patternStart = pattern . charAt ( 0 ) === '.' ? '' // anything
443- // not (start or / followed by . or .. followed by / or end)
444- : options . dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))'
445- : '(?!\\.)'
443+ // even when options.dot is set. However, if the pattern
444+ // starts with ., then traversal patterns can match.
445+ let dotTravAllowed = pattern . charAt ( 0 ) === '.'
446+ let dotFileAllowed = options . dot || dotTravAllowed
447+ const patternStart = ( ) =>
448+ dotTravAllowed
449+ ? ''
450+ : dotFileAllowed
451+ ? '(?!(?:^|\\/)\\.{1,2}(?:$|\\/))'
452+ : '(?!\\.)'
453+ const subPatternStart = ( p ) =>
454+ p . charAt ( 0 ) === '.'
455+ ? ''
456+ : options . dot
457+ ? '(?!(?:^|\\/)\\.{1,2}(?:$|\\/))'
458+ : '(?!\\.)'
459+
446460
447461 const clearStateChar = ( ) => {
448462 if ( stateChar ) {
@@ -492,6 +506,11 @@ class Minimatch {
492506 }
493507
494508 case '\\' :
509+ if ( inClass && pattern . charAt ( i + 1 ) === '-' ) {
510+ re += c
511+ continue
512+ }
513+
495514 clearStateChar ( )
496515 escaping = true
497516 continue
@@ -526,7 +545,7 @@ class Minimatch {
526545 if ( options . noext ) clearStateChar ( )
527546 continue
528547
529- case '(' :
548+ case '(' : {
530549 if ( inClass ) {
531550 re += '('
532551 continue
@@ -537,46 +556,64 @@ class Minimatch {
537556 continue
538557 }
539558
540- patternListStack . push ( {
559+ const plEntry = {
541560 type : stateChar ,
542561 start : i - 1 ,
543562 reStart : re . length ,
544563 open : plTypes [ stateChar ] . open ,
545- close : plTypes [ stateChar ] . close
546- } )
547- // negation is (?:(?!js)[^/]*)
548- re += stateChar === '!' ? '(?:(?!(?:' : '(?:'
564+ close : plTypes [ stateChar ] . close ,
565+ }
566+ this . debug ( this . pattern , '\t' , plEntry )
567+ patternListStack . push ( plEntry )
568+ // negation is (?:(?!(?:js)(?:<rest>))[^/]*)
569+ re += plEntry . open
570+ // next entry starts with a dot maybe?
571+ if ( plEntry . start === 0 && plEntry . type !== '!' ) {
572+ dotTravAllowed = true
573+ re += subPatternStart ( pattern . slice ( i + 1 ) )
574+ }
549575 this . debug ( 'plType %j %j' , stateChar , re )
550576 stateChar = false
551- continue
577+ continue
578+ }
552579
553- case ')' :
554- if ( inClass || ! patternListStack . length ) {
580+ case ')' : {
581+ const plEntry = patternListStack [ patternListStack . length - 1 ]
582+ if ( inClass || ! plEntry ) {
555583 re += '\\)'
556584 continue
557585 }
586+ patternListStack . pop ( )
558587
588+ // closing an extglob
559589 clearStateChar ( )
560590 hasMagic = true
561- pl = patternListStack . pop ( )
591+ pl = plEntry
562592 // negation is (?:(?!js)[^/]*)
563593 // The others are (?:<pattern>)<type>
564594 re += pl . close
565595 if ( pl . type === '!' ) {
566- negativeLists . push ( pl )
596+ negativeLists . push ( Object . assign ( pl , { reEnd : re . length } ) )
567597 }
568- pl . reEnd = re . length
569- continue
598+ continue
599+ }
570600
571- case '|' :
572- if ( inClass || ! patternListStack . length ) {
601+ case '|' : {
602+ const plEntry = patternListStack [ patternListStack . length - 1 ]
603+ if ( inClass || ! plEntry ) {
573604 re += '\\|'
574605 continue
575606 }
576607
577608 clearStateChar ( )
578609 re += '|'
579- continue
610+ // next subpattern can start with a dot?
611+ if ( plEntry . start === 0 && plEntry . type !== '!' ) {
612+ dotTravAllowed = true
613+ re += subPatternStart ( pattern . slice ( i + 1 ) )
614+ }
615+ continue
616+ }
580617
581618 // these are mostly the same in regexp and glob
582619 case '[' :
@@ -604,8 +641,6 @@ class Minimatch {
604641 continue
605642 }
606643
607- // handle the case where we left a class open.
608- // "[z-a]" is valid, equivalent to "\[z-a\]"
609644 // split where the last [ was, make sure we don't have
610645 // an invalid re. if so, re-walk the contents of the
611646 // would-be class to re-translate any characters that
@@ -615,20 +650,16 @@ class Minimatch {
615650 // to do safely. For now, this is safe and works.
616651 cs = pattern . substring ( classStart + 1 , i )
617652 try {
618- RegExp ( '[' + cs + ']' )
653+ RegExp ( '[' + braExpEscape ( charUnescape ( cs ) ) + ']' )
654+ // looks good, finish up the class.
655+ re += c
619656 } catch ( er ) {
620- // not a valid class!
621- sp = this . parse ( cs , SUBPARSE )
622- re = re . substring ( 0 , reClassStart ) + '\\[' + sp [ 0 ] + '\\]'
623- hasMagic = hasMagic || sp [ 1 ]
624- inClass = false
625- continue
657+ // out of order ranges in JS are errors, but in glob syntax,
658+ // they're just a range that matches nothing.
659+ re = re . substring ( 0 , reClassStart ) + '(?:$.)' // match nothing ever
626660 }
627-
628- // finish up the class.
629661 hasMagic = true
630662 inClass = false
631- re += c
632663 continue
633664
634665 default :
@@ -721,14 +752,16 @@ class Minimatch {
721752 // Handle nested stuff like *(*.js|!(*.json)), where open parens
722753 // mean that we should *not* include the ) in the bit that is considered
723754 // "after" the negated section.
724- const openParensBefore = nlBefore . split ( '(' ) . length - 1
755+ const closeParensBefore = nlBefore . split ( ')' ) . length
756+ const openParensBefore = nlBefore . split ( '(' ) . length - closeParensBefore
725757 let cleanAfter = nlAfter
726758 for ( let i = 0 ; i < openParensBefore ; i ++ ) {
727759 cleanAfter = cleanAfter . replace ( / \) [ + * ? ] ? / , '' )
728760 }
729761 nlAfter = cleanAfter
730762
731- const dollar = nlAfter === '' && isSub !== SUBPARSE ? '$' : ''
763+ const dollar = nlAfter === '' && isSub !== SUBPARSE ? '(?:$|\\/)' : ''
764+
732765 re = nlBefore + nlFirst + nlAfter + dollar + nlLast
733766 }
734767
@@ -740,14 +773,19 @@ class Minimatch {
740773 }
741774
742775 if ( addPatternStart ) {
743- re = patternStart + re
776+ re = patternStart ( ) + re
744777 }
745778
746779 // parsing just a piece of a larger pattern.
747780 if ( isSub === SUBPARSE ) {
748781 return [ re , hasMagic ]
749782 }
750783
784+ // if it's nocase, and the lcase/uppercase don't match, it's magic
785+ if ( options . nocase && ! hasMagic ) {
786+ hasMagic = pattern . toUpperCase ( ) !== pattern . toLowerCase ( )
787+ }
788+
751789 // skip the regexp for non-magical patterns
752790 // unescape anything in it, though, so that it'll be
753791 // an exact match against a file etc.
0 commit comments