| 'use strict'; |
| |
| const utils = require('./utils'); |
| const constants = require('./constants'); |
| |
| /** |
| * Constants |
| */ |
| |
| const { |
| MAX_LENGTH, |
| POSIX_REGEX_SOURCE, |
| REGEX_NON_SPECIAL_CHAR, |
| REGEX_SPECIAL_CHARS_BACKREF, |
| REPLACEMENTS |
| } = constants; |
| |
| /** |
| * Helpers |
| */ |
| |
| const expandRange = (args, options) => { |
| if (typeof options.expandRange === 'function') { |
| return options.expandRange(...args, options); |
| } |
| |
| args.sort(); |
| let value = `[${args.join('-')}]`; |
| |
| try { |
| /* eslint-disable no-new */ |
| new RegExp(value); |
| } catch (ex) { |
| return args.map(v => utils.escapeRegex(v)).join('..'); |
| } |
| |
| return value; |
| }; |
| |
| const negate = state => { |
| let count = 1; |
| |
| while (state.peek() === '!' && (state.peek(2) !== '(' || state.peek(3) === '?')) { |
| state.advance(); |
| state.start++; |
| count++; |
| } |
| |
| if (count % 2 === 0) { |
| return false; |
| } |
| |
| state.negated = true; |
| state.start++; |
| return true; |
| }; |
| |
| /** |
| * Create the message for a syntax error |
| */ |
| |
| const syntaxError = (type, char) => { |
| return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; |
| }; |
| |
| /** |
| * Parse the given input string. |
| * @param {String} input |
| * @param {Object} options |
| * @return {Object} |
| */ |
| |
| const parse = (input, options) => { |
| if (typeof input !== 'string') { |
| throw new TypeError('Expected a string'); |
| } |
| |
| input = REPLACEMENTS[input] || input; |
| |
| let opts = { ...options }; |
| let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; |
| let len = input.length; |
| if (len > max) { |
| throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); |
| } |
| |
| let bos = { type: 'bos', value: '', output: opts.prepend || '' }; |
| let tokens = [bos]; |
| |
| let capture = opts.capture ? '' : '?:'; |
| let win32 = utils.isWindows(options); |
| |
| // create constants based on platform, for windows or posix |
| const PLATFORM_CHARS = constants.globChars(win32); |
| const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS); |
| |
| const { |
| DOT_LITERAL, |
| PLUS_LITERAL, |
| SLASH_LITERAL, |
| ONE_CHAR, |
| DOTS_SLASH, |
| NO_DOT, |
| NO_DOT_SLASH, |
| NO_DOTS_SLASH, |
| QMARK, |
| QMARK_NO_DOT, |
| STAR, |
| START_ANCHOR |
| } = PLATFORM_CHARS; |
| |
| const globstar = (opts) => { |
| return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; |
| }; |
| |
| let nodot = opts.dot ? '' : NO_DOT; |
| let star = opts.bash === true ? globstar(opts) : STAR; |
| let qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; |
| |
| if (opts.capture) { |
| star = `(${star})`; |
| } |
| |
| // minimatch options support |
| if (typeof opts.noext === 'boolean') { |
| opts.noextglob = opts.noext; |
| } |
| |
| let state = { |
| index: -1, |
| start: 0, |
| consumed: '', |
| output: '', |
| backtrack: false, |
| brackets: 0, |
| braces: 0, |
| parens: 0, |
| quotes: 0, |
| tokens |
| }; |
| |
| let extglobs = []; |
| let stack = []; |
| let prev = bos; |
| let value; |
| |
| /** |
| * Tokenizing helpers |
| */ |
| |
| const eos = () => state.index === len - 1; |
| const peek = state.peek = (n = 1) => input[state.index + n]; |
| const advance = state.advance = () => input[++state.index]; |
| const append = token => { |
| state.output += token.output != null ? token.output : token.value; |
| state.consumed += token.value || ''; |
| }; |
| |
| const increment = type => { |
| state[type]++; |
| stack.push(type); |
| }; |
| |
| const decrement = type => { |
| state[type]--; |
| stack.pop(); |
| }; |
| |
| /** |
| * Push tokens onto the tokens array. This helper speeds up |
| * tokenizing by 1) helping us avoid backtracking as much as possible, |
| * and 2) helping us avoid creating extra tokens when consecutive |
| * characters are plain text. This improves performance and simplifies |
| * lookbehinds. |
| */ |
| |
| const push = tok => { |
| if (prev.type === 'globstar') { |
| let isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace'); |
| let isExtglob = extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'); |
| if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) { |
| state.output = state.output.slice(0, -prev.output.length); |
| prev.type = 'star'; |
| prev.value = '*'; |
| prev.output = star; |
| state.output += prev.output; |
| } |
| } |
| |
| if (extglobs.length && tok.type !== 'paren' && !EXTGLOB_CHARS[tok.value]) { |
| extglobs[extglobs.length - 1].inner += tok.value; |
| } |
| |
| if (tok.value || tok.output) append(tok); |
| if (prev && prev.type === 'text' && tok.type === 'text') { |
| prev.value += tok.value; |
| return; |
| } |
| |
| tok.prev = prev; |
| tokens.push(tok); |
| prev = tok; |
| }; |
| |
| const extglobOpen = (type, value) => { |
| let token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' }; |
| |
| token.prev = prev; |
| token.parens = state.parens; |
| token.output = state.output; |
| let output = (opts.capture ? '(' : '') + token.open; |
| |
| push({ type, value, output: state.output ? '' : ONE_CHAR }); |
| push({ type: 'paren', extglob: true, value: advance(), output }); |
| increment('parens'); |
| extglobs.push(token); |
| }; |
| |
| const extglobClose = token => { |
| let output = token.close + (opts.capture ? ')' : ''); |
| |
| if (token.type === 'negate') { |
| let extglobStar = star; |
| |
| if (token.inner && token.inner.length > 1 && token.inner.includes('/')) { |
| extglobStar = globstar(opts); |
| } |
| |
| if (extglobStar !== star || eos() || /^\)+$/.test(input.slice(state.index + 1))) { |
| output = token.close = ')$))' + extglobStar; |
| } |
| |
| if (token.prev.type === 'bos' && eos()) { |
| state.negatedExtglob = true; |
| } |
| } |
| |
| push({ type: 'paren', extglob: true, value, output }); |
| decrement('parens'); |
| }; |
| |
| if (opts.fastpaths !== false && !/(^[*!]|[/{[()\]}"])/.test(input)) { |
| let backslashes = false; |
| |
| let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { |
| if (first === '\\') { |
| backslashes = true; |
| return m; |
| } |
| |
| if (first === '?') { |
| if (esc) { |
| return esc + first + (rest ? QMARK.repeat(rest.length) : ''); |
| } |
| if (index === 0) { |
| return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ''); |
| } |
| return QMARK.repeat(chars.length); |
| } |
| |
| if (first === '.') { |
| return DOT_LITERAL.repeat(chars.length); |
| } |
| |
| if (first === '*') { |
| if (esc) { |
| return esc + first + (rest ? star : ''); |
| } |
| return star; |
| } |
| return esc ? m : '\\' + m; |
| }); |
| |
| if (backslashes === true) { |
| if (opts.unescape === true) { |
| output = output.replace(/\\/g, ''); |
| } else { |
| output = output.replace(/\\+/g, m => { |
| return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : ''); |
| }); |
| } |
| } |
| |
| state.output = output; |
| return state; |
| } |
| |
| /** |
| * Tokenize input until we reach end-of-string |
| */ |
| |
| while (!eos()) { |
| value = advance(); |
| |
| if (value === '\u0000') { |
| continue; |
| } |
| |
| /** |
| * Escaped characters |
| */ |
| |
| if (value === '\\') { |
| let next = peek(); |
| |
| if (next === '/' && opts.bash !== true) { |
| continue; |
| } |
| |
| if (next === '.' || next === ';') { |
| continue; |
| } |
| |
| if (!next) { |
| value += '\\'; |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| // collapse slashes to reduce potential for exploits |
| let match = /^\\+/.exec(input.slice(state.index + 1)); |
| let slashes = 0; |
| |
| if (match && match[0].length > 2) { |
| slashes = match[0].length; |
| state.index += slashes; |
| if (slashes % 2 !== 0) { |
| value += '\\'; |
| } |
| } |
| |
| if (opts.unescape === true) { |
| value = advance() || ''; |
| } else { |
| value += advance() || ''; |
| } |
| |
| if (state.brackets === 0) { |
| push({ type: 'text', value }); |
| continue; |
| } |
| } |
| |
| /** |
| * If we're inside a regex character class, continue |
| * until we reach the closing bracket. |
| */ |
| |
| if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) { |
| if (opts.posix !== false && value === ':') { |
| let inner = prev.value.slice(1); |
| if (inner.includes('[')) { |
| prev.posix = true; |
| |
| if (inner.includes(':')) { |
| let idx = prev.value.lastIndexOf('['); |
| let pre = prev.value.slice(0, idx); |
| let rest = prev.value.slice(idx + 2); |
| let posix = POSIX_REGEX_SOURCE[rest]; |
| if (posix) { |
| prev.value = pre + posix; |
| state.backtrack = true; |
| advance(); |
| |
| if (!bos.output && tokens.indexOf(prev) === 1) { |
| bos.output = ONE_CHAR; |
| } |
| continue; |
| } |
| } |
| } |
| } |
| |
| if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) { |
| value = '\\' + value; |
| } |
| |
| if (value === ']' && (prev.value === '[' || prev.value === '[^')) { |
| value = '\\' + value; |
| } |
| |
| if (opts.posix === true && value === '!' && prev.value === '[') { |
| value = '^'; |
| } |
| |
| prev.value += value; |
| append({ value }); |
| continue; |
| } |
| |
| /** |
| * If we're inside a quoted string, continue |
| * until we reach the closing double quote. |
| */ |
| |
| if (state.quotes === 1 && value !== '"') { |
| value = utils.escapeRegex(value); |
| prev.value += value; |
| append({ value }); |
| continue; |
| } |
| |
| /** |
| * Double quotes |
| */ |
| |
| if (value === '"') { |
| state.quotes = state.quotes === 1 ? 0 : 1; |
| if (opts.keepQuotes === true) { |
| push({ type: 'text', value }); |
| } |
| continue; |
| } |
| |
| /** |
| * Parentheses |
| */ |
| |
| if (value === '(') { |
| push({ type: 'paren', value }); |
| increment('parens'); |
| continue; |
| } |
| |
| if (value === ')') { |
| if (state.parens === 0 && opts.strictBrackets === true) { |
| throw new SyntaxError(syntaxError('opening', '(')); |
| } |
| |
| let extglob = extglobs[extglobs.length - 1]; |
| if (extglob && state.parens === extglob.parens + 1) { |
| extglobClose(extglobs.pop()); |
| continue; |
| } |
| |
| push({ type: 'paren', value, output: state.parens ? ')' : '\\)' }); |
| decrement('parens'); |
| continue; |
| } |
| |
| /** |
| * Brackets |
| */ |
| |
| if (value === '[') { |
| if (opts.nobracket === true || !input.slice(state.index + 1).includes(']')) { |
| if (opts.nobracket !== true && opts.strictBrackets === true) { |
| throw new SyntaxError(syntaxError('closing', ']')); |
| } |
| |
| value = '\\' + value; |
| } else { |
| increment('brackets'); |
| } |
| |
| push({ type: 'bracket', value }); |
| continue; |
| } |
| |
| if (value === ']') { |
| if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) { |
| push({ type: 'text', value, output: '\\' + value }); |
| continue; |
| } |
| |
| if (state.brackets === 0) { |
| if (opts.strictBrackets === true) { |
| throw new SyntaxError(syntaxError('opening', '[')); |
| } |
| |
| push({ type: 'text', value, output: '\\' + value }); |
| continue; |
| } |
| |
| decrement('brackets'); |
| |
| let prevValue = prev.value.slice(1); |
| if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) { |
| value = '/' + value; |
| } |
| |
| prev.value += value; |
| append({ value }); |
| |
| // when literal brackets are explicitly disabled |
| // assume we should match with a regex character class |
| if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) { |
| continue; |
| } |
| |
| let escaped = utils.escapeRegex(prev.value); |
| state.output = state.output.slice(0, -prev.value.length); |
| |
| // when literal brackets are explicitly enabled |
| // assume we should escape the brackets to match literal characters |
| if (opts.literalBrackets === true) { |
| state.output += escaped; |
| prev.value = escaped; |
| continue; |
| } |
| |
| // when the user specifies nothing, try to match both |
| prev.value = `(${capture}${escaped}|${prev.value})`; |
| state.output += prev.value; |
| continue; |
| } |
| |
| /** |
| * Braces |
| */ |
| |
| if (value === '{' && opts.nobrace !== true) { |
| push({ type: 'brace', value, output: '(' }); |
| increment('braces'); |
| continue; |
| } |
| |
| if (value === '}') { |
| if (opts.nobrace === true || state.braces === 0) { |
| push({ type: 'text', value, output: '\\' + value }); |
| continue; |
| } |
| |
| let output = ')'; |
| |
| if (state.dots === true) { |
| let arr = tokens.slice(); |
| let range = []; |
| |
| for (let i = arr.length - 1; i >= 0; i--) { |
| tokens.pop(); |
| if (arr[i].type === 'brace') { |
| break; |
| } |
| if (arr[i].type !== 'dots') { |
| range.unshift(arr[i].value); |
| } |
| } |
| |
| output = expandRange(range, opts); |
| state.backtrack = true; |
| } |
| |
| push({ type: 'brace', value, output }); |
| decrement('braces'); |
| continue; |
| } |
| |
| /** |
| * Pipes |
| */ |
| |
| if (value === '|') { |
| if (extglobs.length > 0) { |
| extglobs[extglobs.length - 1].conditions++; |
| } |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| /** |
| * Commas |
| */ |
| |
| if (value === ',') { |
| let output = value; |
| |
| if (state.braces > 0 && stack[stack.length - 1] === 'braces') { |
| output = '|'; |
| } |
| |
| push({ type: 'comma', value, output }); |
| continue; |
| } |
| |
| /** |
| * Slashes |
| */ |
| |
| if (value === '/') { |
| // if the beginning of the glob is "./", advance the start |
| // to the current index, and don't add the "./" characters |
| // to the state. This greatly simplifies lookbehinds when |
| // checking for BOS characters like "!" and "." (not "./") |
| if (prev.type === 'dot' && state.index === 1) { |
| state.start = state.index + 1; |
| state.consumed = ''; |
| state.output = ''; |
| tokens.pop(); |
| prev = bos; // reset "prev" to the first token |
| continue; |
| } |
| |
| push({ type: 'slash', value, output: SLASH_LITERAL }); |
| continue; |
| } |
| |
| /** |
| * Dots |
| */ |
| |
| if (value === '.') { |
| if (state.braces > 0 && prev.type === 'dot') { |
| if (prev.value === '.') prev.output = DOT_LITERAL; |
| prev.type = 'dots'; |
| prev.output += value; |
| prev.value += value; |
| state.dots = true; |
| continue; |
| } |
| |
| push({ type: 'dot', value, output: DOT_LITERAL }); |
| continue; |
| } |
| |
| /** |
| * Question marks |
| */ |
| |
| if (value === '?') { |
| if (prev && prev.type === 'paren') { |
| let next = peek(); |
| let output = value; |
| |
| if (next === '<' && !utils.supportsLookbehinds()) { |
| throw new Error('Node.js v10 or higher is required for regex lookbehinds'); |
| } |
| |
| if (prev.value === '(' && !/[!=<:]/.test(next) || (next === '<' && !/[!=]/.test(peek(2)))) { |
| output = '\\' + value; |
| } |
| |
| push({ type: 'text', value, output }); |
| continue; |
| } |
| |
| if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { |
| extglobOpen('qmark', value); |
| continue; |
| } |
| |
| if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) { |
| push({ type: 'qmark', value, output: QMARK_NO_DOT }); |
| continue; |
| } |
| |
| push({ type: 'qmark', value, output: QMARK }); |
| continue; |
| } |
| |
| /** |
| * Exclamation |
| */ |
| |
| if (value === '!') { |
| if (opts.noextglob !== true && peek() === '(') { |
| if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) { |
| extglobOpen('negate', value); |
| continue; |
| } |
| } |
| |
| if (opts.nonegate !== true && state.index === 0) { |
| negate(state); |
| continue; |
| } |
| } |
| |
| /** |
| * Plus |
| */ |
| |
| if (value === '+') { |
| if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { |
| extglobOpen('plus', value); |
| continue; |
| } |
| |
| if (prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) { |
| let output = prev.extglob === true ? '\\' + value : value; |
| push({ type: 'plus', value, output }); |
| continue; |
| } |
| |
| // use regex behavior inside parens |
| if (state.parens > 0 && opts.regex !== false) { |
| push({ type: 'plus', value }); |
| continue; |
| } |
| |
| push({ type: 'plus', value: PLUS_LITERAL }); |
| continue; |
| } |
| |
| /** |
| * Plain text |
| */ |
| |
| if (value === '@') { |
| if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { |
| push({ type: 'at', value, output: '' }); |
| continue; |
| } |
| |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| /** |
| * Plain text |
| */ |
| |
| if (value !== '*') { |
| if (value === '$' || value === '^') { |
| value = '\\' + value; |
| } |
| |
| let match = REGEX_NON_SPECIAL_CHAR.exec(input.slice(state.index + 1)); |
| if (match) { |
| value += match[0]; |
| state.index += match[0].length; |
| } |
| |
| push({ type: 'text', value }); |
| continue; |
| } |
| |
| /** |
| * Stars |
| */ |
| |
| if (prev && (prev.type === 'globstar' || prev.star === true)) { |
| prev.type = 'star'; |
| prev.star = true; |
| prev.value += value; |
| prev.output = star; |
| state.backtrack = true; |
| state.consumed += value; |
| continue; |
| } |
| |
| if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { |
| extglobOpen('star', value); |
| continue; |
| } |
| |
| if (prev.type === 'star') { |
| if (opts.noglobstar === true) { |
| state.consumed += value; |
| continue; |
| } |
| |
| let prior = prev.prev; |
| let before = prior.prev; |
| let isStart = prior.type === 'slash' || prior.type === 'bos'; |
| let afterStar = before && (before.type === 'star' || before.type === 'globstar'); |
| |
| if (opts.bash === true && (!isStart || (!eos() && peek() !== '/'))) { |
| push({ type: 'star', value, output: '' }); |
| continue; |
| } |
| |
| let isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace'); |
| let isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren'); |
| if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) { |
| push({ type: 'star', value, output: '' }); |
| continue; |
| } |
| |
| // strip consecutive `/**/` |
| while (input.slice(state.index + 1, state.index + 4) === '/**') { |
| let after = input[state.index + 4]; |
| if (after && after !== '/') { |
| break; |
| } |
| state.consumed += '/**'; |
| state.index += 3; |
| } |
| |
| if (prior.type === 'bos' && eos()) { |
| prev.type = 'globstar'; |
| prev.value += value; |
| prev.output = globstar(opts); |
| state.output = prev.output; |
| state.consumed += value; |
| continue; |
| } |
| |
| if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) { |
| state.output = state.output.slice(0, -(prior.output + prev.output).length); |
| prior.output = '(?:' + prior.output; |
| |
| prev.type = 'globstar'; |
| prev.output = globstar(opts) + '|$)'; |
| prev.value += value; |
| |
| state.output += prior.output + prev.output; |
| state.consumed += value; |
| continue; |
| } |
| |
| let next = peek(); |
| if (prior.type === 'slash' && prior.prev.type !== 'bos' && next === '/') { |
| let end = peek(2) !== void 0 ? '|$' : ''; |
| |
| state.output = state.output.slice(0, -(prior.output + prev.output).length); |
| prior.output = '(?:' + prior.output; |
| |
| prev.type = 'globstar'; |
| prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; |
| prev.value += value; |
| |
| state.output += prior.output + prev.output; |
| state.consumed += value + advance(); |
| |
| push({ type: 'slash', value, output: '' }); |
| continue; |
| } |
| |
| if (prior.type === 'bos' && next === '/') { |
| prev.type = 'globstar'; |
| prev.value += value; |
| prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; |
| state.output = prev.output; |
| state.consumed += value + advance(); |
| push({ type: 'slash', value, output: '' }); |
| continue; |
| } |
| |
| // remove single star from output |
| state.output = state.output.slice(0, -prev.output.length); |
| |
| // reset previous token to globstar |
| prev.type = 'globstar'; |
| prev.output = globstar(opts); |
| prev.value += value; |
| |
| // reset output with globstar |
| state.output += prev.output; |
| state.consumed += value; |
| continue; |
| } |
| |
| let token = { type: 'star', value, output: star }; |
| |
| if (opts.bash === true) { |
| token.output = '.*?'; |
| if (prev.type === 'bos' || prev.type === 'slash') { |
| token.output = nodot + token.output; |
| } |
| push(token); |
| continue; |
| } |
| |
| if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) { |
| token.output = value; |
| push(token); |
| continue; |
| } |
| |
| if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') { |
| 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; |
| } |
| |
| if (peek() !== '*') { |
| state.output += ONE_CHAR; |
| prev.output += ONE_CHAR; |
| } |
| } |
| |
| push(token); |
| } |
| |
| while (state.brackets > 0) { |
| if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']')); |
| state.output = utils.escapeLast(state.output, '['); |
| decrement('brackets'); |
| } |
| |
| while (state.parens > 0) { |
| if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')')); |
| state.output = utils.escapeLast(state.output, '('); |
| decrement('parens'); |
| } |
| |
| while (state.braces > 0) { |
| if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}')); |
| state.output = utils.escapeLast(state.output, '{'); |
| decrement('braces'); |
| } |
| |
| if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) { |
| push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` }); |
| } |
| |
| // rebuild the output if we had to backtrack at any point |
| if (state.backtrack === true) { |
| state.output = ''; |
| |
| for (let token of state.tokens) { |
| state.output += token.output != null ? token.output : token.value; |
| |
| if (token.suffix) { |
| state.output += token.suffix; |
| } |
| } |
| } |
| |
| return state; |
| }; |
| |
| /** |
| * Fast paths for creating regular expressions for common glob patterns. |
| * This can significantly speed up processing and has very little downside |
| * impact when none of the fast paths match. |
| */ |
| |
| parse.fastpaths = (input, options) => { |
| let opts = { ...options }; |
| let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; |
| let len = input.length; |
| if (len > max) { |
| throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); |
| } |
| |
| input = REPLACEMENTS[input] || input; |
| let win32 = utils.isWindows(options); |
| |
| // create constants based on platform, for windows or posix |
| const { |
| DOT_LITERAL, |
| SLASH_LITERAL, |
| ONE_CHAR, |
| DOTS_SLASH, |
| NO_DOT, |
| NO_DOTS, |
| NO_DOTS_SLASH, |
| STAR, |
| START_ANCHOR |
| } = constants.globChars(win32); |
| |
| let capture = opts.capture ? '' : '?:'; |
| let star = opts.bash === true ? '.*?' : STAR; |
| let nodot = opts.dot ? NO_DOTS : NO_DOT; |
| let slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; |
| |
| if (opts.capture) { |
| star = `(${star})`; |
| } |
| |
| const globstar = (opts) => { |
| return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; |
| }; |
| |
| const create = str => { |
| switch (str) { |
| case '*': |
| return `${nodot}${ONE_CHAR}${star}`; |
| |
| case '.*': |
| return `${DOT_LITERAL}${ONE_CHAR}${star}`; |
| |
| case '*.*': |
| return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; |
| |
| case '*/*': |
| return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; |
| |
| case '**': |
| return nodot + globstar(opts); |
| |
| case '**/*': |
| return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; |
| |
| case '**/*.*': |
| return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; |
| |
| case '**/.*': |
| return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; |
| |
| default: { |
| let match = /^(.*?)\.(\w+)$/.exec(str); |
| if (!match) return; |
| |
| let source = create(match[1], options); |
| if (!source) return; |
| |
| return source + DOT_LITERAL + match[2]; |
| } |
| } |
| }; |
| |
| let output = create(input); |
| if (output && opts.strictSlashes !== true) { |
| output += `${SLASH_LITERAL}?`; |
| } |
| |
| return output; |
| }; |
| |
| module.exports = parse; |