fs: apply exclude function to root path

In at least some situations, the root path was
being added to the results of globbing even if
it matched the optional exclude function.

In the relevant test file, a variable was renamed
for consistency. (There was a patterns and pattern2,
rather than patterns2.) The test case is added to
patterns2.

Fixes: https://github.com/nodejs/node/issues/56260
PR-URL: https://github.com/nodejs/node/pull/57420
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Jason Zhang <xzha4350@gmail.com>
Reviewed-By: LiviaMedeiros <livia@cirno.name>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Rich Trott
2025-03-15 22:26:54 -07:00
committed by GitHub
parent d5ac3e3803
commit 8a5a849a44
2 changed files with 33 additions and 5 deletions

View File

@@ -9,6 +9,7 @@ const {
ArrayPrototypePop,
ArrayPrototypePush,
ArrayPrototypeSome,
Promise,
PromisePrototypeThen,
SafeMap,
SafeSet,
@@ -125,7 +126,8 @@ class Cache {
}
statSync(path) {
const cached = this.#statsCache.get(path);
if (cached) {
// Do not return a promise from a sync function.
if (cached && !(cached instanceof Promise)) {
return cached;
}
const val = getDirentSync(path);
@@ -326,6 +328,28 @@ class Glob {
if (this.#isExcluded(path)) {
return;
}
const fullpath = resolve(this.#root, path);
// If path is a directory, add trailing slash and test patterns again.
// TODO(Trott): Would running #isExcluded() first and checking isDirectory() only
// if it matches be more performant in the typical use case? #isExcluded()
// is often ()=>false which is about as optimizable as a function gets.
if (this.#cache.statSync(fullpath).isDirectory() && this.#isExcluded(`${fullpath}/`)) {
return;
}
if (this.#exclude) {
if (this.#withFileTypes) {
const stat = this.#cache.statSync(path);
if (stat !== null) {
if (this.#exclude(stat)) {
return;
}
}
} else if (this.#exclude(path)) {
return;
}
}
if (!this.#subpatterns.has(path)) {
this.#subpatterns.set(path, [pattern]);
} else {

View File

@@ -388,7 +388,7 @@ describe('fsPromises glob - withFileTypes', function() {
});
// [pattern, exclude option, expected result]
const pattern2 = [
const patterns2 = [
['a/{b,c}*', ['a/*c'], ['a/b', 'a/cb']],
['a/{a,b,c}*', ['a/*bc*', 'a/cb'], ['a/b', 'a/c']],
['a/**/[cg]', ['**/c'], ['a/abcdef/g', 'a/abcfed/g']],
@@ -427,6 +427,10 @@ const pattern2 = [
[`${absDir}/*{a,q}*`, './a/*{c,b}*/*'],
[`${absDir}/foo`, 'a/c', ...(common.isWindows ? [] : ['a/symlink/a/b/c'])],
],
[ 'a/**', () => true, [] ],
[ 'a/**', [ '*' ], [] ],
[ 'a/**', [ '**' ], [] ],
[ 'a/**', [ 'a/**' ], [] ],
];
describe('globSync - exclude', function() {
@@ -436,7 +440,7 @@ describe('globSync - exclude', function() {
assert.strictEqual(actual.length, 0);
});
}
for (const [pattern, exclude, expected] of pattern2) {
for (const [pattern, exclude, expected] of patterns2) {
test(`${pattern} - exclude: ${exclude}`, () => {
const actual = globSync(pattern, { cwd: fixtureDir, exclude }).sort();
const normalized = expected.filter(Boolean).map((item) => item.replaceAll('/', sep)).sort();
@@ -453,7 +457,7 @@ describe('glob - exclude', function() {
assert.strictEqual(actual.length, 0);
});
}
for (const [pattern, exclude, expected] of pattern2) {
for (const [pattern, exclude, expected] of patterns2) {
test(`${pattern} - exclude: ${exclude}`, async () => {
const actual = (await promisified(pattern, { cwd: fixtureDir, exclude })).sort();
const normalized = expected.filter(Boolean).map((item) => item.replaceAll('/', sep)).sort();
@@ -471,7 +475,7 @@ describe('fsPromises glob - exclude', function() {
assert.strictEqual(actual.length, 0);
});
}
for (const [pattern, exclude, expected] of pattern2) {
for (const [pattern, exclude, expected] of patterns2) {
test(`${pattern} - exclude: ${exclude}`, async () => {
const actual = [];
for await (const item of asyncGlob(pattern, { cwd: fixtureDir, exclude })) actual.push(item);