codekingpro/portable-devtools
115k
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 