From 144e679de4accad948b520dabcee5bee372b2ac9 Mon Sep 17 00:00:00 2001 From: Yusuke Hayashi Date: Thu, 3 Sep 2026 14:36:15 +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 PR-URL: https://github.com/nodejs/node/pull/64817 Reviewed-By: James M Snell Reviewed-By: Stefan Stojanovic Reviewed-By: Trivikram Kamat --- 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 a949303b58b..b5949c47181 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 692f11716d5..ed731750808 100644 --- a/test/parallel/test-fs-glob.mjs +++ b/test/parallel/test-fs-glob.mjs @@ -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, []); + }); +});