[Pkg-javascript-commits] [node-minimatch] 01/14: New upstream version 3.0.3

Jérémy Lal kapouer at moszumanska.debian.org
Thu Nov 3 23:36:01 UTC 2016


This is an automated email from the git hooks/post-receive script.

kapouer pushed a commit to branch master
in repository node-minimatch.

commit edb757bdd5f27641f631c8df9148490a0cb58541
Author: Jérémy Lal <kapouer at melix.org>
Date:   Fri Nov 4 00:03:54 2016 +0100

    New upstream version 3.0.3
---
 .gitignore                             |   1 +
 .npmignore                             |   1 +
 .travis.yml                            |   7 +-
 LICENSE                                |  32 +-
 README.md                              |  13 +-
 benchmark.js                           |  15 +
 minimatch.js                           | 736 +++++++++++++--------------------
 package.json                           |  17 +-
 test/basic.js                          | 392 ++----------------
 test/brace-expand.js                   | 100 +++--
 test/caching.js                        |  14 -
 test/defaults.js                       | 299 ++------------
 test/extglob-ending-with-state-char.js |   2 +-
 test/extglob-unfinished.js             |  13 +
 test/patterns.js                       | 368 +++++++++++++++++
 test/redos.js                          |  28 ++
 test/tricky-negations.js               | 111 +++++
 17 files changed, 997 insertions(+), 1152 deletions(-)

diff --git a/.gitignore b/.gitignore
index 3c3629e..c9106a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 node_modules
+.nyc_output
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..b2a4ba5
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1 @@
+# nothing here
diff --git a/.travis.yml b/.travis.yml
index fca8ef0..9c1a7b6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,7 @@
+sudo: false
 language: node_js
 node_js:
-  - 0.10
-  - 0.11
+  - '0.10'
+  - '0.12'
+  - '4'
+  - '5'
diff --git a/LICENSE b/LICENSE
index 05a4010..19129e3 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,23 +1,15 @@
-Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
-All rights reserved.
+The ISC License
 
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
+Copyright (c) Isaac Z. Schlueter and Contributors
 
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
 
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.md b/README.md
index 5b3967e..ad72b81 100644
--- a/README.md
+++ b/README.md
@@ -2,13 +2,11 @@
 
 A minimal matching utility.
 
