source: node_modules/minimatch/dist/esm/ast.js

main
Last change on this file was 33517cc, checked in by Klimentina Efremova <klimentina08642@…>, 11 days ago

Turned database from SQLite to PostgressSQL, updated database changes from Phase 1 and 2

  • Property mode set to 100644
File size: 29.7 KB
RevLine 
[81bc7da]1// parse a single path portion
[33517cc]2var _a;
[81bc7da]3import { parseClass } from './brace-expressions.js';
4import { unescape } from './unescape.js';
5const types = new Set(['!', '?', '+', '*', '@']);
6const isExtglobType = (c) => types.has(c);
[33517cc]7const isExtglobAST = (c) => isExtglobType(c.type);
8// Map of which extglob types can adopt the children of a nested extglob
9//
10// anything but ! can adopt a matching type:
11// +(a|+(b|c)|d) => +(a|b|c|d)
12// *(a|*(b|c)|d) => *(a|b|c|d)
13// @(a|@(b|c)|d) => @(a|b|c|d)
14// ?(a|?(b|c)|d) => ?(a|b|c|d)
15//
16// * can adopt anything, because 0 or repetition is allowed
17// *(a|?(b|c)|d) => *(a|b|c|d)
18// *(a|+(b|c)|d) => *(a|b|c|d)
19// *(a|@(b|c)|d) => *(a|b|c|d)
20//
21// + can adopt @, because 1 or repetition is allowed
22// +(a|@(b|c)|d) => +(a|b|c|d)
23//
24// + and @ CANNOT adopt *, because 0 would be allowed
25// +(a|*(b|c)|d) => would match "", on *(b|c)
26// @(a|*(b|c)|d) => would match "", on *(b|c)
27//
28// + and @ CANNOT adopt ?, because 0 would be allowed
29// +(a|?(b|c)|d) => would match "", on ?(b|c)
30// @(a|?(b|c)|d) => would match "", on ?(b|c)
31//
32// ? can adopt @, because 0 or 1 is allowed
33// ?(a|@(b|c)|d) => ?(a|b|c|d)
34//
35// ? and @ CANNOT adopt * or +, because >1 would be allowed
36// ?(a|*(b|c)|d) => would match bbb on *(b|c)
37// @(a|*(b|c)|d) => would match bbb on *(b|c)
38// ?(a|+(b|c)|d) => would match bbb on +(b|c)
39// @(a|+(b|c)|d) => would match bbb on +(b|c)
40//
41// ! CANNOT adopt ! (nothing else can either)
42// !(a|!(b|c)|d) => !(a|b|c|d) would fail to match on b (not not b|c)
43//
44// ! can adopt @
45// !(a|@(b|c)|d) => !(a|b|c|d)
46//
47// ! CANNOT adopt *
48// !(a|*(b|c)|d) => !(a|b|c|d) would match on bbb, not allowed
49//
50// ! CANNOT adopt +
51// !(a|+(b|c)|d) => !(a|b|c|d) would match on bbb, not allowed
52//
53// ! CANNOT adopt ?
54// x!(a|?(b|c)|d) => x!(a|b|c|d) would fail to match "x"
55const adoptionMap = new Map([
56 ['!', ['@']],
57 ['?', ['?', '@']],
58 ['@', ['@']],
59 ['*', ['*', '+', '?', '@']],
60 ['+', ['+', '@']],
61]);
62// nested extglobs that can be adopted in, but with the addition of
63// a blank '' element.
64const adoptionWithSpaceMap = new Map([
65 ['!', ['?']],
66 ['@', ['?']],
67 ['+', ['?', '*']],
68]);
69// union of the previous two maps
70const adoptionAnyMap = new Map([
71 ['!', ['?', '@']],
72 ['?', ['?', '@']],
73 ['@', ['?', '@']],
74 ['*', ['*', '+', '?', '@']],
75 ['+', ['+', '@', '?', '*']],
76]);
77// Extglobs that can take over their parent if they are the only child
78// the key is parent, value maps child to resulting extglob parent type
79// '@' is omitted because it's a special case. An `@` extglob with a single
80// member can always be usurped by that subpattern.
81const usurpMap = new Map([
82 ['!', new Map([['!', '@']])],
83 [
84 '?',
85 new Map([
86 ['*', '*'],
87 ['+', '*'],
88 ]),
89 ],
90 [
91 '@',
92 new Map([
93 ['!', '!'],
94 ['?', '?'],
95 ['@', '@'],
96 ['*', '*'],
97 ['+', '+'],
98 ]),
99 ],
100 [
101 '+',
102 new Map([
103 ['?', '*'],
104 ['*', '*'],
105 ]),
106 ],
107]);
[81bc7da]108// Patterns that get prepended to bind to the start of either the
109// entire string, or just a single path portion, to prevent dots
110// and/or traversal patterns, when needed.
111// Exts don't need the ^ or / bit, because the root binds that already.
112const startNoTraversal = '(?!(?:^|/)\\.\\.?(?:$|/))';
113const startNoDot = '(?!\\.)';
114// characters that indicate a start of pattern needs the "no dots" bit,
115// because a dot *might* be matched. ( is not in the list, because in
116// the case of a child extglob, it will handle the prevention itself.
117const addPatternStart = new Set(['[', '.']);
118// cases where traversal is A-OK, no dot prevention needed
119const justDots = new Set(['..', '.']);
120const reSpecials = new Set('().*{}+?[]^$\\!');
121const regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
122// any single thing other than /
123const qmark = '[^/]';
124// * => any number of characters
125const star = qmark + '*?';
126// use + when we need to ensure that *something* matches, because the * is
127// the only thing in the path portion.
128const starNoEmpty = qmark + '+?';
129// remove the \ chars that we added if we end up doing a nonmagic compare
130// const deslash = (s: string) => s.replace(/\\(.)/g, '$1')
[33517cc]131let ID = 0;
[81bc7da]132export class AST {
133 type;
134 #root;
135 #hasMagic;
136 #uflag = false;
137 #parts = [];
138 #parent;
139 #parentIndex;
140 #negs;
141 #filledNegs = false;
142 #options;
143 #toString;
144 // set to true if it's an extglob with no children
145 // (which really means one child of '')
146 #emptyExt = false;
[33517cc]147 id = ++ID;
148 get depth() {
149 return (this.#parent?.depth ?? -1) + 1;
150 }
151 [Symbol.for('nodejs.util.inspect.custom')]() {
152 return {
153 '@@type': 'AST',
154 id: this.id,
155 type: this.type,
156 root: this.#root.id,
157 parent: this.#parent?.id,
158 depth: this.depth,
159 partsLength: this.#parts.length,
160 parts: this.#parts,
161 };
162 }
[81bc7da]163 constructor(type, parent, options = {}) {
164 this.type = type;
165 // extglobs are inherently magical
166 if (type)
167 this.#hasMagic = true;
168 this.#parent = parent;
169 this.#root = this.#parent ? this.#parent.#root : this;
170 this.#options = this.#root === this ? options : this.#root.#options;
171 this.#negs = this.#root === this ? [] : this.#root.#negs;
172 if (type === '!' && !this.#root.#filledNegs)
173 this.#negs.push(this);
174 this.#parentIndex = this.#parent ? this.#parent.#parts.length : 0;
175 }
176 get hasMagic() {
177 /* c8 ignore start */
178 if (this.#hasMagic !== undefined)
179 return this.#hasMagic;
180 /* c8 ignore stop */
181 for (const p of this.#parts) {
182 if (typeof p === 'string')
183 continue;
184 if (p.type || p.hasMagic)
185 return (this.#hasMagic = true);
186 }
187 // note: will be undefined until we generate the regexp src and find out
188 return this.#hasMagic;
189 }
190 // reconstructs the pattern
191 toString() {
[33517cc]192 return (this.#toString !== undefined ? this.#toString
193 : !this.type ?
194 (this.#toString = this.#parts.map(p => String(p)).join(''))
195 : (this.#toString =
196 this.type +
197 '(' +
198 this.#parts.map(p => String(p)).join('|') +
199 ')'));
[81bc7da]200 }
201 #fillNegs() {
202 /* c8 ignore start */
203 if (this !== this.#root)
204 throw new Error('should only call on root');
205 if (this.#filledNegs)
206 return this;
207 /* c8 ignore stop */
208 // call toString() once to fill this out
209 this.toString();
210 this.#filledNegs = true;
211 let n;
212 while ((n = this.#negs.pop())) {
213 if (n.type !== '!')
214 continue;
215 // walk up the tree, appending everthing that comes AFTER parentIndex
216 let p = n;
217 let pp = p.#parent;
218 while (pp) {
219 for (let i = p.#parentIndex + 1; !pp.type && i < pp.#parts.length; i++) {
220 for (const part of n.#parts) {
221 /* c8 ignore start */
222 if (typeof part === 'string') {
223 throw new Error('string part in extglob AST??');
224 }
225 /* c8 ignore stop */
226 part.copyIn(pp.#parts[i]);
227 }
228 }
229 p = pp;
230 pp = p.#parent;
231 }
232 }
233 return this;
234 }
235 push(...parts) {
236 for (const p of parts) {
237 if (p === '')
238 continue;
239 /* c8 ignore start */
240 if (typeof p !== 'string' &&
[33517cc]241 !(p instanceof _a && p.#parent === this)) {
[81bc7da]242 throw new Error('invalid part: ' + p);
243 }
244 /* c8 ignore stop */
245 this.#parts.push(p);
246 }
247 }
248 toJSON() {
249 const ret = this.type === null ?
250 this.#parts
251 .slice()
252 .map(p => (typeof p === 'string' ? p : p.toJSON()))
253 : [this.type, ...this.#parts.map(p => p.toJSON())];
254 if (this.isStart() && !this.type)
255 ret.unshift([]);
256 if (this.isEnd() &&
257 (this === this.#root ||
258 (this.#root.#filledNegs && this.#parent?.type === '!'))) {
259 ret.push({});
260 }
261 return ret;
262 }
263 isStart() {
264 if (this.#root === this)
265 return true;
266 // if (this.type) return !!this.#parent?.isStart()
267 if (!this.#parent?.isStart())
268 return false;
269 if (this.#parentIndex === 0)
270 return true;
271 // if everything AHEAD of this is a negation, then it's still the "start"
272 const p = this.#parent;
273 for (let i = 0; i < this.#parentIndex; i++) {
274 const pp = p.#parts[i];
[33517cc]275 if (!(pp instanceof _a && pp.type === '!')) {
[81bc7da]276 return false;
277 }
278 }
279 return true;
280 }
281 isEnd() {
282 if (this.#root === this)
283 return true;
284 if (this.#parent?.type === '!')
285 return true;
286 if (!this.#parent?.isEnd())
287 return false;
288 if (!this.type)
289 return this.#parent?.isEnd();
290 // if not root, it'll always have a parent
291 /* c8 ignore start */
292 const pl = this.#parent ? this.#parent.#parts.length : 0;
293 /* c8 ignore stop */
294 return this.#parentIndex === pl - 1;
295 }
296 copyIn(part) {
297 if (typeof part === 'string')
298 this.push(part);
299 else
300 this.push(part.clone(this));
301 }
302 clone(parent) {
[33517cc]303 const c = new _a(this.type, parent);
[81bc7da]304 for (const p of this.#parts) {
305 c.copyIn(p);
306 }
307 return c;
308 }
[33517cc]309 static #parseAST(str, ast, pos, opt, extDepth) {
310 const maxDepth = opt.maxExtglobRecursion ?? 2;
[81bc7da]311 let escaping = false;
312 let inBrace = false;
313 let braceStart = -1;
314 let braceNeg = false;
315 if (ast.type === null) {
316 // outside of a extglob, append until we find a start
317 let i = pos;
318 let acc = '';
319 while (i < str.length) {
320 const c = str.charAt(i++);
321 // still accumulate escapes at this point, but we do ignore
322 // starts that are escaped
323 if (escaping || c === '\\') {
324 escaping = !escaping;
325 acc += c;
326 continue;
327 }
328 if (inBrace) {
329 if (i === braceStart + 1) {
330 if (c === '^' || c === '!') {
331 braceNeg = true;
332 }
333 }
334 else if (c === ']' && !(i === braceStart + 2 && braceNeg)) {
335 inBrace = false;
336 }
337 acc += c;
338 continue;
339 }
340 else if (c === '[') {
341 inBrace = true;
342 braceStart = i;
343 braceNeg = false;
344 acc += c;
345 continue;
346 }
[33517cc]347 // we don't have to check for adoption here, because that's
348 // done at the other recursion point.
349 const doRecurse = !opt.noext &&
350 isExtglobType(c) &&
351 str.charAt(i) === '(' &&
352 extDepth <= maxDepth;
353 if (doRecurse) {
[81bc7da]354 ast.push(acc);
355 acc = '';
[33517cc]356 const ext = new _a(c, ast);
357 i = _a.#parseAST(str, ext, i, opt, extDepth + 1);
[81bc7da]358 ast.push(ext);
359 continue;
360 }
361 acc += c;
362 }
363 ast.push(acc);
364 return i;
365 }
366 // some kind of extglob, pos is at the (
367 // find the next | or )
368 let i = pos + 1;
[33517cc]369 let part = new _a(null, ast);
[81bc7da]370 const parts = [];
371 let acc = '';
372 while (i < str.length) {
373 const c = str.charAt(i++);
374 // still accumulate escapes at this point, but we do ignore
375 // starts that are escaped
376 if (escaping || c === '\\') {
377 escaping = !escaping;
378 acc += c;
379 continue;
380 }
381 if (inBrace) {
382 if (i === braceStart + 1) {
383 if (c === '^' || c === '!') {
384 braceNeg = true;
385 }
386 }
387 else if (c === ']' && !(i === braceStart + 2 && braceNeg)) {
388 inBrace = false;
389 }
390 acc += c;
391 continue;
392 }
393 else if (c === '[') {
394 inBrace = true;
395 braceStart = i;
396 braceNeg = false;
397 acc += c;
398 continue;
399 }
[33517cc]400 const doRecurse = !opt.noext &&
401 isExtglobType(c) &&
402 str.charAt(i) === '(' &&
403 /* c8 ignore start - the maxDepth is sufficient here */
404 (extDepth <= maxDepth || (ast && ast.#canAdoptType(c)));
405 /* c8 ignore stop */
406 if (doRecurse) {
407 const depthAdd = ast && ast.#canAdoptType(c) ? 0 : 1;
[81bc7da]408 part.push(acc);
409 acc = '';
[33517cc]410 const ext = new _a(c, part);
[81bc7da]411 part.push(ext);
[33517cc]412 i = _a.#parseAST(str, ext, i, opt, extDepth + depthAdd);
[81bc7da]413 continue;
414 }
415 if (c === '|') {
416 part.push(acc);
417 acc = '';
418 parts.push(part);
[33517cc]419 part = new _a(null, ast);
[81bc7da]420 continue;
421 }
422 if (c === ')') {
423 if (acc === '' && ast.#parts.length === 0) {
424 ast.#emptyExt = true;
425 }
426 part.push(acc);
427 acc = '';
428 ast.push(...parts, part);
429 return i;
430 }
431 acc += c;
432 }
433 // unfinished extglob
434 // if we got here, it was a malformed extglob! not an extglob, but
435 // maybe something else in there.
436 ast.type = null;
437 ast.#hasMagic = undefined;
438 ast.#parts = [str.substring(pos - 1)];
439 return i;
440 }
[33517cc]441 #canAdoptWithSpace(child) {
442 return this.#canAdopt(child, adoptionWithSpaceMap);
443 }
444 #canAdopt(child, map = adoptionMap) {
445 if (!child ||
446 typeof child !== 'object' ||
447 child.type !== null ||
448 child.#parts.length !== 1 ||
449 this.type === null) {
450 return false;
451 }
452 const gc = child.#parts[0];
453 if (!gc || typeof gc !== 'object' || gc.type === null) {
454 return false;
455 }
456 return this.#canAdoptType(gc.type, map);
457 }
458 #canAdoptType(c, map = adoptionAnyMap) {
459 return !!map.get(this.type)?.includes(c);
460 }
461 #adoptWithSpace(child, index) {
462 const gc = child.#parts[0];
463 const blank = new _a(null, gc, this.options);
464 blank.#parts.push('');
465 gc.push(blank);
466 this.#adopt(child, index);
467 }
468 #adopt(child, index) {
469 const gc = child.#parts[0];
470 this.#parts.splice(index, 1, ...gc.#parts);
471 for (const p of gc.#parts) {
472 if (typeof p === 'object')
473 p.#parent = this;
474 }
475 this.#toString = undefined;
476 }
477 #canUsurpType(c) {
478 const m = usurpMap.get(this.type);
479 return !!m?.has(c);
480 }
481 #canUsurp(child) {
482 if (!child ||
483 typeof child !== 'object' ||
484 child.type !== null ||
485 child.#parts.length !== 1 ||
486 this.type === null ||
487 this.#parts.length !== 1) {
488 return false;
489 }
490 const gc = child.#parts[0];
491 if (!gc || typeof gc !== 'object' || gc.type === null) {
492 return false;
493 }
494 return this.#canUsurpType(gc.type);
495 }
496 #usurp(child) {
497 const m = usurpMap.get(this.type);
498 const gc = child.#parts[0];
499 const nt = m?.get(gc.type);
500 /* c8 ignore start - impossible */
501 if (!nt)
502 return false;
503 /* c8 ignore stop */
504 this.#parts = gc.#parts;
505 for (const p of this.#parts) {
506 if (typeof p === 'object') {
507 p.#parent = this;
508 }
509 }
510 this.type = nt;
511 this.#toString = undefined;
512 this.#emptyExt = false;
513 }
[81bc7da]514 static fromGlob(pattern, options = {}) {
[33517cc]515 const ast = new _a(null, undefined, options);
516 _a.#parseAST(pattern, ast, 0, options, 0);
[81bc7da]517 return ast;
518 }
519 // returns the regular expression if there's magic, or the unescaped
520 // string if not.
521 toMMPattern() {
522 // should only be called on root
523 /* c8 ignore start */
524 if (this !== this.#root)
525 return this.#root.toMMPattern();
526 /* c8 ignore stop */
527 const glob = this.toString();
528 const [re, body, hasMagic, uflag] = this.toRegExpSource();
529 // if we're in nocase mode, and not nocaseMagicOnly, then we do
530 // still need a regular expression if we have to case-insensitively
531 // match capital/lowercase characters.
532 const anyMagic = hasMagic ||
533 this.#hasMagic ||
534 (this.#options.nocase &&
535 !this.#options.nocaseMagicOnly &&
536 glob.toUpperCase() !== glob.toLowerCase());
537 if (!anyMagic) {
538 return body;
539 }
540 const flags = (this.#options.nocase ? 'i' : '') + (uflag ? 'u' : '');
541 return Object.assign(new RegExp(`^${re}$`, flags), {
542 _src: re,
543 _glob: glob,
544 });
545 }
546 get options() {
547 return this.#options;
548 }
549 // returns the string match, the regexp source, whether there's magic
550 // in the regexp (so a regular expression is required) and whether or
551 // not the uflag is needed for the regular expression (for posix classes)
552 // TODO: instead of injecting the start/end at this point, just return
553 // the BODY of the regexp, along with the start/end portions suitable
554 // for binding the start/end in either a joined full-path makeRe context
555 // (where we bind to (^|/), or a standalone matchPart context (where
556 // we bind to ^, and not /). Otherwise slashes get duped!
557 //
558 // In part-matching mode, the start is:
559 // - if not isStart: nothing
560 // - if traversal possible, but not allowed: ^(?!\.\.?$)
561 // - if dots allowed or not possible: ^
562 // - if dots possible and not allowed: ^(?!\.)
563 // end is:
564 // - if not isEnd(): nothing
565 // - else: $
566 //
567 // In full-path matching mode, we put the slash at the START of the
568 // pattern, so start is:
569 // - if first pattern: same as part-matching mode
570 // - if not isStart(): nothing
571 // - if traversal possible, but not allowed: /(?!\.\.?(?:$|/))
572 // - if dots allowed or not possible: /
573 // - if dots possible and not allowed: /(?!\.)
574 // end is:
575 // - if last pattern, same as part-matching mode
576 // - else nothing
577 //
578 // Always put the (?:$|/) on negated tails, though, because that has to be
579 // there to bind the end of the negated pattern portion, and it's easier to
580 // just stick it in now rather than try to inject it later in the middle of
581 // the pattern.
582 //
583 // We can just always return the same end, and leave it up to the caller
584 // to know whether it's going to be used joined or in parts.
585 // And, if the start is adjusted slightly, can do the same there:
586 // - if not isStart: nothing
587 // - if traversal possible, but not allowed: (?:/|^)(?!\.\.?$)
588 // - if dots allowed or not possible: (?:/|^)
589 // - if dots possible and not allowed: (?:/|^)(?!\.)
590 //
591 // But it's better to have a simpler binding without a conditional, for
592 // performance, so probably better to return both start options.
593 //
594 // Then the caller just ignores the end if it's not the first pattern,
595 // and the start always gets applied.
596 //
597 // But that's always going to be $ if it's the ending pattern, or nothing,
598 // so the caller can just attach $ at the end of the pattern when building.
599 //
600 // So the todo is:
601 // - better detect what kind of start is needed
602 // - return both flavors of starting pattern
603 // - attach $ at the end of the pattern when creating the actual RegExp
604 //
605 // Ah, but wait, no, that all only applies to the root when the first pattern
606 // is not an extglob. If the first pattern IS an extglob, then we need all
607 // that dot prevention biz to live in the extglob portions, because eg
608 // +(*|.x*) can match .xy but not .yx.
609 //
610 // So, return the two flavors if it's #root and the first child is not an
611 // AST, otherwise leave it to the child AST to handle it, and there,
612 // use the (?:^|/) style of start binding.
613 //
614 // Even simplified further:
615 // - Since the start for a join is eg /(?!\.) and the start for a part
616 // is ^(?!\.), we can just prepend (?!\.) to the pattern (either root
617 // or start or whatever) and prepend ^ or / at the Regexp construction.
618 toRegExpSource(allowDot) {
619 const dot = allowDot ?? !!this.#options.dot;
[33517cc]620 if (this.#root === this) {
621 this.#flatten();
[81bc7da]622 this.#fillNegs();
[33517cc]623 }
624 if (!isExtglobAST(this)) {
[81bc7da]625 const noEmpty = this.isStart() &&
626 this.isEnd() &&
627 !this.#parts.some(s => typeof s !== 'string');
628 const src = this.#parts
629 .map(p => {
630 const [re, _, hasMagic, uflag] = typeof p === 'string' ?
[33517cc]631 _a.#parseGlob(p, this.#hasMagic, noEmpty)
[81bc7da]632 : p.toRegExpSource(allowDot);
633 this.#hasMagic = this.#hasMagic || hasMagic;
634 this.#uflag = this.#uflag || uflag;
635 return re;
636 })
637 .join('');
638 let start = '';
639 if (this.isStart()) {
640 if (typeof this.#parts[0] === 'string') {
641 // this is the string that will match the start of the pattern,
642 // so we need to protect against dots and such.
643 // '.' and '..' cannot match unless the pattern is that exactly,
644 // even if it starts with . or dot:true is set.
645 const dotTravAllowed = this.#parts.length === 1 && justDots.has(this.#parts[0]);
646 if (!dotTravAllowed) {
647 const aps = addPatternStart;
648 // check if we have a possibility of matching . or ..,
649 // and prevent that.
650 const needNoTrav =
651 // dots are allowed, and the pattern starts with [ or .
652 (dot && aps.has(src.charAt(0))) ||
653 // the pattern starts with \., and then [ or .
654 (src.startsWith('\\.') && aps.has(src.charAt(2))) ||
655 // the pattern starts with \.\., and then [ or .
656 (src.startsWith('\\.\\.') && aps.has(src.charAt(4)));
657 // no need to prevent dots if it can't match a dot, or if a
658 // sub-pattern will be preventing it anyway.
659 const needNoDot = !dot && !allowDot && aps.has(src.charAt(0));
660 start =
661 needNoTrav ? startNoTraversal
662 : needNoDot ? startNoDot
663 : '';
664 }
665 }
666 }
667 // append the "end of path portion" pattern to negation tails
668 let end = '';
669 if (this.isEnd() &&
670 this.#root.#filledNegs &&
671 this.#parent?.type === '!') {
672 end = '(?:$|\\/)';
673 }
674 const final = start + src + end;
675 return [
676 final,
677 unescape(src),
678 (this.#hasMagic = !!this.#hasMagic),
679 this.#uflag,
680 ];
681 }
682 // We need to calculate the body *twice* if it's a repeat pattern
683 // at the start, once in nodot mode, then again in dot mode, so a
684 // pattern like *(?) can match 'x.y'
685 const repeated = this.type === '*' || this.type === '+';
686 // some kind of extglob
687 const start = this.type === '!' ? '(?:(?!(?:' : '(?:';
688 let body = this.#partsToRegExp(dot);
689 if (this.isStart() && this.isEnd() && !body && this.type !== '!') {
690 // invalid extglob, has to at least be *something* present, if it's
691 // the entire path portion.
692 const s = this.toString();
[33517cc]693 const me = this;
694 me.#parts = [s];
695 me.type = null;
696 me.#hasMagic = undefined;
[81bc7da]697 return [s, unescape(this.toString()), false, false];
698 }
699 let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot ?
700 ''
701 : this.#partsToRegExp(true);
702 if (bodyDotAllowed === body) {
703 bodyDotAllowed = '';
704 }
705 if (bodyDotAllowed) {
706 body = `(?:${body})(?:${bodyDotAllowed})*?`;
707 }
708 // an empty !() is exactly equivalent to a starNoEmpty
709 let final = '';
710 if (this.type === '!' && this.#emptyExt) {
711 final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty;
712 }
713 else {
714 const close = this.type === '!' ?
715 // !() must match something,but !(x) can match ''
716 '))' +
717 (this.isStart() && !dot && !allowDot ? startNoDot : '') +
718 star +
719 ')'
720 : this.type === '@' ? ')'
721 : this.type === '?' ? ')?'
722 : this.type === '+' && bodyDotAllowed ? ')'
723 : this.type === '*' && bodyDotAllowed ? `)?`
724 : `)${this.type}`;
725 final = start + body + close;
726 }
727 return [
728 final,
729 unescape(body),
730 (this.#hasMagic = !!this.#hasMagic),
731 this.#uflag,
732 ];
733 }
[33517cc]734 #flatten() {
735 if (!isExtglobAST(this)) {
736 for (const p of this.#parts) {
737 if (typeof p === 'object') {
738 p.#flatten();
739 }
740 }
741 }
742 else {
743 // do up to 10 passes to flatten as much as possible
744 let iterations = 0;
745 let done = false;
746 do {
747 done = true;
748 for (let i = 0; i < this.#parts.length; i++) {
749 const c = this.#parts[i];
750 if (typeof c === 'object') {
751 c.#flatten();
752 if (this.#canAdopt(c)) {
753 done = false;
754 this.#adopt(c, i);
755 }
756 else if (this.#canAdoptWithSpace(c)) {
757 done = false;
758 this.#adoptWithSpace(c, i);
759 }
760 else if (this.#canUsurp(c)) {
761 done = false;
762 this.#usurp(c);
763 }
764 }
765 }
766 } while (!done && ++iterations < 10);
767 }
768 this.#toString = undefined;
769 }
[81bc7da]770 #partsToRegExp(dot) {
771 return this.#parts
772 .map(p => {
773 // extglob ASTs should only contain parent ASTs
774 /* c8 ignore start */
775 if (typeof p === 'string') {
776 throw new Error('string type in extglob ast??');
777 }
778 /* c8 ignore stop */
779 // can ignore hasMagic, because extglobs are already always magic
780 const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot);
781 this.#uflag = this.#uflag || uflag;
782 return re;
783 })
784 .filter(p => !(this.isStart() && this.isEnd()) || !!p)
785 .join('|');
786 }
787 static #parseGlob(glob, hasMagic, noEmpty = false) {
788 let escaping = false;
789 let re = '';
790 let uflag = false;
791 // multiple stars that aren't globstars coalesce into one *
792 let inStar = false;
793 for (let i = 0; i < glob.length; i++) {
794 const c = glob.charAt(i);
795 if (escaping) {
796 escaping = false;
797 re += (reSpecials.has(c) ? '\\' : '') + c;
798 continue;
799 }
800 if (c === '*') {
801 if (inStar)
802 continue;
803 inStar = true;
804 re += noEmpty && /^[*]+$/.test(glob) ? starNoEmpty : star;
805 hasMagic = true;
806 continue;
807 }
808 else {
809 inStar = false;
810 }
811 if (c === '\\') {
812 if (i === glob.length - 1) {
813 re += '\\\\';
814 }
815 else {
816 escaping = true;
817 }
818 continue;
819 }
820 if (c === '[') {
821 const [src, needUflag, consumed, magic] = parseClass(glob, i);
822 if (consumed) {
823 re += src;
824 uflag = uflag || needUflag;
825 i += consumed - 1;
826 hasMagic = hasMagic || magic;
827 continue;
828 }
829 }
830 if (c === '?') {
831 re += qmark;
832 hasMagic = true;
833 continue;
834 }
835 re += regExpEscape(c);
836 }
837 return [re, unescape(glob), !!hasMagic, uflag];
838 }
839}
[33517cc]840_a = AST;
[81bc7da]841//# sourceMappingURL=ast.js.map
Note: See TracBrowser for help on using the repository browser.