Skip to content

Commit 56083ef

Browse files
authoredMay 22, 2021
Merge pull request #84 from micromatch/fix-83
Fix 83
2 parents 360b12f + e0042e0 commit 56083ef

9 files changed

+130
-49
lines changed
 

‎.eslintrc.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
],
55

66
"env": {
7-
"es6": true,
7+
"es2021": true,
88
"node": true
99
},
1010

11-
"parserOptions":{
12-
"ecmaVersion": 9
11+
"parserOptions": {
12+
"ecmaVersion": 12
1313
},
1414

1515
"rules": {
1616
"accessor-pairs": 2,
17+
"arrow-parens": [2, "as-needed"],
1718
"arrow-spacing": [2, { "before": true, "after": true }],
1819
"block-spacing": [2, "always"],
1920
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
@@ -26,7 +27,7 @@
2627
"eol-last": 2,
2728
"eqeqeq": [2, "allow-null"],
2829
"generator-star-spacing": [2, { "before": true, "after": true }],
29-
"handle-callback-err": [2, "^(err|error)$" ],
30+
"handle-callback-err": [2, "^(err|error)$"],
3031
"indent": [2, 2, { "SwitchCase": 1 }],
3132
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
3233
"keyword-spacing": [2, { "before": true, "after": true }],
@@ -36,7 +37,6 @@
3637
"no-caller": 2,
3738
"no-class-assign": 2,
3839
"no-cond-assign": 2,
39-
"no-console": 0,
4040
"no-const-assign": 2,
4141
"no-control-regex": 2,
4242
"no-debugger": 2,
@@ -104,13 +104,13 @@
104104
"one-var": [2, { "initialized": "never" }],
105105
"operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }],
106106
"padded-blocks": [0, "never"],
107-
"prefer-const": 2,
107+
"prefer-const": [2, { "destructuring": "all", "ignoreReadBeforeAssign": false }],
108108
"quotes": [2, "single", "avoid-escape"],
109109
"radix": 2,
110110
"semi": [2, "always"],
111111
"semi-spacing": [2, { "before": false, "after": true }],
112112
"space-before-blocks": [2, "always"],
113-
"space-before-function-paren": [2, "never"],
113+
"space-before-function-paren": [2, { "anonymous": "never", "named": "never", "asyncArrow": "always" }],
114114
"space-in-parens": [2, "never"],
115115
"space-infix-ops": 2,
116116
"space-unary-ops": [2, { "words": true, "nonwords": false }],

‎.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- name: Clone repository
2121
uses: actions/checkout@v2
2222

23-
- name: Set up Node.js
23+
- name: Setup Node.js
2424
uses: actions/setup-node@v2
2525
with:
2626
node-version: ${{ matrix.node }}

‎examples/extglob-negated.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
const picomatch = require('..');
4+
5+
const fixtures = [
6+
['/file.d.ts', false],
7+
['/file.ts', true],
8+
['/file.d.something.ts', true],
9+
['/file.dhello.ts', true]
10+
];
11+
12+
const pattern = '/!(*.d).ts';
13+
const isMatch = picomatch(pattern);
14+
15+
console.log(fixtures.map(f => [isMatch(f[0]), f[1]]));

‎examples/regex-quantifier.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
3+
const pico = require('..');
4+
5+
/**
6+
* See: https://github.com/gulpjs/glob-parent/issues/39#issuecomment-794075641
7+
*/
8+
9+
const files = [
10+
'data/100-123a_files/0/',
11+
'data/100-123a_files/1/',
12+
'data/100-123a_files/2/',
13+
'data/100-123a_files/3/',
14+
'data/100-123b_files/0/',
15+
'data/100-123b_files/1/',
16+
'data/100-123b_files/2/',
17+
'data/100-123b_files/3/',
18+
'data/100-123a_files/4/',
19+
'data/100-123ax_files/0/',
20+
'data/100-123A_files/0/',
21+
'data/100-123A_files/1/',
22+
'data/100-123A_files/2/',
23+
'data/100-123A_files/3/',
24+
'data/100-123B_files/0/',
25+
'data/100-123B_files/1/',
26+
'data/100-123B_files/2/',
27+
'data/100-123B_files/3/',
28+
'data/100-123A_files/4/',
29+
'data/100-123AX_files/0/'
30+
];
31+
32+
// ? is a wildcard for matching one character
33+
// by escaping \\{0,3}, and then using `{ unescape: true }, we tell
34+
// picomatch to treat those characters as a regex quantifier, versus
35+
// a brace pattern.
36+
37+
const isMatch = pico('data/100-123?\\{0,3}_files/{0..3}/', { unescape: true });
38+
console.log(files.filter(name => isMatch(name)));
39+
40+
// Alternatively, we can use a regex character class to be more specific
41+
// In the following example, we'll only match uppercase alpha characters
42+
const isMatch2 = pico('data/100-123[A-Z]*_files/{0..3}/', { unescape: true });
43+
console.log(files.filter(name => isMatch2(name)));

‎lib/parse.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const parse = (input, options) => {
9292
START_ANCHOR
9393
} = PLATFORM_CHARS;
9494

95-
const globstar = (opts) => {
95+
const globstar = opts => {
9696
return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
9797
};
9898

@@ -142,12 +142,13 @@ const parse = (input, options) => {
142142

143143
const eos = () => state.index === len - 1;
144144
const peek = state.peek = (n = 1) => input[state.index + n];
145-
const advance = state.advance = () => input[++state.index];
145+
const advance = state.advance = () => input[++state.index] || '';
146146
const remaining = () => input.slice(state.index + 1);
147147
const consume = (value = '', num = 0) => {
148148
state.consumed += value;
149149
state.index += num;
150150
};
151+
151152
const append = token => {
152153
state.output += token.output != null ? token.output : token.value;
153154
consume(token.value);
@@ -203,7 +204,7 @@ const parse = (input, options) => {
203204
}
204205
}
205206

206-
if (extglobs.length && tok.type !== 'paren' && !EXTGLOB_CHARS[tok.value]) {
207+
if (extglobs.length && tok.type !== 'paren') {
207208
extglobs[extglobs.length - 1].inner += tok.value;
208209
}
209210

@@ -235,6 +236,7 @@ const parse = (input, options) => {
235236

236237
const extglobClose = token => {
237238
let output = token.close + (opts.capture ? ')' : '');
239+
let rest;
238240

239241
if (token.type === 'negate') {
240242
let extglobStar = star;
@@ -247,6 +249,10 @@ const parse = (input, options) => {
247249
output = token.close = `)$))${extglobStar}`;
248250
}
249251

252+
if (token.inner.includes('*') && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) {
253+
output = token.close = `)${rest})${extglobStar})`;
254+
}
255+
250256
if (token.prev.type === 'bos') {
251257
state.negatedExtglob = true;
252258
}
@@ -356,9 +362,9 @@ const parse = (input, options) => {
356362
}
357363

358364
if (opts.unescape === true) {
359-
value = advance() || '';
365+
value = advance();
360366
} else {
361-
value += advance() || '';
367+
value += advance();
362368
}
363369

364370
if (state.brackets === 0) {
@@ -1022,7 +1028,7 @@ parse.fastpaths = (input, options) => {
10221028
star = `(${star})`;
10231029
}
10241030

1025-
const globstar = (opts) => {
1031+
const globstar = opts => {
10261032
if (opts.noglobstar === true) return star;
10271033
return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
10281034
};

‎lib/picomatch.js

+34-31
Original file line numberDiff line numberDiff line change
@@ -231,68 +231,71 @@ picomatch.parse = (pattern, options) => {
231231
picomatch.scan = (input, options) => scan(input, options);
232232

233233
/**
234-
* Create a regular expression from a parsed glob pattern.
235-
*
236-
* ```js
237-
* const picomatch = require('picomatch');
238-
* const state = picomatch.parse('*.js');
239-
* // picomatch.compileRe(state[, options]);
234+
* Compile a regular expression from the `state` object returned by the
235+
* [parse()](#parse) method.
240236
*
241-
* console.log(picomatch.compileRe(state));
242-
* //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
243-
* ```
244-
* @param {String} `state` The object returned from the `.parse` method.
237+
* @param {Object} `state`
245238
* @param {Object} `options`
246-
* @return {RegExp} Returns a regex created from the given pattern.
239+
* @param {Boolean} `returnOutput` Intended for implementors, this argument allows you to return the raw output from the parser.
240+
* @param {Boolean} `returnState` Adds the state to a `state` property on the returned regex. Useful for implementors and debugging.
241+
* @return {RegExp}
247242
* @api public
248243
*/
249244

250-
picomatch.compileRe = (parsed, options, returnOutput = false, returnState = false) => {
245+
picomatch.compileRe = (state, options, returnOutput = false, returnState = false) => {
251246
if (returnOutput === true) {
252-
return parsed.output;
247+
return state.output;
253248
}
254249

255250
const opts = options || {};
256251
const prepend = opts.contains ? '' : '^';
257252
const append = opts.contains ? '' : '$';
258253

259-
let source = `${prepend}(?:${parsed.output})${append}`;
260-
if (parsed && parsed.negated === true) {
254+
let source = `${prepend}(?:${state.output})${append}`;
255+
if (state && state.negated === true) {
261256
source = `^(?!${source}).*$`;
262257
}
263258

264259
const regex = picomatch.toRegex(source, options);
265260
if (returnState === true) {
266-
regex.state = parsed;
261+
regex.state = state;
267262
}
268263

269264
return regex;
270265
};
271266

272-
picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => {
267+
/**
268+
* Create a regular expression from a parsed glob pattern.
269+
*
270+
* ```js
271+
* const picomatch = require('picomatch');
272+
* const state = picomatch.parse('*.js');
273+
* // picomatch.compileRe(state[, options]);
274+
*
275+
* console.log(picomatch.compileRe(state));
276+
* //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
277+
* ```
278+
* @param {String} `state` The object returned from the `.parse` method.
279+
* @param {Object} `options`
280+
* @param {Boolean} `returnOutput` Implementors may use this argument to return the compiled output, instead of a regular expression. This is not exposed on the options to prevent end-users from mutating the result.
281+
* @param {Boolean} `returnState` Implementors may use this argument to return the state from the parsed glob with the returned regular expression.
282+
* @return {RegExp} Returns a regex created from the given pattern.
283+
* @api public
284+
*/
285+
286+
picomatch.makeRe = (input, options = {}, returnOutput = false, returnState = false) => {
273287
if (!input || typeof input !== 'string') {
274288
throw new TypeError('Expected a non-empty string');
275289
}
276290

277-
const opts = options || {};
278291
let parsed = { negated: false, fastpaths: true };
279-
let prefix = '';
280-
let output;
281-
282-
if (input.startsWith('./')) {
283-
input = input.slice(2);
284-
prefix = parsed.prefix = './';
285-
}
286292

287-
if (opts.fastpaths !== false && (input[0] === '.' || input[0] === '*')) {
288-
output = parse.fastpaths(input, options);
293+
if (options.fastpaths !== false && (input[0] === '.' || input[0] === '*')) {
294+
parsed.output = parse.fastpaths(input, options);
289295
}
290296

291-
if (output === undefined) {
297+
if (!parsed.output) {
292298
parsed = parse(input, options);
293-
parsed.prefix = prefix + (parsed.prefix || '');
294-
} else {
295-
parsed.output = output;
296299
}
297300

298301
return picomatch.compileRe(parsed, options, returnOutput, returnState);

‎test/api.picomatch.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ const picomatch = require('..');
55
const { isMatch } = picomatch;
66

77
const assertTokens = (actual, expected) => {
8-
const keyValuePairs = actual.map((token) => [token.type, token.value]);
9-
8+
const keyValuePairs = actual.map(token => [token.type, token.value]);
109
assert.deepStrictEqual(keyValuePairs, expected);
1110
};
1211

@@ -214,7 +213,7 @@ describe('picomatch', () => {
214213
assert(!isMatch('zzjs', '*z.js'));
215214
});
216215

217-
it('issue #24', () => {
216+
it('issue #24 - should match zero or more directories', () => {
218217
assert(!isMatch('a/b/c/d/', 'a/b/**/f'));
219218
assert(isMatch('a', 'a/**'));
220219
assert(isMatch('a', '**'));
@@ -249,6 +248,7 @@ describe('picomatch', () => {
249248
assert(!isMatch('deep/foo/bar/baz', '**/bar/*/'));
250249
assert(!isMatch('deep/foo/bar/baz/', '**/bar/*', { strictSlashes: true }));
251250
assert(isMatch('deep/foo/bar/baz/', '**/bar/*'));
251+
assert(isMatch('deep/foo/bar/baz', '**/bar/*'));
252252
assert(isMatch('foo', 'foo/**'));
253253
assert(isMatch('deep/foo/bar/baz/', '**/bar/*{,/}'));
254254
assert(isMatch('a/b/j/c/z/x.md', 'a/**/j/**/z/*.md'));

‎test/extglobs-temp.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { isMatch } = require('..');
66

77
/**
88
* Some of tests were converted from bash 4.3, 4.4, and minimatch unit tests.
9+
* This is called "temp" as a reminder to reorganize these test and remove duplicates.
910
*/
1011

1112
describe('extglobs', () => {

‎test/extglobs.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ describe('extglobs', () => {
5454
assert(isMatch('abc', 'a!(.)c'));
5555
});
5656

57+
// See https://github.com/micromatch/picomatch/issues/83
58+
it('should support stars in negation extglobs', () => {
59+
assert(!isMatch('/file.d.ts', '/!(*.d).ts'));
60+
assert(isMatch('/file.ts', '/!(*.d).ts'));
61+
assert(isMatch('/file.d.something.ts', '/!(*.d).ts'));
62+
assert(isMatch('/file.dhello.ts', '/!(*.d).ts'));
63+
64+
assert(!isMatch('/file.d.ts', '**/!(*.d).ts'));
65+
assert(isMatch('/file.ts', '**/!(*.d).ts'));
66+
assert(isMatch('/file.d.something.ts', '**/!(*.d).ts'));
67+
assert(isMatch('/file.dhello.ts', '**/!(*.d).ts'));
68+
});
69+
5770
it('should support negation extglobs in patterns with slashes', () => {
5871
assert(!isMatch('foo/abc', 'foo/!(abc)'));
5972
assert(isMatch('foo/bar', 'foo/!(abc)'));
@@ -271,7 +284,7 @@ describe('extglobs', () => {
271284
assert(isMatch('ab.md', '?(a|ab|b).md'));
272285
assert(isMatch('b.md', '?(a|ab|b).md'));
273286

274-
// see https://github.com/micromatch/micromatch/issues/186
287+
// See https://github.com/micromatch/micromatch/issues/186
275288
assert(isMatch('ab', '+(a)?(b)'));
276289
assert(isMatch('aab', '+(a)?(b)'));
277290
assert(isMatch('aa', '+(a)?(b)'));

0 commit comments

Comments
 (0)
Please sign in to comment.