Skip to content

Commit feee3bd

Browse files
authored
Merge pull request #258 from mrmlnc/ISSUE-257_fix_patterns_with_leading_dot
ISSUE-257: Fix problem with patterns with leading dot segment
2 parents 78c7780 + abe17b6 commit feee3bd

8 files changed

Lines changed: 66 additions & 20 deletions

File tree

src/providers/filters/deep.spec.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,24 @@ describe('Providers → Filters → Deep', () => {
8787
});
8888

8989
it('should return `false` when an entry starts with "./" and does not match to the positive pattern', () => {
90-
const filter = getFilter('.', ['non-root/directory'], []);
90+
const filter = getFilter('.', ['non-root/*'], []);
9191
const entry = tests.entry.builder().path('./root').directory().build();
9292

9393
const actual = filter(entry);
9494

9595
assert.ok(!actual);
9696
});
9797

98-
it('should return `true` when the positive pattern does not match, but the `baseNameMatch` is enabled', () => {
98+
it('should return `true` when an entry match to the positive pattern with leading dot', () => {
99+
const filter = getFilter('.', ['./root/*'], []);
100+
const entry = tests.entry.builder().path('./root').directory().build();
101+
102+
const actual = filter(entry);
103+
104+
assert.ok(actual);
105+
});
106+
107+
it('should return `true` when the positive pattern does not match by level, but the `baseNameMatch` is enabled', () => {
99108
const filter = getFilter('.', ['*'], [], { baseNameMatch: true });
100109
const entry = tests.entry.builder().path('root/directory').directory().build();
101110

src/providers/filters/deep.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ export default class DeepFilter {
3434
return false;
3535
}
3636

37-
if (this._isSkippedByPositivePatterns(entry, matcher)) {
37+
const filepath = utils.path.removeLeadingDotSegment(entry.path);
38+
39+
if (this._isSkippedByPositivePatterns(filepath, matcher)) {
3840
return false;
3941
}
4042

41-
return this._isSkippedByNegativePatterns(entry, negativeRe);
43+
return this._isSkippedByNegativePatterns(filepath, negativeRe);
4244
}
4345

4446
private _isSkippedByDeep(entryDepth: number): boolean {
@@ -56,11 +58,11 @@ export default class DeepFilter {
5658
return entryPathDepth - (basePath === '' ? 0 : basePathDepth);
5759
}
5860

59-
private _isSkippedByPositivePatterns(entry: Entry, matcher: PartialMatcher): boolean {
60-
return !this._settings.baseNameMatch && !matcher.match(entry.path);
61+
private _isSkippedByPositivePatterns(entryPath: string, matcher: PartialMatcher): boolean {
62+
return !this._settings.baseNameMatch && !matcher.match(entryPath);
6163
}
6264

63-
private _isSkippedByNegativePatterns(entry: Entry, negativeRe: PatternRe[]): boolean {
64-
return !utils.pattern.matchAny(entry.path, negativeRe);
65+
private _isSkippedByNegativePatterns(entryPath: string, negativeRe: PatternRe[]): boolean {
66+
return !utils.pattern.matchAny(entryPath, negativeRe);
6567
}
6668
}

src/providers/filters/entry.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,15 @@ describe('Providers → Filters → Entry', () => {
161161
assert.ok(!actual);
162162
});
163163

164+
it('should return `true` when an entry match to the positive pattern with leading dot', () => {
165+
const filter = getFilter(['./**/*'], []);
166+
const entry = tests.entry.builder().path('./root/file.txt').file().build();
167+
168+
const actual = filter(entry);
169+
170+
assert.ok(actual);
171+
});
172+
164173
it('should return `true` when an entry match to the positive pattern', () => {
165174
const filter = getFilter(['**/*'], []);
166175
const entry = tests.entry.builder().path('root/file.txt').file().build();

src/providers/filters/entry.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ export default class EntryFilter {
6262
return this._isMatchToPatterns(fullpath, negativeRe);
6363
}
6464

65-
private _isMatchToPatterns(filepath: string, patternsRe: PatternRe[]): boolean {
65+
private _isMatchToPatterns(entryPath: string, patternsRe: PatternRe[]): boolean {
66+
const filepath = utils.path.removeLeadingDotSegment(entryPath);
67+
6668
return utils.pattern.matchAny(filepath, patternsRe);
6769
}
6870
}

src/utils/path.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,23 @@ describe('Utils → Path', () => {
5454
assert.strictEqual(util.escape('abc@'), 'abc@');
5555
});
5656
});
57+
58+
describe('.removeLeadingDotCharacters', () => {
59+
it('should return path without changes', () => {
60+
assert.strictEqual(util.removeLeadingDotSegment('../a/b'), '../a/b');
61+
assert.strictEqual(util.removeLeadingDotSegment('~/a/b'), '~/a/b');
62+
assert.strictEqual(util.removeLeadingDotSegment('/a/b'), '/a/b');
63+
assert.strictEqual(util.removeLeadingDotSegment('a/b'), 'a/b');
64+
65+
assert.strictEqual(util.removeLeadingDotSegment('..\\a\\b'), '..\\a\\b');
66+
assert.strictEqual(util.removeLeadingDotSegment('~\\a\\b'), '~\\a\\b');
67+
assert.strictEqual(util.removeLeadingDotSegment('\\a\\b'), '\\a\\b');
68+
assert.strictEqual(util.removeLeadingDotSegment('a\\b'), 'a\\b');
69+
});
70+
71+
it('should return path without leading dit characters', () => {
72+
assert.strictEqual(util.removeLeadingDotSegment('./a/b'), 'a/b');
73+
assert.strictEqual(util.removeLeadingDotSegment('.\\a\\b'), 'a\\b');
74+
});
75+
});
5776
});

src/utils/path.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as path from 'path';
22

33
import { Pattern } from '../types';
44

5+
const LEADING_DOT_SEGMENT_CHARACTERS_COUNT = 2; // ./ or .\\
56
const UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([()*?[\]{|}]|^!|[!+@](?=\())/g;
67

78
/**
@@ -18,3 +19,17 @@ export function makeAbsolute(cwd: string, filepath: string): string {
1819
export function escape(pattern: Pattern): Pattern {
1920
return pattern.replace(UNESCAPED_GLOB_SYMBOLS_RE, '\\$2');
2021
}
22+
23+
export function removeLeadingDotSegment(entry: string): string {
24+
// We do not use `startsWith` because this is 10x slower than current implementation for some cases.
25+
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
26+
if (entry.charAt(0) === '.') {
27+
const secondCharactery = entry.charAt(1);
28+
29+
if (secondCharactery === '/' || secondCharactery === '\\') {
30+
return entry.slice(LEADING_DOT_SEGMENT_CHARACTERS_COUNT);
31+
}
32+
}
33+
34+
return entry;
35+
}

src/utils/pattern.spec.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -399,13 +399,5 @@ describe('Utils → Pattern', () => {
399399

400400
assert.ok(!actual);
401401
});
402-
403-
it('should return true for path with leading slash', () => {
404-
const pattern = util.makeRe('*.js', {});
405-
406-
const actual = util.matchAny('./test.js', [pattern]);
407-
408-
assert.ok(actual);
409-
});
410402
});
411403
});

src/utils/pattern.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,5 @@ export function convertPatternsToRe(patterns: Pattern[], options: MicromatchOpti
127127
}
128128

129129
export function matchAny(entry: string, patternsRe: PatternRe[]): boolean {
130-
const filepath = entry.replace(/^\.[/\\]/, '');
131-
132-
return patternsRe.some((patternRe) => patternRe.test(filepath));
130+
return patternsRe.some((patternRe) => patternRe.test(entry));
133131
}

0 commit comments

Comments
 (0)