diff --git a/lib/internal/fs/glob.js b/lib/internal/fs/glob.js index a0e7837db7c3..8f1e4b952772 100644 --- a/lib/internal/fs/glob.js +++ b/lib/internal/fs/glob.js @@ -354,7 +354,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 bd95bce7d0e3..fa9a814b770d 100644 --- a/test/parallel/test-fs-glob.mjs +++ b/test/parallel/test-fs-glob.mjs @@ -669,3 +669,31 @@ describe('globSync - ENOTDIR', 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, []); + }); +});