19e815959Sopenharmony_ciimport { Selector, SelectorType, AttributeAction } from "./types";
29e815959Sopenharmony_ci
39e815959Sopenharmony_ciconst attribValChars = ["\\", '"'];
49e815959Sopenharmony_ciconst pseudoValChars = [...attribValChars, "(", ")"];
59e815959Sopenharmony_ci
69e815959Sopenharmony_ciconst charsToEscapeInAttributeValue = new Set(
79e815959Sopenharmony_ci    attribValChars.map((c) => c.charCodeAt(0))
89e815959Sopenharmony_ci);
99e815959Sopenharmony_ciconst charsToEscapeInPseudoValue = new Set(
109e815959Sopenharmony_ci    pseudoValChars.map((c) => c.charCodeAt(0))
119e815959Sopenharmony_ci);
129e815959Sopenharmony_ciconst charsToEscapeInName = new Set(
139e815959Sopenharmony_ci    [
149e815959Sopenharmony_ci        ...pseudoValChars,
159e815959Sopenharmony_ci        "~",
169e815959Sopenharmony_ci        "^",
179e815959Sopenharmony_ci        "$",
189e815959Sopenharmony_ci        "*",
199e815959Sopenharmony_ci        "+",
209e815959Sopenharmony_ci        "!",
219e815959Sopenharmony_ci        "|",
229e815959Sopenharmony_ci        ":",
239e815959Sopenharmony_ci        "[",
249e815959Sopenharmony_ci        "]",
259e815959Sopenharmony_ci        " ",
269e815959Sopenharmony_ci        ".",
279e815959Sopenharmony_ci    ].map((c) => c.charCodeAt(0))
289e815959Sopenharmony_ci);
299e815959Sopenharmony_ci
309e815959Sopenharmony_ci/**
319e815959Sopenharmony_ci * Turns `selector` back into a string.
329e815959Sopenharmony_ci *
339e815959Sopenharmony_ci * @param selector Selector to stringify.
349e815959Sopenharmony_ci */
359e815959Sopenharmony_ciexport function stringify(selector: Selector[][]): string {
369e815959Sopenharmony_ci    return selector
379e815959Sopenharmony_ci        .map((token) => token.map(stringifyToken).join(""))
389e815959Sopenharmony_ci        .join(", ");
399e815959Sopenharmony_ci}
409e815959Sopenharmony_ci
419e815959Sopenharmony_cifunction stringifyToken(
429e815959Sopenharmony_ci    token: Selector,
439e815959Sopenharmony_ci    index: number,
449e815959Sopenharmony_ci    arr: Selector[]
459e815959Sopenharmony_ci): string {
469e815959Sopenharmony_ci    switch (token.type) {
479e815959Sopenharmony_ci        // Simple types
489e815959Sopenharmony_ci        case SelectorType.Child:
499e815959Sopenharmony_ci            return index === 0 ? "> " : " > ";
509e815959Sopenharmony_ci        case SelectorType.Parent:
519e815959Sopenharmony_ci            return index === 0 ? "< " : " < ";
529e815959Sopenharmony_ci        case SelectorType.Sibling:
539e815959Sopenharmony_ci            return index === 0 ? "~ " : " ~ ";
549e815959Sopenharmony_ci        case SelectorType.Adjacent:
559e815959Sopenharmony_ci            return index === 0 ? "+ " : " + ";
569e815959Sopenharmony_ci        case SelectorType.Descendant:
579e815959Sopenharmony_ci            return " ";
589e815959Sopenharmony_ci        case SelectorType.ColumnCombinator:
599e815959Sopenharmony_ci            return index === 0 ? "|| " : " || ";
609e815959Sopenharmony_ci        case SelectorType.Universal:
619e815959Sopenharmony_ci            // Return an empty string if the selector isn't needed.
629e815959Sopenharmony_ci            return token.namespace === "*" &&
639e815959Sopenharmony_ci                index + 1 < arr.length &&
649e815959Sopenharmony_ci                "name" in arr[index + 1]
659e815959Sopenharmony_ci                ? ""
669e815959Sopenharmony_ci                : `${getNamespace(token.namespace)}*`;
679e815959Sopenharmony_ci
689e815959Sopenharmony_ci        case SelectorType.Tag:
699e815959Sopenharmony_ci            return getNamespacedName(token);
709e815959Sopenharmony_ci
719e815959Sopenharmony_ci        case SelectorType.PseudoElement:
729e815959Sopenharmony_ci            return `::${escapeName(token.name, charsToEscapeInName)}${
739e815959Sopenharmony_ci                token.data === null
749e815959Sopenharmony_ci                    ? ""
759e815959Sopenharmony_ci                    : `(${escapeName(token.data, charsToEscapeInPseudoValue)})`
769e815959Sopenharmony_ci            }`;
779e815959Sopenharmony_ci
789e815959Sopenharmony_ci        case SelectorType.Pseudo:
799e815959Sopenharmony_ci            return `:${escapeName(token.name, charsToEscapeInName)}${
809e815959Sopenharmony_ci                token.data === null
819e815959Sopenharmony_ci                    ? ""
829e815959Sopenharmony_ci                    : `(${
839e815959Sopenharmony_ci                          typeof token.data === "string"
849e815959Sopenharmony_ci                              ? escapeName(
859e815959Sopenharmony_ci                                    token.data,
869e815959Sopenharmony_ci                                    charsToEscapeInPseudoValue
879e815959Sopenharmony_ci                                )
889e815959Sopenharmony_ci                              : stringify(token.data)
899e815959Sopenharmony_ci                      })`
909e815959Sopenharmony_ci            }`;
919e815959Sopenharmony_ci
929e815959Sopenharmony_ci        case SelectorType.Attribute: {
939e815959Sopenharmony_ci            if (
949e815959Sopenharmony_ci                token.name === "id" &&
959e815959Sopenharmony_ci                token.action === AttributeAction.Equals &&
969e815959Sopenharmony_ci                token.ignoreCase === "quirks" &&
979e815959Sopenharmony_ci                !token.namespace
989e815959Sopenharmony_ci            ) {
999e815959Sopenharmony_ci                return `#${escapeName(token.value, charsToEscapeInName)}`;
1009e815959Sopenharmony_ci            }
1019e815959Sopenharmony_ci            if (
1029e815959Sopenharmony_ci                token.name === "class" &&
1039e815959Sopenharmony_ci                token.action === AttributeAction.Element &&
1049e815959Sopenharmony_ci                token.ignoreCase === "quirks" &&
1059e815959Sopenharmony_ci                !token.namespace
1069e815959Sopenharmony_ci            ) {
1079e815959Sopenharmony_ci                return `.${escapeName(token.value, charsToEscapeInName)}`;
1089e815959Sopenharmony_ci            }
1099e815959Sopenharmony_ci
1109e815959Sopenharmony_ci            const name = getNamespacedName(token);
1119e815959Sopenharmony_ci
1129e815959Sopenharmony_ci            if (token.action === AttributeAction.Exists) {
1139e815959Sopenharmony_ci                return `[${name}]`;
1149e815959Sopenharmony_ci            }
1159e815959Sopenharmony_ci
1169e815959Sopenharmony_ci            return `[${name}${getActionValue(token.action)}="${escapeName(
1179e815959Sopenharmony_ci                token.value,
1189e815959Sopenharmony_ci                charsToEscapeInAttributeValue
1199e815959Sopenharmony_ci            )}"${
1209e815959Sopenharmony_ci                token.ignoreCase === null ? "" : token.ignoreCase ? " i" : " s"
1219e815959Sopenharmony_ci            }]`;
1229e815959Sopenharmony_ci        }
1239e815959Sopenharmony_ci    }
1249e815959Sopenharmony_ci}
1259e815959Sopenharmony_ci
1269e815959Sopenharmony_cifunction getActionValue(action: AttributeAction): string {
1279e815959Sopenharmony_ci    switch (action) {
1289e815959Sopenharmony_ci        case AttributeAction.Equals:
1299e815959Sopenharmony_ci            return "";
1309e815959Sopenharmony_ci        case AttributeAction.Element:
1319e815959Sopenharmony_ci            return "~";
1329e815959Sopenharmony_ci        case AttributeAction.Start:
1339e815959Sopenharmony_ci            return "^";
1349e815959Sopenharmony_ci        case AttributeAction.End:
1359e815959Sopenharmony_ci            return "$";
1369e815959Sopenharmony_ci        case AttributeAction.Any:
1379e815959Sopenharmony_ci            return "*";
1389e815959Sopenharmony_ci        case AttributeAction.Not:
1399e815959Sopenharmony_ci            return "!";
1409e815959Sopenharmony_ci        case AttributeAction.Hyphen:
1419e815959Sopenharmony_ci            return "|";
1429e815959Sopenharmony_ci        case AttributeAction.Exists:
1439e815959Sopenharmony_ci            throw new Error("Shouldn't be here");
1449e815959Sopenharmony_ci    }
1459e815959Sopenharmony_ci}
1469e815959Sopenharmony_ci
1479e815959Sopenharmony_cifunction getNamespacedName(token: {
1489e815959Sopenharmony_ci    name: string;
1499e815959Sopenharmony_ci    namespace: string | null;
1509e815959Sopenharmony_ci}): string {
1519e815959Sopenharmony_ci    return `${getNamespace(token.namespace)}${escapeName(
1529e815959Sopenharmony_ci        token.name,
1539e815959Sopenharmony_ci        charsToEscapeInName
1549e815959Sopenharmony_ci    )}`;
1559e815959Sopenharmony_ci}
1569e815959Sopenharmony_ci
1579e815959Sopenharmony_cifunction getNamespace(namespace: string | null): string {
1589e815959Sopenharmony_ci    return namespace !== null
1599e815959Sopenharmony_ci        ? `${
1609e815959Sopenharmony_ci              namespace === "*"
1619e815959Sopenharmony_ci                  ? "*"
1629e815959Sopenharmony_ci                  : escapeName(namespace, charsToEscapeInName)
1639e815959Sopenharmony_ci          }|`
1649e815959Sopenharmony_ci        : "";
1659e815959Sopenharmony_ci}
1669e815959Sopenharmony_ci
1679e815959Sopenharmony_cifunction escapeName(str: string, charsToEscape: Set<number>): string {
1689e815959Sopenharmony_ci    let lastIdx = 0;
1699e815959Sopenharmony_ci    let ret = "";
1709e815959Sopenharmony_ci
1719e815959Sopenharmony_ci    for (let i = 0; i < str.length; i++) {
1729e815959Sopenharmony_ci        if (charsToEscape.has(str.charCodeAt(i))) {
1739e815959Sopenharmony_ci            ret += `${str.slice(lastIdx, i)}\\${str.charAt(i)}`;
1749e815959Sopenharmony_ci            lastIdx = i + 1;
1759e815959Sopenharmony_ci        }
1769e815959Sopenharmony_ci    }
1779e815959Sopenharmony_ci
1789e815959Sopenharmony_ci    return ret.length > 0 ? ret + str.slice(lastIdx) : str;
1799e815959Sopenharmony_ci}
180