diff --git a/README.md b/README.md index 4023d50e..0d329d10 100644 --- a/README.md +++ b/README.md @@ -283,13 +283,15 @@ Enable a [case-sensitive](https://en.wikipedia.org/wiki/Case_sensitivity) mode f * Case-sensitive for `test/file.*` pattern: `test/file.md` * Case-insensitive for `test/file.*` pattern: `test/file.md`, `test/File.md` -#### matchBase +#### baseNameMatch * Type: `boolean` * Default: `false` Allow glob patterns without slashes to match a file path based on its basename. For example, `a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`. +> :book: This option has no affect to negative patterns from any source (patterns, `ignore`). + #### suppressErrors * Type: `boolean` @@ -404,7 +406,7 @@ Not fully, because `fast-glob` does not implement all options of `node-glob`. Se | `noglobstar` | [`globstar`](#globstar) | | `noext` | [`extglob`](#extglob) | | `nocase` | [`caseSensitiveMatch`](#caseSensitiveMatch) | -| `matchBase` | [`matchbase`](#matchbase) | +| `matchBase` | [`baseNameMatch`](#baseNameMatch) | | `nodir` | [`onlyFiles`](#onlyfiles) | | `ignore` | [`ignore`](#ignore) | | `follow` | [`followSymbolicLinks`](#followSymbolicLinks) | diff --git a/src/providers/filters/deep.spec.ts b/src/providers/filters/deep.spec.ts index 13ed495f..d2a109fc 100644 --- a/src/providers/filters/deep.spec.ts +++ b/src/providers/filters/deep.spec.ts @@ -108,6 +108,15 @@ describe('Providers → Filters → Deep', () => { assert.ok(!actual); }); + it('should return `treu` when the positive pattern has no affect to depth reading, but the `baseNameMatch` is enabled', () => { + const filter = getFilter('.', ['*'], [], { baseNameMatch: true }); + const entry = tests.entry.builder().path('root/directory').directory().build(); + + const actual = filter(entry); + + assert.ok(actual); + }); + it('should return `true` when the negative pattern has no effect to depth reading', () => { const filter = getFilter('.', ['**/*'], ['**/*']); const entry = tests.entry.builder().path('root/directory').directory().build(); diff --git a/src/providers/filters/deep.ts b/src/providers/filters/deep.ts index 83cf867d..33e560e3 100644 --- a/src/providers/filters/deep.ts +++ b/src/providers/filters/deep.ts @@ -60,7 +60,7 @@ export default class DeepFilter { } private _isSkippedByMaxPatternDepth(entryDepth: number, maxPatternDepth: number): boolean { - return maxPatternDepth !== Infinity && entryDepth > maxPatternDepth; + return !this._settings.baseNameMatch && maxPatternDepth !== Infinity && entryDepth > maxPatternDepth; } private _isSkippedSymbolicLink(entry: Entry): boolean { diff --git a/src/providers/filters/entry.spec.ts b/src/providers/filters/entry.spec.ts index 3267fece..ab0a8597 100644 --- a/src/providers/filters/entry.spec.ts +++ b/src/providers/filters/entry.spec.ts @@ -109,7 +109,7 @@ describe('Providers → Filters → Entry', () => { assert.ok(!actual); }); - it('should return `false` when an entry do not match to the negative pattern', () => { + it('should return `true` when an entry do not match to the negative pattern', () => { const filter = getFilter(['**/*'], ['*'], { absolute: true }); const entry = tests.entry.builder().path('root/file.txt').file().build(); @@ -120,6 +120,28 @@ describe('Providers → Filters → Entry', () => { }); }); + describe('options.baseNameMatch', () => { + it('should return `false` when an option is disabled', () => { + const filter = getFilter(['*'], []); + + const entry = tests.entry.builder().path('root/file.txt').file().build(); + + const actual = filter(entry); + + assert.ok(!actual); + }); + + it('should return `true` when an option is enabled', () => { + const filter = getFilter(['*'], [], { baseNameMatch: true }); + + const entry = tests.entry.builder().path('root/file.txt').file().build(); + + const actual = filter(entry); + + assert.ok(actual); + }); + }); + describe('Pattern', () => { it('should return `false` when an entry match to the negative pattern', () => { const filter = getFilter(['**/*'], ['**/*']); diff --git a/src/providers/filters/entry.ts b/src/providers/filters/entry.ts index 83269bb9..f412d1c4 100644 --- a/src/providers/filters/entry.ts +++ b/src/providers/filters/entry.ts @@ -31,7 +31,9 @@ export default class EntryFilter { return false; } - return this._isMatchToPatterns(entry.path, positiveRe) && !this._isMatchToPatterns(entry.path, negativeRe); + const filepath = this._settings.baseNameMatch ? entry.name : entry.path; + + return this._isMatchToPatterns(filepath, positiveRe) && !this._isMatchToPatterns(entry.path, negativeRe); } private _isDuplicateEntry(entry: Entry): boolean { diff --git a/src/providers/provider.ts b/src/providers/provider.ts index 255b5b57..ede278dc 100644 --- a/src/providers/provider.ts +++ b/src/providers/provider.ts @@ -42,7 +42,7 @@ export default abstract class Provider { protected _getMicromatchOptions(): MicromatchOptions { return { dot: this._settings.dot, - matchBase: this._settings.matchBase, + matchBase: this._settings.baseNameMatch, nobrace: !this._settings.braceExpansion, nocase: !this._settings.caseSensitiveMatch, noext: !this._settings.extglob, diff --git a/src/settings.spec.ts b/src/settings.spec.ts index 0850eb95..a77a7091 100644 --- a/src/settings.spec.ts +++ b/src/settings.spec.ts @@ -6,27 +6,27 @@ describe('Settings', () => { it('should return instance with default values', () => { const settings = new Settings(); - assert.strictEqual(settings.concurrency, Infinity); - assert.strictEqual(settings.cwd, process.cwd()); - assert.ok(settings.deep); + assert.deepStrictEqual(settings.fs, DEFAULT_FILE_SYSTEM_ADAPTER); assert.deepStrictEqual(settings.ignore, []); + assert.ok(!settings.absolute); + assert.ok(!settings.baseNameMatch); assert.ok(!settings.dot); + assert.ok(!settings.markDirectories); assert.ok(!settings.objectMode); - assert.ok(!settings.stats); - assert.ok(settings.onlyFiles); assert.ok(!settings.onlyDirectories); - assert.ok(settings.followSymbolicLinks); + assert.ok(!settings.stats); + assert.ok(!settings.suppressErrors); assert.ok(!settings.throwErrorOnBrokenSymbolicLink); - assert.ok(settings.unique); - assert.ok(!settings.markDirectories); - assert.ok(!settings.absolute); assert.ok(settings.braceExpansion); - assert.ok(settings.globstar); - assert.ok(settings.extglob); assert.ok(settings.caseSensitiveMatch); - assert.ok(!settings.matchBase); - assert.ok(!settings.suppressErrors); - assert.deepStrictEqual(settings.fs, DEFAULT_FILE_SYSTEM_ADAPTER); + assert.ok(settings.deep); + assert.ok(settings.extglob); + assert.ok(settings.followSymbolicLinks); + assert.ok(settings.globstar); + assert.ok(settings.onlyFiles); + assert.ok(settings.unique); + assert.strictEqual(settings.concurrency, Infinity); + assert.strictEqual(settings.cwd, process.cwd()); }); it('should return instance with custom values', () => { diff --git a/src/settings.ts b/src/settings.ts index e9b5275e..f9166b99 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -91,7 +91,7 @@ export interface Options { * Allow glob patterns without slashes to match a file path based on its basename. * For example, `a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`. */ - matchBase?: boolean; + baseNameMatch?: boolean; /** * Suppress any errors from reader. * Can be useful when the directory has entries with a special level of access. @@ -105,6 +105,7 @@ export interface Options { export default class Settings { public readonly absolute: boolean = this._getValue(this._options.absolute, false); + public readonly baseNameMatch: boolean = this._getValue(this._options.baseNameMatch, false); public readonly braceExpansion: boolean = this._getValue(this._options.braceExpansion, true); public readonly caseSensitiveMatch: boolean = this._getValue(this._options.caseSensitiveMatch, true); public readonly concurrency: number = this._getValue(this._options.concurrency, Infinity); @@ -117,7 +118,6 @@ export default class Settings { public readonly globstar: boolean = this._getValue(this._options.globstar, true); public readonly ignore: Pattern[] = this._getValue(this._options.ignore, [] as Pattern[]); public readonly markDirectories: boolean = this._getValue(this._options.markDirectories, false); - public readonly matchBase: boolean = this._getValue(this._options.matchBase, false); public readonly objectMode: boolean = this._getValue(this._options.objectMode, false); public readonly onlyDirectories: boolean = this._getValue(this._options.onlyDirectories, false); public readonly onlyFiles: boolean = this._getValue(this._options.onlyFiles, true); diff --git a/src/tests/smoke/base-name-match.ts b/src/tests/smoke/base-name-match.ts new file mode 100644 index 00000000..0cc5a122 --- /dev/null +++ b/src/tests/smoke/base-name-match.ts @@ -0,0 +1,10 @@ +import * as smoke from './smoke'; + +smoke.suite('Smoke → MatchBase', [ + { + pattern: '*.md', + cwd: 'fixtures', + globOptions: { matchBase: true }, + fgOptions: { baseNameMatch: true } + } +]); diff --git a/src/tests/smoke/case-sensitive-match.smoke.ts b/src/tests/smoke/case-sensitive-match.smoke.ts new file mode 100644 index 00000000..f65aa5a1 --- /dev/null +++ b/src/tests/smoke/case-sensitive-match.smoke.ts @@ -0,0 +1,12 @@ +import * as smoke from './smoke'; + +smoke.suite('Smoke → CaseSensitiveMatch', [ + { + pattern: 'fixtures/File.md' + }, + { + pattern: 'fixtures/File.md', + globOptions: { nocase: true }, + fgOptions: { caseSensitiveMatch: false } + } +]); diff --git a/src/tests/smoke/mark-directories.smoke.ts b/src/tests/smoke/mark-directories.smoke.ts new file mode 100644 index 00000000..494dd9ad --- /dev/null +++ b/src/tests/smoke/mark-directories.smoke.ts @@ -0,0 +1,9 @@ +import * as smoke from './smoke'; + +smoke.suite('Smoke → MarkDirectories', [ + { + pattern: 'fixtures/**/*', + globOptions: { mark: true }, + fgOptions: { markDirectories: true } + } +]);