Skip to content

Commit edd6b09

Browse files
committed
feat: ability to include pattern base directory to the result
1 parent cc5e9cc commit edd6b09

File tree

9 files changed

+722
-31
lines changed

9 files changed

+722
-31
lines changed

__snapshots__/include-pattern-base-directory.e2e.js

Lines changed: 536 additions & 0 deletions
Large diffs are not rendered by default.

src/index.spec.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,6 @@ describe('Package', () => {
188188

189189
assert.deepStrictEqual(actual, expected);
190190
});
191-
192-
it('should clean up patterns', () => {
193-
const expected = [
194-
// Clean up duplicate slashes
195-
tests.task.builder().base('fixtures').positive('fixtures/*').build()
196-
];
197-
198-
const actual = fg.generateTasks(['fixtures//*']);
199-
200-
assert.deepStrictEqual(actual, expected);
201-
});
202191
});
203192

204193
describe('.isDynamicPattern', () => {

src/managers/tasks.spec.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,37 +69,74 @@ describe('Managers → Task', () => {
6969

7070
assert.deepStrictEqual(actual, expected);
7171
});
72+
73+
it('should return task for the base directory', () => {
74+
const settings = new Settings({
75+
onlyFiles: false,
76+
includePatternBaseDirectory: true
77+
});
78+
79+
const expected = [
80+
tests.task.builder().base('a').positive('a').negative('b/*.md').static().build(),
81+
tests.task.builder().base('a').positive('a/*').negative('b/*.md').build()
82+
];
83+
84+
const actual = manager.generate(['a/*', '!b/*.md'], settings);
85+
86+
assert.deepStrictEqual(actual, expected);
87+
});
88+
89+
it('should do not generate the task for the base directory when it is a static patterns', () => {
90+
const settings = new Settings({
91+
onlyFiles: false,
92+
includePatternBaseDirectory: true
93+
});
94+
95+
const expected = [
96+
tests.task.builder().base('.').positive('a').static().build()
97+
];
98+
99+
const actual = manager.generate(['a'], settings);
100+
101+
assert.deepStrictEqual(actual, expected);
102+
});
72103
});
73104

