From 773b904713c1432baf23e4ad1d205cef8ea9c72b Mon Sep 17 00:00:00 2001 From: Yusuke Hayashi Date: Wed, 29 Jul 2026 15:31:16 +0900 Subject: [PATCH] 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 --- lib/internal/fs/glob.js | 5 ++++- test/parallel/test-fs-glob.mjs | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/internal/fs/glob.js b/lib/internal/fs/glob.js index 505071e5602d..e32c08f32b73 100644 --- a/lib/internal/fs/glob.js +++ b/lib/internal/fs/glob.js @@ -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); diff --git a/test/parallel/test-fs-glob.mjs b/test/parallel/test-fs-glob.mjs index 35e89257b128..434d05a722e6 100644 --- a/test/parallel/test-fs-glob.mjs +++ b/test/parallel/test-fs-glob.mjs @@ -998,3 +998,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, []); + }); +});