-[![Build Status](https://secure.travis-ci.org/isaacs/minimatch.png)](http://travis-ci.org/isaacs/minimatch)
+[![Build Status](https://secure.travis-ci.org/isaacs/minimatch.svg)](http://travis-ci.org/isaacs/minimatch)
 
 
 This is the matching library used internally by npm.
 
-Eventually, it will replace the C binding in node-glob.
-
 It works by converting glob expressions into JavaScript `RegExp`
 objects.
 
@@ -39,7 +37,7 @@ See:
 
 ## Minimatch Class
 
-Create a minimatch object by instanting the `minimatch.Minimatch` class.
+Create a minimatch object by instantiating the `minimatch.Minimatch` class.
 
 ```javascript
 var Minimatch = require("minimatch").Minimatch
@@ -84,13 +82,6 @@ var mm = new Minimatch(pattern, options)
 
 All other methods are internal, and will be called as necessary.
 
-## Functions
-
-The top-level exported function has a `cache` property, which is an LRU
-cache set to store 100 items.  So, calling these methods repeatedly
-with the same pattern and options will use the same Minimatch object,
-saving the cost of parsing it multiple times.
-
 ### minimatch(path, pattern, options)
 
 Main export.  Tests a path against the pattern using the options.
diff --git a/benchmark.js b/benchmark.js
new file mode 100644
index 0000000..c10a6ac
--- /dev/null
+++ b/benchmark.js
@@ -0,0 +1,15 @@
+var m = require('./minimatch.js')
+var pattern = '**/*.js'
+var expand = require('brace-expansion')
+var files = expand('x/y/z/{1..1000}.js')
+var start = process.hrtime()
+
+for (var i = 0; i < 1000; i++) {
+  for (var f = 0; f < files.length; f++) {
+    var res = m(pattern, files[f])
+  }
+  if (!(i % 10)) process.stdout.write('.')
+}
+console.log('done')
+var dur = process.hrtime(start)
+console.log('%s ms', dur[0] * 1e3 + dur[1] / 1e6)
diff --git a/minimatch.js b/minimatch.js
index 4761786..5b5f8cf 100644
--- a/minimatch.js
+++ b/minimatch.js
@@ -1,65 +1,44 @@
-;(function (require, exports, module, platform) {
+module.exports = minimatch
+minimatch.Minimatch = Minimatch
 
-if (module) module.exports = minimatch
-else exports.minimatch = minimatch
+var path = { sep: '/' }
+try {
+  path = require('path')
+} catch (er) {}
 
-if (!require) {
-  require = function (id) {
-    switch (id) {
-      case "sigmund": return function sigmund (obj) {
-        return JSON.stringify(obj)
-      }
-      case "path": return { basename: function (f) {
-        f = f.split(/[\/\\]/)
-        var e = f.pop()
-        if (!e) e = f.pop()
-        return e
-      }}
-      case "lru-cache": return function LRUCache () {
-        // not quite an LRU, but still space-limited.
-        var cache = {}
-        var cnt = 0
-        this.set = function (k, v) {
-          cnt ++
-          if (cnt >= 100) cache = {}
-          cache[k] = v
-        }
-        this.get = function (k) { return cache[k] }
-      }
-    }
-  }
-}
+var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {}
+var expand = require('brace-expansion')
 
-minimatch.Minimatch = Minimatch
-
-var LRU = require("lru-cache")
-  , cache = minimatch.cache = new LRU({max: 100})
-  , GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {}
-  , sigmund = require("sigmund")
+var plTypes = {
+  '!': { open: '(?:(?!(?:', close: '))[^/]*?)'},
+  '?': { open: '(?:', close: ')?' },
+  '+': { open: '(?:', close: ')+' },
+  '*': { open: '(?:', close: ')*' },
+  '@': { open: '(?:', close: ')' }
+}
 
-var path = require("path")
-  // any single thing other than /
-  // don't need to escape / when using new RegExp()
-  , qmark = "[^/]"
+// any single thing other than /
+// don't need to escape / when using new RegExp()
+var qmark = '[^/]'
 
-  // * => any number of characters
-  , star = qmark + "*?"
+// * => any number of characters
+var star = qmark + '*?'
 
-  // ** when dots are allowed.  Anything goes, except .. and .
-  // not (^ or / followed by one or two dots followed by $ or /),
-  // followed by anything, any number of times.
-  , twoStarDot = "(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?"
+// ** when dots are allowed.  Anything goes, except .. and .
+// not (^ or / followed by one or two dots followed by $ or /),
+// followed by anything, any number of times.
+var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?'
 
-  // not a ^ or / followed by a dot,
-  // followed by anything, any number of times.
-  , twoStarNoDot = "(?:(?!(?:\\\/|^)\\.).)*?"
+// not a ^ or / followed by a dot,
+// followed by anything, any number of times.
+var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?'
 
-  // characters that need to be escaped in RegExp.
-  , reSpecials = charSet("().*{}+?[]^$\\!")
+// characters that need to be escaped in RegExp.
+var reSpecials = charSet('().*{}+?[]^$\\!')
 
 // "abc" -> { a:true, b:true, c:true }
 function charSet (s) {
-  return s.split("").reduce(function (set, c) {
+  return s.split('').reduce(function (set, c) {
     set[c] = true
     return set
   }, {})
@@ -110,51 +89,41 @@ Minimatch.defaults = function (def) {
   return minimatch.defaults(def).Minimatch
 }
 
-
 function minimatch (p, pattern, options) {
-  if (typeof pattern !== "string") {
-    throw new TypeError("glob pattern string required")
+  if (typeof pattern !== 'string') {
+    throw new TypeError('glob pattern string required')
   }
 
   if (!options) options = {}
 
   // shortcut: comments match nothing.
-  if (!options.nocomment && pattern.charAt(0) === "#") {
+  if (!options.nocomment && pattern.charAt(0) === '#') {
     return false
   }
 
   // "" only matches ""
-  if (pattern.trim() === "") return p === ""
+  if (pattern.trim() === '') return p === ''
 
   return new Minimatch(pattern, options).match(p)
 }
 
 function Minimatch (pattern, options) {
   if (!(this instanceof Minimatch)) {
-    return new Minimatch(pattern, options, cache)
+    return new Minimatch(pattern, options)
   }
 
-  if (typeof pattern !== "string") {
-    throw new TypeError("glob pattern string required")
+  if (typeof pattern !== 'string') {
+    throw new TypeError('glob pattern string required')
   }
 
   if (!options) options = {}
   pattern = pattern.trim()
 
-  // windows: need to use /, not \
-  // On other platforms, \ is a valid (albeit bad) filename char.
-  if (platform === "win32") {
-    pattern = pattern.split("\\").join("/")
+  // windows support: need to use /, not \
+  if (path.sep !== '/') {
+    pattern = pattern.split(path.sep).join('/')
   }
 
-  // lru storage.
-  // these things aren't particularly big, but walking down the string
-  // and turning it into a regexp can get pretty costly.
-  var cacheKey = pattern + "\n" + sigmund(options)
-  var cached = minimatch.cache.get(cacheKey)
-  if (cached) return cached
-  minimatch.cache.set(cacheKey, this)
-
   this.options = options
   this.set = []
   this.pattern = pattern
@@ -167,7 +136,7 @@ function Minimatch (pattern, options) {
   this.make()
 }
 
-Minimatch.prototype.debug = function() {}
+Minimatch.prototype.debug = function () {}
 
 Minimatch.prototype.make = make
 function make () {
@@ -178,7 +147,7 @@ function make () {
   var options = this.options
 
   // empty patterns and comments match nothing.
-  if (!options.nocomment && pattern.charAt(0) === "#") {
+  if (!options.nocomment && pattern.charAt(0) === '#') {
     this.comment = true
     return
   }
@@ -217,7 +186,7 @@ function make () {
 
   // filter out everything that didn't compile properly.
   set = set.filter(function (s) {
-    return -1 === s.indexOf(false)
+    return s.indexOf(false) === -1
   })
 
   this.debug(this.pattern, set)
@@ -228,17 +197,17 @@ function make () {
 Minimatch.prototype.parseNegate = parseNegate
 function parseNegate () {
   var pattern = this.pattern
-    , negate = false
-    , options = this.options
-    , negateOffset = 0
+  var negate = false
+  var options = this.options
+  var negateOffset = 0
 
   if (options.nonegate) return
 
-  for ( var i = 0, l = pattern.length
-      ; i < l && pattern.charAt(i) === "!"
-      ; i ++) {
+  for (var i = 0, l = pattern.length
+    ; i < l && pattern.charAt(i) === '!'
+    ; i++) {
     negate = !negate
-    negateOffset ++
+    negateOffset++
   }
 
   if (negateOffset) this.pattern = pattern.substr(negateOffset)
@@ -256,213 +225,34 @@ function parseNegate () {
 // a{2..}b -> a{2..}b
 // a{b}c -> a{b}c
 minimatch.braceExpand = function (pattern, options) {
-  return new Minimatch(pattern, options).braceExpand()
+  return braceExpand(pattern, options)
 }
 
 Minimatch.prototype.braceExpand = braceExpand
 
-function pad(n, width, z) {
-  z = z || '0';
-  n = n + '';
-  return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
-}
-
 function braceExpand (pattern, options) {
-  options = options || this.options
-  pattern = typeof pattern === "undefined"
+  if (!options) {
+    if (this instanceof Minimatch) {
+      options = this.options
+    } else {
+      options = {}
+    }
+  }
+
+  pattern = typeof pattern === 'undefined'
     ? this.pattern : pattern
 
-  if (typeof pattern === "undefined") {
-    throw new Error("undefined pattern")
+  if (typeof pattern === 'undefined') {
+    throw new TypeError('undefined pattern')
   }
 
   if (options.nobrace ||
-      !pattern.match(/\{.*\}/)) {
+    !pattern.match(/\{.*\}/)) {
     // shortcut. no need to expand.
     return [pattern]
   }
 
-  var escaping = false
-
-  // examples and comments refer to this crazy pattern:
-  // a{b,c{d,e},{f,g}h}x{y,z}
-  // expected:
-  // abxy
-  // abxz
-  // acdxy
-  // acdxz
-  // acexy
-  // acexz
-  // afhxy
-  // afhxz
-  // aghxy
-  // aghxz
-
-  // everything before the first \{ is just a prefix.
-  // So, we pluck that off, and work with the rest,
-  // and then prepend it to everything we find.
-  if (pattern.charAt(0) !== "{") {
-    this.debug(pattern)
-    var prefix = null
-    for (var i = 0, l = pattern.length; i < l; i ++) {
-      var c = pattern.charAt(i)
-      this.debug(i, c)
-      if (c === "\\") {
-        escaping = !escaping
-      } else if (c === "{" && !escaping) {
-        prefix = pattern.substr(0, i)
-        break
-      }
-    }
-
-    // actually no sets, all { were escaped.
-    if (prefix === null) {
-      this.debug("no sets")
-      return [pattern]
-    }
-
-   var tail = braceExpand.call(this, pattern.substr(i), options)
-    return tail.map(function (t) {
-      return prefix + t
-    })
-  }
-
-  // now we have something like:
-  // {b,c{d,e},{f,g}h}x{y,z}
-  // walk through the set, expanding each part, until
-  // the set ends.  then, we'll expand the suffix.
-  // If the set only has a single member, then'll put the {} back
-
-  // first, handle numeric sets, since they're easier
-  var numset = pattern.match(/^\{(-?[0-9]+)\.\.(-?[0-9]+)\}/)
-  if (numset) {
-    this.debug("numset", numset[1], numset[2])
-    var suf = braceExpand.call(this, pattern.substr(numset[0].length), options)
-      , start = +numset[1]
-      , needPadding = numset[1][0] === '0'
-      , startWidth = numset[1].length
-      , padded
-      , end = +numset[2]
-      , inc = start > end ? -1 : 1
-      , set = []
-
-    for (var i = start; i != (end + inc); i += inc) {
-      padded = needPadding ? pad(i, startWidth) : i + ''
-      // append all the suffixes
-      for (var ii = 0, ll = suf.length; ii < ll; ii ++) {
-        set.push(padded + suf[ii])
-      }
-    }
-    return set
-  }
-
-  // ok, walk through the set
-  // We hope, somewhat optimistically, that there
-  // will be a } at the end.
-  // If the closing brace isn't found, then the pattern is
-  // interpreted as braceExpand("\\" + pattern) so that
-  // the leading \{ will be interpreted literally.
-  var i = 1 // skip the \{
-    , depth = 1
-    , set = []
-    , member = ""
-    , sawEnd = false
-    , escaping = false
-
-  function addMember () {
-    set.push(member)
-    member = ""
-  }
-
-  this.debug("Entering for")
-  FOR: for (i = 1, l = pattern.length; i < l; i ++) {
-    var c = pattern.charAt(i)
-    this.debug("", i, c)
-
-    if (escaping) {
-      escaping = false
-      member += "\\" + c
-    } else {
-      switch (c) {
-        case "\\":
-          escaping = true
-          continue
-
-        case "{":
-          depth ++
-          member += "{"
-          continue
-
-        case "}":
-          depth --
-          // if this closes the actual set, then we're done
-          if (depth === 0) {
-            addMember()
-            // pluck off the close-brace
-            i ++
-            break FOR
-          } else {
-            member += c
-            continue
-          }
-
-        case ",":
-          if (depth === 1) {
-            addMember()
-          } else {
-            member += c
-          }
-          continue
-
-        default:
-          member += c
-          continue
-      } // switch
-    } // else
-  } // for
-
-  // now we've either finished the set, and the suffix is
-  // pattern.substr(i), or we have *not* closed the set,
-  // and need to escape the leading brace
-  if (depth !== 0) {
-    this.debug("didn't close", pattern)
-    return braceExpand.call(this, "\\" + pattern, options)
-  }
-
-  // x{y,z} -> ["xy", "xz"]
-  this.debug("set", set)
-  this.debug("suffix", pattern.substr(i))
-  var suf = braceExpand.call(this, pattern.substr(i), options)
-  // ["b", "c{d,e}","{f,g}h"] ->
-  //   [["b"], ["cd", "ce"], ["fh", "gh"]]
-  var addBraces = set.length === 1
-  this.debug("set pre-expanded", set)
-  set = set.map(function (p) {
-    return braceExpand.call(this, p, options)
-  }, this)
-  this.debug("set expanded", set)
-
-
-  // [["b"], ["cd", "ce"], ["fh", "gh"]] ->
-  //   ["b", "cd", "ce", "fh", "gh"]
-  set = set.reduce(function (l, r) {
-    return l.concat(r)
-  })
-
-  if (addBraces) {
-    set = set.map(function (s) {
-      return "{" + s + "}"
-    })
-  }
-
-  // now attach the suffixes.
-  var ret = []
-  for (var i = 0, l = set.length; i < l; i ++) {
-    for (var ii = 0, ll = suf.length; ii < ll; ii ++) {
-      ret.push(set[i] + suf[ii])
-    }
-  }
-  return ret
+  return expand(pattern)
 }
 
 // parse a component of the expanded set.
@@ -479,90 +269,93 @@ function braceExpand (pattern, options) {
 Minimatch.prototype.parse = parse
 var SUBPARSE = {}
 function parse (pattern, isSub) {
+  if (pattern.length > 1024 * 64) {
+    throw new TypeError('pattern is too long')
+  }
+
   var options = this.options
 
   // shortcuts
-  if (!options.noglobstar && pattern === "**") return GLOBSTAR
-  if (pattern === "") return ""
-
-  var re = ""
-    , hasMagic = !!options.nocase
-    , escaping = false
-    // ? => one single character
-    , patternListStack = []
-    , plType
-    , stateChar
-    , inClass = false
-    , reClassStart = -1
-    , classStart = -1
-    // . and .. never match anything that doesn't start with .,
-    // even when options.dot is set.
-    , patternStart = pattern.charAt(0) === "." ? "" // anything
-      // not (start or / followed by . or .. followed by / or end)
-      : options.dot ? "(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))"
-      : "(?!\\.)"
-    , self = this
+  if (!options.noglobstar && pattern === '**') return GLOBSTAR
+  if (pattern === '') return ''
+
+  var re = ''
+  var hasMagic = !!options.nocase
+  var escaping = false
+  // ? => one single character
+  var patternListStack = []
+  var negativeLists = []
+  var stateChar
+  var inClass = false
+  var reClassStart = -1
+  var classStart = -1
+  // . and .. never match anything that doesn't start with .,
+  // even when options.dot is set.
+  var patternStart = pattern.charAt(0) === '.' ? '' // anything
+  // not (start or / followed by . or .. followed by / or end)
+  : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))'
+  : '(?!\\.)'
+  var self = this
 
   function clearStateChar () {
     if (stateChar) {
       // we had some state-tracking character
       // that wasn't consumed by this pass.
       switch (stateChar) {
-        case "*":
+        case '*':
           re += star
           hasMagic = true
-          break
-        case "?":
+        break
+        case '?':
           re += qmark
           hasMagic = true
-          break
+        break
         default:
-          re += "\\"+stateChar
-          break
+          re += '\\' + stateChar
+        break
       }
       self.debug('clearStateChar %j %j', stateChar, re)
       stateChar = false
     }
   }
 
-  for ( var i = 0, len = pattern.length, c
-      ; (i < len) && (c = pattern.charAt(i))
-      ; i ++ ) {
-
-    this.debug("%s\t%s %s %j", pattern, i, re, c)
+  for (var i = 0, len = pattern.length, c
+    ; (i < len) && (c = pattern.charAt(i))
+    ; i++) {
+    this.debug('%s\t%s %s %j', pattern, i, re, c)
 
     // skip over any that are escaped.
     if (escaping && reSpecials[c]) {
-      re += "\\" + c
+      re += '\\' + c
       escaping = false
       continue
     }
 
-    SWITCH: switch (c) {
-      case "/":
+    switch (c) {
+      case '/':
         // completely not allowed, even escaped.
         // Should already be path-split by now.
         return false
 
-      case "\\":
+      case '\\':
         clearStateChar()
         escaping = true
-        continue
+      continue
 
       // the various stateChar values
       // for the "extglob" stuff.
-      case "?":
-      case "*":
-      case "+":
-      case "@":
-      case "!":
-        this.debug("%s\t%s %s %j <-- stateChar", pattern, i, re, c)
+      case '?':
+      case '*':
+      case '+':
+      case '@':
+      case '!':
+        this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c)
 
         // all of those are literals inside a class, except that
         // the glob [!a] means [^a] in regexp
         if (inClass) {
           this.debug('  in class')
-          if (c === "!" && i === classStart + 1) c = "^"
+          if (c === '!' && i === classStart + 1) c = '^'
           re += c
           continue
         }
@@ -577,70 +370,68 @@ function parse (pattern, isSub) {
         // just clear the statechar *now*, rather than even diving into
         // the patternList stuff.
         if (options.noext) clearStateChar()
-        continue
+      continue
 
-      case "(":
+      case '(':
         if (inClass) {
-          re += "("
+          re += '('
           continue
         }
 
         if (!stateChar) {
-          re += "\\("
+          re += '\\('
           continue
         }
 
-        plType = stateChar
-        patternListStack.push({ type: plType
-                              , start: i - 1
-                              , reStart: re.length })
+        patternListStack.push({
+          type: stateChar,
+          start: i - 1,
+          reStart: re.length,
+          open: plTypes[stateChar].open,
+          close: plTypes[stateChar].close
+        })
         // negation is (?:(?!js)[^/]*)
-        re += stateChar === "!" ? "(?:(?!" : "(?:"
+        re += stateChar === '!' ? '(?:(?!(?:' : '(?:'
         this.debug('plType %j %j', stateChar, re)
         stateChar = false
-        continue
+      continue
 
-      case ")":
+      case ')':
         if (inClass || !patternListStack.length) {
-          re += "\\)"
+          re += '\\)'
           continue
         }
 
         clearStateChar()
         hasMagic = true
-        re += ")"
-        plType = patternListStack.pop().type
+        var pl = patternListStack.pop()
         // negation is (?:(?!js)[^/]*)
         // The others are (?:<pattern>)<type>
-        switch (plType) {
-          case "!":
-            re += "[^/]*?)"
-            break
-          case "?":
-          case "+":
-          case "*": re += plType
-          case "@": break // the default anyway
+        re += pl.close
+        if (pl.type === '!') {
+          negativeLists.push(pl)
         }
-        continue
+        pl.reEnd = re.length
+      continue
 
-      case "|":
+      case '|':
         if (inClass || !patternListStack.length || escaping) {
-          re += "\\|"
+          re += '\\|'
           escaping = false
           continue
         }
 
         clearStateChar()
-        re += "|"
-        continue
+        re += '|'
+      continue
 
       // these are mostly the same in regexp and glob
-      case "[":
+      case '[':
         // swallow any state-tracking char before the [
         clearStateChar()
 
         if (inClass) {
-          re += "\\" + c
+          re += '\\' + c
           continue
         }
 
@@ -648,24 +439,47 @@ function parse (pattern, isSub) {
         classStart = i
         reClassStart = re.length
         re += c
-        continue
+      continue
 
-      case "]":
+      case ']':
         //  a right bracket shall lose its special
         //  meaning and represent itself in
         //  a bracket expression if it occurs
         //  first in the list.  -- POSIX.2 2.8.3.2
         if (i === classStart + 1 || !inClass) {
-          re += "\\" + c
+          re += '\\' + c
           escaping = false
           continue
         }
 
+        // handle the case where we left a class open.
+        // "[z-a]" is valid, equivalent to "\[z-a\]"
+        if (inClass) {
+          // split where the last [ was, make sure we don't have
+          // an invalid re. if so, re-walk the contents of the
+          // would-be class to re-translate any characters that
+          // were passed through as-is
+          // TODO: It would probably be faster to determine this
+          // without a try/catch and a new RegExp, but it's tricky
+          // to do safely.  For now, this is safe and works.
+          var cs = pattern.substring(classStart + 1, i)
+          try {
+            RegExp('[' + cs + ']')
+          } catch (er) {
+            // not a valid class!
+            var sp = this.parse(cs, SUBPARSE)
+            re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]'
+            hasMagic = hasMagic || sp[1]
+            inClass = false
+            continue
+          }
+        }
+
         // finish up the class.
         hasMagic = true
         inClass = false
         re += c
-        continue
+      continue
 
       default:
         // swallow any state char that wasn't consumed
@@ -675,8 +489,8 @@ function parse (pattern, isSub) {
           // no need
           escaping = false
         } else if (reSpecials[c]
-                   && !(c === "^" && inClass)) {
-          re += "\\"
+          && !(c === '^' && inClass)) {
+          re += '\\'
         }
 
         re += c
@@ -684,7 +498,6 @@ function parse (pattern, isSub) {
     } // switch
   } // for
 
-
   // handle the case where we left a class open.
   // "[abc" is valid, equivalent to "\[abc"
   if (inClass) {
@@ -692,9 +505,9 @@ function parse (pattern, isSub) {
     // this is a huge pita.  We now have to re-walk
     // the contents of the would-be class to re-translate
     // any characters that were passed through as-is
-    var cs = pattern.substr(classStart + 1)
-      , sp = this.parse(cs, SUBPARSE)
-    re = re.substr(0, reClassStart) + "\\[" + sp[0]
+    cs = pattern.substr(classStart + 1)
+    sp = this.parse(cs, SUBPARSE)
+    re = re.substr(0, reClassStart) + '\\[' + sp[0]
     hasMagic = hasMagic || sp[1]
   }
 
@@ -704,14 +517,14 @@ function parse (pattern, isSub) {
   // and escape any | chars that were passed through as-is for the regexp.
   // Go through and escape them, taking care not to double-escape any
   // | chars that were already escaped.
-  var pl
-  while (pl = patternListStack.pop()) {
-    var tail = re.slice(pl.reStart + 3)
+  for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) {
+    var tail = re.slice(pl.reStart + pl.open.length)
+    this.debug('setting tail', re, pl)
     // maybe some even number of \, then maybe 1 \, followed by a |
-    tail = tail.replace(/((?:\\{2})*)(\\?)\|/g, function (_, $1, $2) {
+    tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) {
       if (!$2) {
         // the | isn't already escaped, so escape it.
-        $2 = "\\"
+        $2 = '\\'
       }
 
       // need to escape all those slashes *again*, without escaping the
@@ -720,46 +533,81 @@ function parse (pattern, isSub) {
       // it exactly after itself.  That's why this trick works.
       //
       // I am sorry that you have to see this.
-      return $1 + $1 + $2 + "|"
+      return $1 + $1 + $2 + '|'
     })
 
-    this.debug("tail=%j\n   %s", tail, tail)
-    var t = pl.type === "*" ? star
-          : pl.type === "?" ? qmark
-          : "\\" + pl.type
+    this.debug('tail=%j\n   %s', tail, tail, pl, re)
+    var t = pl.type === '*' ? star
+      : pl.type === '?' ? qmark
+      : '\\' + pl.type
 
     hasMagic = true
-    re = re.slice(0, pl.reStart)
-       + t + "\\("
-       + tail
+    re = re.slice(0, pl.reStart) + t + '\\(' + tail
   }
 
   // handle trailing things that only matter at the very end.
   clearStateChar()
   if (escaping) {
     // trailing \\
-    re += "\\\\"
+    re += '\\\\'
   }
 
   // only need to apply the nodot start if the re starts with
   // something that could conceivably capture a dot
   var addPatternStart = false
   switch (re.charAt(0)) {
-    case ".":
-    case "[":
-    case "(": addPatternStart = true
+    case '.':
+    case '[':
+    case '(': addPatternStart = true
+  }
+
+  // Hack to work around lack of negative lookbehind in JS
+  // A pattern like: *.!(x).!(y|z) needs to ensure that a name
+  // like 'a.xyz.yz' doesn't match.  So, the first negative
+  // lookahead, has to look ALL the way ahead, to the end of
+  // the pattern.
+  for (var n = negativeLists.length - 1; n > -1; n--) {
+    var nl = negativeLists[n]
+
+    var nlBefore = re.slice(0, nl.reStart)
+    var nlFirst = re.slice(nl.reStart, nl.reEnd - 8)
+    var nlLast = re.slice(nl.reEnd - 8, nl.reEnd)
+    var nlAfter = re.slice(nl.reEnd)
+
+    nlLast += nlAfter
+
+    // Handle nested stuff like *(*.js|!(*.json)), where open parens
+    // mean that we should *not* include the ) in the bit that is considered
+    // "after" the negated section.
+    var openParensBefore = nlBefore.split('(').length - 1
+    var cleanAfter = nlAfter
+    for (i = 0; i < openParensBefore; i++) {
+      cleanAfter = cleanAfter.replace(/\)[+*?]?/, '')
+    }
+    nlAfter = cleanAfter
+
+    var dollar = ''
+    if (nlAfter === '' && isSub !== SUBPARSE) {
+      dollar = '$'
+    }
+    var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast
+    re = newRe
   }
 
   // if the re is not "" at this point, then we need to make sure
   // it doesn't match against an empty path part.
   // Otherwise a/* will match a/, which it should not.
-  if (re !== "" && hasMagic) re = "(?=.)" + re
+  if (re !== '' && hasMagic) {
+    re = '(?=.)' + re
+  }
 
-  if (addPatternStart) re = patternStart + re
+  if (addPatternStart) {
+    re = patternStart + re
+  }
 
   // parsing just a piece of a larger pattern.
   if (isSub === SUBPARSE) {
-    return [ re, hasMagic ]
+    return [re, hasMagic]
   }
 
   // skip the regexp for non-magical patterns
@@ -769,8 +617,16 @@ function parse (pattern, isSub) {
     return globUnescape(pattern)
   }
 
-  var flags = options.nocase ? "i" : ""
-    , regExp = new RegExp("^" + re + "$", flags)
+  var flags = options.nocase ? 'i' : ''
+  try {
+    var regExp = new RegExp('^' + re + '$', flags)
+  } catch (er) {
+    // If it was an invalid regular expression, then it can't match
+    // anything.  This trick looks for a character after the end of
+    // the string, which is of course impossible, except in multi-line
+    // mode, but it's not a /m regex.
+    return new RegExp('$.')
+  }
 
   regExp._glob = pattern
   regExp._src = re
@@ -794,34 +650,38 @@ function makeRe () {
   // when you just want to work with a regex.
   var set = this.set
 
-  if (!set.length) return this.regexp = false
+  if (!set.length) {
+    this.regexp = false
+    return this.regexp
+  }
   var options = this.options
 
   var twoStar = options.noglobstar ? star
-      : options.dot ? twoStarDot
-      : twoStarNoDot
-    , flags = options.nocase ? "i" : ""
+    : options.dot ? twoStarDot
+    : twoStarNoDot
+  var flags = options.nocase ? 'i' : ''
 
   var re = set.map(function (pattern) {
     return pattern.map(function (p) {
       return (p === GLOBSTAR) ? twoStar
-           : (typeof p === "string") ? regExpEscape(p)
-           : p._src
-    }).join("\\\/")
-  }).join("|")
+      : (typeof p === 'string') ? regExpEscape(p)
+      : p._src
+    }).join('\\\/')
+  }).join('|')
 
   // must match entire pattern
   // ending in a * or ** will make it less strict.
-  re = "^(?:" + re + ")$"
+  re = '^(?:' + re + ')$'
 
   // can match anything, as long as it's not this.
-  if (this.negate) re = "^(?!" + re + ").*$"
+  if (this.negate) re = '^(?!' + re + ').*$'
 
   try {
-    return this.regexp = new RegExp(re, flags)
+    this.regexp = new RegExp(re, flags)
   } catch (ex) {
-    return this.regexp = false
+    this.regexp = false
   }
+  return this.regexp
 }
 
 minimatch.match = function (list, pattern, options) {
@@ -838,25 +698,24 @@ minimatch.match = function (list, pattern, options) {
 
 Minimatch.prototype.match = match
 function match (f, partial) {
-  this.debug("match", f, this.pattern)
+  this.debug('match', f, this.pattern)
   // short-circuit in the case of busted things.
   // comments, etc.
   if (this.comment) return false
-  if (this.empty) return f === ""
+  if (this.empty) return f === ''
 
-  if (f === "/" && partial) return true
+  if (f === '/' && partial) return true
 
   var options = this.options
 
   // windows: need to use /, not \
-  // On other platforms, \ is a valid (albeit bad) filename char.
-  if (platform === "win32") {
-    f = f.split("\\").join("/")
+  if (path.sep !== '/') {
+    f = f.split(path.sep).join('/')
   }
 
   // treat the test path as a set of pathparts.
   f = f.split(slashSplit)
-  this.debug(this.pattern, "split", f)
+  this.debug(this.pattern, 'split', f)
 
   // just ONE of the pattern sets in this.set needs to match
   // in order for it to be valid.  If negating, then just one
@@ -864,17 +723,19 @@ function match (f, partial) {
   // Either way, return on the first hit.
 
   var set = this.set
-  this.debug(this.pattern, "set", set)
+  this.debug(this.pattern, 'set', set)
 
   // Find the basename of the path by looking for the last non-empty segment
-  var filename;
-  for (var i = f.length - 1; i >= 0; i--) {
+  var filename
+  var i
+  for (i = f.length - 1; i >= 0; i--) {
     filename = f[i]
     if (filename) break
   }
 
-  for (var i = 0, l = set.length; i < l; i ++) {
-    var pattern = set[i], file = f
+  for (i = 0; i < set.length; i++) {
+    var pattern = set[i]
+    var file = f
     if (options.matchBase && pattern.length === 1) {
       file = [filename]
     }
@@ -899,23 +760,20 @@ function match (f, partial) {
 Minimatch.prototype.matchOne = function (file, pattern, partial) {
   var options = this.options
 
-  this.debug("matchOne",
-              { "this": this
-              , file: file
-              , pattern: pattern })
+  this.debug('matchOne',
+    { 'this': this, file: file, pattern: pattern })
 
-  this.debug("matchOne", file.length, pattern.length)
+  this.debug('matchOne', file.length, pattern.length)
 
-  for ( var fi = 0
-          , pi = 0
-          , fl = file.length
-          , pl = pattern.length
+  for (var fi = 0,
+      pi = 0,
+      fl = file.length,
+      pl = pattern.length
       ; (fi < fl) && (pi < pl)
-      ; fi ++, pi ++ ) {
-
-    this.debug("matchOne loop")
+      ; fi++, pi++) {
+    this.debug('matchOne loop')
     var p = pattern[pi]
-      , f = file[fi]
+    var f = file[fi]
 
     this.debug(pattern, p, f)
 
@@ -949,7 +807,7 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) {
       //       - matchOne(z/c, c) -> no
       //       - matchOne(c, c) yes, hit
       var fr = fi
-        , pr = pi + 1
+      var pr = pi + 1
       if (pr === pl) {
         this.debug('** at the end')
         // a ** at the end will just swallow the rest.
@@ -958,19 +816,18 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) {
         // options.dot is set.
         // . and .. are *never* matched by **, for explosively
         // exponential reasons.
-        for ( ; fi < fl; fi ++) {
-          if (file[fi] === "." || file[fi] === ".." ||
-              (!options.dot && file[fi].charAt(0) === ".")) return false
+        for (; fi < fl; fi++) {
+          if (file[fi] === '.' || file[fi] === '..' ||
+            (!options.dot && file[fi].charAt(0) === '.')) return false
         }
         return true
       }
 
       // ok, let's see if we can swallow whatever we can.
-      WHILE: while (fr < fl) {
+      while (fr < fl) {
         var swallowee = file[fr]
 
-        this.debug('\nglobstar while',
-                    file, fr, pattern, pr, swallowee)
+        this.debug('\nglobstar while', file, fr, pattern, pr, swallowee)
 
         // XXX remove this slice.  Just pass the start index.
         if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
@@ -980,23 +837,24 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) {
         } else {
           // can't swallow "." or ".." ever.
           // can only swallow ".foo" when explicitly asked.
-          if (swallowee === "." || swallowee === ".." ||
-              (!options.dot && swallowee.charAt(0) === ".")) {
-            this.debug("dot detected!", file, fr, pattern, pr)
-            break WHILE
+          if (swallowee === '.' || swallowee === '..' ||
+            (!options.dot && swallowee.charAt(0) === '.')) {
+            this.debug('dot detected!', file, fr, pattern, pr)
+            break
           }
 
           // ** swallows a segment, and continue.
           this.debug('globstar swallow a segment, and continue')
-          fr ++
+          fr++
         }
       }
+
       // no match was found.
       // However, in partial mode, we can't say this is necessarily over.
       // If there's more *pattern* left, then
       if (partial) {
         // ran out of file
-        this.debug("\n>>> no match, partial?", file, fr, pattern, pr)
+        this.debug('\n>>> no match, partial?', file, fr, pattern, pr)
         if (fr === fl) return true
       }
       return false
@@ -1006,16 +864,16 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) {
     // non-magic patterns just have to match exactly
     // patterns with magic have been turned into regexps.
     var hit
-    if (typeof p === "string") {
+    if (typeof p === 'string') {
       if (options.nocase) {
         hit = f.toLowerCase() === p.toLowerCase()
       } else {
         hit = f === p
       }
-      this.debug("string match", p, f, hit)
+      this.debug('string match', p, f, hit)
     } else {
       hit = f.match(p)
-      this.debug("pattern match", p, f, hit)
+      this.debug('pattern match', p, f, hit)
     }
 
     if (!hit) return false
@@ -1047,27 +905,19 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) {
     // this is only acceptable if we're on the very last
     // empty segment of a file with a trailing slash.
     // a/* should match a/b/
-    var emptyFileEnd = (fi === fl - 1) && (file[fi] === "")
+    var emptyFileEnd = (fi === fl - 1) && (file[fi] === '')
     return emptyFileEnd
   }
 
   // should be unreachable.
-  throw new Error("wtf?")
+  throw new Error('wtf?')
 }
 
-
 // replace stuff like \* with *
 function globUnescape (s) {
-  return s.replace(/\\(.)/g, "$1")
+  return s.replace(/\\(.)/g, '$1')
 }
 
-
 function regExpEscape (s) {
-  return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
+  return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
 }
-
-})( typeof require === "function" ? require : null,
-    this,
-    typeof module === "object" ? module : null,
-    typeof process === "object" ? process.platform : "win32"
-  )
diff --git a/package.json b/package.json
index 3332329..ec4b9fd 100644
--- a/package.json
+++ b/package.json
@@ -2,27 +2,28 @@
   "author": "Isaac Z. Schlueter <i at izs.me> (http://blog.izs.me)",
   "name": "minimatch",
   "description": "a glob matcher in javascript",
-  "version": "1.0.0",
+  "version": "3.0.3",
   "repository": {
     "type": "git",
     "url": "git://github.com/isaacs/minimatch.git"
   },
   "main": "minimatch.js",
   "scripts": {
+    "posttest": "standard minimatch.js test/*.js",
     "test": "tap test/*.js"
   },
   "engines": {
     "node": "*"
   },
   "dependencies": {
-    "lru-cache": "2",
-    "sigmund": "~1.0.0"
+    "brace-expansion": "^1.0.0"
   },
   "devDependencies": {
-    "tap": ""
+    "standard": "^3.7.2",
+    "tap": "^5.6.0"
   },
-  "license": {
-    "type": "MIT",
-    "url": "http://github.com/isaacs/minimatch/raw/master/LICENSE"
-  }
+  "license": "ISC",
+  "files": [
+    "minimatch.js"
+  ]
 }
diff --git a/test/basic.js b/test/basic.js
index ae7ac73..465795a 100644
--- a/test/basic.js
+++ b/test/basic.js
@@ -3,372 +3,34 @@
 // TODO: Some of these tests do very bad things with backslashes, and will
 // most likely fail badly on windows.  They should probably be skipped.
 
-var tap = require("tap")
-  , globalBefore = Object.keys(global)
-  , mm = require("../")
-  , files = [ "a", "b", "c", "d", "abc"
-            , "abd", "abe", "bb", "bcd"
-            , "ca", "cb", "dd", "de"
-            , "bdir/", "bdir/cfile"]
-  , next = files.concat([ "a-b", "aXb"
-                        , ".x", ".y" ])
-
-
-var patterns =
-  [ "http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test"
-  , ["a*", ["a", "abc", "abd", "abe"]]
-  , ["X*", ["X*"], {nonull: true}]
-
-  // allow null glob expansion
-  , ["X*", []]
-
-  // isaacs: Slightly different than bash/sh/ksh
-  // \\* is not un-escaped to literal "*" in a failed match,
-  // but it does make it get treated as a literal star
-  , ["\\*", ["\\*"], {nonull: true}]
-  , ["\\**", ["\\**"], {nonull: true}]
-  , ["\\*\\*", ["\\*\\*"], {nonull: true}]
-
-  , ["b*/", ["bdir/"]]
-  , ["c*", ["c", "ca", "cb"]]
-  , ["**", files]
-
-  , ["\\.\\./*/", ["\\.\\./*/"], {nonull: true}]
-  , ["s/\\..*//", ["s/\\..*//"], {nonull: true}]
-
-  , "legendary larry crashes bashes"
-  , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"
-    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"], {nonull: true}]
-  , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"
-    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"], {nonull: true}]
-
-  , "character classes"
-  , ["[a-c]b*", ["abc", "abd", "abe", "bb", "cb"]]
-  , ["[a-y]*[^c]", ["abd", "abe", "bb", "bcd",
-     "bdir/", "ca", "cb", "dd", "de"]]
-  , ["a*[^c]", ["abd", "abe"]]
-  , function () { files.push("a-b", "aXb") }
-  , ["a[X-]b", ["a-b", "aXb"]]
-  , function () { files.push(".x", ".y") }
-  , ["[^a-c]*", ["d", "dd", "de"]]
-  , function () { files.push("a*b/", "a*b/ooo") }
-  , ["a\\*b/*", ["a*b/ooo"]]
-  , ["a\\*?/*", ["a*b/ooo"]]
-  , ["*\\\\!*", [], {null: true}, ["echo !7"]]
-  , ["*\\!*", ["echo !7"], null, ["echo !7"]]
-  , ["*.\\*", ["r.*"], null, ["r.*"]]
-  , ["a[b]c", ["abc"]]
-  , ["a[\\b]c", ["abc"]]
-  , ["a?c", ["abc"]]
-  , ["a\\*c", [], {null: true}, ["abc"]]
-  , ["", [""], { null: true }, [""]]
-
-  , "http://www.opensource.apple.com/source/bash/bash-23/" +
-    "bash/tests/glob-test"
-  , function () { files.push("man/", "man/man1/", "man/man1/bash.1") }
-  , ["*/man*/bash.*", ["man/man1/bash.1"]]
-  , ["man/man1/bash.1", ["man/man1/bash.1"]]
-  , ["a***c", ["abc"], null, ["abc"]]
-  , ["a*****?c", ["abc"], null, ["abc"]]
-  , ["?*****??", ["abc"], null, ["abc"]]
-  , ["*****??", ["abc"], null, ["abc"]]
-  , ["?*****?c", ["abc"], null, ["abc"]]
-  , ["?***?****c", ["abc"], null, ["abc"]]
-  , ["?***?****?", ["abc"], null, ["abc"]]
-  , ["?***?****", ["abc"], null, ["abc"]]
-  , ["*******c", ["abc"], null, ["abc"]]
-  , ["*******?", ["abc"], null, ["abc"]]
-  , ["a*cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-  , ["a**?**cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-  , ["a**?**cd**?**??k***", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-  , ["a**?**cd**?**??***k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-  , ["a**?**cd**?**??***k**", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-  , ["a****c**?**??*****", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-  , ["[-abc]", ["-"], null, ["-"]]
-  , ["[abc-]", ["-"], null, ["-"]]
-  , ["\\", ["\\"], null, ["\\"]]
-  , ["[\\\\]", ["\\"], null, ["\\"]]
-  , ["[[]", ["["], null, ["["]]
-  , ["[", ["["], null, ["["]]
-  , ["[*", ["[abc"], null, ["[abc"]]
-  , "a right bracket shall lose its special meaning and\n" +
-    "represent itself in a bracket expression if it occurs\n" +
-    "first in the list.  -- POSIX.2 2.8.3.2"
-  , ["[]]", ["]"], null, ["]"]]
-  , ["[]-]", ["]"], null, ["]"]]
-  , ["[a-\z]", ["p"], null, ["p"]]
-  , ["??**********?****?", [], { null: true }, ["abc"]]
-  , ["??**********?****c", [], { null: true }, ["abc"]]
-  , ["?************c****?****", [], { null: true }, ["abc"]]
-  , ["*c*?**", [], { null: true }, ["abc"]]
-  , ["a*****c*?**", [], { null: true }, ["abc"]]
-  , ["a********???*******", [], { null: true }, ["abc"]]
-  , ["[]", [], { null: true }, ["a"]]
-  , ["[abc", [], { null: true }, ["["]]
-
-  , "nocase tests"
-  , ["XYZ", ["xYz"], { nocase: true, null: true }
-    , ["xYz", "ABC", "IjK"]]
-  , ["ab*", ["ABC"], { nocase: true, null: true }
-    , ["xYz", "ABC", "IjK"]]
-  , ["[ia]?[ck]", ["ABC", "IjK"], { nocase: true, null: true }
-    , ["xYz", "ABC", "IjK"]]
-
-  // [ pattern, [matches], MM opts, files, TAP opts]
-  , "onestar/twostar"
-  , ["{/*,*}", [], {null: true}, ["/asdf/asdf/asdf"]]
-  , ["{/?,*}", ["/a", "bb"], {null: true}
-    , ["/a", "/b/b", "/a/b/c", "bb"]]
-
-  , "dots should not match unless requested"
-  , ["**", ["a/b"], {}, ["a/b", "a/.d", ".a/.d"]]
-
-  // .. and . can only match patterns starting with .,
-  // even when options.dot is set.
-  , function () {
-      files = ["a/./b", "a/../b", "a/c/b", "a/.d/b"]
-    }
-  , ["a/*/b", ["a/c/b", "a/.d/b"], {dot: true}]
-  , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: true}]
-  , ["a/*/b", ["a/c/b"], {dot:false}]
-  , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: false}]
-
-
-  // this also tests that changing the options needs
-  // to change the cache key, even if the pattern is
-  // the same!
-  , ["**", ["a/b","a/.d",".a/.d"], { dot: true }
-    , [ ".a/.d", "a/.d", "a/b"]]
-
-  , "paren sets cannot contain slashes"
-  , ["*(a/b)", ["*(a/b)"], {nonull: true}, ["a/b"]]
-
-  // brace sets trump all else.
-  //
-  // invalid glob pattern.  fails on bash4 and bsdglob.
-  // however, in this implementation, it's easier just
-  // to do the intuitive thing, and let brace-expansion
-  // actually come before parsing any extglob patterns,
-  // like the documentation seems to say.
-  //
-  // XXX: if anyone complains about this, either fix it
-  // or tell them to grow up and stop complaining.
-  //
-  // bash/bsdglob says this:
-  // , ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]]
-  // but we do this instead:
-  , ["*(a|{b),c)}", ["a", "ab", "ac"], {}, ["a", "ab", "ac", "ad"]]
-
-  // test partial parsing in the presence of comment/negation chars
-  , ["[!a*", ["[!ab"], {}, ["[!ab", "[ab"]]
-  , ["[#a*", ["[#ab"], {}, ["[#ab", "[ab"]]
-
-  // like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped.
-  , ["+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g"
-    , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g"]
-    , {}
-    , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g", "a", "b\\c"]]
-
-
-  // crazy nested {,,} and *(||) tests.
-  , function () {
-      files = [ "a", "b", "c", "d"
-              , "ab", "ac", "ad"
-              , "bc", "cb"
-              , "bc,d", "c,db", "c,d"
-              , "d)", "(b|c", "*(b|c"
-              , "b|c", "b|cc", "cb|c"
-              , "x(a|b|c)", "x(a|c)"
-              , "(a|b|c)", "(a|c)"]
-    }
-  , ["*(a|{b,c})", ["a", "b", "c", "ab", "ac"]]
-  , ["{a,*(b|c,d)}", ["a","(b|c", "*(b|c", "d)"]]
-  // a
-  // *(b|c)
-  // *(b|d)
-  , ["{a,*(b|{c,d})}", ["a","b", "bc", "cb", "c", "d"]]
-  , ["*(a|{b|c,c})", ["a", "b", "c", "ab", "ac", "bc", "cb"]]
-
-
-  // test various flag settings.
-  , [ "*(a|{b|c,c})", ["x(a|b|c)", "x(a|c)", "(a|b|c)", "(a|c)"]
-    , { noext: true } ]
-  , ["a?b", ["x/y/acb", "acb/"], {matchBase: true}
-    , ["x/y/acb", "acb/", "acb/d/e", "x/y/acb/d"] ]
-  , ["#*", ["#a", "#b"], {nocomment: true}, ["#a", "#b", "c#d"]]
-
-
-  // begin channelling Boole and deMorgan...
-  , "negation tests"
-  , function () {
-      files = ["d", "e", "!ab", "!abc", "a!b", "\\!a"]
-    }
-
-  // anything that is NOT a* matches.
-  , ["!a*", ["\\!a", "d", "e", "!ab", "!abc"]]
-
-  // anything that IS !a* matches.
-  , ["!a*", ["!ab", "!abc"], {nonegate: true}]
-
-  // anything that IS a* matches
-  , ["!!a*", ["a!b"]]
-
-  // anything that is NOT !a* matches
-  , ["!\\!a*", ["a!b", "d", "e", "\\!a"]]
-
-  // negation nestled within a pattern
-  , function () {
-      files = [ "foo.js"
-              , "foo.bar"
-              // can't match this one without negative lookbehind.
-              , "foo.js.js"
-              , "blar.js"
-              , "foo."
-              , "boo.js.boo" ]
-    }
-  , ["*.!(js)", ["foo.bar", "foo.", "boo.js.boo"] ]
-
-  // https://github.com/isaacs/minimatch/issues/5
-  , function () {
-      files = [ 'a/b/.x/c'
-              , 'a/b/.x/c/d'
-              , 'a/b/.x/c/d/e'
-              , 'a/b/.x'
-              , 'a/b/.x/'
-              , 'a/.x/b'
-              , '.x'
-              , '.x/'
-              , '.x/a'
-              , '.x/a/b'
-              , 'a/.x/b/.x/c'
-              , '.x/.x' ]
-  }
-  , ["**/.x/**", [ '.x/'
-                 , '.x/a'
-                 , '.x/a/b'
-                 , 'a/.x/b'
-                 , 'a/b/.x/'
-                 , 'a/b/.x/c'
-                 , 'a/b/.x/c/d'
-                 , 'a/b/.x/c/d/e' ] ]
-
-  ]
-
-var regexps =
-  [ '/^(?:(?=.)a[^/]*?)$/',
-    '/^(?:(?=.)X[^/]*?)$/',
-    '/^(?:(?=.)X[^/]*?)$/',
-    '/^(?:\\*)$/',
-    '/^(?:(?=.)\\*[^/]*?)$/',
-    '/^(?:\\*\\*)$/',
-    '/^(?:(?=.)b[^/]*?\\/)$/',
-    '/^(?:(?=.)c[^/]*?)$/',
-    '/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
-    '/^(?:\\.\\.\\/(?!\\.)(?=.)[^/]*?\\/)$/',
-    '/^(?:s\\/(?=.)\\.\\.[^/]*?\\/)$/',
-    '/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/1\\/)$/',
-    '/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/\u0001\\/)$/',
-    '/^(?:(?!\\.)(?=.)[a-c]b[^/]*?)$/',
-    '/^(?:(?!\\.)(?=.)[a-y][^/]*?[^c])$/',
-    '/^(?:(?=.)a[^/]*?[^c])$/',
-    '/^(?:(?=.)a[X-]b)$/',
-    '/^(?:(?!\\.)(?=.)[^a-c][^/]*?)$/',
-    '/^(?:a\\*b\\/(?!\\.)(?=.)[^/]*?)$/',
-    '/^(?:(?=.)a\\*[^/]\\/(?!\\.)(?=.)[^/]*?)$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?\\\\\\![^/]*?)$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?\\![^/]*?)$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?\\.\\*)$/',
-    '/^(?:(?=.)a[b]c)$/',
-    '/^(?:(?=.)a[b]c)$/',
-    '/^(?:(?=.)a[^/]c)$/',
-    '/^(?:a\\*c)$/',
-    'false',
-    '/^(?:(?!\\.)(?=.)[^/]*?\\/(?=.)man[^/]*?\\/(?=.)bash\\.[^/]*?)$/',
-    '/^(?:man\\/man1\\/bash\\.1)$/',
-    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?c)$/',
-    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
-    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
-    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
-    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
-    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
-    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c)$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
-    '/^(?:(?=.)a[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
-    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
-    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k[^/]*?[^/]*?[^/]*?)$/',
-    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k)$/',
-    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k[^/]*?[^/]*?)$/',
-    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
-    '/^(?:(?!\\.)(?=.)[-abc])$/',
-    '/^(?:(?!\\.)(?=.)[abc-])$/',
-    '/^(?:\\\\)$/',
-    '/^(?:(?!\\.)(?=.)[\\\\])$/',
-    '/^(?:(?!\\.)(?=.)[\\[])$/',
-    '/^(?:\\[)$/',
-    '/^(?:(?=.)\\[(?!\\.)(?=.)[^/]*?)$/',
-    '/^(?:(?!\\.)(?=.)[\\]])$/',
-    '/^(?:(?!\\.)(?=.)[\\]-])$/',
-    '/^(?:(?!\\.)(?=.)[a-z])$/',
-    '/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
-    '/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
-    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
-    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
-    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
-    '/^(?:\\[\\])$/',
-    '/^(?:\\[abc)$/',
-    '/^(?:(?=.)XYZ)$/i',
-    '/^(?:(?=.)ab[^/]*?)$/i',
-    '/^(?:(?!\\.)(?=.)[ia][^/][ck])$/i',
-    '/^(?:\\/(?!\\.)(?=.)[^/]*?|(?!\\.)(?=.)[^/]*?)$/',
-    '/^(?:\\/(?!\\.)(?=.)[^/]|(?!\\.)(?=.)[^/]*?)$/',
-    '/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
-    '/^(?:a\\/(?!(?:^|\\/)\\.{1,2}(?:$|\\/))(?=.)[^/]*?\\/b)$/',
-    '/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
-    '/^(?:a\\/(?!\\.)(?=.)[^/]*?\\/b)$/',
-    '/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
-    '/^(?:(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?)$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?\\(a\\/b\\))$/',
-    '/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
-    '/^(?:(?=.)\\[(?=.)\\!a[^/]*?)$/',
-    '/^(?:(?=.)\\[(?=.)#a[^/]*?)$/',
-    '/^(?:(?=.)\\+\\(a\\|[^/]*?\\|c\\\\\\\\\\|d\\\\\\\\\\|e\\\\\\\\\\\\\\\\\\|f\\\\\\\\\\\\\\\\\\|g)$/',
-    '/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
-    '/^(?:a|(?!\\.)(?=.)[^/]*?\\(b\\|c|d\\))$/',
-    '/^(?:a|(?!\\.)(?=.)(?:b|c)*|(?!\\.)(?=.)(?:b|d)*)$/',
-    '/^(?:(?!\\.)(?=.)(?:a|b|c)*|(?!\\.)(?=.)(?:a|c)*)$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?\\(a\\|b\\|c\\)|(?!\\.)(?=.)[^/]*?\\(a\\|c\\))$/',
-    '/^(?:(?=.)a[^/]b)$/',
-    '/^(?:(?=.)#[^/]*?)$/',
-    '/^(?!^(?:(?=.)a[^/]*?)$).*$/',
-    '/^(?:(?=.)\\!a[^/]*?)$/',
-    '/^(?:(?=.)a[^/]*?)$/',
-    '/^(?!^(?:(?=.)\\!a[^/]*?)$).*$/',
-    '/^(?:(?!\\.)(?=.)[^/]*?\\.(?:(?!js)[^/]*?))$/',
-    '/^(?:(?:(?!(?:\\/|^)\\.).)*?\\/\\.x\\/(?:(?!(?:\\/|^)\\.).)*?)$/' ]
-var re = 0;
-
-tap.test("basic tests", function (t) {
+var tap = require('tap')
+var globalBefore = Object.keys(global)
+var mm = require('../')
+var patterns = require('./patterns.js')
+var regexps = patterns.regexps
+var re = 0
+
+tap.test('basic tests', function (t) {
   var start = Date.now()
 
   // [ pattern, [matches], MM opts, files, TAP opts]
   patterns.forEach(function (c) {
-    if (typeof c === "function") return c()
-    if (typeof c === "string") return t.comment(c)
+    if (typeof c === 'function') return c()
+    if (typeof c === 'string') return t.comment(c)
 
-    var pattern = c[0]
-      , expect = c[1].sort(alpha)
-      , options = c[2] || {}
-      , f = c[3] || files
-      , tapOpts = c[4] || {}
+    var pattern = c[0],
+      expect = c[1].sort(alpha),
+      options = c[2] || {},
+      f = c[3] || patterns.files,
+      tapOpts = c[4] || {}
 
     // options.debug = true
     var m = new mm.Minimatch(pattern, options)
     var r = m.makeRe()
     var expectRe = regexps[re++]
+    expectRe = '/' + expectRe.slice(1, -1).replace(new RegExp('([^\\\\])/', 'g'), '$1\\\/') + '/'
     tapOpts.re = String(r) || JSON.stringify(r)
+    tapOpts.re = '/' + tapOpts.re.slice(1, -1).replace(new RegExp('([^\\\\])/', 'g'), '$1\\\/') + '/'
     tapOpts.files = JSON.stringify(f)
     tapOpts.pattern = pattern
     tapOpts.set = m.set
@@ -377,20 +39,24 @@ tap.test("basic tests", function (t) {
     var actual = mm.match(f, pattern, options)
     actual.sort(alpha)
 
-    t.equivalent( actual, expect
-                , JSON.stringify(pattern) + " " + JSON.stringify(expect)
-                , tapOpts )
+    t.equivalent(
+      actual, expect,
+      JSON.stringify(pattern) + ' ' + JSON.stringify(expect),
+      tapOpts
+    )
 
-    t.equal(tapOpts.re, expectRe, tapOpts)
+    t.equal(tapOpts.re, expectRe, null, tapOpts)
   })
 
-  t.comment("time=" + (Date.now() - start) + "ms")
+  t.comment('time=' + (Date.now() - start) + 'ms')
   t.end()
 })
 
-tap.test("global leak test", function (t) {
-  var globalAfter = Object.keys(global)
-  t.equivalent(globalAfter, globalBefore, "no new globals, please")
+tap.test('global leak test', function (t) {
+  var globalAfter = Object.keys(global).filter(function (k) {
+    return (k !== '__coverage__')
+  })
+  t.equivalent(globalAfter, globalBefore, 'no new globals, please')
   t.end()
 })
 
diff --git a/test/brace-expand.js b/test/brace-expand.js
index e63d3f6..c7c936f 100644
--- a/test/brace-expand.js
+++ b/test/brace-expand.js
@@ -1,40 +1,72 @@
-var tap = require("tap")
-  , minimatch = require("../")
+var tap = require('tap'),
+  minimatch = require('../')
 
-tap.test("brace expansion", function (t) {
+tap.test('brace expansion', function (t) {
   // [ pattern, [expanded] ]
-  ; [ [ "a{b,c{d,e},{f,g}h}x{y,z}"
-      , [ "abxy"
-        , "abxz"
-        , "acdxy"
-        , "acdxz"
-        , "acexy"
-        , "acexz"
-        , "afhxy"
-        , "afhxz"
-        , "aghxy"
-        , "aghxz" ] ]
-    , [ "a{1..5}b"
-      , [ "a1b"
-        , "a2b"
-        , "a3b"
-        , "a4b"
-        , "a5b" ] ]
-    , [ "a{b}c", ["a{b}c"] ]
-    , [ "a{00..05}b"
-      , ["a00b"
-        ,"a01b"
-        ,"a02b"
-        ,"a03b"
-        ,"a04b"
-        ,"a05b" ] ]
-  ].forEach(function (tc) {
-    var p = tc[0]
-      , expect = tc[1]
+  var patterns = [
+    [
+      'a{b,c{d,e},{f,g}h}x{y,z}',
+      [
+        'abxy',
+        'abxz',
+        'acdxy',
+        'acdxz',
+        'acexy',
+        'acexz',
+        'afhxy',
+        'afhxz',
+        'aghxy',
+        'aghxz'
+      ]
+    ],
+    [
+      'a{1..5}b',
+      [
+        'a1b',
+        'a2b',
+        'a3b',
+        'a4b',
+        'a5b'
+      ]
+    ],
+    ['a{b}c', ['a{b}c']],
+    [
+      'a{00..05}b',
+      [
+        'a00b',
+        'a01b',
+        'a02b',
+        'a03b',
+        'a04b',
+        'a05b'
+      ]
+    ],
+    ['z{a,b},c}d', ['za,c}d', 'zb,c}d']],
+    ['z{a,b{,c}d', ['z{a,bd', 'z{a,bcd']],
+    ['a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']],
+    [
+      'a{b{c{d,e}f{x,y}}g}h',
+      [
+        'a{b{cdfx}g}h',
+        'a{b{cdfy}g}h',
+        'a{b{cefx}g}h',
+        'a{b{cefy}g}h'
+      ]
+    ],
+    [
+      'a{b{c{d,e}f{x,y{}g}h',
+      [
+        'a{b{cdfxh',
+        'a{b{cdfy{}gh',
+        'a{b{cefxh',
+        'a{b{cefy{}gh'
+      ]
+    ]
+  ]
+  patterns.forEach(function (tc) {
+    var p = tc[0],
+      expect = tc[1]
     t.equivalent(minimatch.braceExpand(p), expect, p)
   })
-  console.error("ending")
   t.end()
 })
-
-
diff --git a/test/caching.js b/test/caching.js
deleted file mode 100644
index 0fec4b0..0000000
--- a/test/caching.js
+++ /dev/null
@@ -1,14 +0,0 @@
-var Minimatch = require("../minimatch.js").Minimatch
-var tap = require("tap")
-tap.test("cache test", function (t) {
-  var mm1 = new Minimatch("a?b")
-  var mm2 = new Minimatch("a?b")
-  t.equal(mm1, mm2, "should get the same object")
-  // the lru should drop it after 100 entries
-  for (var i = 0; i < 100; i ++) {
-    new Minimatch("a"+i)
-  }
-  mm2 = new Minimatch("a?b")
-  t.notEqual(mm1, mm2, "cache should have dropped")
-  t.end()
-})
diff --git a/test/defaults.js b/test/defaults.js
index 75e0571..2a103af 100644
--- a/test/defaults.js
+++ b/test/defaults.js
@@ -3,269 +3,56 @@
 // TODO: Some of these tests do very bad things with backslashes, and will
 // most likely fail badly on windows.  They should probably be skipped.
 
-var tap = require("tap")
-  , globalBefore = Object.keys(global)
-  , mm = require("../")
-  , files = [ "a", "b", "c", "d", "abc"
-            , "abd", "abe", "bb", "bcd"
-            , "ca", "cb", "dd", "de"
-            , "bdir/", "bdir/cfile"]
-  , next = files.concat([ "a-b", "aXb"
-                        , ".x", ".y" ])
+var tap = require('tap')
+var globalBefore = Object.keys(global)
+var mm = require('../')
 
-tap.test("basic tests", function (t) {
+var patterns = require('./patterns.js')
+
+tap.test('basic tests', function (t) {
   var start = Date.now()
 
   // [ pattern, [matches], MM opts, files, TAP opts]
-  ; [ "http://www.bashcookbook.com/bashinfo" +
-      "/source/bash-1.14.7/tests/glob-test"
-    , ["a*", ["a", "abc", "abd", "abe"]]
-    , ["X*", ["X*"], {nonull: true}]
-
-    // allow null glob expansion
-    , ["X*", []]
-
-    // isaacs: Slightly different than bash/sh/ksh
-    // \\* is not un-escaped to literal "*" in a failed match,
-    // but it does make it get treated as a literal star
-    , ["\\*", ["\\*"], {nonull: true}]
-    , ["\\**", ["\\**"], {nonull: true}]
-    , ["\\*\\*", ["\\*\\*"], {nonull: true}]
-
-    , ["b*/", ["bdir/"]]
-    , ["c*", ["c", "ca", "cb"]]
-    , ["**", files]
-
-    , ["\\.\\./*/", ["\\.\\./*/"], {nonull: true}]
-    , ["s/\\..*//", ["s/\\..*//"], {nonull: true}]
-
-    , "legendary larry crashes bashes"
-    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"
-      , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"], {nonull: true}]
-    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"
-      , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"], {nonull: true}]
-
-    , "character classes"
-    , ["[a-c]b*", ["abc", "abd", "abe", "bb", "cb"]]
-    , ["[a-y]*[^c]", ["abd", "abe", "bb", "bcd",
-       "bdir/", "ca", "cb", "dd", "de"]]
-    , ["a*[^c]", ["abd", "abe"]]
-    , function () { files.push("a-b", "aXb") }
-    , ["a[X-]b", ["a-b", "aXb"]]
-    , function () { files.push(".x", ".y") }
-    , ["[^a-c]*", ["d", "dd", "de"]]
-    , function () { files.push("a*b/", "a*b/ooo") }
-    , ["a\\*b/*", ["a*b/ooo"]]
-    , ["a\\*?/*", ["a*b/ooo"]]
-    , ["*\\\\!*", [], {null: true}, ["echo !7"]]
-    , ["*\\!*", ["echo !7"], null, ["echo !7"]]
-    , ["*.\\*", ["r.*"], null, ["r.*"]]
-    , ["a[b]c", ["abc"]]
-    , ["a[\\b]c", ["abc"]]
-    , ["a?c", ["abc"]]
-    , ["a\\*c", [], {null: true}, ["abc"]]
-    , ["", [""], { null: true }, [""]]
-
-    , "http://www.opensource.apple.com/source/bash/bash-23/" +
-      "bash/tests/glob-test"
-    , function () { files.push("man/", "man/man1/", "man/man1/bash.1") }
-    , ["*/man*/bash.*", ["man/man1/bash.1"]]
-    , ["man/man1/bash.1", ["man/man1/bash.1"]]
-    , ["a***c", ["abc"], null, ["abc"]]
-    , ["a*****?c", ["abc"], null, ["abc"]]
-    , ["?*****??", ["abc"], null, ["abc"]]
-    , ["*****??", ["abc"], null, ["abc"]]
-    , ["?*****?c", ["abc"], null, ["abc"]]
-    , ["?***?****c", ["abc"], null, ["abc"]]
-    , ["?***?****?", ["abc"], null, ["abc"]]
-    , ["?***?****", ["abc"], null, ["abc"]]
-    , ["*******c", ["abc"], null, ["abc"]]
-    , ["*******?", ["abc"], null, ["abc"]]
-    , ["a*cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-    , ["a**?**cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-    , ["a**?**cd**?**??k***", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-    , ["a**?**cd**?**??***k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-    , ["a**?**cd**?**??***k**", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-    , ["a****c**?**??*****", ["abcdecdhjk"], null, ["abcdecdhjk"]]
-    , ["[-abc]", ["-"], null, ["-"]]
-    , ["[abc-]", ["-"], null, ["-"]]
-    , ["\\", ["\\"], null, ["\\"]]
-    , ["[\\\\]", ["\\"], null, ["\\"]]
-    , ["[[]", ["["], null, ["["]]
-    , ["[", ["["], null, ["["]]
-    , ["[*", ["[abc"], null, ["[abc"]]
-    , "a right bracket shall lose its special meaning and\n" +
-      "represent itself in a bracket expression if it occurs\n" +
-      "first in the list.  -- POSIX.2 2.8.3.2"
-    , ["[]]", ["]"], null, ["]"]]
-    , ["[]-]", ["]"], null, ["]"]]
-    , ["[a-\z]", ["p"], null, ["p"]]
-    , ["??**********?****?", [], { null: true }, ["abc"]]
-    , ["??**********?****c", [], { null: true }, ["abc"]]
-    , ["?************c****?****", [], { null: true }, ["abc"]]
-    , ["*c*?**", [], { null: true }, ["abc"]]
-    , ["a*****c*?**", [], { null: true }, ["abc"]]
-    , ["a********???*******", [], { null: true }, ["abc"]]
-    , ["[]", [], { null: true }, ["a"]]
-    , ["[abc", [], { null: true }, ["["]]
-
-    , "nocase tests"
-    , ["XYZ", ["xYz"], { nocase: true, null: true }
-      , ["xYz", "ABC", "IjK"]]
-    , ["ab*", ["ABC"], { nocase: true, null: true }
-      , ["xYz", "ABC", "IjK"]]
-    , ["[ia]?[ck]", ["ABC", "IjK"], { nocase: true, null: true }
-      , ["xYz", "ABC", "IjK"]]
-
-    // [ pattern, [matches], MM opts, files, TAP opts]
-    , "onestar/twostar"
-    , ["{/*,*}", [], {null: true}, ["/asdf/asdf/asdf"]]
-    , ["{/?,*}", ["/a", "bb"], {null: true}
-      , ["/a", "/b/b", "/a/b/c", "bb"]]
-
-    , "dots should not match unless requested"
-    , ["**", ["a/b"], {}, ["a/b", "a/.d", ".a/.d"]]
-
-    // .. and . can only match patterns starting with .,
-    // even when options.dot is set.
-    , function () {
-        files = ["a/./b", "a/../b", "a/c/b", "a/.d/b"]
-      }
-    , ["a/*/b", ["a/c/b", "a/.d/b"], {dot: true}]
-    , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: true}]
-    , ["a/*/b", ["a/c/b"], {dot:false}]
-    , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: false}]
-
-
-    // this also tests that changing the options needs
-    // to change the cache key, even if the pattern is
-    // the same!
-    , ["**", ["a/b","a/.d",".a/.d"], { dot: true }
-      , [ ".a/.d", "a/.d", "a/b"]]
-
-    , "paren sets cannot contain slashes"
-    , ["*(a/b)", ["*(a/b)"], {nonull: true}, ["a/b"]]
-
-    // brace sets trump all else.
-    //
-    // invalid glob pattern.  fails on bash4 and bsdglob.
-    // however, in this implementation, it's easier just
-    // to do the intuitive thing, and let brace-expansion
-    // actually come before parsing any extglob patterns,
-    // like the documentation seems to say.
-    //
-    // XXX: if anyone complains about this, either fix it
-    // or tell them to grow up and stop complaining.
-    //
-    // bash/bsdglob says this:
-    // , ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]]
-    // but we do this instead:
-    , ["*(a|{b),c)}", ["a", "ab", "ac"], {}, ["a", "ab", "ac", "ad"]]
-
-    // test partial parsing in the presence of comment/negation chars
-    , ["[!a*", ["[!ab"], {}, ["[!ab", "[ab"]]
-    , ["[#a*", ["[#ab"], {}, ["[#ab", "[ab"]]
-
-    // like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped.
-    , ["+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g"
-      , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g"]
-      , {}
-      , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g", "a", "b\\c"]]
-
-
-    // crazy nested {,,} and *(||) tests.
-    , function () {
-        files = [ "a", "b", "c", "d"
-                , "ab", "ac", "ad"
-                , "bc", "cb"
-                , "bc,d", "c,db", "c,d"
-                , "d)", "(b|c", "*(b|c"
-                , "b|c", "b|cc", "cb|c"
-                , "x(a|b|c)", "x(a|c)"
-                , "(a|b|c)", "(a|c)"]
-      }
-    , ["*(a|{b,c})", ["a", "b", "c", "ab", "ac"]]
-    , ["{a,*(b|c,d)}", ["a","(b|c", "*(b|c", "d)"]]
-    // a
-    // *(b|c)
-    // *(b|d)
-    , ["{a,*(b|{c,d})}", ["a","b", "bc", "cb", "c", "d"]]
-    , ["*(a|{b|c,c})", ["a", "b", "c", "ab", "ac", "bc", "cb"]]
-
-
-    // test various flag settings.
-    , [ "*(a|{b|c,c})", ["x(a|b|c)", "x(a|c)", "(a|b|c)", "(a|c)"]
-      , { noext: true } ]
-    , ["a?b", ["x/y/acb", "acb/"], {matchBase: true}
-      , ["x/y/acb", "acb/", "acb/d/e", "x/y/acb/d"] ]
-    , ["#*", ["#a", "#b"], {nocomment: true}, ["#a", "#b", "c#d"]]
-
-
-    // begin channelling Boole and deMorgan...
-    , "negation tests"
-    , function () {
-        files = ["d", "e", "!ab", "!abc", "a!b", "\\!a"]
-      }
-
-    // anything that is NOT a* matches.
-    , ["!a*", ["\\!a", "d", "e", "!ab", "!abc"]]
-
-    // anything that IS !a* matches.
-    , ["!a*", ["!ab", "!abc"], {nonegate: true}]
-
-    // anything that IS a* matches
-    , ["!!a*", ["a!b"]]
-
-    // anything that is NOT !a* matches
-    , ["!\\!a*", ["a!b", "d", "e", "\\!a"]]
-
-    // negation nestled within a pattern
-    , function () {
-        files = [ "foo.js"
-                , "foo.bar"
-                // can't match this one without negative lookbehind.
-                , "foo.js.js"
-                , "blar.js"
-                , "foo."
-                , "boo.js.boo" ]
-      }
-    , ["*.!(js)", ["foo.bar", "foo.", "boo.js.boo"] ]
-
-    ].forEach(function (c) {
-      if (typeof c === "function") return c()
-      if (typeof c === "string") return t.comment(c)
-
-      var pattern = c[0]
-        , expect = c[1].sort(alpha)
-        , options = c[2]
-        , f = c[3] || files
-        , tapOpts = c[4] || {}
-
-      // options.debug = true
-      var Class = mm.defaults(options).Minimatch
-      var m = new Class(pattern, {})
-      var r = m.makeRe()
-      tapOpts.re = String(r) || JSON.stringify(r)
-      tapOpts.files = JSON.stringify(f)
-      tapOpts.pattern = pattern
-      tapOpts.set = m.set
-      tapOpts.negated = m.negate
-
-      var actual = mm.match(f, pattern, options)
-      actual.sort(alpha)
-
-      t.equivalent( actual, expect
-                  , JSON.stringify(pattern) + " " + JSON.stringify(expect)
-                  , tapOpts )
-    })
-
-  t.comment("time=" + (Date.now() - start) + "ms")
+  patterns.forEach(function (c) {
+    if (typeof c === 'function') return c()
+    if (typeof c === 'string') return t.comment(c)
+
+    var pattern = c[0]
+    var expect = c[1].sort(alpha)
+    var options = c[2]
+    var f = c[3] || patterns.files
+    var tapOpts = c[4] || {}
+
+    // options.debug = true
+    var Class = mm.defaults(options).Minimatch
+    var m = new Class(pattern, {})
+    var r = m.makeRe()
+    tapOpts.re = String(r) || JSON.stringify(r)
+    tapOpts.files = JSON.stringify(f)
+    tapOpts.pattern = pattern
+    tapOpts.set = m.set
+    tapOpts.negated = m.negate
+
+    var actual = mm.match(f, pattern, options)
+    actual.sort(alpha)
+
+    t.equivalent(
+      actual,
+      expect,
+      JSON.stringify(pattern) + ' ' + JSON.stringify(expect),
+      tapOpts
+    )
+  })
+
+  t.comment('time=' + (Date.now() - start) + 'ms')
   t.end()
 })
 
-tap.test("global leak test", function (t) {
-  var globalAfter = Object.keys(global)
-  t.equivalent(globalAfter, globalBefore, "no new globals, please")
+tap.test('global leak test', function (t) {
+  var globalAfter = Object.keys(global).filter(function (k) {
+    return (k !== '__coverage__')
+  })
+  t.equivalent(globalAfter, globalBefore, 'no new globals, please')
   t.end()
 })
 
diff --git a/test/extglob-ending-with-state-char.js b/test/extglob-ending-with-state-char.js
index 6676e26..862aa5b 100644
--- a/test/extglob-ending-with-state-char.js
+++ b/test/extglob-ending-with-state-char.js
@@ -1,7 +1,7 @@
 var test = require('tap').test
 var minimatch = require('../')
 
-test('extglob ending with statechar', function(t) {
+test('extglob ending with statechar', function (t) {
   t.notOk(minimatch('ax', 'a?(b*)'))
   t.ok(minimatch('ax', '?(a*|b)'))
   t.end()
diff --git a/test/extglob-unfinished.js b/test/extglob-unfinished.js
new file mode 100644
index 0000000..a35e57c
--- /dev/null
+++ b/test/extglob-unfinished.js
@@ -0,0 +1,13 @@
+var t = require('tap')
+var mm = require('../')
+
+var types = '!?+*@'.split('')
+
+t.plan(types.length)
+types.forEach(function (type) {
+  t.test(type, function (t) {
+    t.plan(2)
+    t.ok(mm(type + '(a|B', type + '(a|B', { nonegate: true }))
+    t.notOk(mm(type + '(a|B', 'B', { nonegate: true }))
+  })
+})
diff --git a/test/patterns.js b/test/patterns.js
new file mode 100644
index 0000000..ca03110
--- /dev/null
+++ b/test/patterns.js
@@ -0,0 +1,368 @@
+if (module === require.main) {
+  console.log('1..1\nok')
+}
+
+var files = [
+  'a', 'b', 'c', 'd', 'abc',
+  'abd', 'abe', 'bb', 'bcd',
+  'ca', 'cb', 'dd', 'de',
+  'bdir/', 'bdir/cfile'
+]
+
+module.exports = [
+  'http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test',
+  ['a*', ['a', 'abc', 'abd', 'abe']],
+  ['X*', ['X*'], {nonull: true}],
+
+  // allow null glob expansion
+  ['X*', []],
+
+  // isaacs: Slightly different than bash/sh/ksh
+  // \\* is not un-escaped to literal "*" in a failed match,
+  // but it does make it get treated as a literal star
+  ['\\*', ['\\*'], {nonull: true}],
+  ['\\**', ['\\**'], {nonull: true}],
+  ['\\*\\*', ['\\*\\*'], {nonull: true}],
+
+  ['b*/', ['bdir/']],
+  ['c*', ['c', 'ca', 'cb']],
+  ['**', files],
+
+  ['\\.\\./*/', ['\\.\\./*/'], {nonull: true}],
+  ['s/\\..*//', ['s/\\..*//'], {nonull: true}],
+
+  'legendary larry crashes bashes',
+  ['/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/',
+    ['/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/'], {nonull: true}],
+  ['/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\u0001/',
+    ['/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\u0001/'], {nonull: true}],
+
+  'character classes',
+  ['[a-c]b*', ['abc', 'abd', 'abe', 'bb', 'cb']],
+  ['[a-y]*[^c]', ['abd', 'abe', 'bb', 'bcd',
+    'bdir/', 'ca', 'cb', 'dd', 'de']],
+  ['a*[^c]', ['abd', 'abe']],
+  function () { files.push('a-b', 'aXb') },
+  ['a[X-]b', ['a-b', 'aXb']],
+  function () { files.push('.x', '.y') },
+  ['[^a-c]*', ['d', 'dd', 'de']],
+  function () { files.push('a*b/', 'a*b/ooo') },
+  ['a\\*b/*', ['a*b/ooo']],
+  ['a\\*?/*', ['a*b/ooo']],
+  ['*\\\\!*', [], {null: true}, ['echo !7']],
+  ['*\\!*', ['echo !7'], null, ['echo !7']],
+  ['*.\\*', ['r.*'], null, ['r.*']],
+  ['a[b]c', ['abc']],
+  ['a[\\b]c', ['abc']],
+  ['a?c', ['abc']],
+  ['a\\*c', [], {null: true}, ['abc']],
+  ['', [''], { null: true }, ['']],
+
+  'http://www.opensource.apple.com/source/bash/bash-23/' +
+  'bash/tests/glob-test',
+  function () { files.push('man/', 'man/man1/', 'man/man1/bash.1') },
+  ['*/man*/bash.*', ['man/man1/bash.1']],
+  ['man/man1/bash.1', ['man/man1/bash.1']],
+  ['a***c', ['abc'], null, ['abc']],
+  ['a*****?c', ['abc'], null, ['abc']],
+  ['?*****??', ['abc'], null, ['abc']],
+  ['*****??', ['abc'], null, ['abc']],
+  ['?*****?c', ['abc'], null, ['abc']],
+  ['?***?****c', ['abc'], null, ['abc']],
+  ['?***?****?', ['abc'], null, ['abc']],
+  ['?***?****', ['abc'], null, ['abc']],
+  ['*******c', ['abc'], null, ['abc']],
+  ['*******?', ['abc'], null, ['abc']],
+  ['a*cd**?**??k', ['abcdecdhjk'], null, ['abcdecdhjk']],
+  ['a**?**cd**?**??k', ['abcdecdhjk'], null, ['abcdecdhjk']],
+  ['a**?**cd**?**??k***', ['abcdecdhjk'], null, ['abcdecdhjk']],
+  ['a**?**cd**?**??***k', ['abcdecdhjk'], null, ['abcdecdhjk']],
+  ['a**?**cd**?**??***k**', ['abcdecdhjk'], null, ['abcdecdhjk']],
+  ['a****c**?**??*****', ['abcdecdhjk'], null, ['abcdecdhjk']],
+  ['[-abc]', ['-'], null, ['-']],
+  ['[abc-]', ['-'], null, ['-']],
+  ['\\', ['\\'], null, ['\\']],
+  ['[\\\\]', ['\\'], null, ['\\']],
+  ['[[]', ['['], null, ['[']],
+  ['[', ['['], null, ['[']],
+  ['[*', ['[abc'], null, ['[abc']],
+
+  'a right bracket shall lose its special meaning and\n' +
+  'represent itself in a bracket expression if it occurs\n' +
+  'first in the list.  -- POSIX.2 2.8.3.2',
+  ['[]]', [']'], null, [']']],
+  ['[]-]', [']'], null, [']']],
+  ['[a-\z]', ['p'], null, ['p']],
+  ['??**********?****?', [], { null: true }, ['abc']],
+  ['??**********?****c', [], { null: true }, ['abc']],
+  ['?************c****?****', [], { null: true }, ['abc']],
+  ['*c*?**', [], { null: true }, ['abc']],
+  ['a*****c*?**', [], { null: true }, ['abc']],
+  ['a********???*******', [], { null: true }, ['abc']],
+  ['[]', [], { null: true }, ['a']],
+  ['[abc', [], { null: true }, ['[']],
+
+  'nocase tests',
+  ['XYZ', ['xYz'], { nocase: true, null: true },
+    ['xYz', 'ABC', 'IjK']],
+  [
+    'ab*',
+    ['ABC'],
+    { nocase: true, null: true },
+    ['xYz', 'ABC', 'IjK']
+  ],
+  [
+    '[ia]?[ck]',
+    ['ABC', 'IjK'],
+    { nocase: true, null: true },
+    ['xYz', 'ABC', 'IjK']
+  ],
+
+  // [ pattern, [matches], MM opts, files, TAP opts]
+  'onestar/twostar',
+  ['{/*,*}', [], {null: true}, ['/asdf/asdf/asdf']],
+  ['{/?,*}', ['/a', 'bb'], {null: true},
+    ['/a', '/b/b', '/a/b/c', 'bb']],
+
+  'dots should not match unless requested',
+  ['**', ['a/b'], {}, ['a/b', 'a/.d', '.a/.d']],
+
+  // .. and . can only match patterns starting with .,
+  // even when options.dot is set.
+  function () {
+    files = ['a/./b', 'a/../b', 'a/c/b', 'a/.d/b']
+  },
+  ['a/*/b', ['a/c/b', 'a/.d/b'], {dot: true}],
+  ['a/.*/b', ['a/./b', 'a/../b', 'a/.d/b'], {dot: true}],
+  ['a/*/b', ['a/c/b'], {dot: false}],
+  ['a/.*/b', ['a/./b', 'a/../b', 'a/.d/b'], {dot: false}],
+
+  // this also tests that changing the options needs
+  // to change the cache key, even if the pattern is
+  // the same!
+  [
+    '**',
+    ['a/b', 'a/.d', '.a/.d'],
+    { dot: true },
+    [ '.a/.d', 'a/.d', 'a/b']
+  ],
+
+  'paren sets cannot contain slashes',
+  ['*(a/b)', ['*(a/b)'], {nonull: true}, ['a/b']],
+
+  // brace sets trump all else.
+  //
+  // invalid glob pattern.  fails on bash4 and bsdglob.
+  // however, in this implementation, it's easier just
+  // to do the intuitive thing, and let brace-expansion
+  // actually come before parsing any extglob patterns,
+  // like the documentation seems to say.
+  //
+  // XXX: if anyone complains about this, either fix it
+  // or tell them to grow up and stop complaining.
+  //
+  // bash/bsdglob says this:
+  // , ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]]
+  // but we do this instead:
+  ['*(a|{b),c)}', ['a', 'ab', 'ac'], {}, ['a', 'ab', 'ac', 'ad']],
+
+  // test partial parsing in the presence of comment/negation chars
+  ['[!a*', ['[!ab'], {}, ['[!ab', '[ab']],
+  ['[#a*', ['[#ab'], {}, ['[#ab', '[ab']],
+
+  // like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped.
+  [
+    '+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g',
+    ['+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g'],
+    {},
+    ['+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g', 'a', 'b\\c']
+  ],
+
+  // crazy nested {,,} and *(||) tests.
+  function () {
+    files = [
+      'a', 'b', 'c', 'd', 'ab', 'ac', 'ad', 'bc', 'cb', 'bc,d',
+      'c,db', 'c,d', 'd)', '(b|c', '*(b|c', 'b|c', 'b|cc', 'cb|c',
+      'x(a|b|c)', 'x(a|c)', '(a|b|c)', '(a|c)'
+    ]
+  },
+  ['*(a|{b,c})', ['a', 'b', 'c', 'ab', 'ac']],
+  ['{a,*(b|c,d)}', ['a', '(b|c', '*(b|c', 'd)']],
+  // a
+  // *(b|c)
+  // *(b|d)
+  ['{a,*(b|{c,d})}', ['a', 'b', 'bc', 'cb', 'c', 'd']],
+  ['*(a|{b|c,c})', ['a', 'b', 'c', 'ab', 'ac', 'bc', 'cb']],
+
+  // test various flag settings.
+  [
+    '*(a|{b|c,c})',
+    ['x(a|b|c)', 'x(a|c)', '(a|b|c)', '(a|c)'],
+    { noext: true }
+  ],
+  [
+    'a?b',
+    ['x/y/acb', 'acb/'],
+    {matchBase: true},
+    ['x/y/acb', 'acb/', 'acb/d/e', 'x/y/acb/d']
+  ],
+  ['#*', ['#a', '#b'], {nocomment: true}, ['#a', '#b', 'c#d']],
+
+  // begin channelling Boole and deMorgan...
+  'negation tests',
+  function () {
+    files = ['d', 'e', '!ab', '!abc', 'a!b', '\\!a']
+  },
+
+  // anything that is NOT a* matches.
+  ['!a*', ['\\!a', 'd', 'e', '!ab', '!abc']],
+
+  // anything that IS !a* matches.
+  ['!a*', ['!ab', '!abc'], {nonegate: true}],
+
+  // anything that IS a* matches
+  ['!!a*', ['a!b']],
+
+  // anything that is NOT !a* matches
+  ['!\\!a*', ['a!b', 'd', 'e', '\\!a']],
+
+  // negation nestled within a pattern
+  function () {
+    files = [
+      'foo.js',
+      'foo.bar',
+      'foo.js.js',
+      'blar.js',
+      'foo.',
+      'boo.js.boo'
+    ]
+  },
+  // last one is tricky! * matches foo, . matches ., and 'js.js' != 'js'
+  // copy bash 4.3 behavior on this.
+  ['*.!(js)', ['foo.bar', 'foo.', 'boo.js.boo', 'foo.js.js'] ],
+
+  'https://github.com/isaacs/minimatch/issues/5',
+  function () {
+    files = [
+      'a/b/.x/c', 'a/b/.x/c/d', 'a/b/.x/c/d/e', 'a/b/.x', 'a/b/.x/',
+      'a/.x/b', '.x', '.x/', '.x/a', '.x/a/b', 'a/.x/b/.x/c', '.x/.x'
+    ]
+  },
+  [
+    '**/.x/**',
+    [
+      '.x/', '.x/a', '.x/a/b', 'a/.x/b', 'a/b/.x/', 'a/b/.x/c',
+      'a/b/.x/c/d', 'a/b/.x/c/d/e'
+    ]
+  ],
+
+  'https://github.com/isaacs/minimatch/issues/59',
+  ['[z-a]', []],
+  ['a/[2015-03-10T00:23:08.647Z]/z', []],
+  ['[a-0][a-\u0100]', []]
+]
+
+module.exports.regexps = [
+  '/^(?:(?=.)a[^/]*?)$/',
+  '/^(?:(?=.)X[^/]*?)$/',
+  '/^(?:(?=.)X[^/]*?)$/',
+  '/^(?:\\*)$/',
+  '/^(?:(?=.)\\*[^/]*?)$/',
+  '/^(?:\\*\\*)$/',
+  '/^(?:(?=.)b[^/]*?\\/)$/',
+  '/^(?:(?=.)c[^/]*?)$/',
+  '/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
+  '/^(?:\\.\\.\\/(?!\\.)(?=.)[^/]*?\\/)$/',
+  '/^(?:s\\/(?=.)\\.\\.[^/]*?\\/)$/',
+  '/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/1\\/)$/',
+  '/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/\u0001\\/)$/',
+  '/^(?:(?!\\.)(?=.)[a-c]b[^/]*?)$/',
+  '/^(?:(?!\\.)(?=.)[a-y][^/]*?[^c])$/',
+  '/^(?:(?=.)a[^/]*?[^c])$/',
+  '/^(?:(?=.)a[X-]b)$/',
+  '/^(?:(?!\\.)(?=.)[^a-c][^/]*?)$/',
+  '/^(?:a\\*b\\/(?!\\.)(?=.)[^/]*?)$/',
+  '/^(?:(?=.)a\\*[^/]\\/(?!\\.)(?=.)[^/]*?)$/',
+  '/^(?:(?!\\.)(?=.)[^/]*?\\\\\\![^/]*?)$/',
+  '/^(?:(?!\\.)(?=.)[^/]*?\\![^/]*?)$/',
+  '/^(?:(?!\\.)(?=.)[^/]*?\\.\\*)$/',
+  '/^(?:(?=.)a[b]c)$/',
+  '/^(?:(?=.)a[b]c)$/',
+  '/^(?:(?=.)a[^/]c)$/',
+  '/^(?:a\\*c)$/',
+  'false',
+  '/^(?:(?!\\.)(?=.)[^/]*?\\/(?=.)man[^/]*?\\/(?=.)bash\\.[^/]*?)$/',
+  '/^(?:man\\/man1\\/bash\\.1)$/',
+  '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?c)$/',
+  '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
+  '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
+  '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
+  '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
+  '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
+  '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
+  '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
+  '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c)$/',
+  '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
+  '/^(?:(?=.)a[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
+  '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
+  '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k[^/]*?[^/]*?[^/]*?)$/',
+  '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k)$/',
+  '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k[^/]*?[^/]*?)$/',
+  '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
+  '/^(?:(?!\\.)(?=.)[-abc])$/',
+  '/^(?:(?!\\.)(?=.)[abc-])$/',
+  '/^(?:\\\\)$/',
+  '/^(?:(?!\\.)(?=.)[\\\\])$/',
+  '/^(?:(?!\\.)(?=.)[\\[])$/',
+  '/^(?:\\[)$/',
+  '/^(?:(?=.)\\[(?!\\.)(?=.)[^/]*?)$/',
+  '/^(?:(?!\\.)(?=.)[\\]])$/',
+  '/^(?:(?!\\.)(?=.)[\\]-])$/',
+  '/^(?:(?!\\.)(?=.)[a-z])$/',
+  '/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
+  '/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
+  '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
+  '/^(?:(?!\\.)(?=.)[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
+  '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
+  '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
+  '/^(?:\\[\\])$/',
+  '/^(?:\\[abc)$/',
+  '/^(?:(?=.)XYZ)$/i',
+  '/^(?:(?=.)ab[^/]*?)$/i',
+  '/^(?:(?!\\.)(?=.)[ia][^/][ck])$/i',
+  '/^(?:\\/(?!\\.)(?=.)[^/]*?|(?!\\.)(?=.)[^/]*?)$/',
+  '/^(?:\\/(?!\\.)(?=.)[^/]|(?!\\.)(?=.)[^/]*?)$/',
+  '/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
+  '/^(?:a\\/(?!(?:^|\\/)\\.{1,2}(?:$|\\/))(?=.)[^/]*?\\/b)$/',
+  '/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
+  '/^(?:a\\/(?!\\.)(?=.)[^/]*?\\/b)$/',
+  '/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
+  '/^(?:(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?)$/',
+  '/^(?:(?!\\.)(?=.)[^/]*?\\(a\\/b\\))$/',
+  '/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
+  '/^(?:(?=.)\\[(?=.)\\!a[^/]*?)$/',
+  '/^(?:(?=.)\\[(?=.)#a[^/]*?)$/',
+  '/^(?:(?=.)\\+\\(a\\|[^/]*?\\|c\\\\\\\\\\|d\\\\\\\\\\|e\\\\\\\\\\\\\\\\\\|f\\\\\\\\\\\\\\\\\\|g)$/',
+  '/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
+  '/^(?:a|(?!\\.)(?=.)[^/]*?\\(b\\|c|d\\))$/',
+  '/^(?:a|(?!\\.)(?=.)(?:b|c)*|(?!\\.)(?=.)(?:b|d)*)$/',
+  '/^(?:(?!\\.)(?=.)(?:a|b|c)*|(?!\\.)(?=.)(?:a|c)*)$/',
+  '/^(?:(?!\\.)(?=.)[^/]*?\\(a\\|b\\|c\\)|(?!\\.)(?=.)[^/]*?\\(a\\|c\\))$/',
+  '/^(?:(?=.)a[^/]b)$/',
+  '/^(?:(?=.)#[^/]*?)$/',
+  '/^(?!^(?:(?=.)a[^/]*?)$).*$/',
+  '/^(?:(?=.)\\!a[^/]*?)$/',
+  '/^(?:(?=.)a[^/]*?)$/',
+  '/^(?!^(?:(?=.)\\!a[^/]*?)$).*$/',
+  '/^(?:(?!\\.)(?=.)[^\\/]*?\\.(?:(?!(?:js)$)[^\\/]*?))$/',
+  '/^(?:(?:(?!(?:\\/|^)\\.).)*?\\/\\.x\\/(?:(?!(?:\\/|^)\\.).)*?)$/',
+  '/^(?:\\[z\\-a\\])$/',
+  '/^(?:a\\/\\[2015\\-03\\-10T00:23:08\\.647Z\\]\\/z)$/',
+  '/^(?:(?=.)\\[a-0\\][a-Ā])$/'
+]
+
+Object.defineProperty(module.exports, 'files', {
+  get: function () {
+    return files
+  }
+})
diff --git a/test/redos.js b/test/redos.js
new file mode 100644
index 0000000..9430a57
--- /dev/null
+++ b/test/redos.js
@@ -0,0 +1,28 @@
+var t = require('tap')
+
+var minimatch = require('../')
+
+// utility function for generating long strings
+var genstr = function (len, chr) {
+  var result = ''
+  for (var i = 0; i <= len; i++) {
+    result = result + chr
+  }
+
+  return result
+}
+
+var exploit = '!(' + genstr(1024 * 15, '\\') + 'A)'
+
+// within the limits, and valid match
+t.ok(minimatch('A', exploit))
+
+// within the limits, but results in an invalid regexp
+exploit = '[!(' + genstr(1024 * 15, '\\') + 'A'
+t.notOk(minimatch('A', exploit))
+
+t.throws(function () {
+  // too long, throws TypeError
+  exploit = '!(' + genstr(1024 * 64, '\\') + 'A)'
+  minimatch('A', exploit)
+}, TypeError)
diff --git a/test/tricky-negations.js b/test/tricky-negations.js
new file mode 100644
index 0000000..aa66074
--- /dev/null
+++ b/test/tricky-negations.js
@@ -0,0 +1,111 @@
+var t = require('tap')
+var minimatch = require('../')
+var cases = {
+  'bar.min.js': {
+    '*.!(js|css)': true,
+    '!*.+(js|css)': false,
+    '*.+(js|css)': true
+  },
+
+  'a-integration-test.js': {
+    '*.!(j)': true,
+    '!(*-integration-test.js)': false,
+    '*-!(integration-)test.js': true,
+    '*-!(integration)-test.js': false,
+    '*!(-integration)-test.js': true,
+    '*!(-integration-)test.js': true,
+    '*!(integration)-test.js': true,
+    '*!(integration-test).js': true,
+    '*-!(integration-test).js': true,
+    '*-!(integration-test.js)': true,
+    '*-!(integra)tion-test.js': false,
+    '*-integr!(ation)-test.js': false,
+    '*-integr!(ation-t)est.js': false,
+    '*-i!(ntegration-)test.js': false,
+    '*i!(ntegration-)test.js': true,
+    '*te!(gration-te)st.js': true,
+    '*-!(integration)?test.js': false,
+    '*?!(integration)?test.js': true
+  },
+
+  'foo-integration-test.js': {
+    'foo-integration-test.js': true,
+    '!(*-integration-test.js)': false
+  },
+
+  'foo.jszzz.js': {
+    '*.!(js).js': true
+  },
+
+  'asd.jss': {
+    '*.!(js)': true
+  },
+
+  'asd.jss.xyz': {
+    '*.!(js).!(xy)': true
+  },
+
+  'asd.jss.xy': {
+    '*.!(js).!(xy)': false
+  },
+
+  'asd.js.xyz': {
+    '*.!(js).!(xy)': false
+  },
+
+  'asd.js.xy': {
+    '*.!(js).!(xy)': false
+  },
+
+  'asd.sjs.zxy': {
+    '*.!(js).!(xy)': true
+  },
+
+  'asd..xyz': {
+    '*.!(js).!(xy)': true
+  },
+
+  'asd..xy': {
+    '*.!(js).!(xy)': false,
+    '*.!(js|x).!(xy)': false
+  },
+
+  'foo.js.js': {
+    '*.!(js)': true
+  },
+
+  'testjson.json': {
+    '*(*.json|!(*.js))': true,
+    '+(*.json|!(*.js))': true,
+    '@(*.json|!(*.js))': true,
+    '?(*.json|!(*.js))': true
+  },
+
+  'foojs.js': {
+    '*(*.json|!(*.js))': false, // XXX bash 4.3 disagrees!
+    '+(*.json|!(*.js))': false, // XXX bash 4.3 disagrees!
+    '@(*.json|!(*.js))': false,
+    '?(*.json|!(*.js))': false
+  },
+
+  'other.bar': {
+    '*(*.json|!(*.js))': true,
+    '+(*.json|!(*.js))': true,
+    '@(*.json|!(*.js))': true,
+    '?(*.json|!(*.js))': true
+  }
+
+}
+
+var options = { nonegate: true }
+
+Object.keys(cases).forEach(function (file) {
+  t.test(file, function (t) {
+    Object.keys(cases[file]).forEach(function (pattern) {
+      var res = cases[file][pattern]
+      var s = file + ' ' + pattern
+      t.equal(minimatch(file, pattern, options), res, s)
+    })
+    t.end()
+  })
+})

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-minimatch.git



More information about the Pkg-javascript-commits mailing list