Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: micromatch/picomatch
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2.3.1
Choose a base ref
...
head repository: micromatch/picomatch
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 3.0.0
Choose a head ref
  • 8 commits
  • 20 files changed
  • 4 contributors

Commits on Nov 23, 2020

  1. Remove automatic windows detection, document windows option

    To make this module usable in other environments besides Node.js, it
    must not depend on node builtin modules. This PR does:
    
    1. `process.platform` is replaced via the existing but undocumented
       `windows` option which is now documented.
    2. `path.basename` is replaced by a simple regex replacement.
    
    This is a breaking change for all users who are passing backslashes to
    this module and they need to pass `process.platform === 'win32'` as the
    `windows` option to attain previous behavior.
    
    Fixes: #61
    Fixes: #64
    Fixes: #68
    silverwind committed Nov 23, 2020

    Verified

    This commit was signed with the committer’s verified signature.
    ssbarnea Sorin Sbarnea
    Copy the full SHA
    2f25761 View commit details

Commits on Oct 11, 2021

  1. Verified

    This commit was signed with the committer’s verified signature.
    ssbarnea Sorin Sbarnea
    Copy the full SHA
    385014b View commit details

Commits on Dec 11, 2021

  1. remove path completely

    acao committed Dec 11, 2021
    Copy the full SHA
    5f88af5 View commit details

Commits on Dec 13, 2021

  1. Copy the full SHA
    7e120bb View commit details

Commits on Dec 16, 2021

  1. Merge pull request #1 from acao/nodeps-posix-export

    `path.sep` + `picomatch/posix` export
    silverwind authored Dec 16, 2021
    Copy the full SHA
    ee6ebcc View commit details

Commits on Aug 17, 2022

  1. Merge pull request #96 from rphillips-nz/patch-1

    Fix one-or-more extglob examples
    jonschlinkert authored Aug 17, 2022
    Copy the full SHA
    13efdf0 View commit details

Commits on Oct 29, 2023

  1. Merge pull request #73 from silverwind/nodeps

    Remove `process` and `path` dependencies, document `windows` option
    jonschlinkert authored Oct 29, 2023
    Copy the full SHA
    49d10c4 View commit details
  2. 3.0.0

    jonschlinkert committed Oct 29, 2023
    Copy the full SHA
    bc719c4 View commit details
Showing with 2,835 additions and 2,821 deletions.
  1. +24 −4 README.md
  2. +25 −1 index.js
  3. +5 −5 lib/constants.js
  4. +2 −4 lib/parse.js
  5. +3 −4 lib/picomatch.js
  6. +16 −12 lib/utils.js
  7. +2 −1 package.json
  8. +3 −0 posix.js
  9. +17 −0 test/api.posix.js
  10. +2 −2 test/dotfiles.js
  11. +643 −647 test/extglobs-bash.js
  12. +635 −639 test/extglobs-minimatch.js
  13. +1,111 −1,115 test/extglobs-temp.js
  14. +2 −9 test/non-globs.js
  15. +54 −58 test/options.js
  16. +9 −0 test/regex-features.js
  17. +0 −5 test/slashes-posix.js
  18. +267 −273 test/slashes-windows.js
  19. +15 −25 test/special-characters.js
  20. +0 −17 test/support/index.js
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -119,6 +119,8 @@ Creates a matcher function from one or more glob patterns. The returned function

**Example**

