Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
ast.js842 linesDownload Raw Back to esm
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
codekingpro/portable-devtools · Team Ai