From 2e565263461b2136e4e960185236b1f83c93e407 Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Sun, 23 Feb 2020 00:49:39 -0500 Subject: [PATCH 1/8] fixes https://github.com/micromatch/picomatch/issues/62 as well as a few other minor optimizations and linting. --- .eslintrc.json | 14 +++---- .verb.md | 1 + bench/glob-parent.js | 10 ++--- bench/index.js | 33 ++++++++------- bench/load-time.js | 13 +++++- examples/scan.js | 59 ++++++++++++++++++++++++++ lib/parse.js | 37 ++++++++++++----- lib/picomatch.js | 48 +++++++++++++-------- lib/scan.js | 85 ++++++++++++++++++++++---------------- test/api.picomatch.js | 6 +-- test/api.scan.js | 14 ++++++- test/dotfiles.js | 4 +- test/extglobs.js | 2 +- test/malicious.js | 4 +- test/parens.js | 12 ++++++ test/special-characters.js | 2 +- test/stars.js | 2 + 17 files changed, 244 insertions(+), 102 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 72926e7..4c1d3fb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,19 +1,18 @@ { - "extends": [ - "eslint:recommended" - ], + "extends": ["eslint:recommended"], "env": { "es6": true, "node": true }, - "parserOptions":{ + "parserOptions": { "ecmaVersion": 9 }, "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 }], @@ -26,7 +25,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 }], @@ -36,7 +35,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, @@ -104,13 +102,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 }], diff --git a/.verb.md b/.verb.md index a8cabdd..8fbb4b7 100644 --- a/.verb.md +++ b/.verb.md @@ -125,6 +125,7 @@ The following options may be used with the main `picomatch()` function or any of | `posixSlashes` | `boolean` | `undefined` | Convert all slashes in file paths to forward slashes. This does not convert slashes in the glob pattern itself | | `prepend` | `boolean` | `undefined` | String to prepend to the generated regex used for matching. | | `regex` | `boolean` | `false` | Use regular expression rules for `+` (instead of matching literal `+`), and for stars that follow closing parentheses or brackets (as in `)*` and `]*`). | +| `strictBraces` | `boolean` | `undefined` | Treat brace patterns with no sets or ranges as literals. For example, `{abc}` would be a literal. | | `strictBrackets` | `boolean` | `undefined` | Throw an error if brackets, braces, or parens are imbalanced. | | `strictSlashes` | `boolean` | `undefined` | When true, picomatch won't match trailing slashes with single stars. | | `unescape` | `boolean` | `undefined` | Remove backslashes preceding escaped characters in the glob pattern. By default, backslashes are retained. | diff --git a/bench/glob-parent.js b/bench/glob-parent.js index 2790415..9b3e770 100644 --- a/bench/glob-parent.js +++ b/bench/glob-parent.js @@ -47,11 +47,6 @@ bench('*.js') .add(' glob-parent', () => parent('*.js')) .run(); -bench('foo/bar/baz') - .add('picomatch.scan', () => scan('foo/bar/baz')) - .add(' glob-parent', () => parent('foo/bar/baz')) - .run(); - bench('foo/*.js') .add('picomatch.scan', () => scan('foo/*.js')) .add(' glob-parent', () => parent('foo/*.js')) @@ -62,6 +57,11 @@ bench('foo/{a,b}/*.js') .add(' glob-parent', () => parent('foo/{a,b}/*.js')) .run(); +bench('foo/bar/baz') + .add('picomatch.scan', () => scan('foo/bar/baz')) + .add(' glob-parent', () => parent('foo/bar/baz')) + .run(); + bench('*.js { parts: true, tokens: true }') .add('picomatch.scan', () => scan('*.js', { parts: true, tokens: true })) .add(' glob-parent', () => parent('*.js')) diff --git a/bench/index.js b/bench/index.js index cf12e43..55778ba 100644 --- a/bench/index.js +++ b/bench/index.js @@ -44,31 +44,36 @@ const bench = (name, options) => { }; bench(`${red('.makeRe')} star`) - .add('picomatch', () => pm.makeRe('*')) + .add('picomatch', () => pm.makeRe('*', { fastpaths: false })) .add('minimatch', () => mm.makeRe('*')) .run(); -bench(`${red('.makeRe')} star; dot=true`) - .add('picomatch', () => pm.makeRe('*', { dot: true })) +bench(`${red('.makeRe')} leading star`) + .add('picomatch', () => pm.makeRe('*.txt', { fastpaths: false })) + .add('minimatch', () => mm.makeRe('*.txt')) + .run(); + +bench(`${red('.makeRe')} path with star`) + .add('picomatch', () => pm.makeRe('foo/*.js', { fastpaths: false })) + .add('minimatch', () => mm.makeRe('foo/*.js')) + .run(); + +bench(`${red('.makeRe')} star w/ { dot: true }`) + .add('picomatch', () => pm.makeRe('*', { dot: true , fastpaths: false })) .add('minimatch', () => mm.makeRe('*', { dot: true })) .run(); bench(`${red('.makeRe')} globstar`) - .add('picomatch', () => pm.makeRe('**')) + .add('picomatch', () => pm.makeRe('**', { fastpaths: false })) .add('minimatch', () => mm.makeRe('**')) .run(); -bench(`${red('.makeRe')} globstars`) - .add('picomatch', () => pm.makeRe('**/**/**')) +bench(`${red('.makeRe')} multiple globstars`) + .add('picomatch', () => pm.makeRe('**/**/**', { fastpaths: false })) .add('minimatch', () => mm.makeRe('**/**/**')) .run(); -bench(`${red('.makeRe')} with leading star`) - .add('picomatch', () => pm.makeRe('*.txt')) - .add('minimatch', () => mm.makeRe('*.txt')) - .run(); - -bench(`${red('.makeRe')} - basic braces`) - .add('picomatch', () => pm.makeRe('{a,b,c}*.txt')) - .add('minimatch', () => mm.makeRe('{a,b,c}*.txt')) +bench(`${red('.makeRe')} basic braces`) + .add('picomatch', () => pm.makeRe('foo/{a,b,c}*.txt', { fastpaths: false })) + .add('minimatch', () => mm.makeRe('foo/{a,b,c}*.txt')) .run(); diff --git a/bench/load-time.js b/bench/load-time.js index b5e5c3a..b66eda4 100644 --- a/bench/load-time.js +++ b/bench/load-time.js @@ -1,10 +1,19 @@ 'use strict'; +const libs = { + get pm() { + return require('..'); + }, + get mm() { + return require('minimatch'); + } +}; + console.log('# Load time'); console.time('picomatch'); -exports.pm = require('..'); +libs.pm; console.timeEnd('picomatch'); console.time('minimatch'); -exports.mm = require('minimatch'); +libs.mm; console.timeEnd('minimatch'); console.log(); diff --git a/examples/scan.js b/examples/scan.js index 962422d..98e53ea 100644 --- a/examples/scan.js +++ b/examples/scan.js @@ -15,3 +15,62 @@ console.log(pm.scan('foo/**/*.js')); console.log(pm.scan('foo/bar/*.js')); console.log(pm.scan('foo/*.js')); console.log(pm.scan('/foo')); + + +const braces = require('braces'); + +const scan = (pattern, options) => { + const cache = new Map(); + const matchers = {}; + const patterns = braces.expand(pattern, options); + const result = patterns.map(p => pm.scan(p, options)); + + for (let i = 0; i < result.length; i++) { + const state = result[i]; + if (state.maxDepth === Infinity) continue; + const matcher = matchers[state.base] || (matchers[state.base] = {}); + let foundGlob = false; + + for (const token of state.tokens) { + if (token.isGlob === true) { + foundGlob = true; + } + + if (foundGlob === false) { + continue; + } + + if (token.isGlob === false) { + token.matcher = name => token.value === name; + } else { + token.matcher = function glob() {} + } + + + + } + console.log(state); + + } + + return result; +}; + +scan('{one/two,foo}/*/abc/{bar,**/*}.js', { parts: true, tokens: true }); + +// scan('./foo/**/*/*.js', { parts: true, tokens: true }); +// scan('**/bar.js', { parts: true, tokens: true }); +// scan('foo/**/bar.js', { parts: true, tokens: true }); +// scan('foo/**/{bar,*/*}.js', { parts: true, tokens: true }); +// scan('foo/**/{bar,*/*}/*.js', { parts: true, tokens: true }); +// const { tokens } = scan('foo/*/{bar,*/*}/*.js', { parts: true, tokens: true }); +// for (const token of tokens) { +// console.log(token); +// } + +// console.log(scan('./foo/**/*/*.js', { parts: true, tokens: true })); +// console.log(scan('foo/**/bar.js', { parts: true, tokens: true })); +// console.log(scan('foo/**/{bar,*/*}.js', { parts: true, tokens: true })); +// console.log(scan('foo/**/{bar,*/*}/*.js', { parts: true, tokens: true })); +// console.log(scan('!./foo/*.js')); +// console.log(scan('!./foo/*.js', { parts: true, tokens: true })); diff --git a/lib/parse.js b/lib/parse.js index 34bdc2f..64a9dbc 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -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}).)*?)`; }; @@ -260,7 +260,7 @@ const parse = (input, options) => { * Fast paths */ - if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) { + if (opts.fastpaths !== false && !/(^[*!]|(?!<\\)[/()[\]{}"])/.test(input)) { let backslashes = false; let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { @@ -444,7 +444,8 @@ const parse = (input, options) => { if (value === '(') { increment('parens'); - push({ type: 'paren', value }); + const output = /(? { continue; } - push({ type: 'paren', value, output: state.parens ? ')' : '\\)' }); + const rest = remaining(); + const special = rest[0] === '?' || rest[0] === '+'; + const paren = `)${special ? rest[0] : ''}\\)?`; + + if (special) { + consume(rest[0], 1); + } + + const output = state.parens ? paren : '\\)'; + push({ type: 'paren', value, output }); decrement('parens'); continue; } @@ -579,7 +589,7 @@ const parse = (input, options) => { state.backtrack = true; } - if (brace.comma !== true && brace.dots !== true) { + if (opts.strictBraces && brace.comma !== true && brace.dots !== true) { const out = state.output.slice(0, brace.outputIndex); const toks = state.tokens.slice(brace.tokensIndex); brace.value = brace.output = '\\{'; @@ -604,6 +614,15 @@ const parse = (input, options) => { if (extglobs.length > 0) { extglobs[extglobs.length - 1].conditions++; } + + // See: https://github.com/micromatch/picomatch/issues/59 + if (opts.strictBraces !== true && braces.length > 0) { + const brace = braces[braces.length - 1]; + if (stack[stack.length - 1] === 'braces') { + brace.comma = true; + } + } + push({ type: 'text', value }); continue; } @@ -829,9 +848,7 @@ const parse = (input, options) => { // strip consecutive `/**/` while (rest.slice(0, 3) === '/**') { const after = input[state.index + 4]; - if (after && after !== '/') { - break; - } + if (after && after !== '/') break; rest = rest.slice(3); consume('/**', 3); } @@ -925,11 +942,9 @@ const parse = (input, options) => { if (prev.type === 'dot') { state.output += NO_DOT_SLASH; prev.output += NO_DOT_SLASH; - } else if (opts.dot === true) { state.output += NO_DOTS_SLASH; prev.output += NO_DOTS_SLASH; - } else { state.output += nodot; prev.output += nodot; @@ -1022,7 +1037,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}).)*?)`; }; diff --git a/lib/picomatch.js b/lib/picomatch.js index df7438a..4c68f24 100644 --- a/lib/picomatch.js +++ b/lib/picomatch.js @@ -59,7 +59,7 @@ const picomatch = (glob, options, returnState = false) => { let isIgnored = () => false; if (opts.ignore) { - const ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; + const ignoreOpts = { ...options, ...opts.ignoreOptions, ignore: null, onMatch: null, onResult: null }; isIgnored = picomatch(opts.ignore, ignoreOpts, returnState); } @@ -231,44 +231,58 @@ 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; }; +/** + * 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'); diff --git a/lib/scan.js b/lib/scan.js index f291890..3fcaf7f 100644 --- a/lib/scan.js +++ b/lib/scan.js @@ -188,16 +188,38 @@ const scan = (input, options) => { if (code === CHAR_RIGHT_PARENTHESES) { isGlob = token.isGlob = true; - finished = true; break; } } continue; } + break; } } + if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) { + while (eos() !== true && (code = advance())) { + if (code === CHAR_BACKWARD_SLASH) { + backslashes = token.backslashes = true; + code = advance(); + continue; + } + + if (code === CHAR_RIGHT_PARENTHESES) { + isGlob = token.isGlob = true; + break; + } + } + + if (scanToEnd === true) { + continue; + } + + finished = isGlob; + break; + } + if (code === CHAR_ASTERISK) { if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true; isGlob = token.isGlob = true; @@ -246,26 +268,6 @@ const scan = (input, options) => { continue; } - if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) { - while (eos() !== true && (code = advance())) { - if (code === CHAR_BACKWARD_SLASH) { - backslashes = token.backslashes = true; - code = advance(); - continue; - } - - if (code === CHAR_RIGHT_PARENTHESES) { - isGlob = token.isGlob = true; - finished = true; - - if (scanToEnd === true) { - continue; - } - break; - } - } - } - if (isGlob === true) { finished = true; @@ -332,33 +334,43 @@ const scan = (input, options) => { if (opts.tokens === true) { state.maxDepth = 0; - if (!isPathSeparator(code)) { - tokens.push(token); - } + tokens.push(token); state.tokens = tokens; + parts.push(token.value); } if (opts.parts === true || opts.tokens === true) { let prevIndex; - for (let idx = 0; idx < slashes.length; idx++) { + for (let i = 0; i < slashes.length; i++) { const n = prevIndex ? prevIndex + 1 : start; - const i = slashes[idx]; - const value = input.slice(n, i); + const slashIndex = slashes[i]; + + if (n === 0 && slashIndex === 0 && slashes.length === 1) { + parts.push('', input.slice(1)); + break; + } + + let value = input.slice(n, slashIndex); + if (value[0] === '/') value = value.slice(1); + if (opts.tokens) { - if (idx === 0 && start !== 0) { - tokens[idx].isPrefix = true; - tokens[idx].value = prefix; + if (i === 0 && start !== 0) { + tokens[i].isPrefix = true; + tokens[i].value = prefix; } else { - tokens[idx].value = value; + tokens[i].value = value; } - depth(tokens[idx]); - state.maxDepth += tokens[idx].depth; + + depth(tokens[i]); + state.maxDepth += tokens[i].depth; } - if (idx !== 0 || value !== '') { + + if (i !== 0 || prefix !== './') { parts.push(value); } - prevIndex = i; + + prevIndex = slashIndex; } if (prevIndex && prevIndex + 1 < input.length) { @@ -370,6 +382,9 @@ const scan = (input, options) => { depth(tokens[tokens.length - 1]); state.maxDepth += tokens[tokens.length - 1].depth; } + + } else if ((str && parts.length === 0) || input[input.length - 1] === '/') { + parts.push(''); } state.slashes = slashes; diff --git a/test/api.picomatch.js b/test/api.picomatch.js index 0d17dba..fc48728 100644 --- a/test/api.picomatch.js +++ b/test/api.picomatch.js @@ -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); }; @@ -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', '**')); @@ -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')); diff --git a/test/api.scan.js b/test/api.scan.js index d23731b..1296990 100644 --- a/test/api.scan.js +++ b/test/api.scan.js @@ -15,8 +15,18 @@ const both = (...args) => { * and both libraries use path.dirname. Picomatch does not. */ -describe('picomatch', () => { - describe('.scan', () => { +describe('.scan', () => { + describe('when', () => { + it('should ', () => { + assert.deepStrictEqual(scan('/a', { parts: true }).parts, ['', 'a']); + assert.deepStrictEqual(scan('/a/b', { parts: true }).parts, ['', 'a', 'b']); + assert.deepStrictEqual(scan('/a/b/', { parts: true }).parts, ['', 'a', 'b', '']); + assert.deepStrictEqual(scan('(!(b/a))', { parts: true }).parts, ['']); + assert.deepStrictEqual(scan('(a|b)/c', { parts: true }).parts, ['(a|b)', 'c']); + }); + }); + + describe('base', () => { it('should get the "base" and "glob" from a pattern', () => { assert.deepStrictEqual(both('foo/bar'), ['foo/bar', '']); assert.deepStrictEqual(both('foo/@bar'), ['foo/@bar', '']); diff --git a/test/dotfiles.js b/test/dotfiles.js index e72e0f6..f90abb8 100644 --- a/test/dotfiles.js +++ b/test/dotfiles.js @@ -224,7 +224,7 @@ describe('dotfiles', () => { assert(isMatch('abc/../abc', '*/../*')); }); - it('should not match double dots when not defined in pattern', async() => { + it('should not match double dots when not defined in pattern', async () => { assert(!isMatch('../abc', '**/*')); assert(!isMatch('../abc', '**/**/**')); assert(!isMatch('../abc', '**/**/abc')); @@ -291,7 +291,7 @@ describe('dotfiles', () => { assert(!isMatch('abc/abc/..', 'abc/*/**/*', { strictSlashes: true })); }); - it('should not match single exclusive dots when not defined in pattern', async() => { + it('should not match single exclusive dots when not defined in pattern', async () => { assert(!isMatch('.', '**')); assert(!isMatch('abc/./abc', '**')); assert(!isMatch('abc/abc/.', '**')); diff --git a/test/extglobs.js b/test/extglobs.js index 29bcb06..979bb57 100644 --- a/test/extglobs.js +++ b/test/extglobs.js @@ -25,7 +25,7 @@ describe('extglobs', () => { }); it('should not convert capture groups to extglobs', () => { - assert.strictEqual(makeRe('c!(?:foo)?z').source, '^(?:c!(?:foo)?z)$'); + assert.strictEqual(makeRe('c!(?:foo)?z').source, '^(?:c!\\(?(?:foo)?\\)?z)$'); assert(!isMatch('c/z', 'c!(?:foo)?z')); assert(isMatch('c!fooz', 'c!(?:foo)?z')); assert(isMatch('c!z', 'c!(?:foo)?z')); diff --git a/test/malicious.js b/test/malicious.js index 33cf6b8..20a2f86 100644 --- a/test/malicious.js +++ b/test/malicious.js @@ -13,7 +13,9 @@ describe('handling of potential regex exploits', () => { if (process.platform !== 'win32') { assert(isMatch('\\A', `${repeat(65500)}A`), 'within the limits, and valid match'); } - assert(isMatch('A', `!${repeat(65500)}A`), 'within the limits, and valid match'); + assert(isMatch('A', `${repeat(65499)}A`), 'within the limits, and invalid match'); + assert(!isMatch('A', `!${repeat(65499)}A`), 'within the limits, and invalid match'); + assert(!isMatch('A', `${repeat(65500)}A`), 'within the limits, and valid match'); assert(isMatch('A', `!(${repeat(65500)}A)`), 'within the limits, and valid match'); assert(!isMatch('A', `[!(${repeat(65500)}A`), 'within the limits, but invalid regex'); }); diff --git a/test/parens.js b/test/parens.js index d73bf63..9398e43 100644 --- a/test/parens.js +++ b/test/parens.js @@ -15,6 +15,18 @@ describe('parens (non-extglobs)', () => { assert(isMatch('aaabbb', '(a|b)*')); }); + it('should match literal parens', () => { + assert(isMatch('(a)', '(a)*')); + assert(isMatch('(az)', '(a)*')); + assert(!isMatch('(zz)', '(a)*')); + assert(isMatch('(ab)', '(a|b)*')); + assert(isMatch('(abc)', '(a|b)*')); + assert(isMatch('(aa)', '(a)*')); + assert(isMatch('(aaab)', '(a|b)*')); + assert(isMatch('(aaabbb)', '(a|b)*')); + assert(!isMatch('(zaabbb)', '(a|b)*')); + }); + it('should not match slashes with single stars', () => { assert(!isMatch('a/b', '(a)*')); assert(!isMatch('a/b', '(a|b)*')); diff --git a/test/special-characters.js b/test/special-characters.js index 7ff8920..8ff260a 100644 --- a/test/special-characters.js +++ b/test/special-characters.js @@ -232,7 +232,7 @@ describe('special characters', () => { assert(isMatch('foo(bar)baz', 'foo*baz')); }); - it('should match literal parens with brackets', async() => { + it('should match literal parens with brackets', async () => { assert(isMatch('foo(bar)baz', 'foo[bar()]+baz')); }); diff --git a/test/stars.js b/test/stars.js index 910b9f2..12bbd0c 100644 --- a/test/stars.js +++ b/test/stars.js @@ -360,6 +360,8 @@ describe('stars', () => { it('should optionally match trailing slashes with braces', () => { assert(isMatch('foo', '**/*')); + assert(isMatch('foo', '*{,/}')); + assert(isMatch('foo/', '*{,/}')); assert(isMatch('foo', '**/*{,/}')); assert(isMatch('foo/', '**/*{,/}')); assert(isMatch('foo/bar', '**/*{,/}')); From eaab152dea9585b99d8446500f59b170c3bcc87d Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Sun, 23 Feb 2020 01:01:52 -0500 Subject: [PATCH 2/8] don't use negative lookbehind --- lib/parse.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 64a9dbc..7e71492 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -444,7 +444,9 @@ const parse = (input, options) => { if (value === '(') { increment('parens'); - const output = /(? -1 && rest[idx - 1] !== '\\') ? '\\(?(' : '\\('; push({ type: 'paren', value, output }); continue; } From 5d0c254ef01f168a4085aad58f0afde41138e0a1 Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Sun, 23 Feb 2020 03:05:37 -0500 Subject: [PATCH 3/8] improve coverage, lint --- .eslintignore.json | 3 ++ .eslintrc.json | 1 + bench/index.js | 2 +- bench/load-time.js | 8 ++-- bench/package.json | 4 +- examples/scan.js | 12 ++---- lib/parse.js | 31 ++++++++------- lib/picomatch.js | 22 +++------- lib/utils.js | 19 ++++----- package.json | 1 + test/extglobs.js | 16 ++++++++ test/{extglobs-temp.js => extglobs2.js} | 2 +- test/options.expandRange.js | 4 ++ test/parse.js | 27 +++++++++++++ test/slashes-posix.js | 7 ++++ test/utils.js | 53 +++++++++++++++++++++++++ 16 files changed, 153 insertions(+), 59 deletions(-) create mode 100644 .eslintignore.json rename test/{extglobs-temp.js => extglobs2.js} (99%) create mode 100644 test/parse.js create mode 100644 test/utils.js diff --git a/.eslintignore.json b/.eslintignore.json new file mode 100644 index 0000000..5d31e4d --- /dev/null +++ b/.eslintignore.json @@ -0,0 +1,3 @@ +{ + "ignorePatterns": ["examples/**", "bench/**"] +} diff --git a/.eslintrc.json b/.eslintrc.json index 4c1d3fb..31658fc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,6 +35,7 @@ "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, diff --git a/bench/index.js b/bench/index.js index 55778ba..2e45255 100644 --- a/bench/index.js +++ b/bench/index.js @@ -59,7 +59,7 @@ bench(`${red('.makeRe')} path with star`) .run(); bench(`${red('.makeRe')} star w/ { dot: true }`) - .add('picomatch', () => pm.makeRe('*', { dot: true , fastpaths: false })) + .add('picomatch', () => pm.makeRe('*', { dot: true, fastpaths: false })) .add('minimatch', () => mm.makeRe('*', { dot: true })) .run(); diff --git a/bench/load-time.js b/bench/load-time.js index b66eda4..2b710f2 100644 --- a/bench/load-time.js +++ b/bench/load-time.js @@ -1,19 +1,19 @@ 'use strict'; const libs = { - get pm() { + pm() { return require('..'); }, - get mm() { + mm() { return require('minimatch'); } }; console.log('# Load time'); console.time('picomatch'); -libs.pm; +libs.pm(); console.timeEnd('picomatch'); console.time('minimatch'); -libs.mm; +libs.mm(); console.timeEnd('minimatch'); console.log(); diff --git a/bench/package.json b/bench/package.json index 317c6bc..af52fab 100644 --- a/bench/package.json +++ b/bench/package.json @@ -4,12 +4,12 @@ "private": true, "main": "index.js", "dependencies": { - "ansi-colors": "^3.0.3", + "ansi-colors": "^4.1.1", "benchmark": "^2.1.4", "minimist": "^1.2.0" }, "devDependencies": { - "glob-parent": "^3.1.0", + "glob-parent": "^5.1.0", "minimatch": "^3.0.4" }, "lintDeps": { diff --git a/examples/scan.js b/examples/scan.js index 98e53ea..55674a4 100644 --- a/examples/scan.js +++ b/examples/scan.js @@ -16,19 +16,18 @@ console.log(pm.scan('foo/bar/*.js')); console.log(pm.scan('foo/*.js')); console.log(pm.scan('/foo')); - const braces = require('braces'); const scan = (pattern, options) => { - const cache = new Map(); - const matchers = {}; + // const matchers = {}; const patterns = braces.expand(pattern, options); const result = patterns.map(p => pm.scan(p, options)); for (let i = 0; i < result.length; i++) { const state = result[i]; if (state.maxDepth === Infinity) continue; - const matcher = matchers[state.base] || (matchers[state.base] = {}); + + // const matcher = matchers[state.base] || (matchers[state.base] = {}); let foundGlob = false; for (const token of state.tokens) { @@ -43,14 +42,11 @@ const scan = (pattern, options) => { if (token.isGlob === false) { token.matcher = name => token.value === name; } else { - token.matcher = function glob() {} + token.matcher = function glob() {}; } - - } console.log(state); - } return result; diff --git a/lib/parse.js b/lib/parse.js index 7e71492..262eef6 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -127,9 +127,6 @@ const parse = (input, options) => { tokens }; - input = utils.removePrefix(input, state); - len = input.length; - const extglobs = []; const braces = []; const stack = []; @@ -144,7 +141,7 @@ const parse = (input, options) => { const peek = state.peek = (n = 1) => input[state.index + n]; const advance = state.advance = () => input[++state.index]; const remaining = () => input.slice(state.index + 1); - const consume = (value = '', num = 0) => { + const consume = (value, num = 0) => { state.consumed += value; state.index += num; }; @@ -297,7 +294,7 @@ const parse = (input, options) => { output = output.replace(/\\/g, ''); } else { output = output.replace(/\\+/g, m => { - return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : ''); + return m.length % 2 === 0 ? '\\\\' : '\\'; }); } } @@ -356,9 +353,9 @@ const parse = (input, options) => { } if (opts.unescape === true) { - value = advance() || ''; + value = advance(); } else { - value += advance() || ''; + value += advance(); } if (state.brackets === 0) { @@ -574,16 +571,21 @@ const parse = (input, options) => { let output = ')'; if (brace.dots === true) { - const arr = tokens.slice(); - const range = []; + const idx = tokens.lastIndexOf(brace); + const arr = tokens.slice(idx); + const range = ['', '']; + let dots = false; for (let i = arr.length - 1; i >= 0; i--) { tokens.pop(); - if (arr[i].type === 'brace') { - break; + + if (arr[i].type === 'dots') { + dots = true; + continue; } - if (arr[i].type !== 'dots') { - range.unshift(arr[i].value); + + if (arr[i] !== brace) { + range[dots ? 0 : 1] += arr[i].value; } } @@ -1082,8 +1084,7 @@ parse.fastpaths = (input, options) => { } }; - const output = utils.removePrefix(input, state); - let source = create(output); + let source = create(input); if (source && opts.strictSlashes !== true) { source += `${SLASH_LITERAL}?`; diff --git a/lib/picomatch.js b/lib/picomatch.js index 4c68f24..ebaa235 100644 --- a/lib/picomatch.js +++ b/lib/picomatch.js @@ -283,30 +283,19 @@ picomatch.compileRe = (state, options, returnOutput = false, returnState = false * @api public */ -picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => { +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); @@ -340,11 +329,12 @@ picomatch.toRegex = (source, options) => { }; /** - * Picomatch constants. + * Picomatch utils and constants. * @return {Object} */ picomatch.constants = constants; +picomatch.utils = utils; /** * Expose "picomatch" diff --git a/lib/utils.js b/lib/utils.js index c3ca766..d578ee6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -9,6 +9,10 @@ const { REGEX_SPECIAL_CHARS_GLOBAL } = require('./constants'); +/** + * isObject, isWindows, and toPosixSlashes are used by micromatch. + */ + exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str); @@ -21,9 +25,9 @@ exports.removeBackslashes = str => { }); }; -exports.supportsLookbehinds = () => { - const segs = process.version.slice(1).split('.').map(Number); - if (segs.length === 3 && segs[0] >= 9 || (segs[0] === 8 && segs[1] >= 10)) { +exports.supportsLookbehinds = (version = process.version) => { + const segs = version.replace(/^v/, '').split('.').map(Number); + if (segs.length === 3 && (segs[0] >= 9 || (segs[0] === 8 && segs[1] >= 10))) { return true; } return false; @@ -43,15 +47,6 @@ exports.escapeLast = (input, char, lastIdx) => { return `${input.slice(0, idx)}\\${input.slice(idx)}`; }; -exports.removePrefix = (input, state = {}) => { - let output = input; - if (output.startsWith('./')) { - output = output.slice(2); - state.prefix = './'; - } - return output; -}; - exports.wrapOutput = (input, state = {}, options = {}) => { const prepend = options.contains ? '' : '^'; const append = options.contains ? '' : '$'; diff --git a/package.json b/package.json index 609b4d3..91e9147 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "test:cover": "nyc npm run mocha" }, "devDependencies": { + "braces": "^3.0.2", "eslint": "^6.8.0", "fill-range": "^7.0.1", "gulp-format-md": "^2.0.0", diff --git a/test/extglobs.js b/test/extglobs.js index 979bb57..78aacdd 100644 --- a/test/extglobs.js +++ b/test/extglobs.js @@ -15,6 +15,21 @@ describe('extglobs', () => { assert.throws(() => makeRe('a)b', opts), /Missing opening: "\("/i); }); + it('should support options.capture with extglobs', () => { + assert(isMatch('cbz', 'c*(b)z', { capture: true })); + assert(!isMatch('cbz', 'c*(a)z', { capture: true })); + assert(!isMatch('cbz', 'c*(*x)', { capture: true })); + }); + + it('should support globstars in extglobs', () => { + assert(!isMatch('cbz', 'c!(**)z')); + assert(!isMatch('c/bz', 'c!(/**)z')); + assert(isMatch('cbz', 'c!(/**)z')); + assert(!isMatch('cbz', 'c!(a|**)z')); + assert(!isMatch('cbz', 'c!(**|a)z')); + assert(!isMatch('cbz', 'c!(**|b)z')); + }); + it('should escape special characters immediately following opening parens', () => { assert(isMatch('cbz', 'c!(.)z')); assert(!isMatch('cbz', 'c!(*)z')); @@ -22,6 +37,7 @@ describe('extglobs', () => { assert(isMatch('cbz', 'c!(+)z')); assert(isMatch('cbz', 'c!(?)z')); assert(isMatch('cbz', 'c!(@)z')); + assert(!isMatch('cbz', 'c!(b)z')); }); it('should not convert capture groups to extglobs', () => { diff --git a/test/extglobs-temp.js b/test/extglobs2.js similarity index 99% rename from test/extglobs-temp.js rename to test/extglobs2.js index f5ea7ba..3237b34 100644 --- a/test/extglobs-temp.js +++ b/test/extglobs2.js @@ -8,7 +8,7 @@ const { isMatch } = require('..'); * Some of tests were converted from bash 4.3, 4.4, and minimatch unit tests. */ -describe('extglobs', () => { +describe('extglobs2', () => { beforeEach(() => support.windowsPathSep()); afterEach(() => support.resetPathSep()); diff --git a/test/options.expandRange.js b/test/options.expandRange.js index 0eb05fc..71b9e78 100644 --- a/test/options.expandRange.js +++ b/test/options.expandRange.js @@ -5,6 +5,10 @@ const fill = require('fill-range'); const { isMatch } = require('..'); describe('options.expandRange', () => { + it('should test against a range', () => { + assert(!isMatch('a/z', 'a/{[]](]..ca0}')); + }); + it('should support a custom function for expanding ranges in brace patterns', () => { assert(isMatch('a/c', 'a/{a..c}', { expandRange: (a, b) => `([${a}-${b}])` })); assert(!isMatch('a/z', 'a/{a..c}', { expandRange: (a, b) => `([${a}-${b}])` })); diff --git a/test/parse.js b/test/parse.js new file mode 100644 index 0000000..cfb9a90 --- /dev/null +++ b/test/parse.js @@ -0,0 +1,27 @@ +'use strict'; + +const assert = require('assert'); +const { parse } = require('..'); + +describe('parse', () => { + it('should ignore \u0000', () => { + assert.equal(parse('\u0000*').consumed, '*'); + }); + + it('should parse ./ from string', () => { + assert.equal(parse('./*', { fastpaths: false }).consumed, '*'); + }); + + it('should parse a glob', () => { + assert.equal(parse('*').consumed, '*'); + assert.equal(parse('*').output, '(?!\\.)(?=.)[^/]*?\\/?'); + }); + + it('should support capture', () => { + assert.equal(parse('*', { capture: true }).output, '(?!\\.)(?=.)([^/]*?)\\/?'); + }); + + it('should throw an error when value is not a string', () => { + assert.throws(() => parse({}), /expected a string/i); + }); +}); diff --git a/test/slashes-posix.js b/test/slashes-posix.js index 50abfd6..d0eb80c 100644 --- a/test/slashes-posix.js +++ b/test/slashes-posix.js @@ -9,6 +9,13 @@ describe('slash handling - posix', () => { after(() => support.resetPathSep()); afterEach(() => support.resetPathSep()); + it('should handle backslashes', () => { + assert(isMatch('\\\\\\a', '\\\\\\a')); + assert(!isMatch('\\\\\\a', '\\\\a')); + assert(isMatch('\\\\\\a', '\\\\a', { contains: true })); + assert(isMatch('\\', '\\', { fastpaths: false })); + }); + it('should match a literal string', () => { assert(!isMatch('a/a', '(a/b)')); assert(isMatch('a/b', '(a/b)')); diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..cb4af10 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,53 @@ +'use strict'; + +const path = require('path'); +const assert = require('assert').strict; +const utils = require('../lib/utils'); + +/** + * isObject, isWindows, and toPosixSlashes are used by micromatch. + */ + +describe('utils', () => { + it('.isRegexChar', () => { + assert.equal(utils.isRegexChar('*'), true); + assert.equal(utils.isRegexChar('?'), true); + assert.equal(utils.isRegexChar('a'), false); + }); + + it('.isWindows', () => { + assert.equal(utils.isWindows(), process.platform === 'win32'); + // this hack is only used to allow us to do better unit tests on windows paths + const sep = path.sep; + path.sep = '\\'; + assert.equal(utils.isWindows(), true); + path.sep = sep; + }); + + it('.isRegexChar', () => { + assert.equal(utils.isRegexChar('*'), true); + assert.equal(utils.isRegexChar('?'), true); + assert.equal(utils.isRegexChar('a'), false); + }); + + it('.isObject', () => { + assert.equal(utils.isObject('*'), false); + assert.equal(utils.isObject({}), true); + }); + + it('.wrapOutput', () => { + assert.equal(utils.wrapOutput('foo'), '^(?:foo)$'); + assert.equal(utils.wrapOutput('foo', {}, { contains: true }), '(?:foo)'); + assert.equal(utils.wrapOutput('foo', { negated: true }), '(?:^(?!^(?:foo)$).*$)'); + }); + + it('.toPosixSlashes', () => { + assert.equal(utils.toPosixSlashes('a\\b\\c'), 'a/b/c'); + }); + + it('.supportsLookbehinds', () => { + assert.equal(utils.supportsLookbehinds('8.9.0'), false); + assert.equal(utils.supportsLookbehinds('8.10.0'), true); + assert.equal(utils.supportsLookbehinds('9.0.0'), true); + }); +}); From a49bb2072b9bce3a285dadb739d9b0bcda024d23 Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Sun, 23 Feb 2020 03:13:57 -0500 Subject: [PATCH 4/8] fix tests For now, we're going to skip `nyc` with the github workflow, since it gives the appearance that tests are failing when we have less than 100% coverage. I'd like to have 100%, but we're close, and that's good enough for now. --- package.json | 2 +- test/parse.js | 12 ++++++++++-- test/slashes-posix.js | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 91e9147..222b12e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "mocha": "mocha --reporter dot", "test": "npm run lint && npm run mocha", "test:ci": "npm run test:cover", - "test:cover": "nyc npm run mocha" + "test:cover": "npm run mocha" }, "devDependencies": { "braces": "^3.0.2", diff --git a/test/parse.js b/test/parse.js index cfb9a90..c4ed927 100644 --- a/test/parse.js +++ b/test/parse.js @@ -14,11 +14,19 @@ describe('parse', () => { it('should parse a glob', () => { assert.equal(parse('*').consumed, '*'); - assert.equal(parse('*').output, '(?!\\.)(?=.)[^/]*?\\/?'); + if (process.platform === 'win32') { + assert.equal(parse('*').output, '(?!\\.)(?=.)[^\\\\/]*?[\\\\/]?'); + } else { + assert.equal(parse('*').output, '(?!\\.)(?=.)[^/]*?\\/?'); + } }); it('should support capture', () => { - assert.equal(parse('*', { capture: true }).output, '(?!\\.)(?=.)([^/]*?)\\/?'); + if (process.platform === 'win32') { + assert.equal(parse('*', { capture: true }).output, '(?!\\.)(?=.)([^\\\\/]*?)[\\\\/]?'); + } else { + assert.equal(parse('*', { capture: true }).output, '(?!\\.)(?=.)([^/]*?)\\/?'); + } }); it('should throw an error when value is not a string', () => { diff --git a/test/slashes-posix.js b/test/slashes-posix.js index d0eb80c..7092285 100644 --- a/test/slashes-posix.js +++ b/test/slashes-posix.js @@ -12,8 +12,10 @@ describe('slash handling - posix', () => { it('should handle backslashes', () => { assert(isMatch('\\\\\\a', '\\\\\\a')); assert(!isMatch('\\\\\\a', '\\\\a')); - assert(isMatch('\\\\\\a', '\\\\a', { contains: true })); assert(isMatch('\\', '\\', { fastpaths: false })); + if (process.platform !== 'win32') { + assert(isMatch('\\\\\\a', '\\\\a', { contains: true })); + } }); it('should match a literal string', () => { From ad51b07530f84aa50191a1746bf1198553a8dcf4 Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Sun, 23 Feb 2020 03:31:54 -0500 Subject: [PATCH 5/8] fix assert for 8.6 --- test/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils.js b/test/utils.js index cb4af10..571de55 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,7 +1,7 @@ 'use strict'; const path = require('path'); -const assert = require('assert').strict; +const assert = require('assert'); const utils = require('../lib/utils'); /** From dbe5d92abd660f719f233075e0884af2ae212cc3 Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Sun, 23 Feb 2020 03:51:18 -0500 Subject: [PATCH 6/8] fixes https://github.com/micromatch/picomatch/issues/57 fixes https://github.com/micromatch/picomatch/issues/60 fixes https://github.com/micromatch/picomatch/issues/61 --- .verb.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.verb.md b/.verb.md index 8fbb4b7..aefeab5 100644 --- a/.verb.md +++ b/.verb.md @@ -122,14 +122,15 @@ The following options may be used with the main `picomatch()` function or any of | [onMatch](#optionsonMatch) | `function` | `undefined` | Function to be called on matched items. | | [onResult](#optionsonResult) | `function` | `undefined` | Function to be called on all items, regardless of whether or not they are matched or ignored. | | `posix` | `boolean` | `false` | Support POSIX character classes ("posix brackets"). | -| `posixSlashes` | `boolean` | `undefined` | Convert all slashes in file paths to forward slashes. This does not convert slashes in the glob pattern itself | | `prepend` | `boolean` | `undefined` | String to prepend to the generated regex used for matching. | | `regex` | `boolean` | `false` | Use regular expression rules for `+` (instead of matching literal `+`), and for stars that follow closing parentheses or brackets (as in `)*` and `]*`). | -| `strictBraces` | `boolean` | `undefined` | Treat brace patterns with no sets or ranges as literals. For example, `{abc}` would be a literal. | -| `strictBrackets` | `boolean` | `undefined` | Throw an error if brackets, braces, or parens are imbalanced. | +| `strictBraces` | `boolean` | `undefined` | Treat brace patterns with no sets or ranges as literals. For example, `{abc}` would be a literal. | +| `strictBrackets` | `boolean` | `undefined` | Throw an error if brackets, braces, or parens are imbalanced. | | `strictSlashes` | `boolean` | `undefined` | When true, picomatch won't match trailing slashes with single stars. | | `unescape` | `boolean` | `undefined` | Remove backslashes preceding escaped characters in the glob pattern. By default, backslashes are retained. | | `unixify` | `boolean` | `undefined` | Alias for `posixSlashes`, for backwards compatibility. | +| `windows` | `boolean` | `undefined` | Force picomatch to act like the platform is Windows. When true, converts all slashes file paths (or whatever input strings are passed) to forward (posix) slashes. Note that this does not convert slashes in **glob patterns**, only in the strings to be matched. | + ### Scan Options From 4157b78d9708218380e5f7051eca3e55b075021b Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Sun, 23 Feb 2020 04:26:36 -0500 Subject: [PATCH 7/8] ensure tok.output is the same as tok.value when type is `text` --- lib/parse.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/parse.js b/lib/parse.js index 262eef6..e91ea1d 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -211,6 +211,10 @@ const parse = (input, options) => { return; } + if (tok.type === 'text') { + tok.output = tok.value; + } + tok.prev = prev; tokens.push(tok); prev = tok; From 850e970e6ba330aacc4aeeb1280cf3d2798ab16c Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Sun, 23 Feb 2020 04:26:43 -0500 Subject: [PATCH 8/8] upgrade deps --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 222b12e..3f82c7b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "eslint": "^6.8.0", "fill-range": "^7.0.1", "gulp-format-md": "^2.0.0", - "mocha": "^6.2.2", + "mocha": "^7.0.1", "nyc": "^15.0.0", "time-require": "github:jonschlinkert/time-require" }, @@ -65,6 +65,8 @@ "related": { "list": [ "braces", + "fill-range", + "to-regex-range", "micromatch" ] },