From a5f6a25baaa8d76021e5b5839131c823574fc34e Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 16 Jan 2023 23:04:43 -0800 Subject: [PATCH 1/2] Handle UNC paths, add preserveMultipleSlashes This adds some slightly modified behavior when a pattern or path starts with `//` on Windows. - In the case of `//?/:/...`, the `?` is treated as a literal character, rather than a wildcard. That is, `//?/c:` will _not_ match `//x/c:`. - UNC patterns starting with `//?/:/` will match file paths starting with `:` if the drive letters match case-insensitively. - File paths starting with `//?/:/` will match file patterns starting with `:` if the drive letters match case-insensitively. Add `{preserveMultipleSlashes:true}` option to suppress the behavior where multiple consecutive `/` characters would be effectively coerced into a single path portion separator. --- README.md | 151 ++++++++++++++--------- changelog.md | 27 ++++- src/index.ts | 94 +++++++++++++-- test/basic.js | 20 +--- test/brace-expand.js | 51 ++------ test/class-edge-cases.js | 14 +-- test/consecutive-glob-stars.js | 4 +- test/defaults.js | 8 +- test/escaping.js | 26 ++-- test/nocase-magic.js | 8 +- test/patterns.js | 192 ++++++++++++++++++------------ test/preserve-multiple-slashes.ts | 10 ++ test/tricky-negations.js | 35 +++--- test/unc.ts | 69 +++++++++++ test/win-path-sep.js | 27 +++-- 15 files changed, 471 insertions(+), 265 deletions(-) create mode 100644 test/preserve-multiple-slashes.ts create mode 100644 test/unc.ts diff --git a/README.md b/README.md index 3aa96972..5a7d5f66 100644 --- a/README.md +++ b/README.md @@ -13,35 +13,35 @@ objects. // hybrid module, load with require() or import import { minimatch } from 'minimatch' // or: -const { minimatch } = require("minimatch") +const { minimatch } = require('minimatch') -minimatch("bar.foo", "*.foo") // true! -minimatch("bar.foo", "*.bar") // false! -minimatch("bar.foo", "*.+(bar|foo)", { debug: true }) // true, and noisy! +minimatch('bar.foo', '*.foo') // true! +minimatch('bar.foo', '*.bar') // false! +minimatch('bar.foo', '*.+(bar|foo)', { debug: true }) // true, and noisy! ``` ## Features Supports these glob features: -* Brace Expansion -* Extended glob matching -* "Globstar" `**` matching +- Brace Expansion +- Extended glob matching +- "Globstar" `**` matching See: -* `man sh` -* `man bash` -* `man 3 fnmatch` -* `man 5 gitignore` +- `man sh` +- `man bash` +- `man 3 fnmatch` +- `man 5 gitignore` ## Windows **Please only use forward-slashes in glob expressions.** Though windows uses either `/` or `\` as its path separator, only `/` -characters are used by this glob implementation. You must use -forward-slashes **only** in glob expressions. Back-slashes in patterns +characters are used by this glob implementation. You must use +forward-slashes **only** in glob expressions. Back-slashes in patterns will always be interpreted as escape characters, not path separators. Note that `\` or `/` _will_ be interpreted as path separators in paths on @@ -49,48 +49,71 @@ Windows, and will match against `/` in glob expressions. So just always use `/` in patterns. +### UNC Paths + +On Windows, UNC paths like `//?/c:/...` or +`//ComputerName/Share/...` are handled specially. + +- Patterns starting with a double-slash followed by some + non-slash characters will preserve their double-slash. As a + result, a pattern like `//*` will match `//x`, but not `/x`. +- Patterns staring with `//?/:` will *not* treat + the `?` as a wildcard character. Instead, it will be treated + as a normal string. +- Patterns starting with `//?/:/...` will match + file paths starting with `:/...`, and vice versa, + as if the `//?/` was not present. This behavior only is + present when the drive letters are a case-insensitive match to + one another. The remaining portions of the path/pattern are + compared case sensitively, unless `nocase:true` is set. + +Note that specifying a UNC path using `\` characters as path +separators is always allowed in the file path argument, but only +allowed in the pattern argument when `windowsPathsNoEscape: true` +is set in the options. + ## Minimatch Class Create a minimatch object by instantiating the `minimatch.Minimatch` class. ```javascript -var Minimatch = require("minimatch").Minimatch +var Minimatch = require('minimatch').Minimatch var mm = new Minimatch(pattern, options) ``` ### Properties -* `pattern` The original pattern the minimatch object represents. -* `options` The options supplied to the constructor. -* `set` A 2-dimensional array of regexp or string expressions. +- `pattern` The original pattern the minimatch object represents. +- `options` The options supplied to the constructor. +- `set` A 2-dimensional array of regexp or string expressions. Each row in the - array corresponds to a brace-expanded pattern. Each item in the row - corresponds to a single path-part. For example, the pattern + array corresponds to a brace-expanded pattern. Each item in the row + corresponds to a single path-part. For example, the pattern `{a,b/c}/d` would expand to a set of patterns like: [ [ a, d ] , [ b, c, d ] ] - If a portion of the pattern doesn't have any "magic" in it - (that is, it's something like `"foo"` rather than `fo*o?`), then it - will be left as a string rather than converted to a regular - expression. + If a portion of the pattern doesn't have any "magic" in it + (that is, it's something like `"foo"` rather than `fo*o?`), then it + will be left as a string rather than converted to a regular + expression. -* `regexp` Created by the `makeRe` method. A single regular expression - expressing the entire pattern. This is useful in cases where you wish +- `regexp` Created by the `makeRe` method. A single regular expression + expressing the entire pattern. This is useful in cases where you wish to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled. -* `negate` True if the pattern is negated. -* `comment` True if the pattern is a comment. -* `empty` True if the pattern is `""`. +- `negate` True if the pattern is negated. +- `comment` True if the pattern is a comment. +- `empty` True if the pattern is `""`. ### Methods -* `makeRe` Generate the `regexp` member if necessary, and return it. +- `makeRe` Generate the `regexp` member if necessary, and return it. Will return `false` if the pattern is invalid. -* `match(fname)` Return true if the filename matches the pattern, or +- `match(fname)` Return true if the filename matches the pattern, or false otherwise. -* `matchOne(fileArray, patternArray, partial)` Take a `/`-split - filename, and match it against a single row in the `regExpSet`. This +- `matchOne(fileArray, patternArray, partial)` Take a `/`-split + filename, and match it against a single row in the `regExpSet`. This method is mainly for internal use, but is exposed so that it can be used by a glob-walker that needs to avoid excessive filesystem calls. @@ -98,29 +121,29 @@ All other methods are internal, and will be called as necessary. ### minimatch(path, pattern, options) -Main export. Tests a path against the pattern using the options. +Main export. Tests a path against the pattern using the options. ```javascript -var isJS = minimatch(file, "*.js", { matchBase: true }) +var isJS = minimatch(file, '*.js', { matchBase: true }) ``` ### minimatch.filter(pattern, options) Returns a function that tests its -supplied argument, suitable for use with `Array.filter`. Example: +supplied argument, suitable for use with `Array.filter`. Example: ```javascript -var javascripts = fileList.filter(minimatch.filter("*.js", {matchBase: true})) +var javascripts = fileList.filter(minimatch.filter('*.js', { matchBase: true })) ``` ### minimatch.match(list, pattern, options) Match against the list of -files, in the style of fnmatch or glob. If nothing is matched, and +files, in the style of fnmatch or glob. If nothing is matched, and options.nonull is set, then return a list containing the pattern itself. ```javascript -var javascripts = minimatch.match(fileList, "*.js", {matchBase: true}) +var javascripts = minimatch.match(fileList, '*.js', { matchBase: true }) ``` ### minimatch.makeRe(pattern, options) @@ -162,13 +185,13 @@ Perform a case-insensitive match. ### nonull When a match is not found by `minimatch.match`, return a list containing -the pattern itself if this option is set. When not set, an empty list +the pattern itself if this option is set. When not set, an empty list is returned if there are no matches. ### matchBase If set, then patterns without slashes will be matched -against the basename of the path if it contains slashes. For example, +against the basename of the path if it contains slashes. For example, `a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`. ### nocomment @@ -187,73 +210,83 @@ Returns from negate expressions the same as if they were not negated. ### partial -Compare a partial path to a pattern. As long as the parts of the path that +Compare a partial path to a pattern. As long as the parts of the path that are present are not contradicted by the pattern, it will be treated as a -match. This is useful in applications where you're walking through a +match. This is useful in applications where you're walking through a folder structure, and don't yet have the full path, but want to ensure that you do not walk down paths that can never be a match. For example, ```js -minimatch('/a/b', '/a/*/c/d', { partial: true }) // true, might be /a/b/c/d -minimatch('/a/b', '/**/d', { partial: true }) // true, might be /a/b/.../d +minimatch('/a/b', '/a/*/c/d', { partial: true }) // true, might be /a/b/c/d +minimatch('/a/b', '/**/d', { partial: true }) // true, might be /a/b/.../d minimatch('/x/y/z', '/a/**/z', { partial: true }) // false, because x !== a ``` ### windowsPathsNoEscape Use `\\` as a path separator _only_, and _never_ as an escape -character. If set, all `\\` characters are replaced with `/` in -the pattern. Note that this makes it **impossible** to match +character. If set, all `\\` characters are replaced with `/` in +the pattern. Note that this makes it **impossible** to match against paths containing literal glob pattern characters, but allows matching with patterns constructed using `path.join()` and `path.resolve()` on Windows platforms, mimicking the (buggy!) -behavior of earlier versions on Windows. Please use with +behavior of earlier versions on Windows. Please use with caution, and be mindful of [the caveat about Windows paths](#windows). For legacy reasons, this is also set if `options.allowWindowsEscape` is set to the exact value `false`. +### preserveMultipleSlashes + +By default, multiple `/` characters (other than the leading `//` +in a UNC path, see "UNC Paths" above) are treated as a single +`/`. + +That is, a pattern like `a///b` will match the file path `a/b`. + +Set `preserveMultipleSlashes: true` to suppress this behavior. + ## Comparisons to other fnmatch/glob implementations While strict compliance with the existing standards is a worthwhile goal, some discrepancies exist between minimatch and other implementations, and are intentional. -If the pattern starts with a `!` character, then it is negated. Set the +If the pattern starts with a `!` character, then it is negated. Set the `nonegate` flag to suppress this behavior, and treat leading `!` -characters normally. This is perhaps relevant if you wish to start the -pattern with a negative extglob pattern like `!(a|B)`. Multiple `!` +characters normally. This is perhaps relevant if you wish to start the +pattern with a negative extglob pattern like `!(a|B)`. Multiple `!` characters at the start of a pattern will negate the pattern multiple times. If a pattern starts with `#`, then it is treated as a comment, and -will not match anything. Use `\#` to match a literal `#` at the +will not match anything. Use `\#` to match a literal `#` at the start of a line, or set the `nocomment` flag to suppress this behavior. The double-star character `**` is supported by default, unless the -`noglobstar` flag is set. This is supported in the manner of bsdglob +`noglobstar` flag is set. This is supported in the manner of bsdglob and bash 4.1, where `**` only has special significance if it is the only -thing in a path part. That is, `a/**/b` will match `a/x/y/b`, but +thing in a path part. That is, `a/**/b` will match `a/x/y/b`, but `a/**b` will not. If an escaped pattern has no matches, and the `nonull` flag is set, then minimatch.match returns the pattern as-provided, rather than -interpreting the character escapes. For example, +interpreting the character escapes. For example, `minimatch.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than -`"*a?"`. This is akin to setting the `nullglob` option in bash, except +`"*a?"`. This is akin to setting the `nullglob` option in bash, except that it does not resolve escaped pattern characters. If brace expansion is not disabled, then it is performed before any -other interpretation of the glob pattern. Thus, a pattern like +other interpretation of the glob pattern. Thus, a pattern like `+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded **first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are -checked for validity. Since those two are valid, matching proceeds. +checked for validity. Since those two are valid, matching proceeds. Note that `fnmatch(3)` in libc is an extremely naive string comparison -matcher, which does not do anything special for slashes. This library is +matcher, which does not do anything special for slashes. This library is designed to be used in glob searching and file walkers, and so it does do -special things with `/`. Thus, `foo*` will not match `foo/bar` in this +special things with `/`. Thus, `foo*` will not match `foo/bar` in this library, even though it would in `fnmatch(3)`. diff --git a/changelog.md b/changelog.md index 85ae9b83..a76c8dfa 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,26 @@ # change log +## 6.1 + +- Handle UNC paths on Windows + + This adds some slightly modified behavior when a pattern or path starts + with `//` on Windows. + + - In the case of `//?/:/...`, the `?` is treated as a + literal character, rather than a wildcard. That is, `//?/c:` will + _not_ match `//x/c:`. + - UNC patterns starting with `//?/:/` will match file paths + starting with `:` if the drive letters match + case-insensitively. + - File paths starting with `//?/:/` will match file + patterns starting with `:` if the drive letters match + case-insensitively. + +- Add `{preserveMultipleSlashes:true}` option to suppress the + behavior where multiple consecutive `/` characters would be + effectively coerced into a single path portion separator. + ## 6.0 - hybrid module supporting both `require()` and `import` @@ -9,9 +30,9 @@ - use windowsPathNoEscape/allowWindowsEscape opts - make character classes more faithful to bash glob behavior - - fix handling of escapes - - treat invalid character classes as non-matching pattern - rather than escaped literals + - fix handling of escapes + - treat invalid character classes as non-matching pattern + rather than escaped literals ## 5.0 diff --git a/src/index.ts b/src/index.ts index b38abe7d..0601010c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export interface MinimatchOptions { nocase?: boolean matchBase?: boolean flipNegate?: boolean + preserveMultipleSlashes?: boolean } export const minimatch = ( @@ -89,9 +90,6 @@ const reSpecials = charSet('().*{}+?[]^$\\!') // characters that indicate we have to add the pattern start const addPatternStartSet = charSet('[.(') -// normalizes slashes. -const slashSplit = /\/+/ - export const filter = (pattern: string, options: MinimatchOptions = {}) => (p: string) => @@ -248,6 +246,7 @@ export class Minimatch { negate: boolean comment: boolean empty: boolean + preserveMultipleSlashes: boolean partial: boolean globSet: string[] globParts: string[][] @@ -264,6 +263,7 @@ export class Minimatch { if (this.windowsPathsNoEscape) { this.pattern = this.pattern.replace(/\\/g, '/') } + this.preserveMultipleSlashes = !!options.preserveMultipleSlashes this.regexp = null this.negate = false this.nonegate = !!options.nonegate @@ -313,7 +313,7 @@ export class Minimatch { // These will be regexps, except in the case of "**", which is // set to the GLOBSTAR object for globstar behavior, // and will not contain any / characters - const rawGlobParts = this.globSet.map(s => s.split(slashSplit)) + const rawGlobParts = this.globSet.map(s => this.slashSplit(s)) // consecutive globstars are an unncessary perf killer this.globParts = this.options.noglobstar @@ -339,6 +339,22 @@ export class Minimatch { s => s.indexOf(false) === -1 ) as ParseReturnFiltered[][] + // do not treat the ? in UNC paths as magic + if (isWindows) { + for (let i = 0; i < this.set.length; i++) { + const p = this.set[i] + if ( + p[0] === '' && + p[1] === '' && + this.globParts[i][2] === '?' && + typeof p[3] === 'string' && + /^[a-z]:$/i.test(p[3]) + ) { + p[2] = '?' + } + } + } + this.debug(this.pattern, this.set) } @@ -364,10 +380,47 @@ export class Minimatch { // out of pattern, then that's fine, as long as all // the parts match. matchOne(file: string[], pattern: ParseReturn[], partial: boolean = false) { - var options = this.options + const options = this.options - this.debug('matchOne', { this: this, file: file, pattern: pattern }) + // a UNC pattern like //?/c:/* can match a path like c:/x + // and vice versa + if (isWindows) { + const fileUNC = + file[0] === '' && + file[1] === '' && + file[2] === '?' && + typeof file[3] === 'string' && + /^[a-z]:$/i.test(file[3]) + const patternUNC = + pattern[0] === '' && + pattern[1] === '' && + pattern[2] === '?' && + typeof pattern[3] === 'string' && + /^[a-z]:$/i.test(pattern[3]) + + if (fileUNC && patternUNC) { + const fd = file[3] as string + const pd = pattern[3] as string + if (fd.toLowerCase() === pd.toLowerCase()) { + file[3] = pd + } + } else if (patternUNC && typeof file[0] === 'string') { + const pd = pattern[3] as string + const fd = file[0] + if (pd.toLowerCase() === fd.toLowerCase()) { + pattern[3] = fd + pattern = pattern.slice(3) + } + } else if (fileUNC && typeof pattern[0] === 'string') { + const fd = file[3] + if (fd.toLowerCase() === pattern[0].toLowerCase()) { + pattern[0] = fd + file = file.slice(3) + } + } + } + this.debug('matchOne', this, { file, pattern }) this.debug('matchOne', file.length, pattern.length) for ( @@ -993,6 +1046,21 @@ export class Minimatch { return this.regexp } + slashSplit(p: string) { + // if p starts with // on windows, we preserve that + // so that UNC paths aren't broken. Otherwise, any number of + // / characters are coalesced into one, unless + // preserveMultipleSlashes is set to true. + if (this.preserveMultipleSlashes) { + return p.split('/') + } else if (isWindows && /^\/\/[^\/]+/.test(p)) { + // add an extra '' for the one we lose + return ['', ...p.split(/\/+/)] + } else { + return p.split(/\/+/) + } + } + match(f: string, partial = this.partial) { this.debug('match', f, this.pattern) // short-circuit in the case of busted things. @@ -1004,7 +1072,9 @@ export class Minimatch { return f === '' } - if (f === '/' && partial) return true + if (f === '/' && partial) { + return true + } const options = this.options @@ -1014,7 +1084,7 @@ export class Minimatch { } // treat the test path as a set of pathparts. - const ff = f.split(slashSplit) + const ff = this.slashSplit(f) this.debug(this.pattern, 'split', ff) // just ONE of the pattern sets in this.set needs to match @@ -1041,14 +1111,18 @@ export class Minimatch { } const hit = this.matchOne(file, pattern, partial) if (hit) { - if (options.flipNegate) return true + if (options.flipNegate) { + return true + } return !this.negate } } // didn't get any hits. this is success if it's a negative // pattern, failure otherwise. - if (options.flipNegate) return false + if (options.flipNegate) { + return false + } return this.negate } diff --git a/test/basic.js b/test/basic.js index e06f2ff5..cd385dad 100644 --- a/test/basic.js +++ b/test/basic.js @@ -37,7 +37,8 @@ t.test('basic tests', function (t) { actual.sort(alpha) t.same( - actual, expect, + actual, + expect, JSON.stringify(pattern) + ' ' + JSON.stringify(expect), tapOpts ) @@ -51,7 +52,7 @@ t.test('basic tests', function (t) { t.test('global leak test', function (t) { var globalAfter = Object.keys(global).filter(function (k) { - return (k !== '__coverage__' && k !== '__core-js_shared__') + return k !== '__coverage__' && k !== '__core-js_shared__' }) t.same(globalAfter, globalBefore, 'no new globals, please') t.end() @@ -66,16 +67,7 @@ t.test('invalid patterns', t => { t.throws(() => mm.match(['xy'], toolong), expectTooLong) const invalid = { message: 'invalid pattern' } - const invalids = [ - null, - 1234, - NaN, - Infinity, - undefined, - {a: 1}, - true, - false, - ] + const invalids = [null, 1234, NaN, Infinity, undefined, { a: 1 }, true, false] for (const i of invalids) { t.throws(() => mm.braceExpand(i), invalid) t.throws(() => new mm.Minimatch(i), invalid) @@ -113,7 +105,7 @@ t.test('whitespace handling', t => { t.test('mm debug', t => { const { error } = console - t.teardown(() => console.error = error) + t.teardown(() => (console.error = error)) const errs = [] console.error = (...msg) => errs.push(msg) t.equal(mm('a/b/c', 'a/**/@(b|c)/**', { debug: true }), true) @@ -149,7 +141,7 @@ t.test('flipNegate', t => { t.end() }) -function alpha (a, b) { +function alpha(a, b) { return a > b ? 1 : -1 } diff --git a/test/brace-expand.js b/test/brace-expand.js index e29402fc..b8731c35 100644 --- a/test/brace-expand.js +++ b/test/brace-expand.js @@ -16,60 +16,25 @@ tap.test('brace expansion', function (t) { 'afhxy', 'afhxz', 'aghxy', - 'aghxz' - ] - ], - [ - 'a{1..5}b', - [ - 'a1b', - 'a2b', - 'a3b', - 'a4b', - 'a5b' - ] + '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' - ] - ], + ['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{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' - ] + ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh'], ], - [ - '{a,b}${c}${d}', - ['a${c}${d}','b${c}${d}'] - ], - [ - '${a}${b}{c,d}', - ['${a}${b}c','${a}${b}d'] - ], + ['{a,b}${c}${d}', ['a${c}${d}', 'b${c}${d}']], + ['${a}${b}{c,d}', ['${a}${b}c', '${a}${b}d']], ] patterns.forEach(function (tc) { var p = tc[0], diff --git a/test/class-edge-cases.js b/test/class-edge-cases.js index a5a75ef9..741755d3 100644 --- a/test/class-edge-cases.js +++ b/test/class-edge-cases.js @@ -1,13 +1,7 @@ const t = require('tap') const minimatch = require('../').default -const files = [ - 'a[]b', - '[b-a]x', - 'a]b', - 'a[]]b', - 'a[[]b', -] +const files = ['a[]b', '[b-a]x', 'a]b', 'a[]]b', 'a[[]b'] const patterns = [ '\\[b-a]*', @@ -16,7 +10,7 @@ const patterns = [ '[b-a]*', 'a[]]*', 'a[[]*', - 'a\[[]]*' + 'a[[]]*', ] // uncomment and pipe stderr into a bash script to test bash behavior @@ -44,5 +38,5 @@ for (const pattern of patterns) { } } -t.comment({files, patterns, matches}) -t.matchSnapshot({files, patterns, matches}) +t.comment({ files, patterns, matches }) +t.matchSnapshot({ files, patterns, matches }) diff --git a/test/consecutive-glob-stars.js b/test/consecutive-glob-stars.js index cd5555aa..37f3ca60 100644 --- a/test/consecutive-glob-stars.js +++ b/test/consecutive-glob-stars.js @@ -1,9 +1,9 @@ const t = require('tap') -const {Minimatch, GLOBSTAR} = require('../') +const { Minimatch, GLOBSTAR } = require('../') const patterns = [] for (const a of ['**', '**/**', '**/**/**']) { - for (const b of['**', '**/**', '**/**/**']) { + for (const b of ['**', '**/**', '**/**/**']) { for (const c of ['**', '**/**', '**/**/**']) { patterns.push(`x/${a}/y/${b}/z/${c}`) } diff --git a/test/defaults.js b/test/defaults.js index 82b6e73d..d9d8458b 100644 --- a/test/defaults.js +++ b/test/defaults.js @@ -50,7 +50,7 @@ tap.test('basic tests', function (t) { tap.test('global leak test', function (t) { var globalAfter = Object.keys(global).filter(function (k) { - return (k !== '__coverage__') + return k !== '__coverage__' }) t.same(globalAfter, globalBefore, 'no new globals, please') t.end() @@ -80,13 +80,13 @@ tap.test('call defaults mm function', t => { t.same(unmm.options, { nocomment: false }) const f = defmm.filter('#nc') - t.same(['x','#nc', 'y'].filter(f), ['#nc']) - t.same(defmm.match(['x','#nc', 'y'], '#nc'), ['#nc']) + t.same(['x', '#nc', 'y'].filter(f), ['#nc']) + t.same(defmm.match(['x', '#nc', 'y'], '#nc'), ['#nc']) t.same(defmm.braceExpand('# {a,b}'), ['# a', '# b']) t.same(defmm.makeRe('# {a,b}'), /^(?:\#\ a|\#\ b)$/) t.end() }) -function alpha (a, b) { +function alpha(a, b) { return a > b ? 1 : -1 } diff --git a/test/escaping.js b/test/escaping.js index ff41a93f..c0d45957 100644 --- a/test/escaping.js +++ b/test/escaping.js @@ -3,13 +3,13 @@ var minimatch = require('../').default // test all characters with codes in range [mincc,maxcc] var mincc = 0x20 -var maxcc = 0xFF +var maxcc = 0xff // except listed in exceptions array var exceptions = ['/', '\\'] -var pre = 'x' // prepended to the testable character +var pre = 'x' // prepended to the testable character var post = 'y' // appended to the testable character -function escapeChar (cc) { +function escapeChar(cc) { return '"\\u' + ('000' + cc.toString(16).toUpperCase()).slice(-4) + '"' } @@ -19,9 +19,13 @@ tap.test('escaping tests', function (t) { if (exceptions.indexOf(cp) === -1) { var str = pre + cp + post var pattern = '*\\' + cp + '*' - var msg = JSON.stringify(str) + - ' (for codepoint ' + escapeChar(cc) + ')' + - ' should match pattern ' + JSON.stringify(pattern) + var msg = + JSON.stringify(str) + + ' (for codepoint ' + + escapeChar(cc) + + ')' + + ' should match pattern ' + + JSON.stringify(pattern) t.equal(minimatch(str, pattern), true, msg) } } @@ -34,9 +38,13 @@ tap.test('class escaping tests', function (t) { if (exceptions.indexOf(cp) === -1) { var str = pre + cp + post var pattern = '*[\\' + cp + ']*' - var msg = JSON.stringify(str) + - ' (for codepoint ' + escapeChar(cc) + ')' + - ' should match pattern ' + JSON.stringify(pattern) + var msg = + JSON.stringify(str) + + ' (for codepoint ' + + escapeChar(cc) + + ')' + + ' should match pattern ' + + JSON.stringify(pattern) t.equal(minimatch(str, pattern), true, msg) } } diff --git a/test/nocase-magic.js b/test/nocase-magic.js index b16562e3..4c22945f 100644 --- a/test/nocase-magic.js +++ b/test/nocase-magic.js @@ -4,10 +4,6 @@ const { Minimatch } = require('../').default const nomagic = '../1/2/3' const yesmagic = '../x' -t.same(new Minimatch(nomagic, { nocase: true }).set, [ - [ '..', '1', '2', '3'] -]) +t.same(new Minimatch(nomagic, { nocase: true }).set, [['..', '1', '2', '3']]) -t.same(new Minimatch(yesmagic, { nocase: true }).set, [ - ['..', /^x$/i] -]) +t.same(new Minimatch(yesmagic, { nocase: true }).set, [['..', /^x$/i]]) diff --git a/test/patterns.js b/test/patterns.js index 48c8207f..81144b8a 100644 --- a/test/patterns.js +++ b/test/patterns.js @@ -3,16 +3,27 @@ if (module === require.main) { } var files = [ - 'a', 'b', 'c', 'd', 'abc', - 'abd', 'abe', 'bb', 'bcd', - 'ca', 'cb', 'dd', 'de', - 'bdir/', 'bdir/cfile' + '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}], + ['X*', ['X*'], { nonull: true }], // allow null glob expansion ['X*', []], @@ -20,47 +31,60 @@ module.exports = [ // 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}], + ['\\*', ['\\*'], { nonull: true }], + ['\\**', ['\\**'], { nonull: true }], + ['\\*\\*', ['\\*\\*'], { nonull: true }], ['b*/', ['bdir/']], ['c*', ['c', 'ca', 'cb']], ['**', files], - ['\\.\\./*/', ['\\.\\./*/'], {nonull: true}], - ['s/\\..*//', ['s/\\..*//'], {nonull: true}], + ['\\.\\./*/', ['\\.\\./*/'], { 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}], + [ + '/^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-y]*[^c]', ['abd', 'abe', 'bb', 'bcd', 'bdir/', 'ca', 'cb', 'dd', 'de']], ['a*[^c]', ['abd', 'abe']], - function () { files.push('a-b', 'aXb') }, + function () { + files.push('a-b', 'aXb') + }, ['a[X-]b', ['a-b', 'aXb']], - function () { files.push('.x', '.y') }, + function () { + files.push('.x', '.y') + }, ['[^a-c]*', ['d', 'dd', 'de']], - function () { files.push('a*b/', 'a*b/ooo') }, + function () { + files.push('a*b/', 'a*b/ooo') + }, ['a\\*b/*', ['a*b/ooo']], ['a\\*?/*', ['a*b/ooo']], - ['*\\\\!*', [], {null: true}, ['echo !7']], + ['*\\\\!*', [], { 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']], + ['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') }, + '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']], @@ -88,11 +112,11 @@ module.exports = [ ['[*', ['[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', + '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']], + ['[a-z]', ['p'], null, ['p']], ['??**********?****?', [], { null: true }, ['abc']], ['??**********?****c', [], { null: true }, ['abc']], ['?************c****?****', [], { null: true }, ['abc']], @@ -103,26 +127,19 @@ module.exports = [ ['[abc', [], { null: true }, ['[']], 'nocase tests', - ['XYZ', ['xYz'], { nocase: true, null: true }, - ['xYz', 'ABC', 'IjK']], - [ - 'ab*', - ['ABC'], - { nocase: true, null: true }, - ['xYz', 'ABC', 'IjK'] - ], + ['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'] + ['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']], + ['{/*,*}', [], { 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']], @@ -132,23 +149,18 @@ module.exports = [ 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}], + ['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'] - ], + ['**', ['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']], + ['*(a/b)', ['*(a/b)'], { nonull: true }, ['a/b']], // brace sets trump all else. // @@ -176,15 +188,34 @@ module.exports = [ ['+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g'], {}, ['+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g', 'a', 'b\\c'], - {skip: process.platform === 'win32'}, + { skip: process.platform === 'win32' }, ], // 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', + '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']], @@ -196,18 +227,14 @@ module.exports = [ ['*(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|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'] + { matchBase: true }, + ['x/y/acb', 'acb/', 'acb/d/e', 'x/y/acb/d'], ], - ['#*', ['#a', '#b'], {nocomment: true}, ['#a', '#b', 'c#d']], + ['#*', ['#a', '#b'], { nocomment: true }, ['#a', '#b', 'c#d']], // begin channelling Boole and deMorgan... 'negation tests', @@ -219,7 +246,7 @@ module.exports = [ ['!a*', ['\\!a', 'd', 'e', '!ab', '!abc']], // anything that IS !a* matches. - ['!a*', ['!ab', '!abc'], {nonegate: true}], + ['!a*', ['!ab', '!abc'], { nonegate: true }], // anything that IS a* matches ['!!a*', ['a!b']], @@ -229,35 +256,44 @@ module.exports = [ // negation nestled within a pattern function () { - files = [ - 'foo.js', - 'foo.bar', - 'foo.js.js', - 'blar.js', - 'foo.', - 'boo.js.boo' - ] + 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'] ], + ['*.!(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' + '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' - ] + '.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', + ], ], - ['**/.x/**', ['a/.x/b'], {noglobstar: true}], + ['**/.x/**', ['a/.x/b'], { noglobstar: true }], 'https://github.com/isaacs/minimatch/issues/59', ['[z-a]', []], @@ -280,5 +316,5 @@ module.exports = [ Object.defineProperty(module.exports, 'files', { get: function () { return files - } + }, }) diff --git a/test/preserve-multiple-slashes.ts b/test/preserve-multiple-slashes.ts new file mode 100644 index 00000000..dac65573 --- /dev/null +++ b/test/preserve-multiple-slashes.ts @@ -0,0 +1,10 @@ +import t from 'tap' +import { Minimatch } from '../' + +const mm = new Minimatch('a///b', { + preserveMultipleSlashes: true, +}) + +t.same(mm.set, [['a', '', '', 'b']]) + +t.same(mm.globSet, ['a///b']) diff --git a/test/tricky-negations.js b/test/tricky-negations.js index ff1ee0a7..656621c8 100644 --- a/test/tricky-negations.js +++ b/test/tricky-negations.js @@ -4,7 +4,7 @@ var cases = { 'bar.min.js': { '*.!(js|css)': true, '!*.+(js|css)': false, - '*.+(js|css)': true + '*.+(js|css)': true, }, 'a-integration-test.js': { @@ -25,76 +25,75 @@ var cases = { '*i!(ntegration-)test.js': true, '*te!(gration-te)st.js': true, '*-!(integration)?test.js': false, - '*?!(integration)?test.js': true + '*?!(integration)?test.js': true, }, 'foo-integration-test.js': { 'foo-integration-test.js': true, - '!(*-integration-test.js)': false + '!(*-integration-test.js)': false, }, 'foo.jszzz.js': { - '*.!(js).js': true + '*.!(js).js': true, }, 'asd.jss': { - '*.!(js)': true + '*.!(js)': true, }, 'asd.jss.xyz': { - '*.!(js).!(xy)': true + '*.!(js).!(xy)': true, }, 'asd.jss.xy': { - '*.!(js).!(xy)': false + '*.!(js).!(xy)': false, }, 'asd.js.xyz': { - '*.!(js).!(xy)': false + '*.!(js).!(xy)': false, }, 'asd.js.xy': { - '*.!(js).!(xy)': false + '*.!(js).!(xy)': false, }, 'asd.sjs.zxy': { - '*.!(js).!(xy)': true + '*.!(js).!(xy)': true, }, 'asd..xyz': { - '*.!(js).!(xy)': true + '*.!(js).!(xy)': true, }, 'asd..xy': { '*.!(js).!(xy)': false, - '*.!(js|x).!(xy)': false + '*.!(js|x).!(xy)': false, }, 'foo.js.js': { - '*.!(js)': true + '*.!(js)': true, }, 'testjson.json': { '*(*.json|!(*.js))': true, '+(*.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 + '?(*.json|!(*.js))': false, }, 'other.bar': { '*(*.json|!(*.js))': true, '+(*.json|!(*.js))': true, '@(*.json|!(*.js))': true, - '?(*.json|!(*.js))': true - } - + '?(*.json|!(*.js))': true, + }, } var options = { nonegate: true } diff --git a/test/unc.ts b/test/unc.ts new file mode 100644 index 00000000..bcd98661 --- /dev/null +++ b/test/unc.ts @@ -0,0 +1,69 @@ +process.env.__MINIMATCH_TESTING_PLATFORM__ = 'win32' +import t from 'tap' +import { minimatch, Minimatch, MinimatchOptions } from '../' + +t.test('UNC patterns do not lose their //', async t => { + const share = new Minimatch('//host/share/*') + t.match(share.set, [['', '', 'host', 'share', RegExp]]) + const uncPath = new Minimatch('//?/d:/*') + t.match(uncPath.set, [['', '', '?', 'd:', RegExp]]) +}) + +type Case = [f: string, p: string, e: boolean, opt?: MinimatchOptions] +const cases: Case[] = [ + ['c:/x', '//?/c:/*', true], + ['//?/c:/x', 'c:/*', true], + ['//?/c:/x', '//?/c:/*', true], + ['C:/x', '//?/c:/*', true], + ['//?/C:/x', 'c:/*', true], + ['//?/C:/x', '//?/c:/*', true], + ['c:/x', '//?/C:/*', true], + ['//?/c:/x', 'C:/*', true], + ['//?/c:/x', '//?/C:/*', true], + + ['d:/x', '//?/c:/*', false], + ['//?/d:/x', 'c:/*', false], + ['//?/d:/x', '//?/c:/*', false], + + // ? is not a wild card + ['//x/c:/x', '//?/c:/*', false], + + // but UNC remote paths do match + ['//x/y/z', '//x/y/*', true], +] + +t.test('UNC drive letter paths match normal paths', async t => { + for (const [file, pattern, expect, opt = {}] of cases) { + t.test(`f=${file} p=${pattern}`, t => { + t.test('/ only', t => { + t.equal(minimatch(file, pattern, opt), expect) + t.end() + }) + t.test('file \\', t => { + t.equal(minimatch(file.replace(/\//g, '\\'), pattern, opt), expect) + t.end() + }) + t.test('pattern \\', t => { + t.equal( + minimatch(file, pattern.replace(/\//g, '\\'), { + ...opt, + windowsPathsNoEscape: true, + }), + expect + ) + t.end() + }) + t.test('both \\', t => { + t.equal( + minimatch(file.replace(/\//g, '\\'), pattern.replace(/\//g, '\\'), { + ...opt, + windowsPathsNoEscape: true, + }), + expect + ) + t.end() + }) + t.end() + }) + } +}) diff --git a/test/win-path-sep.js b/test/win-path-sep.js index ac4d988a..4dcae24c 100644 --- a/test/win-path-sep.js +++ b/test/win-path-sep.js @@ -19,19 +19,28 @@ t.test('override with options', t => { process.env.__MINIMATCH_TESTING_PLATFORM__ = 'win32' const mm = t.mock('../', {}).default - t.equal(mm('c:\\foo\\bar', 'c:\\foo\\*', { - windowsPathsNoEscape: true, - }), true) + t.equal( + mm('c:\\foo\\bar', 'c:\\foo\\*', { + windowsPathsNoEscape: true, + }), + true + ) - t.equal(mm('c:\\foo\\bar', 'c:\\foo\\*', { - allowWindowsEscape: false, - }), true) + t.equal( + mm('c:\\foo\\bar', 'c:\\foo\\*', { + allowWindowsEscape: false, + }), + true + ) t.equal(mm('c:\\foo\\bar', 'c:\\foo\\*', {}), false) - t.equal(mm('c:\\foo\\bar', 'c:\\foo\\*', { - allowWindowsEscape: null, - }), false) + t.equal( + mm('c:\\foo\\bar', 'c:\\foo\\*', { + allowWindowsEscape: null, + }), + false + ) t.end() }) From 0e7b19e667c0aac25271e3a55992fc3f8692d1cc Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 16 Jan 2023 23:10:47 -0800 Subject: [PATCH 2/2] 6.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76e24495..f5457c7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minimatch", - "version": "6.0.4", + "version": "6.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minimatch", - "version": "6.0.4", + "version": "6.1.0", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" diff --git a/package.json b/package.json index 5d2d3cb3..cc9de401 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Isaac Z. Schlueter (http://blog.izs.me)", "name": "minimatch", "description": "a glob matcher in javascript", - "version": "6.0.4", + "version": "6.1.0", "repository": { "type": "git", "url": "git://github.com/isaacs/minimatch.git"