Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 61 additions & 29 deletions doc/api/path.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,22 @@

A [`TypeError`][] is thrown if `path` is not a string.

## `path.escapeGlob(pattern)`

<!-- YAML
added: REPLACEME
-->

* `pattern` {string}
* Returns: {string}

The `path.escapeGlob()` method escapes glob characters in a `pattern`.

```js
path.escapeGlob('foo*bar');
// Returns: 'foo[*]bar'
```

## `path.extname(path)`

<!-- YAML
Expand Down Expand Up @@ -283,35 +299,6 @@
// Returns: 'C:\\path\\dir\\file.txt'
```

## `path.matchesGlob(path, pattern)`

<!-- YAML
added:
- v22.5.0
- v20.17.0
changes:
- version:
- v24.8.0
- v22.20.0
pr-url: https://github.com/nodejs/node/pull/59572
description: Marking the API stable.
-->

* `path` {string} The path to glob-match against.
* `pattern` {string} The glob to check the path against.
* Returns: {boolean} Whether or not the `path` matched the `pattern`.

The `path.matchesGlob()` method determines if `path` matches the `pattern`.

For example:

```js
path.matchesGlob('/foo/bar', '/foo/*'); // true
path.matchesGlob('/foo/bar*', 'foo/bird'); // false
```

A [`TypeError`][] is thrown if `path` or `pattern` are not strings.

## `path.isAbsolute(path)`

<!-- YAML
Expand Down Expand Up @@ -376,6 +363,35 @@

A [`TypeError`][] is thrown if any of the path segments is not a string.

## `path.matchesGlob(path, pattern)`

<!-- YAML
added:
- v22.5.0
- v20.17.0
changes:
- version:
- v24.8.0
- v22.20.0
pr-url: https://github.com/nodejs/node/pull/59572

Check warning on line 376 in doc/api/path.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Marking the API stable.
-->

* `path` {string} The path to glob-match against.
* `pattern` {string} The glob to check the path against.
* Returns: {boolean} Whether or not the `path` matched the `pattern`.

The `path.matchesGlob()` method determines if `path` matches the `pattern`.

For example:

```js
path.matchesGlob('/foo/bar', '/foo/*'); // true
path.matchesGlob('/foo/bar*', 'foo/bird'); // false
```

A [`TypeError`][] is thrown if `path` or `pattern` are not strings.

## `path.normalize(path)`

<!-- YAML
Expand Down Expand Up @@ -640,6 +656,22 @@
This method is meaningful only on Windows systems. On POSIX systems, the
method is non-operational and always returns `path` without modifications.

## `path.unescapeGlob(pattern)`

<!-- YAML
added: REPLACEME
-->

* `pattern` {string}
* Returns: {string}

The `path.unescapeGlob()` method unescapes the given glob pattern.

```js
path.unescapeGlob('foo[*]bar');
// Returns: 'foo*bar'
```

## `path.win32`

<!-- YAML
Expand Down
28 changes: 28 additions & 0 deletions lib/internal/fs/glob.js
Original file line number Diff line number Diff line change
Expand Up @@ -794,8 +794,36 @@ function matchGlobPattern(path, pattern, windows = isWindows) {
});
}

/**
* Escape special glob characters in a pattern
* @param {string} pattern the glob pattern to escape
* @returns {string} the escaped glob pattern
*/
function escapeGlobPattern(pattern) {
validateString(pattern, 'pattern');
return lazyMinimatch().escape(pattern, {
windowsPathsNoEscape: true,
magicalBraces: true,
});
}

/**
* Restore special glob characters in a pattern
* @param {string} pattern the glob pattern to unescape
* @returns {string} the unescaped glob pattern
*/
function unescapeGlobPattern(pattern) {
validateString(pattern, 'pattern');
return lazyMinimatch().unescape(pattern, {
windowsPathsNoEscape: true,
magicalBraces: true,
});
}

module.exports = {
__proto__: null,
Glob,
escapeGlobPattern,
matchGlobPattern,
unescapeGlobPattern,
};
22 changes: 19 additions & 3 deletions lib/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const {
getLazy,
} = require('internal/util');

const lazyMatchGlobPattern = getLazy(() => require('internal/fs/glob').matchGlobPattern);
const lazyGlobPattern = getLazy(() => require('internal/fs/glob'));

function isPathSeparator(code) {
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
Expand Down Expand Up @@ -1212,7 +1212,15 @@ const win32 = {
},

matchesGlob(path, pattern) {
return lazyMatchGlobPattern()(path, pattern, true);
return lazyGlobPattern().matchGlobPattern(path, pattern, true);
},

escapeGlob(pattern) {
return lazyGlobPattern().escapeGlobPattern(pattern);
},

unescapeGlob(pattern) {
return lazyGlobPattern().unescapeGlobPattern(pattern);
},

sep: '\\',
Expand Down Expand Up @@ -1694,7 +1702,15 @@ const posix = {
},

matchesGlob(path, pattern) {
return lazyMatchGlobPattern()(path, pattern, false);
return lazyGlobPattern().matchGlobPattern(path, pattern, false);
},

escapeGlob(pattern) {
return lazyGlobPattern().escapeGlobPattern(pattern);
},

unescapeGlob(pattern) {
return lazyGlobPattern().unescapeGlobPattern(pattern);
},

sep: '/',
Expand Down
24 changes: 24 additions & 0 deletions test/parallel/test-path-escapeglob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

require('../common');
const assert = require('assert');
const path = require('path');

const samples = [
['file*.txt', 'file[*].txt'],
['file?.txt', 'file[?].txt'],
['file[abc].txt', 'file[[]abc[]].txt'],
['folder/**/*.txt', 'folder/[*][*]/[*].txt'],
['file{1,2}.txt', 'file[{]1,2[}].txt'],
['file[0-9]?.txt', 'file[[]0-9[]][?].txt'],
['C:\\Users\\*.txt', 'C:\\Users\\[*].txt'],
['?[]', '[?][[][]]'],
];

for (const [pattern, expected] of samples) {
const actual = path.escapeGlob(pattern);
assert.strictEqual(actual, expected, `Expected ${pattern} to be escaped as ${expected}, got ${actual} instead`);
}

// Test for non-string input
assert.throws(() => path.escapeGlob(123), /.*must be of type string.*/);
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const globs = {
['foo\\bar\\baz', 'foo\\[bcr]ar\\baz', true], // Matches 'bar' or 'car' in 'foo\\bar'
['foo\\bar\\baz', 'foo\\[!bcr]ar\\baz', false], // Matches anything except 'bar' or 'car' in 'foo\\bar'
['foo\\bar\\baz', 'foo\\[bc-r]ar\\baz', true], // Matches 'bar' or 'car' using range in 'foo\\bar'
['foo\\bar\\baz', 'foo\\*\\!bar\\*\\baz', false], // Matches anything with 'foo' and 'baz' but not 'bar' in between
['foo\\bar\\baz', 'foo\\*\\!bar\\*\\baz', false], // Matches anything with 'foo' and 'baz' but not 'bar'
// in between
['foo\\bar1\\baz', 'foo\\bar[0-9]\\baz', true], // Matches 'bar' followed by any digit in 'foo\\bar1'
['foo\\bar5\\baz', 'foo\\bar[0-9]\\baz', true], // Matches 'bar' followed by any digit in 'foo\\bar5'
['foo\\barx\\baz', 'foo\\bar[a-z]\\baz', true], // Matches 'bar' followed by any lowercase letter in 'foo\\barx'
Expand Down
24 changes: 24 additions & 0 deletions test/parallel/test-path-unescapeglob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

require('../common');
const assert = require('assert');
const path = require('path');

const samples = [
['file*.txt', 'file[*].txt'],
['file?.txt', 'file[?].txt'],
['file[abc].txt', 'file[[]abc[]].txt'],
['folder/**/*.txt', 'folder/[*][*]/[*].txt'],
['file{1,2}.txt', 'file[{]1,2[}].txt'],
['file[0-9]?.txt', 'file[[]0-9[]][?].txt'],
['C:\\Users\\*.txt', 'C:\\Users\\[*].txt'],
['?[]', '[?][[][]]'],
];

for (const [expected, escapedPattern] of samples) {
const actual = path.unescapeGlob(escapedPattern);
assert.strictEqual(actual, expected, `Expected ${escapedPattern} to be unescaped as ${expected}, got ${actual} instead`);
}

// Test for non-string input
assert.throws(() => path.unescapeGlob(123), /.*must be of type string.*/);
Loading