Skip to content

Commit

Permalink
Merge pull request #84 from micromatch/fix-83
Browse files Browse the repository at this point in the history
Fix 83
  • Loading branch information
jonschlinkert committed May 22, 2021
2 parents 360b12f + e0042e0 commit 56083ef
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 49 deletions.
14 changes: 7 additions & 7 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
],

"env": {
"es6": true,
"es2021": true,
"node": true
},

"parserOptions":{
"ecmaVersion": 9
"parserOptions": {
"ecmaVersion": 12
},

"rules": {
"accessor-pairs": 2,
"arrow-parens": [2, "as-needed"],
"arrow-spacing": [2, { "before": true, "after": true }],
"block-spacing": [2, "always"],
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
Expand All @@ -26,7 +27,7 @@
"eol-last": 2,
"eqeqeq": [2, "allow-null"],
"generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [2, "^(err|error)$" ],
"handle-callback-err": [2, "^(err|error)$"],
"indent": [2, 2, { "SwitchCase": 1 }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"keyword-spacing": [2, { "before": true, "after": true }],
Expand All @@ -36,7 +37,6 @@
"no-caller": 2,
"no-class-assign": 2,
"no-cond-assign": 2,
"no-console": 0,
"no-const-assign": 2,
"no-control-regex": 2,
"no-debugger": 2,
Expand Down Expand Up @@ -104,13 +104,13 @@
"one-var": [2, { "initialized": "never" }],
"operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }],
"padded-blocks": [0, "never"],
"prefer-const": 2,
"prefer-const": [2, { "destructuring": "all", "ignoreReadBeforeAssign": false }],
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"semi": [2, "always"],
"semi-spacing": [2, { "before": false, "after": true }],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-before-function-paren": [2, { "anonymous": "never", "named": "never", "asyncArrow": "always" }],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Clone repository
uses: actions/checkout@v2

- name: Set up Node.js
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
Expand Down
15 changes: 15 additions & 0 deletions examples/extglob-negated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

const picomatch = require('..');

const fixtures = [
['/file.d.ts', false],
['/file.ts', true],
['/file.d.something.ts', true],
['/file.dhello.ts', true]
];

const pattern = '/!(*.d).ts';
const isMatch = picomatch(pattern);

console.log(fixtures.map(f => [isMatch(f[0]), f[1]]));
43 changes: 43 additions & 0 deletions examples/regex-quantifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

const pico = require('..');

/**
* See: https://github.com/gulpjs/glob-parent/issues/39#issuecomment-794075641
*/

const files = [
'data/100-123a_files/0/',
'data/100-123a_files/1/',
'data/100-123a_files/2/',
'data/100-123a_files/3/',
'data/100-123b_files/0/',
'data/100-123b_files/1/',
'data/100-123b_files/2/',
'data/100-123b_files/3/',
'data/100-123a_files/4/',
'data/100-123ax_files/0/',
'data/100-123A_files/0/',
'data/100-123A_files/1/',
'data/100-123A_files/2/',
'data/100-123A_files/3/',
'data/100-123B_files/0/',
'data/100-123B_files/1/',
'data/100-123B_files/2/',
'data/100-123B_files/3/',
'data/100-123A_files/4/',
'data/100-123AX_files/0/'
];

// ? is a wildcard for matching one character
// by escaping \\{0,3}, and then using `{ unescape: true }, we tell
// picomatch to treat those characters as a regex quantifier, versus
// a brace pattern.

const isMatch = pico('data/100-123?\\{0,3}_files/{0..3}/', { unescape: true });
console.log(files.filter(name => isMatch(name)));

// Alternatively, we can use a regex character class to be more specific
// In the following example, we'll only match uppercase alpha characters
const isMatch2 = pico('data/100-123[A-Z]*_files/{0..3}/', { unescape: true });
console.log(files.filter(name => isMatch2(name)));
18 changes: 12 additions & 6 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const parse = (input, options) => {
START_ANCHOR
} = PLATFORM_CHARS;

const globstar = (opts) => {
const globstar = opts => {
return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
};

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

const eos = () => state.index === len - 1;
const peek = state.peek = (n = 1) => input[state.index + n];
const advance = state.advance = () => input[++state.index];
const advance = state.advance = () => input[++state.index] || '';
const remaining = () => input.slice(state.index + 1);
const consume = (value = '', num = 0) => {
state.consumed += value;
state.index += num;
};

const append = token => {
state.output += token.output != null ? token.output : token.value;
consume(token.value);
Expand Down Expand Up @@ -203,7 +204,7 @@ const parse = (input, options) => {
}
}

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

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

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

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

if (token.inner.includes('*') && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) {
output = token.close = `)${rest})${extglobStar})`;
}

if (token.prev.type === 'bos') {
state.negatedExtglob = true;
}
Expand Down Expand Up @@ -356,9 +362,9 @@ const parse = (input, options) => {
}

if (opts.unescape === true) {
value = advance() || '';
value = advance();
} else {
value += advance() || '';
value += advance();
}

if (state.brackets === 0) {
Expand Down Expand Up @@ -1022,7 +1028,7 @@ parse.fastpaths = (input, options) => {
star = `(${star})`;
}

const globstar = (opts) => {
const globstar = opts => {
if (opts.noglobstar === true) return star;
return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
};
Expand Down
65 changes: 34 additions & 31 deletions lib/picomatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,68 +231,71 @@ picomatch.parse = (pattern, options) => {
picomatch.scan = (input, options) => scan(input, options);

/**
* Create a regular expression from a parsed glob pattern.
*
* ```js
* const picomatch = require('picomatch');
* const state = picomatch.parse('*.js');
* // picomatch.compileRe(state[, options]);
* Compile a regular expression from the `state` object returned by the
* [parse()](#parse) method.
*
* console.log(picomatch.compileRe(state));
* //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
* ```
* @param {String} `state` The object returned from the `.parse` method.
* @param {Object} `state`
* @param {Object} `options`
* @return {RegExp} Returns a regex created from the given pattern.
* @param {Boolean} `returnOutput` Intended for implementors, this argument allows you to return the raw output from the parser.
* @param {Boolean} `returnState` Adds the state to a `state` property on the returned regex. Useful for implementors and debugging.
* @return {RegExp}
* @api public
*/

picomatch.compileRe = (parsed, options, returnOutput = false, returnState = false) => {
picomatch.compileRe = (state, options, returnOutput = false, returnState = false) => {
if (returnOutput === true) {
return parsed.output;
return state.output;
}

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

let source = `${prepend}(?:${parsed.output})${append}`;
if (parsed && parsed.negated === true) {
let source = `${prepend}(?:${state.output})${append}`;
if (state && state.negated === true) {
source = `^(?!${source}).*$`;
}

const regex = picomatch.toRegex(source, options);
if (returnState === true) {
regex.state = parsed;
regex.state = state;
}

return regex;
};

picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => {
/**
* Create a regular expression from a parsed glob pattern.
*
* ```js
* const picomatch = require('picomatch');
* const state = picomatch.parse('*.js');
* // picomatch.compileRe(state[, options]);
*
* console.log(picomatch.compileRe(state));
* //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
* ```
* @param {String} `state` The object returned from the `.parse` method.
* @param {Object} `options`
* @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.
* @param {Boolean} `returnState` Implementors may use this argument to return the state from the parsed glob with the returned regular expression.
* @return {RegExp} Returns a regex created from the given pattern.
* @api public
*/

picomatch.makeRe = (input, options = {}, returnOutput = false, returnState = false) => {
if (!input || typeof input !== 'string') {
throw new TypeError('Expected a non-empty string');
}

const opts = options || {};
let parsed = { negated: false, fastpaths: true };
let prefix = '';
let output;

if (input.startsWith('./')) {
input = input.slice(2);
prefix = parsed.prefix = './';
}

if (opts.fastpaths !== false && (input[0] === '.' || input[0] === '*')) {
output = parse.fastpaths(input, options);
if (options.fastpaths !== false && (input[0] === '.' || input[0] === '*')) {
parsed.output = parse.fastpaths(input, options);
}

if (output === undefined) {
if (!parsed.output) {
parsed = parse(input, options);
parsed.prefix = prefix + (parsed.prefix || '');
} else {
parsed.output = output;
}

return picomatch.compileRe(parsed, options, returnOutput, returnState);
Expand Down
6 changes: 3 additions & 3 deletions test/api.picomatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ const picomatch = require('..');
const { isMatch } = picomatch;

const assertTokens = (actual, expected) => {
const keyValuePairs = actual.map((token) => [token.type, token.value]);

const keyValuePairs = actual.map(token => [token.type, token.value]);
assert.deepStrictEqual(keyValuePairs, expected);
};

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

it('issue #24', () => {
it('issue #24 - should match zero or more directories', () => {
assert(!isMatch('a/b/c/d/', 'a/b/**/f'));
assert(isMatch('a', 'a/**'));
assert(isMatch('a', '**'));
Expand Down Expand Up @@ -249,6 +248,7 @@ describe('picomatch', () => {
assert(!isMatch('deep/foo/bar/baz', '**/bar/*/'));
assert(!isMatch('deep/foo/bar/baz/', '**/bar/*', { strictSlashes: true }));
assert(isMatch('deep/foo/bar/baz/', '**/bar/*'));
assert(isMatch('deep/foo/bar/baz', '**/bar/*'));
assert(isMatch('foo', 'foo/**'));
assert(isMatch('deep/foo/bar/baz/', '**/bar/*{,/}'));
assert(isMatch('a/b/j/c/z/x.md', 'a/**/j/**/z/*.md'));
Expand Down
1 change: 1 addition & 0 deletions test/extglobs-temp.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { isMatch } = require('..');

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

describe('extglobs', () => {
Expand Down
15 changes: 14 additions & 1 deletion test/extglobs.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ describe('extglobs', () => {
assert(isMatch('abc', 'a!(.)c'));
});

// See https://github.com/micromatch/picomatch/issues/83
it('should support stars in negation extglobs', () => {
assert(!isMatch('/file.d.ts', '/!(*.d).ts'));
assert(isMatch('/file.ts', '/!(*.d).ts'));
assert(isMatch('/file.d.something.ts', '/!(*.d).ts'));
assert(isMatch('/file.dhello.ts', '/!(*.d).ts'));

assert(!isMatch('/file.d.ts', '**/!(*.d).ts'));
assert(isMatch('/file.ts', '**/!(*.d).ts'));
assert(isMatch('/file.d.something.ts', '**/!(*.d).ts'));
assert(isMatch('/file.dhello.ts', '**/!(*.d).ts'));
});

it('should support negation extglobs in patterns with slashes', () => {
assert(!isMatch('foo/abc', 'foo/!(abc)'));
assert(isMatch('foo/bar', 'foo/!(abc)'));
Expand Down Expand Up @@ -271,7 +284,7 @@ describe('extglobs', () => {
assert(isMatch('ab.md', '?(a|ab|b).md'));
assert(isMatch('b.md', '?(a|ab|b).md'));

// see https://github.com/micromatch/micromatch/issues/186
// See https://github.com/micromatch/micromatch/issues/186
assert(isMatch('ab', '+(a)?(b)'));
assert(isMatch('aab', '+(a)?(b)'));
assert(isMatch('aa', '+(a)?(b)'));
Expand Down

0 comments on commit 56083ef

Please sign in to comment.