source: trip-planner-front/node_modules/picomatch/lib/parse.js@ bdd6491

Last change on this file since bdd6491 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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