UNPKG

@tbela99/css-parser

Version:

CSS parser, minifier and validator for node and the browser

1,142 lines (1,139 loc) 44.5 kB
import { EnumToken, ColorType } from '../ast/types.js'; import { walkValues, WalkerOptionEnum } from '../ast/walk.js'; import { toDegrees } from '../parser/utils/angle.js'; import { memoize } from '../parser/utils/cache.js'; import { equalsIgnoreCase } from '../parser/utils/text.js'; import { trimArray } from '../validation/match.js'; import { splitTokenList } from '../validation/utils/list.js'; import { getColorSpace } from './color/utils/colorspace.js'; import { getColorComponents } from './color/utils/components.js'; import { nonStandardColors, systemColors, deprecatedSystemColors, COLORS_NAMES, colorsFunc, colorFuncColorSpace, LOC } from './constants.js'; import { isOkLabClose } from './color/utils/distance.js'; // https://www.w3.org/TR/CSS21/syndata.html#syntax // https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/#typedef-ident-token // '\\' const REVERSE_SOLIDUS = 0x5c; const dimensionUnits = new Set([ "q", "cap", "ch", "cm", "cqb", "cqh", "cqi", "cqmax", "cqmin", "cqw", "dvb", "dvh", "dvi", "dvmax", "dvmin", "dvw", "em", "ex", "ic", "in", "lh", "lvb", "lvh", "lvi", "lvmax", "lvw", "mm", "pc", "pt", "px", "rem", "rlh", "svb", "svh", "svi", "svmin", "svw", "vb", "vh", "vi", "vmax", "vmin", "vw", ]); // https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions // https://developer.mozilla.org/en-US/docs/Web/CSS/Mozilla_Extensions const pseudoAliasMap = { "-moz-center": "center", "-webkit-center": "center", "-ms-grid-columns": "grid-template-columns", "-ms-grid-rows": "grid-template-rows", "-ms-grid-row": "grid-row-start", "-ms-grid-column": "grid-column-start", "-ms-grid-row-align": "align-self", "-ms-grid-row-span": "grid-row-end", "-ms-grid-column-span": "grid-column-end", "-ms-grid-column-align": "justify-self", ":-ms-input-placeholder": "::placeholder", "::-ms-input-placeholder": "::placeholder", ":-moz-any()": ":is", "-moz-user-modify": "user-modify", "-webkit-match-parent": "match-parent", "-moz-background-clip": "background-clip", "-moz-background-origin": "background-origin", "-ms-input-placeholder": "placeholder", ":-webkit-autofill": ":autofill", ":-webkit-any()": ":is", "::-webkit-input-placeholder": "::placeholder", "::-webkit-file-upload-button": "::file-selector-button", "::-moz-placeholder": "::placeholder", ":-webkit-any-link": ":any-link", "-webkit-border-after": "border-block-end", "-webkit-border-after-color": "border-block-end-color", "-webkit-border-after-style": "border-block-end-style", "-webkit-border-after-width": "border-block-end-width", "-webkit-border-before": "border-block-start", "-webkit-border-before-color": "border-block-start-color", "-webkit-border-before-style": "border-block-start-style", "-webkit-border-before-width": "border-block-start-width", "-webkit-border-end": "border-inline-end", "-webkit-border-end-color": "border-inline-end-color", "-webkit-border-end-style": "border-inline-end-style", "-webkit-border-end-width": "border-inline-end-width", "-webkit-border-start": "border-inline-start", "-webkit-border-start-color": "border-inline-start-color", "-webkit-border-start-style": "border-inline-start-style", "-webkit-border-start-width": "border-inline-start-width", "-webkit-box-align": "align-items", "-webkit-box-direction": "flex-direction", "-webkit-box-flex": "flex-grow", "-webkit-box-lines": "flex-flow", "-webkit-box-ordinal-group": "order", "-webkit-box-orient": "flex-direction", "-webkit-box-pack": "justify-content", "-webkit-column-break-after": "break-after", "-webkit-column-break-before": "break-before", "-webkit-column-break-inside": "break-inside", "-webkit-font-feature-settings": "font-feature-settings", "-webkit-hyphenate-character": "hyphenate-character", "-webkit-initial-letter": "initial-letter", "-webkit-margin-end": "margin-block-end", "-webkit-margin-start": "margin-block-start", "-webkit-padding-after": "padding-block-end", "-webkit-padding-before": "padding-block-start", "-webkit-padding-end": "padding-inline-end", "-webkit-padding-start": "padding-inline-start", "-webkit-min-device-pixel-ratio": "min-resolution", "-webkit-max-device-pixel-ratio": "max-resolution", "-webkit-font-smoothing": "font-smooth", "-webkit-line-clamp": "line-clamp", ":-webkit-autofill-strong-password": ":autofill", ":-webkit-full-page-media": ":fullscreen", ":-webkit-full-screen": ":fullscreen", ":-webkit-full-screen-ancestor": ":fullscreen", ":-webkit-full-screen-document": ":fullscreen", ":-webkit-full-screen-controls-hidden": ":fullscreen", "-moz-background-inline-policy": "box-decoration-break", "-moz-background-size": "background-size", "-moz-border-end": "border-inline-end", "-moz-border-end-color": "border-inline-end-color", "-moz-border-end-style": "border-inline-end-style", "-moz-border-end-width": "border-inline-end-width", "-moz-border-image": "border-inline-end-width", "-moz-border-start": "border-inline-start", "-moz-border-start-color": "border-inline-start-color", "-moz-border-start-style": "border-inline-start-style", "-moz-border-start-width": "border-inline-start-width", "-moz-column-count": "column-count", "-moz-column-fill": "column-fill", "-moz-column-gap": "column-gap", "-moz-column-width": "column-width", "-moz-column-rule": "column-rule", "-moz-column-rule-width": "column-rule-width", "-moz-column-rule-style": "column-rule-style", "-moz-column-rule-color": "column-rule-color", "-moz-margin-end": "margin-inline-end", "-moz-margin-start": "margin-inline-start", "-moz-opacity": "opacity", "-moz-outline": "outline", "-moz-outline-color": "outline-color", "-moz-outline-offset": "outline-offset", "-moz-outline-style": "outline-style", "-moz-outline-width": "outline-width", "-moz-padding-end": "padding-inline-end", "-moz-padding-start": "padding-inline-start", "-moz-tab-size": "tab-size", "-moz-text-align-last": "text-align-last", "-moz-text-decoration-color": "text-decoration-color", "-moz-text-decoration-line": "text-decoration-line", "-moz-text-decoration-style": "text-decoration-style", "-moz-transition": "transition", "-moz-transition-delay": "transition-delay", "-moz-transition-duration": "transition-duration", "-moz-transition-property": "transition-property", "-moz-transition-timing-function": "transition-timing-function", "-moz-user-select": "user-select", "-moz-initial": "initial", "-moz-linear-gradient()": "linear-gradient", "-moz-radial-gradient()": "radial-gradient", "-moz-element()": "element", "-moz-crisp-edges": "crisp-edges", "-moz-calc()": "calc", "-moz-min-content": "min-content", "-moz-fit-content": "fit-content", "-moz-max-content": "max-content", "-moz-available": "stretch", ":-moz-any-link": ":any-link", ":-moz-full-screen": ":fullscreen", ":-moz-full-screen-ancestor": ":fullscreen", ":-moz-placeholder": ":placeholder-shown", ":-moz-read-only": ":read-only", ":-moz-read-write": ":read-write", ":-moz-submit-invalid": ":invalid", ":-moz-ui-invalid": ":user-invalid", ":-moz-ui-valid": ":user-valid", "::-moz-selection": "::selection", }; // renamed standard properties const renamedStandardProperties = new Map([["color-adjust", "print-color-adjust"]]); function isLength(dimension) { return "unit" in dimension && dimensionUnits.has(dimension.unit.toLowerCase()); } function isResolution(dimension) { return "unit" in dimension && ["dpi", "dpcm", "dppx", "x"].includes(dimension.unit.toLowerCase()); } function isAngle(dimension) { return "unit" in dimension && ["rad", "turn", "deg", "grad"].includes(dimension.unit.toLowerCase()); } function isTime(dimension) { return "unit" in dimension && ["ms", "s"].includes(dimension.unit.toLowerCase()); } function isFrequency(dimension) { return "unit" in dimension && ["hz", "khz"].includes(dimension.unit.toLowerCase()); } /** * Reduce color stops * @param stops * @returns */ function reduceColorStops(stops) { const parts = splitTokenList(stops); const n = parts.length == 1 ? 1 : parts.length - 1; let j; let i; let k = -1; let updated = false; for (i = 0; i < parts.length; i++) { k++; if (parts[i].length != 3) { continue; } if (i > 0 && isOkLabClose(parts[i - 1][0], parts[i][0])) { if (parts[i - 1].length == 1) { parts[i - 1].push({ typ: EnumToken.WhitespaceTokenType }, { typ: EnumToken.PercentageTokenType, val: ((k - 1) * 100) / n }); } parts[i - 1].push(...parts[i].slice(1)); parts.splice(i--, 1); updated = true; continue; } for (j = 0; j < parts[i].length; j++) { if ((parts[i][j].typ == EnumToken.LengthTokenType && 0 == parts[i][j].val) || parts[i][j].typ == EnumToken.NumberTokenType || parts[i][j].typ == EnumToken.PercentageTokenType) { if (parts[i][j].val === (k * 100) / n) { parts[i].length = j; trimArray(parts[i]); updated = true; break; } } } } if (updated) { stops.length = 0; for (j = 0; j < parts.length; j++) { if (stops.length > 0) { stops.push({ typ: EnumToken.CommaTokenType }); } stops.push(...parts[j]); } } return stops; } /** * Reduce background-position values. * @param positions * @param position */ function reducegradientBackgroundPosition(positions, position) { switch (position) { case "50%": case "50% 50%": case "center": case "center center": positions.length = 0; break; case "0% 50%": case "0 50%": case "left": case "left center": case "center left": positions.length = 0; positions.push({ typ: EnumToken.PercentageTokenType, val: 0 }); break; case "50% 0%": case "50% 0": case "top center": case "center top": positions.length = 0; positions.push({ typ: EnumToken.IdenTokenType, val: "top" }); break; case "bottom center": case "center bottom": case "bottom": case "50% 100%": positions.length = 0; positions.push({ typ: EnumToken.IdenTokenType, val: "bottom" }); break; // case "left": // case "0 50%": // case "0% 50%": // case "left center": // case "center left": // positions.length = 0; // positions.push({ typ: EnumToken.PercentageTokenType, val: 0 }); // break; case "right center": case "center right": case "100% 50%": case "right": positions.length = 0; positions.push({ typ: EnumToken.PercentageTokenType, val: 100 }); break; case "bottom left": case "left bottom": positions.length = 0; positions.push({ typ: EnumToken.PercentageTokenType, val: 0 }, { typ: EnumToken.WhitespaceTokenType }, { typ: EnumToken.PercentageTokenType, val: 100 }); break; case "bottom right": case "right bottom": positions.length = 0; positions.push({ typ: EnumToken.PercentageTokenType, val: 100 }, { typ: EnumToken.WhitespaceTokenType }, { typ: EnumToken.PercentageTokenType, val: 100 }); break; case "top left": case "left top": positions.length = 0; positions.push({ typ: EnumToken.PercentageTokenType, val: 0 }, { typ: EnumToken.WhitespaceTokenType }, { typ: EnumToken.PercentageTokenType, val: 0 }); break; case "top right": case "right top": positions.length = 0; positions.push({ typ: EnumToken.PercentageTokenType, val: 100 }, { typ: EnumToken.WhitespaceTokenType }, { typ: EnumToken.PercentageTokenType, val: 0 }); break; } } /** * Reduce conic-gradient color stops * @param stops * @returns */ function reduceConicColorStops(stops) { const parts = splitTokenList(stops); const n = parts.length == 1 ? 1 : parts.length - 1; let j; let i; let k = -1; let updated = false; for (i = 0; i < parts.length; i++) { k++; if (parts[i].length != 3) { continue; } if (i > 0 && isOkLabClose(parts[i - 1][0], parts[i][0])) { if (parts[i - 1].length == 1) { parts[i - 1].push({ typ: EnumToken.WhitespaceTokenType }, { typ: EnumToken.AngleTokenType, val: ((k - 1) * 100) / n, unit: "deg" }); } parts[i - 1].push(...parts[i].slice(1)); parts.splice(i--, 1); updated = true; continue; } for (j = 0; j < parts[i].length; j++) { if ((parts[i][j].typ == EnumToken.NumberTokenType && 0 == parts[i][j].val) || parts[i][j].typ == EnumToken.AngleTokenType) { if (toDegrees(parts[i][j]).val === (k * 360) / n) { parts[i].length = j; trimArray(parts[i]); updated = true; break; } } } } if (updated) { stops.length = 0; for (j = 0; j < parts.length; j++) { if (stops.length > 0) { stops.push({ typ: EnumToken.CommaTokenType }); } stops.push(...parts[j]); } } return stops; } /** * is rectangular orthogonal colorspace * @param token * @returns */ function isRectangularOrthogonalColorspace(token) { return (token.typ === EnumToken.IdenTokenType && colorFuncColorSpace.some((t) => equalsIgnoreCase(t, token.val))); } /** * Is polar colorspace * @param token * @returns */ function isPolarColorspace(token) { return (token.typ === EnumToken.IdenTokenType && ["hsl", "hwb", "lch", "oklch"].some((t) => equalsIgnoreCase(t, token.val))); } /** * Is ident color * @param token * @returns */ function isIdentColor(token) { return (token.typ == EnumToken.ColorTokenType && [ColorType.SYS, ColorType.DPSYS, ColorType.LIT].includes(token.kin) && isIdent(token.val)); } function isColor(token, errors) { if (token.typ == EnumToken.WildCardFunctionTokenType) { return true; } if (token.typ == EnumToken.ColorTokenType) { if ("kin" in token && !("chi" in token)) { return true; } } if (token.typ == EnumToken.IdenTokenType) { const val = token.val.toLowerCase(); if (systemColors.has(val) || deprecatedSystemColors.has(val) || nonStandardColors.has(val)) { return true; } // named color return val in COLORS_NAMES || "currentcolor" === val || "transparent" === val; } if (token.typ === EnumToken.FunctionTokenType || token.typ === EnumToken.ColorTokenType) { // if (!colorsFunc.includes((token as FunctionToken).val.toLowerCase())) { // return false; // } if (token.chi.length > 0) { // @ts-ignore if (token.val === "light-dark") { // @ts-ignore const children = token.chi.filter((t) => [ EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.LiteralTokenType, EnumToken.ColorTokenType, EnumToken.FunctionTokenType, EnumToken.PercentageTokenType, EnumToken.WildCardFunctionTokenType, ].includes(t.typ)); if (children.length != 2) { errors?.push({ message: "light-dark function must have 2 arguments", node: token, action: "drop", }); return false; } if (isColor(children[0]) && isColor(children[1])) { return true; } } // adding numbers and percentages is disallowed // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/lch#defining_relative_color_output_channel_components:~:text=Adding%20a%20%3Cpercentage%3E%20to%20a%20%3Cnumber%3E%2C%20for%20example%2C%20doesn%27t%20work const components = getColorComponents(token); if (components !== null) { const colorSpace = getColorSpace(token)?.split?.(""); if (colorSpace != null) { for (const value of components) { if (value.typ === EnumToken.IdenTokenType) { const val = value.val.toLowerCase(); if ( // @ts-expect-error typeof Math[val.toUpperCase()] !== "number" && val != "in" && val != "hue" && val != "from" && val != "alpha" && val != "none" && val != "shorter" && val != "longer" && val != "increasing" && val != "decreasing" && !colorsFunc.includes(val) && !colorSpace.includes(val) && !colorFuncColorSpace.includes(val)) { errors?.push({ action: "drop", message: `Unexpected constant '${val}'`, node: value, location: value[LOC], }); return false; } } else if (value.typ === EnumToken.MathFunctionTokenType && equalsIgnoreCase("calc", value.val)) { let val; for (const v of walkValues(value.chi)) { if (v.value.typ === EnumToken.IdenTokenType) { val = v.value.val.toLowerCase(); if ( // @ts-expect-error typeof Math[val.toUpperCase()] !== "number" && val != "in" && val != "hue" && val != "from" && val != "alpha" && val != "none" && val != "shorter" && val != "longer" && val != "increasing" && val != "decreasing" && !colorsFunc.includes(val) && !colorSpace.includes(val) && !colorFuncColorSpace.includes(val)) { errors?.push({ action: "drop", message: `Unexpected constant '${val}'`, node: v.value, location: v.value[LOC], }); return false; } } } } } } } // @ts-ignore for (const { value, parent } of walkValues(token.chi, token, (value) => value.typ === EnumToken.WildCardFunctionTokenType ? WalkerOptionEnum.Ignore | WalkerOptionEnum.IgnoreChildren : null)) { let k = 0; let l; let tk = null; let tl = null; if (value.typ === EnumToken.BinaryExpressionTokenType) { tk = value.l; tl = value.r; } else if (parent?.typ === EnumToken.MathFunctionTokenType && parent.val === "calc") { l = k + 1; while (l + 1 < parent.chi.length) { const tk = parent.chi[l]; if (tk.typ === EnumToken.WhitespaceTokenType || tk.typ === EnumToken.CommentTokenType || tk.typ === EnumToken.Add || tk.typ === EnumToken.Sub || tk.typ === EnumToken.Div || tk.typ === EnumToken.Mul) { l++; continue; } break; } tk = parent.chi[k]; tl = parent.chi[l]; } if (tk != null && tl != null) { if ((tk.typ === EnumToken.PercentageTokenType || tl.typ === EnumToken.PercentageTokenType) && tk.typ !== tl.typ) { errors?.push({ action: "drop", message: "adding percentage and number is not allowed", node: token, location: token[LOC], }); return false; } } } // @ts-ignore if (token.val == "color") { // @ts-ignore const children = token.chi.filter((t) => [ EnumToken.DashedIdenTokenType, EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.LiteralTokenType, EnumToken.ColorTokenType, EnumToken.FunctionTokenType, EnumToken.MathFunctionTokenType, EnumToken.PercentageTokenType, ].includes(t.typ)); const isRelative = children[0].typ == EnumToken.IdenTokenType && children[0].val == "from"; let offset = 0; if (isRelative) { offset = 2; } if (children[offset]?.typ == EnumToken.DashedIdenTokenType) { if (children.length <= offset + 1) { errors?.push({ action: "drop", message: `Invalid color at ${token[LOC]?.src}:${token[LOC]?.sta.lin}:${token[LOC]?.sta.col}`, node: token, location: token[LOC], }); return false; } for (let i = offset + 1; i < children.length; i++) { if (children[i].typ == EnumToken.NumberTokenType || children[i].typ == EnumToken.LiteralTokenType || children[i].typ == EnumToken.ColorTokenType || children[i].typ == EnumToken.FunctionTokenType || children[i].typ == EnumToken.MathFunctionTokenType || children[i].typ == EnumToken.PercentageTokenType || isColor(children[i]) || (children[i].typ == EnumToken.IdenTokenType && equalsIgnoreCase("none", children[i].val))) { continue; } return false; } return true; } if (children.length == 4 || (isRelative && children.length == 6)) { return true; } if (children.length == 8 || children.length == 6) { children.at(-2); children.at(-1); // @ts-ignore // if ( // ((children.length > 6 || !isRelative) && sep.typ != EnumToken.LiteralTokenType) || // (sep as LiteralToken).val != "/" // ) { // return false; // } // if (alpha.typ == EnumToken.IdenTokenType && (alpha as IdentToken).val != "none") { // return false; // } else { // // @ts-ignore // if (alpha.typ == EnumToken.PercentageTokenType) { // if (+(alpha as PercentageToken).val < 0 || +(alpha as PercentageToken).val > 100) { // return false; // } // } else if (alpha.typ == EnumToken.NumberTokenType) { // if (+(alpha as NumberToken).val < 0 || +(alpha as NumberToken).val > 1) { // return false; // } // } // } } return true; } // @ts-ignore else if (token.val == "color-mix") { // @ts-ignore const children = token.chi.reduce((acc, t) => { if (t.typ == EnumToken.CommaTokenType) { acc.push([]); } else { if (![EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType].includes(t.typ)) { acc[acc.length - 1].push(t); } } return acc; }, [[]]); // if (children.length === 0 || children[0].length === 0) { // return false; // } let j = 0; let k = 0; if (children[j][0].typ === EnumToken.IdenTokenType && equalsIgnoreCase("in", children[j][k].val)) { k++; if (children[j][k]?.typ === EnumToken.IdenTokenType) { if (!isRectangularOrthogonalColorspace(children[j][k])) { if (isPolarColorspace(children[j][k++])) { if (k == children[j].length) ; else if (children[j][k].typ !== EnumToken.IdenTokenType) { return false; } else if (equalsIgnoreCase("hue", children[j][k].val)) { k++; } else { switch (children[j][k].val) { case "increasing": case "decreasing": case "longer": case "shorter": k++; break; default: return false; } if (children[j][k]?.typ !== EnumToken.IdenTokenType || !equalsIgnoreCase("hue", children[j][k].val)) { return false; } k++; } } // else { // return false; // } } else { k++; } } // else { // return false; // } // if (k != children[j].length) { // return false; // } j++; } // while (j < children.length) { // if (children[j].length > 2) { // return false; // } // if ( // !isColor(children[j][0]) && // !( // children[j][0].typ == EnumToken.WildCardFunctionTokenType && // equalsIgnoreCase("calc", (children[j][0] as FunctionToken).val) // ) // ) { // return false; // } // if (children[j][0].typ == EnumToken.WildCardFunctionTokenType) { // const result = matchAllSyntaxes( // getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, "calc()") as ValidationFunctionToken[], // createValidationContext([children[j][0]]), // {}, // ); // if (!result.success) { // return false; // } // } // if (children[j].length > 1 && !isPercentageToken(children[j][1])) { // return false; // } // j++; // } return true; } else { const keywords = ["from", "none"]; // @ts-ignore if (["rgb", "hsl", "hwb", "lab", "lch", "oklab", "oklch"].some((t) => equalsIgnoreCase(t, token.val))) { // @ts-ignore keywords.push("alpha", ...token.val.slice(-3).split("")); } // @ts-ignore for (const v of token.chi) { if (v.typ == EnumToken.IdenTokenType) { // if (isColor(v)) { // continue; // } // if (!(keywords.includes(v.val) || COLORS_NAMES[v.val.toLowerCase()] != null)) { // return false; // } // if (keywords.includes(v.val)) { // if (isLegacySyntax) { // return false; // } // // @ts-ignore // if (v.val == "from" && ["rgba", "hsla"].includes((token as ColorToken).val)) { // return false; // } // } continue; } if (v.typ === EnumToken.MathFunctionTokenType || v.typ === EnumToken.WildCardFunctionTokenType || colorsFunc.includes(v.val)) { continue; } // if ( // ![ // EnumToken.ColorTokenType, // EnumToken.IdenTokenType, // EnumToken.NumberTokenType, // EnumToken.AngleTokenType, // EnumToken.PercentageTokenType, // EnumToken.CommaTokenType, // EnumToken.WhitespaceTokenType, // EnumToken.LiteralTokenType, // ].includes(v.typ) // ) { // return false; // } } } return true; } } return false; } function parseColor(token) { if (token.typ === EnumToken.IdenTokenType) { const val = token.val.toLowerCase(); if (nonStandardColors.has(val)) { Object.assign(token, { typ: EnumToken.ColorTokenType, kin: ColorType.NON_STD, }); return token; } if (systemColors.has(val)) { Object.assign(token, { typ: EnumToken.ColorTokenType, kin: ColorType.SYS, }); return token; } if (deprecatedSystemColors.has(val)) { Object.assign(token, { typ: EnumToken.ColorTokenType, kin: ColorType.DPSYS, }); return token; } if (val in COLORS_NAMES || val === "currentcolor") { Object.assign(token, { typ: EnumToken.ColorTokenType, val, kin: ColorType.LIT, }); return token; } } if (token.typ === EnumToken.ColorTokenType) { if (!("kin" in token) && "val" in token) { // @ts-expect-error token.kin = ColorType[token.val.replaceAll("-", "_").toUpperCase()]; } if ("chi" in token) { const tk = token.chi?.find((t) => t.typ !== EnumToken.WhitespaceTokenType && t.typ !== EnumToken.CommentTokenType); if (tk?.typ === EnumToken.IdenTokenType && tk.val === "from") { token.cal = "rel"; } else if (token.val == "color-mix" && tk.val == "in") { token.cal = "mix"; } if (token.val == "color") { let index = token.chi.indexOf(tk); // if ((token as ColorToken).cal == "rel") { // for (let k = 0; k < (token as ColorToken).chi!.length; k++) { // if (EnumToken.DashedIdenTokenType == (token as ColorToken).chi![k].typ) { // index = k; // break; // } // } // } if (EnumToken.DashedIdenTokenType == token?.chi?.[index]?.typ) { token.kin = ColorType.CUSTOM_COLOR; } } } // return token; } // @ts-ignore // token.typ = EnumToken.ColorTokenType; // // @ts-ignore // (token as ColorToken).kin = ColorType[token.val.replaceAll("-", "_").toUpperCase()]; // if (!("chi" in token)) { // const val: string = (token as ColorToken).val.toLowerCase(); // if (val == "currentcolor" || val == "transparent" || val in COLORS_NAMES) { // (token as ColorToken).kin = ColorType.LIT; // } else if (isHexColor(val)) { // (token as ColorToken).kin = ColorType.HEX; // } // const tk = (token as ColorToken).chi?.find( // (t) => t.typ !== EnumToken.WhitespaceTokenType && t.typ !== EnumToken.CommentTokenType, // ); // if (tk?.typ === EnumToken.IdenTokenType && (tk as IdentToken).val === "from") { // (token as ColorToken).cal = "rel"; // } else if ((token as ColorToken).val == "color-mix" && (tk as IdentToken).val == "in") { // (token as ColorToken).cal = "mix"; // } else if ((token as ColorToken).val == "color") { // (token as ColorToken).cal = "col"; // } // return token; // } // // @ts-ignore // if (((token as ColorToken).chi as Token[])[0].typ == EnumToken.IdenTokenType) { // // @ts-ignore // if (((token as ColorToken).chi as Token[])[0].val == "from") { // // @ts-ignore // (token as ColorToken).cal = "rel"; // } // // @ts-ignore // else if ((token as ColorToken).val == "color-mix" && ((token as ColorToken).chi as Token[])[0].val == "in") { // // @ts-ignore // (token as ColorToken).cal = "mix"; // } else { // // @ts-ignore // if ((token as ColorToken).val == "color") { // // @ts-ignore // (token as ColorToken).cal = "col"; // } // } // } return token; } function isLetter(codepoint) { // lowercase return ((codepoint >= 0x61 && codepoint <= 0x7a) || // uppercase (codepoint >= 0x41 && codepoint <= 0x5a)); } function isNonAscii(codepoint) { return codepoint >= 0x80; } function isIdentStart(codepoint) { // _ return codepoint == 0x5f || isLetter(codepoint) || isNonAscii(codepoint) || codepoint == REVERSE_SOLIDUS; } function isDigit(codepoint) { return codepoint >= 0x30 && codepoint <= 0x39; } function isIdentCodepoint(codepoint) { // - return codepoint == 0x2d || isDigit(codepoint) || isIdentStart(codepoint); } const isIdent = memoize(function (name) { const j = name.length - 1; let i = 0; let codepoint = name.charCodeAt(0); // - if (codepoint == 0x2d) { const nextCodepoint = name.charCodeAt(1); if (Number.isNaN(nextCodepoint)) { return false; } // if (nextCodepoint == REVERSE_SOLIDUS) { // return name.length > 2 && !isNewLine(name.charCodeAt(2) as number); // } if (isDigit(nextCodepoint)) { return false; } codepoint = nextCodepoint; i = 1; } if (codepoint !== 0x2d && !isIdentStart(codepoint)) { return false; } if (codepoint == REVERSE_SOLIDUS) { codepoint = name.charCodeAt(i + 1); // if (!isIdentCodepoint(codepoint)) { // return false; // } i += String.fromCodePoint(codepoint).length; // if (i < j) { // codepoint = name.charCodeAt(i) as number; // if (!isIdentCodepoint(codepoint)) { // return false; // } // } } while (i < j) { i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; codepoint = name.charCodeAt(i); if (codepoint == REVERSE_SOLIDUS) { i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; codepoint = name.charCodeAt(i); i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; continue; } if (codepoint !== 0x2d && !isIdentCodepoint(codepoint)) { return false; } } return true; }); function isPseudo(name) { return (name.charAt(0) == ":" && ((name.endsWith("(") && isIdent(name.charAt(1) == ":" ? name.slice(2, -1) : name.slice(1, -1))) || isIdent(name.charAt(1) == ":" ? name.slice(2) : name.slice(1)))); } function isHash(name) { return name.charAt(0) == "#" && isIdent(name.charAt(1)); } const isNumber = memoize(function (name) { // if (name.length == 0) { // return false; // } let codepoint = name.charCodeAt(0); let i = 0; const j = name.length; if (j == 1 && !isDigit(codepoint)) { return false; } // '+' '-' if ([0x2b, 0x2d].includes(codepoint)) { i++; } // consume digits while (i < j) { codepoint = name.charCodeAt(i); if (isDigit(codepoint)) { i++; continue; } // '.' 'E' 'e' if (codepoint == 0x2e || codepoint == 0x45 || codepoint == 0x65) { break; } return false; } // '.' if (codepoint == 0x2e) { if (!isDigit(name.charCodeAt(++i))) { return false; } } while (i < j) { codepoint = name.charCodeAt(i); if (isDigit(codepoint)) { i++; continue; } // 'E' 'e' if (codepoint == 0x45 || codepoint == 0x65) { i++; break; } return false; } // 'E' 'e' if (codepoint == 0x45 || codepoint == 0x65) { // if (i == j) { // return false; // } codepoint = name.charCodeAt(i + 1); // '+' '-' // if ([0x2b, 0x2d].includes(codepoint)) { // i++; // } codepoint = name.charCodeAt(i + 1); if (!isDigit(codepoint)) { return false; } } // while (++i < j) { // codepoint = name.charCodeAt(i) as number; // if (!isDigit(codepoint)) { // return false; // } // } return true; }); function isPercentage(name) { return name.endsWith("%") && isNumber(name.slice(0, -1)); } function isFlex(dimension) { return "unit" in dimension && "fr" == dimension.unit.toLowerCase(); } function parseDimension(name) { let index = name.length; while (index--) { if (isLetter(name.charCodeAt(index))) { continue; } index++; break; } if (index < 0) { return null; } const dimension = { typ: EnumToken.DimensionTokenType, val: +name.slice(0, index), unit: name.slice(index), }; if (Number.isNaN(dimension.val)) { return null; } if (isAngle(dimension)) { // @ts-ignore dimension.typ = EnumToken.AngleTokenType; } else if (isLength(dimension)) { // @ts-ignore dimension.typ = EnumToken.LengthTokenType; } else if (isTime(dimension)) { // @ts-ignore dimension.typ = EnumToken.TimeTokenType; } else if (isResolution(dimension)) { // @ts-ignore dimension.typ = EnumToken.ResolutionTokenType; if (dimension.unit == "dppx") { dimension.unit = "x"; } } else if (isFrequency(dimension)) { // @ts-ignore dimension.typ = EnumToken.FrequencyTokenType; } else if (isFlex(dimension)) { // @ts-ignore dimension.typ = EnumToken.FlexTokenType; } return dimension; } function isHexColor(name) { if (name.charAt(0) != "#" || ![4, 5, 7, 9].includes(name.length)) { return false; } for (let chr of name.slice(1)) { let codepoint = chr.charCodeAt(0); if (!isDigit(codepoint) && // A-F !(codepoint >= 0x41 && codepoint <= 0x46) && // a-f !(codepoint >= 0x61 && codepoint <= 0x66)) { return false; } } return true; } function isFunction(name) { return name.endsWith("(") && isIdent(name.slice(0, -1)); } function isNewLine(codepoint) { // \n \r \f \v return (codepoint == 0xa || codepoint == 0xb || codepoint == 0xc || codepoint == 0xd || codepoint == 0x2028 || codepoint == 0x2029); } function isWhiteSpace(codepoint) { return (codepoint == 0x9 || codepoint == 0x20 || // isNewLine codepoint == 0xa || codepoint == 0xb || codepoint == 0xc || codepoint == 0xd || codepoint == 0x2028 || codepoint == 0x2029); } export { dimensionUnits, isAngle, isColor, isDigit, isFlex, isFrequency, isFunction, isHash, isHexColor, isIdent, isIdentCodepoint, isIdentColor, isIdentStart, isLength, isLetter, isNewLine, isNumber, isPercentage, isPolarColorspace, isPseudo, isRectangularOrthogonalColorspace, isResolution, isTime, isWhiteSpace, parseColor, parseDimension, pseudoAliasMap, reduceColorStops, reduceConicColorStops, reducegradientBackgroundPosition, renamedStandardProperties };