By default, `picomatch` uses [`os.platform()`](https://nodejs.org/api/os.html#osplatform) to detect the operating system.

```js
const picomatch = require('picomatch');
// picomatch(glob[, options]);
@@ -128,6 +130,23 @@ console.log(isMatch('a.a')); //=> false
console.log(isMatch('a.b')); //=> true
```

**Example without node.js**

For environments without `node.js`, `picomatch/posix` provides you a dependency-free matcher, without automatic OS detection.

```js
const picomatch = require('picomatch/posix');
// the same API, defaulting to posix paths
const isMatch = picomatch('a/*');
console.log(isMatch('a\\b')); //=> false
console.log(isMatch('a/b')); //=> true

// you can still configure the matcher function to accept windows paths
const isMatch = picomatch('a/*', { options: windows });
console.log(isMatch('a\\b')); //=> true
console.log(isMatch('a/b')); //=> true
```

### [.test](lib/picomatch.js#L117)

Test `input` with the given `regex`. This is used by the main `picomatch()` function to test the input string.
@@ -339,6 +358,7 @@ The following options may be used with the main `picomatch()` function or any of
| `strictSlashes` | `boolean` | `undefined` | When true, picomatch won't match trailing slashes with single stars. |
| `unescape` | `boolean` | `undefined` | Remove backslashes preceding escaped characters in the glob pattern. By default, backslashes are retained. |
| `unixify` | `boolean` | `undefined` | Alias for `posixSlashes`, for backwards compatibility. |
| `windows` | `boolean` | `false` | Also accept backslashes as the path separator. |

picomatch has automatic detection for regex positive and negative lookbehinds. If the pattern contains a negative lookbehind, you must be using Node.js >= 8.10 or else picomatch will throw an error.

@@ -484,7 +504,7 @@ isMatch('baz');
| **Character** | **Description** |
| --- | --- |
| `*` | Matches any character zero or more times, excluding path separators. Does _not match_ path separators or hidden files or directories ("dotfiles"), unless explicitly enabled by setting the `dot` option to `true`. |
| `**` | Matches any character zero or more times, including path separators. Note that `**` will only match path separators (`/`, and `\\` on Windows) when they are the only characters in a path segment. Thus, `foo**/bar` is equivalent to `foo*/bar`, and `foo/a**b/bar` is equivalent to `foo/a*b/bar`, and _more than two_ consecutive stars in a glob path segment are regarded as _a single star_. Thus, `foo/***/bar` is equivalent to `foo/*/bar`. |
| `**` | Matches any character zero or more times, including path separators. Note that `**` will only match path separators (`/`, and `\\` with the `windows` option) when they are the only characters in a path segment. Thus, `foo**/bar` is equivalent to `foo*/bar`, and `foo/a**b/bar` is equivalent to `foo/a*b/bar`, and _more than two_ consecutive stars in a glob path segment are regarded as _a single star_. Thus, `foo/***/bar` is equivalent to `foo/*/bar`. |
| `?` | Matches any character excluding path separators one time. Does _not match_ path separators or leading dots. |
| `[abc]` | Matches any characters inside the brackets. For example, `[abc]` would match the characters `a`, `b` or `c`, and nothing else. |
@@ -524,9 +544,9 @@ console.log(pm.isMatch('az', 'a*(z)')); // true
console.log(pm.isMatch('azzz', 'a*(z)')); // true
// +(pattern) matches ONE or more of "pattern"
console.log(pm.isMatch('a', 'a*(z)')); // true
console.log(pm.isMatch('az', 'a*(z)')); // true
console.log(pm.isMatch('azzz', 'a*(z)')); // true
console.log(pm.isMatch('a', 'a+(z)')); // false
console.log(pm.isMatch('az', 'a+(z)')); // true
console.log(pm.isMatch('azzz', 'a+(z)')); // true
// supports multiple extglobs
console.log(pm.isMatch('foo.bar', '!(foo).!(bar)')); // false
26 changes: 25 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
'use strict';

module.exports = require('./lib/picomatch');
const os = require('os');
const pico = require('./lib/picomatch');

const isWindows = os.platform() === 'win32';

function picomatch(glob, options, returnState = false) {
// default to os.platform()
if (options && (options.windows === null || options.windows === undefined)) {
// don't mutate the original options object
options = { ...options, windows: isWindows };
}
return pico(glob, options, returnState);
}

module.exports = picomatch;
// public api
module.exports.test = pico.test;
module.exports.matchBase = pico.matchBase;
module.exports.isMatch = pico.isMatch;
module.exports.parse = pico.parse;
module.exports.scan = pico.scan;
module.exports.compileRe = pico.compileRe;
module.exports.toRegex = pico.toRegex;
// for tests
module.exports.makeRe = pico.makeRe;
10 changes: 5 additions & 5 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';

const path = require('path');
const WIN_SLASH = '\\\\/';
const WIN_NO_SLASH = `[^${WIN_SLASH}]`;

@@ -23,6 +22,7 @@ const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`;
const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`;
const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`;
const STAR = `${QMARK}*?`;
const SEP = '/';

const POSIX_CHARS = {
DOT_LITERAL,
@@ -39,7 +39,8 @@ const POSIX_CHARS = {
NO_DOTS_SLASH,
QMARK_NO_DOT,
STAR,
START_ANCHOR
START_ANCHOR,
SEP
};

/**
@@ -59,7 +60,8 @@ const WINDOWS_CHARS = {
NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`,
QMARK_NO_DOT: `[^.${WIN_SLASH}]`,
START_ANCHOR: `(?:^|[${WIN_SLASH}])`,
END_ANCHOR: `(?:[${WIN_SLASH}]|$)`
END_ANCHOR: `(?:[${WIN_SLASH}]|$)`,
SEP: '\\'
};

