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
21 changes: 21 additions & 0 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,9 @@ behavior is similar to `cp dir1/ dir2/`.
<!-- YAML
added: v22.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63205
description: Add support for the `dot` option.
- version: v26.1.0
pr-url: https://github.com/nodejs/node/pull/62695
description: Add support for the `followSymlinks` option.
Expand All @@ -1380,6 +1383,10 @@ changes:
* `pattern` {string|string\[]}
* `options` {Object}
* `cwd` {string|URL} current working directory. **Default:** `process.cwd()`
* `dot` {boolean} When `true`, allows `*` and `**` patterns to match
basenames starting with a period (`.`), and allows the walker to
descend into directories whose names start with a period. **Default:**
`false`.
* `exclude` {Function|string\[]} Function to filter out files/directories or a
list of glob patterns to be excluded. If a function is provided, return
`true` to exclude the item, `false` to include it. **Default:** `undefined`.
Expand Down Expand Up @@ -3475,6 +3482,9 @@ descriptor. See [`fs.utimes()`][].
<!-- YAML
added: v22.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63205
description: Add support for the `dot` option.
- version: v26.1.0
pr-url: https://github.com/nodejs/node/pull/62695
description: Add support for the `followSymlinks` option.
Expand Down Expand Up @@ -3502,6 +3512,10 @@ changes:

