From 102939138c7c6ffefc46df9dfaee45cf2e455b97 Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 17 Jan 2023 07:20:12 -0800 Subject: [PATCH 1/4] test verifying that accented chars work in nocase:true Re: https://github.com/isaacs/node-glob/issues/394 --- tap-snapshots/test/basic.js.test.cjs | 32 ++++++++++++++++++++++++++++ test/patterns.js | 25 ++++++++++++---------- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/tap-snapshots/test/basic.js.test.cjs b/tap-snapshots/test/basic.js.test.cjs index becc8ff4..6cfc9590 100644 --- a/tap-snapshots/test/basic.js.test.cjs +++ b/tap-snapshots/test/basic.js.test.cjs @@ -428,3 +428,35 @@ exports[`test/basic.js TAP basic tests > makeRe {a,*(b|c,d)} 1`] = ` exports[`test/basic.js TAP basic tests > makeRe {a,*(b|{c,d})} 1`] = ` /^(?:a|(?!\\.)(?=.)(?:b|c)*|(?!\\.)(?=.)(?:b|d)*)$/ ` + +exports[`test/basic.js TAP basic tests > makeRe Å 1`] = ` +/^(?:Å)$/i +` + +exports[`test/basic.js TAP basic tests > makeRe Å 2`] = ` +/^(?:Å)$/ +` + +exports[`test/basic.js TAP basic tests > makeRe Å 3`] = ` +/^(?:Å)$/ +` + +exports[`test/basic.js TAP basic tests > makeRe Å 4`] = ` +/^(?:Å)$/i +` + +exports[`test/basic.js TAP basic tests > makeRe å 1`] = ` +/^(?:å)$/ +` + +exports[`test/basic.js TAP basic tests > makeRe å 2`] = ` +/^(?:å)$/i +` + +exports[`test/basic.js TAP basic tests > makeRe å 3`] = ` +/^(?:å)$/i +` + +exports[`test/basic.js TAP basic tests > makeRe å 4`] = ` +/^(?:å)$/ +` diff --git a/test/patterns.js b/test/patterns.js index 12b7e785..096680f6 100644 --- a/test/patterns.js +++ b/test/patterns.js @@ -314,19 +314,22 @@ module.exports = [ // https://github.com/isaacs/node-glob/issues/415 () => { - files = [ - 'ac', - 'abc', - 'acd', - 'acc', - 'acd', - 'adc', - 'bbc', - 'bac', - 'bcc', - ] + files = ['ac', 'abc', 'acd', 'acc', 'acd', 'adc', 'bbc', 'bac', 'bcc'] }, ['+(a)!(b)+(c)', ['ac', 'acc', 'adc']], + + // https://github.com/isaacs/node-glob/issues/394 + () => (files = ['å']), + ['å', ['å']], + ['å', ['å'], { nocase: true }], + ['Å', ['å'], { nocase: true }], + ['Å', [], {}], + + () => (files = ['Å']), + ['Å', ['Å']], + ['å', ['Å'], { nocase: true }], + ['Å', ['Å'], { nocase: true }], + ['å', [], {}], ] Object.defineProperty(module.exports, 'files', { From f35d0b8244d96eada702d83319aa44565e35f297 Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 17 Jan 2023 09:22:41 -0800 Subject: [PATCH 2/4] Detect leading dots in extglob subpatterns Fix: https://github.com/isaacs/node-glob/issues/387 Will backport to v4 and v5 --- src/index.ts | 62 +++++++++++++++++------- tap-snapshots/test/basic.js.test.cjs | 56 ++++++++++++++++++---- test/patterns.js | 72 +++++++++++++++------------- 3 files changed, 131 insertions(+), 59 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3ff35b16..9941efbf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -615,12 +615,20 @@ export class Minimatch { let pl: PatternListEntry | undefined let sp: SubparseReturn // . and .. never match anything that doesn't start with ., - // even when options.dot is set. - const patternStart = - pattern.charAt(0) === '.' - ? '' // anything - : // not (start or / followed by . or .. followed by / or end) - options.dot + // even when options.dot is set. However, if the pattern + // starts with ., then traversal patterns can match. + let dotTravAllowed = pattern.charAt(0) === '.' + let dotFileAllowed = options.dot || dotTravAllowed + const patternStart = () => + dotTravAllowed + ? '' + : dotFileAllowed + ? '(?!(?:^|\\/)\\.{1,2}(?:$|\\/))' + : '(?!\\.)' + const subPatternStart = (p: string) => + p.charAt(0) === '.' + ? '' + : options.dot ? '(?!(?:^|\\/)\\.{1,2}(?:$|\\/))' : '(?!\\.)' @@ -719,7 +727,7 @@ export class Minimatch { if (options.noext) clearStateChar() continue - case '(': + case '(': { if (inClass) { re += '(' continue @@ -730,28 +738,38 @@ export class Minimatch { continue } - patternListStack.push({ + const plEntry: PatternListEntry = { type: stateChar, start: i - 1, reStart: re.length, open: plTypes[stateChar].open, close: plTypes[stateChar].close, - }) - // negation is (?:(?!js)[^/]*) - re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + } + this.debug(this.pattern, '\t', plEntry) + patternListStack.push(plEntry) + // negation is (?:(?!(?:js)(?:))[^/]*) + re += plEntry.open + // next entry starts with a dot maybe? + if (plEntry.start === 0 && plEntry.type !== '!') { + dotTravAllowed = true + re += subPatternStart(pattern.slice(i + 1)) + } this.debug('plType %j %j', stateChar, re) stateChar = false continue + } - case ')': - if (inClass || !patternListStack.length) { + case ')': { + const plEntry = patternListStack.pop() + if (inClass || !plEntry) { re += '\\)' continue } + // closing an extglob clearStateChar() hasMagic = true - pl = patternListStack.pop() as PatternListEntry + pl = plEntry // negation is (?:(?!js)[^/]*) // The others are (?:) re += pl.close @@ -759,16 +777,24 @@ export class Minimatch { negativeLists.push(Object.assign(pl, { reEnd: re.length })) } continue + } - case '|': - if (inClass || !patternListStack.length) { + case '|': { + const plEntry = patternListStack[patternListStack.length - 1] + if (inClass || !plEntry) { re += '\\|' continue } clearStateChar() re += '|' + // next subpattern can start with a dot? + if (plEntry.start === 0 && plEntry.type !== '!') { + dotTravAllowed = true + re += subPatternStart(pattern.slice(i + 1)) + } continue + } // these are mostly the same in regexp and glob case '[': @@ -852,7 +878,7 @@ export class Minimatch { for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { let tail: string tail = re.slice(pl.reStart + pl.open.length) - this.debug('setting tail', re, pl) + this.debug(this.pattern, 'setting tail', re, pl) // maybe some even number of \, then maybe 1 \, followed by a | tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, (_, $1, $2) => { if (!$2) { @@ -928,7 +954,7 @@ export class Minimatch { } if (addPatternStart) { - re = patternStart + re + re = patternStart() + re } // parsing just a piece of a larger pattern. diff --git a/tap-snapshots/test/basic.js.test.cjs b/tap-snapshots/test/basic.js.test.cjs index 6cfc9590..1af77ff1 100644 --- a/tap-snapshots/test/basic.js.test.cjs +++ b/tap-snapshots/test/basic.js.test.cjs @@ -13,6 +13,10 @@ exports[`test/basic.js TAP basic tests > makeRe !!a* 1`] = ` /^(?:(?=.)a[^/]*?)$/ ` +exports[`test/basic.js TAP basic tests > makeRe !(.a|js)@(.*) 1`] = ` +/^(?:(?!\\.)(?=.)(?:(?!(?:\\.a|js)(?:\\.[^/]*?))[^/]*?)(?:\\.[^/]*?))$/ +` + exports[`test/basic.js TAP basic tests > makeRe !\\!a* 1`] = ` /^(?!^(?:(?=.)\\!a[^/]*?)$).*$/ ` @@ -33,20 +37,24 @@ exports[`test/basic.js TAP basic tests > makeRe #* 1`] = ` /^(?:(?=.)#[^/]*?)$/ ` +exports[`test/basic.js TAP basic tests > makeRe * 1`] = ` +/^(?:(?!(?:^|\\/)\\.{1,2}(?:$|\\/))(?=.)[^/]*?)$/ +` + exports[`test/basic.js TAP basic tests > makeRe *(a/b) 1`] = ` -/^(?:(?!\\.)(?=.)[^/]*?\\(a\\/b\\))$/ +/^(?:(?=.)[^/]*?\\((?!\\.)a\\/b\\))$/ ` exports[`test/basic.js TAP basic tests > makeRe *(a|{b),c)} 1`] = ` -/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/ +/^(?:(?=.)(?:(?!\\.)a|(?!\\.)b)*|(?=.)(?:(?!\\.)a|(?!\\.)c)*)$/ ` exports[`test/basic.js TAP basic tests > makeRe *(a|{b,c}) 1`] = ` -/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/ +/^(?:(?=.)(?:(?!\\.)a|(?!\\.)b)*|(?=.)(?:(?!\\.)a|(?!\\.)c)*)$/ ` exports[`test/basic.js TAP basic tests > makeRe *(a|{b|c,c}) 1`] = ` -/^(?:(?!\\.)(?=.)(?:a|b|c)*|(?!\\.)(?=.)(?:a|c)*)$/ +/^(?:(?=.)(?:(?!\\.)a|(?!\\.)b|(?!\\.)c)*|(?=.)(?:(?!\\.)a|(?!\\.)c)*)$/ ` exports[`test/basic.js TAP basic tests > makeRe *(a|{b|c,c}) 2`] = ` @@ -110,11 +118,15 @@ exports[`test/basic.js TAP basic tests > makeRe *c*?** 1`] = ` ` exports[`test/basic.js TAP basic tests > makeRe +(a)!(b)+(c) 1`] = ` -/^(?:(?!\\.)(?=.)(?:a)+(?:(?!(?:b)(?:c)+)[^/]*?)(?:c)+)$/ +/^(?:(?=.)(?:(?!\\.)a)+(?:(?!(?:b)(?:c)+)[^/]*?)(?:c)+)$/ ` exports[`test/basic.js TAP basic tests > makeRe +(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g 1`] = ` -/^(?:(?=.)\\+\\(a\\|[^/]*?\\|c\\\\\\\\\\|d\\\\\\\\\\|e\\\\\\\\\\\\\\\\\\|f\\\\\\\\\\\\\\\\\\|g)$/ +/^(?:(?=.)\\+\\((?!\\.)a\\|(?!\\.)[^/]*?\\|c\\\\\\\\\\|(?!\\.)d\\\\\\\\\\|e\\\\\\\\\\\\\\\\\\|(?!\\.)f\\\\\\\\\\\\\\\\\\|g)$/ +` + +exports[`test/basic.js TAP basic tests > makeRe .* 1`] = ` +/^(?:(?=.)\\.[^/]*?)$/ ` exports[`test/basic.js TAP basic tests > makeRe /^root:/{s/^[^:]*:[^:]*:([^:]*).*$// 1`] = ` @@ -157,6 +169,34 @@ exports[`test/basic.js TAP basic tests > makeRe ??**********?****c 1`] = ` /^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/ ` +exports[`test/basic.js TAP basic tests > makeRe @(*|.*) 1`] = ` +/^(?:(?=.)(?:(?!\\.)[^/]*?|\\.[^/]*?))$/ +` + +exports[`test/basic.js TAP basic tests > makeRe @(*|a) 1`] = ` +/^(?:(?=.)(?:(?!(?:^|\\/)\\.{1,2}(?:$|\\/))[^/]*?|(?!(?:^|\\/)\\.{1,2}(?:$|\\/))a))$/ +` + +exports[`test/basic.js TAP basic tests > makeRe @(.*) 1`] = ` +/^(?:(?=.)(?:\\.[^/]*?))$/ +` + +exports[`test/basic.js TAP basic tests > makeRe @(.*) 2`] = ` +/^(?:(?=.)(?:\\.[^/]*?))$/ +` + +exports[`test/basic.js TAP basic tests > makeRe @(.*|*) 1`] = ` +/^(?:(?=.)(?:\\.[^/]*?|(?!\\.)[^/]*?))$/ +` + +exports[`test/basic.js TAP basic tests > makeRe @(.*|js) 1`] = ` +/^(?:(?=.)(?:\\.[^/]*?|(?!\\.)js))$/ +` + +exports[`test/basic.js TAP basic tests > makeRe @(js|.*) 1`] = ` +/^(?:(?=.)(?:(?!\\.)js|\\.[^/]*?))$/ +` + exports[`test/basic.js TAP basic tests > makeRe X* 1`] = ` /^(?:(?=.)X[^/]*?)$/ ` @@ -422,11 +462,11 @@ exports[`test/basic.js TAP basic tests > makeRe {/?,*} 1`] = ` ` exports[`test/basic.js TAP basic tests > makeRe {a,*(b|c,d)} 1`] = ` -/^(?:a|(?!\\.)(?=.)[^/]*?\\(b\\|c|d\\))$/ +/^(?:a|(?=.)[^/]*?\\((?!\\.)b\\|(?!\\.)c|d\\))$/ ` exports[`test/basic.js TAP basic tests > makeRe {a,*(b|{c,d})} 1`] = ` -/^(?:a|(?!\\.)(?=.)(?:b|c)*|(?!\\.)(?=.)(?:b|d)*)$/ +/^(?:a|(?=.)(?:(?!\\.)b|(?!\\.)c)*|(?=.)(?:(?!\\.)b|(?!\\.)d)*)$/ ` exports[`test/basic.js TAP basic tests > makeRe Å 1`] = ` diff --git a/test/patterns.js b/test/patterns.js index 096680f6..fddbe812 100644 --- a/test/patterns.js +++ b/test/patterns.js @@ -58,17 +58,11 @@ module.exports = [ ['[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') - }, + () => files.push('a-b', 'aXb'), ['a[X-]b', ['a-b', 'aXb']], - function () { - files.push('.x', '.y') - }, + () => files.push('.x', '.y'), ['[^a-c]*', ['d', 'dd', 'de']], - function () { - files.push('a*b/', 'a*b/ooo') - }, + () => files.push('a*b/', 'a*b/ooo'), ['a\\*b/*', ['a*b/ooo']], ['a\\*?/*', ['a*b/ooo']], ['*\\\\!*', [], { null: true }, ['echo !7']], @@ -82,9 +76,7 @@ module.exports = [ 'http://www.opensource.apple.com/source/bash/bash-23/' + 'bash/tests/glob-test', - function () { - files.push('man/', 'man/man1/', 'man/man1/bash.1') - }, + () => 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']], @@ -146,9 +138,7 @@ module.exports = [ // .. 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'] - }, + () => (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 }], @@ -192,8 +182,8 @@ module.exports = [ ], // crazy nested {,,} and *(||) tests. - function () { - files = [ + () => + (files = [ 'a', 'b', 'c', @@ -216,8 +206,7 @@ module.exports = [ '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 @@ -238,9 +227,7 @@ module.exports = [ // begin channelling Boole and deMorgan... 'negation tests', - function () { - files = ['d', 'e', '!ab', '!abc', 'a!b', '\\!a'] - }, + () => (files = ['d', 'e', '!ab', '!abc', 'a!b', '\\!a']), // anything that is NOT a* matches. ['!a*', ['\\!a', 'd', 'e', '!ab', '!abc']], @@ -254,17 +241,23 @@ module.exports = [ // 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'] - }, + 'negation nestled within a pattern', + () => + (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 = [ + () => + (files = [ 'a/b/.x/c', 'a/b/.x/c/d', 'a/b/.x/c/d/e', @@ -277,8 +270,7 @@ module.exports = [ '.x/a/b', 'a/.x/b/.x/c', '.x/.x', - ] - }, + ]), [ '**/.x/**', [ @@ -312,24 +304,38 @@ module.exports = [ ['[\\-\\]]', []], ['[a-b-c]', []], - // https://github.com/isaacs/node-glob/issues/415 + 'https://github.com/isaacs/node-glob/issues/415', () => { files = ['ac', 'abc', 'acd', 'acc', 'acd', 'adc', 'bbc', 'bac', 'bcc'] }, ['+(a)!(b)+(c)', ['ac', 'acc', 'adc']], - // https://github.com/isaacs/node-glob/issues/394 + 'https://github.com/isaacs/node-glob/issues/394', () => (files = ['å']), ['å', ['å']], ['å', ['å'], { nocase: true }], ['Å', ['å'], { nocase: true }], ['Å', [], {}], - () => (files = ['Å']), ['Å', ['Å']], ['å', ['Å'], { nocase: true }], ['Å', ['Å'], { nocase: true }], ['å', [], {}], + + 'https://github.com/isaacs/node-glob/issues/387', + () => (files = ['.a', '.a.js', '.js', 'a', 'a.js', 'js']), + ['.*', ['.a', '.a.js', '.js']], + ['*', ['.a', '.a.js', '.js', 'a', 'a.js', 'js'], { dot: true }], + ['@(*|.*)', ['.a', '.a.js', '.js', 'a', 'a.js', 'js']], + ['@(.*|*)', ['.a', '.a.js', '.js', 'a', 'a.js', 'js']], + ['@(*|a)', ['.a', '.a.js', '.js', 'a', 'a.js', 'js'], { dot: true }], + ['@(.*)', ['.a', '.a.js', '.js']], + ['@(.*)', ['.a', '.a.js', '.js'], { dot: true }], + ['@(js|.*)', ['js', '.a', '.a.js', '.js']], + ['@(.*|js)', ['js', '.a', '.a.js', '.js']], + // doesn't start at 0, no dice + // neg extglobs don't trigger this behavior. + ['!(.a|js)@(.*)', ['a.js'], { nonegate: true }], ] Object.defineProperty(module.exports, 'files', { From 312360f4131bcec7fc87d2d601ca29afbe1a5d9d Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 17 Jan 2023 09:23:36 -0800 Subject: [PATCH 3/4] formatting --- README.md | 10 +++++----- changelog.md | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 061f909f..3d76a142 100644 --- a/README.md +++ b/README.md @@ -60,16 +60,16 @@ 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 + 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 +- 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 + 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 + 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 diff --git a/changelog.md b/changelog.md index a76c8dfa..12cd6dee 100644 --- a/changelog.md +++ b/changelog.md @@ -4,18 +4,18 @@ - 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. + 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 From 9ddf1df5d987988378a9e557a26f96011d673b8a Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 17 Jan 2023 09:23:46 -0800 Subject: [PATCH 4/4] 6.1.3 --- 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 5997349e..bc87a5c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minimatch", - "version": "6.1.2", + "version": "6.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minimatch", - "version": "6.1.2", + "version": "6.1.3", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" diff --git a/package.json b/package.json index 285c8553..440eaad7 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.1.2", + "version": "6.1.3", "repository": { "type": "git", "url": "git://github.com/isaacs/minimatch.git"