Skip to content

Commit ef533d0

Browse files
authored
Merge pull request #196 from rajanpanth/fix/bracket-negation-default
fix: translate POSIX bracket negation by default
2 parents ea09356 + 73de57b commit ef533d0

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

lib/parse.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -748,8 +748,12 @@ const parse = (input, options) => {
748748
value = `\\${value}`;
749749
}
750750

751-
if (opts.posix === true && value === '!' && prev.value === '[') {
752-
value = '^';
751+
if (value === '!' && prev.value === '[' && opts.literalBrackets !== true) {
752+
const isLoneNegation = peek() === ']' && !remaining().slice(1).includes(']');
753+
if (!isLoneNegation) {
754+
// POSIX bracket negation: [!abc] is equivalent to [^abc]
755+
value = '^';
756+
}
753757
}
754758

755759
prev.value += value;

test/brackets.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,83 @@ describe('brackets', () => {
2323
assert(!isMatch('a/b', '[a]*'));
2424
});
2525
});
26+
27+
describe('bracket negation', () => {
28+
it('should negate a bracket expression with a leading "!"', () => {
29+
assert(!isMatch('a', '[!abc]'));
30+
assert(!isMatch('b', '[!abc]'));
31+
assert(!isMatch('c', '[!abc]'));
32+
assert(isMatch('d', '[!abc]'));
33+
assert(isMatch('x', '[!abc]'));
34+
});
35+
36+
it('should negate ranges with a leading "!"', () => {
37+
assert(!isMatch('a', '[!a-c]'));
38+
assert(!isMatch('c', '[!a-c]'));
39+
assert(isMatch('d', '[!a-c]'));
40+
});
41+
42+
it('should support "^" as an alternative to "!"', () => {
43+
assert(!isMatch('a', '[^abc]'));
44+
assert(isMatch('d', '[^abc]'));
45+
assert(!isMatch('a', '[^a-c]'));
46+
assert(isMatch('d', '[^a-c]'));
47+
});
48+
49+
it('should support negated brackets in larger patterns', () => {
50+
assert(!isMatch('abc', 'a[!b]c'));
51+
assert(isMatch('axc', 'a[!b]c'));
52+
assert(!isMatch('ad', '[!abc]d'));
53+
assert(isMatch('xd', '[!abc]d'));
54+
});
55+
56+
it('should not match slashes with negated brackets', () => {
57+
assert(!isMatch('/', '[!a]'));
58+
assert(!isMatch('a/b', 'a[!x]b'));
59+
});
60+
61+
it('should treat escaped "!" as a literal character', () => {
62+
assert(isMatch('!', '[\\!a]'));
63+
assert(isMatch('a', '[\\!a]'));
64+
assert(!isMatch('b', '[\\!a]'));
65+
});
66+
67+
it('should treat non-leading "!" as literal characters', () => {
68+
assert(isMatch('!', '[a!]'));
69+
assert(isMatch('a', '[a!]'));
70+
assert(!isMatch('b', '[a!]'));
71+
});
72+
73+
it('should negate a literal "!"', () => {
74+
assert(!isMatch('!', '[!!]'));
75+
assert(isMatch('a', '[!!]'));
76+
});
77+
78+
it('should preserve incomplete bracket expressions', () => {
79+
assert(isMatch('zx[!]y', '*x[!]y'));
80+
assert(isMatch('zx!y', '*x[!]y'));
81+
});
82+
83+
it('should negate a closing bracket when it is first in the class', () => {
84+
assert(!isMatch(']', '[!]]'));
85+
assert(isMatch('a', '[!]]'));
86+
});
87+
88+
it('should negate brackets when `options.posix` is false', () => {
89+
assert(!isMatch('a', '[!abc]', { posix: false }));
90+
assert(isMatch('d', '[!abc]', { posix: false }));
91+
});
92+
93+
it('should respect `options.literalBrackets`', () => {
94+
const options = { literalBrackets: true };
95+
assert(isMatch('zx[!a]y', '*x[!a]y', options));
96+
assert(!isMatch('xby', 'x[!a]y', options));
97+
});
98+
99+
it('should respect `options.nobracket`', () => {
100+
const options = { nobracket: true };
101+
assert(isMatch('zx[!a]y', '*x[!a]y', options));
102+
assert(!isMatch('xby', 'x[!a]y', options));
103+
});
104+
});
26105
});

0 commit comments

Comments
 (0)