74105
describe('.convertPatternsToTasks', () => {
75106
it('should return one task when positive patterns have a global pattern', () => {
107+
const settings = new Settings();
108+
76109
const expected = [
77110
tests.task.builder().base('.').positive('*').negative('*.md').build()
78111
];
79112

80-
const actual = manager.convertPatternsToTasks(['*'], ['*.md'], /* dynamic */ true);
113+
const actual = manager.convertPatternsToTasks(['*'], ['*.md'], settings, /* dynamic */ true);
81114

82115
assert.deepStrictEqual(actual, expected);
83116
});
84117

85118
it('should return two tasks when one of patterns contains reference to the parent directory', () => {
119+
const settings = new Settings();
120+
86121
const expected = [
87122
tests.task.builder().base('..').positive('../*.md').negative('*.md').build(),
88123
tests.task.builder().base('.').positive('*').positive('a/*').negative('*.md').build()
89124
];
90125

91-
const actual = manager.convertPatternsToTasks(['*', 'a/*', '../*.md'], ['*.md'], /* dynamic */ true);
126+
const actual = manager.convertPatternsToTasks(['*', 'a/*', '../*.md'], ['*.md'], settings, /* dynamic */ true);
92127

93128
assert.deepStrictEqual(actual, expected);
94129
});
95130

96131
it('should return two tasks when all patterns refers to the different base directories', () => {
132+
const settings = new Settings();
133+
97134
const expected = [
98135
tests.task.builder().base('a').positive('a/*').negative('b/*.md').build(),
99136
tests.task.builder().base('b').positive('b/*').negative('b/*.md').build()
100137
];
101138

102-
const actual = manager.convertPatternsToTasks(['a/*', 'b/*'], ['b/*.md'], /* dynamic */ true);
139+
const actual = manager.convertPatternsToTasks(['a/*', 'b/*'], ['b/*.md'], settings, /* dynamic */ true);
103140

104141
assert.deepStrictEqual(actual, expected);
105142
});
@@ -156,12 +193,14 @@ describe('Managers → Task', () => {
156193

157194
describe('.convertPatternGroupsToTasks', () => {
158195
it('should return two tasks', () => {
196+
const settings = new Settings();
197+
159198
const expected = [
160199
tests.task.builder().base('a').positive('a/*').negative('b/*.md').build(),
161200
tests.task.builder().base('b').positive('b/*').negative('b/*.md').build()
162201
];
163202

164-
const actual = manager.convertPatternGroupsToTasks({ a: ['a/*'], b: ['b/*'] }, ['b/*.md'], /* dynamic */ true);
203+
const actual = manager.convertPatternGroupsToTasks({ a: ['a/*'], b: ['b/*'] }, ['b/*.md'], settings, /* dynamic */ true);
165204

166205
assert.deepStrictEqual(actual, expected);
167206
});

src/managers/tasks.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ export function generate(input: Pattern[], settings: Settings): Task[] {
2020
const staticPatterns = positivePatterns.filter((pattern) => utils.pattern.isStaticPattern(pattern, settings));
2121
const dynamicPatterns = positivePatterns.filter((pattern) => utils.pattern.isDynamicPattern(pattern, settings));
2222

23-
const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false);
24-
const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true);
23+
const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, settings, /* dynamic */ false);
24+
const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, settings, /* dynamic */ true);
2525

2626
return staticTasks.concat(dynamicTasks);
2727
}
@@ -52,27 +52,27 @@ function processPatterns(input: Pattern[], settings: Settings): Pattern[] {
5252
* Patterns that can be found inside (`./`) and outside (`../`) the current directory are handled separately.
5353
* This is necessary because directory traversal starts at the base directory and goes deeper.
5454
*/
55-
export function convertPatternsToTasks(positive: Pattern[], negative: Pattern[], dynamic: boolean): Task[] {
55+
export function convertPatternsToTasks(positive: Pattern[], negative: Pattern[], settings: Settings, dynamic: boolean): Task[] {
5656
const tasks: Task[] = [];
5757

5858
const patternsOutsideCurrentDirectory = utils.pattern.getPatternsOutsideCurrentDirectory(positive);
5959
const patternsInsideCurrentDirectory = utils.pattern.getPatternsInsideCurrentDirectory(positive);
6060

6161
const outsideCurrentDirectoryGroup = groupPatternsByBaseDirectory(patternsOutsideCurrentDirectory);
62-
const insideCurrentDirectoryGroup = groupPatternsByBaseDirectory(patternsInsideCurrentDirectory);
62+
let insideCurrentDirectoryGroup = groupPatternsByBaseDirectory(patternsInsideCurrentDirectory);
6363

64-
tasks.push(...convertPatternGroupsToTasks(outsideCurrentDirectoryGroup, negative, dynamic));
64+
tasks.push(...convertPatternGroupsToTasks(outsideCurrentDirectoryGroup, negative, settings, dynamic));
6565

6666
/*
6767
* For the sake of reducing future accesses to the file system, we merge all tasks within the current directory
6868
* into a global task, if at least one pattern refers to the root (`.`). In this case, the global task covers the rest.
6969
*/
7070
if ('.' in insideCurrentDirectoryGroup) {
71-
tasks.push(convertPatternGroupToTask('.', patternsInsideCurrentDirectory, negative, dynamic));
72-
} else {
73-
tasks.push(...convertPatternGroupsToTasks(insideCurrentDirectoryGroup, negative, dynamic));
71+
insideCurrentDirectoryGroup = { '.': patternsInsideCurrentDirectory };
7472
}
7573

74+
tasks.push(...convertPatternGroupsToTasks(insideCurrentDirectoryGroup, negative, settings, dynamic));
75+
7676
return tasks;
7777
}
7878

@@ -103,10 +103,22 @@ export function groupPatternsByBaseDirectory(patterns: Pattern[]): PatternsGroup
103103
}, group);
104104
}
105105

106-
export function convertPatternGroupsToTasks(positive: PatternsGroup, negative: Pattern[], dynamic: boolean): Task[] {
107-
return Object.keys(positive).map((base) => {
108-
return convertPatternGroupToTask(base, positive[base], negative, dynamic);
109-
});
106+
export function convertPatternGroupsToTasks(group: PatternsGroup, negative: Pattern[], settings: Settings, dynamic: boolean): Task[] {
107+
const tasks: Task[] = [];
108+
109+
for (const [base, patterns] of Object.entries(group)) {
110+
if (settings.includePatternBaseDirectory && dynamic) {
111+
tasks.push(createBaseDirectoryTask(base, negative));
112+
}
113+
114+
tasks.push(convertPatternGroupToTask(base, patterns, negative, dynamic));
115+
}
116+
117+
return tasks;
118+
}
119+
120+
function createBaseDirectoryTask(base: string, negative: Pattern[]): Task {
121+
return convertPatternGroupToTask(base, [base], negative, /* dynamic */ false);
110122
}
111123

112124
export function convertPatternGroupToTask(base: string, positive: Pattern[], negative: Pattern[], dynamic: boolean): Task {

src/settings.spec.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('Settings', () => {
2626
assert.ok(settings.globstar);
2727
assert.ok(settings.onlyFiles);
2828
assert.ok(settings.unique);
29+
assert.ok(!settings.includePatternBaseDirectory);
2930
assert.strictEqual(settings.concurrency, os.cpus().length);
3031
assert.strictEqual(settings.cwd, process.cwd());
3132
});
@@ -38,16 +39,27 @@ describe('Settings', () => {
3839
assert.ok(!settings.onlyFiles);
3940
});
4041

41-
it('should set the "onlyFiles" option when the "onlyDirectories" is enabled', () => {
42+
it('should set the "onlyFiles" option when the "onlyDirectories" option is enabled', () => {
4243
const settings = new Settings({
43-
onlyDirectories: true
44+
onlyDirectories: true,
45+
onlyFiles: true
4446
});
4547

4648
assert.ok(!settings.onlyFiles);
4749
assert.ok(settings.onlyDirectories);
4850
});
4951

50-
it('should set the "objectMode" option when the "stats" is enabled', () => {
52+
it('should disable the "includePatternBaseDirectory" option when the "onlyFiles" option is enabled', () => {
53+
const settings = new Settings({
54+
onlyFiles: true,
55+
includePatternBaseDirectory: true
56+
});
57+
58+
assert.ok(settings.onlyFiles);
59+
assert.ok(!settings.includePatternBaseDirectory);
60+
});
61+
62+
it('should set the "objectMode" option when the "stats" option is enabled', () => {
5163
const settings = new Settings({
5264
stats: true
5365
});

src/settings.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ export type Options = {
152152
* @default true
153153
*/
154154
unique?: boolean;
155+
/**
156+
* Include basic pattern directories in the search results.
157+
*
158+
* @default false
159+
*/
160+
includePatternBaseDirectory?: boolean;
155161
};
156162

157163
export default class Settings {
@@ -176,12 +182,17 @@ export default class Settings {
176182
public readonly suppressErrors: boolean = this._getValue(this._options.suppressErrors, false);
177183
public readonly throwErrorOnBrokenSymbolicLink: boolean = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, false);
178184
public readonly unique: boolean = this._getValue(this._options.unique, true);
185+
public readonly includePatternBaseDirectory: boolean = this._getValue(this._options.includePatternBaseDirectory, false);
179186

180187
constructor(private readonly _options: Options = {}) {
181188
if (this.onlyDirectories) {
182189
this.onlyFiles = false;
183190
}
184191

192+
if (this.onlyFiles) {
193+
this.includePatternBaseDirectory = false;
194+
}
195+
185196
if (this.stats) {
186197
this.objectMode = true;
187198
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as runner from '../runner';
2+
3+
runner.suite('Options IncludePatternBaseDirectory', {
4+
tests: [
5+
{
6+
pattern: 'fixtures/**',
7+
options: {
8+
onlyFiles: false,
9+
includePatternBaseDirectory: true
10+
}
11+
},
12+
{
13+
pattern: 'fixtures/**',
14+
options: {
15+
ignore: ['*'],
16+
onlyFiles: false,
17+
includePatternBaseDirectory: true
18+
}
19+
},
20+
{
21+
pattern: 'fixtures/{first,second}/**',
22+
options: {
23+
onlyFiles: false,
24+
includePatternBaseDirectory: true
25+
}
26+
},
27+
{
28+
pattern: 'fixtures/{first,}/**',
29+
options: {
30+
onlyFiles: false,
31+
includePatternBaseDirectory: true
32+
}
33+
}
34+
]
35+
});
36+
37+
runner.suite('Options IncludePatternBaseDirectory (cwd)', {
38+
tests: [
39+
{
40+
pattern: './*',
41+
options: {
42+
cwd: 'fixtures',
43+
onlyFiles: false,
44+
includePatternBaseDirectory: true
45+
}
46+
},
47+
{
48+
pattern: './**',
49+
options: {
50+
cwd: 'fixtures',
51+
onlyFiles: false,
52+
includePatternBaseDirectory: true
53+
}
54+
},
55+
{
56+
pattern: '**',
57+
options: {
58+
cwd: 'fixtures',
59+
onlyFiles: false,
60+
includePatternBaseDirectory: true
61+
}
62+
},
63+
{
64+
pattern: '**',
65+
options: {
66+
cwd: 'fixtures',
67+
ignore: ['*'],
68+
onlyFiles: false,
69+
includePatternBaseDirectory: true
70+
}
71+
},
72+
{
73+
pattern: '{first,second}/**',
74+
options: {
75+
cwd: 'fixtures',
76+
onlyFiles: false,
77+
includePatternBaseDirectory: true
78+
}
79+
}
80+
]
81+
});

src/utils/pattern.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,14 @@ describe('Utils → Pattern', () => {
422422

423423
assert.deepStrictEqual(actual, expected);
424424
});
425+
426+
it('should filter an empty patterns after expansion', () => {
427+
const expected = ['a', 'b'];
428+
429+
const actual = util.expandBraceExpansion('{a,{b,},}');
430+
431+
assert.deepStrictEqual(actual, expected);
432+
});
425433
});
426434

427435
describe('.getPatternParts', () => {

src/utils/pattern.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ export function expandBraceExpansion(pattern: Pattern): Pattern[] {
164164
*/
165165
patterns.sort((a, b) => a.length - b.length);
166166

167-
return patterns;
167+
/**
168+
* Micromatch may return an empty string in the case of patterns like `{a,}`.
169+
*/
170+
return patterns.filter((pattern) => pattern !== '');
168171
}
169172

170173
export function getPatternParts(pattern: Pattern, options: MicromatchOptions): Pattern[] {

0 commit comments

Comments
 (0)