* `options` {Object}
* `cwd` {string|URL} current working directory. **Default:** `process.cwd()`
* `dot` {boolean} When `true`, allows `*` and `**` patterns to match
basenames starting with a period (`.`), and allows the walker to
descend into directories whose names start with a period. **Default:**
`false`.
* `exclude` {Function|string\[]} Function to filter out files/directories or a
list of glob patterns to be excluded. If a function is provided, return
`true` to exclude the item, `false` to include it. **Default:** `undefined`.
Expand Down Expand Up @@ -6057,6 +6071,9 @@ Synchronous version of [`fs.futimes()`][]. Returns `undefined`.
<!-- YAML
added: v22.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63205
description: Add support for the `dot` option.
- version: v26.1.0
pr-url: https://github.com/nodejs/node/pull/62695
description: Add support for the `followSymlinks` option.
Expand All @@ -6083,6 +6100,10 @@ changes:
* `pattern` {string|string\[]}
* `options` {Object}
* `cwd` {string|URL} current working directory. **Default:** `process.cwd()`
* `dot` {boolean} When `true`, allows `*` and `**` patterns to match
basenames starting with a period (`.`), and allows the walker to
descend into directories whose names start with a period. **Default:**
`false`.
* `exclude` {Function|string\[]} Function to filter out files/directories or a
list of glob patterns to be excluded. If a function is provided, return
`true` to exclude the item, `false` to include it. **Default:** `undefined`.
Expand Down
16 changes: 11 additions & 5 deletions lib/internal/fs/glob.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,16 +336,22 @@ class Glob {
#patterns;
#withFileTypes;
#followSymlinks = false;
#dot = false;
#isExcluded = () => false;
constructor(pattern, options = kEmptyObject) {
validateObject(options, 'options');
const { exclude, cwd, followSymlinks, withFileTypes } = options;
const { dot, exclude, cwd, followSymlinks, withFileTypes } = options;
this.#root = toPathIfFileURL(cwd) ?? '.';
if (followSymlinks != null) {
validateBoolean(followSymlinks, 'options.followSymlinks');
this.#followSymlinks = followSymlinks;
}
if (dot != null) {
validateBoolean(dot, 'options.dot');
this.#dot = dot;
}
this.#withFileTypes = !!withFileTypes;
const matcherOpts = { __proto__: null, dot: this.#dot };
if (exclude != null) {
validateStringArrayOrFunction(exclude, 'options.exclude');
if (ArrayIsArray(exclude)) {
Expand All @@ -354,7 +360,7 @@ class Glob {
// consistent comparison before instantiating matchers.
const matchers = exclude
.map((pattern) => resolve(this.#root, pattern))
.map((pattern) => createMatcher(pattern));
.map((pattern) => createMatcher(pattern, matcherOpts));
this.#isExcluded = (value) =>
matchers.some((matcher) => matcher.match(value));
this.#results.setup(this.#root, this.#isExcluded);
Expand All @@ -370,7 +376,7 @@ class Glob {
validateString(pattern, 'patterns');
patterns = [pattern];
}
this.matchers = ArrayPrototypeMap(patterns, (pattern) => createMatcher(pattern));
this.matchers = ArrayPrototypeMap(patterns, (pattern) => createMatcher(pattern, matcherOpts));
this.#patterns = ArrayPrototypeFlatMap(this.matchers, (matcher) => ArrayPrototypeMap(matcher.set,
(pattern, i) => new Pattern(
pattern,
Expand Down Expand Up @@ -595,7 +601,7 @@ class Glob {

const matchesDot = isDot && pattern.test(nextNonGlobIndex, entry.name);

if ((isDot && !matchesDot) ||
if ((!this.#dot && isDot && !matchesDot) ||
(this.#exclude && this.#exclude(this.#withFileTypes ? entry : entry.name))) {
continue;
}
Expand Down Expand Up @@ -812,7 +818,7 @@ class Glob {

const matchesDot = isDot && pattern.test(nextNonGlobIndex, entry.name);

if ((isDot && !matchesDot) ||
if ((!this.#dot && isDot && !matchesDot) ||
(this.#exclude && this.#exclude(this.#withFileTypes ? entry : entry.name))) {
continue;
}
Expand Down
124 changes: 124 additions & 0 deletions test/parallel/test-fs-glob.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -669,3 +669,127 @@ describe('globSync - ENOTDIR', function() {
}
});
});

const dotFixtureDir = tmpdir.resolve('dotfixtures');
async function setupDotFixtures() {
const files = [
'lib/visible.js',
'lib/.hidden.js',
'lib/sub/visible.js',
'lib/sub/.hidden.js',
'lib/.dotdir/regular.js',
'lib/.dotdir/sub/deep.js',
];
for (const f of files) {
const full = resolve(dotFixtureDir, f);
await mkdir(dirname(full), { recursive: true });
await writeFile(full, '');
}
}
await setupDotFixtures();

const dotExpectedWithDot = [
'lib',
'lib/.dotdir',
'lib/.dotdir/regular.js',
'lib/.dotdir/sub',
'lib/.dotdir/sub/deep.js',
'lib/.hidden.js',
'lib/sub',
'lib/sub/.hidden.js',
'lib/sub/visible.js',
'lib/visible.js',
];
const dotExpectedWithoutDot = [
'lib',
'lib/sub',
'lib/sub/visible.js',
'lib/visible.js',
];
const dotStarExpectedWithDot = [
'lib/.hidden.js',
'lib/visible.js',
];
const dotStarExpectedWithoutDot = [
'lib/visible.js',
];

describe('glob - dot', function() {
const promisified = promisify(glob);

test('does not match dotfiles by default', async () => {
const actual = (await promisified('lib/**', { cwd: dotFixtureDir })).sort();
assert.deepStrictEqual(actual, dotExpectedWithoutDot);
});

test('matches dotfiles and traverses dot-dirs when enabled', async () => {
const actual = (await promisified('lib/**', {
cwd: dotFixtureDir,
dot: true,
})).sort();
assert.deepStrictEqual(actual, dotExpectedWithDot);
});

test('respects dot option for STAR patterns', async () => {
const actual = (await promisified('lib/*.js', {
cwd: dotFixtureDir,
dot: true,
})).sort();
assert.deepStrictEqual(actual, dotStarExpectedWithDot);
});
});

describe('globSync - dot', function() {
test('does not match dotfiles by default', () => {
const actual = globSync('lib/**', { cwd: dotFixtureDir }).sort();
assert.deepStrictEqual(actual, dotExpectedWithoutDot);
});

test('validates dot', () => {
assert.throws(() => {
globSync('lib/**', { cwd: dotFixtureDir, dot: 1 });
}, {
code: 'ERR_INVALID_ARG_TYPE',
});
});

test('matches dotfiles and traverses dot-dirs when enabled', () => {
const actual = globSync('lib/**', {
cwd: dotFixtureDir,
dot: true,
}).sort();
assert.deepStrictEqual(actual, dotExpectedWithDot);
});

test('respects dot option for STAR patterns', () => {
const actual = globSync('lib/*.js', {
cwd: dotFixtureDir,
dot: true,
}).sort();
assert.deepStrictEqual(actual, dotStarExpectedWithDot);
});

test('STAR patterns drop dotfiles by default', () => {
const actual = globSync('lib/*.js', { cwd: dotFixtureDir }).sort();
assert.deepStrictEqual(actual, dotStarExpectedWithoutDot);
});
});

describe('fsPromises glob - dot', function() {
test('does not match dotfiles by default', async () => {
const actual = [];
for await (const item of asyncGlob('lib/**', { cwd: dotFixtureDir })) actual.push(item);
actual.sort();
assert.deepStrictEqual(actual, dotExpectedWithoutDot);
});

test('matches dotfiles and traverses dot-dirs when enabled', async () => {
const actual = [];
for await (const item of asyncGlob('lib/**', {
cwd: dotFixtureDir,
dot: true,
})) actual.push(item);
actual.sort();
assert.deepStrictEqual(actual, dotExpectedWithDot);
});
});
Loading