Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
parse.js1086 linesDownload Raw Back to lib
1'use strict';
2
3const constants = require('./constants');
4const utils = require('./utils');
5
6/**
7 * Constants
8 */
9
10const {
11  MAX_LENGTH,
12  POSIX_REGEX_SOURCE,
13  REGEX_NON_SPECIAL_CHARS,
14  REGEX_SPECIAL_CHARS_BACKREF,
15  REPLACEMENTS
16} = constants;
17
18/**
19 * Helpers
20 */
21
22const expandRange = (args, options) => {
23  if (typeof options.expandRange === 'function') {
24    return options.expandRange(...args, options);
25  }
26
27  args.sort();
28  const value = `[${args.join('-')}]`;
29
30  try {
31    /* eslint-disable-next-line no-new */
32    new RegExp(value);
33  } catch (ex) {
34    return args.map(v => utils.escapeRegex(v)).join('..');
35  }
36
37  return value;
38};
39
40/**
41 * Create the message for a syntax error
42 */
43
44const syntaxError = (type, char) => {
45  return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
46};
47
48/**
49 * Parse the given input string.
50 * @param {String} input
51 * @param {Object} options
52 * @return {Object}
53 */
54
55const parse = (input, options) => {
56  if (typeof input !== 'string') {
57    throw new TypeError('Expected a string');
58  }
59
60  input = REPLACEMENTS[input] || input;
61
62  const opts = { ...options };
63  const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
64
65  let len = input.length;
66  if (len > max) {
67    throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
68  }
69
70  const bos = { type: 'bos', value: '', output: opts.prepend || '' };
71  const tokens = [bos];
72
73  const capture = opts.capture ? '' : '?:';
74
75  // create constants based on platform, for windows or posix
76  const PLATFORM_CHARS = constants.globChars(opts.windows);
77  const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
78
79  const {
80    DOT_LITERAL,
81    PLUS_LITERAL,
82    SLASH_LITERAL,
83    ONE_CHAR,
84    DOTS_SLASH,
85    NO_DOT,
86    NO_DOT_SLASH,
87    NO_DOTS_SLASH,
88    QMARK,
89    QMARK_NO_DOT,
90    STAR,
91    START_ANCHOR
92  } = PLATFORM_CHARS;
93
94  const globstar = opts => {
95    return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
96  };
97
98  const nodot = opts.dot ? '' : NO_DOT;
99  const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT;
100  let star = opts.bash === true ? globstar(opts) : STAR;
101
102  if (opts.capture) {
103    star = `(${star})`;
104  }
105
106  // minimatch options support
107  if (typeof opts.noext === 'boolean') {
108    opts.noextglob = opts.noext;
109  }
110
111  const state = {
112    input,
113    index: -1,
114    start: 0,
115    dot: opts.dot === true,
116    consumed: '',
117    output: '',
118    prefix: '',
119    backtrack: false,
120    negated: false,
121    brackets: 0,
122    braces: 0,
123    parens: 0,
124    quotes: 0,
125    globstar: false,
126    tokens
127  };
128
129  input = utils.removePrefix(input, state);
130  len = input.length;
131
132  const extglobs = [];
133  const braces = [];
134  const stack = [];
135  let prev = bos;
136  let value;
137
138  /**
139   * Tokenizing helpers
140   */
141
142  const eos = () => state.index === len - 1;
143  const peek = state.peek = (n = 1) => input[state.index + n];
144  const advance = state.advance = () => input[++state.index] || '';
145  const remaining = () => input.slice(state.index + 1);
146  const consume = (value = '', num = 0) => {
147    state.consumed += value;
148    state.index += num;
149  };
150
151  const append = token => {
152    state.output += token.output != null ? token.output : token.value;
153    consume(token.value);
154  };
155
156  const negate = () => {
157    let count = 1;
158
159    while (peek() === '!' && (peek(2) !== '(' || peek(3) === '?')) {
160      advance();
161      state.start++;
162      count++;
163    }
164
165    if (count % 2 === 0) {
166      return false;
167    }
168
169    state.negated = true;
170    state.start++;
171    return true;
172  };
173
174  const increment = type => {
175    state[type]++;
176    stack.push(type);
177  };
178
179  const decrement = type => {
180    state[type]--;
181    stack.pop();
182  };
183
184  /**
185   * Push tokens onto the tokens array. This helper speeds up
186   * tokenizing by 1) helping us avoid backtracking as much as possible,
187   * and 2) helping us avoid creating extra tokens when consecutive
188   * characters are plain text. This improves performance and simplifies
189   * lookbehinds.
190   */
191
192  const push = tok => {
193    if (prev.type === 'globstar') {
194      const isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace');
195      const isExtglob = tok.extglob === true || (extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'));
196
197      if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) {
198        state.output = state.output.slice(0, -prev.output.length);
199        prev.type = 'star';
200        prev.value = '*';
201        prev.output = star;
202        state.output += prev.output;
203      }
204    }
205
206    if (extglobs.length && tok.type !== 'paren') {
207      extglobs[extglobs.length - 1].inner += tok.value;
208    }
209
210    if (tok.value || tok.output) append(tok);
211    if (prev && prev.type === 'text' && tok.type === 'text') {
212      prev.output = (prev.output || prev.value) + tok.value;
213      prev.value += tok.value;
214      return;
215    }
216
217    tok.prev = prev;
218    tokens.push(tok);
219    prev = tok;
220  };
221
222  const extglobOpen = (type, value) => {
223    const token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' };
224
225    token.prev = prev;
226    token.parens = state.parens;
227    token.output = state.output;
228    const output = (opts.capture ? '(' : '') + token.open;
229
230    increment('parens');
231    push({ type, value, output: state.output ? '' : ONE_CHAR });
232    push({ type: 'paren', extglob: true, value: advance(), output });
233    extglobs.push(token);
234  };
235
236  const extglobClose = token => {
237    let output = token.close + (opts.capture ? ')' : '');
238    let rest;
239
240    if (token.type === 'negate') {
241      let extglobStar = star;
242
243      if (token.inner && token.inner.length > 1 && token.inner.includes('/')) {
244        extglobStar = globstar(opts);
245      }
246
247      if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) {
248        output = token.close = `)$))${extglobStar}`;
249      }
250
251      if (token.inner.includes('*') && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) {
252        // Any non-magical string (`.ts`) or even nested expression (`.{ts,tsx}`) can follow after the closing parenthesis.
253        // In this case, we need to parse the string and use it in the output of the original pattern.
254        // Suitable patterns: `/!(*.d).ts`, `/!(*.d).{ts,tsx}`, `**/!(*-dbg).@(js)`.
255        //
256        // Disabling the `fastpaths` option due to a problem with parsing strings as `.ts` in the pattern like `**/!(*.d).ts`.
257        const expression = parse(rest, { ...options, fastpaths: false }).output;
258
259        output = token.close = `)${expression})${extglobStar})`;
260      }
261
262      if (token.prev.type === 'bos') {
263        state.negatedExtglob = true;
264      }
265    }
266
267    push({ type: 'paren', extglob: true, value, output });
268    decrement('parens');
269  };
270
271  /**
272   * Fast paths
273   */
274
275  if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
276    let backslashes = false;
277
278    let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
279      if (first === '\\') {
280        backslashes = true;
281        return m;
282      }
283
284      if (first === '?') {
285        if (esc) {
286          return esc + first + (rest ? QMARK.repeat(rest.length) : '');
287        }
288        if (index === 0) {
289          return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : '');
290        }
291        return QMARK.repeat(chars.length);
292      }
293
294      if (first === '.') {
295        return DOT_LITERAL.repeat(chars.length);
296      }
297
298      if (first === '*') {
299        if (esc) {
300          return esc + first + (rest ? star : '');
301        }
302        return star;
303      }
304      return esc ? m : `\\${m}`;
305    });
306
307    if (backslashes === true) {
308      if (opts.unescape === true) {
309        output = output.replace(/\\/g, '');
310      } else {
311        output = output.replace(/\\+/g, m => {
312          return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : '');
313        });
314      }
315    }
316
317    if (output === input && opts.contains === true) {
318      state.output = input;
319      return state;
320    }
321
322    state.output = utils.wrapOutput(output, state, options);
323    return state;
324  }
325
326  /**
327   * Tokenize input until we reach end-of-string
328   */
329
330  while (!eos()) {
331    value = advance();
332
333    if (value === '\u0000') {
334      continue;
335    }
336
337    /**
338     * Escaped characters
339     */
340
341    if (value === '\\') {
342      const next = peek();
343
344      if (next === '/' && opts.bash !== true) {
345        continue;
346      }
347
348      if (next === '.' || next === ';') {
349        continue;
350      }
351
352      if (!next) {
353        value += '\\';
354        push({ type: 'text', value });
355        continue;
356      }
357
358      // collapse slashes to reduce potential for exploits
359      const match = /^\\+/.exec(remaining());
360      let slashes = 0;
361
362      if (match && match[0].length > 2) {
363        slashes = match[0].length;
364        state.index += slashes;
365        if (slashes % 2 !== 0) {
366          value += '\\';
367        }
368      }
369
370      if (opts.unescape === true) {
371        value = advance();
372      } else {
373        value += advance();
374      }
375
376      if (state.brackets === 0) {
377        push({ type: 'text', value });
378        continue;
379      }
380    }
381
382    /**
383     * If we're inside a regex character class, continue
384     * until we reach the closing bracket.
385     */
386
387    if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) {
388      if (opts.posix !== false && value === ':') {
389        const inner = prev.value.slice(1);
390        if (inner.includes('[')) {
391          prev.posix = true;
392
393          if (inner.includes(':')) {
394            const idx = prev.value.lastIndexOf('[');
395            const pre = prev.value.slice(0, idx);
396            const rest = prev.value.slice(idx + 2);
397            const posix = POSIX_REGEX_SOURCE[rest];
398            if (posix) {
399              prev.value = pre + posix;
400              state.backtrack = true;
401              advance();
402
403              if (!bos.output && tokens.indexOf(prev) === 1) {
404                bos.output = ONE_CHAR;
405              }
406              continue;
407            }
408          }
409        }
410      }
411
412      if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) {
413        value = `\\${value}`;
414      }
415
416      if (value === ']' && (prev.value === '[' || prev.value === '[^')) {
417        value = `\\${value}`;
418      }
419
420      if (opts.posix === true && value === '!' && prev.value === '[') {
421        value = '^';
422      }
423
424      prev.value += value;
425      append({ value });
426      continue;
427    }
428
429    /**
430     * If we're inside a quoted string, continue
431     * until we reach the closing double quote.
432     */
433
434    if (state.quotes === 1 && value !== '"') {
435      value = utils.escapeRegex(value);
436      prev.value += value;
437      append({ value });
438      continue;
439    }
440
441    /**
442     * Double quotes
443     */
444
445    if (value === '"') {
446      state.quotes = state.quotes === 1 ? 0 : 1;
447      if (opts.keepQuotes === true) {
448        push({ type: 'text', value });
449      }
450      continue;
451    }
452
453    /**
454     * Parentheses
455     */
456
457    if (value === '(') {
458      increment('parens');
459      push({ type: 'paren', value });
460      continue;
461    }
462
463    if (value === ')') {
464      if (state.parens === 0 && opts.strictBrackets === true) {
465        throw new SyntaxError(syntaxError('opening', '('));
466      }
467
468      const extglob = extglobs[extglobs.length - 1];
469      if (extglob && state.parens === extglob.parens + 1) {
470        extglobClose(extglobs.pop());
471        continue;
472      }
473
474      push({ type: 'paren', value, output: state.parens ? ')' : '\\)' });
475      decrement('parens');
476      continue;
477    }
478
479    /**
480     * Square brackets
481     */
482
483    if (value === '[') {
484      if (opts.nobracket === true || !remaining().includes(']')) {
485        if (opts.nobracket !== true && opts.strictBrackets === true) {
486          throw new SyntaxError(syntaxError('closing', ']'));
487        }
488
489        value = `\\${value}`;
490      } else {
491        increment('brackets');
492      }
493
494      push({ type: 'bracket', value });
495      continue;
496    }
497
498    if (value === ']') {
499      if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) {
500        push({ type: 'text', value, output: `\\${value}` });
501        continue;
502      }
503
504      if (state.brackets === 0) {
505        if (opts.strictBrackets === true) {
506          throw new SyntaxError(syntaxError('opening', '['));
507        }
508
509        push({ type: 'text', value, output: `\\${value}` });
510        continue;
511      }
512
513      decrement('brackets');
514
515      const prevValue = prev.value.slice(1);
516      if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) {
517        value = `/${value}`;
518      }
519
520      prev.value += value;
521      append({ value });
522
523      // when literal brackets are explicitly disabled
524      // assume we should match with a regex character class
525      if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) {
526        continue;
527      }
528
529      const escaped = utils.escapeRegex(prev.value);
530      state.output = state.output.slice(0, -prev.value.length);
531
532      // when literal brackets are explicitly enabled
533      // assume we should escape the brackets to match literal characters
534      if (opts.literalBrackets === true) {
535        state.output += escaped;
536        prev.value = escaped;
537        continue;
538      }
539
540      // when the user specifies nothing, try to match both
541      prev.value = `(${capture}${escaped}|${prev.value})`;
542      state.output += prev.value;
543      continue;
544    }
545
546    /**
547     * Braces
548     */
549
550    if (value === '{' && opts.nobrace !== true) {
551      increment('braces');
552
553      const open = {
554        type: 'brace',
555        value,
556        output: '(',
557        outputIndex: state.output.length,
558        tokensIndex: state.tokens.length
559      };
560
561      braces.push(open);
562      push(open);
563      continue;
564    }
565
566    if (value === '}') {
567      const brace = braces[braces.length - 1];
568
569      if (opts.nobrace === true || !brace) {
570        push({ type: 'text', value, output: value });
571        continue;
572      }
573
574      let output = ')';
575
576      if (brace.dots === true) {
577        const arr = tokens.slice();
578        const range = [];
579
580        for (let i = arr.length - 1; i >= 0; i--) {
581          tokens.pop();
582          if (arr[i].type === 'brace') {
583            break;
584          }
585          if (arr[i].type !== 'dots') {
586            range.unshift(arr[i].value);
587          }
588        }
589
590        output = expandRange(range, opts);
591        state.backtrack = true;
592      }
593
594      if (brace.comma !== true && brace.dots !== true) {
595        const out = state.output.slice(0, brace.outputIndex);
596        const toks = state.tokens.slice(brace.tokensIndex);
597        brace.value = brace.output = '\\{';
598        value = output = '\\}';
599        state.output = out;
600        for (const t of toks) {
601          state.output += (t.output || t.value);
602        }
603      }
604
605      push({ type: 'brace', value, output });
606      decrement('braces');
607      braces.pop();
608      continue;
609    }
610
611    /**
612     * Pipes
613     */
614
615    if (value === '|') {
616      if (extglobs.length > 0) {
617        extglobs[extglobs.length - 1].conditions++;
618      }
619      push({ type: 'text', value });
620      continue;
621    }
622
623    /**
624     * Commas
625     */
626
627    if (value === ',') {
628      let output = value;
629
630      const brace = braces[braces.length - 1];
631      if (brace && stack[stack.length - 1] === 'braces') {
632        brace.comma = true;
633        output = '|';
634      }
635
636      push({ type: 'comma', value, output });
637      continue;
638    }
639
640    /**
641     * Slashes
642     */
643
644    if (value === '/') {
645      // if the beginning of the glob is "./", advance the start
646      // to the current index, and don't add the "./" characters
647      // to the state. This greatly simplifies lookbehinds when
648      // checking for BOS characters like "!" and "." (not "./")
649      if (prev.type === 'dot' && state.index === state.start + 1) {
650        state.start = state.index + 1;
651        state.consumed = '';
652        state.output = '';
653        tokens.pop();
654        prev = bos; // reset "prev" to the first token
655        continue;
656      }
657
658      push({ type: 'slash', value, output: SLASH_LITERAL });
659      continue;
660    }
661
662    /**
663     * Dots
664     */
665
666    if (value === '.') {
667      if (state.braces > 0 && prev.type === 'dot') {
668        if (prev.value === '.') prev.output = DOT_LITERAL;
669        const brace = braces[braces.length - 1];
670        prev.type = 'dots';
671        prev.output += value;
672        prev.value += value;
673        brace.dots = true;
674        continue;
675      }
676
677      if ((state.braces + state.parens) === 0 && prev.type !== 'bos' && prev.type !== 'slash') {
678        push({ type: 'text', value, output: DOT_LITERAL });
679        continue;
680      }
681
682      push({ type: 'dot', value, output: DOT_LITERAL });
683      continue;
684    }
685
686    /**
687     * Question marks
688     */
689
690    if (value === '?') {
691      const isGroup = prev && prev.value === '(';
692      if (!isGroup && opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
693        extglobOpen('qmark', value);
694        continue;
695      }
696
697      if (prev && prev.type === 'paren') {
698        const next = peek();
699        let output = value;
700
701        if ((prev.value === '(' && !/[!=<:]/.test(next)) || (next === '<' && !/<([!=]|\w+>)/.test(remaining()))) {
702          output = `\\${value}`;
703        }
704
705        push({ type: 'text', value, output });
706        continue;
707      }
708
709      if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) {
710        push({ type: 'qmark', value, output: QMARK_NO_DOT });
711        continue;
712      }
713
714      push({ type: 'qmark', value, output: QMARK });
715      continue;
716    }
717
718    /**
719     * Exclamation
720     */
721
722    if (value === '!') {
723      if (opts.noextglob !== true && peek() === '(') {
724        if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) {
725          extglobOpen('negate', value);
726          continue;
727        }
728      }
729
730      if (opts.nonegate !== true && state.index === 0) {
731        negate();
732        continue;
733      }
734    }
735
736    /**
737     * Plus
738     */
739
740    if (value === '+') {
741      if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
742        extglobOpen('plus', value);
743        continue;
744      }
745
746      if ((prev && prev.value === '(') || opts.regex === false) {
747        push({ type: 'plus', value, output: PLUS_LITERAL });
748        continue;
749      }
750
751      if ((prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) || state.parens > 0) {
752        push({ type: 'plus', value });
753        continue;
754      }
755
756      push({ type: 'plus', value: PLUS_LITERAL });
757      continue;
758    }
759
760    /**
761     * Plain text
762     */
763
764    if (value === '@') {
765      if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
766        push({ type: 'at', extglob: true, value, output: '' });
767        continue;
768      }
769
770      push({ type: 'text', value });
771      continue;
772    }
773
774    /**
775     * Plain text
776     */
777
778    if (value !== '*') {
779      if (value === '$' || value === '^') {
780        value = `\\${value}`;
781      }
782
783      const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
784      if (match) {
785        value += match[0];
786        state.index += match[0].length;
787      }
788
789      push({ type: 'text', value });
790      continue;
791    }
792
793    /**
794     * Stars
795     */
796
797    if (prev && (prev.type === 'globstar' || prev.star === true)) {
798      prev.type = 'star';
799      prev.star = true;
800      prev.value += value;
801      prev.output = star;
802      state.backtrack = true;
803      state.globstar = true;
804      consume(value);
805      continue;
806    }
807
808    let rest = remaining();
809    if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
810      extglobOpen('star', value);
811      continue;
812    }
813
814    if (prev.type === 'star') {
815      if (opts.noglobstar === true) {
816        consume(value);
817        continue;
818      }
819
820      const prior = prev.prev;
821      const before = prior.prev;
822      const isStart = prior.type === 'slash' || prior.type === 'bos';
823      const afterStar = before && (before.type === 'star' || before.type === 'globstar');
824
825      if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== '/'))) {
826        push({ type: 'star', value, output: '' });
827        continue;
828      }
829
830      const isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace');
831      const isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren');
832      if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) {
833        push({ type: 'star', value, output: '' });
834        continue;
835      }
836
837      // strip consecutive `/**/`
838      while (rest.slice(0, 3) === '/**') {
839        const after = input[state.index + 4];
840        if (after && after !== '/') {
841          break;
842        }
843        rest = rest.slice(3);
844        consume('/**', 3);
845      }
846
847      if (prior.type === 'bos' && eos()) {
848        prev.type = 'globstar';
849        prev.value += value;
850        prev.output = globstar(opts);
851        state.output = prev.output;
852        state.globstar = true;
853        consume(value);
854        continue;
855      }
856
857      if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) {
858        state.output = state.output.slice(0, -(prior.output + prev.output).length);
859        prior.output = `(?:${prior.output}`;
860
861        prev.type = 'globstar';
862        prev.output = globstar(opts) + (opts.strictSlashes ? ')' : '|$)');
863        prev.value += value;
864        state.globstar = true;
865        state.output += prior.output + prev.output;
866        consume(value);
867        continue;
868      }
869
870      if (prior.type === 'slash' && prior.prev.type !== 'bos' && rest[0] === '/') {
871        const end = rest[1] !== void 0 ? '|$' : '';
872
873        state.output = state.output.slice(0, -(prior.output + prev.output).length);
874        prior.output = `(?:${prior.output}`;
875
876        prev.type = 'globstar';
877        prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
878        prev.value += value;
879
880        state.output += prior.output + prev.output;
881        state.globstar = true;
882
883        consume(value + advance());
884
885        push({ type: 'slash', value: '/', output: '' });
886        continue;
887      }
888
889      if (prior.type === 'bos' && rest[0] === '/') {
890        prev.type = 'globstar';
891        prev.value += value;
892        prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
893        state.output = prev.output;
894        state.globstar = true;
895        consume(value + advance());
896        push({ type: 'slash', value: '/', output: '' });
897        continue;
898      }
899
900      // remove single star from output
901      state.output = state.output.slice(0, -prev.output.length);
902
903      // reset previous token to globstar
904      prev.type = 'globstar';
905      prev.output = globstar(opts);
906      prev.value += value;
907
908      // reset output with globstar
909      state.output += prev.output;
910      state.globstar = true;
911      consume(value);
912      continue;
913    }
914
915    const token = { type: 'star', value, output: star };
916
917    if (opts.bash === true) {
918      token.output = '.*?';
919      if (prev.type === 'bos' || prev.type === 'slash') {
920        token.output = nodot + token.output;
921      }
922      push(token);
923      continue;
924    }
925
926    if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) {
927      token.output = value;
928      push(token);
929      continue;
930    }
931
932    if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') {
933      if (prev.type === 'dot') {
934        state.output += NO_DOT_SLASH;
935        prev.output += NO_DOT_SLASH;
936
937      } else if (opts.dot === true) {
938        state.output += NO_DOTS_SLASH;
939        prev.output += NO_DOTS_SLASH;
940
941      } else {
942        state.output += nodot;
943        prev.output += nodot;
944      }
945
946      if (peek() !== '*') {
947        state.output += ONE_CHAR;
948        prev.output += ONE_CHAR;
949      }
950    }
951
952    push(token);
953  }
954
955  while (state.brackets > 0) {
956    if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']'));
957    state.output = utils.escapeLast(state.output, '[');
958    decrement('brackets');
959  }
960
961  while (state.parens > 0) {
962    if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')'));
963    state.output = utils.escapeLast(state.output, '(');
964    decrement('parens');
965  }
966
967  while (state.braces > 0) {
968    if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}'));
969    state.output = utils.escapeLast(state.output, '{');
970    decrement('braces');
971  }
972
973  if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) {
974    push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` });
975  }
976
977  // rebuild the output if we had to backtrack at any point
978  if (state.backtrack === true) {
979    state.output = '';
980
981    for (const token of state.tokens) {
982      state.output += token.output != null ? token.output : token.value;
983
984      if (token.suffix) {
985        state.output += token.suffix;
986      }
987    }
988  }
989
990  return state;
991};
992
993/**
994 * Fast paths for creating regular expressions for common glob patterns.
995 * This can significantly speed up processing and has very little downside
996 * impact when none of the fast paths match.
997 */
998
999parse.fastpaths = (input, options) => {
1000  const opts = { ...options };
1001  const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
1002  const len = input.length;
1003  if (len > max) {
1004    throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
1005  }
1006
1007  input = REPLACEMENTS[input] || input;
1008
1009  // create constants based on platform, for windows or posix
1010  const {
1011    DOT_LITERAL,
1012    SLASH_LITERAL,
1013    ONE_CHAR,
1014    DOTS_SLASH,
1015    NO_DOT,
1016    NO_DOTS,
1017    NO_DOTS_SLASH,
1018    STAR,
1019    START_ANCHOR
1020  } = constants.globChars(opts.windows);
1021
1022  const nodot = opts.dot ? NO_DOTS : NO_DOT;
1023  const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
1024  const capture = opts.capture ? '' : '?:';
1025  const state = { negated: false, prefix: '' };
1026  let star = opts.bash === true ? '.*?' : STAR;
1027
1028  if (opts.capture) {
1029    star = `(${star})`;
1030  }
1031
1032  const globstar = opts => {
1033    if (opts.noglobstar === true) return star;
1034    return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
1035  };
1036
1037  const create = str => {
1038    switch (str) {
1039      case '*':
1040        return `${nodot}${ONE_CHAR}${star}`;
1041
1042      case '.*':
1043        return `${DOT_LITERAL}${ONE_CHAR}${star}`;
1044
1045      case '*.*':
1046        return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
1047
1048      case '*/*':
1049        return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
1050
1051      case '**':
1052        return nodot + globstar(opts);
1053
1054      case '**/*':
1055        return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
1056
1057      case '**/*.*':
1058        return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
1059
1060      case '**/.*':
1061        return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
1062
1063      default: {
1064        const match = /^(.*?)\.(\w+)$/.exec(str);
1065        if (!match) return;
1066
1067        const source = create(match[1]);
1068        if (!source) return;
1069
1070        return source + DOT_LITERAL + match[2];
1071      }
1072    }
1073  };
1074
1075  const output = utils.removePrefix(input, state);
1076  let source = create(output);
1077
1078  if (source && opts.strictSlashes !== true) {
1079    source += `${SLASH_LITERAL}?`;
1080  }
1081
1082  return source;
1083};
1084
1085module.exports = parse;
1086 
codekingpro/portable-devtools · Team Ai