source: imaps-frontend/node_modules/picomatch/lib/parse.js

main
Last change on this file was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

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