From ce756cc16db778697677e29b96c12dcae6f27d61 Mon Sep 17 00:00:00 2001 From: Thierry Bela Date: Wed, 16 Aug 2023 23:48:02 -0400 Subject: [PATCH] add new test cases #10 --- dist/index-umd-web.js | 3185 +++++++++++++-------------- dist/index.cjs | 3185 +++++++++++++-------------- dist/index.d.ts | 7 +- dist/lib/parser/declaration/list.js | 1 + dist/lib/parser/declaration/map.js | 3 +- dist/lib/parser/parse.js | 55 +- dist/lib/parser/tokenize.js | 68 +- dist/lib/parser/utils/syntax.js | 39 +- dist/lib/parser/utils/type.js | 2 +- dist/lib/renderer/render.js | 3 +- dist/lib/transform.js | 1 + dist/node/index.js | 6 +- dist/web/index.js | 6 +- package.json | 2 +- src/@types/index.d.ts | 6 + src/@types/tokenize.ts | 5 +- src/lib/ast/walk.ts | 8 +- src/lib/parser/parse.ts | 39 +- src/lib/parser/tokenize.ts | 76 +- src/lib/parser/utils/syntax.ts | 55 +- src/lib/parser/utils/type.ts | 2 +- src/lib/renderer/render.ts | 2 + test/specs/malformed.spec.js | 75 + test/specs/walk.spec.js | 49 + 24 files changed, 3451 insertions(+), 3429 deletions(-) create mode 100644 test/specs/malformed.spec.js create mode 100644 test/specs/walk.spec.js diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index 86b6bbf..1571bf6 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -4,1079 +4,6 @@ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.CSSParser = {})); })(this, (function (exports) { 'use strict'; - // 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 = [ - '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' - ]; - function isLength(dimension) { - return 'unit' in dimension && dimensionUnits.includes(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()); - } - 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); - } - function isDigit(codepoint) { - return codepoint >= 0x30 && codepoint <= 0x39; - } - function isIdentCodepoint(codepoint) { - // - - return codepoint == 0x2d || isDigit(codepoint) || isIdentStart(codepoint); - } - function isIdent(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 == 0x2d) { - return true; - } - if (nextCodepoint == REVERSE_SOLIDUS) { - return name.length > 2 && !isNewLine(name.charCodeAt(2)); - } - return true; - } - if (!isIdentStart(codepoint)) { - return false; - } - while (i < j) { - i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; - codepoint = name.charCodeAt(i); - if (!isIdentCodepoint(codepoint)) { - return false; - } - } - return true; - } - function isPseudo(name) { - if (name.charAt(0) != ':') { - return false; - } - if (name.endsWith('(')) { - return isIdent(name.charAt(1) == ':' ? name.slice(2, -1) : name.slice(1, -1)); - } - return isIdent(name.charAt(1) == ':' ? name.slice(2) : name.slice(1)); - } - function isHash(name) { - if (name.charAt(0) != '#') { - return false; - } - return isIdent(name.charAt(1)); - } - function isNumber(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); - if (!isDigit(codepoint)) { - return false; - } - } - return true; - } - function isDimension(name) { - let index = name.length; - while (index--) { - if (isLetter(name.charCodeAt(index))) { - continue; - } - index++; - break; - } - const number = name.slice(0, index); - return number.length > 0 && isIdentStart(name.charCodeAt(index)) && isNumber(number); - } - function isPercentage(name) { - return name.endsWith('%') && isNumber(name.slice(0, -1)); - } - function parseDimension(name) { - let index = name.length; - while (index--) { - if (isLetter(name.charCodeAt(index))) { - continue; - } - index++; - break; - } - const dimension = { typ: 'Dimension', val: name.slice(0, index), unit: name.slice(index) }; - if (isAngle(dimension)) { - // @ts-ignore - dimension.typ = 'Angle'; - } - else if (isLength(dimension)) { - // @ts-ignore - dimension.typ = 'Length'; - } - else if (isTime(dimension)) { - // @ts-ignore - dimension.typ = 'Time'; - } - else if (isResolution(dimension)) { - // @ts-ignore - dimension.typ = 'Resolution'; - if (dimension.unit == 'dppx') { - dimension.unit = 'x'; - } - } - else if (isFrequency(dimension)) { - // @ts-ignore - dimension.typ = 'Frequency'; - } - 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 isHexDigit(name) { - if (name.length || name.length > 6) { - return false; - } - for (let chr of name) { - 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 isAtKeyword(name) { - return name.charCodeAt(0) == 0x40 && isIdent(name.slice(1)); - } - function isNewLine(codepoint) { - // \n \r \f - return codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; - } - function isWhiteSpace(codepoint) { - return codepoint == 0x9 || codepoint == 0x20 || - // isNewLine - codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; - } - - var properties = { - inset: { - shorthand: "inset", - properties: [ - "top", - "right", - "bottom", - "left" - ], - types: [ - "Length", - "Perc" - ], - multiple: false, - separator: null, - keywords: [ - "auto" - ] - }, - top: { - shorthand: "inset" - }, - right: { - shorthand: "inset" - }, - bottom: { - shorthand: "inset" - }, - left: { - shorthand: "inset" - }, - margin: { - shorthand: "margin", - properties: [ - "margin-top", - "margin-right", - "margin-bottom", - "margin-left" - ], - types: [ - "Length", - "Perc" - ], - multiple: false, - separator: null, - keywords: [ - "auto" - ] - }, - "margin-top": { - shorthand: "margin" - }, - "margin-right": { - shorthand: "margin" - }, - "margin-bottom": { - shorthand: "margin" - }, - "margin-left": { - shorthand: "margin" - }, - padding: { - shorthand: "padding", - properties: [ - "padding-top", - "padding-right", - "padding-bottom", - "padding-left" - ], - types: [ - "Length", - "Perc" - ], - keywords: [ - ] - }, - "padding-top": { - shorthand: "padding" - }, - "padding-right": { - shorthand: "padding" - }, - "padding-bottom": { - shorthand: "padding" - }, - "padding-left": { - shorthand: "padding" - }, - "border-radius": { - shorthand: "border-radius", - properties: [ - "border-top-left-radius", - "border-top-right-radius", - "border-bottom-right-radius", - "border-bottom-left-radius" - ], - types: [ - "Length", - "Perc" - ], - multiple: true, - separator: "/", - keywords: [ - ] - }, - "border-top-left-radius": { - shorthand: "border-radius" - }, - "border-top-right-radius": { - shorthand: "border-radius" - }, - "border-bottom-right-radius": { - shorthand: "border-radius" - }, - "border-bottom-left-radius": { - shorthand: "border-radius" - }, - "border-width": { - shorthand: "border-width", - map: "border", - properties: [ - "border-top-width", - "border-right-width", - "border-bottom-width", - "border-left-width" - ], - types: [ - "Length", - "Perc" - ], - "default": [ - "medium" - ], - keywords: [ - "thin", - "medium", - "thick" - ] - }, - "border-top-width": { - map: "border", - shorthand: "border-width" - }, - "border-right-width": { - map: "border", - shorthand: "border-width" - }, - "border-bottom-width": { - map: "border", - shorthand: "border-width" - }, - "border-left-width": { - map: "border", - shorthand: "border-width" - }, - "border-style": { - shorthand: "border-style", - map: "border", - properties: [ - "border-top-style", - "border-right-style", - "border-bottom-style", - "border-left-style" - ], - types: [ - ], - "default": [ - "none" - ], - keywords: [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ] - }, - "border-top-style": { - map: "border", - shorthand: "border-style" - }, - "border-right-style": { - map: "border", - shorthand: "border-style" - }, - "border-bottom-style": { - map: "border", - shorthand: "border-style" - }, - "border-left-style": { - map: "border", - shorthand: "border-style" - }, - "border-color": { - shorthand: "border-color", - map: "border", - properties: [ - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color" - ], - types: [ - "Color" - ], - "default": [ - "currentcolor" - ], - keywords: [ - ] - }, - "border-top-color": { - map: "border", - shorthand: "border-color" - }, - "border-right-color": { - map: "border", - shorthand: "border-color" - }, - "border-bottom-color": { - map: "border", - shorthand: "border-color" - }, - "border-left-color": { - map: "border", - shorthand: "border-color" - } - }; - var map = { - border: { - shorthand: "border", - pattern: "border-color border-style border-width", - keywords: [ - "none" - ], - "default": [ - "0", - "none" - ], - properties: { - "border-color": { - types: [ - "Color" - ], - "default": [ - "currentcolor" - ], - keywords: [ - ] - }, - "border-style": { - types: [ - ], - "default": [ - "none" - ], - keywords: [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ] - }, - "border-width": { - types: [ - "Length", - "Perc" - ], - "default": [ - "medium" - ], - keywords: [ - "thin", - "medium", - "thick" - ] - } - } - }, - "border-color": { - shorthand: "border" - }, - "border-style": { - shorthand: "border" - }, - "border-width": { - shorthand: "border" - }, - outline: { - shorthand: "outline", - pattern: "outline-color outline-style outline-width", - keywords: [ - "none" - ], - "default": [ - "0", - "none" - ], - properties: { - "outline-color": { - types: [ - "Color" - ], - "default": [ - "currentColor" - ], - keywords: [ - "currentColor" - ] - }, - "outline-style": { - types: [ - ], - "default": [ - "none" - ], - keywords: [ - "auto", - "none", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ] - }, - "outline-width": { - types: [ - "Length", - "Perc" - ], - "default": [ - "medium" - ], - keywords: [ - "thin", - "medium", - "thick" - ] - } - } - }, - "outline-color": { - shorthand: "outline" - }, - "outline-style": { - shorthand: "outline" - }, - "outline-width": { - shorthand: "outline" - }, - font: { - shorthand: "font", - pattern: "font-weight font-style font-size line-height font-stretch font-variant font-family", - keywords: [ - "caption", - "icon", - "menu", - "message-box", - "small-caption", - "status-bar", - "-moz-window, ", - "-moz-document, ", - "-moz-desktop, ", - "-moz-info, ", - "-moz-dialog", - "-moz-button", - "-moz-pull-down-menu", - "-moz-list", - "-moz-field" - ], - "default": [ - ], - properties: { - "font-weight": { - types: [ - "Number" - ], - "default": [ - "normal", - "400" - ], - keywords: [ - "normal", - "bold", - "lighter", - "bolder" - ], - constraints: { - value: { - min: "1", - max: "1000" - } - }, - mapping: { - thin: "100", - hairline: "100", - "extra light": "200", - "ultra light": "200", - light: "300", - normal: "400", - regular: "400", - medium: "500", - "semi bold": "600", - "demi bold": "600", - bold: "700", - "extra bold": "800", - "ultra bold": "800", - black: "900", - heavy: "900", - "extra black": "950", - "ultra black": "950" - } - }, - "font-style": { - types: [ - "Angle" - ], - "default": [ - "normal" - ], - keywords: [ - "normal", - "italic", - "oblique" - ] - }, - "font-size": { - types: [ - "Length", - "Perc" - ], - "default": [ - ], - keywords: [ - "xx-small", - "x-small", - "small", - "medium", - "large", - "x-large", - "xx-large", - "xxx-large", - "larger", - "smaller" - ], - required: true - }, - "line-height": { - types: [ - "Length", - "Perc", - "Number" - ], - "default": [ - "normal" - ], - keywords: [ - "normal" - ], - previous: "font-size", - prefix: { - typ: "Literal", - val: "/" - } - }, - "font-stretch": { - types: [ - "Perc" - ], - "default": [ - "normal" - ], - keywords: [ - "ultra-condensed", - "extra-condensed", - "condensed", - "semi-condensed", - "normal", - "semi-expanded", - "expanded", - "extra-expanded", - "ultra-expanded" - ], - mapping: { - "ultra-condensed": "50%", - "extra-condensed": "62.5%", - condensed: "75%", - "semi-condensed": "87.5%", - normal: "100%", - "semi-expanded": "112.5%", - expanded: "125%", - "extra-expanded": "150%", - "ultra-expanded": "200%" - } - }, - "font-variant": { - types: [ - ], - "default": [ - "normal" - ], - keywords: [ - "normal", - "none", - "common-ligatures", - "no-common-ligatures", - "discretionary-ligatures", - "no-discretionary-ligatures", - "historical-ligatures", - "no-historical-ligatures", - "contextual", - "no-contextual", - "historical-forms", - "small-caps", - "all-small-caps", - "petite-caps", - "all-petite-caps", - "unicase", - "titling-caps", - "ordinal", - "slashed-zero", - "lining-nums", - "oldstyle-nums", - "proportional-nums", - "tabular-nums", - "diagonal-fractions", - "stacked-fractions", - "ordinal", - "slashed-zero", - "ruby", - "jis78", - "jis83", - "jis90", - "jis04", - "simplified", - "traditional", - "full-width", - "proportional-width", - "ruby", - "sub", - "super", - "text", - "emoji", - "unicode" - ] - }, - "font-family": { - types: [ - "String", - "Iden" - ], - "default": [ - ], - keywords: [ - "serif", - "sans-serif", - "monospace", - "cursive", - "fantasy", - "system-ui", - "ui-serif", - "ui-sans-serif", - "ui-monospace", - "ui-rounded", - "math", - "emoji", - "fangsong" - ], - required: true, - multiple: true, - separator: { - typ: "Comma" - } - } - } - }, - "font-weight": { - shorthand: "font" - }, - "font-style": { - shorthand: "font" - }, - "font-size": { - shorthand: "font" - }, - "line-height": { - shorthand: "font" - }, - "font-stretch": { - shorthand: "font" - }, - "font-variant": { - shorthand: "font" - }, - "font-family": { - shorthand: "font" - }, - background: { - shorthand: "background", - pattern: "background-repeat background-color background-image background-attachment background-clip background-origin background-position background-size", - keywords: [ - "none" - ], - "default": [ - ], - multiple: true, - separator: { - typ: "Comma" - }, - properties: { - "background-repeat": { - types: [ - ], - "default": [ - "repeat" - ], - multiple: true, - keywords: [ - "repeat-x", - "repeat-y", - "repeat", - "space", - "round", - "no-repeat" - ], - mapping: { - "repeat no-repeat": "repeat-x", - "no-repeat repeat": "repeat-y", - "repeat repeat": "repeat", - "space space": "space", - "round round": "round", - "no-repeat no-repeat": "no-repeat" - } - }, - "background-color": { - types: [ - "Color" - ], - "default": [ - "transparent" - ], - multiple: true, - keywords: [ - ] - }, - "background-image": { - types: [ - "UrlFunc" - ], - "default": [ - "none" - ], - keywords: [ - "none" - ] - }, - "background-attachment": { - types: [ - ], - "default": [ - "scroll" - ], - multiple: true, - keywords: [ - "scroll", - "fixed", - "local" - ] - }, - "background-clip": { - types: [ - ], - "default": [ - "border-box" - ], - multiple: true, - keywords: [ - "border-box", - "padding-box", - "content-box", - "text" - ] - }, - "background-origin": { - types: [ - ], - "default": [ - "padding-box" - ], - multiple: true, - keywords: [ - "border-box", - "padding-box", - "content-box" - ] - }, - "background-position": { - multiple: true, - types: [ - "Perc", - "Length" - ], - "default": [ - "0 0", - "top left", - "left top" - ], - keywords: [ - "top", - "left", - "center", - "bottom", - "right" - ], - mapping: { - left: "0", - top: "0", - center: "50%", - bottom: "100%", - right: "100%" - }, - constraints: { - mapping: { - max: 2 - } - } - }, - "background-size": { - multiple: true, - previous: "background-position", - prefix: { - typ: "Literal", - val: "/" - }, - types: [ - "Perc", - "Length" - ], - "default": [ - "auto", - "auto auto" - ], - keywords: [ - "auto", - "cover", - "contain" - ], - mapping: { - "auto auto": "auto" - } - } - } - }, - "background-repeat": { - shorthand: "background" - }, - "background-color": { - shorthand: "background" - }, - "background-image": { - shorthand: "background" - }, - "background-attachment": { - shorthand: "background" - }, - "background-clip": { - shorthand: "background" - }, - "background-origin": { - shorthand: "background" - }, - "background-position": { - shorthand: "background" - }, - "background-size": { - shorthand: "background" - } - }; - var config$1 = { - properties: properties, - map: map - }; - - const getConfig = () => config$1; - - const funcList = ['clamp', 'calc']; - function matchType(val, properties) { - if (val.typ == 'Iden' && properties.keywords.includes(val.val) || - (properties.types.includes(val.typ))) { - return true; - } - if (val.typ == 'Number' && val.val == '0') { - return properties.types.some(type => type == 'Length' || type == 'Angle'); - } - if (val.typ == 'Func' && funcList.includes(val.val)) { - return val.chi.every((t => ['Literal', 'Comma', 'Whitespace', 'Start-parens', 'End-parens'].includes(t.typ) || matchType(t, properties))); - } - return false; - } - // name to color const COLORS_NAMES = Object.seal({ 'aliceblue': '#f0f8ff', @@ -1391,487 +318,1573 @@ // @ts-ignore value += Math.round(t.typ == 'Perc' ? 255 * t.val / 100 : t.val).toString(16).padStart(2, '0'); } - // @ts-ignore - if (token.chi.length == 7) { - // @ts-ignore - t = token.chi[6]; + // @ts-ignore + if (token.chi.length == 7) { + // @ts-ignore + t = token.chi[6]; + // @ts-ignore + if ((t.typ == 'Number' && t.val < 1) || + // @ts-ignore + (t.typ == 'Perc' && t.val < 100)) { + // @ts-ignore + value += Math.round(255 * (t.typ == 'Perc' ? t.val / 100 : t.val)).toString(16).padStart(2, '0'); + } + } + return value; + } + function hsl2Hex(token) { + let t; + // @ts-ignore + let h = getAngle(token.chi[0]); + // @ts-ignore + t = token.chi[2]; + // @ts-ignore + let s = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[4]; + // @ts-ignore + let l = t.typ == 'Perc' ? t.val / 100 : t.val; + let a = null; + if (token.chi?.length == 7) { + // @ts-ignore + t = token.chi[6]; + // @ts-ignore + if ((t.typ == 'Perc' && t.val < 100) || + // @ts-ignore + (t.typ == 'Number' && t.val < 1)) { + // @ts-ignore + a = (t.typ == 'Perc' ? t.val / 100 : t.val); + } + } + return `#${hsl2rgb(h, s, l, a).reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; + } + function hwb2hex(token) { + let t; + // @ts-ignore + let h = getAngle(token.chi[0]); + // @ts-ignore + t = token.chi[2]; + // @ts-ignore + let white = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[4]; + // @ts-ignore + let black = t.typ == 'Perc' ? t.val / 100 : t.val; + let a = null; + if (token.chi?.length == 7) { + // @ts-ignore + t = token.chi[6]; + // @ts-ignore + if ((t.typ == 'Perc' && t.val < 100) || + // @ts-ignore + (t.typ == 'Number' && t.val < 1)) { + // @ts-ignore + a = (t.typ == 'Perc' ? t.val / 100 : t.val); + } + } + const rgb = hsl2rgb(h, 1, .5, a); + let value; + for (let i = 0; i < 3; i++) { + value = rgb[i] / 255; + value *= (1 - white - black); + value += white; + rgb[i] = Math.round(value * 255); + } + return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; + } + function cmyk2hex(token) { + // @ts-ignore + let t = token.chi[0]; + // @ts-ignore + const c = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[2]; + // @ts-ignore + const m = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[4]; + // @ts-ignore + const y = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[6]; + // @ts-ignore + const k = t.typ == 'Perc' ? t.val / 100 : t.val; + const rgb = [ + Math.round(255 * (1 - Math.min(1, c * (1 - k) + k))), + Math.round(255 * (1 - Math.min(1, m * (1 - k) + k))), + Math.round(255 * (1 - Math.min(1, y * (1 - k) + k))) + ]; + // @ts-ignore + if (token.chi.length >= 9) { + // @ts-ignore + t = token.chi[8]; + // @ts-ignore + rgb.push(Math.round(255 * (t.typ == 'Perc' ? t.val / 100 : t.val))); + } + return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; + } + function getAngle(token) { + if (token.typ == 'Angle') { + switch (token.unit) { + case 'deg': + // @ts-ignore + return token.val / 360; + case 'rad': + // @ts-ignore + return token.val / (2 * Math.PI); + case 'grad': + // @ts-ignore + return token.val / 400; + case 'turn': + // @ts-ignore + return +token.val; + } + } + // @ts-ignore + return token.val / 360; + } + function hsl2rgb(h, s, l, a = null) { + let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; + let r = l; + let g = l; + let b = l; + if (v > 0) { + let m = l + l - v; + let sv = (v - m) / v; + h *= 6.0; + let sextant = Math.floor(h); + let fract = h - sextant; + let vsf = v * sv * fract; + let mid1 = m + vsf; + let mid2 = v - vsf; + switch (sextant) { + case 0: + r = v; + g = mid1; + b = m; + break; + case 1: + r = mid2; + g = v; + b = m; + break; + case 2: + r = m; + g = v; + b = mid1; + break; + case 3: + r = m; + g = mid2; + b = v; + break; + case 4: + r = mid1; + g = m; + b = v; + break; + case 5: + r = v; + g = m; + b = mid2; + break; + } + } + const values = [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + if (a != null && a != 1) { + values.push(Math.round(a * 255)); + } + return values; + } + + const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk']; + function reduceNumber(val) { + val = (+val).toString(); + if (val === '0') { + return '0'; + } + const chr = val.charAt(0); + if (chr == '-') { + const slice = val.slice(0, 2); + if (slice == '-0') { + return val.length == 2 ? '0' : '-' + val.slice(2); + } + } + if (chr == '0') { + return val.slice(1); + } + return val; + } + function render(data, opt = {}) { + const startTime = performance.now(); + const options = Object.assign(opt.minify ?? true ? { + indent: '', + newLine: '', + removeComments: true + } : { + indent: ' ', + newLine: '\n', + compress: false, + removeComments: false, + }, { colorConvert: true, preserveLicense: false }, opt); + return { + code: doRender(data, options, function reducer(acc, curr) { + if (curr.typ == 'Comment' && options.removeComments) { + if (!options.preserveLicense || !curr.val.startsWith('/*!')) { + return acc; + } + return acc + curr.val; + } + return acc + renderToken(curr, options, reducer); + }, 0), stats: { + total: `${(performance.now() - startTime).toFixed(2)}ms` + } + }; + } + // @ts-ignore + function doRender(data, options, reducer, level = 0, indents = []) { + if (indents.length < level + 1) { + indents.push(options.indent.repeat(level)); + } + if (indents.length < level + 2) { + indents.push(options.indent.repeat(level + 1)); + } + const indent = indents[level]; + const indentSub = indents[level + 1]; + switch (data.typ) { + case 'Declaration': + return `${data.nam}:${options.indent}${data.val.reduce(reducer, '')}`; + case 'Comment': + return !options.removeComments || (options.preserveLicense && data.val.startsWith('/*!')) ? data.val : ''; + case 'StyleSheet': + return data.chi.reduce((css, node) => { + const str = doRender(node, options, reducer, level, indents); + if (str === '') { + return css; + } + if (css === '') { + return str; + } + return `${css}${options.newLine}${str}`; + }, ''); + case 'AtRule': + case 'Rule': + if (data.typ == 'AtRule' && !('chi' in data)) { + return `${indent}@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val};`; + } + // @ts-ignore + let children = data.chi.reduce((css, node) => { + let str; + if (node.typ == 'Comment') { + str = options.removeComments && (!options.preserveLicense || !node.val.startsWith('/*!')) ? '' : node.val; + } + else if (node.typ == 'Declaration') { + if (node.val.length == 0) { + console.error(`invalid declaration`, node); + return ''; + } + str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`; + } + else if (node.typ == 'AtRule' && !('chi' in node)) { + str = `${data.val === '' ? '' : options.indent || ' '}${data.val};`; + } + else { + str = doRender(node, options, reducer, level + 1, indents); + } + if (css === '') { + return str; + } + if (str === '') { + return css; + } + return `${css}${options.newLine}${indentSub}${str}`; + }, ''); + if (children.endsWith(';')) { + children = children.slice(0, -1); + } + if (data.typ == 'AtRule') { + return `@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val}${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; + } + return data.sel + `${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; + } + return ''; + } + function renderToken(token, options = {}, reducer) { + if (reducer == null) { + reducer = function (acc, curr) { + if (curr.typ == 'Comment' && options.removeComments) { + if (!options.preserveLicense || !curr.val.startsWith('/*!')) { + return acc; + } + return acc + curr.val; + } + return acc + renderToken(curr, options, reducer); + }; + } + switch (token.typ) { + case 'Color': + if (options.minify || options.colorConvert) { + if (token.kin == 'lit' && token.val.toLowerCase() == 'currentcolor') { + return 'currentcolor'; + } + let value = token.kin == 'hex' ? token.val.toLowerCase() : (token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : ''); + if (token.val == 'rgb' || token.val == 'rgba') { + value = rgb2Hex(token); + } + else if (token.val == 'hsl' || token.val == 'hsla') { + value = hsl2Hex(token); + } + else if (token.val == 'hwb') { + value = hwb2hex(token); + } + else if (token.val == 'device-cmyk') { + value = cmyk2hex(token); + } + const named_color = NAMES_COLORS[value]; + if (value !== '') { + if (value.length == 7) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6]) { + value = `#${value[1]}${value[3]}${value[5]}`; + } + } + else if (value.length == 9) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6] && + value[7] == value[8]) { + value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; + } + } + return named_color != null && named_color.length <= value.length ? named_color : value; + } + } + if (token.kin == 'hex' || token.kin == 'lit') { + return token.val; + } + case 'Start-parens': + if (!('chi' in token)) { + return '('; + } + case 'Func': + case 'UrlFunc': + case 'Pseudo-class-func': + // @ts-ignore + return ( /* options.minify && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce(reducer, '') + ')'; + case 'Includes': + return '~='; + case 'Dash-match': + return '|='; + case 'Lt': + return '<'; + case 'Lte': + return '<='; + case 'Gt': + return '>'; + case 'Gte': + return '>='; + case 'End-parens': + return ')'; + case 'Attr-start': + return '['; + case 'Attr-end': + return ']'; + case 'Whitespace': + return ' '; + case 'Colon': + return ':'; + case 'Semi-colon': + return ';'; + case 'Comma': + return ','; + case 'Important': + return '!important'; + case 'Attr': + return '[' + token.chi.reduce(reducer, '') + ']'; + case 'Time': + case 'Angle': + case 'Length': + case 'Dimension': + case 'Frequency': + case 'Resolution': + let val = reduceNumber(token.val); + let unit = token.unit; + if (token.typ == 'Angle') { + const angle = getAngle(token); + let v; + let value = val + unit; + for (const u of ['turn', 'deg', 'rad', 'grad']) { + if (token.unit == u) { + continue; + } + switch (u) { + case 'turn': + v = reduceNumber(angle); + if (v.length + 4 < value.length) { + val = v; + unit = u; + value = v + u; + } + break; + case 'deg': + v = reduceNumber(angle * 360); + if (v.length + 3 < value.length) { + val = v; + unit = u; + value = v + u; + } + break; + case 'rad': + v = reduceNumber(angle * (2 * Math.PI)); + if (v.length + 3 < value.length) { + val = v; + unit = u; + value = v + u; + } + break; + case 'grad': + v = reduceNumber(angle * 400); + if (v.length + 4 < value.length) { + val = v; + unit = u; + value = v + u; + } + break; + } + } + } + if (val === '0') { + if (token.typ == 'Time') { + return '0s'; + } + if (token.typ == 'Frequency') { + return '0Hz'; + } + // @ts-ignore + if (token.typ == 'Resolution') { + return '0x'; + } + return '0'; + } + return val + unit; + case 'Perc': + return token.val + '%'; + case 'Number': + const num = (+token.val).toString(); + if (token.val.length < num.length) { + return token.val; + } + if (num.charAt(0) === '0' && num.length > 1) { + return num.slice(1); + } + const slice = num.slice(0, 2); + if (slice == '-0') { + return '-' + num.slice(2); + } + return num; + case 'Comment': + if (options.removeComments && (!options.preserveLicense || !token.val.startsWith('/*!'))) { + return ''; + } + case 'Url-token': + case 'At-rule': + case 'Hash': + case 'Pseudo-class': + case 'Literal': + case 'String': + case 'Iden': + case 'Delim': + return /* options.minify && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val; + } + console.error(`unexpected token ${JSON.stringify(token, null, 1)}`); + // throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`); + return ''; + } + + // 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 = [ + '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' + ]; + function isLength(dimension) { + return 'unit' in dimension && dimensionUnits.includes(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()); + } + function isColor(token) { + if (token.typ == 'Color') { + return true; + } + if (token.typ == 'Iden') { + // named color + return token.val.toLowerCase() in COLORS_NAMES; + } + if (token.typ == 'Func' && token.chi.length > 0 && colorsFunc.includes(token.val)) { // @ts-ignore - if ((t.typ == 'Number' && t.val < 1) || - // @ts-ignore - (t.typ == 'Perc' && t.val < 100)) { - // @ts-ignore - value += Math.round(255 * (t.typ == 'Perc' ? t.val / 100 : t.val)).toString(16).padStart(2, '0'); + for (const v of token.chi) { + if (!['Number', 'Perc', 'Comma', 'Whitespace'].includes(v.typ)) { + return false; + } } + return true; } - return value; + return false; } - function hsl2Hex(token) { - let t; - // @ts-ignore - let h = getAngle(token.chi[0]); - // @ts-ignore - t = token.chi[2]; - // @ts-ignore - let s = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[4]; - // @ts-ignore - let l = t.typ == 'Perc' ? t.val / 100 : t.val; - let a = null; - if (token.chi?.length == 7) { - // @ts-ignore - t = token.chi[6]; - // @ts-ignore - if ((t.typ == 'Perc' && t.val < 100) || - // @ts-ignore - (t.typ == 'Number' && t.val < 1)) { - // @ts-ignore - a = (t.typ == 'Perc' ? t.val / 100 : t.val); + 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); + } + function isDigit(codepoint) { + return codepoint >= 0x30 && codepoint <= 0x39; + } + function isIdentCodepoint(codepoint) { + // - + return codepoint == 0x2d || isDigit(codepoint) || isIdentStart(codepoint); + } + function isIdent(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 == 0x2d) { + return true; + } + if (nextCodepoint == REVERSE_SOLIDUS) { + return name.length > 2 && !isNewLine(name.charCodeAt(2)); } + return true; } - return `#${hsl2rgb(h, s, l, a).reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; + if (!isIdentStart(codepoint)) { + return false; + } + while (i < j) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = name.charCodeAt(i); + if (!isIdentCodepoint(codepoint)) { + return false; + } + } + return true; } - function hwb2hex(token) { - let t; - // @ts-ignore - let h = getAngle(token.chi[0]); - // @ts-ignore - t = token.chi[2]; - // @ts-ignore - let white = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[4]; - // @ts-ignore - let black = t.typ == 'Perc' ? t.val / 100 : t.val; - let a = null; - if (token.chi?.length == 7) { - // @ts-ignore - t = token.chi[6]; - // @ts-ignore - if ((t.typ == 'Perc' && t.val < 100) || - // @ts-ignore - (t.typ == 'Number' && t.val < 1)) { - // @ts-ignore - a = (t.typ == 'Perc' ? t.val / 100 : t.val); + 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)); + } + function isNumber(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; } - const rgb = hsl2rgb(h, 1, .5, a); - let value; - for (let i = 0; i < 3; i++) { - value = rgb[i] / 255; - value *= (1 - white - black); - value += white; - rgb[i] = Math.round(value * 255); + // '.' + if (codepoint == 0x2e) { + if (!isDigit(name.charCodeAt(++i))) { + return false; + } } - return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; - } - function cmyk2hex(token) { - // @ts-ignore - let t = token.chi[0]; - // @ts-ignore - const c = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[2]; - // @ts-ignore - const m = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[4]; - // @ts-ignore - const y = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[6]; - // @ts-ignore - const k = t.typ == 'Perc' ? t.val / 100 : t.val; - const rgb = [ - Math.round(255 * (1 - Math.min(1, c * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, m * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, y * (1 - k) + k))) - ]; - // @ts-ignore - if (token.chi.length >= 9) { - // @ts-ignore - t = token.chi[8]; - // @ts-ignore - rgb.push(Math.round(255 * (t.typ == 'Perc' ? t.val / 100 : t.val))); + while (i < j) { + codepoint = name.charCodeAt(i); + if (isDigit(codepoint)) { + i++; + continue; + } + // 'E' 'e' + if (codepoint == 0x45 || codepoint == 0x65) { + i++; + break; + } + return false; } - return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; + // '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); + if (!isDigit(codepoint)) { + return false; + } + } + return true; } - function getAngle(token) { - if (token.typ == 'Angle') { - switch (token.unit) { - case 'deg': - // @ts-ignore - return token.val / 360; - case 'rad': - // @ts-ignore - return token.val / (2 * Math.PI); - case 'grad': - // @ts-ignore - return token.val / 400; - case 'turn': - // @ts-ignore - return +token.val; + function isDimension(name) { + let index = name.length; + while (index--) { + if (isLetter(name.charCodeAt(index))) { + continue; } + index++; + break; } - // @ts-ignore - return token.val / 360; + const number = name.slice(0, index); + return number.length > 0 && isIdentStart(name.charCodeAt(index)) && isNumber(number); } - function hsl2rgb(h, s, l, a = null) { - let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; - let r = l; - let g = l; - let b = l; - if (v > 0) { - let m = l + l - v; - let sv = (v - m) / v; - h *= 6.0; - let sextant = Math.floor(h); - let fract = h - sextant; - let vsf = v * sv * fract; - let mid1 = m + vsf; - let mid2 = v - vsf; - switch (sextant) { - case 0: - r = v; - g = mid1; - b = m; - break; - case 1: - r = mid2; - g = v; - b = m; - break; - case 2: - r = m; - g = v; - b = mid1; - break; - case 3: - r = m; - g = mid2; - b = v; - break; - case 4: - r = mid1; - g = m; - b = v; - break; - case 5: - r = v; - g = m; - b = mid2; - break; + function isPercentage(name) { + return name.endsWith('%') && isNumber(name.slice(0, -1)); + } + function parseDimension(name) { + let index = name.length; + while (index--) { + if (isLetter(name.charCodeAt(index))) { + continue; } + index++; + break; } - const values = [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; - if (a != null && a != 1) { - values.push(Math.round(a * 255)); + const dimension = { typ: 'Dimension', val: name.slice(0, index), unit: name.slice(index) }; + if (isAngle(dimension)) { + // @ts-ignore + dimension.typ = 'Angle'; } - return values; - } - - function reduceNumber(val) { - val = (+val).toString(); - if (val === '0') { - return '0'; + else if (isLength(dimension)) { + // @ts-ignore + dimension.typ = 'Length'; } - const chr = val.charAt(0); - if (chr == '-') { - const slice = val.slice(0, 2); - if (slice == '-0') { - return val.length == 2 ? '0' : '-' + val.slice(2); + else if (isTime(dimension)) { + // @ts-ignore + dimension.typ = 'Time'; + } + else if (isResolution(dimension)) { + // @ts-ignore + dimension.typ = 'Resolution'; + if (dimension.unit == 'dppx') { + dimension.unit = 'x'; } } - if (chr == '0') { - return val.slice(1); + else if (isFrequency(dimension)) { + // @ts-ignore + dimension.typ = 'Frequency'; } - return val; + return dimension; } - function render(data, opt = {}) { - const startTime = performance.now(); - const options = Object.assign(opt.minify ?? true ? { - indent: '', - newLine: '', - removeComments: true - } : { - indent: ' ', - newLine: '\n', - compress: false, - removeComments: false, - }, { colorConvert: true, preserveLicense: false }, opt); - return { - code: doRender(data, options, function reducer(acc, curr) { - if (curr.typ == 'Comment' && options.removeComments) { - if (!options.preserveLicense || !curr.val.startsWith('/*!')) { - return acc; - } - return acc + curr.val; - } - return acc + renderToken(curr, options, reducer); - }, 0), stats: { - total: `${(performance.now() - startTime).toFixed(2)}ms` + 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; } - // @ts-ignore - function doRender(data, options, reducer, level = 0, indents = []) { - if (indents.length < level + 1) { - indents.push(options.indent.repeat(level)); + function isHexDigit(name) { + if (name.length || name.length > 6) { + return false; } - if (indents.length < level + 2) { - indents.push(options.indent.repeat(level + 1)); + for (let chr of name) { + let codepoint = chr.charCodeAt(0); + if (!isDigit(codepoint) && + // A F + !(codepoint >= 0x41 && codepoint <= 0x46) && + // a f + !(codepoint >= 0x61 && codepoint <= 0x66)) { + return false; + } } - const indent = indents[level]; - const indentSub = indents[level + 1]; - switch (data.typ) { - case 'Declaration': - return `${data.nam}:${options.indent}${data.val.reduce(reducer, '')}`; - case 'Comment': - return !options.removeComments || (options.preserveLicense && data.val.startsWith('/*!')) ? data.val : ''; - case 'StyleSheet': - return data.chi.reduce((css, node) => { - const str = doRender(node, options, reducer, level, indents); - if (str === '') { - return css; - } - if (css === '') { - return str; - } - return `${css}${options.newLine}${str}`; - }, ''); - case 'AtRule': - case 'Rule': - if (data.typ == 'AtRule' && !('chi' in data)) { - return `${indent}@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val};`; - } - // @ts-ignore - let children = data.chi.reduce((css, node) => { - let str; - if (node.typ == 'Comment') { - str = options.removeComments && (!options.preserveLicense || !node.val.startsWith('/*!')) ? '' : node.val; - } - else if (node.typ == 'Declaration') { - if (node.val.length == 0) { - console.error(`invalid declaration`, node); - return ''; - } - str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`; - } - else if (node.typ == 'AtRule' && !('chi' in node)) { - str = `${data.val === '' ? '' : options.indent || ' '}${data.val};`; - } - else { - str = doRender(node, options, reducer, level + 1, indents); - } - if (css === '') { - return str; - } - if (str === '') { - return css; - } - return `${css}${options.newLine}${indentSub}${str}`; - }, ''); - if (children.endsWith(';')) { - children = children.slice(0, -1); - } - if (data.typ == 'AtRule') { - return `@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val}${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; - } - return data.sel + `${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; + return true; + } + function isFunction(name) { + return name.endsWith('(') && isIdent(name.slice(0, -1)); + } + function isAtKeyword(name) { + return name.charCodeAt(0) == 0x40 && isIdent(name.slice(1)); + } + function isNewLine(codepoint) { + // \n \r \f + return codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; + } + function isWhiteSpace(codepoint) { + return codepoint == 0x9 || codepoint == 0x20 || + // isNewLine + codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; + } + + var properties = { + inset: { + shorthand: "inset", + properties: [ + "top", + "right", + "bottom", + "left" + ], + types: [ + "Length", + "Perc" + ], + multiple: false, + separator: null, + keywords: [ + "auto" + ] + }, + top: { + shorthand: "inset" + }, + right: { + shorthand: "inset" + }, + bottom: { + shorthand: "inset" + }, + left: { + shorthand: "inset" + }, + margin: { + shorthand: "margin", + properties: [ + "margin-top", + "margin-right", + "margin-bottom", + "margin-left" + ], + types: [ + "Length", + "Perc" + ], + multiple: false, + separator: null, + keywords: [ + "auto" + ] + }, + "margin-top": { + shorthand: "margin" + }, + "margin-right": { + shorthand: "margin" + }, + "margin-bottom": { + shorthand: "margin" + }, + "margin-left": { + shorthand: "margin" + }, + padding: { + shorthand: "padding", + properties: [ + "padding-top", + "padding-right", + "padding-bottom", + "padding-left" + ], + types: [ + "Length", + "Perc" + ], + keywords: [ + ] + }, + "padding-top": { + shorthand: "padding" + }, + "padding-right": { + shorthand: "padding" + }, + "padding-bottom": { + shorthand: "padding" + }, + "padding-left": { + shorthand: "padding" + }, + "border-radius": { + shorthand: "border-radius", + properties: [ + "border-top-left-radius", + "border-top-right-radius", + "border-bottom-right-radius", + "border-bottom-left-radius" + ], + types: [ + "Length", + "Perc" + ], + multiple: true, + separator: "/", + keywords: [ + ] + }, + "border-top-left-radius": { + shorthand: "border-radius" + }, + "border-top-right-radius": { + shorthand: "border-radius" + }, + "border-bottom-right-radius": { + shorthand: "border-radius" + }, + "border-bottom-left-radius": { + shorthand: "border-radius" + }, + "border-width": { + shorthand: "border-width", + map: "border", + properties: [ + "border-top-width", + "border-right-width", + "border-bottom-width", + "border-left-width" + ], + types: [ + "Length", + "Perc" + ], + "default": [ + "medium" + ], + keywords: [ + "thin", + "medium", + "thick" + ] + }, + "border-top-width": { + map: "border", + shorthand: "border-width" + }, + "border-right-width": { + map: "border", + shorthand: "border-width" + }, + "border-bottom-width": { + map: "border", + shorthand: "border-width" + }, + "border-left-width": { + map: "border", + shorthand: "border-width" + }, + "border-style": { + shorthand: "border-style", + map: "border", + properties: [ + "border-top-style", + "border-right-style", + "border-bottom-style", + "border-left-style" + ], + types: [ + ], + "default": [ + "none" + ], + keywords: [ + "none", + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset" + ] + }, + "border-top-style": { + map: "border", + shorthand: "border-style" + }, + "border-right-style": { + map: "border", + shorthand: "border-style" + }, + "border-bottom-style": { + map: "border", + shorthand: "border-style" + }, + "border-left-style": { + map: "border", + shorthand: "border-style" + }, + "border-color": { + shorthand: "border-color", + map: "border", + properties: [ + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color" + ], + types: [ + "Color" + ], + "default": [ + "currentcolor" + ], + keywords: [ + ] + }, + "border-top-color": { + map: "border", + shorthand: "border-color" + }, + "border-right-color": { + map: "border", + shorthand: "border-color" + }, + "border-bottom-color": { + map: "border", + shorthand: "border-color" + }, + "border-left-color": { + map: "border", + shorthand: "border-color" + } + }; + var map = { + border: { + shorthand: "border", + pattern: "border-color border-style border-width", + keywords: [ + "none" + ], + "default": [ + "0", + "none" + ], + properties: { + "border-color": { + types: [ + "Color" + ], + "default": [ + "currentcolor" + ], + keywords: [ + ] + }, + "border-style": { + types: [ + ], + "default": [ + "none" + ], + keywords: [ + "none", + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset" + ] + }, + "border-width": { + types: [ + "Length", + "Perc" + ], + "default": [ + "medium" + ], + keywords: [ + "thin", + "medium", + "thick" + ] + } + } + }, + "border-color": { + shorthand: "border" + }, + "border-style": { + shorthand: "border" + }, + "border-width": { + shorthand: "border" + }, + outline: { + shorthand: "outline", + pattern: "outline-color outline-style outline-width", + keywords: [ + "none" + ], + "default": [ + "0", + "none" + ], + properties: { + "outline-color": { + types: [ + "Color" + ], + "default": [ + "currentColor" + ], + keywords: [ + "currentColor" + ] + }, + "outline-style": { + types: [ + ], + "default": [ + "none" + ], + keywords: [ + "auto", + "none", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset" + ] + }, + "outline-width": { + types: [ + "Length", + "Perc" + ], + "default": [ + "medium" + ], + keywords: [ + "thin", + "medium", + "thick" + ] + } + } + }, + "outline-color": { + shorthand: "outline" + }, + "outline-style": { + shorthand: "outline" + }, + "outline-width": { + shorthand: "outline" + }, + font: { + shorthand: "font", + pattern: "font-weight font-style font-size line-height font-stretch font-variant font-family", + keywords: [ + "caption", + "icon", + "menu", + "message-box", + "small-caption", + "status-bar", + "-moz-window, ", + "-moz-document, ", + "-moz-desktop, ", + "-moz-info, ", + "-moz-dialog", + "-moz-button", + "-moz-pull-down-menu", + "-moz-list", + "-moz-field" + ], + "default": [ + ], + properties: { + "font-weight": { + types: [ + "Number" + ], + "default": [ + "normal", + "400" + ], + keywords: [ + "normal", + "bold", + "lighter", + "bolder" + ], + constraints: { + value: { + min: "1", + max: "1000" + } + }, + mapping: { + thin: "100", + hairline: "100", + "extra light": "200", + "ultra light": "200", + light: "300", + normal: "400", + regular: "400", + medium: "500", + "semi bold": "600", + "demi bold": "600", + bold: "700", + "extra bold": "800", + "ultra bold": "800", + black: "900", + heavy: "900", + "extra black": "950", + "ultra black": "950" + } + }, + "font-style": { + types: [ + "Angle" + ], + "default": [ + "normal" + ], + keywords: [ + "normal", + "italic", + "oblique" + ] + }, + "font-size": { + types: [ + "Length", + "Perc" + ], + "default": [ + ], + keywords: [ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "xxx-large", + "larger", + "smaller" + ], + required: true + }, + "line-height": { + types: [ + "Length", + "Perc", + "Number" + ], + "default": [ + "normal" + ], + keywords: [ + "normal" + ], + previous: "font-size", + prefix: { + typ: "Literal", + val: "/" + } + }, + "font-stretch": { + types: [ + "Perc" + ], + "default": [ + "normal" + ], + keywords: [ + "ultra-condensed", + "extra-condensed", + "condensed", + "semi-condensed", + "normal", + "semi-expanded", + "expanded", + "extra-expanded", + "ultra-expanded" + ], + mapping: { + "ultra-condensed": "50%", + "extra-condensed": "62.5%", + condensed: "75%", + "semi-condensed": "87.5%", + normal: "100%", + "semi-expanded": "112.5%", + expanded: "125%", + "extra-expanded": "150%", + "ultra-expanded": "200%" + } + }, + "font-variant": { + types: [ + ], + "default": [ + "normal" + ], + keywords: [ + "normal", + "none", + "common-ligatures", + "no-common-ligatures", + "discretionary-ligatures", + "no-discretionary-ligatures", + "historical-ligatures", + "no-historical-ligatures", + "contextual", + "no-contextual", + "historical-forms", + "small-caps", + "all-small-caps", + "petite-caps", + "all-petite-caps", + "unicase", + "titling-caps", + "ordinal", + "slashed-zero", + "lining-nums", + "oldstyle-nums", + "proportional-nums", + "tabular-nums", + "diagonal-fractions", + "stacked-fractions", + "ordinal", + "slashed-zero", + "ruby", + "jis78", + "jis83", + "jis90", + "jis04", + "simplified", + "traditional", + "full-width", + "proportional-width", + "ruby", + "sub", + "super", + "text", + "emoji", + "unicode" + ] + }, + "font-family": { + types: [ + "String", + "Iden" + ], + "default": [ + ], + keywords: [ + "serif", + "sans-serif", + "monospace", + "cursive", + "fantasy", + "system-ui", + "ui-serif", + "ui-sans-serif", + "ui-monospace", + "ui-rounded", + "math", + "emoji", + "fangsong" + ], + required: true, + multiple: true, + separator: { + typ: "Comma" + } + } + } + }, + "font-weight": { + shorthand: "font" + }, + "font-style": { + shorthand: "font" + }, + "font-size": { + shorthand: "font" + }, + "line-height": { + shorthand: "font" + }, + "font-stretch": { + shorthand: "font" + }, + "font-variant": { + shorthand: "font" + }, + "font-family": { + shorthand: "font" + }, + background: { + shorthand: "background", + pattern: "background-repeat background-color background-image background-attachment background-clip background-origin background-position background-size", + keywords: [ + "none" + ], + "default": [ + ], + multiple: true, + separator: { + typ: "Comma" + }, + properties: { + "background-repeat": { + types: [ + ], + "default": [ + "repeat" + ], + multiple: true, + keywords: [ + "repeat-x", + "repeat-y", + "repeat", + "space", + "round", + "no-repeat" + ], + mapping: { + "repeat no-repeat": "repeat-x", + "no-repeat repeat": "repeat-y", + "repeat repeat": "repeat", + "space space": "space", + "round round": "round", + "no-repeat no-repeat": "no-repeat" + } + }, + "background-color": { + types: [ + "Color" + ], + "default": [ + "transparent" + ], + multiple: true, + keywords: [ + ] + }, + "background-image": { + types: [ + "UrlFunc" + ], + "default": [ + "none" + ], + keywords: [ + "none" + ] + }, + "background-attachment": { + types: [ + ], + "default": [ + "scroll" + ], + multiple: true, + keywords: [ + "scroll", + "fixed", + "local" + ] + }, + "background-clip": { + types: [ + ], + "default": [ + "border-box" + ], + multiple: true, + keywords: [ + "border-box", + "padding-box", + "content-box", + "text" + ] + }, + "background-origin": { + types: [ + ], + "default": [ + "padding-box" + ], + multiple: true, + keywords: [ + "border-box", + "padding-box", + "content-box" + ] + }, + "background-position": { + multiple: true, + types: [ + "Perc", + "Length" + ], + "default": [ + "0 0", + "top left", + "left top" + ], + keywords: [ + "top", + "left", + "center", + "bottom", + "right" + ], + mapping: { + left: "0", + top: "0", + center: "50%", + bottom: "100%", + right: "100%" + }, + constraints: { + mapping: { + max: 2 + } + } + }, + "background-size": { + multiple: true, + previous: "background-position", + prefix: { + typ: "Literal", + val: "/" + }, + types: [ + "Perc", + "Length" + ], + "default": [ + "auto", + "auto auto" + ], + keywords: [ + "auto", + "cover", + "contain" + ], + mapping: { + "auto auto": "auto" + } + } + } + }, + "background-repeat": { + shorthand: "background" + }, + "background-color": { + shorthand: "background" + }, + "background-image": { + shorthand: "background" + }, + "background-attachment": { + shorthand: "background" + }, + "background-clip": { + shorthand: "background" + }, + "background-origin": { + shorthand: "background" + }, + "background-position": { + shorthand: "background" + }, + "background-size": { + shorthand: "background" + } + }; + var config$1 = { + properties: properties, + map: map + }; + + const getConfig = () => config$1; + + const funcList = ['clamp', 'calc']; + function matchType(val, properties) { + if (val.typ == 'Iden' && properties.keywords.includes(val.val) || + (properties.types.includes(val.typ))) { + return true; } - return ''; - } - function renderToken(token, options = {}, reducer) { - if (reducer == null) { - reducer = function (acc, curr) { - if (curr.typ == 'Comment' && options.removeComments) { - if (!options.preserveLicense || !curr.val.startsWith('/*!')) { - return acc; - } - return acc + curr.val; - } - return acc + renderToken(curr, options, reducer); - }; + if (val.typ == 'Number' && val.val == '0') { + return properties.types.some(type => type == 'Length' || type == 'Angle'); } - switch (token.typ) { - case 'Color': - if (options.minify || options.colorConvert) { - if (token.kin == 'lit' && token.val.toLowerCase() == 'currentcolor') { - return 'currentcolor'; - } - let value = token.kin == 'hex' ? token.val.toLowerCase() : (token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : ''); - if (token.val == 'rgb' || token.val == 'rgba') { - value = rgb2Hex(token); - } - else if (token.val == 'hsl' || token.val == 'hsla') { - value = hsl2Hex(token); - } - else if (token.val == 'hwb') { - value = hwb2hex(token); - } - else if (token.val == 'device-cmyk') { - value = cmyk2hex(token); - } - const named_color = NAMES_COLORS[value]; - if (value !== '') { - if (value.length == 7) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6]) { - value = `#${value[1]}${value[3]}${value[5]}`; - } - } - else if (value.length == 9) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6] && - value[7] == value[8]) { - value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; - } - } - return named_color != null && named_color.length <= value.length ? named_color : value; - } - } - if (token.kin == 'hex' || token.kin == 'lit') { - return token.val; - } - case 'Start-parens': - if (!('chi' in token)) { - return '('; - } - case 'Func': - case 'UrlFunc': - case 'Pseudo-class-func': - // @ts-ignore - return ( /* options.minify && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce(reducer, '') + ')'; - case 'Includes': - return '~='; - case 'Dash-match': - return '|='; - case 'Lt': - return '<'; - case 'Lte': - return '<='; - case 'Gt': - return '>'; - case 'Gte': - return '>='; - case 'End-parens': - return ')'; - case 'Attr-start': - return '['; - case 'Attr-end': - return ']'; - case 'Whitespace': - return ' '; - case 'Colon': - return ':'; - case 'Semi-colon': - return ';'; - case 'Comma': - return ','; - case 'Important': - return '!important'; - case 'Attr': - return '[' + token.chi.reduce(reducer, '') + ']'; - case 'Time': - case 'Angle': - case 'Length': - case 'Dimension': - case 'Frequency': - case 'Resolution': - let val = reduceNumber(token.val); - let unit = token.unit; - if (token.typ == 'Angle') { - const angle = getAngle(token); - let v; - let value = val + unit; - for (const u of ['turn', 'deg', 'rad', 'grad']) { - if (token.unit == u) { - continue; - } - switch (u) { - case 'turn': - v = reduceNumber(angle); - if (v.length + 4 < value.length) { - val = v; - unit = u; - value = v + u; - } - break; - case 'deg': - v = reduceNumber(angle * 360); - if (v.length + 3 < value.length) { - val = v; - unit = u; - value = v + u; - } - break; - case 'rad': - v = reduceNumber(angle * (2 * Math.PI)); - if (v.length + 3 < value.length) { - val = v; - unit = u; - value = v + u; - } - break; - case 'grad': - v = reduceNumber(angle * 400); - if (v.length + 4 < value.length) { - val = v; - unit = u; - value = v + u; - } - break; - } - } - } - if (val === '0') { - if (token.typ == 'Time') { - return '0s'; - } - if (token.typ == 'Frequency') { - return '0Hz'; - } - // @ts-ignore - if (token.typ == 'Resolution') { - return '0x'; - } - return '0'; - } - return val + unit; - case 'Perc': - return token.val + '%'; - case 'Number': - const num = (+token.val).toString(); - if (token.val.length < num.length) { - return token.val; - } - if (num.charAt(0) === '0' && num.length > 1) { - return num.slice(1); - } - const slice = num.slice(0, 2); - if (slice == '-0') { - return '-' + num.slice(2); - } - return num; - case 'Comment': - if (options.removeComments && (!options.preserveLicense || !token.val.startsWith('/*!'))) { - return ''; - } - case 'Url-token': - case 'At-rule': - case 'Hash': - case 'Pseudo-class': - case 'Literal': - case 'String': - case 'Iden': - case 'Delim': - return /* options.minify && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val; + if (val.typ == 'Func' && funcList.includes(val.val)) { + return val.chi.every((t => ['Literal', 'Comma', 'Whitespace', 'Start-parens', 'End-parens'].includes(t.typ) || matchType(t, properties))); } - console.error(`unexpected token ${JSON.stringify(token, null, 1)}`); - // throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`); - return ''; + return false; } function eq(a, b) { @@ -3519,31 +3532,18 @@ return char; } while (value = next()) { - if (ind >= iterator.length) { - if (buffer.length > 0) { - yield pushToken(buffer); - buffer = ''; - } - break; - } if (isWhiteSpace(value.charCodeAt(0))) { if (buffer.length > 0) { yield pushToken(buffer); buffer = ''; } while (value = next()) { - if (ind >= iterator.length) { - break; - } if (!isWhiteSpace(value.charCodeAt(0))) { break; } } yield pushToken('', 'Whitespace'); buffer = ''; - if (ind >= iterator.length) { - break; - } } switch (value) { case '/': @@ -3557,34 +3557,12 @@ } buffer += value; if (peek() == '*') { - buffer += '*'; - // i++; - next(); + buffer += next(); while (value = next()) { - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - if (value == '\\') { - buffer += value; - value = next(); - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - buffer += value; - continue; - } if (value == '*') { buffer += value; - value = next(); - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - buffer += value; - if (value == '/') { - yield pushToken(buffer, 'Comment'); + if (peek() == '/') { + yield pushToken(buffer + next(), 'Comment'); buffer = ''; break; } @@ -3593,6 +3571,8 @@ buffer += value; } } + yield pushToken(buffer, 'Bad-comment'); + buffer = ''; } break; case '<': @@ -3606,15 +3586,9 @@ break; } buffer += value; - value = next(); - if (ind >= iterator.length) { - break; - } if (peek(3) == '!--') { + buffer += next(3); while (value = next()) { - if (ind >= iterator.length) { - break; - } buffer += value; if (value == '>' && prev(2) == '--') { yield pushToken(buffer, 'CDOCOMM'); @@ -3623,15 +3597,14 @@ } } } - if (ind >= iterator.length) { - yield pushToken(buffer, 'BADCDO'); - buffer = ''; - } + // if (!peek()) { + yield pushToken(buffer, 'Bad-cdo'); + buffer = ''; + // } break; case '\\': - value = next(); // EOF - if (ind + 1 >= iterator.length) { + if (!(value = next())) { // end of stream ignore \\ yield pushToken(buffer); buffer = ''; @@ -3650,8 +3623,7 @@ buffer = ''; } buffer += value; - value = next(); - if (ind >= iterator.length) { + if (!(value = next())) { yield pushToken(buffer); buffer = ''; break; @@ -3818,8 +3790,7 @@ yield pushToken(buffer); buffer = ''; } - const important = peek(9); - if (important == 'important') { + if (peek(9) == 'important') { yield pushToken('', 'Important'); next(9); buffer = ''; @@ -4162,6 +4133,11 @@ if (item == null) { break; } + // console.debug({item}); + if (item.hint != null && item.hint.startsWith('Bad-')) { + // bad token + continue; + } tokens.push(item); bytesIn = item.bytesIn; if (item.token == ';' || item.token == '{') { @@ -4471,41 +4447,33 @@ // @ts-ignore t.chi.pop(); } - let isColor = true; // @ts-ignore - if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { + if (options.parseColor && t.typ == 'Func' && isColor(t)) { + // if (isColor) { // @ts-ignore - for (const v of t.chi) { - if (v.typ == 'Func' && v.val == 'var') { - isColor = false; - break; - } - } - if (isColor) { - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = t.val; + t.typ = 'Color'; + // @ts-ignore + t.kin = t.val; + // @ts-ignore + let m = t.chi.length; + while (m-- > 0) { // @ts-ignore - let m = t.chi.length; - while (m-- > 0) { + if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) { // @ts-ignore - if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) { + if (t.chi[m + 1]?.typ == 'Whitespace') { // @ts-ignore - if (t.chi[m + 1]?.typ == 'Whitespace') { - // @ts-ignore - t.chi.splice(m + 1, 1); - } + t.chi.splice(m + 1, 1); + } + // @ts-ignore + if (t.chi[m - 1]?.typ == 'Whitespace') { // @ts-ignore - if (t.chi[m - 1]?.typ == 'Whitespace') { - // @ts-ignore - t.chi.splice(m - 1, 1); - m--; - } + t.chi.splice(m - 1, 1); + m--; } } - continue; } + continue; + // } } if (t.typ == 'UrlFunc') { // @ts-ignore @@ -4530,7 +4498,7 @@ // @ts-ignore if (t.chi.length > 0) { // @ts-ignore - parseTokens(t.chi, t.typ); + parseTokens(t.chi, options); if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.minify) { // const count = t.chi.filter(t => t.typ != 'Comment').length; @@ -4549,7 +4517,7 @@ if (t.typ == 'Iden') { // named color const value = t.val.toLowerCase(); - if (COLORS_NAMES[value] != null) { + if (value in COLORS_NAMES) { Object.assign(t, { typ: 'Color', val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, @@ -4717,12 +4685,15 @@ })); } + exports.colorsFunc = colorsFunc; exports.combinators = combinators; exports.dirname = dirname; + exports.funcList = funcList; exports.getConfig = getConfig; exports.hasDeclaration = hasDeclaration; exports.isAngle = isAngle; exports.isAtKeyword = isAtKeyword; + exports.isColor = isColor; exports.isDigit = isDigit; exports.isDimension = isDimension; exports.isFrequency = isFrequency; diff --git a/dist/index.cjs b/dist/index.cjs index 45007dd..7a43774 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -2,1079 +2,6 @@ var promises = require('fs/promises'); -// 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 = [ - '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' -]; -function isLength(dimension) { - return 'unit' in dimension && dimensionUnits.includes(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()); -} -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); -} -function isDigit(codepoint) { - return codepoint >= 0x30 && codepoint <= 0x39; -} -function isIdentCodepoint(codepoint) { - // - - return codepoint == 0x2d || isDigit(codepoint) || isIdentStart(codepoint); -} -function isIdent(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 == 0x2d) { - return true; - } - if (nextCodepoint == REVERSE_SOLIDUS) { - return name.length > 2 && !isNewLine(name.charCodeAt(2)); - } - return true; - } - if (!isIdentStart(codepoint)) { - return false; - } - while (i < j) { - i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; - codepoint = name.charCodeAt(i); - if (!isIdentCodepoint(codepoint)) { - return false; - } - } - return true; -} -function isPseudo(name) { - if (name.charAt(0) != ':') { - return false; - } - if (name.endsWith('(')) { - return isIdent(name.charAt(1) == ':' ? name.slice(2, -1) : name.slice(1, -1)); - } - return isIdent(name.charAt(1) == ':' ? name.slice(2) : name.slice(1)); -} -function isHash(name) { - if (name.charAt(0) != '#') { - return false; - } - return isIdent(name.charAt(1)); -} -function isNumber(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); - if (!isDigit(codepoint)) { - return false; - } - } - return true; -} -function isDimension(name) { - let index = name.length; - while (index--) { - if (isLetter(name.charCodeAt(index))) { - continue; - } - index++; - break; - } - const number = name.slice(0, index); - return number.length > 0 && isIdentStart(name.charCodeAt(index)) && isNumber(number); -} -function isPercentage(name) { - return name.endsWith('%') && isNumber(name.slice(0, -1)); -} -function parseDimension(name) { - let index = name.length; - while (index--) { - if (isLetter(name.charCodeAt(index))) { - continue; - } - index++; - break; - } - const dimension = { typ: 'Dimension', val: name.slice(0, index), unit: name.slice(index) }; - if (isAngle(dimension)) { - // @ts-ignore - dimension.typ = 'Angle'; - } - else if (isLength(dimension)) { - // @ts-ignore - dimension.typ = 'Length'; - } - else if (isTime(dimension)) { - // @ts-ignore - dimension.typ = 'Time'; - } - else if (isResolution(dimension)) { - // @ts-ignore - dimension.typ = 'Resolution'; - if (dimension.unit == 'dppx') { - dimension.unit = 'x'; - } - } - else if (isFrequency(dimension)) { - // @ts-ignore - dimension.typ = 'Frequency'; - } - 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 isHexDigit(name) { - if (name.length || name.length > 6) { - return false; - } - for (let chr of name) { - 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 isAtKeyword(name) { - return name.charCodeAt(0) == 0x40 && isIdent(name.slice(1)); -} -function isNewLine(codepoint) { - // \n \r \f - return codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; -} -function isWhiteSpace(codepoint) { - return codepoint == 0x9 || codepoint == 0x20 || - // isNewLine - codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; -} - -var properties = { - inset: { - shorthand: "inset", - properties: [ - "top", - "right", - "bottom", - "left" - ], - types: [ - "Length", - "Perc" - ], - multiple: false, - separator: null, - keywords: [ - "auto" - ] - }, - top: { - shorthand: "inset" - }, - right: { - shorthand: "inset" - }, - bottom: { - shorthand: "inset" - }, - left: { - shorthand: "inset" - }, - margin: { - shorthand: "margin", - properties: [ - "margin-top", - "margin-right", - "margin-bottom", - "margin-left" - ], - types: [ - "Length", - "Perc" - ], - multiple: false, - separator: null, - keywords: [ - "auto" - ] - }, - "margin-top": { - shorthand: "margin" - }, - "margin-right": { - shorthand: "margin" - }, - "margin-bottom": { - shorthand: "margin" - }, - "margin-left": { - shorthand: "margin" - }, - padding: { - shorthand: "padding", - properties: [ - "padding-top", - "padding-right", - "padding-bottom", - "padding-left" - ], - types: [ - "Length", - "Perc" - ], - keywords: [ - ] - }, - "padding-top": { - shorthand: "padding" - }, - "padding-right": { - shorthand: "padding" - }, - "padding-bottom": { - shorthand: "padding" - }, - "padding-left": { - shorthand: "padding" - }, - "border-radius": { - shorthand: "border-radius", - properties: [ - "border-top-left-radius", - "border-top-right-radius", - "border-bottom-right-radius", - "border-bottom-left-radius" - ], - types: [ - "Length", - "Perc" - ], - multiple: true, - separator: "/", - keywords: [ - ] - }, - "border-top-left-radius": { - shorthand: "border-radius" - }, - "border-top-right-radius": { - shorthand: "border-radius" - }, - "border-bottom-right-radius": { - shorthand: "border-radius" - }, - "border-bottom-left-radius": { - shorthand: "border-radius" - }, - "border-width": { - shorthand: "border-width", - map: "border", - properties: [ - "border-top-width", - "border-right-width", - "border-bottom-width", - "border-left-width" - ], - types: [ - "Length", - "Perc" - ], - "default": [ - "medium" - ], - keywords: [ - "thin", - "medium", - "thick" - ] - }, - "border-top-width": { - map: "border", - shorthand: "border-width" - }, - "border-right-width": { - map: "border", - shorthand: "border-width" - }, - "border-bottom-width": { - map: "border", - shorthand: "border-width" - }, - "border-left-width": { - map: "border", - shorthand: "border-width" - }, - "border-style": { - shorthand: "border-style", - map: "border", - properties: [ - "border-top-style", - "border-right-style", - "border-bottom-style", - "border-left-style" - ], - types: [ - ], - "default": [ - "none" - ], - keywords: [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ] - }, - "border-top-style": { - map: "border", - shorthand: "border-style" - }, - "border-right-style": { - map: "border", - shorthand: "border-style" - }, - "border-bottom-style": { - map: "border", - shorthand: "border-style" - }, - "border-left-style": { - map: "border", - shorthand: "border-style" - }, - "border-color": { - shorthand: "border-color", - map: "border", - properties: [ - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color" - ], - types: [ - "Color" - ], - "default": [ - "currentcolor" - ], - keywords: [ - ] - }, - "border-top-color": { - map: "border", - shorthand: "border-color" - }, - "border-right-color": { - map: "border", - shorthand: "border-color" - }, - "border-bottom-color": { - map: "border", - shorthand: "border-color" - }, - "border-left-color": { - map: "border", - shorthand: "border-color" - } -}; -var map = { - border: { - shorthand: "border", - pattern: "border-color border-style border-width", - keywords: [ - "none" - ], - "default": [ - "0", - "none" - ], - properties: { - "border-color": { - types: [ - "Color" - ], - "default": [ - "currentcolor" - ], - keywords: [ - ] - }, - "border-style": { - types: [ - ], - "default": [ - "none" - ], - keywords: [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ] - }, - "border-width": { - types: [ - "Length", - "Perc" - ], - "default": [ - "medium" - ], - keywords: [ - "thin", - "medium", - "thick" - ] - } - } - }, - "border-color": { - shorthand: "border" - }, - "border-style": { - shorthand: "border" - }, - "border-width": { - shorthand: "border" - }, - outline: { - shorthand: "outline", - pattern: "outline-color outline-style outline-width", - keywords: [ - "none" - ], - "default": [ - "0", - "none" - ], - properties: { - "outline-color": { - types: [ - "Color" - ], - "default": [ - "currentColor" - ], - keywords: [ - "currentColor" - ] - }, - "outline-style": { - types: [ - ], - "default": [ - "none" - ], - keywords: [ - "auto", - "none", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ] - }, - "outline-width": { - types: [ - "Length", - "Perc" - ], - "default": [ - "medium" - ], - keywords: [ - "thin", - "medium", - "thick" - ] - } - } - }, - "outline-color": { - shorthand: "outline" - }, - "outline-style": { - shorthand: "outline" - }, - "outline-width": { - shorthand: "outline" - }, - font: { - shorthand: "font", - pattern: "font-weight font-style font-size line-height font-stretch font-variant font-family", - keywords: [ - "caption", - "icon", - "menu", - "message-box", - "small-caption", - "status-bar", - "-moz-window, ", - "-moz-document, ", - "-moz-desktop, ", - "-moz-info, ", - "-moz-dialog", - "-moz-button", - "-moz-pull-down-menu", - "-moz-list", - "-moz-field" - ], - "default": [ - ], - properties: { - "font-weight": { - types: [ - "Number" - ], - "default": [ - "normal", - "400" - ], - keywords: [ - "normal", - "bold", - "lighter", - "bolder" - ], - constraints: { - value: { - min: "1", - max: "1000" - } - }, - mapping: { - thin: "100", - hairline: "100", - "extra light": "200", - "ultra light": "200", - light: "300", - normal: "400", - regular: "400", - medium: "500", - "semi bold": "600", - "demi bold": "600", - bold: "700", - "extra bold": "800", - "ultra bold": "800", - black: "900", - heavy: "900", - "extra black": "950", - "ultra black": "950" - } - }, - "font-style": { - types: [ - "Angle" - ], - "default": [ - "normal" - ], - keywords: [ - "normal", - "italic", - "oblique" - ] - }, - "font-size": { - types: [ - "Length", - "Perc" - ], - "default": [ - ], - keywords: [ - "xx-small", - "x-small", - "small", - "medium", - "large", - "x-large", - "xx-large", - "xxx-large", - "larger", - "smaller" - ], - required: true - }, - "line-height": { - types: [ - "Length", - "Perc", - "Number" - ], - "default": [ - "normal" - ], - keywords: [ - "normal" - ], - previous: "font-size", - prefix: { - typ: "Literal", - val: "/" - } - }, - "font-stretch": { - types: [ - "Perc" - ], - "default": [ - "normal" - ], - keywords: [ - "ultra-condensed", - "extra-condensed", - "condensed", - "semi-condensed", - "normal", - "semi-expanded", - "expanded", - "extra-expanded", - "ultra-expanded" - ], - mapping: { - "ultra-condensed": "50%", - "extra-condensed": "62.5%", - condensed: "75%", - "semi-condensed": "87.5%", - normal: "100%", - "semi-expanded": "112.5%", - expanded: "125%", - "extra-expanded": "150%", - "ultra-expanded": "200%" - } - }, - "font-variant": { - types: [ - ], - "default": [ - "normal" - ], - keywords: [ - "normal", - "none", - "common-ligatures", - "no-common-ligatures", - "discretionary-ligatures", - "no-discretionary-ligatures", - "historical-ligatures", - "no-historical-ligatures", - "contextual", - "no-contextual", - "historical-forms", - "small-caps", - "all-small-caps", - "petite-caps", - "all-petite-caps", - "unicase", - "titling-caps", - "ordinal", - "slashed-zero", - "lining-nums", - "oldstyle-nums", - "proportional-nums", - "tabular-nums", - "diagonal-fractions", - "stacked-fractions", - "ordinal", - "slashed-zero", - "ruby", - "jis78", - "jis83", - "jis90", - "jis04", - "simplified", - "traditional", - "full-width", - "proportional-width", - "ruby", - "sub", - "super", - "text", - "emoji", - "unicode" - ] - }, - "font-family": { - types: [ - "String", - "Iden" - ], - "default": [ - ], - keywords: [ - "serif", - "sans-serif", - "monospace", - "cursive", - "fantasy", - "system-ui", - "ui-serif", - "ui-sans-serif", - "ui-monospace", - "ui-rounded", - "math", - "emoji", - "fangsong" - ], - required: true, - multiple: true, - separator: { - typ: "Comma" - } - } - } - }, - "font-weight": { - shorthand: "font" - }, - "font-style": { - shorthand: "font" - }, - "font-size": { - shorthand: "font" - }, - "line-height": { - shorthand: "font" - }, - "font-stretch": { - shorthand: "font" - }, - "font-variant": { - shorthand: "font" - }, - "font-family": { - shorthand: "font" - }, - background: { - shorthand: "background", - pattern: "background-repeat background-color background-image background-attachment background-clip background-origin background-position background-size", - keywords: [ - "none" - ], - "default": [ - ], - multiple: true, - separator: { - typ: "Comma" - }, - properties: { - "background-repeat": { - types: [ - ], - "default": [ - "repeat" - ], - multiple: true, - keywords: [ - "repeat-x", - "repeat-y", - "repeat", - "space", - "round", - "no-repeat" - ], - mapping: { - "repeat no-repeat": "repeat-x", - "no-repeat repeat": "repeat-y", - "repeat repeat": "repeat", - "space space": "space", - "round round": "round", - "no-repeat no-repeat": "no-repeat" - } - }, - "background-color": { - types: [ - "Color" - ], - "default": [ - "transparent" - ], - multiple: true, - keywords: [ - ] - }, - "background-image": { - types: [ - "UrlFunc" - ], - "default": [ - "none" - ], - keywords: [ - "none" - ] - }, - "background-attachment": { - types: [ - ], - "default": [ - "scroll" - ], - multiple: true, - keywords: [ - "scroll", - "fixed", - "local" - ] - }, - "background-clip": { - types: [ - ], - "default": [ - "border-box" - ], - multiple: true, - keywords: [ - "border-box", - "padding-box", - "content-box", - "text" - ] - }, - "background-origin": { - types: [ - ], - "default": [ - "padding-box" - ], - multiple: true, - keywords: [ - "border-box", - "padding-box", - "content-box" - ] - }, - "background-position": { - multiple: true, - types: [ - "Perc", - "Length" - ], - "default": [ - "0 0", - "top left", - "left top" - ], - keywords: [ - "top", - "left", - "center", - "bottom", - "right" - ], - mapping: { - left: "0", - top: "0", - center: "50%", - bottom: "100%", - right: "100%" - }, - constraints: { - mapping: { - max: 2 - } - } - }, - "background-size": { - multiple: true, - previous: "background-position", - prefix: { - typ: "Literal", - val: "/" - }, - types: [ - "Perc", - "Length" - ], - "default": [ - "auto", - "auto auto" - ], - keywords: [ - "auto", - "cover", - "contain" - ], - mapping: { - "auto auto": "auto" - } - } - } - }, - "background-repeat": { - shorthand: "background" - }, - "background-color": { - shorthand: "background" - }, - "background-image": { - shorthand: "background" - }, - "background-attachment": { - shorthand: "background" - }, - "background-clip": { - shorthand: "background" - }, - "background-origin": { - shorthand: "background" - }, - "background-position": { - shorthand: "background" - }, - "background-size": { - shorthand: "background" - } -}; -var config$1 = { - properties: properties, - map: map -}; - -const getConfig = () => config$1; - -const funcList = ['clamp', 'calc']; -function matchType(val, properties) { - if (val.typ == 'Iden' && properties.keywords.includes(val.val) || - (properties.types.includes(val.typ))) { - return true; - } - if (val.typ == 'Number' && val.val == '0') { - return properties.types.some(type => type == 'Length' || type == 'Angle'); - } - if (val.typ == 'Func' && funcList.includes(val.val)) { - return val.chi.every((t => ['Literal', 'Comma', 'Whitespace', 'Start-parens', 'End-parens'].includes(t.typ) || matchType(t, properties))); - } - return false; -} - // name to color const COLORS_NAMES = Object.seal({ 'aliceblue': '#f0f8ff', @@ -1389,487 +316,1573 @@ function rgb2Hex(token) { // @ts-ignore value += Math.round(t.typ == 'Perc' ? 255 * t.val / 100 : t.val).toString(16).padStart(2, '0'); } - // @ts-ignore - if (token.chi.length == 7) { - // @ts-ignore - t = token.chi[6]; + // @ts-ignore + if (token.chi.length == 7) { + // @ts-ignore + t = token.chi[6]; + // @ts-ignore + if ((t.typ == 'Number' && t.val < 1) || + // @ts-ignore + (t.typ == 'Perc' && t.val < 100)) { + // @ts-ignore + value += Math.round(255 * (t.typ == 'Perc' ? t.val / 100 : t.val)).toString(16).padStart(2, '0'); + } + } + return value; +} +function hsl2Hex(token) { + let t; + // @ts-ignore + let h = getAngle(token.chi[0]); + // @ts-ignore + t = token.chi[2]; + // @ts-ignore + let s = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[4]; + // @ts-ignore + let l = t.typ == 'Perc' ? t.val / 100 : t.val; + let a = null; + if (token.chi?.length == 7) { + // @ts-ignore + t = token.chi[6]; + // @ts-ignore + if ((t.typ == 'Perc' && t.val < 100) || + // @ts-ignore + (t.typ == 'Number' && t.val < 1)) { + // @ts-ignore + a = (t.typ == 'Perc' ? t.val / 100 : t.val); + } + } + return `#${hsl2rgb(h, s, l, a).reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; +} +function hwb2hex(token) { + let t; + // @ts-ignore + let h = getAngle(token.chi[0]); + // @ts-ignore + t = token.chi[2]; + // @ts-ignore + let white = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[4]; + // @ts-ignore + let black = t.typ == 'Perc' ? t.val / 100 : t.val; + let a = null; + if (token.chi?.length == 7) { + // @ts-ignore + t = token.chi[6]; + // @ts-ignore + if ((t.typ == 'Perc' && t.val < 100) || + // @ts-ignore + (t.typ == 'Number' && t.val < 1)) { + // @ts-ignore + a = (t.typ == 'Perc' ? t.val / 100 : t.val); + } + } + const rgb = hsl2rgb(h, 1, .5, a); + let value; + for (let i = 0; i < 3; i++) { + value = rgb[i] / 255; + value *= (1 - white - black); + value += white; + rgb[i] = Math.round(value * 255); + } + return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; +} +function cmyk2hex(token) { + // @ts-ignore + let t = token.chi[0]; + // @ts-ignore + const c = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[2]; + // @ts-ignore + const m = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[4]; + // @ts-ignore + const y = t.typ == 'Perc' ? t.val / 100 : t.val; + // @ts-ignore + t = token.chi[6]; + // @ts-ignore + const k = t.typ == 'Perc' ? t.val / 100 : t.val; + const rgb = [ + Math.round(255 * (1 - Math.min(1, c * (1 - k) + k))), + Math.round(255 * (1 - Math.min(1, m * (1 - k) + k))), + Math.round(255 * (1 - Math.min(1, y * (1 - k) + k))) + ]; + // @ts-ignore + if (token.chi.length >= 9) { + // @ts-ignore + t = token.chi[8]; + // @ts-ignore + rgb.push(Math.round(255 * (t.typ == 'Perc' ? t.val / 100 : t.val))); + } + return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; +} +function getAngle(token) { + if (token.typ == 'Angle') { + switch (token.unit) { + case 'deg': + // @ts-ignore + return token.val / 360; + case 'rad': + // @ts-ignore + return token.val / (2 * Math.PI); + case 'grad': + // @ts-ignore + return token.val / 400; + case 'turn': + // @ts-ignore + return +token.val; + } + } + // @ts-ignore + return token.val / 360; +} +function hsl2rgb(h, s, l, a = null) { + let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; + let r = l; + let g = l; + let b = l; + if (v > 0) { + let m = l + l - v; + let sv = (v - m) / v; + h *= 6.0; + let sextant = Math.floor(h); + let fract = h - sextant; + let vsf = v * sv * fract; + let mid1 = m + vsf; + let mid2 = v - vsf; + switch (sextant) { + case 0: + r = v; + g = mid1; + b = m; + break; + case 1: + r = mid2; + g = v; + b = m; + break; + case 2: + r = m; + g = v; + b = mid1; + break; + case 3: + r = m; + g = mid2; + b = v; + break; + case 4: + r = mid1; + g = m; + b = v; + break; + case 5: + r = v; + g = m; + b = mid2; + break; + } + } + const values = [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + if (a != null && a != 1) { + values.push(Math.round(a * 255)); + } + return values; +} + +const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk']; +function reduceNumber(val) { + val = (+val).toString(); + if (val === '0') { + return '0'; + } + const chr = val.charAt(0); + if (chr == '-') { + const slice = val.slice(0, 2); + if (slice == '-0') { + return val.length == 2 ? '0' : '-' + val.slice(2); + } + } + if (chr == '0') { + return val.slice(1); + } + return val; +} +function render(data, opt = {}) { + const startTime = performance.now(); + const options = Object.assign(opt.minify ?? true ? { + indent: '', + newLine: '', + removeComments: true + } : { + indent: ' ', + newLine: '\n', + compress: false, + removeComments: false, + }, { colorConvert: true, preserveLicense: false }, opt); + return { + code: doRender(data, options, function reducer(acc, curr) { + if (curr.typ == 'Comment' && options.removeComments) { + if (!options.preserveLicense || !curr.val.startsWith('/*!')) { + return acc; + } + return acc + curr.val; + } + return acc + renderToken(curr, options, reducer); + }, 0), stats: { + total: `${(performance.now() - startTime).toFixed(2)}ms` + } + }; +} +// @ts-ignore +function doRender(data, options, reducer, level = 0, indents = []) { + if (indents.length < level + 1) { + indents.push(options.indent.repeat(level)); + } + if (indents.length < level + 2) { + indents.push(options.indent.repeat(level + 1)); + } + const indent = indents[level]; + const indentSub = indents[level + 1]; + switch (data.typ) { + case 'Declaration': + return `${data.nam}:${options.indent}${data.val.reduce(reducer, '')}`; + case 'Comment': + return !options.removeComments || (options.preserveLicense && data.val.startsWith('/*!')) ? data.val : ''; + case 'StyleSheet': + return data.chi.reduce((css, node) => { + const str = doRender(node, options, reducer, level, indents); + if (str === '') { + return css; + } + if (css === '') { + return str; + } + return `${css}${options.newLine}${str}`; + }, ''); + case 'AtRule': + case 'Rule': + if (data.typ == 'AtRule' && !('chi' in data)) { + return `${indent}@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val};`; + } + // @ts-ignore + let children = data.chi.reduce((css, node) => { + let str; + if (node.typ == 'Comment') { + str = options.removeComments && (!options.preserveLicense || !node.val.startsWith('/*!')) ? '' : node.val; + } + else if (node.typ == 'Declaration') { + if (node.val.length == 0) { + console.error(`invalid declaration`, node); + return ''; + } + str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`; + } + else if (node.typ == 'AtRule' && !('chi' in node)) { + str = `${data.val === '' ? '' : options.indent || ' '}${data.val};`; + } + else { + str = doRender(node, options, reducer, level + 1, indents); + } + if (css === '') { + return str; + } + if (str === '') { + return css; + } + return `${css}${options.newLine}${indentSub}${str}`; + }, ''); + if (children.endsWith(';')) { + children = children.slice(0, -1); + } + if (data.typ == 'AtRule') { + return `@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val}${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; + } + return data.sel + `${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; + } + return ''; +} +function renderToken(token, options = {}, reducer) { + if (reducer == null) { + reducer = function (acc, curr) { + if (curr.typ == 'Comment' && options.removeComments) { + if (!options.preserveLicense || !curr.val.startsWith('/*!')) { + return acc; + } + return acc + curr.val; + } + return acc + renderToken(curr, options, reducer); + }; + } + switch (token.typ) { + case 'Color': + if (options.minify || options.colorConvert) { + if (token.kin == 'lit' && token.val.toLowerCase() == 'currentcolor') { + return 'currentcolor'; + } + let value = token.kin == 'hex' ? token.val.toLowerCase() : (token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : ''); + if (token.val == 'rgb' || token.val == 'rgba') { + value = rgb2Hex(token); + } + else if (token.val == 'hsl' || token.val == 'hsla') { + value = hsl2Hex(token); + } + else if (token.val == 'hwb') { + value = hwb2hex(token); + } + else if (token.val == 'device-cmyk') { + value = cmyk2hex(token); + } + const named_color = NAMES_COLORS[value]; + if (value !== '') { + if (value.length == 7) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6]) { + value = `#${value[1]}${value[3]}${value[5]}`; + } + } + else if (value.length == 9) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6] && + value[7] == value[8]) { + value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; + } + } + return named_color != null && named_color.length <= value.length ? named_color : value; + } + } + if (token.kin == 'hex' || token.kin == 'lit') { + return token.val; + } + case 'Start-parens': + if (!('chi' in token)) { + return '('; + } + case 'Func': + case 'UrlFunc': + case 'Pseudo-class-func': + // @ts-ignore + return ( /* options.minify && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce(reducer, '') + ')'; + case 'Includes': + return '~='; + case 'Dash-match': + return '|='; + case 'Lt': + return '<'; + case 'Lte': + return '<='; + case 'Gt': + return '>'; + case 'Gte': + return '>='; + case 'End-parens': + return ')'; + case 'Attr-start': + return '['; + case 'Attr-end': + return ']'; + case 'Whitespace': + return ' '; + case 'Colon': + return ':'; + case 'Semi-colon': + return ';'; + case 'Comma': + return ','; + case 'Important': + return '!important'; + case 'Attr': + return '[' + token.chi.reduce(reducer, '') + ']'; + case 'Time': + case 'Angle': + case 'Length': + case 'Dimension': + case 'Frequency': + case 'Resolution': + let val = reduceNumber(token.val); + let unit = token.unit; + if (token.typ == 'Angle') { + const angle = getAngle(token); + let v; + let value = val + unit; + for (const u of ['turn', 'deg', 'rad', 'grad']) { + if (token.unit == u) { + continue; + } + switch (u) { + case 'turn': + v = reduceNumber(angle); + if (v.length + 4 < value.length) { + val = v; + unit = u; + value = v + u; + } + break; + case 'deg': + v = reduceNumber(angle * 360); + if (v.length + 3 < value.length) { + val = v; + unit = u; + value = v + u; + } + break; + case 'rad': + v = reduceNumber(angle * (2 * Math.PI)); + if (v.length + 3 < value.length) { + val = v; + unit = u; + value = v + u; + } + break; + case 'grad': + v = reduceNumber(angle * 400); + if (v.length + 4 < value.length) { + val = v; + unit = u; + value = v + u; + } + break; + } + } + } + if (val === '0') { + if (token.typ == 'Time') { + return '0s'; + } + if (token.typ == 'Frequency') { + return '0Hz'; + } + // @ts-ignore + if (token.typ == 'Resolution') { + return '0x'; + } + return '0'; + } + return val + unit; + case 'Perc': + return token.val + '%'; + case 'Number': + const num = (+token.val).toString(); + if (token.val.length < num.length) { + return token.val; + } + if (num.charAt(0) === '0' && num.length > 1) { + return num.slice(1); + } + const slice = num.slice(0, 2); + if (slice == '-0') { + return '-' + num.slice(2); + } + return num; + case 'Comment': + if (options.removeComments && (!options.preserveLicense || !token.val.startsWith('/*!'))) { + return ''; + } + case 'Url-token': + case 'At-rule': + case 'Hash': + case 'Pseudo-class': + case 'Literal': + case 'String': + case 'Iden': + case 'Delim': + return /* options.minify && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val; + } + console.error(`unexpected token ${JSON.stringify(token, null, 1)}`); + // throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`); + return ''; +} + +// 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 = [ + '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' +]; +function isLength(dimension) { + return 'unit' in dimension && dimensionUnits.includes(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()); +} +function isColor(token) { + if (token.typ == 'Color') { + return true; + } + if (token.typ == 'Iden') { + // named color + return token.val.toLowerCase() in COLORS_NAMES; + } + if (token.typ == 'Func' && token.chi.length > 0 && colorsFunc.includes(token.val)) { // @ts-ignore - if ((t.typ == 'Number' && t.val < 1) || - // @ts-ignore - (t.typ == 'Perc' && t.val < 100)) { - // @ts-ignore - value += Math.round(255 * (t.typ == 'Perc' ? t.val / 100 : t.val)).toString(16).padStart(2, '0'); + for (const v of token.chi) { + if (!['Number', 'Perc', 'Comma', 'Whitespace'].includes(v.typ)) { + return false; + } } + return true; } - return value; + return false; } -function hsl2Hex(token) { - let t; - // @ts-ignore - let h = getAngle(token.chi[0]); - // @ts-ignore - t = token.chi[2]; - // @ts-ignore - let s = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[4]; - // @ts-ignore - let l = t.typ == 'Perc' ? t.val / 100 : t.val; - let a = null; - if (token.chi?.length == 7) { - // @ts-ignore - t = token.chi[6]; - // @ts-ignore - if ((t.typ == 'Perc' && t.val < 100) || - // @ts-ignore - (t.typ == 'Number' && t.val < 1)) { - // @ts-ignore - a = (t.typ == 'Perc' ? t.val / 100 : t.val); +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); +} +function isDigit(codepoint) { + return codepoint >= 0x30 && codepoint <= 0x39; +} +function isIdentCodepoint(codepoint) { + // - + return codepoint == 0x2d || isDigit(codepoint) || isIdentStart(codepoint); +} +function isIdent(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 == 0x2d) { + return true; + } + if (nextCodepoint == REVERSE_SOLIDUS) { + return name.length > 2 && !isNewLine(name.charCodeAt(2)); } + return true; } - return `#${hsl2rgb(h, s, l, a).reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; + if (!isIdentStart(codepoint)) { + return false; + } + while (i < j) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = name.charCodeAt(i); + if (!isIdentCodepoint(codepoint)) { + return false; + } + } + return true; } -function hwb2hex(token) { - let t; - // @ts-ignore - let h = getAngle(token.chi[0]); - // @ts-ignore - t = token.chi[2]; - // @ts-ignore - let white = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[4]; - // @ts-ignore - let black = t.typ == 'Perc' ? t.val / 100 : t.val; - let a = null; - if (token.chi?.length == 7) { - // @ts-ignore - t = token.chi[6]; - // @ts-ignore - if ((t.typ == 'Perc' && t.val < 100) || - // @ts-ignore - (t.typ == 'Number' && t.val < 1)) { - // @ts-ignore - a = (t.typ == 'Perc' ? t.val / 100 : t.val); +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)); +} +function isNumber(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; } - const rgb = hsl2rgb(h, 1, .5, a); - let value; - for (let i = 0; i < 3; i++) { - value = rgb[i] / 255; - value *= (1 - white - black); - value += white; - rgb[i] = Math.round(value * 255); + // '.' + if (codepoint == 0x2e) { + if (!isDigit(name.charCodeAt(++i))) { + return false; + } } - return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; -} -function cmyk2hex(token) { - // @ts-ignore - let t = token.chi[0]; - // @ts-ignore - const c = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[2]; - // @ts-ignore - const m = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[4]; - // @ts-ignore - const y = t.typ == 'Perc' ? t.val / 100 : t.val; - // @ts-ignore - t = token.chi[6]; - // @ts-ignore - const k = t.typ == 'Perc' ? t.val / 100 : t.val; - const rgb = [ - Math.round(255 * (1 - Math.min(1, c * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, m * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, y * (1 - k) + k))) - ]; - // @ts-ignore - if (token.chi.length >= 9) { - // @ts-ignore - t = token.chi[8]; - // @ts-ignore - rgb.push(Math.round(255 * (t.typ == 'Perc' ? t.val / 100 : t.val))); + while (i < j) { + codepoint = name.charCodeAt(i); + if (isDigit(codepoint)) { + i++; + continue; + } + // 'E' 'e' + if (codepoint == 0x45 || codepoint == 0x65) { + i++; + break; + } + return false; } - return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`; + // '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); + if (!isDigit(codepoint)) { + return false; + } + } + return true; } -function getAngle(token) { - if (token.typ == 'Angle') { - switch (token.unit) { - case 'deg': - // @ts-ignore - return token.val / 360; - case 'rad': - // @ts-ignore - return token.val / (2 * Math.PI); - case 'grad': - // @ts-ignore - return token.val / 400; - case 'turn': - // @ts-ignore - return +token.val; +function isDimension(name) { + let index = name.length; + while (index--) { + if (isLetter(name.charCodeAt(index))) { + continue; } + index++; + break; } - // @ts-ignore - return token.val / 360; + const number = name.slice(0, index); + return number.length > 0 && isIdentStart(name.charCodeAt(index)) && isNumber(number); } -function hsl2rgb(h, s, l, a = null) { - let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; - let r = l; - let g = l; - let b = l; - if (v > 0) { - let m = l + l - v; - let sv = (v - m) / v; - h *= 6.0; - let sextant = Math.floor(h); - let fract = h - sextant; - let vsf = v * sv * fract; - let mid1 = m + vsf; - let mid2 = v - vsf; - switch (sextant) { - case 0: - r = v; - g = mid1; - b = m; - break; - case 1: - r = mid2; - g = v; - b = m; - break; - case 2: - r = m; - g = v; - b = mid1; - break; - case 3: - r = m; - g = mid2; - b = v; - break; - case 4: - r = mid1; - g = m; - b = v; - break; - case 5: - r = v; - g = m; - b = mid2; - break; +function isPercentage(name) { + return name.endsWith('%') && isNumber(name.slice(0, -1)); +} +function parseDimension(name) { + let index = name.length; + while (index--) { + if (isLetter(name.charCodeAt(index))) { + continue; } + index++; + break; } - const values = [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; - if (a != null && a != 1) { - values.push(Math.round(a * 255)); + const dimension = { typ: 'Dimension', val: name.slice(0, index), unit: name.slice(index) }; + if (isAngle(dimension)) { + // @ts-ignore + dimension.typ = 'Angle'; } - return values; -} - -function reduceNumber(val) { - val = (+val).toString(); - if (val === '0') { - return '0'; + else if (isLength(dimension)) { + // @ts-ignore + dimension.typ = 'Length'; } - const chr = val.charAt(0); - if (chr == '-') { - const slice = val.slice(0, 2); - if (slice == '-0') { - return val.length == 2 ? '0' : '-' + val.slice(2); + else if (isTime(dimension)) { + // @ts-ignore + dimension.typ = 'Time'; + } + else if (isResolution(dimension)) { + // @ts-ignore + dimension.typ = 'Resolution'; + if (dimension.unit == 'dppx') { + dimension.unit = 'x'; } } - if (chr == '0') { - return val.slice(1); + else if (isFrequency(dimension)) { + // @ts-ignore + dimension.typ = 'Frequency'; } - return val; + return dimension; } -function render(data, opt = {}) { - const startTime = performance.now(); - const options = Object.assign(opt.minify ?? true ? { - indent: '', - newLine: '', - removeComments: true - } : { - indent: ' ', - newLine: '\n', - compress: false, - removeComments: false, - }, { colorConvert: true, preserveLicense: false }, opt); - return { - code: doRender(data, options, function reducer(acc, curr) { - if (curr.typ == 'Comment' && options.removeComments) { - if (!options.preserveLicense || !curr.val.startsWith('/*!')) { - return acc; - } - return acc + curr.val; - } - return acc + renderToken(curr, options, reducer); - }, 0), stats: { - total: `${(performance.now() - startTime).toFixed(2)}ms` +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; } -// @ts-ignore -function doRender(data, options, reducer, level = 0, indents = []) { - if (indents.length < level + 1) { - indents.push(options.indent.repeat(level)); +function isHexDigit(name) { + if (name.length || name.length > 6) { + return false; } - if (indents.length < level + 2) { - indents.push(options.indent.repeat(level + 1)); + for (let chr of name) { + let codepoint = chr.charCodeAt(0); + if (!isDigit(codepoint) && + // A F + !(codepoint >= 0x41 && codepoint <= 0x46) && + // a f + !(codepoint >= 0x61 && codepoint <= 0x66)) { + return false; + } } - const indent = indents[level]; - const indentSub = indents[level + 1]; - switch (data.typ) { - case 'Declaration': - return `${data.nam}:${options.indent}${data.val.reduce(reducer, '')}`; - case 'Comment': - return !options.removeComments || (options.preserveLicense && data.val.startsWith('/*!')) ? data.val : ''; - case 'StyleSheet': - return data.chi.reduce((css, node) => { - const str = doRender(node, options, reducer, level, indents); - if (str === '') { - return css; - } - if (css === '') { - return str; - } - return `${css}${options.newLine}${str}`; - }, ''); - case 'AtRule': - case 'Rule': - if (data.typ == 'AtRule' && !('chi' in data)) { - return `${indent}@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val};`; - } - // @ts-ignore - let children = data.chi.reduce((css, node) => { - let str; - if (node.typ == 'Comment') { - str = options.removeComments && (!options.preserveLicense || !node.val.startsWith('/*!')) ? '' : node.val; - } - else if (node.typ == 'Declaration') { - if (node.val.length == 0) { - console.error(`invalid declaration`, node); - return ''; - } - str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`; - } - else if (node.typ == 'AtRule' && !('chi' in node)) { - str = `${data.val === '' ? '' : options.indent || ' '}${data.val};`; - } - else { - str = doRender(node, options, reducer, level + 1, indents); - } - if (css === '') { - return str; - } - if (str === '') { - return css; - } - return `${css}${options.newLine}${indentSub}${str}`; - }, ''); - if (children.endsWith(';')) { - children = children.slice(0, -1); - } - if (data.typ == 'AtRule') { - return `@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val}${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; - } - return data.sel + `${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; + return true; +} +function isFunction(name) { + return name.endsWith('(') && isIdent(name.slice(0, -1)); +} +function isAtKeyword(name) { + return name.charCodeAt(0) == 0x40 && isIdent(name.slice(1)); +} +function isNewLine(codepoint) { + // \n \r \f + return codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; +} +function isWhiteSpace(codepoint) { + return codepoint == 0x9 || codepoint == 0x20 || + // isNewLine + codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; +} + +var properties = { + inset: { + shorthand: "inset", + properties: [ + "top", + "right", + "bottom", + "left" + ], + types: [ + "Length", + "Perc" + ], + multiple: false, + separator: null, + keywords: [ + "auto" + ] + }, + top: { + shorthand: "inset" + }, + right: { + shorthand: "inset" + }, + bottom: { + shorthand: "inset" + }, + left: { + shorthand: "inset" + }, + margin: { + shorthand: "margin", + properties: [ + "margin-top", + "margin-right", + "margin-bottom", + "margin-left" + ], + types: [ + "Length", + "Perc" + ], + multiple: false, + separator: null, + keywords: [ + "auto" + ] + }, + "margin-top": { + shorthand: "margin" + }, + "margin-right": { + shorthand: "margin" + }, + "margin-bottom": { + shorthand: "margin" + }, + "margin-left": { + shorthand: "margin" + }, + padding: { + shorthand: "padding", + properties: [ + "padding-top", + "padding-right", + "padding-bottom", + "padding-left" + ], + types: [ + "Length", + "Perc" + ], + keywords: [ + ] + }, + "padding-top": { + shorthand: "padding" + }, + "padding-right": { + shorthand: "padding" + }, + "padding-bottom": { + shorthand: "padding" + }, + "padding-left": { + shorthand: "padding" + }, + "border-radius": { + shorthand: "border-radius", + properties: [ + "border-top-left-radius", + "border-top-right-radius", + "border-bottom-right-radius", + "border-bottom-left-radius" + ], + types: [ + "Length", + "Perc" + ], + multiple: true, + separator: "/", + keywords: [ + ] + }, + "border-top-left-radius": { + shorthand: "border-radius" + }, + "border-top-right-radius": { + shorthand: "border-radius" + }, + "border-bottom-right-radius": { + shorthand: "border-radius" + }, + "border-bottom-left-radius": { + shorthand: "border-radius" + }, + "border-width": { + shorthand: "border-width", + map: "border", + properties: [ + "border-top-width", + "border-right-width", + "border-bottom-width", + "border-left-width" + ], + types: [ + "Length", + "Perc" + ], + "default": [ + "medium" + ], + keywords: [ + "thin", + "medium", + "thick" + ] + }, + "border-top-width": { + map: "border", + shorthand: "border-width" + }, + "border-right-width": { + map: "border", + shorthand: "border-width" + }, + "border-bottom-width": { + map: "border", + shorthand: "border-width" + }, + "border-left-width": { + map: "border", + shorthand: "border-width" + }, + "border-style": { + shorthand: "border-style", + map: "border", + properties: [ + "border-top-style", + "border-right-style", + "border-bottom-style", + "border-left-style" + ], + types: [ + ], + "default": [ + "none" + ], + keywords: [ + "none", + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset" + ] + }, + "border-top-style": { + map: "border", + shorthand: "border-style" + }, + "border-right-style": { + map: "border", + shorthand: "border-style" + }, + "border-bottom-style": { + map: "border", + shorthand: "border-style" + }, + "border-left-style": { + map: "border", + shorthand: "border-style" + }, + "border-color": { + shorthand: "border-color", + map: "border", + properties: [ + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color" + ], + types: [ + "Color" + ], + "default": [ + "currentcolor" + ], + keywords: [ + ] + }, + "border-top-color": { + map: "border", + shorthand: "border-color" + }, + "border-right-color": { + map: "border", + shorthand: "border-color" + }, + "border-bottom-color": { + map: "border", + shorthand: "border-color" + }, + "border-left-color": { + map: "border", + shorthand: "border-color" + } +}; +var map = { + border: { + shorthand: "border", + pattern: "border-color border-style border-width", + keywords: [ + "none" + ], + "default": [ + "0", + "none" + ], + properties: { + "border-color": { + types: [ + "Color" + ], + "default": [ + "currentcolor" + ], + keywords: [ + ] + }, + "border-style": { + types: [ + ], + "default": [ + "none" + ], + keywords: [ + "none", + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset" + ] + }, + "border-width": { + types: [ + "Length", + "Perc" + ], + "default": [ + "medium" + ], + keywords: [ + "thin", + "medium", + "thick" + ] + } + } + }, + "border-color": { + shorthand: "border" + }, + "border-style": { + shorthand: "border" + }, + "border-width": { + shorthand: "border" + }, + outline: { + shorthand: "outline", + pattern: "outline-color outline-style outline-width", + keywords: [ + "none" + ], + "default": [ + "0", + "none" + ], + properties: { + "outline-color": { + types: [ + "Color" + ], + "default": [ + "currentColor" + ], + keywords: [ + "currentColor" + ] + }, + "outline-style": { + types: [ + ], + "default": [ + "none" + ], + keywords: [ + "auto", + "none", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset" + ] + }, + "outline-width": { + types: [ + "Length", + "Perc" + ], + "default": [ + "medium" + ], + keywords: [ + "thin", + "medium", + "thick" + ] + } + } + }, + "outline-color": { + shorthand: "outline" + }, + "outline-style": { + shorthand: "outline" + }, + "outline-width": { + shorthand: "outline" + }, + font: { + shorthand: "font", + pattern: "font-weight font-style font-size line-height font-stretch font-variant font-family", + keywords: [ + "caption", + "icon", + "menu", + "message-box", + "small-caption", + "status-bar", + "-moz-window, ", + "-moz-document, ", + "-moz-desktop, ", + "-moz-info, ", + "-moz-dialog", + "-moz-button", + "-moz-pull-down-menu", + "-moz-list", + "-moz-field" + ], + "default": [ + ], + properties: { + "font-weight": { + types: [ + "Number" + ], + "default": [ + "normal", + "400" + ], + keywords: [ + "normal", + "bold", + "lighter", + "bolder" + ], + constraints: { + value: { + min: "1", + max: "1000" + } + }, + mapping: { + thin: "100", + hairline: "100", + "extra light": "200", + "ultra light": "200", + light: "300", + normal: "400", + regular: "400", + medium: "500", + "semi bold": "600", + "demi bold": "600", + bold: "700", + "extra bold": "800", + "ultra bold": "800", + black: "900", + heavy: "900", + "extra black": "950", + "ultra black": "950" + } + }, + "font-style": { + types: [ + "Angle" + ], + "default": [ + "normal" + ], + keywords: [ + "normal", + "italic", + "oblique" + ] + }, + "font-size": { + types: [ + "Length", + "Perc" + ], + "default": [ + ], + keywords: [ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "xxx-large", + "larger", + "smaller" + ], + required: true + }, + "line-height": { + types: [ + "Length", + "Perc", + "Number" + ], + "default": [ + "normal" + ], + keywords: [ + "normal" + ], + previous: "font-size", + prefix: { + typ: "Literal", + val: "/" + } + }, + "font-stretch": { + types: [ + "Perc" + ], + "default": [ + "normal" + ], + keywords: [ + "ultra-condensed", + "extra-condensed", + "condensed", + "semi-condensed", + "normal", + "semi-expanded", + "expanded", + "extra-expanded", + "ultra-expanded" + ], + mapping: { + "ultra-condensed": "50%", + "extra-condensed": "62.5%", + condensed: "75%", + "semi-condensed": "87.5%", + normal: "100%", + "semi-expanded": "112.5%", + expanded: "125%", + "extra-expanded": "150%", + "ultra-expanded": "200%" + } + }, + "font-variant": { + types: [ + ], + "default": [ + "normal" + ], + keywords: [ + "normal", + "none", + "common-ligatures", + "no-common-ligatures", + "discretionary-ligatures", + "no-discretionary-ligatures", + "historical-ligatures", + "no-historical-ligatures", + "contextual", + "no-contextual", + "historical-forms", + "small-caps", + "all-small-caps", + "petite-caps", + "all-petite-caps", + "unicase", + "titling-caps", + "ordinal", + "slashed-zero", + "lining-nums", + "oldstyle-nums", + "proportional-nums", + "tabular-nums", + "diagonal-fractions", + "stacked-fractions", + "ordinal", + "slashed-zero", + "ruby", + "jis78", + "jis83", + "jis90", + "jis04", + "simplified", + "traditional", + "full-width", + "proportional-width", + "ruby", + "sub", + "super", + "text", + "emoji", + "unicode" + ] + }, + "font-family": { + types: [ + "String", + "Iden" + ], + "default": [ + ], + keywords: [ + "serif", + "sans-serif", + "monospace", + "cursive", + "fantasy", + "system-ui", + "ui-serif", + "ui-sans-serif", + "ui-monospace", + "ui-rounded", + "math", + "emoji", + "fangsong" + ], + required: true, + multiple: true, + separator: { + typ: "Comma" + } + } + } + }, + "font-weight": { + shorthand: "font" + }, + "font-style": { + shorthand: "font" + }, + "font-size": { + shorthand: "font" + }, + "line-height": { + shorthand: "font" + }, + "font-stretch": { + shorthand: "font" + }, + "font-variant": { + shorthand: "font" + }, + "font-family": { + shorthand: "font" + }, + background: { + shorthand: "background", + pattern: "background-repeat background-color background-image background-attachment background-clip background-origin background-position background-size", + keywords: [ + "none" + ], + "default": [ + ], + multiple: true, + separator: { + typ: "Comma" + }, + properties: { + "background-repeat": { + types: [ + ], + "default": [ + "repeat" + ], + multiple: true, + keywords: [ + "repeat-x", + "repeat-y", + "repeat", + "space", + "round", + "no-repeat" + ], + mapping: { + "repeat no-repeat": "repeat-x", + "no-repeat repeat": "repeat-y", + "repeat repeat": "repeat", + "space space": "space", + "round round": "round", + "no-repeat no-repeat": "no-repeat" + } + }, + "background-color": { + types: [ + "Color" + ], + "default": [ + "transparent" + ], + multiple: true, + keywords: [ + ] + }, + "background-image": { + types: [ + "UrlFunc" + ], + "default": [ + "none" + ], + keywords: [ + "none" + ] + }, + "background-attachment": { + types: [ + ], + "default": [ + "scroll" + ], + multiple: true, + keywords: [ + "scroll", + "fixed", + "local" + ] + }, + "background-clip": { + types: [ + ], + "default": [ + "border-box" + ], + multiple: true, + keywords: [ + "border-box", + "padding-box", + "content-box", + "text" + ] + }, + "background-origin": { + types: [ + ], + "default": [ + "padding-box" + ], + multiple: true, + keywords: [ + "border-box", + "padding-box", + "content-box" + ] + }, + "background-position": { + multiple: true, + types: [ + "Perc", + "Length" + ], + "default": [ + "0 0", + "top left", + "left top" + ], + keywords: [ + "top", + "left", + "center", + "bottom", + "right" + ], + mapping: { + left: "0", + top: "0", + center: "50%", + bottom: "100%", + right: "100%" + }, + constraints: { + mapping: { + max: 2 + } + } + }, + "background-size": { + multiple: true, + previous: "background-position", + prefix: { + typ: "Literal", + val: "/" + }, + types: [ + "Perc", + "Length" + ], + "default": [ + "auto", + "auto auto" + ], + keywords: [ + "auto", + "cover", + "contain" + ], + mapping: { + "auto auto": "auto" + } + } + } + }, + "background-repeat": { + shorthand: "background" + }, + "background-color": { + shorthand: "background" + }, + "background-image": { + shorthand: "background" + }, + "background-attachment": { + shorthand: "background" + }, + "background-clip": { + shorthand: "background" + }, + "background-origin": { + shorthand: "background" + }, + "background-position": { + shorthand: "background" + }, + "background-size": { + shorthand: "background" + } +}; +var config$1 = { + properties: properties, + map: map +}; + +const getConfig = () => config$1; + +const funcList = ['clamp', 'calc']; +function matchType(val, properties) { + if (val.typ == 'Iden' && properties.keywords.includes(val.val) || + (properties.types.includes(val.typ))) { + return true; } - return ''; -} -function renderToken(token, options = {}, reducer) { - if (reducer == null) { - reducer = function (acc, curr) { - if (curr.typ == 'Comment' && options.removeComments) { - if (!options.preserveLicense || !curr.val.startsWith('/*!')) { - return acc; - } - return acc + curr.val; - } - return acc + renderToken(curr, options, reducer); - }; + if (val.typ == 'Number' && val.val == '0') { + return properties.types.some(type => type == 'Length' || type == 'Angle'); } - switch (token.typ) { - case 'Color': - if (options.minify || options.colorConvert) { - if (token.kin == 'lit' && token.val.toLowerCase() == 'currentcolor') { - return 'currentcolor'; - } - let value = token.kin == 'hex' ? token.val.toLowerCase() : (token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : ''); - if (token.val == 'rgb' || token.val == 'rgba') { - value = rgb2Hex(token); - } - else if (token.val == 'hsl' || token.val == 'hsla') { - value = hsl2Hex(token); - } - else if (token.val == 'hwb') { - value = hwb2hex(token); - } - else if (token.val == 'device-cmyk') { - value = cmyk2hex(token); - } - const named_color = NAMES_COLORS[value]; - if (value !== '') { - if (value.length == 7) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6]) { - value = `#${value[1]}${value[3]}${value[5]}`; - } - } - else if (value.length == 9) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6] && - value[7] == value[8]) { - value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; - } - } - return named_color != null && named_color.length <= value.length ? named_color : value; - } - } - if (token.kin == 'hex' || token.kin == 'lit') { - return token.val; - } - case 'Start-parens': - if (!('chi' in token)) { - return '('; - } - case 'Func': - case 'UrlFunc': - case 'Pseudo-class-func': - // @ts-ignore - return ( /* options.minify && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce(reducer, '') + ')'; - case 'Includes': - return '~='; - case 'Dash-match': - return '|='; - case 'Lt': - return '<'; - case 'Lte': - return '<='; - case 'Gt': - return '>'; - case 'Gte': - return '>='; - case 'End-parens': - return ')'; - case 'Attr-start': - return '['; - case 'Attr-end': - return ']'; - case 'Whitespace': - return ' '; - case 'Colon': - return ':'; - case 'Semi-colon': - return ';'; - case 'Comma': - return ','; - case 'Important': - return '!important'; - case 'Attr': - return '[' + token.chi.reduce(reducer, '') + ']'; - case 'Time': - case 'Angle': - case 'Length': - case 'Dimension': - case 'Frequency': - case 'Resolution': - let val = reduceNumber(token.val); - let unit = token.unit; - if (token.typ == 'Angle') { - const angle = getAngle(token); - let v; - let value = val + unit; - for (const u of ['turn', 'deg', 'rad', 'grad']) { - if (token.unit == u) { - continue; - } - switch (u) { - case 'turn': - v = reduceNumber(angle); - if (v.length + 4 < value.length) { - val = v; - unit = u; - value = v + u; - } - break; - case 'deg': - v = reduceNumber(angle * 360); - if (v.length + 3 < value.length) { - val = v; - unit = u; - value = v + u; - } - break; - case 'rad': - v = reduceNumber(angle * (2 * Math.PI)); - if (v.length + 3 < value.length) { - val = v; - unit = u; - value = v + u; - } - break; - case 'grad': - v = reduceNumber(angle * 400); - if (v.length + 4 < value.length) { - val = v; - unit = u; - value = v + u; - } - break; - } - } - } - if (val === '0') { - if (token.typ == 'Time') { - return '0s'; - } - if (token.typ == 'Frequency') { - return '0Hz'; - } - // @ts-ignore - if (token.typ == 'Resolution') { - return '0x'; - } - return '0'; - } - return val + unit; - case 'Perc': - return token.val + '%'; - case 'Number': - const num = (+token.val).toString(); - if (token.val.length < num.length) { - return token.val; - } - if (num.charAt(0) === '0' && num.length > 1) { - return num.slice(1); - } - const slice = num.slice(0, 2); - if (slice == '-0') { - return '-' + num.slice(2); - } - return num; - case 'Comment': - if (options.removeComments && (!options.preserveLicense || !token.val.startsWith('/*!'))) { - return ''; - } - case 'Url-token': - case 'At-rule': - case 'Hash': - case 'Pseudo-class': - case 'Literal': - case 'String': - case 'Iden': - case 'Delim': - return /* options.minify && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val; + if (val.typ == 'Func' && funcList.includes(val.val)) { + return val.chi.every((t => ['Literal', 'Comma', 'Whitespace', 'Start-parens', 'End-parens'].includes(t.typ) || matchType(t, properties))); } - console.error(`unexpected token ${JSON.stringify(token, null, 1)}`); - // throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`); - return ''; + return false; } function eq(a, b) { @@ -3517,31 +3530,18 @@ function* tokenize(iterator) { return char; } while (value = next()) { - if (ind >= iterator.length) { - if (buffer.length > 0) { - yield pushToken(buffer); - buffer = ''; - } - break; - } if (isWhiteSpace(value.charCodeAt(0))) { if (buffer.length > 0) { yield pushToken(buffer); buffer = ''; } while (value = next()) { - if (ind >= iterator.length) { - break; - } if (!isWhiteSpace(value.charCodeAt(0))) { break; } } yield pushToken('', 'Whitespace'); buffer = ''; - if (ind >= iterator.length) { - break; - } } switch (value) { case '/': @@ -3555,34 +3555,12 @@ function* tokenize(iterator) { } buffer += value; if (peek() == '*') { - buffer += '*'; - // i++; - next(); + buffer += next(); while (value = next()) { - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - if (value == '\\') { - buffer += value; - value = next(); - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - buffer += value; - continue; - } if (value == '*') { buffer += value; - value = next(); - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - buffer += value; - if (value == '/') { - yield pushToken(buffer, 'Comment'); + if (peek() == '/') { + yield pushToken(buffer + next(), 'Comment'); buffer = ''; break; } @@ -3591,6 +3569,8 @@ function* tokenize(iterator) { buffer += value; } } + yield pushToken(buffer, 'Bad-comment'); + buffer = ''; } break; case '<': @@ -3604,15 +3584,9 @@ function* tokenize(iterator) { break; } buffer += value; - value = next(); - if (ind >= iterator.length) { - break; - } if (peek(3) == '!--') { + buffer += next(3); while (value = next()) { - if (ind >= iterator.length) { - break; - } buffer += value; if (value == '>' && prev(2) == '--') { yield pushToken(buffer, 'CDOCOMM'); @@ -3621,15 +3595,14 @@ function* tokenize(iterator) { } } } - if (ind >= iterator.length) { - yield pushToken(buffer, 'BADCDO'); - buffer = ''; - } + // if (!peek()) { + yield pushToken(buffer, 'Bad-cdo'); + buffer = ''; + // } break; case '\\': - value = next(); // EOF - if (ind + 1 >= iterator.length) { + if (!(value = next())) { // end of stream ignore \\ yield pushToken(buffer); buffer = ''; @@ -3648,8 +3621,7 @@ function* tokenize(iterator) { buffer = ''; } buffer += value; - value = next(); - if (ind >= iterator.length) { + if (!(value = next())) { yield pushToken(buffer); buffer = ''; break; @@ -3816,8 +3788,7 @@ function* tokenize(iterator) { yield pushToken(buffer); buffer = ''; } - const important = peek(9); - if (important == 'important') { + if (peek(9) == 'important') { yield pushToken('', 'Important'); next(9); buffer = ''; @@ -4160,6 +4131,11 @@ async function parse$1(iterator, opt = {}) { if (item == null) { break; } + // console.debug({item}); + if (item.hint != null && item.hint.startsWith('Bad-')) { + // bad token + continue; + } tokens.push(item); bytesIn = item.bytesIn; if (item.token == ';' || item.token == '{') { @@ -4469,41 +4445,33 @@ function parseTokens(tokens, options = {}) { // @ts-ignore t.chi.pop(); } - let isColor = true; // @ts-ignore - if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { + if (options.parseColor && t.typ == 'Func' && isColor(t)) { + // if (isColor) { // @ts-ignore - for (const v of t.chi) { - if (v.typ == 'Func' && v.val == 'var') { - isColor = false; - break; - } - } - if (isColor) { - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = t.val; + t.typ = 'Color'; + // @ts-ignore + t.kin = t.val; + // @ts-ignore + let m = t.chi.length; + while (m-- > 0) { // @ts-ignore - let m = t.chi.length; - while (m-- > 0) { + if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) { // @ts-ignore - if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) { + if (t.chi[m + 1]?.typ == 'Whitespace') { // @ts-ignore - if (t.chi[m + 1]?.typ == 'Whitespace') { - // @ts-ignore - t.chi.splice(m + 1, 1); - } + t.chi.splice(m + 1, 1); + } + // @ts-ignore + if (t.chi[m - 1]?.typ == 'Whitespace') { // @ts-ignore - if (t.chi[m - 1]?.typ == 'Whitespace') { - // @ts-ignore - t.chi.splice(m - 1, 1); - m--; - } + t.chi.splice(m - 1, 1); + m--; } } - continue; } + continue; + // } } if (t.typ == 'UrlFunc') { // @ts-ignore @@ -4528,7 +4496,7 @@ function parseTokens(tokens, options = {}) { // @ts-ignore if (t.chi.length > 0) { // @ts-ignore - parseTokens(t.chi, t.typ); + parseTokens(t.chi, options); if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.minify) { // const count = t.chi.filter(t => t.typ != 'Comment').length; @@ -4547,7 +4515,7 @@ function parseTokens(tokens, options = {}) { if (t.typ == 'Iden') { // named color const value = t.val.toLowerCase(); - if (COLORS_NAMES[value] != null) { + if (value in COLORS_NAMES) { Object.assign(t, { typ: 'Color', val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, @@ -4701,12 +4669,15 @@ async function transform(css, options = {}) { return transform$1(css, Object.assign(options, { load, resolve, cwd: options.cwd ?? process.cwd() })); } +exports.colorsFunc = colorsFunc; exports.combinators = combinators; exports.dirname = dirname; +exports.funcList = funcList; exports.getConfig = getConfig; exports.hasDeclaration = hasDeclaration; exports.isAngle = isAngle; exports.isAtKeyword = isAtKeyword; +exports.isColor = isColor; exports.isDigit = isDigit; exports.isDimension = isDimension; exports.isFrequency = isFrequency; diff --git a/dist/index.d.ts b/dist/index.d.ts index 88cef23..3b6b452 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -119,7 +119,7 @@ interface CDOCommentToken { val: string; } interface BadCDOCommentToken { - typ: 'BADCDO'; + typ: 'Bad-cdo'; val: string; } interface IncludesToken { @@ -642,6 +642,7 @@ declare function isResolution(dimension: DimensionToken): boolean; declare function isAngle(dimension: DimensionToken): boolean; declare function isTime(dimension: DimensionToken): boolean; declare function isFrequency(dimension: DimensionToken): boolean; +declare function isColor(token: Token): boolean; declare function isIdentStart(codepoint: number): boolean; declare function isDigit(codepoint: number): boolean; declare function isIdentCodepoint(codepoint: number): boolean; @@ -661,8 +662,10 @@ declare function isWhiteSpace(codepoint: number): boolean; declare const getConfig: () => PropertiesConfig; +declare const funcList: string[]; declare function matchType(val: Token, properties: PropertyMapType): boolean; +declare const colorsFunc: string[]; declare function render(data: AstNode, opt?: RenderOptions): RenderResult; declare function renderToken(token: Token, options?: RenderOptions, reducer?: (acc: string, curr: Token) => string): string; @@ -695,4 +698,4 @@ declare function resolve(url: string, currentDirectory: string, cwd?: string): { declare function parse(iterator: string, opt?: ParserOptions): Promise; declare function transform(css: string, options?: TransformOptions): Promise; -export { combinators, dirname, getConfig, hasDeclaration, isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, load, matchType, matchUrl, minify, minifyRule, parse, parseDimension, parseString, reduceSelector, render, renderToken, resolve, tokenize, transform, urlTokenMatcher, walk }; +export { colorsFunc, combinators, dirname, funcList, getConfig, hasDeclaration, isAngle, isAtKeyword, isColor, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, load, matchType, matchUrl, minify, minifyRule, parse, parseDimension, parseString, reduceSelector, render, renderToken, resolve, tokenize, transform, urlTokenMatcher, walk }; diff --git a/dist/lib/parser/declaration/list.js b/dist/lib/parser/declaration/list.js index ecfb830..b32052e 100644 --- a/dist/lib/parser/declaration/list.js +++ b/dist/lib/parser/declaration/list.js @@ -1,4 +1,5 @@ import { PropertySet } from './set.js'; +import '../../renderer/utils/color.js'; import { getConfig } from '../utils/config.js'; import { PropertyMap } from './map.js'; import { parseString } from '../parse.js'; diff --git a/dist/lib/parser/declaration/map.js b/dist/lib/parser/declaration/map.js index 0e1a815..277940d 100644 --- a/dist/lib/parser/declaration/map.js +++ b/dist/lib/parser/declaration/map.js @@ -1,7 +1,8 @@ import { eq } from '../utils/eq.js'; +import { renderToken } from '../../renderer/render.js'; +import '../../renderer/utils/color.js'; import { getConfig } from '../utils/config.js'; import { matchType } from '../utils/type.js'; -import { renderToken } from '../../renderer/render.js'; import { parseString } from '../parse.js'; import { PropertySet } from './set.js'; diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index 568126e..5e3208f 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.js @@ -1,4 +1,4 @@ -import { isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHexColor, isHash, isIdentStart } from './utils/syntax.js'; +import { isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHexColor, isHash, isIdentStart, isColor } from './utils/syntax.js'; import { renderToken } from '../renderer/render.js'; import { COLORS_NAMES } from '../renderer/utils/color.js'; import { minify, combinators } from '../ast/minify.js'; @@ -328,6 +328,11 @@ async function parse(iterator, opt = {}) { if (item == null) { break; } + // console.debug({item}); + if (item.hint != null && item.hint.startsWith('Bad-')) { + // bad token + continue; + } tokens.push(item); bytesIn = item.bytesIn; if (item.token == ';' || item.token == '{') { @@ -637,41 +642,33 @@ function parseTokens(tokens, options = {}) { // @ts-ignore t.chi.pop(); } - let isColor = true; // @ts-ignore - if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { + if (options.parseColor && t.typ == 'Func' && isColor(t)) { + // if (isColor) { // @ts-ignore - for (const v of t.chi) { - if (v.typ == 'Func' && v.val == 'var') { - isColor = false; - break; - } - } - if (isColor) { - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = t.val; + t.typ = 'Color'; + // @ts-ignore + t.kin = t.val; + // @ts-ignore + let m = t.chi.length; + while (m-- > 0) { // @ts-ignore - let m = t.chi.length; - while (m-- > 0) { + if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) { // @ts-ignore - if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) { + if (t.chi[m + 1]?.typ == 'Whitespace') { // @ts-ignore - if (t.chi[m + 1]?.typ == 'Whitespace') { - // @ts-ignore - t.chi.splice(m + 1, 1); - } + t.chi.splice(m + 1, 1); + } + // @ts-ignore + if (t.chi[m - 1]?.typ == 'Whitespace') { // @ts-ignore - if (t.chi[m - 1]?.typ == 'Whitespace') { - // @ts-ignore - t.chi.splice(m - 1, 1); - m--; - } + t.chi.splice(m - 1, 1); + m--; } } - continue; } + continue; + // } } if (t.typ == 'UrlFunc') { // @ts-ignore @@ -696,7 +693,7 @@ function parseTokens(tokens, options = {}) { // @ts-ignore if (t.chi.length > 0) { // @ts-ignore - parseTokens(t.chi, t.typ); + parseTokens(t.chi, options); if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.minify) { // const count = t.chi.filter(t => t.typ != 'Comment').length; @@ -715,7 +712,7 @@ function parseTokens(tokens, options = {}) { if (t.typ == 'Iden') { // named color const value = t.val.toLowerCase(); - if (COLORS_NAMES[value] != null) { + if (value in COLORS_NAMES) { Object.assign(t, { typ: 'Color', val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, diff --git a/dist/lib/parser/tokenize.js b/dist/lib/parser/tokenize.js index 74365f3..e4c1165 100644 --- a/dist/lib/parser/tokenize.js +++ b/dist/lib/parser/tokenize.js @@ -154,31 +154,18 @@ function* tokenize(iterator) { return char; } while (value = next()) { - if (ind >= iterator.length) { - if (buffer.length > 0) { - yield pushToken(buffer); - buffer = ''; - } - break; - } if (isWhiteSpace(value.charCodeAt(0))) { if (buffer.length > 0) { yield pushToken(buffer); buffer = ''; } while (value = next()) { - if (ind >= iterator.length) { - break; - } if (!isWhiteSpace(value.charCodeAt(0))) { break; } } yield pushToken('', 'Whitespace'); buffer = ''; - if (ind >= iterator.length) { - break; - } } switch (value) { case '/': @@ -192,34 +179,12 @@ function* tokenize(iterator) { } buffer += value; if (peek() == '*') { - buffer += '*'; - // i++; - next(); + buffer += next(); while (value = next()) { - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - if (value == '\\') { - buffer += value; - value = next(); - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - buffer += value; - continue; - } if (value == '*') { buffer += value; - value = next(); - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - buffer += value; - if (value == '/') { - yield pushToken(buffer, 'Comment'); + if (peek() == '/') { + yield pushToken(buffer + next(), 'Comment'); buffer = ''; break; } @@ -228,6 +193,8 @@ function* tokenize(iterator) { buffer += value; } } + yield pushToken(buffer, 'Bad-comment'); + buffer = ''; } break; case '<': @@ -241,15 +208,9 @@ function* tokenize(iterator) { break; } buffer += value; - value = next(); - if (ind >= iterator.length) { - break; - } if (peek(3) == '!--') { + buffer += next(3); while (value = next()) { - if (ind >= iterator.length) { - break; - } buffer += value; if (value == '>' && prev(2) == '--') { yield pushToken(buffer, 'CDOCOMM'); @@ -258,15 +219,14 @@ function* tokenize(iterator) { } } } - if (ind >= iterator.length) { - yield pushToken(buffer, 'BADCDO'); - buffer = ''; - } + // if (!peek()) { + yield pushToken(buffer, 'Bad-cdo'); + buffer = ''; + // } break; case '\\': - value = next(); // EOF - if (ind + 1 >= iterator.length) { + if (!(value = next())) { // end of stream ignore \\ yield pushToken(buffer); buffer = ''; @@ -285,8 +245,7 @@ function* tokenize(iterator) { buffer = ''; } buffer += value; - value = next(); - if (ind >= iterator.length) { + if (!(value = next())) { yield pushToken(buffer); buffer = ''; break; @@ -453,8 +412,7 @@ function* tokenize(iterator) { yield pushToken(buffer); buffer = ''; } - const important = peek(9); - if (important == 'important') { + if (peek(9) == 'important') { yield pushToken('', 'Important'); next(9); buffer = ''; diff --git a/dist/lib/parser/utils/syntax.js b/dist/lib/parser/utils/syntax.js index 29893a6..ed41aaa 100644 --- a/dist/lib/parser/utils/syntax.js +++ b/dist/lib/parser/utils/syntax.js @@ -1,3 +1,6 @@ +import { colorsFunc } from '../../renderer/render.js'; +import { COLORS_NAMES } from '../../renderer/utils/color.js'; + // https://www.w3.org/TR/CSS21/syndata.html#syntax // https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/#typedef-ident-token // '\\' @@ -23,6 +26,25 @@ function isTime(dimension) { function isFrequency(dimension) { return 'unit' in dimension && ['hz', 'khz'].includes(dimension.unit.toLowerCase()); } +function isColor(token) { + if (token.typ == 'Color') { + return true; + } + if (token.typ == 'Iden') { + // named color + return token.val.toLowerCase() in COLORS_NAMES; + } + if (token.typ == 'Func' && token.chi.length > 0 && colorsFunc.includes(token.val)) { + // @ts-ignore + for (const v of token.chi) { + if (!['Number', 'Perc', 'Comma', 'Whitespace'].includes(v.typ)) { + return false; + } + } + return true; + } + return false; +} function isLetter(codepoint) { // lowercase return (codepoint >= 0x61 && codepoint <= 0x7a) || @@ -75,19 +97,12 @@ function isIdent(name) { return true; } function isPseudo(name) { - if (name.charAt(0) != ':') { - return false; - } - if (name.endsWith('(')) { - return isIdent(name.charAt(1) == ':' ? name.slice(2, -1) : name.slice(1, -1)); - } - return isIdent(name.charAt(1) == ':' ? name.slice(2) : name.slice(1)); + 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) { - if (name.charAt(0) != '#') { - return false; - } - return isIdent(name.charAt(1)); + return name.charAt(0) == '#' && isIdent(name.charAt(1)); } function isNumber(name) { if (name.length == 0) { @@ -256,4 +271,4 @@ function isWhiteSpace(codepoint) { codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; } -export { isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension }; +export { isAngle, isAtKeyword, isColor, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension }; diff --git a/dist/lib/parser/utils/type.js b/dist/lib/parser/utils/type.js index f6a03c9..e11c06c 100644 --- a/dist/lib/parser/utils/type.js +++ b/dist/lib/parser/utils/type.js @@ -13,4 +13,4 @@ function matchType(val, properties) { return false; } -export { matchType }; +export { funcList, matchType }; diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index 2e2762c..225d4e0 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -1,5 +1,6 @@ import { getAngle, COLORS_NAMES, rgb2Hex, hsl2Hex, hwb2hex, cmyk2hex, NAMES_COLORS } from './utils/color.js'; +const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk']; function reduceNumber(val) { val = (+val).toString(); if (val === '0') { @@ -304,4 +305,4 @@ function renderToken(token, options = {}, reducer) { return ''; } -export { render, renderToken }; +export { colorsFunc, render, renderToken }; diff --git a/dist/lib/transform.js b/dist/lib/transform.js index 3b2815f..512e5f3 100644 --- a/dist/lib/transform.js +++ b/dist/lib/transform.js @@ -1,5 +1,6 @@ import { parse } from './parser/parse.js'; import { render } from './renderer/render.js'; +import './renderer/utils/color.js'; async function transform(css, options = {}) { options = { minify: true, removeEmpty: true, ...options }; diff --git a/dist/node/index.js b/dist/node/index.js index 52e7139..669ef3d 100644 --- a/dist/node/index.js +++ b/dist/node/index.js @@ -1,10 +1,10 @@ import { parse as parse$1 } from '../lib/parser/parse.js'; export { parseString, urlTokenMatcher } from '../lib/parser/parse.js'; export { tokenize } from '../lib/parser/tokenize.js'; -export { isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension } from '../lib/parser/utils/syntax.js'; +export { isAngle, isAtKeyword, isColor, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension } from '../lib/parser/utils/syntax.js'; export { getConfig } from '../lib/parser/utils/config.js'; -export { matchType } from '../lib/parser/utils/type.js'; -export { render, renderToken } from '../lib/renderer/render.js'; +export { funcList, matchType } from '../lib/parser/utils/type.js'; +export { colorsFunc, render, renderToken } from '../lib/renderer/render.js'; import { transform as transform$1 } from '../lib/transform.js'; export { combinators, hasDeclaration, minify, minifyRule, reduceSelector } from '../lib/ast/minify.js'; export { walk } from '../lib/ast/walk.js'; diff --git a/dist/web/index.js b/dist/web/index.js index 6fa40a1..2425f44 100644 --- a/dist/web/index.js +++ b/dist/web/index.js @@ -1,10 +1,10 @@ import { parse as parse$1 } from '../lib/parser/parse.js'; export { parseString, urlTokenMatcher } from '../lib/parser/parse.js'; export { tokenize } from '../lib/parser/tokenize.js'; -export { isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension } from '../lib/parser/utils/syntax.js'; +export { isAngle, isAtKeyword, isColor, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension } from '../lib/parser/utils/syntax.js'; export { getConfig } from '../lib/parser/utils/config.js'; -export { matchType } from '../lib/parser/utils/type.js'; -export { render, renderToken } from '../lib/renderer/render.js'; +export { funcList, matchType } from '../lib/parser/utils/type.js'; +export { colorsFunc, render, renderToken } from '../lib/renderer/render.js'; import { transform as transform$1 } from '../lib/transform.js'; export { combinators, hasDeclaration, minify, minifyRule, reduceSelector } from '../lib/ast/minify.js'; export { walk } from '../lib/ast/walk.js'; diff --git a/package.json b/package.json index 0c2b992..97c37d7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@tbela99/css-parser", "description": "CSS parser for node and the browser", - "version": "0.0.1-rc4", + "version": "0.0.1-rc5", "exports": { ".": "./dist/node/index.js", "./umd": "./dist/index-umd-web.js", diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index 08a0708..92f7b3f 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -179,3 +179,9 @@ export type AstNode = | AstAtRule | AstRule | AstDeclaration; + +export interface WalkResult { + node: AstNode; + parent?: AstRuleList; + root?: AstRuleList; +} \ No newline at end of file diff --git a/src/@types/tokenize.ts b/src/@types/tokenize.ts index 565927b..ce5f4c6 100644 --- a/src/@types/tokenize.ts +++ b/src/@types/tokenize.ts @@ -180,7 +180,7 @@ export interface CDOCommentToken { export interface BadCDOCommentToken { - typ: 'BADCDO'; + typ: 'Bad-cdo'; val: string; } @@ -277,8 +277,9 @@ export interface TokenStream { col: number; } +// export declare type BadTokenType = 'Bad-comment' | 'Bad-string' | 'Bad-url-token'; export declare type TokenType = 'Dimension' | 'Number' | 'Perc' | 'Angle' | 'Length' | 'Time' | 'Frequency' | - 'Resolution' | 'Attr'; + 'Resolution' | 'Attr' ; export declare type Token = LiteralToken | IdentToken | CommaToken | ColonToken | SemiColonToken | NumberToken | AtRuleToken | PercentageToken | FunctionURLToken | FunctionToken | DimensionToken | LengthToken | diff --git a/src/lib/ast/walk.ts b/src/lib/ast/walk.ts index 01d5dc5..e2ff224 100644 --- a/src/lib/ast/walk.ts +++ b/src/lib/ast/walk.ts @@ -1,4 +1,4 @@ -import {AstNode, AstRuleList} from "../../@types"; +import {AstNode, AstRuleList, WalkResult} from "../../@types"; export function* walk(node: AstNode): Generator<{ node: AstNode, @@ -10,11 +10,7 @@ export function* walk(node: AstNode): Generator<{ yield* doWalk(node, null, null); } -function* doWalk(node: AstNode, parent?: AstRuleList, root?: AstRuleList): Generator<{ - node: AstNode, - parent?: AstRuleList, - root?: AstRuleList -}> { +function* doWalk(node: AstNode, parent?: AstRuleList, root?: AstRuleList): Generator { yield {node, parent, root}; diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index 8cfa0a3..722d3f6 100644 --- a/src/lib/parser/parse.ts +++ b/src/lib/parser/parse.ts @@ -6,8 +6,7 @@ import { AstRule, AstRuleList, AstRuleStyleSheet, - AtRuleToken, - ErrorDescription, + AtRuleToken, ErrorDescription, LiteralToken, Location, ParseResult, @@ -17,18 +16,17 @@ import { PseudoClassFunctionToken, PseudoClassToken, Token, - TokenizeResult, - UrlToken + TokenizeResult, UrlToken } from "../../@types"; import { - isAtKeyword, isDimension, + isAtKeyword, isColor, isDimension, isFunction, isHash, isHexColor, isIdent, isIdentStart, isNumber, isPercentage, isPseudo, parseDimension } from "./utils"; -import {renderToken} from "../renderer"; +import {colorsFunc, renderToken} from "../renderer"; import {COLORS_NAMES} from "../renderer/utils"; import {combinators, minify} from "../ast"; import {tokenize} from "./tokenize"; @@ -435,6 +433,14 @@ export async function parse(iterator: string, opt: ParserOptions = {}): Promise< break; } + // console.debug({item}); + + if (item.hint != null && item.hint.startsWith('Bad-')) { + + // bad token + continue; + } + tokens.push(item); bytesIn = item.bytesIn; @@ -823,17 +829,11 @@ function parseTokens(tokens: Token[], options: ParseTokenOptions = {}) { // @ts-ignore t.chi.pop(); } - let isColor = true; + // @ts-ignore - if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { - // @ts-ignore - for (const v of t.chi) { - if (v.typ == 'Func' && v.val == 'var') { - isColor = false; - break; - } - } - if (isColor) { + if (options.parseColor && t.typ == 'Func' && isColor(t)) { + + // if (isColor) { // @ts-ignore t.typ = 'Color'; // @ts-ignore @@ -858,8 +858,9 @@ function parseTokens(tokens: Token[], options: ParseTokenOptions = {}) { } } } + continue; - } + // } } if (t.typ == 'UrlFunc') { // @ts-ignore @@ -884,7 +885,7 @@ function parseTokens(tokens: Token[], options: ParseTokenOptions = {}) { // @ts-ignore if (t.chi.length > 0) { // @ts-ignore - parseTokens(t.chi, t.typ, options); + parseTokens(t.chi, options); if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.minify) { // const count = t.chi.filter(t => t.typ != 'Comment').length; @@ -905,7 +906,7 @@ function parseTokens(tokens: Token[], options: ParseTokenOptions = {}) { if (t.typ == 'Iden') { // named color const value = t.val.toLowerCase(); - if (COLORS_NAMES[value] != null) { + if (value in COLORS_NAMES) { Object.assign(t, { typ: 'Color', val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, diff --git a/src/lib/parser/tokenize.ts b/src/lib/parser/tokenize.ts index 91f3b3f..3ab34cc 100644 --- a/src/lib/parser/tokenize.ts +++ b/src/lib/parser/tokenize.ts @@ -229,13 +229,6 @@ export function* tokenize(iterator: string): Generator { while (value = next()) { - if (ind >= iterator.length) { - if (buffer.length > 0) { - yield pushToken(buffer); - buffer = ''; - } - break; - } if (isWhiteSpace(value.charCodeAt(0))) { if (buffer.length > 0) { @@ -245,9 +238,6 @@ export function* tokenize(iterator: string): Generator { } while (value = next()) { - if (ind >= iterator.length) { - break; - } if (!isWhiteSpace(value.charCodeAt(0))) { break; } @@ -256,11 +246,6 @@ export function* tokenize(iterator: string): Generator { yield pushToken('', 'Whitespace'); buffer = ''; - - if (ind >= iterator.length) { - - break; - } } switch (value) { case '/': @@ -280,40 +265,16 @@ export function* tokenize(iterator: string): Generator { buffer += value; if (peek() == '*') { - buffer += '*'; - // i++; - next(); + buffer += next(); while (value = next()) { - if (ind >= iterator.length) { - - yield pushToken(buffer, 'Bad-comment'); - break; - } - if (value == '\\') { - buffer += value; - value = next(); - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - buffer += value; - continue; - } if (value == '*') { buffer += value; - value = next(); - - if (ind >= iterator.length) { - yield pushToken(buffer, 'Bad-comment'); - break; - } - buffer += value; - if (value == '/') { - yield pushToken(buffer, 'Comment'); + if (peek() == '/') { + yield pushToken(buffer + next(), 'Comment'); buffer = ''; break; } @@ -321,6 +282,9 @@ export function* tokenize(iterator: string): Generator { buffer += value; } } + + yield pushToken(buffer, 'Bad-comment'); + buffer = ''; } break; case '<': @@ -338,18 +302,13 @@ export function* tokenize(iterator: string): Generator { } buffer += value; - value = next(); - if (ind >= iterator.length) { - break; - } if (peek(3) == '!--') { + buffer += next(3); + while (value = next()) { - if (ind >= iterator.length) { - break; - } buffer += value; if (value == '>' && prev(2) == '--') { @@ -360,19 +319,17 @@ export function* tokenize(iterator: string): Generator { } } - if (ind >= iterator.length) { + // if (!peek()) { - yield pushToken(buffer, 'BADCDO'); + yield pushToken(buffer, 'Bad-cdo'); buffer = ''; - } + // } break; case '\\': - value = next(); - // EOF - if (ind + 1 >= iterator.length) { + if (!(value = next())) { // end of stream ignore \\ yield pushToken(buffer); buffer = ''; @@ -380,7 +337,6 @@ export function* tokenize(iterator: string): Generator { } buffer += prev() + value; - break; case '"': case "'": @@ -394,8 +350,8 @@ export function* tokenize(iterator: string): Generator { buffer = ''; } buffer += value; - value = next(); - if (ind >= iterator.length) { + + if (!(value = next())) { yield pushToken(buffer); buffer = ''; break; @@ -639,9 +595,7 @@ export function* tokenize(iterator: string): Generator { buffer = ''; } - const important = peek(9); - - if (important == 'important') { + if (peek(9) == 'important') { yield pushToken('', 'Important'); next(9); diff --git a/src/lib/parser/utils/syntax.ts b/src/lib/parser/utils/syntax.ts index e3cd11a..698f50f 100644 --- a/src/lib/parser/utils/syntax.ts +++ b/src/lib/parser/utils/syntax.ts @@ -1,7 +1,9 @@ // https://www.w3.org/TR/CSS21/syndata.html#syntax // https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/#typedef-ident-token -import {AngleToken, DimensionToken, LengthToken} from '../../../@types'; +import {AngleToken, DimensionToken, LengthToken, Token} from '../../../@types'; +import {colorsFunc} from "../../renderer"; +import {COLORS_NAMES} from "../../renderer/utils"; // '\\' const REVERSE_SOLIDUS = 0x5c; @@ -37,6 +39,34 @@ export function isFrequency(dimension: DimensionToken): boolean { return 'unit' in dimension && ['hz', 'khz'].includes(dimension.unit.toLowerCase()); } +export function isColor(token: Token): boolean { + + if (token.typ == 'Color') { + + return true; + } + if (token.typ == 'Iden') { + // named color + return token.val.toLowerCase() in COLORS_NAMES; + } + + if (token.typ == 'Func' && token.chi.length > 0 && colorsFunc.includes(token.val)) { + + // @ts-ignore + for (const v of token.chi) { + + if (!['Number', 'Perc', 'Comma', 'Whitespace'].includes(v.typ)) { + + return false; + } + } + + return true; + } + + return false; +} + function isLetter(codepoint: number): boolean { // lowercase @@ -119,27 +149,16 @@ export function isIdent(name: string): boolean { export function isPseudo(name: string): boolean { - if (name.charAt(0) != ':') { - - return false; - } - - if (name.endsWith('(')) { - - return isIdent(name.charAt(1) == ':' ? name.slice(2, -1) : name.slice(1, -1)) - } - - return isIdent(name.charAt(1) == ':' ? name.slice(2) : name.slice(1)) + 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)) + ); } export function isHash(name: string): boolean { - if (name.charAt(0) != '#') { - - return false; - } - - return isIdent(name.charAt(1)); + return name.charAt(0) == '#' && isIdent(name.charAt(1)); } export function isNumber(name: string): boolean { diff --git a/src/lib/parser/utils/type.ts b/src/lib/parser/utils/type.ts index 3faa986..ba6e1b2 100644 --- a/src/lib/parser/utils/type.ts +++ b/src/lib/parser/utils/type.ts @@ -1,6 +1,6 @@ import {FunctionToken, IdentToken, PropertyMapType, Token} from "../../../@types"; -const funcList = ['clamp', 'calc']; +export const funcList = ['clamp', 'calc']; export function matchType(val: Token, properties: PropertyMapType): boolean { diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index 673c272..720aeaf 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -11,6 +11,8 @@ import { } from "../../@types"; import {cmyk2hex, COLORS_NAMES, getAngle, hsl2Hex, hwb2hex, NAMES_COLORS, rgb2Hex} from "./utils"; +export const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk']; + function reduceNumber(val: string | number) { val = (+val).toString(); diff --git a/test/specs/malformed.spec.js b/test/specs/malformed.spec.js new file mode 100644 index 0000000..cf9f836 --- /dev/null +++ b/test/specs/malformed.spec.js @@ -0,0 +1,75 @@ +/* generate from test/specs/block.spec.ts */ +import {transform, render} from '../../dist/node/index.js'; +import {expect} from "@esm-bundle/chai"; +import {readFile} from "fs/promises"; +import {dirname} from "path"; + +describe('malformed tokens', function () { + + it('unclosed string #1', async function () { + const css = ` +@import 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.css`; + + return readFile(dirname(new URL(import.meta.url).pathname) + '/../files/result/font-awesome-all.css', {encoding: 'utf-8'}). + then(content => transform(css, {minify: false, resolveImport: true}).then(result => expect(result.code).equals(content))); + }); + + it('bad string #2', async function () { + const css = ` +@import 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.css +;`; + + return transform(css, {minify: false, resolveImport: true}).then(result => expect(result.code).equals('')); + }); + + it('bad string #3', async function () { + const css = ` +@import 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.css +`; + + return transform(css, {minify: false, resolveImport: true}).then(result => expect(result.code).equals('')); + }); + + it('bad string #4', async function () { + const css = ` + @charset "utf-8"; +@import 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.css +`; + + return transform(css, {minify: false, resolveImport: true}).then(result => expect(result.code).equals('')); + }); + + it('bad comment #5', async function () { + const css = ` + +.search-and-account a svg { + filter: drop-shadow(#0000004d 0 2px 5px) +/* secret +`; + + return transform(css, {minify: transform, resolveImport: true}).then(result => expect(render(result.ast, { + minify: false, + removeComments: true, + preserveLicense: true + }).code).equals(`.search-and-account a svg { + filter: drop-shadow(#0000004d 0 2px 5px) +}`)); + }); + + it('bad comment #6', async function () { + const css = ` + +#div a svg { + filter: drop-shadow(#0000004d 0 2px 5px) +