Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
ast.js841 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        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                        ')'));
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' &&
241                !(p instanceof _a && p.#parent === this)) {
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];
275            if (!(pp instanceof _a && pp.type === '!')) {
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) {
303        const c = new _a(this.type, parent);
304        for (const p of this.#parts) {
305            c.copyIn(p);
306        }
307        return c;
308    }
309    static #parseAST(str, ast, pos, opt, extDepth) {
310        const maxDepth = opt.maxExtglobRecursion ?? 2;
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                }
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) {
354                    ast.push(acc);
355                    acc = '';
356                    const ext = new _a(c, ast);
357                    i = _a.#parseAST(str, ext, i, opt, extDepth + 1);
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;
369        let part = new _a(null, ast);
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            }
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;
408                part.push(acc);
409                acc = '';
410                const ext = new _a(c, part);
411                part.push(ext);
412                i = _a.#parseAST(str, ext, i, opt, extDepth + depthAdd);
413                continue;
414            }
415            if (c === '|') {
416                part.push(acc);
417                acc = '';
418                parts.push(part);
419                part = new _a(null, ast);
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    }
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    }
514    static fromGlob(pattern, options = {}) {
515        const ast = new _a(null, undefined, options);
516        _a.#parseAST(pattern, ast, 0, options, 0);
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;
620        if (this.#root === this) {
621            this.#flatten();
622            this.#fillNegs();
623        }
624        if (!isExtglobAST(this)) {
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' ?
631                    _a.#parseGlob(p, this.#hasMagic, noEmpty)
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();
693            const me = this;
694            me.#parts = [s];
695            me.type = null;
696            me.#hasMagic = undefined;
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    }
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    }
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}
840_a = AST;
841//# sourceMappingURL=ast.js.map
codekingpro/portable-devtools · Team Ai