Skip to content

Commit eb78791

Browse files
committed
Add expandNegationOnlyPatterns option
Fixes #274
1 parent 59e269f commit eb78791

4 files changed

Lines changed: 71 additions & 2 deletions

File tree

‎index.d.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ export type Options = {
6363
*/
6464
readonly ignoreFiles?: string | readonly string[];
6565

66+
/**
67+
When only negation patterns are provided (e.g., `['!*.json']`), automatically prepend a catch-all pattern (`**\/*`) to match all files before applying negations.
68+
69+
Set to `false` to return an empty array when only negation patterns are provided. This can be useful when patterns are user-controlled, to avoid unexpectedly matching all files.
70+
71+
@default true
72+
73+
@example
74+
```
75+
import {globby} from 'globby';
76+
77+
// Default behavior: matches all files except .json
78+
await globby(['!*.json']);
79+
//=> ['file.txt', 'image.png', ...]
80+
81+
// Disable expansion: returns empty array
82+
await globby(['!*.json'], {expandNegationOnlyPatterns: false});
83+
//=> []
84+
```
85+
*/
86+
readonly expandNegationOnlyPatterns?: boolean;
87+
6688
/**
6789
The current working directory in which to search.
6890

‎index.js‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,14 @@ const createFilterFunction = (isIgnored, cwd) => {
290290
const unionFastGlobResults = (results, filter) => results.flat().filter(fastGlobResult => filter(fastGlobResult));
291291

292292
const convertNegativePatterns = (patterns, options) => {
293-
// If all patterns are negative, prepend a positive catch-all pattern
294-
// This makes negation-only patterns work intuitively (e.g., '!*.json' matches all files except JSON)
293+
// If all patterns are negative and expandNegationOnlyPatterns is enabled (default),
294+
// prepend a positive catch-all pattern to make negation-only patterns work intuitively
295+
// (e.g., '!*.json' matches all files except JSON)
295296
if (patterns.length > 0 && patterns.every(pattern => isNegativePattern(pattern))) {
297+
if (options.expandNegationOnlyPatterns === false) {
298+
return [];
299+
}
300+
296301
patterns = ['**/*', ...patterns];
297302
}
298303

‎readme.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,27 @@ This is a more generic form of the `gitignore` option, allowing you to find igno
106106

107107
**Performance tip:** Using a specific path like `'.gitignore'` is much faster than recursive patterns.
108108

109+
##### expandNegationOnlyPatterns
110+
111+
Type: `boolean`\
112+
Default: `true`
113+
114+
When only negation patterns are provided (e.g., `['!*.json']`), automatically prepend a catch-all pattern (`**/*`) to match all files before applying negations.
115+
116+
Set to `false` to return an empty array when only negation patterns are provided. This can be useful when patterns are user-controlled, to avoid unexpectedly matching all files.
117+
118+
```js
119+
import {globby} from 'globby';
120+
121+
// Default behavior: matches all files except .json
122+
await globby(['!*.json']);
123+
//=> ['file.txt', 'image.png', ...]
124+
125+
// Disable expansion: returns empty array
126+
await globby(['!*.json'], {expandNegationOnlyPatterns: false});
127+
//=> []
128+
```
129+
109130
##### fs
110131

111132
Type: [`FileSystemAdapter`](https://github.com/mrmlnc/fast-glob#fs)\

‎tests/globby.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,27 @@ test('negation pattern with absolute path is normalized to relative', async t =>
272272
t.deepEqual(result, []);
273273
});
274274

275+
test('expandNegationOnlyPatterns: false returns empty array for negation-only patterns', async t => {
276+
const result = await runGlobby(t, ['!a.tmp', '!b.tmp'], {cwd: temporary, expandNegationOnlyPatterns: false});
277+
t.deepEqual(result, []);
278+
});
279+
280+
test('expandNegationOnlyPatterns: false with single negation pattern returns empty array', async t => {
281+
const result = await runGlobby(t, '!a.tmp', {cwd: temporary, expandNegationOnlyPatterns: false});
282+
t.deepEqual(result, []);
283+
});
284+
285+
test('expandNegationOnlyPatterns: false does not affect mixed patterns', async t => {
286+
// When there are positive patterns, negation-only expansion is not triggered
287+
const result = await runGlobby(t, ['*.tmp', '!a.tmp', '!b.tmp'], {cwd: temporary, expandNegationOnlyPatterns: false});
288+
t.deepEqual(result, ['c.tmp', 'd.tmp', 'e.tmp']);
289+
});
290+
291+
test('expandNegationOnlyPatterns: true (default) works with negation-only patterns', async t => {
292+
const result = await runGlobby(t, ['!a.tmp', '!b.tmp'], {cwd: temporary, expandNegationOnlyPatterns: true});
293+
t.deepEqual(result, ['c.tmp', 'd.tmp', 'e.tmp']);
294+
});
295+
275296
test('glob - stream async iterator support', async t => {
276297
const results = [];
277298
for await (const path of globbyStream('*.tmp')) {

0 commit comments

Comments
 (0)