/**
@@ -153,8 +155,6 @@ module.exports = {
CHAR_VERTICAL_LINE: 124, /* | */
CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */

SEP: path.sep,

/**
* Create EXTGLOB_CHARS
*/
6 changes: 2 additions & 4 deletions lib/parse.js
Original file line number Diff line number Diff line change
@@ -71,10 +71,9 @@ const parse = (input, options) => {
const tokens = [bos];

const capture = opts.capture ? '' : '?:';
const win32 = utils.isWindows(options);

// create constants based on platform, for windows or posix
const PLATFORM_CHARS = constants.globChars(win32);
const PLATFORM_CHARS = constants.globChars(opts.windows);
const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);

const {
@@ -1010,7 +1009,6 @@ parse.fastpaths = (input, options) => {
}

input = REPLACEMENTS[input] || input;
const win32 = utils.isWindows(options);

// create constants based on platform, for windows or posix
const {
@@ -1023,7 +1021,7 @@ parse.fastpaths = (input, options) => {
NO_DOTS_SLASH,
STAR,
START_ANCHOR
} = constants.globChars(win32);
} = constants.globChars(opts.windows);

const nodot = opts.dot ? NO_DOTS : NO_DOT;
const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
7 changes: 3 additions & 4 deletions lib/picomatch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';

const path = require('path');
const scan = require('./scan');
const parse = require('./parse');
const utils = require('./utils');
@@ -49,7 +48,7 @@ const picomatch = (glob, options, returnState = false) => {
}

const opts = options || {};
const posix = utils.isWindows(options);
const posix = opts.windows;
const regex = isState
? picomatch.compileRe(glob, options)
: picomatch.makeRe(glob, options, false, true);
@@ -158,9 +157,9 @@ picomatch.test = (input, regex, options, { glob, posix } = {}) => {
* @api public
*/

picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => {
picomatch.matchBase = (input, glob, options) => {
const regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options);
return regex.test(path.basename(input));
return regex.test(utils.basename(input));
};

/**
28 changes: 16 additions & 12 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict';

const path = require('path');
const win32 = process.platform === 'win32';
const {
REGEX_BACKSLASH,
REGEX_REMOVE_BACKSLASH,
@@ -22,20 +20,15 @@ exports.removeBackslashes = str => {
};

exports.supportsLookbehinds = () => {
const segs = process.version.slice(1).split('.').map(Number);
if (segs.length === 3 && segs[0] >= 9 || (segs[0] === 8 && segs[1] >= 10)) {
return true;
if (typeof process !== 'undefined') {
const segs = process.version.slice(1).split('.').map(Number);
if (segs.length === 3 && segs[0] >= 9 || (segs[0] === 8 && segs[1] >= 10)) {
return true;
}
}
return false;
};

exports.isWindows = options => {
if (options && typeof options.windows === 'boolean') {
return options.windows;
}
return win32 === true || path.sep === '\\';
};

exports.escapeLast = (input, char, lastIdx) => {
const idx = input.lastIndexOf(char, lastIdx);
if (idx === -1) return input;
@@ -62,3 +55,14 @@ exports.wrapOutput = (input, state = {}, options = {}) => {
}
return output;
};

exports.basename = (path, { windows } = {}) => {
const segs = path.split(windows ? /[\\/]/ : '/');
const last = segs[segs.length - 1];

if (last === '') {
return segs[segs.length - 2];
}

return last;
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "picomatch",
"description": "Blazing fast and accurate glob matcher written in JavaScript, with no dependencies and full support for standard and extended Bash glob features, including braces, extglobs, POSIX brackets, and regular expressions.",
"version": "2.3.1",
"version": "3.0.0",
"homepage": "https://github.com/micromatch/picomatch",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"funding": "https://github.com/sponsors/jonschlinkert",
@@ -12,6 +12,7 @@
"license": "MIT",
"files": [
"index.js",
"posix.js",
"lib"
],
"main": "index.js",
3 changes: 3 additions & 0 deletions posix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = require('./lib/picomatch');
17 changes: 17 additions & 0 deletions test/api.posix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const assert = require('assert');
const picomatch = require('../posix');

describe('picomatch/posix', () => {
it('should use posix paths only by default', () => {
const match = picomatch('a/**');
assert(match('a/b'));
assert(!match('a\\b'));
});
it('should still be manually configurable to accept non-posix paths', () => {
const match = picomatch('a/**', { windows: true });
assert(match('a\\b'));
assert(match('a/b'));
});
});
4 changes: 2 additions & 2 deletions test/dotfiles.js
Original file line number Diff line number Diff line change
@@ -224,7 +224,7 @@ describe('dotfiles', () => {
assert(isMatch('abc/../abc', '*/../*'));
});

it('should not match double dots when not defined in pattern', async() => {
it('should not match double dots when not defined in pattern', async () => {
assert(!isMatch('../abc', '**/*'));
assert(!isMatch('../abc', '**/**/**'));
assert(!isMatch('../abc', '**/**/abc'));
@@ -291,7 +291,7 @@ describe('dotfiles', () => {
assert(!isMatch('abc/abc/..', 'abc/*/**/*', { strictSlashes: true }));
});

it('should not match single exclusive dots when not defined in pattern', async() => {
it('should not match single exclusive dots when not defined in pattern', async () => {
assert(!isMatch('.', '**'));
assert(!isMatch('abc/./abc', '**'));
assert(!isMatch('abc/abc/.', '**'));
1,290 changes: 643 additions & 647 deletions test/extglobs-bash.js

Large diffs are not rendered by default.

1,274 changes: 635 additions & 639 deletions test/extglobs-minimatch.js

Large diffs are not rendered by default.

2,226 changes: 1,111 additions & 1,115 deletions test/extglobs-temp.js

Large diffs are not rendered by default.

11 changes: 2 additions & 9 deletions test/non-globs.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
'use strict';

const assert = require('assert');
const support = require('./support');
const { isMatch } = require('..');

describe('non-globs', () => {
before(() => support.resetPathSep());
after(() => support.resetPathSep());
afterEach(() => support.resetPathSep());

it('should match non-globs', () => {
assert(!isMatch('/ab', '/a'));
assert(!isMatch('a/a', 'a/b'));
@@ -53,9 +48,7 @@ describe('non-globs', () => {
});

it('should match windows paths', () => {
support.windowsPathSep();
assert(isMatch('aaa\\bbb', 'aaa/bbb'));
assert(isMatch('aaa/bbb', 'aaa/bbb'));
support.resetPathSep();
assert(isMatch('aaa\\bbb', 'aaa/bbb', { windows: true }));
assert(isMatch('aaa/bbb', 'aaa/bbb', { windows: true }));
});
});
112 changes: 54 additions & 58 deletions test/options.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,52 @@
'use strict';

const assert = require('assert');
const support = require('./support');
const match = require('./support/match');
const { isMatch } = require('..');

describe('options', () => {
beforeEach(() => support.windowsPathSep());
afterEach(() => support.resetPathSep());

describe('options.matchBase', () => {
it('should match the basename of file paths when `options.matchBase` is true', () => {
assert.deepStrictEqual(match(['a/b/c/d.md'], '*.md'), [], 'should not match multiple levels');
assert.deepStrictEqual(match(['a/b/c/foo.md'], '*.md'), [], 'should not match multiple levels');
assert.deepStrictEqual(match(['ab', 'acb', 'acb/', 'acb/d/e', 'x/y/acb', 'x/y/acb/d'], 'a?b'), ['acb'], 'should not match multiple levels');
assert.deepStrictEqual(match(['a/b/c/d.md'], '*.md', { matchBase: true }), ['a/b/c/d.md']);
assert.deepStrictEqual(match(['a/b/c/foo.md'], '*.md', { matchBase: true }), ['a/b/c/foo.md']);
assert.deepStrictEqual(match(['x/y/acb', 'acb/', 'acb/d/e', 'x/y/acb/d'], 'a?b', { matchBase: true }), ['x/y/acb', 'acb/']);
assert.deepStrictEqual(match(['a/b/c/d.md'], '*.md', { windows: true }), [], 'should not match multiple levels');
assert.deepStrictEqual(match(['a/b/c/foo.md'], '*.md', { windows: true }), [], 'should not match multiple levels');
assert.deepStrictEqual(match(['ab', 'acb', 'acb/', 'acb/d/e', 'x/y/acb', 'x/y/acb/d'], 'a?b', { windows: true }), ['acb'], 'should not match multiple levels');
assert.deepStrictEqual(match(['a/b/c/d.md'], '*.md', { matchBase: true, windows: true }), ['a/b/c/d.md']);
assert.deepStrictEqual(match(['a/b/c/foo.md'], '*.md', { matchBase: true, windows: true }), ['a/b/c/foo.md']);
assert.deepStrictEqual(match(['x/y/acb', 'acb/', 'acb/d/e', 'x/y/acb/d'], 'a?b', { matchBase: true, windows: true }), ['x/y/acb', 'acb/']);
});

it('should work with negation patterns', () => {
assert(isMatch('./x/y.js', '*.js', { matchBase: true }));
assert(!isMatch('./x/y.js', '!*.js', { matchBase: true }));
assert(isMatch('./x/y.js', '**/*.js', { matchBase: true }));
assert(!isMatch('./x/y.js', '!**/*.js', { matchBase: true }));
assert(isMatch('./x/y.js', '*.js', { matchBase: true, windows: true }));
assert(!isMatch('./x/y.js', '!*.js', { matchBase: true, windows: true }));
assert(isMatch('./x/y.js', '**/*.js', { matchBase: true, windows: true }));
assert(!isMatch('./x/y.js', '!**/*.js', { matchBase: true, windows: true }));
});
});

describe('options.flags', () => {
it('should be case-sensitive by default', () => {
assert.deepStrictEqual(match(['a/b/d/e.md'], 'a/b/D/*.md'), [], 'should not match a dirname');
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/*/E.md'), [], 'should not match a basename');
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD'), [], 'should not match a file extension');
assert.deepStrictEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', { windows: true }), [], 'should not match a dirname');
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', { windows: true }), [], 'should not match a basename');
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', { windows: true }), [], 'should not match a file extension');
});

it('should not be case-sensitive when `i` is set on `options.flags`', () => {
assert.deepStrictEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', { flags: 'i' }), ['a/b/d/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', { flags: 'i' }), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', { flags: 'i' }), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', { flags: 'i', windows: true }), ['a/b/d/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', { flags: 'i', windows: true }), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', { flags: 'i', windows: true }), ['a/b/c/e.md']);
});
});

describe('options.nocase', () => {
it('should not be case-sensitive when `options.nocase` is true', () => {
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', { nocase: true }), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', { nocase: true }), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/C/*.md', { nocase: true }), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', { nocase: true }), ['a/b/d/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', { nocase: true, windows: true }), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', { nocase: true, windows: true }), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/C/*.md', { nocase: true, windows: true }), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', { nocase: true, windows: true }), ['a/b/d/e.md']);
});

it('should not double-set `i` when both `nocase` and the `i` flag are set', () => {
const opts = { nocase: true, flags: 'i' };
const opts = { nocase: true, flags: 'i', windows: true };
assert.deepStrictEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', opts), ['a/b/d/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', opts), ['a/b/c/e.md']);
assert.deepStrictEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', opts), ['a/b/c/e.md']);
@@ -59,66 +55,66 @@ describe('options', () => {

describe('options.noextglob', () => {
it('should match literal parens when noextglob is true (issue #116)', () => {
assert(isMatch('a/(dir)', 'a/(dir)', { noextglob: true }));
assert(isMatch('a/(dir)', 'a/(dir)', { noextglob: true, windows: true }));
});

it('should not match extglobs when noextglob is true', () => {
assert(!isMatch('ax', '?(a*|b)', { noextglob: true }));
assert.deepStrictEqual(match(['a.j.js', 'a.md.js'], '*.*(j).js', { noextglob: true }), ['a.j.js']);
assert.deepStrictEqual(match(['a/z', 'a/b', 'a/!(z)'], 'a/!(z)', { noextglob: true }), ['a/!(z)']);
assert.deepStrictEqual(match(['a/z', 'a/b'], 'a/!(z)', { noextglob: true }), []);
assert.deepStrictEqual(match(['c/a/v'], 'c/!(z)/v', { noextglob: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'c/a/v'], 'c/!(z)/v', { noextglob: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'c/a/v'], 'c/@(z)/v', { noextglob: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'c/a/v'], 'c/+(z)/v', { noextglob: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'c/a/v'], 'c/*(z)/v', { noextglob: true }), ['c/z/v']);
assert.deepStrictEqual(match(['c/z/v', 'z', 'zf', 'fz'], '?(z)', { noextglob: true }), ['fz']);
assert.deepStrictEqual(match(['c/z/v', 'z', 'zf', 'fz'], '+(z)', { noextglob: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'z', 'zf', 'fz'], '*(z)', { noextglob: true }), ['z', 'fz']);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a@(z)', { noextglob: true }), []);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a*@(z)', { noextglob: true }), []);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a!(z)', { noextglob: true }), []);
assert.deepStrictEqual(match(['cz', 'abz', 'az', 'azz'], 'a?(z)', { noextglob: true }), ['abz', 'azz']);
assert.deepStrictEqual(match(['cz', 'abz', 'az', 'azz', 'a+z'], 'a+(z)', { noextglob: true }), ['a+z']);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a*(z)', { noextglob: true }), ['abz', 'az']);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a**(z)', { noextglob: true }), ['abz', 'az']);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a*!(z)', { noextglob: true }), []);
assert(!isMatch('ax', '?(a*|b)', { noextglob: true, windows: true }));
assert.deepStrictEqual(match(['a.j.js', 'a.md.js'], '*.*(j).js', { noextglob: true, windows: true }), ['a.j.js']);
assert.deepStrictEqual(match(['a/z', 'a/b', 'a/!(z)'], 'a/!(z)', { noextglob: true, windows: true }), ['a/!(z)']);
assert.deepStrictEqual(match(['a/z', 'a/b'], 'a/!(z)', { noextglob: true, windows: true }), []);
assert.deepStrictEqual(match(['c/a/v'], 'c/!(z)/v', { noextglob: true, windows: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'c/a/v'], 'c/!(z)/v', { noextglob: true, windows: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'c/a/v'], 'c/@(z)/v', { noextglob: true, windows: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'c/a/v'], 'c/+(z)/v', { noextglob: true, windows: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'c/a/v'], 'c/*(z)/v', { noextglob: true, windows: true }), ['c/z/v']);
assert.deepStrictEqual(match(['c/z/v', 'z', 'zf', 'fz'], '?(z)', { noextglob: true, windows: true }), ['fz']);
assert.deepStrictEqual(match(['c/z/v', 'z', 'zf', 'fz'], '+(z)', { noextglob: true, windows: true }), []);
assert.deepStrictEqual(match(['c/z/v', 'z', 'zf', 'fz'], '*(z)', { noextglob: true, windows: true }), ['z', 'fz']);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a@(z)', { noextglob: true, windows: true }), []);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a*@(z)', { noextglob: true, windows: true }), []);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a!(z)', { noextglob: true, windows: true }), []);
assert.deepStrictEqual(match(['cz', 'abz', 'az', 'azz'], 'a?(z)', { noextglob: true, windows: true }), ['abz', 'azz']);
assert.deepStrictEqual(match(['cz', 'abz', 'az', 'azz', 'a+z'], 'a+(z)', { noextglob: true, windows: true }), ['a+z']);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a*(z)', { noextglob: true, windows: true }), ['abz', 'az']);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a**(z)', { noextglob: true, windows: true }), ['abz', 'az']);
assert.deepStrictEqual(match(['cz', 'abz', 'az'], 'a*!(z)', { noextglob: true, windows: true }), []);
});
});

describe('options.unescape', () => {
it('should remove backslashes in glob patterns:', () => {
const fixtures = ['abc', '/a/b/c', '\\a\\b\\c'];
assert.deepStrictEqual(match(fixtures, '\\a\\b\\c'), ['/a/b/c']);
assert.deepStrictEqual(match(fixtures, '\\a\\b\\c', { unescape: true }), ['abc', '/a/b/c']);
assert.deepStrictEqual(match(fixtures, '\\a\\b\\c', { unescape: false }), ['/a/b/c']);
assert.deepStrictEqual(match(fixtures, '\\a\\b\\c', { windows: true }), ['/a/b/c']);
assert.deepStrictEqual(match(fixtures, '\\a\\b\\c', { unescape: true, windows: true }), ['abc', '/a/b/c']);
assert.deepStrictEqual(match(fixtures, '\\a\\b\\c', { unescape: false, windows: true }), ['/a/b/c']);
});
});

describe('options.nonegate', () => {
it('should support the `nonegate` option:', () => {
assert.deepStrictEqual(match(['a/a/a', 'a/b/a', 'b/b/a', 'c/c/a', 'c/c/b'], '!**/a'), ['c/c/b']);
assert.deepStrictEqual(match(['a.md', '!a.md', 'a.txt'], '!*.md', { nonegate: true }), ['!a.md']);
assert.deepStrictEqual(match(['!a/a/a', '!a/a', 'a/b/a', 'b/b/a', '!c/c/a', '!c/a'], '!**/a', { nonegate: true }), ['!a/a', '!c/a']);
assert.deepStrictEqual(match(['!*.md', '.dotfile.txt', 'a/b/.dotfile'], '!*.md', { nonegate: true }), ['!*.md']);
assert.deepStrictEqual(match(['a/a/a', 'a/b/a', 'b/b/a', 'c/c/a', 'c/c/b'], '!**/a', { windows: true }), ['c/c/b']);
assert.deepStrictEqual(match(['a.md', '!a.md', 'a.txt'], '!*.md', { nonegate: true, windows: true }), ['!a.md']);
assert.deepStrictEqual(match(['!a/a/a', '!a/a', 'a/b/a', 'b/b/a', '!c/c/a', '!c/a'], '!**/a', { nonegate: true, windows: true }), ['!a/a', '!c/a']);
assert.deepStrictEqual(match(['!*.md', '.dotfile.txt', 'a/b/.dotfile'], '!*.md', { nonegate: true, windows: true }), ['!*.md']);
});
});

describe('options.windows', () => {
it('should windows file paths by default', () => {
assert.deepStrictEqual(match(['a\\b\\c.md'], '**/*.md'), ['a/b/c.md']);
assert.deepStrictEqual(match(['a\\b\\c.md'], '**/*.md', { windows: true }), ['a/b/c.md']);
assert.deepStrictEqual(match(['a\\b\\c.md'], '**/*.md', { windows: false }), ['a\\b\\c.md']);
});

it('should windows absolute paths', () => {
assert.deepStrictEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md'), ['E:/a/b/c.md']);
assert.deepStrictEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: true }), ['E:/a/b/c.md']);
assert.deepStrictEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: false }), []);
});

it('should strip leading `./`', () => {
const fixtures = ['./a', './a/a/a', './a/a/a/a', './a/a/a/a/a', './a/b', './a/x', './z/z', 'a', 'a/a', 'a/a/b', 'a/c', 'b', 'x/y'].sort();
const format = str => str.replace(/^\.\//, '');
const opts = { format };
const opts = { format, windows: true };
assert.deepStrictEqual(match(fixtures, '*', opts), ['a', 'b']);
assert.deepStrictEqual(match(fixtures, '**/a/**', opts), ['a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/b', 'a/x', 'a/a', 'a/a/b', 'a/c']);
assert.deepStrictEqual(match(fixtures, '*/*', opts), ['a/b', 'a/x', 'z/z', 'a/a', 'a/c', 'x/y']);
@@ -153,12 +149,12 @@ describe('options', () => {

describe('windows', () => {
it('should convert file paths to posix slashes', () => {
assert.deepStrictEqual(match(['a\\b\\c.md'], '**/*.md'), ['a/b/c.md']);
assert.deepStrictEqual(match(['a\\b\\c.md'], '**/*.md', { windows: true }), ['a/b/c.md']);
assert.deepStrictEqual(match(['a\\b\\c.md'], '**/*.md', { windows: false }), ['a\\b\\c.md']);
});

it('should convert absolute paths to posix slashes', () => {
assert.deepStrictEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md'), ['E:/a/b/c.md']);
assert.deepStrictEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: true }), ['E:/a/b/c.md']);
assert.deepStrictEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: false }), []);
});
});
9 changes: 9 additions & 0 deletions test/regex-features.js
Original file line number Diff line number Diff line change
@@ -311,5 +311,14 @@ describe('regex features', () => {
assert(isMatch('b ', '@(!(a \\{1,2\\}))*'));
assert(isMatch('b ', '@(!(a \\{1,2\\}))*'));
});

it('should basename paths', () => {
assert.equal(utils.basename('/a/b/c'), 'c');
assert.equal(utils.basename('/a/b/c/'), 'c');
assert.equal(utils.basename('/a\\b/c', { windows: true }), 'c');
assert.equal(utils.basename('/a\\b/c\\', { windows: true }), 'c');
assert.equal(utils.basename('\\a/b\\c', { windows: true }), 'c');
assert.equal(utils.basename('\\a/b\\c/', { windows: true }), 'c');
});
});
});
5 changes: 0 additions & 5 deletions test/slashes-posix.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
'use strict';

const assert = require('assert');
const support = require('./support');
const { isMatch } = require('..');

describe('slash handling - posix', () => {
before(() => support.resetPathSep());
after(() => support.resetPathSep());
afterEach(() => support.resetPathSep());

it('should match a literal string', () => {
assert(!isMatch('a/a', '(a/b)'));
assert(isMatch('a/b', '(a/b)'));
540 changes: 267 additions & 273 deletions test/slashes-windows.js

Large diffs are not rendered by default.

40 changes: 15 additions & 25 deletions test/special-characters.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
'use strict';

const path = require('path');
const assert = require('assert');
const support = require('./support');
const { isMatch, makeRe } = require('..');

describe('special characters', () => {
before(() => support.resetPathSep());
after(() => support.resetPathSep());
afterEach(() => support.resetPathSep());

describe('numbers', () => {
it('should match numbers in the input string', () => {
assert(!isMatch('1', '*/*'));
@@ -232,7 +226,7 @@ describe('special characters', () => {
assert(isMatch('foo(bar)baz', 'foo*baz'));
});

it('should match literal parens with brackets', async() => {
it('should match literal parens with brackets', async () => {
assert(isMatch('foo(bar)baz', 'foo[bar()]+baz'));
});

@@ -262,27 +256,23 @@ describe('special characters', () => {
});

it('should not match multiple windows directories with a single star', () => {
path.sep = '\\';
assert(isMatch('c:\\', '*{,/}'));
assert(!isMatch('C:\\Users\\', '*'));
assert(!isMatch('C:cwd\\another', '*'));
path.sep = '/';
assert(isMatch('c:\\', '*{,/}', { windows: true }));
assert(!isMatch('C:\\Users\\', '*', { windows: true }));
assert(!isMatch('C:cwd\\another', '*', { windows: true }));
});

it('should match mixed slashes on windows', () => {
path.sep = '\\';
assert(isMatch('//C://user\\docs\\Letter.txt', '**'));
assert(isMatch('//C:\\\\user/docs/Letter.txt', '**'));
assert(isMatch(':\\', '*{,/}'));
assert(isMatch(':\\', ':*{,/}'));
assert(isMatch('\\\\foo/bar', '**'));
assert(isMatch('\\\\foo/bar', '//*/*'));
assert(isMatch('\\\\unc\\admin$', '**'));
assert(isMatch('\\\\unc\\admin$', '//*/*$'));
assert(isMatch('\\\\unc\\admin$\\system32', '//*/*$/*32'));
assert(isMatch('\\\\unc\\share\\foo', '//u*/s*/f*'));
assert(isMatch('foo\\bar\\baz', 'f*/*/*'));
path.sep = '/';
assert(isMatch('//C://user\\docs\\Letter.txt', '**', { windows: true }));
assert(isMatch('//C:\\\\user/docs/Letter.txt', '**', { windows: true }));
assert(isMatch(':\\', '*{,/}', { windows: true }));
assert(isMatch(':\\', ':*{,/}', { windows: true }));
assert(isMatch('\\\\foo/bar', '**', { windows: true }));
assert(isMatch('\\\\foo/bar', '//*/*', { windows: true }));
assert(isMatch('\\\\unc\\admin$', '**', { windows: true }));
assert(isMatch('\\\\unc\\admin$', '//*/*$', { windows: true }));
assert(isMatch('\\\\unc\\admin$\\system32', '//*/*$/*32', { windows: true }));
assert(isMatch('\\\\unc\\share\\foo', '//u*/s*/f*', { windows: true }));
assert(isMatch('foo\\bar\\baz', 'f*/*/*', { windows: true }));
});

it('should match mixed slashes when options.windows is true', () => {
17 changes: 0 additions & 17 deletions test/support/index.js

This file was deleted.