19e815959Sopenharmony_ciimport {
29e815959Sopenharmony_ci    Selector,
39e815959Sopenharmony_ci    SelectorType,
49e815959Sopenharmony_ci    AttributeSelector,
59e815959Sopenharmony_ci    Traversal,
69e815959Sopenharmony_ci    AttributeAction,
79e815959Sopenharmony_ci    TraversalType,
89e815959Sopenharmony_ci    DataType,
99e815959Sopenharmony_ci} from "./types";
109e815959Sopenharmony_ci
119e815959Sopenharmony_ciconst reName = /^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/;
129e815959Sopenharmony_ciconst reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi;
139e815959Sopenharmony_ci
149e815959Sopenharmony_ciconst enum CharCode {
159e815959Sopenharmony_ci    LeftParenthesis = 40,
169e815959Sopenharmony_ci    RightParenthesis = 41,
179e815959Sopenharmony_ci    LeftSquareBracket = 91,
189e815959Sopenharmony_ci    RightSquareBracket = 93,
199e815959Sopenharmony_ci    Comma = 44,
209e815959Sopenharmony_ci    Period = 46,
219e815959Sopenharmony_ci    Colon = 58,
229e815959Sopenharmony_ci    SingleQuote = 39,
239e815959Sopenharmony_ci    DoubleQuote = 34,
249e815959Sopenharmony_ci    Plus = 43,
259e815959Sopenharmony_ci    Tilde = 126,
269e815959Sopenharmony_ci    QuestionMark = 63,
279e815959Sopenharmony_ci    ExclamationMark = 33,
289e815959Sopenharmony_ci    Slash = 47,
299e815959Sopenharmony_ci    Star = 42,
309e815959Sopenharmony_ci    Equal = 61,
319e815959Sopenharmony_ci    Dollar = 36,
329e815959Sopenharmony_ci    Pipe = 124,
339e815959Sopenharmony_ci    Circumflex = 94,
349e815959Sopenharmony_ci    Asterisk = 42,
359e815959Sopenharmony_ci    GreaterThan = 62,
369e815959Sopenharmony_ci    LessThan = 60,
379e815959Sopenharmony_ci    Hash = 35,
389e815959Sopenharmony_ci    LowerI = 105,
399e815959Sopenharmony_ci    LowerS = 115,
409e815959Sopenharmony_ci    BackSlash = 92,
419e815959Sopenharmony_ci
429e815959Sopenharmony_ci    // Whitespace
439e815959Sopenharmony_ci    Space = 32,
449e815959Sopenharmony_ci    Tab = 9,
459e815959Sopenharmony_ci    NewLine = 10,
469e815959Sopenharmony_ci    FormFeed = 12,
479e815959Sopenharmony_ci    CarriageReturn = 13,
489e815959Sopenharmony_ci}
499e815959Sopenharmony_ci
509e815959Sopenharmony_ciconst actionTypes = new Map<number, AttributeAction>([
519e815959Sopenharmony_ci    [CharCode.Tilde, AttributeAction.Element],
529e815959Sopenharmony_ci    [CharCode.Circumflex, AttributeAction.Start],
539e815959Sopenharmony_ci    [CharCode.Dollar, AttributeAction.End],
549e815959Sopenharmony_ci    [CharCode.Asterisk, AttributeAction.Any],
559e815959Sopenharmony_ci    [CharCode.ExclamationMark, AttributeAction.Not],
569e815959Sopenharmony_ci    [CharCode.Pipe, AttributeAction.Hyphen],
579e815959Sopenharmony_ci]);
589e815959Sopenharmony_ci
599e815959Sopenharmony_ci// Pseudos, whose data property is parsed as well.
609e815959Sopenharmony_ciconst unpackPseudos = new Set([
619e815959Sopenharmony_ci    "has",
629e815959Sopenharmony_ci    "not",
639e815959Sopenharmony_ci    "matches",
649e815959Sopenharmony_ci    "is",
659e815959Sopenharmony_ci    "where",
669e815959Sopenharmony_ci    "host",
679e815959Sopenharmony_ci    "host-context",
689e815959Sopenharmony_ci]);
699e815959Sopenharmony_ci
709e815959Sopenharmony_ci/**
719e815959Sopenharmony_ci * Checks whether a specific selector is a traversal.
729e815959Sopenharmony_ci * This is useful eg. in swapping the order of elements that
739e815959Sopenharmony_ci * are not traversals.
749e815959Sopenharmony_ci *
759e815959Sopenharmony_ci * @param selector Selector to check.
769e815959Sopenharmony_ci */
779e815959Sopenharmony_ciexport function isTraversal(selector: Selector): selector is Traversal {
789e815959Sopenharmony_ci    switch (selector.type) {
799e815959Sopenharmony_ci        case SelectorType.Adjacent:
809e815959Sopenharmony_ci        case SelectorType.Child:
819e815959Sopenharmony_ci        case SelectorType.Descendant:
829e815959Sopenharmony_ci        case SelectorType.Parent:
839e815959Sopenharmony_ci        case SelectorType.Sibling:
849e815959Sopenharmony_ci        case SelectorType.ColumnCombinator:
859e815959Sopenharmony_ci            return true;
869e815959Sopenharmony_ci        default:
879e815959Sopenharmony_ci            return false;
889e815959Sopenharmony_ci    }
899e815959Sopenharmony_ci}
909e815959Sopenharmony_ci
919e815959Sopenharmony_ciconst stripQuotesFromPseudos = new Set(["contains", "icontains"]);
929e815959Sopenharmony_ci
939e815959Sopenharmony_ci// Unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L152
949e815959Sopenharmony_cifunction funescape(_: string, escaped: string, escapedWhitespace?: string) {
959e815959Sopenharmony_ci    const high = parseInt(escaped, 16) - 0x10000;
969e815959Sopenharmony_ci
979e815959Sopenharmony_ci    // NaN means non-codepoint
989e815959Sopenharmony_ci    return high !== high || escapedWhitespace
999e815959Sopenharmony_ci        ? escaped
1009e815959Sopenharmony_ci        : high < 0
1019e815959Sopenharmony_ci        ? // BMP codepoint
1029e815959Sopenharmony_ci          String.fromCharCode(high + 0x10000)
1039e815959Sopenharmony_ci        : // Supplemental Plane codepoint (surrogate pair)
1049e815959Sopenharmony_ci          String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);
1059e815959Sopenharmony_ci}
1069e815959Sopenharmony_ci
1079e815959Sopenharmony_cifunction unescapeCSS(str: string) {
1089e815959Sopenharmony_ci    return str.replace(reEscape, funescape);
1099e815959Sopenharmony_ci}
1109e815959Sopenharmony_ci
1119e815959Sopenharmony_cifunction isQuote(c: number): boolean {
1129e815959Sopenharmony_ci    return c === CharCode.SingleQuote || c === CharCode.DoubleQuote;
1139e815959Sopenharmony_ci}
1149e815959Sopenharmony_ci
1159e815959Sopenharmony_cifunction isWhitespace(c: number): boolean {
1169e815959Sopenharmony_ci    return (
1179e815959Sopenharmony_ci        c === CharCode.Space ||
1189e815959Sopenharmony_ci        c === CharCode.Tab ||
1199e815959Sopenharmony_ci        c === CharCode.NewLine ||
1209e815959Sopenharmony_ci        c === CharCode.FormFeed ||
1219e815959Sopenharmony_ci        c === CharCode.CarriageReturn
1229e815959Sopenharmony_ci    );
1239e815959Sopenharmony_ci}
1249e815959Sopenharmony_ci
1259e815959Sopenharmony_ci/**
1269e815959Sopenharmony_ci * Parses `selector`, optionally with the passed `options`.
1279e815959Sopenharmony_ci *
1289e815959Sopenharmony_ci * @param selector Selector to parse.
1299e815959Sopenharmony_ci * @param options Options for parsing.
1309e815959Sopenharmony_ci * @returns Returns a two-dimensional array.
1319e815959Sopenharmony_ci * The first dimension represents selectors separated by commas (eg. `sub1, sub2`),
1329e815959Sopenharmony_ci * the second contains the relevant tokens for that selector.
1339e815959Sopenharmony_ci */
1349e815959Sopenharmony_ciexport function parse(selector: string): Selector[][] {
1359e815959Sopenharmony_ci    const subselects: Selector[][] = [];
1369e815959Sopenharmony_ci
1379e815959Sopenharmony_ci    const endIndex = parseSelector(subselects, `${selector}`, 0);
1389e815959Sopenharmony_ci
1399e815959Sopenharmony_ci    if (endIndex < selector.length) {
1409e815959Sopenharmony_ci        throw new Error(`Unmatched selector: ${selector.slice(endIndex)}`);
1419e815959Sopenharmony_ci    }
1429e815959Sopenharmony_ci
1439e815959Sopenharmony_ci    return subselects;
1449e815959Sopenharmony_ci}
1459e815959Sopenharmony_ci
1469e815959Sopenharmony_cifunction parseSelector(
1479e815959Sopenharmony_ci    subselects: Selector[][],
1489e815959Sopenharmony_ci    selector: string,
1499e815959Sopenharmony_ci    selectorIndex: number
1509e815959Sopenharmony_ci): number {
1519e815959Sopenharmony_ci    let tokens: Selector[] = [];
1529e815959Sopenharmony_ci
1539e815959Sopenharmony_ci    function getName(offset: number): string {
1549e815959Sopenharmony_ci        const match = selector.slice(selectorIndex + offset).match(reName);
1559e815959Sopenharmony_ci
1569e815959Sopenharmony_ci        if (!match) {
1579e815959Sopenharmony_ci            throw new Error(
1589e815959Sopenharmony_ci                `Expected name, found ${selector.slice(selectorIndex)}`
1599e815959Sopenharmony_ci            );
1609e815959Sopenharmony_ci        }
1619e815959Sopenharmony_ci
1629e815959Sopenharmony_ci        const [name] = match;
1639e815959Sopenharmony_ci        selectorIndex += offset + name.length;
1649e815959Sopenharmony_ci        return unescapeCSS(name);
1659e815959Sopenharmony_ci    }
1669e815959Sopenharmony_ci
1679e815959Sopenharmony_ci    function stripWhitespace(offset: number) {
1689e815959Sopenharmony_ci        selectorIndex += offset;
1699e815959Sopenharmony_ci
1709e815959Sopenharmony_ci        while (
1719e815959Sopenharmony_ci            selectorIndex < selector.length &&
1729e815959Sopenharmony_ci            isWhitespace(selector.charCodeAt(selectorIndex))
1739e815959Sopenharmony_ci        ) {
1749e815959Sopenharmony_ci            selectorIndex++;
1759e815959Sopenharmony_ci        }
1769e815959Sopenharmony_ci    }
1779e815959Sopenharmony_ci
1789e815959Sopenharmony_ci    function readValueWithParenthesis(): string {
1799e815959Sopenharmony_ci        selectorIndex += 1;
1809e815959Sopenharmony_ci        const start = selectorIndex;
1819e815959Sopenharmony_ci        let counter = 1;
1829e815959Sopenharmony_ci
1839e815959Sopenharmony_ci        for (
1849e815959Sopenharmony_ci            ;
1859e815959Sopenharmony_ci            counter > 0 && selectorIndex < selector.length;
1869e815959Sopenharmony_ci            selectorIndex++
1879e815959Sopenharmony_ci        ) {
1889e815959Sopenharmony_ci            if (
1899e815959Sopenharmony_ci                selector.charCodeAt(selectorIndex) ===
1909e815959Sopenharmony_ci                    CharCode.LeftParenthesis &&
1919e815959Sopenharmony_ci                !isEscaped(selectorIndex)
1929e815959Sopenharmony_ci            ) {
1939e815959Sopenharmony_ci                counter++;
1949e815959Sopenharmony_ci            } else if (
1959e815959Sopenharmony_ci                selector.charCodeAt(selectorIndex) ===
1969e815959Sopenharmony_ci                    CharCode.RightParenthesis &&
1979e815959Sopenharmony_ci                !isEscaped(selectorIndex)
1989e815959Sopenharmony_ci            ) {
1999e815959Sopenharmony_ci                counter--;
2009e815959Sopenharmony_ci            }
2019e815959Sopenharmony_ci        }
2029e815959Sopenharmony_ci
2039e815959Sopenharmony_ci        if (counter) {
2049e815959Sopenharmony_ci            throw new Error("Parenthesis not matched");
2059e815959Sopenharmony_ci        }
2069e815959Sopenharmony_ci
2079e815959Sopenharmony_ci        return unescapeCSS(selector.slice(start, selectorIndex - 1));
2089e815959Sopenharmony_ci    }
2099e815959Sopenharmony_ci
2109e815959Sopenharmony_ci    function isEscaped(pos: number): boolean {
2119e815959Sopenharmony_ci        let slashCount = 0;
2129e815959Sopenharmony_ci
2139e815959Sopenharmony_ci        while (selector.charCodeAt(--pos) === CharCode.BackSlash) slashCount++;
2149e815959Sopenharmony_ci        return (slashCount & 1) === 1;
2159e815959Sopenharmony_ci    }
2169e815959Sopenharmony_ci
2179e815959Sopenharmony_ci    function ensureNotTraversal() {
2189e815959Sopenharmony_ci        if (tokens.length > 0 && isTraversal(tokens[tokens.length - 1])) {
2199e815959Sopenharmony_ci            throw new Error("Did not expect successive traversals.");
2209e815959Sopenharmony_ci        }
2219e815959Sopenharmony_ci    }
2229e815959Sopenharmony_ci
2239e815959Sopenharmony_ci    function addTraversal(type: TraversalType) {
2249e815959Sopenharmony_ci        if (
2259e815959Sopenharmony_ci            tokens.length > 0 &&
2269e815959Sopenharmony_ci            tokens[tokens.length - 1].type === SelectorType.Descendant
2279e815959Sopenharmony_ci        ) {
2289e815959Sopenharmony_ci            tokens[tokens.length - 1].type = type;
2299e815959Sopenharmony_ci            return;
2309e815959Sopenharmony_ci        }
2319e815959Sopenharmony_ci
2329e815959Sopenharmony_ci        ensureNotTraversal();
2339e815959Sopenharmony_ci
2349e815959Sopenharmony_ci        tokens.push({ type });
2359e815959Sopenharmony_ci    }
2369e815959Sopenharmony_ci
2379e815959Sopenharmony_ci    function addSpecialAttribute(name: string, action: AttributeAction) {
2389e815959Sopenharmony_ci        tokens.push({
2399e815959Sopenharmony_ci            type: SelectorType.Attribute,
2409e815959Sopenharmony_ci            name,
2419e815959Sopenharmony_ci            action,
2429e815959Sopenharmony_ci            value: getName(1),
2439e815959Sopenharmony_ci            namespace: null,
2449e815959Sopenharmony_ci            ignoreCase: "quirks",
2459e815959Sopenharmony_ci        });
2469e815959Sopenharmony_ci    }
2479e815959Sopenharmony_ci
2489e815959Sopenharmony_ci    /**
2499e815959Sopenharmony_ci     * We have finished parsing the current part of the selector.
2509e815959Sopenharmony_ci     *
2519e815959Sopenharmony_ci     * Remove descendant tokens at the end if they exist,
2529e815959Sopenharmony_ci     * and return the last index, so that parsing can be
2539e815959Sopenharmony_ci     * picked up from here.
2549e815959Sopenharmony_ci     */
2559e815959Sopenharmony_ci    function finalizeSubselector() {
2569e815959Sopenharmony_ci        if (
2579e815959Sopenharmony_ci            tokens.length &&
2589e815959Sopenharmony_ci            tokens[tokens.length - 1].type === SelectorType.Descendant
2599e815959Sopenharmony_ci        ) {
2609e815959Sopenharmony_ci            tokens.pop();
2619e815959Sopenharmony_ci        }
2629e815959Sopenharmony_ci
2639e815959Sopenharmony_ci        if (tokens.length === 0) {
2649e815959Sopenharmony_ci            throw new Error("Empty sub-selector");
2659e815959Sopenharmony_ci        }
2669e815959Sopenharmony_ci
2679e815959Sopenharmony_ci        subselects.push(tokens);
2689e815959Sopenharmony_ci    }
2699e815959Sopenharmony_ci
2709e815959Sopenharmony_ci    stripWhitespace(0);
2719e815959Sopenharmony_ci
2729e815959Sopenharmony_ci    if (selector.length === selectorIndex) {
2739e815959Sopenharmony_ci        return selectorIndex;
2749e815959Sopenharmony_ci    }
2759e815959Sopenharmony_ci
2769e815959Sopenharmony_ci    loop: while (selectorIndex < selector.length) {
2779e815959Sopenharmony_ci        const firstChar = selector.charCodeAt(selectorIndex);
2789e815959Sopenharmony_ci
2799e815959Sopenharmony_ci        switch (firstChar) {
2809e815959Sopenharmony_ci            // Whitespace
2819e815959Sopenharmony_ci            case CharCode.Space:
2829e815959Sopenharmony_ci            case CharCode.Tab:
2839e815959Sopenharmony_ci            case CharCode.NewLine:
2849e815959Sopenharmony_ci            case CharCode.FormFeed:
2859e815959Sopenharmony_ci            case CharCode.CarriageReturn: {
2869e815959Sopenharmony_ci                if (
2879e815959Sopenharmony_ci                    tokens.length === 0 ||
2889e815959Sopenharmony_ci                    tokens[0].type !== SelectorType.Descendant
2899e815959Sopenharmony_ci                ) {
2909e815959Sopenharmony_ci                    ensureNotTraversal();
2919e815959Sopenharmony_ci                    tokens.push({ type: SelectorType.Descendant });
2929e815959Sopenharmony_ci                }
2939e815959Sopenharmony_ci
2949e815959Sopenharmony_ci                stripWhitespace(1);
2959e815959Sopenharmony_ci                break;
2969e815959Sopenharmony_ci            }
2979e815959Sopenharmony_ci            // Traversals
2989e815959Sopenharmony_ci            case CharCode.GreaterThan: {
2999e815959Sopenharmony_ci                addTraversal(SelectorType.Child);
3009e815959Sopenharmony_ci                stripWhitespace(1);
3019e815959Sopenharmony_ci                break;
3029e815959Sopenharmony_ci            }
3039e815959Sopenharmony_ci            case CharCode.LessThan: {
3049e815959Sopenharmony_ci                addTraversal(SelectorType.Parent);
3059e815959Sopenharmony_ci                stripWhitespace(1);
3069e815959Sopenharmony_ci                break;
3079e815959Sopenharmony_ci            }
3089e815959Sopenharmony_ci            case CharCode.Tilde: {
3099e815959Sopenharmony_ci                addTraversal(SelectorType.Sibling);
3109e815959Sopenharmony_ci                stripWhitespace(1);
3119e815959Sopenharmony_ci                break;
3129e815959Sopenharmony_ci            }
3139e815959Sopenharmony_ci            case CharCode.Plus: {
3149e815959Sopenharmony_ci                addTraversal(SelectorType.Adjacent);
3159e815959Sopenharmony_ci                stripWhitespace(1);
3169e815959Sopenharmony_ci                break;
3179e815959Sopenharmony_ci            }
3189e815959Sopenharmony_ci            // Special attribute selectors: .class, #id
3199e815959Sopenharmony_ci            case CharCode.Period: {
3209e815959Sopenharmony_ci                addSpecialAttribute("class", AttributeAction.Element);
3219e815959Sopenharmony_ci                break;
3229e815959Sopenharmony_ci            }
3239e815959Sopenharmony_ci            case CharCode.Hash: {
3249e815959Sopenharmony_ci                addSpecialAttribute("id", AttributeAction.Equals);
3259e815959Sopenharmony_ci                break;
3269e815959Sopenharmony_ci            }
3279e815959Sopenharmony_ci            case CharCode.LeftSquareBracket: {
3289e815959Sopenharmony_ci                stripWhitespace(1);
3299e815959Sopenharmony_ci
3309e815959Sopenharmony_ci                // Determine attribute name and namespace
3319e815959Sopenharmony_ci
3329e815959Sopenharmony_ci                let name: string;
3339e815959Sopenharmony_ci                let namespace: string | null = null;
3349e815959Sopenharmony_ci
3359e815959Sopenharmony_ci                if (selector.charCodeAt(selectorIndex) === CharCode.Pipe) {
3369e815959Sopenharmony_ci                    // Equivalent to no namespace
3379e815959Sopenharmony_ci                    name = getName(1);
3389e815959Sopenharmony_ci                } else if (selector.startsWith("*|", selectorIndex)) {
3399e815959Sopenharmony_ci                    namespace = "*";
3409e815959Sopenharmony_ci                    name = getName(2);
3419e815959Sopenharmony_ci                } else {
3429e815959Sopenharmony_ci                    name = getName(0);
3439e815959Sopenharmony_ci
3449e815959Sopenharmony_ci                    if (
3459e815959Sopenharmony_ci                        selector.charCodeAt(selectorIndex) === CharCode.Pipe &&
3469e815959Sopenharmony_ci                        selector.charCodeAt(selectorIndex + 1) !==
3479e815959Sopenharmony_ci                            CharCode.Equal
3489e815959Sopenharmony_ci                    ) {
3499e815959Sopenharmony_ci                        namespace = name;
3509e815959Sopenharmony_ci                        name = getName(1);
3519e815959Sopenharmony_ci                    }
3529e815959Sopenharmony_ci                }
3539e815959Sopenharmony_ci
3549e815959Sopenharmony_ci                stripWhitespace(0);
3559e815959Sopenharmony_ci
3569e815959Sopenharmony_ci                // Determine comparison operation
3579e815959Sopenharmony_ci
3589e815959Sopenharmony_ci                let action: AttributeAction = AttributeAction.Exists;
3599e815959Sopenharmony_ci                const possibleAction = actionTypes.get(
3609e815959Sopenharmony_ci                    selector.charCodeAt(selectorIndex)
3619e815959Sopenharmony_ci                );
3629e815959Sopenharmony_ci
3639e815959Sopenharmony_ci                if (possibleAction) {
3649e815959Sopenharmony_ci                    action = possibleAction;
3659e815959Sopenharmony_ci
3669e815959Sopenharmony_ci                    if (
3679e815959Sopenharmony_ci                        selector.charCodeAt(selectorIndex + 1) !==
3689e815959Sopenharmony_ci                        CharCode.Equal
3699e815959Sopenharmony_ci                    ) {
3709e815959Sopenharmony_ci                        throw new Error("Expected `=`");
3719e815959Sopenharmony_ci                    }
3729e815959Sopenharmony_ci
3739e815959Sopenharmony_ci                    stripWhitespace(2);
3749e815959Sopenharmony_ci                } else if (
3759e815959Sopenharmony_ci                    selector.charCodeAt(selectorIndex) === CharCode.Equal
3769e815959Sopenharmony_ci                ) {
3779e815959Sopenharmony_ci                    action = AttributeAction.Equals;
3789e815959Sopenharmony_ci                    stripWhitespace(1);
3799e815959Sopenharmony_ci                }
3809e815959Sopenharmony_ci
3819e815959Sopenharmony_ci                // Determine value
3829e815959Sopenharmony_ci
3839e815959Sopenharmony_ci                let value = "";
3849e815959Sopenharmony_ci                let ignoreCase: boolean | null = null;
3859e815959Sopenharmony_ci
3869e815959Sopenharmony_ci                if (action !== "exists") {
3879e815959Sopenharmony_ci                    if (isQuote(selector.charCodeAt(selectorIndex))) {
3889e815959Sopenharmony_ci                        const quote = selector.charCodeAt(selectorIndex);
3899e815959Sopenharmony_ci                        let sectionEnd = selectorIndex + 1;
3909e815959Sopenharmony_ci                        while (
3919e815959Sopenharmony_ci                            sectionEnd < selector.length &&
3929e815959Sopenharmony_ci                            (selector.charCodeAt(sectionEnd) !== quote ||
3939e815959Sopenharmony_ci                                isEscaped(sectionEnd))
3949e815959Sopenharmony_ci                        ) {
3959e815959Sopenharmony_ci                            sectionEnd += 1;
3969e815959Sopenharmony_ci                        }
3979e815959Sopenharmony_ci
3989e815959Sopenharmony_ci                        if (selector.charCodeAt(sectionEnd) !== quote) {
3999e815959Sopenharmony_ci                            throw new Error("Attribute value didn't end");
4009e815959Sopenharmony_ci                        }
4019e815959Sopenharmony_ci
4029e815959Sopenharmony_ci                        value = unescapeCSS(
4039e815959Sopenharmony_ci                            selector.slice(selectorIndex + 1, sectionEnd)
4049e815959Sopenharmony_ci                        );
4059e815959Sopenharmony_ci                        selectorIndex = sectionEnd + 1;
4069e815959Sopenharmony_ci                    } else {
4079e815959Sopenharmony_ci                        const valueStart = selectorIndex;
4089e815959Sopenharmony_ci
4099e815959Sopenharmony_ci                        while (
4109e815959Sopenharmony_ci                            selectorIndex < selector.length &&
4119e815959Sopenharmony_ci                            ((!isWhitespace(
4129e815959Sopenharmony_ci                                selector.charCodeAt(selectorIndex)
4139e815959Sopenharmony_ci                            ) &&
4149e815959Sopenharmony_ci                                selector.charCodeAt(selectorIndex) !==
4159e815959Sopenharmony_ci                                    CharCode.RightSquareBracket) ||
4169e815959Sopenharmony_ci                                isEscaped(selectorIndex))
4179e815959Sopenharmony_ci                        ) {
4189e815959Sopenharmony_ci                            selectorIndex += 1;
4199e815959Sopenharmony_ci                        }
4209e815959Sopenharmony_ci
4219e815959Sopenharmony_ci                        value = unescapeCSS(
4229e815959Sopenharmony_ci                            selector.slice(valueStart, selectorIndex)
4239e815959Sopenharmony_ci                        );
4249e815959Sopenharmony_ci                    }
4259e815959Sopenharmony_ci
4269e815959Sopenharmony_ci                    stripWhitespace(0);
4279e815959Sopenharmony_ci
4289e815959Sopenharmony_ci                    // See if we have a force ignore flag
4299e815959Sopenharmony_ci
4309e815959Sopenharmony_ci                    const forceIgnore =
4319e815959Sopenharmony_ci                        selector.charCodeAt(selectorIndex) | 0x20;
4329e815959Sopenharmony_ci
4339e815959Sopenharmony_ci                    // If the forceIgnore flag is set (either `i` or `s`), use that value
4349e815959Sopenharmony_ci                    if (forceIgnore === CharCode.LowerS) {
4359e815959Sopenharmony_ci                        ignoreCase = false;
4369e815959Sopenharmony_ci                        stripWhitespace(1);
4379e815959Sopenharmony_ci                    } else if (forceIgnore === CharCode.LowerI) {
4389e815959Sopenharmony_ci                        ignoreCase = true;
4399e815959Sopenharmony_ci                        stripWhitespace(1);
4409e815959Sopenharmony_ci                    }
4419e815959Sopenharmony_ci                }
4429e815959Sopenharmony_ci
4439e815959Sopenharmony_ci                if (
4449e815959Sopenharmony_ci                    selector.charCodeAt(selectorIndex) !==
4459e815959Sopenharmony_ci                    CharCode.RightSquareBracket
4469e815959Sopenharmony_ci                ) {
4479e815959Sopenharmony_ci                    throw new Error("Attribute selector didn't terminate");
4489e815959Sopenharmony_ci                }
4499e815959Sopenharmony_ci
4509e815959Sopenharmony_ci                selectorIndex += 1;
4519e815959Sopenharmony_ci
4529e815959Sopenharmony_ci                const attributeSelector: AttributeSelector = {
4539e815959Sopenharmony_ci                    type: SelectorType.Attribute,
4549e815959Sopenharmony_ci                    name,
4559e815959Sopenharmony_ci                    action,
4569e815959Sopenharmony_ci                    value,
4579e815959Sopenharmony_ci                    namespace,
4589e815959Sopenharmony_ci                    ignoreCase,
4599e815959Sopenharmony_ci                };
4609e815959Sopenharmony_ci
4619e815959Sopenharmony_ci                tokens.push(attributeSelector);
4629e815959Sopenharmony_ci                break;
4639e815959Sopenharmony_ci            }
4649e815959Sopenharmony_ci            case CharCode.Colon: {
4659e815959Sopenharmony_ci                if (selector.charCodeAt(selectorIndex + 1) === CharCode.Colon) {
4669e815959Sopenharmony_ci                    tokens.push({
4679e815959Sopenharmony_ci                        type: SelectorType.PseudoElement,
4689e815959Sopenharmony_ci                        name: getName(2).toLowerCase(),
4699e815959Sopenharmony_ci                        data:
4709e815959Sopenharmony_ci                            selector.charCodeAt(selectorIndex) ===
4719e815959Sopenharmony_ci                            CharCode.LeftParenthesis
4729e815959Sopenharmony_ci                                ? readValueWithParenthesis()
4739e815959Sopenharmony_ci                                : null,
4749e815959Sopenharmony_ci                    });
4759e815959Sopenharmony_ci                    continue;
4769e815959Sopenharmony_ci                }
4779e815959Sopenharmony_ci
4789e815959Sopenharmony_ci                const name = getName(1).toLowerCase();
4799e815959Sopenharmony_ci                let data: DataType = null;
4809e815959Sopenharmony_ci
4819e815959Sopenharmony_ci                if (
4829e815959Sopenharmony_ci                    selector.charCodeAt(selectorIndex) ===
4839e815959Sopenharmony_ci                    CharCode.LeftParenthesis
4849e815959Sopenharmony_ci                ) {
4859e815959Sopenharmony_ci                    if (unpackPseudos.has(name)) {
4869e815959Sopenharmony_ci                        if (isQuote(selector.charCodeAt(selectorIndex + 1))) {
4879e815959Sopenharmony_ci                            throw new Error(
4889e815959Sopenharmony_ci                                `Pseudo-selector ${name} cannot be quoted`
4899e815959Sopenharmony_ci                            );
4909e815959Sopenharmony_ci                        }
4919e815959Sopenharmony_ci
4929e815959Sopenharmony_ci                        data = [];
4939e815959Sopenharmony_ci                        selectorIndex = parseSelector(
4949e815959Sopenharmony_ci                            data,
4959e815959Sopenharmony_ci                            selector,
4969e815959Sopenharmony_ci                            selectorIndex + 1
4979e815959Sopenharmony_ci                        );
4989e815959Sopenharmony_ci
4999e815959Sopenharmony_ci                        if (
5009e815959Sopenharmony_ci                            selector.charCodeAt(selectorIndex) !==
5019e815959Sopenharmony_ci                            CharCode.RightParenthesis
5029e815959Sopenharmony_ci                        ) {
5039e815959Sopenharmony_ci                            throw new Error(
5049e815959Sopenharmony_ci                                `Missing closing parenthesis in :${name} (${selector})`
5059e815959Sopenharmony_ci                            );
5069e815959Sopenharmony_ci                        }
5079e815959Sopenharmony_ci
5089e815959Sopenharmony_ci                        selectorIndex += 1;
5099e815959Sopenharmony_ci                    } else {
5109e815959Sopenharmony_ci                        data = readValueWithParenthesis();
5119e815959Sopenharmony_ci
5129e815959Sopenharmony_ci                        if (stripQuotesFromPseudos.has(name)) {
5139e815959Sopenharmony_ci                            const quot = data.charCodeAt(0);
5149e815959Sopenharmony_ci
5159e815959Sopenharmony_ci                            if (
5169e815959Sopenharmony_ci                                quot === data.charCodeAt(data.length - 1) &&
5179e815959Sopenharmony_ci                                isQuote(quot)
5189e815959Sopenharmony_ci                            ) {
5199e815959Sopenharmony_ci                                data = data.slice(1, -1);
5209e815959Sopenharmony_ci                            }
5219e815959Sopenharmony_ci                        }
5229e815959Sopenharmony_ci
5239e815959Sopenharmony_ci                        data = unescapeCSS(data);
5249e815959Sopenharmony_ci                    }
5259e815959Sopenharmony_ci                }
5269e815959Sopenharmony_ci
5279e815959Sopenharmony_ci                tokens.push({ type: SelectorType.Pseudo, name, data });
5289e815959Sopenharmony_ci                break;
5299e815959Sopenharmony_ci            }
5309e815959Sopenharmony_ci            case CharCode.Comma: {
5319e815959Sopenharmony_ci                finalizeSubselector();
5329e815959Sopenharmony_ci                tokens = [];
5339e815959Sopenharmony_ci                stripWhitespace(1);
5349e815959Sopenharmony_ci                break;
5359e815959Sopenharmony_ci            }
5369e815959Sopenharmony_ci            default: {
5379e815959Sopenharmony_ci                if (selector.startsWith("/*", selectorIndex)) {
5389e815959Sopenharmony_ci                    const endIndex = selector.indexOf("*/", selectorIndex + 2);
5399e815959Sopenharmony_ci
5409e815959Sopenharmony_ci                    if (endIndex < 0) {
5419e815959Sopenharmony_ci                        throw new Error("Comment was not terminated");
5429e815959Sopenharmony_ci                    }
5439e815959Sopenharmony_ci
5449e815959Sopenharmony_ci                    selectorIndex = endIndex + 2;
5459e815959Sopenharmony_ci
5469e815959Sopenharmony_ci                    // Remove leading whitespace
5479e815959Sopenharmony_ci                    if (tokens.length === 0) {
5489e815959Sopenharmony_ci                        stripWhitespace(0);
5499e815959Sopenharmony_ci                    }
5509e815959Sopenharmony_ci
5519e815959Sopenharmony_ci                    break;
5529e815959Sopenharmony_ci                }
5539e815959Sopenharmony_ci
5549e815959Sopenharmony_ci                let namespace = null;
5559e815959Sopenharmony_ci                let name: string;
5569e815959Sopenharmony_ci
5579e815959Sopenharmony_ci                if (firstChar === CharCode.Asterisk) {
5589e815959Sopenharmony_ci                    selectorIndex += 1;
5599e815959Sopenharmony_ci                    name = "*";
5609e815959Sopenharmony_ci                } else if (firstChar === CharCode.Pipe) {
5619e815959Sopenharmony_ci                    name = "";
5629e815959Sopenharmony_ci
5639e815959Sopenharmony_ci                    if (
5649e815959Sopenharmony_ci                        selector.charCodeAt(selectorIndex + 1) === CharCode.Pipe
5659e815959Sopenharmony_ci                    ) {
5669e815959Sopenharmony_ci                        addTraversal(SelectorType.ColumnCombinator);
5679e815959Sopenharmony_ci                        stripWhitespace(2);
5689e815959Sopenharmony_ci                        break;
5699e815959Sopenharmony_ci                    }
5709e815959Sopenharmony_ci                } else if (reName.test(selector.slice(selectorIndex))) {
5719e815959Sopenharmony_ci                    name = getName(0);
5729e815959Sopenharmony_ci                } else {
5739e815959Sopenharmony_ci                    break loop;
5749e815959Sopenharmony_ci                }
5759e815959Sopenharmony_ci
5769e815959Sopenharmony_ci                if (
5779e815959Sopenharmony_ci                    selector.charCodeAt(selectorIndex) === CharCode.Pipe &&
5789e815959Sopenharmony_ci                    selector.charCodeAt(selectorIndex + 1) !== CharCode.Pipe
5799e815959Sopenharmony_ci                ) {
5809e815959Sopenharmony_ci                    namespace = name;
5819e815959Sopenharmony_ci                    if (
5829e815959Sopenharmony_ci                        selector.charCodeAt(selectorIndex + 1) ===
5839e815959Sopenharmony_ci                        CharCode.Asterisk
5849e815959Sopenharmony_ci                    ) {
5859e815959Sopenharmony_ci                        name = "*";
5869e815959Sopenharmony_ci                        selectorIndex += 2;
5879e815959Sopenharmony_ci                    } else {
5889e815959Sopenharmony_ci                        name = getName(1);
5899e815959Sopenharmony_ci                    }
5909e815959Sopenharmony_ci                }
5919e815959Sopenharmony_ci
5929e815959Sopenharmony_ci                tokens.push(
5939e815959Sopenharmony_ci                    name === "*"
5949e815959Sopenharmony_ci                        ? { type: SelectorType.Universal, namespace }
5959e815959Sopenharmony_ci                        : { type: SelectorType.Tag, name, namespace }
5969e815959Sopenharmony_ci                );
5979e815959Sopenharmony_ci            }
5989e815959Sopenharmony_ci        }
5999e815959Sopenharmony_ci    }
6009e815959Sopenharmony_ci
6019e815959Sopenharmony_ci    finalizeSubselector();
6029e815959Sopenharmony_ci    return selectorIndex;
6039e815959Sopenharmony_ci}
604