You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1084 lines
						
					
					
						
							27 KiB
						
					
					
				
			
		
		
	
	
							1084 lines
						
					
					
						
							27 KiB
						
					
					
				'use strict'; | 
						|
 | 
						|
const constants = require('./constants'); | 
						|
const utils = require('./utils'); | 
						|
 | 
						|
/** | 
						|
 * Constants | 
						|
 */ | 
						|
 | 
						|
const { | 
						|
  MAX_LENGTH, | 
						|
  POSIX_REGEX_SOURCE, | 
						|
  REGEX_NON_SPECIAL_CHARS, | 
						|
  REGEX_SPECIAL_CHARS_BACKREF, | 
						|
  REPLACEMENTS | 
						|
} = constants; | 
						|
 | 
						|
/** | 
						|
 * Helpers | 
						|
 */ | 
						|
 | 
						|
const expandRange = (args, options) => { | 
						|
  if (typeof options.expandRange === 'function') { | 
						|
    return options.expandRange(...args, options); | 
						|
  } | 
						|
 | 
						|
  args.sort(); | 
						|
  const value = `[${args.join('-')}]`; | 
						|
 | 
						|
  try { | 
						|
    /* eslint-disable-next-line no-new */ | 
						|
    new RegExp(value); | 
						|
  } catch (ex) { | 
						|
    return args.map(v => utils.escapeRegex(v)).join('..'); | 
						|
  } | 
						|
 | 
						|
  return value; | 
						|
}; | 
						|
 | 
						|
