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

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 33.2 KB
RevLine 
[9af201e]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
48const splitTopLevel = input => {
49 const parts = [];
50 let bracket = 0;
51 let paren = 0;
52 let quote = 0;
53 let value = '';
54 let escaped = false;
55
56 for (const ch of input) {
57 if (escaped === true) {
58 value += ch;
59 escaped = false;
60 continue;
61 }
62
63 if (ch === '\\') {
64 value += ch;
65 escaped = true;
66 continue;
67 }
68
69 if (ch === '"') {
70 quote = quote === 1 ? 0 : 1;
71 value += ch;
72 continue;
73 }
74
75 if (quote === 0) {
76 if (ch === '[') {
77 bracket++;
78 } else if (ch === ']' && bracket > 0) {
79 bracket--;
80 } else if (bracket === 0) {
81 if (ch === '(') {
82 paren++;
83 } else if (ch === ')' && paren > 0) {
84 paren--;
85 } else if (ch === '|' && paren === 0) {
86 parts.push(value);
87 value = '';
88 continue;
89 }
90 }
91 }
92
93 value += ch;
94 }
95
96 parts.push(value);
97 return parts;
98};
99
100const isPlainBranch = branch => {
101 let escaped = false;
102
103 for (const ch of branch) {
104 if (escaped === true) {
105 escaped = false;
106 continue;
107 }
108
109 if (ch === '\\') {
110 escaped = true;
111 continue;
112 }
113
114 if (/[?*+@!()[\]{}]/.test(ch)) {
115 return false;
116 }
117 }
118
119 return true;
120};
121
122const normalizeSimpleBranch = branch => {
123 let value = branch.trim();
124 let changed = true;
125
126 while (changed === true) {
127 changed = false;
128
129 if (/^@\([^\\()[\]{}|]+\)$/.test(value)) {
130 value = value.slice(2, -1);
131 changed = true;
132 }
133 }
134
135 if (!isPlainBranch(value)) {
136 return;
137 }
138
139 return value.replace(/\\(.)/g, '$1');
140};
141
142const hasRepeatedCharPrefixOverlap = branches => {
143 const values = branches.map(normalizeSimpleBranch).filter(Boolean);
144
145 for (let i = 0; i < values.length; i++) {
146 for (let j = i + 1; j < values.length; j++) {
147 const a = values[i];
148 const b = values[j];
149 const char = a[0];
150
151 if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) {
152 continue;
153 }
154
155 if (a === b || a.startsWith(b) || b.startsWith(a)) {
156 return true;
157 }
158 }
159 }
160
161 return false;
162};
163
164const parseRepeatedExtglob = (pattern, requireEnd = true) => {
165 if ((pattern[0] !== '+' && pattern[0] !== '*') || pattern[1] !== '(') {
166 return;
167 }
168
169 let bracket = 0;
170 let paren = 0;
171 let quote = 0;
172 let escaped = false;
173
174 for (let i = 1; i < pattern.length; i++) {
175 const ch = pattern[i];
176
177 if (escaped === true) {
178 escaped = false;
179 continue;
180 }
181
182 if (ch === '\\') {
183 escaped = true;
184 continue;
185 }
186
187 if (ch === '"') {
188 quote = quote === 1 ? 0 : 1;
189 continue;
190 }
191
192 if (quote === 1) {
193 continue;
194 }
195
196 if (ch === '[') {
197 bracket++;
198 continue;
199 }
200
201 if (ch === ']' && bracket > 0) {
202 bracket--;
203 continue;
204 }
205
206 if (bracket > 0) {
207 continue;
208 }
209
210 if (ch === '(') {
211 paren++;
212 continue;
213 }
214
215 if (ch === ')') {
216 paren--;
217
218 if (paren === 0) {
219 if (requireEnd === true && i !== pattern.length - 1) {
220 return;
221 }
222
223 return {
224 type: pattern[0],
225 body: pattern.slice(2, i),
226 end: i
227 };
228 }
229 }
230 }
231};
232
233const getStarExtglobSequenceOutput = pattern => {
234 let index = 0;
235 const chars = [];
236
237 while (index < pattern.length) {
238 const match = parseRepeatedExtglob(pattern.slice(index), false);
239
240 if (!match || match.type !== '*') {
241 return;
242 }
243
244 const branches = splitTopLevel(match.body).map(branch => branch.trim());
245 if (branches.length !== 1) {
246 return;
247 }
248
249 const branch = normalizeSimpleBranch(branches[0]);
250 if (!branch || branch.length !== 1) {
251 return;
252 }
253
254 chars.push(branch);
255 index += match.end + 1;
256 }
257
258 if (chars.length < 1) {
259 return;
260 }
261
262 const source = chars.length === 1
263 ? utils.escapeRegex(chars[0])
264 : `[${chars.map(ch => utils.escapeRegex(ch)).join('')}]`;
265
266 return `${source}*`;
267};
268
269const repeatedExtglobRecursion = pattern => {
270 let depth = 0;
271 let value = pattern.trim();
272 let match = parseRepeatedExtglob(value);
273
274 while (match) {
275 depth++;
276 value = match.body.trim();
277 match = parseRepeatedExtglob(value);
278 }
279
280 return depth;
281};
282
283const analyzeRepeatedExtglob = (body, options) => {
284 if (options.maxExtglobRecursion === false) {
285 return { risky: false };
286 }
287
288 const max =
289 typeof options.maxExtglobRecursion === 'number'
290 ? options.maxExtglobRecursion
291 : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
292
293 const branches = splitTopLevel(body).map(branch => branch.trim());
294
295 if (branches.length > 1) {
296 if (
297 branches.some(branch => branch === '') ||
298 branches.some(branch => /^[*?]+$/.test(branch)) ||
299 hasRepeatedCharPrefixOverlap(branches)
300 ) {
301 return { risky: true };
302 }
303 }
304
305 for (const branch of branches) {
306 const safeOutput = getStarExtglobSequenceOutput(branch);
307 if (safeOutput) {
308 return { risky: true, safeOutput };
309 }
310
311 if (repeatedExtglobRecursion(branch) > max) {
312 return { risky: true };
313 }
314 }
315
316 return { risky: false };
317};
318
319/**
320 * Parse the given input string.
321 * @param {String} input
322 * @param {Object} options
323 * @return {Object}
324 */
325
326const parse = (input, options) => {
327 if (typeof input !== 'string') {
328 throw new TypeError('Expected a string');
329 }
330
331 input = REPLACEMENTS[input] || input;
332
333 const opts = { ...options };
334 const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
335
336 let len = input.length;
337 if (len > max) {
338 throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
339 }
340
341 const bos = { type: 'bos', value: '', output: opts.prepend || '' };
342 const tokens = [bos];
343
344 const capture = opts.capture ? '' : '?:';
345 const win32 = utils.isWindows(options);
346
347 // create constants based on platform, for windows or posix
348 const PLATFORM_CHARS = constants.globChars(win32);
349 const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
350
351 const {
352 DOT_LITERAL,
353 PLUS_LITERAL,
354 SLASH_LITERAL,
355 ONE_CHAR,
356 DOTS_SLASH,
357 NO_DOT,
358 NO_DOT_SLASH,
359 NO_DOTS_SLASH,
360 QMARK,
361 QMARK_NO_DOT,
362 STAR,
363 START_ANCHOR
364 } = PLATFORM_CHARS;
365
366 const globstar = opts => {
367 return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
368 };
369
370 const nodot = opts.dot ? '' : NO_DOT;
371 const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT;
372 let star = opts.bash === true ? globstar(opts) : STAR;
373
374 if (opts.capture) {
375 star = `(${star})`;
376 }
377
378 // minimatch options support
379 if (typeof opts.noext === 'boolean') {
380 opts.noextglob = opts.noext;
381 }
382
383 const state = {
384 input,
385 index: -1,
386 start: 0,
387 dot: opts.dot === true,
388 consumed: '',
389 output: '',
390 prefix: '',
391 backtrack: false,
392 negated: false,
393 brackets: 0,
394 braces: 0,
395 parens: 0,
396 quotes: 0,
397 globstar: false,
398 tokens
399 };
400
401 input = utils.removePrefix(input, state);
402 len = input.length;
403
404 const extglobs = [];
405 const braces = [];
406 const stack = [];
407 let prev = bos;
408 let value;
409
410 /**
411 * Tokenizing helpers
412 */
413
414 const eos = () => state.index === len - 1;
415 const peek = state.peek = (n = 1) => input[state.index + n];
416 const advance = state.advance = () => input[++state.index] || '';
417 const remaining = () => input.slice(state.index + 1);
418 const consume = (value = '', num = 0) => {
419 state.consumed += value;
420 state.index += num;
421 };
422
423 const append = token => {
424 state.output += token.output != null ? token.output : token.value;
425 consume(token.value);
426 };
427
428 const negate = () => {
429 let count = 1;
430
431 while (peek() === '!' && (peek(2) !== '(' || peek(3) === '?')) {
432 advance();
433 state.start++;
434 count++;
435 }
436
437 if (count % 2 === 0) {
438 return false;
439 }
440
441 state.negated = true;
442 state.start++;
443 return true;
444 };
445
446 const increment = type => {
447 state[type]++;
448 stack.push(type);
449 };
450
451 const decrement = type => {
452 state[type]--;
453 stack.pop();
454 };
455
456 /**
457 * Push tokens onto the tokens array. This helper speeds up
458 * tokenizing by 1) helping us avoid backtracking as much as possible,
459 * and 2) helping us avoid creating extra tokens when consecutive
460 * characters are plain text. This improves performance and simplifies
461 * lookbehinds.
462 */
463
464 const push = tok => {
465 if (prev.type === 'globstar') {
466 const isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace');
467 const isExtglob = tok.extglob === true || (extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'));
468
469 if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) {
470 state.output = state.output.slice(0, -prev.output.length);
471 prev.type = 'star';
472 prev.value = '*';
473 prev.output = star;
474 state.output += prev.output;
475 }
476 }
477
478 if (extglobs.length && tok.type !== 'paren') {
479 extglobs[extglobs.length - 1].inner += tok.value;
480 }
481
482 if (tok.value || tok.output) append(tok);
483 if (prev && prev.type === 'text' && tok.type === 'text') {
484 prev.value += tok.value;
485 prev.output = (prev.output || '') + tok.value;
486 return;
487 }
488
489 tok.prev = prev;
490 tokens.push(tok);
491 prev = tok;
492 };
493
494 const extglobOpen = (type, value) => {
495 const token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' };
496
497 token.prev = prev;
498 token.parens = state.parens;
499 token.output = state.output;
500 token.startIndex = state.index;
501 token.tokensIndex = tokens.length;
502 const output = (opts.capture ? '(' : '') + token.open;
503
504 increment('parens');
505 push({ type, value, output: state.output ? '' : ONE_CHAR });
506 push({ type: 'paren', extglob: true, value: advance(), output });
507 extglobs.push(token);
508 };
509
510 const extglobClose = token => {
511 const literal = input.slice(token.startIndex, state.index + 1);
512 const body = input.slice(token.startIndex + 2, state.index);
513 const analysis = analyzeRepeatedExtglob(body, opts);
514
515 if ((token.type === 'plus' || token.type === 'star') && analysis.risky) {
516 const safeOutput = analysis.safeOutput
517 ? (token.output ? '' : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput)
518 : undefined;
519 const open = tokens[token.tokensIndex];
520
521 open.type = 'text';
522 open.value = literal;
523 open.output = safeOutput || utils.escapeRegex(literal);
524
525 for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
526 tokens[i].value = '';
527 tokens[i].output = '';
528 delete tokens[i].suffix;
529 }
530
531 state.output = token.output + open.output;
532 state.backtrack = true;
533
534 push({ type: 'paren', extglob: true, value, output: '' });
535 decrement('parens');
536 return;
537 }
538
539 let output = token.close + (opts.capture ? ')' : '');
540 let rest;
541
542 if (token.type === 'negate') {
543 let extglobStar = star;
544
545 if (token.inner && token.inner.length > 1 && token.inner.includes('/')) {
546 extglobStar = globstar(opts);
547 }
548
549 if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) {
550 output = token.close = `)$))${extglobStar}`;
551 }
552
553 if (token.inner.includes('*') && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) {
554 // Any non-magical string (`.ts`) or even nested expression (`.{ts,tsx}`) can follow after the closing parenthesis.
555 // In this case, we need to parse the string and use it in the output of the original pattern.
556 // Suitable patterns: `/!(*.d).ts`, `/!(*.d).{ts,tsx}`, `**/!(*-dbg).@(js)`.
557 //
558 // Disabling the `fastpaths` option due to a problem with parsing strings as `.ts` in the pattern like `**/!(*.d).ts`.
559 const expression = parse(rest, { ...options, fastpaths: false }).output;
560
561 output = token.close = `)${expression})${extglobStar})`;
562 }
563
564 if (token.prev.type === 'bos') {
565 state.negatedExtglob = true;
566 }
567 }
568
569 push({ type: 'paren', extglob: true, value, output });
570 decrement('parens');
571 };
572
573 /**
574 * Fast paths
575 */
576
577 if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
578 let backslashes = false;
579
580 let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
581 if (first === '\\') {
582 backslashes = true;
583 return m;
584 }
585
586 if (first === '?') {
587 if (esc) {
588 return esc + first + (rest ? QMARK.repeat(rest.length) : '');
589 }
590 if (index === 0) {
591 return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : '');
592 }
593 return QMARK.repeat(chars.length);
594 }
595
596 if (first === '.') {
597 return DOT_LITERAL.repeat(chars.length);
598 }
599
600 if (first === '*') {
601 if (esc) {
602 return esc + first + (rest ? star : '');
603 }
604 return star;
605 }
606 return esc ? m : `\\${m}`;
607 });
608
609 if (backslashes === true) {
610 if (opts.unescape === true) {
611 output = output.replace(/\\/g, '');
612 } else {
613 output = output.replace(/\\+/g, m => {
614 return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : '');
615 });
616 }
617 }
618
619 if (output === input && opts.contains === true) {
620 state.output = input;
621 return state;
622 }
623
624 state.output = utils.wrapOutput(output, state, options);
625 return state;
626 }
627
628 /**
629 * Tokenize input until we reach end-of-string
630 */
631
632 while (!eos()) {
633 value = advance();
634
635 if (value === '\u0000') {
636 continue;
637 }
638
639 /**
640 * Escaped characters
641 */
642
643 if (value === '\\') {
644 const next = peek();
645
646 if (next === '/' && opts.bash !== true) {
647 continue;
648 }
649
650 if (next === '.' || next === ';') {
651 continue;
652 }
653
654 if (!next) {
655 value += '\\';
656 push({ type: 'text', value });
657 continue;
658 }
659
660 // collapse slashes to reduce potential for exploits
661 const match = /^\\+/.exec(remaining());
662 let slashes = 0;
663
664 if (match && match[0].length > 2) {
665 slashes = match[0].length;
666 state.index += slashes;
667 if (slashes % 2 !== 0) {
668 value += '\\';
669 }
670 }
671
672 if (opts.unescape === true) {
673 value = advance();
674 } else {
675 value += advance();
676 }
677
678 if (state.brackets === 0) {
679 push({ type: 'text', value });
680 continue;
681 }
682 }
683
684 /**
685 * If we're inside a regex character class, continue
686 * until we reach the closing bracket.
687 */
688
689 if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) {
690 if (opts.posix !== false && value === ':') {
691 const inner = prev.value.slice(1);
692 if (inner.includes('[')) {
693 prev.posix = true;
694
695 if (inner.includes(':')) {
696 const idx = prev.value.lastIndexOf('[');
697 const pre = prev.value.slice(0, idx);
698 const rest = prev.value.slice(idx + 2);
699 const posix = POSIX_REGEX_SOURCE[rest];
700 if (posix) {
701 prev.value = pre + posix;
702 state.backtrack = true;
703 advance();
704
705 if (!bos.output && tokens.indexOf(prev) === 1) {
706 bos.output = ONE_CHAR;
707 }
708 continue;
709 }
710 }
711 }
712 }
713
714 if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) {
715 value = `\\${value}`;
716 }
717
718 if (value === ']' && (prev.value === '[' || prev.value === '[^')) {
719 value = `\\${value}`;
720 }
721
722 if (opts.posix === true && value === '!' && prev.value === '[') {
723 value = '^';
724 }
725
726 prev.value += value;
727 append({ value });
728 continue;
729 }
730
731 /**
732 * If we're inside a quoted string, continue
733 * until we reach the closing double quote.
734 */
735
736 if (state.quotes === 1 && value !== '"') {
737 value = utils.escapeRegex(value);
738 prev.value += value;
739 append({ value });
740 continue;
741 }
742
743 /**
744 * Double quotes
745 */
746
747 if (value === '"') {
748 state.quotes = state.quotes === 1 ? 0 : 1;
749 if (opts.keepQuotes === true) {
750 push({ type: 'text', value });
751 }
752 continue;
753 }
754
755 /**
756 * Parentheses
757 */
758
759 if (value === '(') {
760 increment('parens');
761 push({ type: 'paren', value });
762 continue;
763 }
764
765 if (value === ')') {
766 if (state.parens === 0 && opts.strictBrackets === true) {
767 throw new SyntaxError(syntaxError('opening', '('));
768 }
769
770 const extglob = extglobs[extglobs.length - 1];
771 if (extglob && state.parens === extglob.parens + 1) {
772 extglobClose(extglobs.pop());
773 continue;
774 }
775
776 push({ type: 'paren', value, output: state.parens ? ')' : '\\)' });
777 decrement('parens');
778 continue;
779 }
780
781 /**
782 * Square brackets
783 */
784
785 if (value === '[') {
786 if (opts.nobracket === true || !remaining().includes(']')) {
787 if (opts.nobracket !== true && opts.strictBrackets === true) {
788 throw new SyntaxError(syntaxError('closing', ']'));
789 }
790
791 value = `\\${value}`;
792 } else {
793 increment('brackets');
794 }
795
796 push({ type: 'bracket', value });
797 continue;
798 }
799
800 if (value === ']') {
801 if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) {
802 push({ type: 'text', value, output: `\\${value}` });
803 continue;
804 }
805
806 if (state.brackets === 0) {
807 if (opts.strictBrackets === true) {
808 throw new SyntaxError(syntaxError('opening', '['));
809 }
810
811 push({ type: 'text', value, output: `\\${value}` });
812 continue;
813 }
814
815 decrement('brackets');
816
817 const prevValue = prev.value.slice(1);
818 if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) {
819 value = `/${value}`;
820 }
821
822 prev.value += value;
823 append({ value });
824
825 // when literal brackets are explicitly disabled
826 // assume we should match with a regex character class
827 if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) {
828 continue;
829 }
830
831 const escaped = utils.escapeRegex(prev.value);
832 state.output = state.output.slice(0, -prev.value.length);
833
834 // when literal brackets are explicitly enabled
835 // assume we should escape the brackets to match literal characters
836 if (opts.literalBrackets === true) {
837 state.output += escaped;
838 prev.value = escaped;
839 continue;
840 }
841
842 // when the user specifies nothing, try to match both
843 prev.value = `(${capture}${escaped}|${prev.value})`;
844 state.output += prev.value;
845 continue;
846 }
847
848 /**
849 * Braces
850 */
851
852 if (value === '{' && opts.nobrace !== true) {
853 increment('braces');
854
855 const open = {
856 type: 'brace',
857 value,
858 output: '(',
859 outputIndex: state.output.length,
860 tokensIndex: state.tokens.length
861 };
862
863 braces.push(open);
864 push(open);
865 continue;
866 }
867
868 if (value === '}') {
869 const brace = braces[braces.length - 1];
870
871 if (opts.nobrace === true || !brace) {
872 push({ type: 'text', value, output: value });
873 continue;
874 }
875
876 let output = ')';
877
878 if (brace.dots === true) {
879 const arr = tokens.slice();
880 const range = [];
881
882 for (let i = arr.length - 1; i >= 0; i--) {
883 tokens.pop();
884 if (arr[i].type === 'brace') {
885 break;
886 }
887 if (arr[i].type !== 'dots') {
888 range.unshift(arr[i].value);
889 }
890 }
891
892 output = expandRange(range, opts);
893 state.backtrack = true;
894 }
895
896 if (brace.comma !== true && brace.dots !== true) {
897 const out = state.output.slice(0, brace.outputIndex);
898 const toks = state.tokens.slice(brace.tokensIndex);
899 brace.value = brace.output = '\\{';
900 value = output = '\\}';
901 state.output = out;
902 for (const t of toks) {
903 state.output += (t.output || t.value);
904 }
905 }
906
907 push({ type: 'brace', value, output });
908 decrement('braces');
909 braces.pop();
910 continue;
911 }
912
913 /**
914 * Pipes
915 */
916
917 if (value === '|') {
918 if (extglobs.length > 0) {
919 extglobs[extglobs.length - 1].conditions++;
920 }
921 push({ type: 'text', value });
922 continue;
923 }
924
925 /**
926 * Commas
927 */
928
929 if (value === ',') {
930 let output = value;
931
932 const brace = braces[braces.length - 1];
933 if (brace && stack[stack.length - 1] === 'braces') {
934 brace.comma = true;
935 output = '|';
936 }
937
938 push({ type: 'comma', value, output });
939 continue;
940 }
941
942 /**
943 * Slashes
944 */
945
946 if (value === '/') {
947 // if the beginning of the glob is "./", advance the start
948 // to the current index, and don't add the "./" characters
949 // to the state. This greatly simplifies lookbehinds when
950 // checking for BOS characters like "!" and "." (not "./")
951 if (prev.type === 'dot' && state.index === state.start + 1) {
952 state.start = state.index + 1;
953 state.consumed = '';
954 state.output = '';
955 tokens.pop();
956 prev = bos; // reset "prev" to the first token
957 continue;
958 }
959
960 push({ type: 'slash', value, output: SLASH_LITERAL });
961 continue;
962 }
963
964 /**
965 * Dots
966 */
967
968 if (value === '.') {
969 if (state.braces > 0 && prev.type === 'dot') {
970 if (prev.value === '.') prev.output = DOT_LITERAL;
971 const brace = braces[braces.length - 1];
972 prev.type = 'dots';
973 prev.output += value;
974 prev.value += value;
975 brace.dots = true;
976 continue;
977 }
978
979 if ((state.braces + state.parens) === 0 && prev.type !== 'bos' && prev.type !== 'slash') {
980 push({ type: 'text', value, output: DOT_LITERAL });
981 continue;
982 }
983
984 push({ type: 'dot', value, output: DOT_LITERAL });
985 continue;
986 }
987
988 /**
989 * Question marks
990 */
991
992 if (value === '?') {
993 const isGroup = prev && prev.value === '(';
994 if (!isGroup && opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
995 extglobOpen('qmark', value);
996 continue;
997 }
998
999 if (prev && prev.type === 'paren') {
1000 const next = peek();
1001 let output = value;
1002
1003 if (next === '<' && !utils.supportsLookbehinds()) {
1004 throw new Error('Node.js v10 or higher is required for regex lookbehinds');
1005 }
1006
1007 if ((prev.value === '(' && !/[!=<:]/.test(next)) || (next === '<' && !/<([!=]|\w+>)/.test(remaining()))) {
1008 output = `\\${value}`;
1009 }
1010
1011 push({ type: 'text', value, output });
1012 continue;
1013 }
1014
1015 if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) {
1016 push({ type: 'qmark', value, output: QMARK_NO_DOT });
1017 continue;
1018 }
1019
1020 push({ type: 'qmark', value, output: QMARK });
1021 continue;
1022 }
1023
1024 /**
1025 * Exclamation
1026 */
1027
1028 if (value === '!') {
1029 if (opts.noextglob !== true && peek() === '(') {
1030 if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) {
1031 extglobOpen('negate', value);
1032 continue;
1033 }
1034 }
1035
1036 if (opts.nonegate !== true && state.index === 0) {
1037 negate();
1038 continue;
1039 }
1040 }
1041
1042 /**
1043 * Plus
1044 */
1045
1046 if (value === '+') {
1047 if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
1048 extglobOpen('plus', value);
1049 continue;
1050 }
1051
1052 if ((prev && prev.value === '(') || opts.regex === false) {
1053 push({ type: 'plus', value, output: PLUS_LITERAL });
1054 continue;
1055 }
1056
1057 if ((prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) || state.parens > 0) {
1058 push({ type: 'plus', value });
1059 continue;
1060 }
1061
1062 push({ type: 'plus', value: PLUS_LITERAL });
1063 continue;
1064 }
1065
1066 /**
1067 * Plain text
1068 */
1069
1070 if (value === '@') {
1071 if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
1072 push({ type: 'at', extglob: true, value, output: '' });
1073 continue;
1074 }
1075
1076 push({ type: 'text', value });
1077 continue;
1078 }
1079
1080 /**
1081 * Plain text
1082 */
1083
1084 if (value !== '*') {
1085 if (value === '$' || value === '^') {
1086 value = `\\${value}`;
1087 }
1088
1089 const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
1090 if (match) {
1091 value += match[0];
1092 state.index += match[0].length;
1093 }
1094
1095 push({ type: 'text', value });
1096 continue;
1097 }
1098
1099 /**
1100 * Stars
1101 */
1102
1103 if (prev && (prev.type === 'globstar' || prev.star === true)) {
1104 prev.type = 'star';
1105 prev.star = true;
1106 prev.value += value;
1107 prev.output = star;
1108 state.backtrack = true;
1109 state.globstar = true;
1110 consume(value);
1111 continue;
1112 }
1113
1114 let rest = remaining();
1115 if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
1116 extglobOpen('star', value);
1117 continue;
1118 }
1119
1120 if (prev.type === 'star') {
1121 if (opts.noglobstar === true) {
1122 consume(value);
1123 continue;
1124 }
1125
1126 const prior = prev.prev;
1127 const before = prior.prev;
1128 const isStart = prior.type === 'slash' || prior.type === 'bos';
1129 const afterStar = before && (before.type === 'star' || before.type === 'globstar');
1130
1131 if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== '/'))) {
1132 push({ type: 'star', value, output: '' });
1133 continue;
1134 }
1135
1136 const isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace');
1137 const isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren');
1138 if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) {
1139 push({ type: 'star', value, output: '' });
1140 continue;
1141 }
1142
1143 // strip consecutive `/**/`
1144 while (rest.slice(0, 3) === '/**') {
1145 const after = input[state.index + 4];
1146 if (after && after !== '/') {
1147 break;
1148 }
1149 rest = rest.slice(3);
1150 consume('/**', 3);
1151 }
1152
1153 if (prior.type === 'bos' && eos()) {
1154 prev.type = 'globstar';
1155 prev.value += value;
1156 prev.output = globstar(opts);
1157 state.output = prev.output;
1158 state.globstar = true;
1159 consume(value);
1160 continue;
1161 }
1162
1163 if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) {
1164 state.output = state.output.slice(0, -(prior.output + prev.output).length);
1165 prior.output = `(?:${prior.output}`;
1166
1167 prev.type = 'globstar';
1168 prev.output = globstar(opts) + (opts.strictSlashes ? ')' : '|$)');
1169 prev.value += value;
1170 state.globstar = true;
1171 state.output += prior.output + prev.output;
1172 consume(value);
1173 continue;
1174 }
1175
1176 if (prior.type === 'slash' && prior.prev.type !== 'bos' && rest[0] === '/') {
1177 const end = rest[1] !== void 0 ? '|$' : '';
1178
1179 state.output = state.output.slice(0, -(prior.output + prev.output).length);
1180 prior.output = `(?:${prior.output}`;
1181
1182 prev.type = 'globstar';
1183 prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
1184 prev.value += value;
1185
1186 state.output += prior.output + prev.output;
1187 state.globstar = true;
1188
1189 consume(value + advance());
1190
1191 push({ type: 'slash', value: '/', output: '' });
1192 continue;
1193 }
1194
1195 if (prior.type === 'bos' && rest[0] === '/') {
1196 prev.type = 'globstar';
1197 prev.value += value;
1198 prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
1199 state.output = prev.output;
1200 state.globstar = true;
1201 consume(value + advance());
1202 push({ type: 'slash', value: '/', output: '' });
1203 continue;
1204 }
1205
1206 // remove single star from output
1207 state.output = state.output.slice(0, -prev.output.length);
1208
1209 // reset previous token to globstar
1210 prev.type = 'globstar';
1211 prev.output = globstar(opts);
1212 prev.value += value;
1213
1214 // reset output with globstar
1215 state.output += prev.output;
1216 state.globstar = true;
1217 consume(value);
1218 continue;
1219 }
1220
1221 const token = { type: 'star', value, output: star };
1222
1223 if (opts.bash === true) {
1224 token.output = '.*?';
1225 if (prev.type === 'bos' || prev.type === 'slash') {
1226 token.output = nodot + token.output;
1227 }
1228 push(token);
1229 continue;
1230 }
1231
1232 if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) {
1233 token.output = value;
1234 push(token);
1235 continue;
1236 }
1237
1238 if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') {
1239 if (prev.type === 'dot') {
1240 state.output += NO_DOT_SLASH;
1241 prev.output += NO_DOT_SLASH;
1242
1243 } else if (opts.dot === true) {
1244 state.output += NO_DOTS_SLASH;
1245 prev.output += NO_DOTS_SLASH;
1246
1247 } else {
1248 state.output += nodot;
1249 prev.output += nodot;
1250 }
1251
1252 if (peek() !== '*') {
1253 state.output += ONE_CHAR;
1254 prev.output += ONE_CHAR;
1255 }
1256 }
1257
1258 push(token);
1259 }
1260
1261 while (state.brackets > 0) {
1262 if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']'));
1263 state.output = utils.escapeLast(state.output, '[');
1264 decrement('brackets');
1265 }
1266
1267 while (state.parens > 0) {
1268 if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')'));
1269 state.output = utils.escapeLast(state.output, '(');
1270 decrement('parens');
1271 }
1272
1273 while (state.braces > 0) {
1274 if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}'));
1275 state.output = utils.escapeLast(state.output, '{');
1276 decrement('braces');
1277 }
1278
1279 if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) {
1280 push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` });
1281 }
1282
1283 // rebuild the output if we had to backtrack at any point
1284 if (state.backtrack === true) {
1285 state.output = '';
1286
1287 for (const token of state.tokens) {
1288 state.output += token.output != null ? token.output : token.value;
1289
1290 if (token.suffix) {
1291 state.output += token.suffix;
1292 }
1293 }
1294 }
1295
1296 return state;
1297};
1298
1299/**
1300 * Fast paths for creating regular expressions for common glob patterns.
1301 * This can significantly speed up processing and has very little downside
1302 * impact when none of the fast paths match.
1303 */
1304
1305parse.fastpaths = (input, options) => {
1306 const opts = { ...options };
1307 const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
1308 const len = input.length;
1309 if (len > max) {
1310 throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
1311 }
1312
1313 input = REPLACEMENTS[input] || input;
1314 const win32 = utils.isWindows(options);
1315
1316 // create constants based on platform, for windows or posix
1317 const {
1318 DOT_LITERAL,
1319 SLASH_LITERAL,
1320 ONE_CHAR,
1321 DOTS_SLASH,
1322 NO_DOT,
1323 NO_DOTS,
1324 NO_DOTS_SLASH,
1325 STAR,
1326 START_ANCHOR
1327 } = constants.globChars(win32);
1328
1329 const nodot = opts.dot ? NO_DOTS : NO_DOT;
1330 const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
1331 const capture = opts.capture ? '' : '?:';
1332 const state = { negated: false, prefix: '' };
1333 let star = opts.bash === true ? '.*?' : STAR;
1334
1335 if (opts.capture) {
1336 star = `(${star})`;
1337 }
1338
1339 const globstar = opts => {
1340 if (opts.noglobstar === true) return star;
1341 return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
1342 };
1343
1344 const create = str => {
1345 switch (str) {
1346 case '*':
1347 return `${nodot}${ONE_CHAR}${star}`;
1348
1349 case '.*':
1350 return `${DOT_LITERAL}${ONE_CHAR}${star}`;
1351
1352 case '*.*':
1353 return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
1354
1355 case '*/*':
1356 return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
1357
1358 case '**':
1359 return nodot + globstar(opts);
1360
1361 case '**/*':
1362 return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
1363
1364 case '**/*.*':
1365 return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
1366
1367 case '**/.*':
1368 return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
1369
1370 default: {
1371 const match = /^(.*?)\.(\w+)$/.exec(str);
1372 if (!match) return;
1373
1374 const source = create(match[1]);
1375 if (!source) return;
1376
1377 return source + DOT_LITERAL + match[2];
1378 }
1379 }
1380 };
1381
1382 const output = utils.removePrefix(input, state);
1383 let source = create(output);
1384
1385 if (source && opts.strictSlashes !== true) {
1386 source += `${SLASH_LITERAL}?`;
1387 }
1388
1389 return source;
1390};
1391
1392module.exports = parse;
Note: See TracBrowser for help on using the repository browser.