Skip to content

Commit 57b8b8e

Browse files
avivkellermarco-ippolito
authored andcommitted
path: add matchesGlob method
PR-URL: #52881 Reviewed-By: Moshe Atlow <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent 62f4f6f commit 57b8b8e

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed

doc/api/path.md

+23
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,29 @@ path.format({
279279
// Returns: 'C:\\path\\dir\\file.txt'
280280
```
281281

282+
## `path.matchesGlob(path, pattern)`
283+
284+
<!-- YAML
285+
added: REPLACEME
286+
-->
287+
288+
> Stability: 1 - Experimental
289+
290+
* `path` {string} The path to glob-match against.
291+
* `pattern` {string} The glob to check the path against.
292+
* Returns: {boolean} Whether or not the `path` matched the `pattern`.
293+
294+
The `path.matchesGlob()` method determines if `path` matches the `pattern`.
295+
296+
For example:
297+
298+
```js
299+
path.matchesGlob('/foo/bar', '/foo/*'); // true
300+
path.matchesGlob('/foo/bar*', 'foo/bird'); // false
301+
```
302+
303+
A [`TypeError`][] is thrown if `path` or `pattern` are not strings.
304+
282305
## `path.isAbsolute(path)`
283306

284307
<!-- YAML

lib/path.js

+32
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,15 @@ const {
4747
validateString,
4848
} = require('internal/validators');
4949

50+
const {
51+
getLazy,
52+
emitExperimentalWarning,
53+
} = require('internal/util');
54+
55+
const lazyMinimatch = getLazy(() => require('internal/deps/minimatch/index'));
56+
5057
const platformIsWin32 = (process.platform === 'win32');
58+
const platformIsOSX = (process.platform === 'darwin');
5159

5260
function isPathSeparator(code) {
5361
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
@@ -153,6 +161,22 @@ function _format(sep, pathObject) {
153161
return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;
154162
}
155163

164+
function glob(path, pattern, windows) {
165+
emitExperimentalWarning('glob');
166+
validateString(path, 'path');
167+
validateString(pattern, 'pattern');
168+
return lazyMinimatch().minimatch(path, pattern, {
169+
__proto__: null,
170+
nocase: platformIsOSX || platformIsWin32,
171+
windowsPathsNoEscape: true,
172+
nonegate: true,
173+
nocomment: true,
174+
optimizationLevel: 2,
175+
platform: windows ? 'win32' : 'posix',
176+
nocaseMagicOnly: true,
177+
});
178+
}
179+
156180
const win32 = {
157181
/**
158182
* path.resolve([from ...], to)
@@ -1065,6 +1089,10 @@ const win32 = {
10651089
return ret;
10661090
},
10671091

1092+
matchesGlob(path, pattern) {
1093+
return glob(path, pattern, true);
1094+
},
1095+
10681096
sep: '\\',
10691097
delimiter: ';',
10701098
win32: null,
@@ -1530,6 +1558,10 @@ const posix = {
15301558
return ret;
15311559
},
15321560

1561+
matchesGlob(path, pattern) {
1562+
return glob(path, pattern, false);
1563+
},
1564+
15331565
sep: '/',
15341566
delimiter: ':',
15351567
win32: null,

test/parallel/test-path-glob.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const path = require('path');
6+
7+
const globs = {
8+
win32: [
9+
['foo\\bar\\baz', 'foo\\[bcr]ar\\baz', true], // Matches 'bar' or 'car' in 'foo\\bar'
10+
['foo\\bar\\baz', 'foo\\[!bcr]ar\\baz', false], // Matches anything except 'bar' or 'car' in 'foo\\bar'
11+
['foo\\bar\\baz', 'foo\\[bc-r]ar\\baz', true], // Matches 'bar' or 'car' using range in 'foo\\bar'
12+
['foo\\bar\\baz', 'foo\\*\\!bar\\*\\baz', false], // Matches anything with 'foo' and 'baz' but not 'bar' in between
13+
['foo\\bar1\\baz', 'foo\\bar[0-9]\\baz', true], // Matches 'bar' followed by any digit in 'foo\\bar1'
14+
['foo\\bar5\\baz', 'foo\\bar[0-9]\\baz', true], // Matches 'bar' followed by any digit in 'foo\\bar5'
15+
['foo\\barx\\baz', 'foo\\bar[a-z]\\baz', true], // Matches 'bar' followed by any lowercase letter in 'foo\\barx'
16+
['foo\\bar\\baz\\boo', 'foo\\[bc-r]ar\\baz\\*', true], // Matches 'bar' or 'car' in 'foo\\bar'
17+
['foo\\bar\\baz', 'foo/**', true], // Matches anything in 'foo'
18+
['foo\\bar\\baz', '*', false], // No match
19+
],
20+
posix: [
21+
['foo/bar/baz', 'foo/[bcr]ar/baz', true], // Matches 'bar' or 'car' in 'foo/bar'
22+
['foo/bar/baz', 'foo/[!bcr]ar/baz', false], // Matches anything except 'bar' or 'car' in 'foo/bar'
23+
['foo/bar/baz', 'foo/[bc-r]ar/baz', true], // Matches 'bar' or 'car' using range in 'foo/bar'
24+
['foo/bar/baz', 'foo/*/!bar/*/baz', false], // Matches anything with 'foo' and 'baz' but not 'bar' in between
25+
['foo/bar1/baz', 'foo/bar[0-9]/baz', true], // Matches 'bar' followed by any digit in 'foo/bar1'
26+
['foo/bar5/baz', 'foo/bar[0-9]/baz', true], // Matches 'bar' followed by any digit in 'foo/bar5'
27+
['foo/barx/baz', 'foo/bar[a-z]/baz', true], // Matches 'bar' followed by any lowercase letter in 'foo/barx'
28+
['foo/bar/baz/boo', 'foo/[bc-r]ar/baz/*', true], // Matches 'bar' or 'car' in 'foo/bar'
29+
['foo/bar/baz', 'foo/**', true], // Matches anything in 'foo'
30+
['foo/bar/baz', '*', false], // No match
31+
],
32+
};
33+
34+
35+
for (const [platform, platformGlobs] of Object.entries(globs)) {
36+
for (const [pathStr, glob, expected] of platformGlobs) {
37+
const actual = path[platform].matchesGlob(pathStr, glob);
38+
assert.strictEqual(actual, expected, `Expected ${pathStr} to ` + (expected ? '' : 'not ') + `match ${glob} on ${platform}`);
39+
}
40+
}
41+
42+
// Test for non-string input
43+
assert.throws(() => path.matchesGlob(123, 'foo/bar/baz'), /.*must be of type string.*/);
44+
assert.throws(() => path.matchesGlob('foo/bar/baz', 123), /.*must be of type string.*/);

0 commit comments

Comments
 (0)