/** | 
						|
 * 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; | 
						|
 | 
						|
  const opts = { ...options }; | 
						|
  const 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}`); | 
						|
  } | 
						|
 | 
						|
  const bos = { type: 'bos', value: '', output: opts.prepend || '' }; | 
						|
  const tokens = [bos]; | 
						|
 | 
						|
  const capture = opts.capture ? '' : '?:'; | 
						|
  const 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}).)*?)`; | 
						|
  }; | 
						|
 | 
						|
  const nodot = opts.dot ? '' : NO_DOT; | 
						|
  const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; | 
						|
  let star = opts.bash === true ? globstar(opts) : STAR; | 
						|
 | 
						|
  if (opts.capture) { | 
						|
    star = `(${star})`; | 
						|
  } | 
						|
 | 
						|
  // minimatch options support | 
						|
  if (typeof opts.noext === 'boolean') { | 
						|
    opts.noextglob = opts.noext; | 
						|
  } | 
						|
 | 
						|
  const state = { | 
						|
    input, | 
						|
    index: -1, | 
						|
    start: 0, | 
						|
    dot: opts.dot === true, | 
						|
    consumed: '', | 
						|
    output: '', | 
						|
    prefix: '', | 
						|
    backtrack: false, | 
						|
    negated: false, | 
						|
    brackets: 0, | 
						|
    braces: 0, | 
						|
    parens: 0, | 
						|
    quotes: 0, | 
						|
    globstar: false, | 
						|
    tokens | 
						|
  }; | 
						|
 | 
						|
  input = utils.removePrefix(input, state); | 
						|
  len = input.length; | 
						|
 | 
						|
  const extglobs = []; | 
						|
  const braces = []; | 
						|
  const 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 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); | 
						|
  }; | 
						|
 | 
						|
  const negate = () => { | 
						|
    let count = 1; | 
						|
 | 
						|
    while (peek() === '!' && (peek(2) !== '(' || peek(3) === '?')) { | 
						|
      advance(); | 
						|
      state.start++; | 
						|
      count++; | 
						|
    } | 
						|
 | 
						|
    if (count % 2 === 0) { | 
						|
      return false; | 
						|
    } | 
						|
 | 
						|
    state.negated = true; | 
						|
    state.start++; | 
						|
    return true; | 
						|
  }; | 
						|
 | 
						|
  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') { | 
						|
      const isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace'); | 
						|
      const isExtglob = tok.extglob === true || (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') { | 
						|
      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; | 
						|
      prev.output = (prev.output || '') + tok.value; | 
						|
      return; | 
						|
    } | 
						|
 | 
						|
    tok.prev = prev; | 
						|
    tokens.push(tok); | 
						|
    prev = tok; | 
						|
  }; | 
						|
 | 
						|
  const extglobOpen = (type, value) => { | 
						|
    const token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' }; | 
						|
 | 
						|
    token.prev = prev; | 
						|
    token.parens = state.parens; | 
						|
    token.output = state.output; | 
						|
    const output = (opts.capture ? '(' : '') + token.open; | 
						|
 | 
						|
    increment('parens'); | 
						|
    push({ type, value, output: state.output ? '' : ONE_CHAR }); | 
						|
    push({ type: 'paren', extglob: true, value: advance(), output }); | 
						|
    extglobs.push(token); | 
						|
  }; | 
						|
 | 
						|
  const extglobClose = token => { | 
						|
    let output = token.close + (opts.capture ? ')' : ''); | 
						|
    let rest; | 
						|
 | 
						|
    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(remaining())) { | 
						|
        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; | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    push({ type: 'paren', extglob: true, value, output }); | 
						|
    decrement('parens'); | 
						|
  }; | 
						|
 | 
						|
  /** | 
						|
   * Fast paths | 
						|
   */ | 
						|
 | 
						|
  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 ? '\\' : ''); | 
						|
        }); | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    if (output === input && opts.contains === true) { | 
						|
      state.output = input; | 
						|
      return state; | 
						|
    } | 
						|
 | 
						|
    state.output = utils.wrapOutput(output, state, options); | 
						|
    return state; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Tokenize input until we reach end-of-string | 
						|
   */ | 
						|
 | 
						|
  while (!eos()) { | 
						|
    value = advance(); | 
						|
 | 
						|
    if (value === '\u0000') { | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    /** | 
						|
     * Escaped characters | 
						|
     */ | 
						|
 | 
						|
    if (value === '\\') { | 
						|
      const 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 | 
						|
      const match = /^\\+/.exec(remaining()); | 
						|
      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 === ':') { | 
						|
        const inner = prev.value.slice(1); | 
						|
        if (inner.includes('[')) { | 
						|
          prev.posix = true; | 
						|
 | 
						|
          if (inner.includes(':')) { | 
						|
            const idx = prev.value.lastIndexOf('['); | 
						|
            const pre = prev.value.slice(0, idx); | 
						|
            const rest = prev.value.slice(idx + 2); | 
						|
            const 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 === '(') { | 
						|
      increment('parens'); | 
						|
      push({ type: 'paren', value }); | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    if (value === ')') { | 
						|
      if (state.parens === 0 && opts.strictBrackets === true) { | 
						|
        throw new SyntaxError(syntaxError('opening', '(')); | 
						|
      } | 
						|
 | 
						|
      const 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; | 
						|
    } | 
						|
 | 
						|
    /** | 
						|
     * Square brackets | 
						|
     */ | 
						|
 | 
						|
    if (value === '[') { | 
						|
      if (opts.nobracket === true || !remaining().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'); | 
						|
 | 
						|
      const 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; | 
						|
      } | 
						|
 | 
						|
      const 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) { | 
						|
      increment('braces'); | 
						|
 | 
						|
      const open = { | 
						|
        type: 'brace', | 
						|
        value, | 
						|
        output: '(', | 
						|
        outputIndex: state.output.length, | 
						|
        tokensIndex: state.tokens.length | 
						|
      }; | 
						|
 | 
						|
      braces.push(open); | 
						|
      push(open); | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    if (value === '}') { | 
						|
      const brace = braces[braces.length - 1]; | 
						|
 | 
						|
      if (opts.nobrace === true || !brace) { | 
						|
        push({ type: 'text', value, output: value }); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      let output = ')'; | 
						|
 | 
						|
      if (brace.dots === true) { | 
						|
        const arr = tokens.slice(); | 
						|
        const 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; | 
						|
      } | 
						|
 | 
						|
      if (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 = '\\{'; | 
						|
        value = output = '\\}'; | 
						|
        state.output = out; | 
						|
        for (const t of toks) { | 
						|
          state.output += (t.output || t.value); | 
						|
        } | 
						|
      } | 
						|
 | 
						|
      push({ type: 'brace', value, output }); | 
						|
      decrement('braces'); | 
						|
      braces.pop(); | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    /** | 
						|
     * Pipes | 
						|
     */ | 
						|
 | 
						|
    if (value === '|') { | 
						|
      if (extglobs.length > 0) { | 
						|
        extglobs[extglobs.length - 1].conditions++; | 
						|
      } | 
						|
      push({ type: 'text', value }); | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    /** | 
						|
     * Commas | 
						|
     */ | 
						|
 | 
						|
    if (value === ',') { | 
						|
      let output = value; | 
						|
 | 
						|
      const brace = braces[braces.length - 1]; | 
						|
      if (brace && stack[stack.length - 1] === 'braces') { | 
						|
        brace.comma = true; | 
						|
        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 === state.start + 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; | 
						|
        const brace = braces[braces.length - 1]; | 
						|
        prev.type = 'dots'; | 
						|
        prev.output += value; | 
						|
        prev.value += value; | 
						|
        brace.dots = true; | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      if ((state.braces + state.parens) === 0 && prev.type !== 'bos' && prev.type !== 'slash') { | 
						|
        push({ type: 'text', value, output: DOT_LITERAL }); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      push({ type: 'dot', value, output: DOT_LITERAL }); | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    /** | 
						|
     * Question marks | 
						|
     */ | 
						|
 | 
						|
    if (value === '?') { | 
						|
      const isGroup = prev && prev.value === '('; | 
						|
      if (!isGroup && opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { | 
						|
        extglobOpen('qmark', value); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      if (prev && prev.type === 'paren') { | 
						|
        const 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 === '<' && !/<([!=]|\w+>)/.test(remaining()))) { | 
						|
          output = `\\${value}`; | 
						|
        } | 
						|
 | 
						|
        push({ type: 'text', value, output }); | 
						|
        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(); | 
						|
        continue; | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    /** | 
						|
     * Plus | 
						|
     */ | 
						|
 | 
						|
    if (value === '+') { | 
						|
      if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { | 
						|
        extglobOpen('plus', value); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      if ((prev && prev.value === '(') || opts.regex === false) { | 
						|
        push({ type: 'plus', value, output: PLUS_LITERAL }); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      if ((prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) || state.parens > 0) { | 
						|
        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', extglob: true, value, output: '' }); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      push({ type: 'text', value }); | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    /** | 
						|
     * Plain text | 
						|
     */ | 
						|
 | 
						|
    if (value !== '*') { | 
						|
      if (value === '$' || value === '^') { | 
						|
        value = `\\${value}`; | 
						|
      } | 
						|
 | 
						|
      const match = REGEX_NON_SPECIAL_CHARS.exec(remaining()); | 
						|
      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.globstar = true; | 
						|
      consume(value); | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    let rest = remaining(); | 
						|
    if (opts.noextglob !== true && /^\([^?]/.test(rest)) { | 
						|
      extglobOpen('star', value); | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    if (prev.type === 'star') { | 
						|
      if (opts.noglobstar === true) { | 
						|
        consume(value); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      const prior = prev.prev; | 
						|
      const before = prior.prev; | 
						|
      const isStart = prior.type === 'slash' || prior.type === 'bos'; | 
						|
      const afterStar = before && (before.type === 'star' || before.type === 'globstar'); | 
						|
 | 
						|
      if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== '/'))) { | 
						|
        push({ type: 'star', value, output: '' }); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      const isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace'); | 
						|
      const 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 (rest.slice(0, 3) === '/**') { | 
						|
        const after = input[state.index + 4]; | 
						|
        if (after && after !== '/') { | 
						|
          break; | 
						|
        } | 
						|
        rest = rest.slice(3); | 
						|
        consume('/**', 3); | 
						|
      } | 
						|
 | 
						|
      if (prior.type === 'bos' && eos()) { | 
						|
        prev.type = 'globstar'; | 
						|
        prev.value += value; | 
						|
        prev.output = globstar(opts); | 
						|
        state.output = prev.output; | 
						|
        state.globstar = true; | 
						|
        consume(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) + (opts.strictSlashes ? ')' : '|$)'); | 
						|
        prev.value += value; | 
						|
        state.globstar = true; | 
						|
        state.output += prior.output + prev.output; | 
						|
        consume(value); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      if (prior.type === 'slash' && prior.prev.type !== 'bos' && rest[0] === '/') { | 
						|
        const end = rest[1] !== 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.globstar = true; | 
						|
 | 
						|
        consume(value + advance()); | 
						|
 | 
						|
        push({ type: 'slash', value: '/', output: '' }); | 
						|
        continue; | 
						|
      } | 
						|
 | 
						|
      if (prior.type === 'bos' && rest[0] === '/') { | 
						|
        prev.type = 'globstar'; | 
						|
        prev.value += value; | 
						|
        prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; | 
						|
        state.output = prev.output; | 
						|
        state.globstar = true; | 
						|
        consume(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.globstar = true; | 
						|
      consume(value); | 
						|
      continue; | 
						|
    } | 
						|
 | 
						|
    const 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 (const 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) => { | 
						|
  const opts = { ...options }; | 
						|
  const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; | 
						|
  const len = input.length; | 
						|
  if (len > max) { | 
						|
    throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); | 
						|
  } | 
						|
 | 
						|
  input = REPLACEMENTS[input] || input; | 
						|
  const 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); | 
						|
 | 
						|
  const nodot = opts.dot ? NO_DOTS : NO_DOT; | 
						|
  const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; | 
						|
  const capture = opts.capture ? '' : '?:'; | 
						|
  const state = { negated: false, prefix: '' }; | 
						|
  let star = opts.bash === true ? '.*?' : STAR; | 
						|
 | 
						|
  if (opts.capture) { | 
						|
    star = `(${star})`; | 
						|
  } | 
						|
 | 
						|
  const globstar = opts => { | 
						|
    if (opts.noglobstar === true) return star; | 
						|
    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: { | 
						|
        const match = /^(.*?)\.(\w+)$/.exec(str); | 
						|
        if (!match) return; | 
						|
 | 
						|
        const source = create(match[1]); | 
						|
        if (!source) return; | 
						|
 | 
						|
        return source + DOT_LITERAL + match[2]; | 
						|
      } | 
						|
    } | 
						|
  }; | 
						|
 | 
						|
  const output = utils.removePrefix(input, state); | 
						|
  let source = create(output); | 
						|
 | 
						|
  if (source && opts.strictSlashes !== true) { | 
						|
    source += `${SLASH_LITERAL}?`; | 
						|
  } | 
						|
 | 
						|
  return source; | 
						|
}; | 
						|
 | 
						|
module.exports = parse;
 | 
						|
 |