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
40 changes: 26 additions & 14 deletions lib/internal/fs/glob.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,18 @@ class Glob {
ArrayPrototypePush(this.#subpatterns.get(path), pattern);
}
}
#addResult(path, dirent) {
if (this.#exclude) {
if (this.#withFileTypes) {
if (dirent !== null && this.#exclude(dirent)) {
return false;
}
} else if (this.#exclude(path)) {
return false;
}
}
return this.#results.add(path);
}
#addSubpatterns(path, pattern) {
const seen = this.#cache.add(path, pattern);
if (seen) {
Expand Down Expand Up @@ -532,7 +544,7 @@ class Glob {
const p = pattern.at(-1);
const stat = this.#cache.statSync(join(fullpath, p));
if (stat && (p || isDirectory)) {
this.#results.add(join(path, p));
this.#addResult(join(path, p), stat);
}
if (pattern.indexes.size === 1 && pattern.indexes.has(last)) {
return;
Expand All @@ -541,7 +553,7 @@ class Glob {
(path !== '.' || pattern.at(0) === '.' || (last === 0 && stat))) {
// If pattern ends with **, add to results
// if path is ".", add it only if pattern starts with "." or pattern is exactly "**"
this.#results.add(path);
this.#addResult(path, stat);
}

if (!isDirectory || this.#isCyclicSync(fullpath, isDirectory, pattern)) {
Expand Down Expand Up @@ -604,14 +616,14 @@ class Glob {
subPatterns.add(index);
} else if (!fromSymlink && index === last) {
// If ** is last, add to results
this.#results.add(entryPath);
this.#addResult(entryPath, entry);
}

// Any pattern after ** is also a potential pattern
// so we can already test it here
if (nextMatches && nextIndex === last && !isLast) {
// If next pattern is the last one, add to results
this.#results.add(entryPath);
this.#addResult(entryPath, entry);
} else if (nextMatches && entryIsDirectory) {
// Pattern matched, meaning two patterns forward
// are also potential patterns
Expand Down Expand Up @@ -644,11 +656,11 @@ class Glob {
} else {
if (!this.#cache.seen(path, pattern, nextIndex)) {
this.#cache.add(path, pattern.child(new SafeSet().add(nextIndex)));
this.#results.add(path);
this.#addResult(path, this.#cache.statSync(fullpath));
}
if (!this.#cache.seen(path, pattern, nextIndex) || !this.#cache.seen(parent, pattern, nextIndex)) {
this.#cache.add(parent, pattern.child(new SafeSet().add(nextIndex)));
this.#results.add(parent);
this.#addResult(parent, this.#cache.statSync(join(this.#root, parent)));
}
}
}
Expand All @@ -671,7 +683,7 @@ class Glob {
// If current pattern is a regex that matches entry name (e.g *.js)
// add next pattern to potential patterns, or to results if it's the last pattern
if (index === last) {
this.#results.add(entryPath);
this.#addResult(entryPath, entry);
} else if (entryIsDirectory) {
subPatterns.add(nextIndex);
}
Expand Down Expand Up @@ -740,7 +752,7 @@ class Glob {
if (stat && (p || isDirectory)) {
const result = join(path, p);
if (!this.#results.has(result)) {
if (this.#results.add(result)) {
if (this.#addResult(result, stat)) {
yield this.#withFileTypes ? stat : result;
}
}
Expand All @@ -753,7 +765,7 @@ class Glob {
// If pattern ends with **, add to results
// if path is ".", add it only if pattern starts with "." or pattern is exactly "**"
if (!this.#results.has(path)) {
if (this.#results.add(path)) {
if (this.#addResult(path, stat)) {
yield this.#withFileTypes ? stat : path;
}
}
Expand Down Expand Up @@ -821,7 +833,7 @@ class Glob {
subPatterns.add(index);
} else if (!fromSymlink && index === last) {
// If ** is last, add to results
if (!this.#results.has(entryPath) && this.#results.add(entryPath)) {
if (!this.#results.has(entryPath) && this.#addResult(entryPath, entry)) {
yield this.#withFileTypes ? entry : entryPath;
}
}
Expand All @@ -830,7 +842,7 @@ class Glob {
// so we can already test it here
if (nextMatches && nextIndex === last && !isLast) {
// If next pattern is the last one, add to results
if (!this.#results.has(entryPath) && this.#results.add(entryPath)) {
if (!this.#results.has(entryPath) && this.#addResult(entryPath, entry)) {
yield this.#withFileTypes ? entry : entryPath;
}
} else if (nextMatches && entryIsDirectory) {
Expand Down Expand Up @@ -866,15 +878,15 @@ class Glob {
if (!this.#cache.seen(path, pattern, nextIndex)) {
this.#cache.add(path, pattern.child(new SafeSet().add(nextIndex)));
if (!this.#results.has(path)) {
if (this.#results.add(path)) {
if (this.#addResult(path, this.#cache.statSync(fullpath))) {
yield this.#withFileTypes ? this.#cache.statSync(fullpath) : path;
}
}
}
if (!this.#cache.seen(path, pattern, nextIndex) || !this.#cache.seen(parent, pattern, nextIndex)) {
this.#cache.add(parent, pattern.child(new SafeSet().add(nextIndex)));
if (!this.#results.has(parent)) {
if (this.#results.add(parent)) {
if (this.#addResult(parent, this.#cache.statSync(join(this.#root, parent)))) {
yield this.#withFileTypes ? this.#cache.statSync(join(this.#root, parent)) : parent;
}
}
Expand Down Expand Up @@ -905,7 +917,7 @@ class Glob {
// add next pattern to potential patterns, or to results if it's the last pattern
if (index === last) {
if (!this.#results.has(entryPath)) {
if (this.#results.add(entryPath)) {
if (this.#addResult(entryPath, entry)) {
yield this.#withFileTypes ? entry : entryPath;
}
}
Expand Down
153 changes: 153 additions & 0 deletions test/parallel/test-fs-glob.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,9 @@ const patterns2 = [
[ 'a/**', [ 'a/**' ], [] ],
];

const excludedNestedFile = ['a', 'b', 'c', 'd'].join(sep);
const excludesNestedFile = (path) => path === excludedNestedFile;

describe('globSync - exclude', function() {
for (const [pattern, exclude] of Object.entries(patterns).map(([k, v]) => [k, v.filter(Boolean)])) {
test(`${pattern} - exclude: ${exclude}`, () => {
Expand All @@ -500,6 +503,57 @@ describe('globSync - exclude', function() {
assert.deepStrictEqual(actual, normalized);
});
}

test('applies function exclude to terminal globstar results', () => {
const actual = globSync('**', {
cwd: fixtureDir,
exclude: excludesNestedFile,
});
assert.ok(!actual.includes(excludedNestedFile));
});

test('applies function exclude to terminal pattern results', () => {
const actual = globSync('**/d', {
cwd: fixtureDir,
exclude: excludesNestedFile,
});
assert.ok(!actual.includes(excludedNestedFile));
});

test('applies function exclude to terminal results with file types', () => {
const actual = globSync('**/d', {
cwd: fixtureDir,
exclude: (dirent) => normalizeDirent(dirent) === excludedNestedFile,
withFileTypes: true,
});
assert.ok(!actual.map(normalizeDirent).includes(excludedNestedFile));
});

test('applies function exclude to direct terminal results with file types', () => {
const actual = globSync('a/b/c/d', {
cwd: fixtureDir,
exclude: (dirent) => normalizeDirent(dirent) === excludedNestedFile,
withFileTypes: true,
});
assert.deepStrictEqual(actual, []);
});

test('keeps terminal results when function exclude returns false', () => {
const actual = globSync('**/d', {
cwd: fixtureDir,
exclude: () => false,
});
assert.ok(actual.includes(excludedNestedFile));
});

test('keeps terminal results with file types when function exclude returns false', () => {
const actual = globSync('**/d', {
cwd: fixtureDir,
exclude: () => false,
withFileTypes: true,
});
assert.ok(actual.map(normalizeDirent).includes(excludedNestedFile));
});
});

describe('glob - exclude', function() {
Expand All @@ -517,6 +571,48 @@ describe('glob - exclude', function() {
assert.deepStrictEqual(actual, normalized);
});
}

test('applies function exclude to terminal globstar results', async () => {
const actual = await promisified('**', {
cwd: fixtureDir,
exclude: excludesNestedFile,
});
assert.ok(!actual.includes(excludedNestedFile));
});

test('applies function exclude to terminal pattern results', async () => {
const actual = await promisified('**/d', {
cwd: fixtureDir,
exclude: excludesNestedFile,
});
assert.ok(!actual.includes(excludedNestedFile));
});

test('applies function exclude to terminal results with file types', async () => {
const actual = await promisified('**/d', {
cwd: fixtureDir,
exclude: (dirent) => normalizeDirent(dirent) === excludedNestedFile,
withFileTypes: true,
});
assert.ok(!actual.map(normalizeDirent).includes(excludedNestedFile));
});

test('keeps terminal results when function exclude returns false', async () => {
const actual = await promisified('**/d', {
cwd: fixtureDir,
exclude: () => false,
});
assert.ok(actual.includes(excludedNestedFile));
});

test('keeps terminal results with file types when function exclude returns false', async () => {
const actual = await promisified('**/d', {
cwd: fixtureDir,
exclude: () => false,
withFileTypes: true,
});
assert.ok(actual.map(normalizeDirent).includes(excludedNestedFile));
});
});

describe('fsPromises glob - exclude', function() {
Expand All @@ -536,6 +632,63 @@ describe('fsPromises glob - exclude', function() {
assert.deepStrictEqual(actual.sort(), normalized);
});
}

test('applies function exclude to terminal globstar results', async () => {
const actual = [];
for await (const item of asyncGlob('**', {
cwd: fixtureDir,
exclude: excludesNestedFile,
})) {
actual.push(item);
}
assert.ok(!actual.includes(excludedNestedFile));
});

test('applies function exclude to terminal pattern results', async () => {
const actual = [];
for await (const item of asyncGlob('**/d', {
cwd: fixtureDir,
exclude: excludesNestedFile,
})) {
actual.push(item);
}
assert.ok(!actual.includes(excludedNestedFile));
});

test('applies function exclude to terminal results with file types', async () => {
const actual = [];
for await (const item of asyncGlob('**/d', {
cwd: fixtureDir,
exclude: (dirent) => normalizeDirent(dirent) === excludedNestedFile,
withFileTypes: true,
})) {
actual.push(item);
}
assert.ok(!actual.map(normalizeDirent).includes(excludedNestedFile));
});

test('keeps terminal results when function exclude returns false', async () => {
const actual = [];
for await (const item of asyncGlob('**/d', {
cwd: fixtureDir,
exclude: () => false,
})) {
actual.push(item);
}
assert.ok(actual.includes(excludedNestedFile));
});

test('keeps terminal results with file types when function exclude returns false', async () => {
const actual = [];
for await (const item of asyncGlob('**/d', {
cwd: fixtureDir,
exclude: () => false,
withFileTypes: true,
})) {
actual.push(item);
}
assert.ok(actual.map(normalizeDirent).includes(excludedNestedFile));
});
});

const followSymlinkPattern = 'follow/**';
Expand Down
Loading