@@ -1726,7 +1726,7 @@ function expand(str, isTop) {
17261726 var isOptions = m.body.indexOf(',') >= 0;
17271727 if (!isSequence && !isOptions) {
17281728 // {a},b}
1729- if (m.post.match(/,.*\}/)) {
1729+ if (m.post.match(/,(?!,) .*\}/)) {
17301730 str = m.pre + '{' + m.body + escClose + m.post;
17311731 return expand(str);
17321732 }
@@ -3502,6 +3502,8 @@ function Minimatch (pattern, options) {
35023502 }
35033503
35043504 this.options = options
3505+ this.maxGlobstarRecursion = options.maxGlobstarRecursion !== undefined
3506+ ? options.maxGlobstarRecursion : 200
35053507 this.set = []
35063508 this.pattern = pattern
35073509 this.regexp = null
@@ -3750,6 +3752,9 @@ function parse (pattern, isSub) {
37503752 continue
37513753 }
37523754
3755+ // coalesce consecutive non-globstar * characters
3756+ if (c === '*' && stateChar === '*') continue
3757+
37533758 // if we already have a stateChar, then it means
37543759 // that there was something like ** or +? in there.
37553760 // Handle the stateChar, then proceed with this one.
@@ -4144,109 +4149,173 @@ Minimatch.prototype.match = function match (f, partial) {
41444149// out of pattern, then that's fine, as long as all
41454150// the parts match.
41464151Minimatch.prototype.matchOne = function (file, pattern, partial) {
4147- var options = this.options
4152+ if (pattern.indexOf(GLOBSTAR) !== -1) {
4153+ return this._matchGlobstar(file, pattern, partial, 0, 0)
4154+ }
4155+ return this._matchOne(file, pattern, partial, 0, 0)
4156+ }
41484157
4149- this.debug('matchOne',
4150- { 'this': this, file: file, pattern: pattern })
4158+ Minimatch.prototype._matchGlobstar = function (file, pattern, partial, fileIndex, patternIndex) {
4159+ var i
41514160
4152- this.debug('matchOne', file.length, pattern.length)
4161+ // find first globstar from patternIndex
4162+ var firstgs = -1
4163+ for (i = patternIndex; i < pattern.length; i++) {
4164+ if (pattern[i] === GLOBSTAR) { firstgs = i; break }
4165+ }
41534166
4154- for (var fi = 0,
4155- pi = 0,
4156- fl = file.length,
4157- pl = pattern.length
4158- ; (fi < fl) && (pi < pl)
4159- ; fi++, pi++) {
4160- this.debug('matchOne loop')
4161- var p = pattern[pi]
4162- var f = file[fi]
4167+ // find last globstar
4168+ var lastgs = -1
4169+ for (i = pattern.length - 1; i >= 0; i--) {
4170+ if (pattern[i] === GLOBSTAR) { lastgs = i; break }
4171+ }
41634172
4164- this.debug(pattern, p, f)
4173+ var head = pattern.slice(patternIndex, firstgs)
4174+ var body = partial ? pattern.slice(firstgs + 1) : pattern.slice(firstgs + 1, lastgs)
4175+ var tail = partial ? [] : pattern.slice(lastgs + 1)
41654176
4166- // should be impossible.
4167- // some invalid regexp stuff in the set.
4168- /* istanbul ignore if */
4169- if (p === false) return false
4170-
4171- if (p === GLOBSTAR) {
4172- this.debug('GLOBSTAR', [pattern, p, f])
4173-
4174- // "**"
4175- // a/**/b/**/c would match the following:
4176- // a/b/x/y/z/c
4177- // a/x/y/z/b/c
4178- // a/b/x/b/x/c
4179- // a/b/c
4180- // To do this, take the rest of the pattern after
4181- // the **, and see if it would match the file remainder.
4182- // If so, return success.
4183- // If not, the ** "swallows" a segment, and try again.
4184- // This is recursively awful.
4185- //
4186- // a/**/b/**/c matching a/b/x/y/z/c
4187- // - a matches a
4188- // - doublestar
4189- // - matchOne(b/x/y/z/c, b/**/c)
4190- // - b matches b
4191- // - doublestar
4192- // - matchOne(x/y/z/c, c) -> no
4193- // - matchOne(y/z/c, c) -> no
4194- // - matchOne(z/c, c) -> no
4195- // - matchOne(c, c) yes, hit
4196- var fr = fi
4197- var pr = pi + 1
4198- if (pr === pl) {
4199- this.debug('** at the end')
4200- // a ** at the end will just swallow the rest.
4201- // We have found a match.
4202- // however, it will not swallow /.x, unless
4203- // options.dot is set.
4204- // . and .. are *never* matched by **, for explosively
4205- // exponential reasons.
4206- for (; fi < fl; fi++) {
4207- if (file[fi] === '.' || file[fi] === '..' ||
4208- (!options.dot && file[fi].charAt(0) === '.')) return false
4209- }
4210- return true
4177+ // check the head
4178+ if (head.length) {
4179+ var fileHead = file.slice(fileIndex, fileIndex + head.length)
4180+ if (!this._matchOne(fileHead, head, partial, 0, 0)) {
4181+ return false
4182+ }
4183+ fileIndex += head.length
4184+ }
4185+
4186+ // check the tail
4187+ var fileTailMatch = 0
4188+ if (tail.length) {
4189+ if (tail.length + fileIndex > file.length) return false
4190+
4191+ var tailStart = file.length - tail.length
4192+ if (this._matchOne(file, tail, partial, tailStart, 0)) {
4193+ fileTailMatch = tail.length
4194+ } else {
4195+ // affordance for stuff like a/**/* matching a/b/
4196+ if (file[file.length - 1] !== '' ||
4197+ fileIndex + tail.length === file.length) {
4198+ return false
4199+ }
4200+ tailStart--
4201+ if (!this._matchOne(file, tail, partial, tailStart, 0)) {
4202+ return false
4203+ }
4204+ fileTailMatch = tail.length + 1
4205+ }
4206+ }
4207+
4208+ // if body is empty (single ** between head and tail)
4209+ if (!body.length) {
4210+ var sawSome = !!fileTailMatch
4211+ for (i = fileIndex; i < file.length - fileTailMatch; i++) {
4212+ var f = String(file[i])
4213+ sawSome = true
4214+ if (f === '.' || f === '..' ||
4215+ (!this.options.dot && f.charAt(0) === '.')) {
4216+ return false
42114217 }
4218+ }
4219+ return partial || sawSome
4220+ }
42124221
4213- // ok, let's see if we can swallow whatever we can.
4214- while (fr < fl) {
4215- var swallowee = file[fr]
4222+ // split body into segments at each GLOBSTAR
4223+ var bodySegments = [[[], 0]]
4224+ var currentBody = bodySegments[0]
4225+ var nonGsParts = 0
4226+ var nonGsPartsSums = [0]
4227+ for (var bi = 0; bi < body.length; bi++) {
4228+ var b = body[bi]
4229+ if (b === GLOBSTAR) {
4230+ nonGsPartsSums.push(nonGsParts)
4231+ currentBody = [[], 0]
4232+ bodySegments.push(currentBody)
4233+ } else {
4234+ currentBody[0].push(b)
4235+ nonGsParts++
4236+ }
4237+ }
42164238
4217- this.debug('\nglobstar while', file, fr, pattern, pr, swallowee)
4239+ var idx = bodySegments.length - 1
4240+ var fileLength = file.length - fileTailMatch
4241+ for (var si = 0; si < bodySegments.length; si++) {
4242+ bodySegments[si][1] = fileLength -
4243+ (nonGsPartsSums[idx--] + bodySegments[si][0].length)
4244+ }
42184245
4219- // XXX remove this slice. Just pass the start index.
4220- if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
4221- this.debug('globstar found match!', fr, fl, swallowee)
4222- // found a match.
4223- return true
4224- } else {
4225- // can't swallow "." or ".." ever.
4226- // can only swallow ".foo" when explicitly asked.
4227- if (swallowee === '.' || swallowee === '..' ||
4228- (!options.dot && swallowee.charAt(0) === '.')) {
4229- this.debug('dot detected!', file, fr, pattern, pr)
4230- break
4231- }
4246+ return !!this._matchGlobStarBodySections(
4247+ file, bodySegments, fileIndex, 0, partial, 0, !!fileTailMatch
4248+ )
4249+ }
42324250
4233- // ** swallows a segment, and continue.
4234- this.debug('globstar swallow a segment, and continue')
4235- fr++
4236- }
4251+ // return false for "nope, not matching"
4252+ // return null for "not matching, cannot keep trying"
4253+ Minimatch.prototype._matchGlobStarBodySections = function (
4254+ file, bodySegments, fileIndex, bodyIndex, partial, globStarDepth, sawTail
4255+ ) {
4256+ var bs = bodySegments[bodyIndex]
4257+ if (!bs) {
4258+ // just make sure there are no bad dots
4259+ for (var i = fileIndex; i < file.length; i++) {
4260+ sawTail = true
4261+ var f = file[i]
4262+ if (f === '.' || f === '..' ||
4263+ (!this.options.dot && f.charAt(0) === '.')) {
4264+ return false
42374265 }
4266+ }
4267+ return sawTail
4268+ }
42384269
4239- // no match was found.
4240- // However, in partial mode, we can't say this is necessarily over.
4241- // If there's more *pattern* left, then
4242- /* istanbul ignore if */
4243- if (partial) {
4244- // ran out of file
4245- this.debug('\n>>> no match, partial?', file, fr, pattern, pr)
4246- if (fr === fl) return true
4270+ var body = bs[0]
4271+ var after = bs[1]
4272+ while (fileIndex <= after) {
4273+ var m = this._matchOne(
4274+ file.slice(0, fileIndex + body.length),
4275+ body,
4276+ partial,
4277+ fileIndex,
4278+ 0
4279+ )
4280+ // if limit exceeded, no match. intentional false negative,
4281+ // acceptable break in correctness for security.
4282+ if (m && globStarDepth < this.maxGlobstarRecursion) {
4283+ var sub = this._matchGlobStarBodySections(
4284+ file, bodySegments,
4285+ fileIndex + body.length, bodyIndex + 1,
4286+ partial, globStarDepth + 1, sawTail
4287+ )
4288+ if (sub !== false) {
4289+ return sub
42474290 }
4291+ }
4292+ var f = file[fileIndex]
4293+ if (f === '.' || f === '..' ||
4294+ (!this.options.dot && f.charAt(0) === '.')) {
42484295 return false
42494296 }
4297+ fileIndex++
4298+ }
4299+ return partial || null
4300+ }
4301+
4302+ Minimatch.prototype._matchOne = function (file, pattern, partial, fileIndex, patternIndex) {
4303+ var fi, pi, fl, pl
4304+ for (
4305+ fi = fileIndex, pi = patternIndex, fl = file.length, pl = pattern.length
4306+ ; (fi < fl) && (pi < pl)
4307+ ; fi++, pi++
4308+ ) {
4309+ this.debug('matchOne loop')
4310+ var p = pattern[pi]
4311+ var f = file[fi]
4312+
4313+ this.debug(pattern, p, f)
4314+
4315+ // should be impossible.
4316+ // some invalid regexp stuff in the set.
4317+ /* istanbul ignore if */
4318+ if (p === false || p === GLOBSTAR) return false
42504319
42514320 // something other than **
42524321 // non-magic patterns just have to match exactly
@@ -4263,17 +4332,6 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) {
42634332 if (!hit) return false
42644333 }
42654334
4266- // Note: ending in / means that we'll get a final ""
4267- // at the end of the pattern. This can only match a
4268- // corresponding "" at the end of the file.
4269- // If the file ends in /, then it can only match a
4270- // a pattern that ends in /, unless the pattern just
4271- // doesn't have any more for it. But, a/b/ should *not*
4272- // match "a/b/*", even though "" matches against the
4273- // [^/]*? pattern, except in partial mode, where it might
4274- // simply not be reached yet.
4275- // However, a/b/ should still satisfy a/*
4276-
42774335 // now either we fell off the end of the pattern, or we're done.
42784336 if (fi === fl && pi === pl) {
42794337 // ran out of pattern and filename at the same time.
0 commit comments