fs: apply nocase to literal glob exclude patterns

On case-insensitive platforms include patterns match entries
regardless of case, but literal (non-magic) exclude patterns were
matched case-sensitively because exclude matchers are created with
nocaseMagicOnly. Exclusion is a pure string match with no filesystem
lookups, so disable nocaseMagicOnly for exclude matchers.

Fixes: https://github.com/nodejs/node/issues/58991
Refs: https://github.com/nodejs/node/pull/63446
Signed-off-by: Yusuke Hayashi <yusuke8h@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64817
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
This commit is contained in:
Yusuke Hayashi authored and GitHub committed 2026-09-03 05:36:15 +00:00
1 parent 0a1a37ecbb
commit 144e679de4
2 files changed
+32 -1

No files matched your search

+4 -1
View File
@@ -664,7 +664,10 @@ class Glob {
// consistent comparison before instantiating matchers.
const matchers = exclude
.map((pattern) => resolve(this.#root, pattern))
.map((pattern) => createMatcher(pattern));
// Exclude matching is a pure string comparison with no filesystem
// lookups, so unlike include patterns, literal patterns must also
// match case-insensitively on case-insensitive platforms.
.map((pattern) => createMatcher(pattern, { nocaseMagicOnly: false }));
this.#isExcluded = (value) =>
matchers.some((matcher) => matcher.match(value));
this.#results.setup(this.#root, this.#isExcluded);
+28
View File
@@ -983,3 +983,31 @@ describe('glob - seen cache', function() {
);
});
});
// gh-58991: exclude patterns must apply the same case-sensitivity as include
// patterns. On case-insensitive filesystems include patterns match entries
// regardless of case, so literal exclude patterns must match that behavior.
const skipCaseTests = { skip: !common.isWindows && !common.isMacOS };
describe('glob - exclude case-insensitive consistency', skipCaseTests, function() {
test('literal exclude ignores case (sync)', () => {
assert.deepStrictEqual(
globSync('a/b', { cwd: fixtureDir, exclude: ['A/B'] }), []);
});
test('literal exclude matches differently-cased results (sync)', () => {
assert.deepStrictEqual(
globSync('A/b', { cwd: fixtureDir, exclude: ['a/b'] }), []);
});
test('literal exclude ignores case (async)', async () => {
const promisified = promisify(glob);
assert.deepStrictEqual(
await promisified('a/b', { cwd: fixtureDir, exclude: ['A/B'] }), []);
});
test('literal exclude ignores case (promise)', async () => {
const actual = [];
for await (const entry of asyncGlob(
'a/b', { cwd: fixtureDir, exclude: ['A/B'] })) {
actual.push(entry);
}
assert.deepStrictEqual(actual, []);
});
});