codekingpro/portable-devtools
115k
1// parse a single path portion
2var _a;
3import { parseClass } from './brace-expressions.js';
4import { unescape } from './unescape.js';
5const types = new Set(['!', '?', '+', '*', '@']);
6const isExtglobType = (c) => types.has(c);
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]);
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')
131let ID = 0;
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;
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 }
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() {
192 if (this.#toString !== undefined)
193 return this.#toString;
194 if (!this.type) {
195 return (this.#toString = this.#parts.map(p => String(p)).join(''));
196 }
197 else {
198 return (this.#toString =
199 this.type + '(' + this.#parts.map(p => String(p)).join('|') + ')');
200 }
201 }
202 #fillNegs() {
203 /* c8 ignore start */
204 if (this !== this.#root)
205 throw new Error('should only call on root');
206 if (this.#filledNegs)
207 return this;
208 /* c8 ignore stop */
209 // call toString() once to fill this out
210 this.toString();
211 this.#filledNegs = true;
212 let n;
213 while ((n = this.#negs.pop())) {
214 if (n.type !== '!')
215 continue;
216 // walk up the tree, appending everthing that comes AFTER parentIndex
217 let p = n;
218 let pp = p.#parent;
219 while (pp) {
220 for (let i = p.#parentIndex + 1; !pp.type && i < pp.#parts.length; i++) {
221 for (const part of n.#parts) {
222 /* c8 ignore start */
223 if (typeof part === 'string') {
224 throw new Error('string part in extglob AST??');
225 }
226 /* c8 ignore stop */
227 part.copyIn(pp.#parts[i]);
228 }
229 }
230 p = pp;
231 pp = p.#parent;
232 }
233 }
234 return this;
235 }
236 push(...parts) {
237 for (const p of parts) {
238 if (p === '')
239 continue;
240 /* c8 ignore start */
241 if (typeof p !== 'string' &&
242 !(p instanceof _a && p.#parent === this)) {
243 throw new Error('invalid part: ' + p);
244 }
245 /* c8 ignore stop */
246 this.#parts.push(p);
247 }
248 }
249 toJSON() {
250 const ret = this.type === null ?
251 this.#parts
252 .slice()
253 .map(p => (typeof p === 'string' ? p : p.toJSON()))
254 : [this.type, ...this.#parts.map(p => p.toJSON())];
255 if (this.isStart() && !this.type)
256 ret.unshift([]);
257 if (this.isEnd() &&
258 (this === this.#root ||
259 (this.#root.#filledNegs && this.#parent?.type === '!'))) {
260 ret.push({});
261 }
262 return ret;
263 }
264 isStart() {
265 if (this.#root === this)
266 return true;
267 // if (this.type) return !!this.#parent?.isStart()
268 if (!this.#parent?.isStart())
269 return false;
270 if (this.#parentIndex === 0)
271 return true;
272 // if everything AHEAD of this is a negation, then it's still the "start"
273 const p = this.#parent;
274 for (let i = 0; i < this.#parentIndex; i++) {
275 const pp = p.#parts[i];
276 if (!(pp instanceof _a && pp.type === '!')) {
277 return false;
278 }
279 }
280 return true;
281 }
282 isEnd() {
283 if (this.#root === this)
284 return true;
285 if (this.#parent?.type === '!')
286 return true;
287 if (!this.#parent?.isEnd())
288 return false;
289 if (!this.type)
290 return this.#parent?.isEnd();
291 // if not root, it'll always have a parent
292 /* c8 ignore start */
293 const pl = this.#parent ? this.#parent.#parts.length : 0;
294 /* c8 ignore stop */
295 return this.#parentIndex === pl - 1;
296 }
297 copyIn(part) {
298 if (typeof part === 'string')
299 this.push(part);
300 else
301 this.push(part.clone(this));
302 }
303 clone(parent) {
304 const c = new _a(this.type, parent);
305 for (const p of this.#parts) {
306 c.copyIn(p);
307 }
308 return c;
309 }
310 static #parseAST(str, ast, pos, opt, extDepth) {
311 const maxDepth = opt.maxExtglobRecursion ?? 2;
312 let escaping = false;
313 let inBrace = false;
314 let braceStart = -1;
315 let braceNeg = false;
316 if (ast.type === null) {
317 // outside of a extglob, append until we find a start
318 let i = pos;
319 let acc = '';
320 while (i < str.length) {
321 const c = str.charAt(i++);
322 // still accumulate escapes at this point, but we do ignore
323 // starts that are escaped
324 if (escaping || c === '\\') {
325 escaping = !escaping;
326 acc += c;
327 continue;
328 }
329 if (inBrace) {
330 if (i === braceStart + 1) {
331 if (c === '^' || c === '!') {
332 braceNeg = true;
333 }
334 }
335 else if (c === ']' && !(i === braceStart + 2 && braceNeg)) {
336 inBrace = false;
337 }
338 acc += c;
339 continue;
340 }
341 else if (c === '[') {
342 inBrace = true;
343 braceStart = i;
344 braceNeg = false;
345 acc += c;
346 continue;
347 }
348 // we don't have to check for adoption here, because that's
349 // done at the other recursion point.
350 const doRecurse = !opt.noext &&
351 isExtglobType(c) &&
352 str.charAt(i) === '(' &&
353 extDepth <= maxDepth;
354 if (doRecurse) {
355 ast.push(acc);
356 acc = '';
357 const ext = new _a(c, ast);
358 i = _a.#parseAST(str, ext, i, opt, extDepth + 1);
359 ast.push(ext);
360 continue;
361 }
362 acc += c;
363 }
364 ast.push(acc);
365 return i;
366 }
367 // some kind of extglob, pos is at the (
368 // find the next | or )
369 let i = pos + 1;
370 let part = new _a(null, ast);
371 const parts = [];
372 let acc = '';
373 while (i < str.length) {
374 const c = str.charAt(i++);
375 // still accumulate escapes at this point, but we do ignore
376 // starts that are escaped
377 if (escaping || c === '\\') {
378 escaping = !escaping;
379 acc += c;
380 continue;
381 }
382 if (inBrace) {
383 if (i === braceStart + 1) {
384 if (c === '^' || c === '!') {
385 braceNeg = true;
386 }
387 }
388 else if (c === ']' && !(i === braceStart + 2 && braceNeg)) {
389 inBrace = false;
390 }
391 acc += c;
392 continue;
393 }
394 else if (c === '[') {
395 inBrace = true;
396 braceStart = i;
397 braceNeg = false;
398 acc += c;
399 continue;
400 }
401 const doRecurse = !opt.noext &&
402 isExtglobType(c) &&
403 str.charAt(i) === '(' &&
404 /* c8 ignore start - the maxDepth is sufficient here */
405 (extDepth <= maxDepth || (ast && ast.#canAdoptType(c)));
406 /* c8 ignore stop */
407 if (doRecurse) {
408 const depthAdd = ast && ast.#canAdoptType(c) ? 0 : 1;
409 part.push(acc);
410 acc = '';
411 const ext = new _a(c, part);
412 part.push(ext);
413 i = _a.#parseAST(str, ext, i, opt, extDepth + depthAdd);
414 continue;
415 }
416 if (c === '|') {
417 part.push(acc);
418 acc = '';
419 parts.push(part);
420 part = new _a(null, ast);
421 continue;
422 }
423 if (c === ')') {
424 if (acc === '' && ast.#parts.length === 0) {
425 ast.#emptyExt = true;
426 }
427 part.push(acc);
428 acc = '';
429 ast.push(...parts, part);
430 return i;
431 }
432 acc += c;
433 }
434 // unfinished extglob
435 // if we got here, it was a malformed extglob! not an extglob, but
436 // maybe something else in there.
437 ast.type = null;
438 ast.#hasMagic = undefined;
439 ast.#parts = [str.substring(pos - 1)];
440 return i;
441 }
442 #canAdoptWithSpace(child) {
443 return this.#canAdopt(child, adoptionWithSpaceMap);
444 }
445 #canAdopt(child, map = adoptionMap) {
446 if (!child ||
447 typeof child !== 'object' ||
448 child.type !== null ||
449 child.#parts.length !== 1 ||
450 this.type === null) {
451 return false;
452 }
453 const gc = child.#parts[0];
454 if (!gc || typeof gc !== 'object' || gc.type === null) {
455 return false;
456 }
457 return this.#canAdoptType(gc.type, map);
458 }
459 #canAdoptType(c, map = adoptionAnyMap) {
460 return !!map.get(this.type)?.includes(c);
461 }
462 #adoptWithSpace(child, index) {
463 const gc = child.#parts[0];
464 const blank = new _a(null, gc, this.options);
465 blank.#parts.push('');
466 gc.push(blank);
467 this.#adopt(child, index);
468 }
469 #adopt(child, index) {
470 const gc = child.#parts[0];
471 this.#parts.splice(index, 1, ...gc.#parts);
472 for (const p of gc.#parts) {
473 if (typeof p === 'object')
474 p.#parent = this;
475 }
476 this.#toString = undefined;
477 }
478 #canUsurpType(c) {
479 const m = usurpMap.get(this.type);
480 return !!(m?.has(c));
481 }
482 #canUsurp(child) {
483 if (!child ||
484 typeof child !== 'object' ||
485 child.type !== null ||
486 child.#parts.length !== 1 ||
487 this.type === null ||
488 this.#parts.length !== 1) {
489 return false;
490 }
491 const gc = child.#parts[0];
492 if (!gc || typeof gc !== 'object' || gc.type === null) {
493 return false;
494 }
495 return this.#canUsurpType(gc.type);
496 }
497 #usurp(child) {
498 const m = usurpMap.get(this.type);
499 const gc = child.#parts[0];
500 const nt = m?.get(gc.type);
501 /* c8 ignore start - impossible */
502 if (!nt)
503 return false;
504 /* c8 ignore stop */
505 this.#parts = gc.#parts;
506 for (const p of this.#parts) {
507 if (typeof p === 'object') {
508 p.#parent = this;
509 }
510 }
511 this.type = nt;
512 this.#toString = undefined;
513 this.#emptyExt = false;
514 }
515 static fromGlob(pattern, options = {}) {
516 const ast = new _a(null, undefined, options);
517 _a.#parseAST(pattern, ast, 0, options, 0);
518 return ast;
519 }
520 // returns the regular expression if there's magic, or the unescaped
521 // string if not.
522 toMMPattern() {
523 // should only be called on root
524 /* c8 ignore start */
525 if (this !== this.#root)
526 return this.#root.toMMPattern();
527 /* c8 ignore stop */
528 const glob = this.toString();
529 const [re, body, hasMagic, uflag] = this.toRegExpSource();
530 // if we're in nocase mode, and not nocaseMagicOnly, then we do
531 // still need a regular expression if we have to case-insensitively
532 // match capital/lowercase characters.
533 const anyMagic = hasMagic ||
534 this.#hasMagic ||
535 (this.#options.nocase &&
536 !this.#options.nocaseMagicOnly &&
537 glob.toUpperCase() !== glob.toLowerCase());
538 if (!anyMagic) {
539 return body;
540 }
541 const flags = (this.#options.nocase ? 'i' : '') + (uflag ? 'u' : '');
542 return Object.assign(new RegExp(`^${re}$`, flags), {
543 _src: re,
544 _glob: glob,
545 });
546 }
547 get options() {
548 return this.#options;
549 }
550 // returns the string match, the regexp source, whether there's magic
551 // in the regexp (so a regular expression is required) and whether or
552 // not the uflag is needed for the regular expression (for posix classes)
553 // TODO: instead of injecting the start/end at this point, just return
554 // the BODY of the regexp, along with the start/end portions suitable
555 // for binding the start/end in either a joined full-path makeRe context
556 // (where we bind to (^|/), or a standalone matchPart context (where
557 // we bind to ^, and not /). Otherwise slashes get duped!
558 //
559 // In part-matching mode, the start is:
560 // - if not isStart: nothing
561 // - if traversal possible, but not allowed: ^(?!\.\.?$)
562 // - if dots allowed or not possible: ^
563 // - if dots possible and not allowed: ^(?!\.)
564 // end is:
565 // - if not isEnd(): nothing
566 // - else: $
567 //
568 // In full-path matching mode, we put the slash at the START of the
569 // pattern, so start is:
570 // - if first pattern: same as part-matching mode
571 // - if not isStart(): nothing
572 // - if traversal possible, but not allowed: /(?!\.\.?(?:$|/))
573 // - if dots allowed or not possible: /
574 // - if dots possible and not allowed: /(?!\.)
575 // end is:
576 // - if last pattern, same as part-matching mode
577 // - else nothing
578 //
579 // Always put the (?:$|/) on negated tails, though, because that has to be
580 // there to bind the end of the negated pattern portion, and it's easier to
581 // just stick it in now rather than try to inject it later in the middle of
582 // the pattern.
583 //
584 // We can just always return the same end, and leave it up to the caller
585 // to know whether it's going to be used joined or in parts.
586 // And, if the start is adjusted slightly, can do the same there:
587 // - if not isStart: nothing
588 // - if traversal possible, but not allowed: (?:/|^)(?!\.\.?$)
589 // - if dots allowed or not possible: (?:/|^)
590 // - if dots possible and not allowed: (?:/|^)(?!\.)
591 //
592 // But it's better to have a simpler binding without a conditional, for
593 // performance, so probably better to return both start options.
594 //
595 // Then the caller just ignores the end if it's not the first pattern,
596 // and the start always gets applied.
597 //
598 // But that's always going to be $ if it's the ending pattern, or nothing,
599 // so the caller can just attach $ at the end of the pattern when building.
600 //
601 // So the todo is:
602 // - better detect what kind of start is needed
603 // - return both flavors of starting pattern
604 // - attach $ at the end of the pattern when creating the actual RegExp
605 //
606 // Ah, but wait, no, that all only applies to the root when the first pattern
607 // is not an extglob. If the first pattern IS an extglob, then we need all
608 // that dot prevention biz to live in the extglob portions, because eg
609 // +(*|.x*) can match .xy but not .yx.
610 //
611 // So, return the two flavors if it's #root and the first child is not an
612 // AST, otherwise leave it to the child AST to handle it, and there,
613 // use the (?:^|/) style of start binding.
614 //
615 // Even simplified further:
616 // - Since the start for a join is eg /(?!\.) and the start for a part
617 // is ^(?!\.), we can just prepend (?!\.) to the pattern (either root
618 // or start or whatever) and prepend ^ or / at the Regexp construction.
619 toRegExpSource(allowDot) {
620 const dot = allowDot ?? !!this.#options.dot;
621 if (this.#root === this) {
622 this.#flatten();
623 this.#fillNegs();
624 }
625 if (!isExtglobAST(this)) {
626 const noEmpty = this.isStart() &&
627 this.isEnd() &&
628 !this.#parts.some(s => typeof s !== 'string');
629 const src = this.#parts
630 .map(p => {
631 const [re, _, hasMagic, uflag] = typeof p === 'string' ?
632 _a.#parseGlob(p, this.#hasMagic, noEmpty)
633 : p.toRegExpSource(allowDot);
634 this.#hasMagic = this.#hasMagic || hasMagic;
635 this.#uflag = this.#uflag || uflag;
636 return re;
637 })
638 .join('');
639 let start = '';
640 if (this.isStart()) {
641 if (typeof this.#parts[0] === 'string') {
642 // this is the string that will match the start of the pattern,
643 // so we need to protect against dots and such.
644 // '.' and '..' cannot match unless the pattern is that exactly,
645 // even if it starts with . or dot:true is set.
646 const dotTravAllowed = this.#parts.length === 1 && justDots.has(this.#parts[0]);
647 if (!dotTravAllowed) {
648 const aps = addPatternStart;
649 // check if we have a possibility of matching . or ..,
650 // and prevent that.
651 const needNoTrav =
652 // dots are allowed, and the pattern starts with [ or .
653 (dot && aps.has(src.charAt(0))) ||
654 // the pattern starts with \., and then [ or .
655 (src.startsWith('\\.') && aps.has(src.charAt(2))) ||
656 // the pattern starts with \.\., and then [ or .
657 (src.startsWith('\\.\\.') && aps.has(src.charAt(4)));
658 // no need to prevent dots if it can't match a dot, or if a
659 // sub-pattern will be preventing it anyway.
660 const needNoDot = !dot && !allowDot && aps.has(src.charAt(0));
661 start =
662 needNoTrav ? startNoTraversal
663 : needNoDot ? startNoDot
664 : '';
665 }
666 }
667 }
668 // append the "end of path portion" pattern to negation tails
669 let end = '';
670 if (this.isEnd() &&
671 this.#root.#filledNegs &&
672 this.#parent?.type === '!') {
673 end = '(?:$|\\/)';
674 }
675 const final = start + src + end;
676 return [
677 final,
678 unescape(src),
679 (this.#hasMagic = !!this.#hasMagic),
680 this.#uflag,
681 ];
682 }
683 // We need to calculate the body *twice* if it's a repeat pattern
684 // at the start, once in nodot mode, then again in dot mode, so a
685 // pattern like *(?) can match 'x.y'
686 const repeated = this.type === '*' || this.type === '+';
687 // some kind of extglob
688 const start = this.type === '!' ? '(?:(?!(?:' : '(?:';
689 let body = this.#partsToRegExp(dot);
690 if (this.isStart() && this.isEnd() && !body && this.type !== '!') {
691 // invalid extglob, has to at least be *something* present, if it's
692 // the entire path portion.
693 const s = this.toString();
694 const me = this;
695 me.#parts = [s];
696 me.type = null;
697 me.#hasMagic = undefined;
698 return [s, unescape(this.toString()), false, false];
699 }
700 let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot ?
701 ''
702 : this.#partsToRegExp(true);
703 if (bodyDotAllowed === body) {
704 bodyDotAllowed = '';
705 }
706 if (bodyDotAllowed) {
707 body = `(?:${body})(?:${bodyDotAllowed})*?`;
708 }
709 // an empty !() is exactly equivalent to a starNoEmpty
710 let final = '';
711 if (this.type === '!' && this.#emptyExt) {
712 final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty;
713 }
714 else {
715 const close = this.type === '!' ?
716 // !() must match something,but !(x) can match ''
717 '))' +
718 (this.isStart() && !dot && !allowDot ? startNoDot : '') +
719 star +
720 ')'
721 : this.type === '@' ? ')'
722 : this.type === '?' ? ')?'
723 : this.type === '+' && bodyDotAllowed ? ')'
724 : this.type === '*' && bodyDotAllowed ? `)?`
725 : `)${this.type}`;
726 final = start + body + close;
727 }
728 return [
729 final,
730 unescape(body),
731 (this.#hasMagic = !!this.#hasMagic),
732 this.#uflag,
733 ];
734 }
735 #flatten() {
736 if (!isExtglobAST(this)) {
737 for (const p of this.#parts) {
738 if (typeof p === 'object') {
739 p.#flatten();
740 }
741 }
742 }
743 else {
744 // do up to 10 passes to flatten as much as possible
745 let iterations = 0;
746 let done = false;
747 do {
748 done = true;
749 for (let i = 0; i < this.#parts.length; i++) {
750 const c = this.#parts[i];
751 if (typeof c === 'object') {
752 c.#flatten();
753 if (this.#canAdopt(c)) {
754 done = false;
755 this.#adopt(c, i);
756 }
757 else if (this.#canAdoptWithSpace(c)) {
758 done = false;
759 this.#adoptWithSpace(c, i);
760 }
761 else if (this.#canUsurp(c)) {
762 done = false;
763 this.#usurp(c);
764 }
765 }
766 }
767 } while (!done && ++iterations < 10);
768 }
769 this.#toString = undefined;
770 }
771 #partsToRegExp(dot) {
772 return this.#parts
773 .map(p => {
774 // extglob ASTs should only contain parent ASTs
775 /* c8 ignore start */
776 if (typeof p === 'string') {
777 throw new Error('string type in extglob ast??');
778 }
779 /* c8 ignore stop */
780 // can ignore hasMagic, because extglobs are already always magic
781 const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot);
782 this.#uflag = this.#uflag || uflag;
783 return re;
784 })
785 .filter(p => !(this.isStart() && this.isEnd()) || !!p)
786 .join('|');
787 }
788 static #parseGlob(glob, hasMagic, noEmpty = false) {
789 let escaping = false;
790 let re = '';
791 let uflag = false;
792 // multiple stars that aren't globstars coalesce into one *
793 let inStar = false;
794 for (let i = 0; i < glob.length; i++) {
795 const c = glob.charAt(i);
796 if (escaping) {
797 escaping = false;
798 re += (reSpecials.has(c) ? '\\' : '') + c;
799 continue;
800 }
801 if (c === '*') {
802 if (inStar)
803 continue;
804 inStar = true;
805 re += noEmpty && /^[*]+$/.test(glob) ? starNoEmpty : star;
806 hasMagic = true;
807 continue;
808 }
809 else {
810 inStar = false;
811 }
812 if (c === '\\') {
813 if (i === glob.length - 1) {
814 re += '\\\\';
815 }
816 else {
817 escaping = true;
818 }
819 continue;
820 }
821 if (c === '[') {
822 const [src, needUflag, consumed, magic] = parseClass(glob, i);
823 if (consumed) {
824 re += src;
825 uflag = uflag || needUflag;
826 i += consumed - 1;
827 hasMagic = hasMagic || magic;
828 continue;
829 }
830 }
831 if (c === '?') {
832 re += qmark;
833 hasMagic = true;
834 continue;
835 }
836 re += regExpEscape(c);
837 }
838 return [re, unescape(glob), !!hasMagic, uflag];
839 }
840}
841_a = AST;
842//# sourceMappingURL=ast.js.map