diff --git a/README.md b/README.md index 53e3e01..efaf8c9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,37 @@ CSS parser for node and the browser $ npm install @tbela99/css-parser ``` +## Transform + +Parse and render css in a single pass. + +```javascript +import {transform} from '@tbela99/css-parser'; + +const {ast, code, errors} = transform(css); +``` + +### Usage + +```javascript + +transform(css, transformOptions = {}) +``` +### TransformOptions + +Include both ParseOptions and RenderOptions + +- src: string, optional. css file location to be used with sourcemap. +- location: boolean, optional. includes node location in the ast, required for sourcemap generation. +- processImport: boolean, process @import node - not yet implemented +- compress: boolean, default to _true_. optimize ast and minify css. +- removeEmpty: boolean, remove empty nodes from the ast. +- indent: string, optional. css indention string. uses space character by default. +- newLine: string, new line character. +- removeComments: boolean, remove comments in generated css. +- preserveLicense: boolean, force preserving comments starting with '/\*!' when compress is enabling. + + ## Parsing ```javascript @@ -22,7 +53,7 @@ const {ast, errors} = parse(css); parse(css, parseOptions = {}) ``` -### parseOptions +### ParseOptions - src: string, optional. css file location - location: boolean, optional. includes node location in the ast @@ -46,7 +77,7 @@ const {code} = render(ast, {compress: true}); console.log(code); ``` -### Rendering options +### RenderOptions - compress: boolean, optional. minify output. Also remove comments - indent: string, optional. indention string. uses space character by default. diff --git a/dist/index-umd.js b/dist/index-umd.js index c83c4ee..60fa99a 100644 --- a/dist/index-umd.js +++ b/dist/index-umd.js @@ -1546,7 +1546,7 @@ children = children.slice(0, -1); } if (data.typ == 'AtRule') { - return `@${data.nam} ${data.val ? data.val + options.indent : ''}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; + return `@${data.nam}${data.val ? ' ' + 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 + `}`; } @@ -1594,8 +1594,9 @@ } case 'Func': case 'UrlFunc': + case 'Pseudo-class-func': // @ts-ignore - return token.val + '(' + token.chi.reduce((acc, curr) => { + return (options.compress && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) : token.val) + '(' + token.chi.reduce((acc, curr) => { if (options.removeComments && curr.typ == 'Comment') { if (!options.preserveLicense || !curr.val.startsWith('/*!')) { return acc; @@ -1629,6 +1630,8 @@ return ','; case 'Important': return '!important'; + case 'Attr': + return '[' + token.chi.reduce((acc, curr) => acc + renderToken(curr, options), '') + ']'; case 'Time': case 'Frequency': case 'Angle': @@ -1682,1727 +1685,2011 @@ case 'At-rule': case 'Hash': case 'Pseudo-class': - case 'Pseudo-class-func': case 'Literal': case 'String': case 'Iden': case 'Delim': - return token.val; + return options.compress && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : token.val; } throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`); } - function tokenize(iterator, errors, options) { - const tokens = []; - const src = options.src; - const stack = []; - const root = { - typ: "StyleSheet", - chi: [] - }; - const position = { - ind: 0, - lin: 1, - col: 1 - }; - let value; - let buffer = ''; - let ind = -1; - let lin = 1; - let col = 0; - let total = iterator.length; - let map = new Map; - let context = root; - if (options.location) { - root.loc = { - sta: { - ind: 0, - lin: 1, - col: 1 - }, - src: '' - }; + function eq(a, b) { + if ((typeof a != 'object') || typeof b != 'object') { + return a === b; } - function getType(val) { - if (val === '') { - throw new Error('empty string?'); - } - if (val == ':') { - return { typ: 'Colon' }; - } - if (val == ')') { - return { typ: 'End-parens' }; - } - if (val == '(') { - return { typ: 'Start-parens' }; - } - if (val == '=') { - return { typ: 'Delim', val }; - } - if (val == ';') { - return { typ: 'Semi-colon' }; - } - if (val == ',') { - return { typ: 'Comma' }; - } - if (val == '<') { - return { typ: 'Lt' }; - } - if (val == '>') { - return { typ: 'Gt' }; - } - if (isPseudo(val)) { - return { - typ: val.endsWith('(') ? 'Pseudo-class-func' : 'Pseudo-class', - val - }; - } - if (isAtKeyword(val)) { - return { - typ: 'At-rule', - val: val.slice(1) - // buffer: buffer.slice() - }; - } - if (isFunction(val)) { - val = val.slice(0, -1); - return { - typ: val == 'url' ? 'UrlFunc' : 'Func', - val, - chi: [] - }; - } - if (isNumber(val)) { - return { - typ: 'Number', - val - }; - } - if (isDimension(val)) { - return parseDimension(val); - } - if (isPercentage(val)) { - return { - typ: 'Perc', - val: val.slice(0, -1) - }; - } - if (val == 'currentColor') { - return { - typ: 'Color', - val, - kin: 'lit' - }; - } - if (isIdent(val)) { - return { - typ: 'Iden', - val - }; - } - if (val.charAt(0) == '#' && isHash(val)) { - return { - typ: 'Hash', - val - }; - } - if ('"\''.includes(val.charAt(0))) { - return { - typ: 'Unclosed-string', - val - }; - } - return { - typ: 'Literal', - val - }; + const k1 = Object.keys(a); + const k2 = Object.keys(b); + return k1.length == k2.length && + k1.every((key) => { + return eq(a[key], b[key]); + }); + } + + class PropertySet { + config; + declarations; + constructor(config) { + this.config = config; + this.declarations = new Map; } - // consume and throw away - function consume(open, close) { - let count = 1; - let chr; - while (true) { - chr = next(); - if (chr == '\\') { - if (next() === '') { - break; - } - continue; - } - else if (chr == '/' && peek() == '*') { - next(); - while (true) { - chr = next(); - if (chr === '') { - break; - } - if (chr == '*' && peek() == '/') { - next(); - break; - } - } - } - else if (chr == close) { - count--; - } - else if (chr == open) { - count++; - } - if (chr === '' || count == 0) { - break; - } + add(declaration) { + if (declaration.nam == this.config.shorthand) { + this.declarations.clear(); + this.declarations.set(declaration.nam, declaration); } - } - function parseNode(tokens) { - let i = 0; - let loc; - for (i = 0; i < tokens.length; i++) { - if (tokens[i].typ == 'Comment') { + else { + // expand shorthand + if (declaration.nam != this.config.shorthand && this.declarations.has(this.config.shorthand)) { + let isValid = true; + let current = -1; + const tokens = []; // @ts-ignore - context.chi.push(tokens[i]); - const position = map.get(tokens[i]); - loc = { - sta: position, - src - }; - if (options.location) { - tokens[i].loc = loc; - } - } - else if (tokens[i].typ != 'Whitespace') { - break; - } - } - tokens = tokens.slice(i); - const delim = tokens.pop(); - while (['Whitespace', 'Bad-string', 'Bad-comment'].includes(tokens[tokens.length - 1]?.typ)) { - tokens.pop(); - } - if (tokens.length == 0) { - return null; - } - if (tokens[0]?.typ == 'At-rule') { - const atRule = tokens.shift(); - const position = map.get(atRule); - if (atRule.val == 'charset' && position.ind > 0) { - errors.push({ action: 'drop', message: 'invalid @charset', location: { src, ...position } }); - return null; - } - while (['Whitespace'].includes(tokens[0]?.typ)) { - tokens.shift(); - } - if (atRule.val == 'import') { - // only @charset and @layer are accepted before @import - if (context.chi.length > 0) { - let i = context.chi.length; - while (i--) { - const type = context.chi[i].typ; - if (type == 'Comment') { - continue; + for (let token of this.declarations.get(this.config.shorthand).val) { + if (this.config.types.includes(token.typ) || (token.typ == 'Number' && token.val == '0' && + (this.config.types.includes('Length') || + this.config.types.includes('Angle') || + this.config.types.includes('Dimension')))) { + if (tokens.length == 0) { + tokens.push([]); + current++; } - if (type != 'AtRule') { - errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); - return null; + tokens[current].push(token); + continue; + } + if (token.typ != 'Whitespace' && token.typ != 'Comment') { + if (token.typ == 'Iden' && this.config.keywords.includes(token.val)) { + tokens[current].push(token); } - const name = context.chi[i].nam; - if (name != 'charset' && name != 'import' && name != 'layer') { - errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); - return null; + if (token.typ == 'Literal' && token.val == this.config.separator) { + tokens.push([]); + current++; + continue; } + isValid = false; break; } } - // @ts-ignore - if (tokens[0]?.typ != 'String' && tokens[0]?.typ != 'UrlFunc') { - errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); - return null; - } - // @ts-ignore - if (tokens[0].typ == 'UrlFunc' && tokens[1]?.typ != 'Url-token' && tokens[1]?.typ != 'String') { - errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); - return null; - } - } - if (atRule.val == 'import') { - // @ts-ignore - if (tokens[0].typ == 'UrlFunc' && tokens[1].typ == 'Url-token') { - tokens.shift(); - // const token: Token = tokens.shift(); - // @ts-ignore - tokens[0].typ = 'String'; - // @ts-ignore - tokens[0].val = `"${tokens[0].val}"`; - // tokens[0] = token; - } - } - // https://www.w3.org/TR/css-nesting-1/#conditionals - // allowed nesting at-rules - // there must be a top level rule in the stack - const node = { - typ: 'AtRule', - nam: renderToken(atRule, { removeComments: true }), - val: tokens.reduce((acc, curr, index, array) => { - if (curr.typ == 'Whitespace') { - if (array[index + 1]?.typ == 'Start-parens' || - array[index - 1]?.typ == 'End-parens' || - array[index - 1]?.typ == 'Func') { - return acc; - } - } - return acc + renderToken(curr, { removeComments: true }); - }, '') - }; - if (node.nam == 'import') { - if (options.processImport) { - // @ts-ignore - let fileToken = tokens[tokens[0].typ == 'UrlFunc' ? 1 : 0]; - let file = fileToken.typ == 'String' ? fileToken.val.slice(1, -1) : fileToken.val; - if (!file.startsWith('data:')) ; - } - } - if (delim.typ == 'Block-start') { - node.chi = []; - } - loc = { - sta: position, - src - }; - if (options.location) { - node.loc = loc; - } - // @ts-ignore - context.chi.push(node); - return delim.typ == 'Block-start' ? node : null; - } - else { - // rule - if (delim.typ == 'Block-start') { - let inAttr = 0; - const position = map.get(tokens[0]); - if (context.typ == 'Rule') { - if (tokens[0]?.typ == 'Iden') { - errors.push({ action: 'drop', message: 'invalid nesting rule', location: { src, ...position } }); - return null; - } - } - const node = { - typ: 'Rule', - // @ts-ignore - sel: tokens.reduce((acc, curr) => { - if (acc[acc.length - 1].length == 0 && curr.typ == 'Whitespace') { - return acc; - } - if (inAttr > 0 && curr.typ == 'String') { - const ident = curr.val.slice(1, -1); - if (isIdent(ident)) { - // @ts-ignore - curr.typ = 'Iden'; - curr.val = ident; + if (isValid && tokens.length > 0) { + this.declarations.delete(this.config.shorthand); + for (const values of tokens) { + this.config.properties.forEach((property, index) => { + // if (property == declaration.nam) { + // + // return; + // } + if (!this.declarations.has(property)) { + this.declarations.set(property, { + typ: 'Declaration', + nam: property, + val: [] + }); } - } - if (curr.typ == 'Attr-start') { - inAttr++; - } - else if (curr.typ == 'Attr-end') { - inAttr--; - } - if (inAttr == 0 && curr.typ == "Comma") { - acc.push([]); - } - else { - acc[acc.length - 1].push(curr); - } - return acc; - }, [[]]).map(part => part.reduce((acc, p, index, array) => { - if (p.typ == 'Whitespace') { - if (array[index + 1]?.typ == 'Start-parens' || - array[index - 1]?.typ == 'End-parens') { - return acc; + while (index > 0 && index >= values.length) { + if (index > 1) { + index %= 2; + } + else { + index = 0; + break; + } } - } - return acc + renderToken(p, { removeComments: true }); - }, '')).join(), - chi: [] - }; - loc = { - sta: position, - src - }; - if (options.location) { - node.loc = loc; + // @ts-ignore + const val = this.declarations.get(property).val; + if (val.length > 0) { + val.push({ typ: 'Whitespace' }); + } + val.push({ ...values[index] }); + }); + } } - // @ts-ignore - context.chi.push(node); - return node; + this.declarations.set(declaration.nam, declaration); + return this; } - else { - // declaration + // declaration.chi = declaration.chi.reduce((acc: Token[], token: Token) => { + // + // if (this.config.types.includes(token.typ) || ('0' == (token).chi && ( + // this.config.types.includes('Length') || + // this.config.types.includes('Angle') || + // this.config.types.includes('Dimension'))) || (token.typ == 'Iden' && this.config.keywords.includes(token.chi))) { + // + // acc.push(token); + // } + // + // return acc; + // }, []); + this.declarations.set(declaration.nam, declaration); + } + return this; + } + [Symbol.iterator]() { + let iterator; + const declarations = this.declarations; + if (declarations.size < this.config.properties.length || this.config.properties.some((property, index) => { + return !declarations.has(property) || (index > 0 && // @ts-ignore - let name = null; + declarations.get(property).val.length != declarations.get(this.config.properties[Math.floor(index / 2)]).val.length); + })) { + iterator = declarations.values(); + } + else { + const values = []; + this.config.properties.forEach((property) => { + let index = 0; // @ts-ignore - let value = null; - for (let i = 0; i < tokens.length; i++) { - if (tokens[i].typ == 'Comment') { + for (const token of this.declarations.get(property).val) { + if (token.typ == 'Whitespace') { continue; } - if (tokens[i].typ == 'Colon') { - name = tokens.slice(0, i); - value = tokens.slice(i + 1); - } - else if (['Func', 'Pseudo-class'].includes(tokens[i].typ) && tokens[i].val.startsWith(':')) { - tokens[i].val = tokens[i].val.slice(1); - if (tokens[i].typ == 'Pseudo-class') { - tokens[i].typ = 'Iden'; - } - name = tokens.slice(0, i); - value = tokens.slice(i); - } - } - if (name == null) { - name = tokens; - } - const position = map.get(name[0]); - // const rawName: string = (name.shift())?.val; - if (name.length > 0) { - for (let i = 1; i < name.length; i++) { - if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') { - errors.push({ - action: 'drop', - message: 'invalid declaration', - location: { src, ...position } - }); - return null; - } + if (values.length == index) { + values.push([]); } + values[index].push(token); + index++; } - // if (name.length == 0) { - // - // errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); - // return null; - // } - if (value == null) { - errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); - return null; - } - // let j: number = value.length - let i = 0; - let t; - for (; i < value.length; i++) { - t = value[i]; - if (t.typ == 'Iden') { - // named color - const value = t.val.toLowerCase(); - if (COLORS_NAMES[value] != null) { - Object.assign(t, { - typ: 'Color', - val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, - kin: 'hex' - }); + }); + for (const value of values) { + let i = value.length; + while (i-- > 1) { + const t = value[i]; + const k = value[i == 1 ? 0 : i % 2]; + if (t.val == k.val && t.val == '0') { + if ((t.typ == 'Number' && isLength(k)) || + (k.typ == 'Number' && isLength(t)) || + (isLength(k) || isLength(t))) { + value.splice(i, 1); + continue; } - continue; } - if (t.typ == 'Hash' && isHexColor(t.val)) { - // hex color - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = 'hex'; + if (eq(t, k)) { + value.splice(i, 1); continue; } - if (t.typ == 'Func' || t.typ == 'UrlFunc') { - // func color - let parens = 1; - let k = i; - let j = value.length; - let isScalar = true; - while (++k < j) { - switch (value[k].typ) { - case 'Start-parens': - case 'Func': - case 'UrlFunc': - parens++; - isScalar = false; - break; - case 'End-parens': - parens--; - break; - } - if (parens == 0) { - break; + break; + } + } + iterator = [{ + typ: 'Declaration', + nam: this.config.shorthand, + val: values.reduce((acc, curr) => { + if (curr.length > 1) { + const k = curr.length * 2 - 1; + let i = 1; + while (i < k) { + curr.splice(i, 0, { typ: 'Whitespace' }); + i += 2; } } - t.chi = value.splice(i + 1, k - i); - if (t.chi.at(-1).typ == 'End-parens') { - t.chi.pop(); - } - if (isScalar) { - if (['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = t.val; - let i = t.chi.length; - while (i-- > 0) { - if (t.chi[i].typ == 'Literal') { - if (t.chi[i + 1]?.typ == 'Whitespace') { - t.chi.splice(i + 1, 1); - } - if (t.chi[i - 1]?.typ == 'Whitespace') { - t.chi.splice(i - 1, 1); - i--; - } - } - } - } - else if (t.typ = 'UrlFunc') { - if (t.chi[0]?.typ == 'String') { - const value = t.chi[0].val.slice(1, -1); - if (/^[/%.a-zA-Z0-9_-]+$/.test(value)) { - t.chi[0].typ = 'Url-token'; - t.chi[0].val = value; - } - } - } + if (acc.length > 0) { + acc.push({ typ: 'Literal', val: this.config.separator }); } - continue; - } - if (t.typ == 'Whitespace' || t.typ == 'Comment') { - value.slice(i, 1); - } - } - if (value.length == 0) { - errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); - return null; + acc.push(...curr); + return acc; + }, []) + }][Symbol.iterator](); + return { + next() { + return iterator.next(); } - const node = { - typ: 'Declaration', - // @ts-ignore - nam: renderToken(name.shift(), { removeComments: true }), - // @ts-ignore - val: value - }; - while (node.val[0]?.typ == 'Whitespace') { - node.val.shift(); - } - if (node.val.length == 0) { - errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); - return null; - } - loc = { - sta: position, - src - }; - if (options.location) { - node.loc = loc; - } - // @ts-ignore - context.chi.push(node); - return null; - } - } - } - function peek(count = 1) { - if (count == 1) { - return iterator.charAt(ind + 1); + }; } - return iterator.slice(ind + 1, ind + count + 1); + return { + next() { + return iterator.next(); + } + }; } - function prev(count = 1) { - if (count == 1) { - return ind == 0 ? '' : iterator.charAt(ind - 1); - } - return iterator.slice(ind - 1 - count, ind - 1); + } + + function matchType(val, properties) { + if (val.typ == 'Iden' && properties.keywords.includes(val.val) || + (properties.types.includes(val.typ))) { + return true; } - function next(count = 1) { - let char = ''; - while (count-- > 0 && ind < total) { - const codepoint = iterator.charCodeAt(++ind); - if (codepoint == null) { - return char; - } - // if (codepoint < 0x80) { - char += iterator.charAt(ind); - // } - // else { - // - // const chr: string = String.fromCodePoint(codepoint); - // - // ind += chr.length - 1; - // char += chr; - // } - // ind += codepoint < 256 ? 1 : String.fromCodePoint(codepoint).length; - if (isNewLine(codepoint)) { - lin++; - col = 0; - // \r\n - // if (codepoint == 0xd && iterator.charCodeAt(i + 1) == 0xa) { - // offset++; - // ind++; - // } - } - else { - col++; - } - // ind++; - // i += offset; - } - return char; + if (val.typ == 'Number' && val.val == '0') { + return properties.types.some(type => type == 'Length' || type == 'Angle'); } - function pushToken(token) { - tokens.push(token); - map.set(token, { ...position }); - position.ind = ind; - position.lin = lin; - position.col = col == 0 ? 1 : col; - // } + return false; + } + + function getTokenType(val) { + if (val == 'transparent' || val == 'currentcolor') { + return { + typ: 'Color', + val, + kin: 'lit' + }; } - function consumeWhiteSpace() { - let count = 0; - while (isWhiteSpace(iterator.charAt(count + ind + 1).charCodeAt(0))) { - count++; - } - next(count); - return count; + if (val.endsWith('%')) { + return { + typ: 'Perc', + val: val.slice(0, -1) + }; } - function consumeString(quoteStr) { - const quote = quoteStr; - let value; - let hasNewLine = false; - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - buffer += quoteStr; - while (ind < total) { - value = peek(); - if (ind >= total) { - pushToken({ typ: hasNewLine ? 'Bad-string' : 'Unclosed-string', val: buffer }); - break; - } - if (value == '\\') { - // buffer += value; - if (ind >= total) { - // drop '\\' at the end - pushToken(getType(buffer)); - break; - } - buffer += next(2); - continue; - } - if (value == quote) { - buffer += value; - pushToken({ typ: hasNewLine ? 'Bad-string' : 'String', val: buffer }); - next(); - // i += value.length; - buffer = ''; - break; - } - if (isNewLine(value.charCodeAt(0))) { - hasNewLine = true; - } - if (hasNewLine && value == ';') { - pushToken({ typ: 'Bad-string', val: buffer }); - buffer = ''; - break; - } - buffer += value; - // i += value.length; - next(); + return { + typ: isNumber(val) ? 'Number' : 'Iden', + val + }; + } + function parseString(val) { + return val.split(/\s/).map(getTokenType).reduce((acc, curr) => { + if (acc.length > 0) { + acc.push({ typ: 'Whitespace' }); } + acc.push(curr); + return acc; + }, []); + } + class PropertyMap { + config; + declarations; + requiredCount; + pattern; + constructor(config) { + const values = Object.values(config.properties); + this.requiredCount = values.reduce((acc, curr) => curr.required ? ++acc : acc, 0) || values.length; + this.config = config; + this.declarations = new Map; + this.pattern = config.pattern.split(/\s/); } - while (ind < total) { - value = next(); - if (ind >= total) { - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - break; - } - if (isWhiteSpace(value.charCodeAt(0))) { - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - while (ind < total) { - value = next(); - if (ind >= total) { - break; - } - if (!isWhiteSpace(value.charCodeAt(0))) { - break; - } - } - pushToken({ typ: 'Whitespace' }); - buffer = ''; - if (ind >= total) { - break; - } + add(declaration) { + if (declaration.nam == this.config.shorthand) { + this.declarations.clear(); + this.declarations.set(declaration.nam, declaration); } - switch (value) { - case '/': - if (buffer.length > 0 && tokens.at(-1)?.typ == 'Whitespace') { - pushToken(getType(buffer)); - buffer = ''; - if (peek() != '*') { - pushToken(getType(value)); - break; + else { + const separator = this.config.separator; + // expand shorthand + if (declaration.nam != this.config.shorthand && this.declarations.has(this.config.shorthand)) { + const tokens = {}; + const values = []; + // @ts-ignore + this.declarations.get(this.config.shorthand).val.slice().reduce((acc, curr) => { + if (separator != null && separator.typ == curr.typ && eq(separator, curr)) { + acc.push([]); + return acc; } - } - buffer += value; - if (peek() == '*') { - buffer += '*'; - // i++; - next(); - while (ind < total) { - value = next(); - if (ind >= total) { - pushToken({ - typ: 'Bad-comment', val: buffer - }); - break; - } - if (value == '\\') { - buffer += value; - value = next(); - if (ind >= total) { - pushToken({ - typ: 'Bad-comment', - val: buffer - }); - break; + // else { + // @ts-ignore + acc.at(-1).push(curr); + // } + return acc; + }, [[]]). + // @ts-ignore + reduce((acc, list, current) => { + values.push(...this.pattern.reduce((acc, property) => { + // let current: number = 0; + const props = this.config.properties[property]; + for (let i = 0; i < acc.length; i++) { + if (acc[i].typ == 'Comment' || acc[i].typ == 'Whitespace') { + acc.splice(i, 1); + i--; + continue; } - buffer += value; - continue; - } - if (value == '*') { - buffer += value; - value = next(); - if (ind >= total) { - pushToken({ - typ: 'Bad-comment', val: buffer - }); - break; + if (matchType(acc[i], props)) { + if ('prefix' in props && props.previous != null && !(props.previous in tokens)) { + return acc; + } + if (!(property in tokens)) { + tokens[property] = [[acc[i]]]; + } + else { + if (current == tokens[property].length) { + tokens[property].push([acc[i]]); + // tokens[property][current].push(); + } + else { + tokens[property][current].push({ typ: 'Whitespace' }, acc[i]); + } + } + acc.splice(i, 1); + i--; + // @ts-ignore + if ('prefix' in props && acc[i]?.typ == props.prefix.typ) { + // @ts-ignore + if (eq(acc[i], this.config.properties[property].prefix)) { + acc.splice(i, 1); + i--; + } + } + if (props.multiple) { + continue; + } + return acc; } - buffer += value; - if (value == '/') { - pushToken({ typ: 'Comment', val: buffer }); - buffer = ''; - break; + else { + if (property in tokens && tokens[property].length > current) { + return acc; + } } } - else { - buffer += value; - } - } - } - // else { - // - // pushToken(getType(buffer)); - // buffer = ''; - // } - break; - case '<': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - buffer += value; - value = next(); - if (ind >= total) { - break; - } - if (peek(3) == '!--') { - while (ind < total) { - value = next(); - if (ind >= total) { - break; + if (property in tokens && tokens[property].length > current) { + return acc; } - buffer += value; - if (value == '>' && prev(2) == '--') { - pushToken({ - typ: 'CDOCOMM', - val: buffer - }); - buffer = ''; - break; + // default + if (props.default.length > 0) { + const defaults = parseString(props.default[0]); + if (!(property in tokens)) { + tokens[property] = [ + [...defaults + ] + ]; + } + else { + if (current == tokens[property].length) { + tokens[property].push([]); + tokens[property][current].push(...defaults); + } + else { + tokens[property][current].push({ typ: 'Whitespace' }, ...defaults); + } + } } - } - } - if (ind >= total) { - pushToken({ typ: 'BADCDO', val: buffer }); - buffer = ''; + return acc; + }, list)); + return values; + }, []); + if (values.length == 0) { + this.declarations = Object.entries(tokens).reduce((acc, curr) => { + acc.set(curr[0], { + typ: 'Declaration', + nam: curr[0], + val: curr[1].reduce((acc, curr) => { + if (acc.length > 0) { + acc.push({ ...separator }); + } + acc.push(...curr); + return acc; + }, []) + }); + return acc; + }, new Map); } - break; - case '\\': - value = next(); - // EOF - if (ind + 1 >= total) { - // end of stream ignore \\ - pushToken(getType(buffer)); - buffer = ''; - break; + } + this.declarations.set(declaration.nam, declaration); + } + return this; + } + [Symbol.iterator]() { + let requiredCount = Object.keys(this.config.properties).reduce((acc, curr) => this.declarations.has(curr) && this.config.properties[curr].required ? ++acc : acc, 0); + if (requiredCount == 0) { + requiredCount = this.declarations.size; + } + if (requiredCount < this.requiredCount) { + // if (this.declarations.size == 1 && this.declarations.has(this.config.shorthand)) { + // + // this.declarations + // } + return this.declarations.values(); + } + let count = 0; + const separator = this.config.separator; + const tokens = {}; + // @ts-ignore + const valid = Object.entries(this.config.properties).reduce((acc, curr) => { + if (!this.declarations.has(curr[0])) { + if (curr[1].required) { + acc.push(curr[0]); } - buffer += value; - break; - case '"': - case "'": - consumeString(value); - break; - case '~': - case '|': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; + return acc; + } + let current = 0; + const props = this.config.properties[curr[0]]; + // @ts-ignore + for (const val of this.declarations.get(curr[0]).val) { + if (separator != null && separator.typ == val.typ && eq(separator, val)) { + current++; + if (tokens[curr[0]].length == current) { + tokens[curr[0]].push([]); + } + continue; } - buffer += value; - value = next(); - if (ind >= total) { - pushToken(getType(buffer)); - buffer = ''; - break; + if (val.typ == 'Whitespace' || val.typ == 'Comment') { + continue; } - if (value == '=') { - buffer += value; - pushToken({ - typ: buffer[0] == '~' ? 'Includes' : 'Dash-matches', - val: buffer - }); - buffer = ''; - break; + if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(val, props.separator)) { + continue; } - pushToken(getType(buffer)); - buffer = value; - break; - case '>': - if (tokens[tokens.length - 1]?.typ == 'Whitespace') { - tokens.pop(); + if (matchType(val, curr[1])) { + if (!(curr[0] in tokens)) { + tokens[curr[0]] = [[]]; + } + // is default value + tokens[curr[0]][current].push(val); + continue; } - pushToken({ typ: 'Gt' }); - consumeWhiteSpace(); + acc.push(curr[0]); break; - case ':': - case ',': - case '=': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - if (value == ':' && isIdent(peek())) { - buffer += value; - break; + } + if (count == 0) { + count = current; + } + return acc; + }, []); + if (valid.length > 0 || Object.values(tokens).every(v => v.every(v => v.length == count))) { + return this.declarations.values(); + } + const values = Object.entries(tokens).reduce((acc, curr) => { + const props = this.config.properties[curr[0]]; + for (let i = 0; i < curr[1].length; i++) { + if (acc.length == i) { + acc.push([]); } - // if (value == ',' && tokens[tokens.length - 1]?.typ == 'Whitespace') { - // - // tokens.pop(); - // } - pushToken(getType(value)); - buffer = ''; - while (isWhiteSpace(peek().charCodeAt(0))) { - next(); + let values = curr[1][i].reduce((acc, curr) => { + if (acc.length > 0) { + acc.push({ typ: 'Whitespace' }); + } + acc.push(curr); + return acc; + }, []); + if (props.default.includes(curr[1][i].reduce((acc, curr) => acc + renderToken(curr) + ' ', '').trimEnd())) { + continue; } - break; - case ')': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - pushToken({ typ: 'End-parens' }); - break; - case '(': - if (buffer.length == 0) { - pushToken({ typ: 'Start-parens' }); - } - else { - buffer += value; - pushToken(getType(buffer)); - buffer = ''; - const token = tokens[tokens.length - 1]; - if (token.typ == 'UrlFunc' /* && token.val == 'url' */) { - // consume either string or url token - let whitespace = ''; - value = peek(); - while (isWhiteSpace(value.charCodeAt(0))) { - whitespace += value; - } - if (whitespace.length > 0) { - next(whitespace.length); - } - value = peek(); - if (value == '"' || value == "'") { - consumeString(next()); - let token = tokens[tokens.length - 1]; - if (['String', 'Literal'].includes(token.typ) && /^(["']?)[a-zA-Z0-9_/-][a-zA-Z0-9_/:.-]+(\1)$/.test(token.val)) { - if (token.typ == 'String') { - token.val = token.val.slice(1, -1); - } + values = values.filter((val) => { + if (val.typ == 'Whitespace' || val.typ == 'Comment') { + return false; + } + return !(val.typ == 'Iden' && props.default.includes(val.val)); + }); + if (values.length > 0) { + if ('mapping' in props) { + // @ts-ignore + if (!('constraints' in props) || !('max' in props.constraints) || values.length <= props.constraints.mapping.max) { + let i = values.length; + while (i--) { // @ts-ignore - // token.typ = 'Url-token'; - } - break; - } - else { - buffer = ''; - do { - let cp = value.charCodeAt(0); - // EOF - - if (cp == null) { - pushToken({ typ: 'Bad-url-token', val: buffer }); - break; - } - // ')' - if (cp == 0x29 || cp == null) { - if (buffer.length == 0) { - pushToken({ typ: 'Bad-url-token', val: '' }); - } - else { - pushToken({ typ: 'Url-token', val: buffer }); - } - if (cp != null) { - pushToken(getType(next())); - } - break; - } - if (isWhiteSpace(cp)) { - whitespace = next(); - while (true) { - value = peek(); - cp = value.charCodeAt(0); - if (isWhiteSpace(cp)) { - whitespace += value; - continue; - } - break; - } - if (cp == null || cp == 0x29) { - continue; - } - // bad url token - buffer += next(whitespace.length); - do { - value = peek(); - cp = value.charCodeAt(0); - if (cp == null || cp == 0x29) { - break; - } - buffer += next(); - } while (true); - pushToken({ typ: 'Bad-url-token', val: buffer }); - continue; + if (values[i].typ == 'Iden' && values[i].val in props.mapping) { + // @ts-ignore + values.splice(i, 1, ...parseString(props.mapping[values[i].val])); } - buffer += next(); - value = peek(); - } while (true); - buffer = ''; + } } } - } - break; - case '[': - case ']': - case '{': - case '}': - case ';': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - pushToken(getBlockType(value)); - let node = null; - if (value == '{' || value == ';') { - node = parseNode(tokens); - if (node != null) { - stack.push(node); + if ('prefix' in props) { // @ts-ignore - context = node; - } - else if (value == '{') { - // node == null - // consume and throw away until the closing '}' or EOF - consume('{', '}'); + acc[i].push({ ...props.prefix }); } - tokens.length = 0; - map.clear(); - } - else if (value == '}') { - node = parseNode(tokens); - const previousNode = stack.pop(); - // @ts-ignore - context = stack[stack.length - 1] || root; - // if (options.location && context != root) { - // @ts-ignore - // context.loc.end = {ind, lin, col: col == 0 ? 1 : col} - // } - // @ts-ignore - if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); + else if (acc[i].length > 0) { + acc[i].push({ typ: 'Whitespace' }); } - tokens.length = 0; - map.clear(); - buffer = ''; - } - // @ts-ignore - // if (node != null && options.location && ['}', ';'].includes(value) && context.chi[context.chi.length - 1].loc.end == null) { - // @ts-ignore - // context.chi[context.chi.length - 1].loc.end = {ind, lin, col}; - // } - break; - case '!': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; + acc[i].push(...values.reduce((acc, curr) => { + if (acc.length > 0) { + // @ts-ignore + acc.push({ ...(props.separator ?? { typ: 'Whitespace' }) }); + } + // @ts-ignore + acc.push(curr); + return acc; + }, [])); } - const important = peek(9); - if (important == 'important') { - if (tokens[tokens.length - 1]?.typ == 'Whitespace') { - tokens.pop(); + } + return acc; + }, []).reduce((acc, curr) => { + if (acc.length > 0) { + acc.push({ ...separator }); + } + if (curr.length == 0) { + curr.push(...this.config.default[0].split(/\s/).map(getTokenType).reduce((acc, curr) => { + if (acc.length > 0) { + acc.push({ typ: 'Whitespace' }); } - pushToken({ typ: 'Important' }); - next(9); - buffer = ''; - break; - } - buffer = '!'; - break; - default: - buffer += value; - break; - } - } - if (buffer.length > 0) { - pushToken(getType(buffer)); - } - if (tokens.length > 0) { - parseNode(tokens); - } - // pushToken({typ: 'EOF'}); - // - // if (col == 0) { - // - // col = 1; - // } - // if (options.location) { - // - // // @ts-ignore - // root.loc.end = {ind, lin, col}; - // - // for (const context of stack) { - // - // // @ts-ignore - // context.loc.end = {ind, lin, col}; - // } - // } - return root; - } - function getBlockType(chr) { - if (chr == ';') { - return { typ: 'Semi-colon' }; - } - if (chr == '{') { - return { typ: 'Block-start' }; - } - if (chr == '}') { - return { typ: 'Block-end' }; - } - if (chr == '[') { - return { typ: 'Attr-start' }; - } - if (chr == ']') { - return { typ: 'Attr-end' }; - } - throw new Error(`unhandled token: '${chr}'`); - } - - function eq(a, b) { - if ((typeof a != 'object') || typeof b != 'object') { - return a === b; + acc.push(curr); + return acc; + }, [])); + } + acc.push(...curr); + return acc; + }, []); + return [{ + typ: 'Declaration', + nam: this.config.shorthand, + val: values + }][Symbol.iterator](); } - const k1 = Object.keys(a); - const k2 = Object.keys(b); - return k1.length == k2.length && - k1.every((key) => { - return eq(a[key], b[key]); - }); } - class PropertySet { - config; + const config = getConfig(); + class PropertyList { declarations; - constructor(config) { - this.config = config; + constructor() { this.declarations = new Map; } add(declaration) { - if (declaration.nam == this.config.shorthand) { - this.declarations.clear(); - this.declarations.set(declaration.nam, declaration); + if (declaration.typ != 'Declaration') { + this.declarations.set(Number(Math.random().toString().slice(2)).toString(36), declaration); + return this; } - else { - // expand shorthand - if (declaration.nam != this.config.shorthand && this.declarations.has(this.config.shorthand)) { - let isValid = true; - let current = -1; - const tokens = []; + const propertyName = declaration.nam; + if (propertyName in config.properties) { + // @ts-ignore + const shorthand = config.properties[propertyName].shorthand; + if (!this.declarations.has(shorthand)) { // @ts-ignore - for (let token of this.declarations.get(this.config.shorthand).val) { - if (this.config.types.includes(token.typ) || (token.typ == 'Number' && token.val == '0' && - (this.config.types.includes('Length') || - this.config.types.includes('Angle') || - this.config.types.includes('Dimension')))) { - if (tokens.length == 0) { - tokens.push([]); - current++; - } - tokens[current].push(token); - continue; + this.declarations.set(shorthand, new PropertySet(config.properties[shorthand])); + } + this.declarations.get(shorthand).add(declaration); + return this; + } + if (propertyName in config.map) { + // @ts-ignore + const shorthand = config.map[propertyName].shorthand; + if (!this.declarations.has(shorthand)) { + // @ts-ignore + this.declarations.set(shorthand, new PropertyMap(config.map[shorthand])); + } + this.declarations.get(shorthand).add(declaration); + return this; + } + this.declarations.set(propertyName, declaration); + return this; + } + [Symbol.iterator]() { + let iterator = this.declarations.values(); + const iterators = []; + return { + next() { + let value = iterator.next(); + while ((value.done && iterators.length > 0) || + value.value instanceof PropertySet || + value.value instanceof PropertyMap) { + if (value.value instanceof PropertySet || value.value instanceof PropertyMap) { + iterators.unshift(iterator); + // @ts-ignore + iterator = value.value[Symbol.iterator](); + value = iterator.next(); } - if (token.typ != 'Whitespace' && token.typ != 'Comment') { - if (token.typ == 'Iden' && this.config.keywords.includes(token.val)) { - tokens[current].push(token); - } - if (token.typ == 'Literal' && token.val == this.config.separator) { - tokens.push([]); - current++; + if (value.done && iterators.length > 0) { + iterator = iterators.shift(); + value = iterator.next(); + } + } + return value; + } + }; + } + } + + const configuration = getConfig(); + function deduplicate(ast, options = {}, recursive = false) { + // @ts-ignore + if (('chi' in ast) && ast.chi?.length > 0) { + let i = 0; + let previous; + let node; + let nodeIndex; + // @ts-ignore + for (; i < ast.chi.length; i++) { + // @ts-ignore + if (ast.chi[i].typ == 'Comment') { + continue; + } + // @ts-ignore + node = ast.chi[i]; + if (node.typ == 'AtRule' && node.nam == 'font-face') { + continue; + } + if (node.typ == 'AtRule' && node.val == 'all') { + // @ts-ignore + ast.chi?.splice(i, 1, ...node.chi); + i--; + continue; + } + // @ts-ignore + if (previous != null && 'chi' in previous && ('chi' in node)) { + // @ts-ignore + if (previous.typ == node.typ) { + let shouldMerge = true; + // @ts-ignore + let k = previous.chi.length; + while (k-- > 0) { + // @ts-ignore + if (previous.chi[k].typ == 'Comment') { continue; } - isValid = false; + // @ts-ignore + shouldMerge = previous.chi[k].typ == 'Declaration'; break; } - } - if (isValid && tokens.length > 0) { - this.declarations.delete(this.config.shorthand); - for (const values of tokens) { - this.config.properties.forEach((property, index) => { - // if (property == declaration.nam) { - // - // return; - // } - if (!this.declarations.has(property)) { - this.declarations.set(property, { - typ: 'Declaration', - nam: property, - val: [] - }); + if (shouldMerge) { + // @ts-ignore + if ((node.typ == 'Rule' && node.sel == previous.sel) || + // @ts-ignore + (node.typ == 'AtRule') && node.val == previous.val) { + // @ts-ignore + node.chi.unshift(...previous.chi); + // @ts-ignore + ast.chi.splice(nodeIndex, 1); + // @ts-ignore + if (hasDeclaration(node)) { + deduplicateRule(node); } - while (index > 0 && index >= values.length) { - if (index > 1) { - index %= 2; + else { + deduplicate(node, options, recursive); + } + i--; + previous = node; + nodeIndex = i; + continue; + } + else if (node.typ == 'Rule' && previous?.typ == 'Rule') { + const intersect = diff(previous, node, options); + if (intersect != null) { + if (intersect.node1.chi.length == 0) { + // @ts-ignore + ast.chi.splice(i, 1); } else { - index = 0; - break; + // @ts-ignore + ast.chi.splice(i, 1, intersect.node1); + } + if (intersect.node2.chi.length == 0) { + // @ts-ignore + ast.chi.splice(nodeIndex, 1, intersect.result); + } + else { + // @ts-ignore + ast.chi.splice(nodeIndex, 1, intersect.result, intersect.node2); } } - // @ts-ignore - const val = this.declarations.get(property).val; - if (val.length > 0) { - val.push({ typ: 'Whitespace' }); - } - val.push({ ...values[index] }); - }); + } } } - this.declarations.set(declaration.nam, declaration); - return this; - } - // declaration.val = declaration.val.reduce((acc: Token[], token: Token) => { - // - // if (this.config.types.includes(token.typ) || ('0' == (token).val && ( - // this.config.types.includes('Length') || - // this.config.types.includes('Angle') || - // this.config.types.includes('Dimension'))) || (token.typ == 'Iden' && this.config.keywords.includes(token.val))) { - // - // acc.push(token); - // } - // - // return acc; - // }, []); - this.declarations.set(declaration.nam, declaration); - } - return this; - } - [Symbol.iterator]() { - let iterator; - const declarations = this.declarations; - if (declarations.size < this.config.properties.length || this.config.properties.some((property, index) => { - return !declarations.has(property) || (index > 0 && - // @ts-ignore - declarations.get(property).val.length != declarations.get(this.config.properties[Math.floor(index / 2)]).val.length); - })) { - iterator = declarations.values(); - } - else { - const values = []; - this.config.properties.forEach((property) => { - let index = 0; // @ts-ignore - for (const token of this.declarations.get(property).val) { - if (token.typ == 'Whitespace') { - continue; - } - if (values.length == index) { - values.push([]); - } - values[index].push(token); - index++; - } - }); - for (const value of values) { - let i = value.length; - while (i-- > 1) { - const t = value[i]; - const k = value[i == 1 ? 0 : i % 2]; - if (t.val == k.val && t.val == '0') { - if ((t.typ == 'Number' && isLength(k)) || - (k.typ == 'Number' && isLength(t)) || - (isLength(k) || isLength(t))) { - value.splice(i, 1); - continue; - } + if (recursive && previous != node) { + // @ts-ignore + if (hasDeclaration(previous)) { + deduplicateRule(previous); } - if (eq(t, k)) { - value.splice(i, 1); - continue; + else { + deduplicate(previous, options, recursive); } - break; } } - iterator = [{ - typ: 'Declaration', - nam: this.config.shorthand, - val: values.reduce((acc, curr) => { - if (curr.length > 1) { - const k = curr.length * 2 - 1; - let i = 1; - while (i < k) { - curr.splice(i, 0, { typ: 'Whitespace' }); - i += 2; - } - } - if (acc.length > 0) { - acc.push({ typ: 'Literal', val: this.config.separator }); - } - acc.push(...curr); - return acc; - }, []) - }][Symbol.iterator](); - return { - next() { - return iterator.next(); - } - }; + previous = node; + nodeIndex = i; } - return { - next() { - return iterator.next(); + // @ts-ignore + if (recursive && node != null && ('chi' in node)) { + // @ts-ignore + if (node.chi.some(n => n.typ == 'Declaration')) { + deduplicateRule(node); } - }; + else { + deduplicate(node, options, recursive); + } + } } + return ast; } - - 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'); + function hasDeclaration(node) { + // @ts-ignore + for (let i = 0; i < node.chi?.length; i++) { + // @ts-ignore + if (node.chi[i].typ == 'Comment') { + continue; + } + // @ts-ignore + return node.chi[i].typ == 'Declaration'; } - return false; + return true; } + function deduplicateRule(ast, options = {}) { + // @ts-ignore + if (!('chi' in ast) || ast.chi?.length <= 1) { + return ast; + } + // @ts-ignore + const j = ast.chi.length; + let k = 0; + let map = new Map; + // @ts-ignore + for (; k < j; k++) { + // @ts-ignore + const node = ast.chi[k]; + if (node.typ == 'Comment') { + // @ts-ignore + map.set(node, node); + continue; + } + else if (node.typ != 'Declaration') { + break; + } + if (node.nam in configuration.map || + node.nam in configuration.properties) { + // @ts-ignore + const shorthand = node.nam in configuration.map ? configuration.map[node.nam].shorthand : configuration.properties[node.nam].shorthand; + if (!map.has(shorthand)) { + map.set(shorthand, new PropertyList()); + } + map.get(shorthand).add(node); + } + else { + map.set(node.nam, node); + } + } + const children = []; + for (let child of map.values()) { + if (child instanceof PropertyList) { + // @ts-ignore + children.push(...child); + } + else { + // @ts-ignore + children.push(child); + } + } + // @ts-ignore + ast.chi = children.concat(ast.chi?.slice(k)); + /* + // @ts-ignore - function getTokenType(val) { - if (val == 'transparent' || val == 'currentcolor') { - return { - typ: 'Color', - val, - kin: 'lit' - }; + const properties: PropertyList = new PropertyList(); + + for (; k < j; k++) { + + // @ts-ignore + if ('Comment' == ast.chi[k].typ || 'Declaration' == ast.chi[k].typ) { + + // @ts-ignore + properties.add(ast.chi[k]); + continue; + } + + break; } - if (val.endsWith('%')) { - return { - typ: 'Perc', - val: val.slice(0, -1) - }; + + // @ts-ignore + ast.chi = [...properties].concat(ast.chi.slice(k)); + */ + // + // @ts-ignore + // ast.chi.splice(0, k - 1, ...properties); + return ast; + } + function splitRule(buffer) { + const result = []; + let str = ''; + for (let i = 0; i < buffer.length; i++) { + let chr = buffer.charAt(i); + if (chr == ',') { + if (str !== '') { + result.push(str); + str = ''; + } + continue; + } + str += chr; + if (chr == '\\') { + str += buffer.charAt(++i); + continue; + } + if (chr == '"' || chr == "'") { + let k = i; + while (++k < buffer.length) { + chr = buffer.charAt(k); + str += chr; + if (chr == '//') { + str += buffer.charAt(++k); + continue; + } + if (chr == buffer.charAt(i)) { + break; + } + } + continue; + } + if (chr == '(' || chr == '[') { + const open = chr; + const close = chr == '(' ? ')' : ']'; + let inParens = 1; + let k = i; + while (++k < buffer.length) { + chr = buffer.charAt(k); + if (chr == '\\') { + str += buffer.slice(k, k + 2); + k++; + continue; + } + str += chr; + if (chr == open) { + inParens++; + } + else if (chr == close) { + inParens--; + } + if (inParens == 0) { + break; + } + } + i = k; + continue; + } } - return { - typ: isNumber(val) ? 'Number' : 'Iden', - val - }; + if (str !== '') { + result.push(str); + } + return result; } - function parseString(val) { - return val.split(/\s/).map(getTokenType).reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ typ: 'Whitespace' }); + function diff(n1, n2, options = {}) { + let node1 = n1; + let node2 = n2; + let exchanged = false; + if (node1.chi.length > node2.chi.length) { + const t = node1; + node1 = node2; + node2 = t; + exchanged = true; + } + let i = node1.chi.length; + let j = node2.chi.length; + if (i == 0 || j == 0) { + // @ts-ignore + return null; + } + node1 = { ...node1, chi: node1.chi.slice() }; + node2 = { ...node2, chi: node2.chi.slice() }; + const intersect = []; + while (i--) { + if (node1.chi[i].typ == 'Comment') { + continue; } - acc.push(curr); - return acc; - }, []); + j = node2.chi.length; + if (j == 0) { + break; + } + while (j--) { + if (node2.chi[j].typ == 'Comment') { + continue; + } + if (node1.chi[i].nam == node2.chi[j].nam) { + if (eq(node1.chi[i], node2.chi[j])) { + intersect.push(node1.chi[i]); + node1.chi.splice(i, 1); + node2.chi.splice(j, 1); + break; + } + } + } + } + // @ts-ignore + const result = (intersect.length == 0 ? null : { + ...node1, + // @ts-ignore + sel: [...new Set([...(n1.raw || splitRule(n1.sel)).concat(n2.raw || splitRule(n2.sel))])].join(), + chi: intersect.reverse() + }); + if (result == null || [n1, n2].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + render(curr, options).code.length, 0) <= [node1, node2, result].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + render(curr, options).code.length, 0)) { + // @ts-ignore + return null; + } + return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node2 : node2 }; } - class PropertyMap { - config; - declarations; - requiredCount; - pattern; - constructor(config) { - const values = Object.values(config.properties); - this.requiredCount = values.reduce((acc, curr) => curr.required ? ++acc : acc, 0) || values.length; - this.config = config; - this.declarations = new Map; - this.pattern = config.pattern.split(/\s/); + + const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func']; + function parse(iterator, opt = {}) { + const errors = []; + const options = { + src: '', + location: false, + compress: false, + processImport: false, + removeEmpty: true, + ...opt + }; + if (iterator.length == 0) { + // @ts-ignore + return null; } - add(declaration) { - if (declaration.nam == this.config.shorthand) { - this.declarations.clear(); - this.declarations.set(declaration.nam, declaration); + let ind = -1; + let lin = 1; + let col = 0; + const tokens = []; + const src = options.src; + const stack = []; + const ast = { + typ: "StyleSheet", + chi: [] + }; + const position = { + ind: Math.max(ind, 0), + lin: lin, + col: Math.max(col, 1) + }; + let value; + let buffer = ''; + let total = iterator.length; + let map = new Map; + let context = ast; + if (options.location) { + ast.loc = { + sta: { + ind: ind, + lin: lin, + col: col + }, + src: '' + }; + } + function getType(val) { + if (val === '') { + throw new Error('empty string?'); } - else { - const separator = this.config.separator; - // expand shorthand - if (declaration.nam != this.config.shorthand && this.declarations.has(this.config.shorthand)) { - const tokens = {}; - const values = []; - this.declarations.get(this.config.shorthand).val.slice().reduce((acc, curr) => { - if (separator != null && separator.typ == curr.typ && eq(separator, curr)) { - acc.push([]); - return acc; - } - // else { - acc.at(-1).push(curr); - // } - return acc; - }, [[]]).reduce((acc, list, current) => { - values.push(...this.pattern.reduce((acc, property) => { - // let current: number = 0; - const props = this.config.properties[property]; - for (let i = 0; i < acc.length; i++) { - if (acc[i].typ == 'Comment' || acc[i].typ == 'Whitespace') { - acc.splice(i, 1); - i--; - continue; - } - if (matchType(acc[i], props)) { - if ('prefix' in props && props.previous != null && !(props.previous in tokens)) { - return acc; - } - if (!(property in tokens)) { - tokens[property] = [[acc[i]]]; + if (val == ':') { + return { typ: 'Colon' }; + } + if (val == ')') { + return { typ: 'End-parens' }; + } + if (val == '(') { + return { typ: 'Start-parens' }; + } + if (val == '=') { + return { typ: 'Delim', val }; + } + if (val == ';') { + return { typ: 'Semi-colon' }; + } + if (val == ',') { + return { typ: 'Comma' }; + } + if (val == '<') { + return { typ: 'Lt' }; + } + if (val == '>') { + return { typ: 'Gt' }; + } + if (isPseudo(val)) { + return val.endsWith('(') ? { + typ: 'Pseudo-class-func', + val: val.slice(0, -1), + chi: [] + } + : { + typ: 'Pseudo-class', + val + }; + } + if (isAtKeyword(val)) { + return { + typ: 'At-rule', + val: val.slice(1) + }; + } + if (isFunction(val)) { + val = val.slice(0, -1); + return { + typ: val == 'url' ? 'UrlFunc' : 'Func', + val, + chi: [] + }; + } + if (isNumber(val)) { + return { + typ: 'Number', + val + }; + } + if (isDimension(val)) { + return parseDimension(val); + } + if (isPercentage(val)) { + return { + typ: 'Perc', + val: val.slice(0, -1) + }; + } + if (val == 'currentColor') { + return { + typ: 'Color', + val, + kin: 'lit' + }; + } + if (isIdent(val)) { + return { + typ: 'Iden', + val + }; + } + if (val.charAt(0) == '#' && isHash(val)) { + return { + typ: 'Hash', + val + }; + } + if ('"\''.includes(val.charAt(0))) { + return { + typ: 'Unclosed-string', + val + }; + } + return { + typ: 'Literal', + val + }; + } + // consume and throw away + function consume(open, close) { + let count = 1; + let chr; + while (true) { + chr = next(); + if (chr == '\\') { + if (next() === '') { + break; + } + continue; + } + else if (chr == '/' && peek() == '*') { + next(); + while (true) { + chr = next(); + if (chr === '') { + break; + } + if (chr == '*' && peek() == '/') { + next(); + break; + } + } + } + else if (chr == close) { + count--; + } + else if (chr == open) { + count++; + } + if (chr === '' || count == 0) { + break; + } + } + } + function parseNode(tokens) { + let i = 0; + let loc; + for (i = 0; i < tokens.length; i++) { + if (tokens[i].typ == 'Comment') { + // @ts-ignore + context.chi.push(tokens[i]); + const position = map.get(tokens[i]); + loc = { + sta: position, + src + }; + if (options.location) { + tokens[i].loc = loc; + } + } + else if (tokens[i].typ != 'Whitespace') { + break; + } + } + tokens = tokens.slice(i); + const delim = tokens.pop(); + while (['Whitespace', 'Bad-string', 'Bad-comment'].includes(tokens[tokens.length - 1]?.typ)) { + tokens.pop(); + } + if (tokens.length == 0) { + return null; + } + if (tokens[0]?.typ == 'At-rule') { + const atRule = tokens.shift(); + const position = map.get(atRule); + if (atRule.val == 'charset' && position.ind > 0) { + errors.push({ action: 'drop', message: 'invalid @charset', location: { src, ...position } }); + return null; + } + while (['Whitespace'].includes(tokens[0]?.typ)) { + tokens.shift(); + } + if (atRule.val == 'import') { + // only @charset and @layer are accepted before @import + if (context.chi.length > 0) { + let i = context.chi.length; + while (i--) { + const type = context.chi[i].typ; + if (type == 'Comment') { + continue; + } + if (type != 'AtRule') { + errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); + return null; + } + const name = context.chi[i].nam; + if (name != 'charset' && name != 'import' && name != 'layer') { + errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); + return null; + } + break; + } + } + // @ts-ignore + if (tokens[0]?.typ != 'String' && tokens[0]?.typ != 'UrlFunc') { + errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); + return null; + } + // @ts-ignore + if (tokens[0].typ == 'UrlFunc' && tokens[1]?.typ != 'Url-token' && tokens[1]?.typ != 'String') { + errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); + return null; + } + } + if (atRule.val == 'import') { + // @ts-ignore + if (tokens[0].typ == 'UrlFunc' && tokens[1].typ == 'Url-token') { + tokens.shift(); + // const token: Token = tokens.shift(); + // @ts-ignore + tokens[0].typ = 'String'; + // @ts-ignore + tokens[0].val = `"${tokens[0].val}"`; + // tokens[0] = token; + } + } + // https://www.w3.org/TR/css-nesting-1/#conditionals + // allowed nesting at-rules + // there must be a top level rule in the stack + const node = { + typ: 'AtRule', + nam: renderToken(atRule, { removeComments: true }), + val: tokens.reduce((acc, curr, index, array) => { + if (curr.typ == 'Whitespace') { + if (array[index + 1]?.typ == 'Start-parens' || + array[index - 1]?.typ == 'End-parens' || + array[index - 1]?.typ == 'Func') { + return acc; + } + } + return acc + renderToken(curr, { removeComments: true }); + }, '') + }; + if (node.nam == 'import') { + if (options.processImport) { + // @ts-ignore + let fileToken = tokens[tokens[0].typ == 'UrlFunc' ? 1 : 0]; + let file = fileToken.typ == 'String' ? fileToken.val.slice(1, -1) : fileToken.val; + if (!file.startsWith('data:')) ; + } + } + if (delim.typ == 'Block-start') { + node.chi = []; + } + loc = { + sta: position, + src + }; + if (options.location) { + node.loc = loc; + } + // @ts-ignore + context.chi.push(node); + return delim.typ == 'Block-start' ? node : null; + } + else { + // rule + if (delim.typ == 'Block-start') { + const position = map.get(tokens[0]); + if (context.typ == 'Rule') { + if (tokens[0]?.typ == 'Iden') { + errors.push({ action: 'drop', message: 'invalid nesting rule', location: { src, ...position } }); + return null; + } + } + const sel = parseTokens(tokens, { compress: options.compress }).map(curr => renderToken(curr, { compress: true })); + const raw = [...new Set(sel.reduce((acc, curr) => { + if (curr == ',') { + acc.push(''); + } + else { + acc[acc.length - 1] += curr; + } + return acc; + }, ['']))]; + const node = { + typ: 'Rule', + // @ts-ignore + sel: raw.join(','), + chi: [] + }; + Object.defineProperty(node, 'raw', { enumerable: false, get: () => raw }); + loc = { + sta: position, + src + }; + if (options.location) { + node.loc = loc; + } + // @ts-ignore + context.chi.push(node); + return node; + } + else { + // declaration + // @ts-ignore + let name = null; + // @ts-ignore + let value = null; + for (let i = 0; i < tokens.length; i++) { + if (tokens[i].typ == 'Comment') { + continue; + } + if (tokens[i].typ == 'Colon') { + name = tokens.slice(0, i); + value = parseTokens(tokens.slice(i + 1), { parseColor: true }); + } + } + if (name == null) { + name = tokens; + } + const position = map.get(name[0]); + if (name.length > 0) { + for (let i = 1; i < name.length; i++) { + if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') { + errors.push({ + action: 'drop', + message: 'invalid declaration', + location: { src, ...position } + }); + return null; + } + } + } + // if (name.length == 0) { + // + // errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); + // return null; + // } + if (value == null) { + errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); + return null; + } + if (value.length == 0) { + errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); + return null; + } + const node = { + typ: 'Declaration', + // @ts-ignore + nam: renderToken(name.shift(), { removeComments: true }), + // @ts-ignore + val: value + }; + while (node.val[0]?.typ == 'Whitespace') { + node.val.shift(); + } + if (node.val.length == 0) { + errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); + return null; + } + loc = { + sta: position, + src + }; + if (options.location) { + node.loc = loc; + } + // @ts-ignore + context.chi.push(node); + return null; + } + } + } + function peek(count = 1) { + if (count == 1) { + return iterator.charAt(ind + 1); + } + return iterator.slice(ind + 1, ind + count + 1); + } + function prev(count = 1) { + if (count == 1) { + return ind == 0 ? '' : iterator.charAt(ind - 1); + } + return iterator.slice(ind - 1 - count, ind - 1); + } + function next(count = 1) { + let char = ''; + while (count-- > 0 && ind < total) { + const codepoint = iterator.charCodeAt(++ind); + if (isNaN(codepoint)) { + return char; + } + char += iterator.charAt(ind); + if (isNewLine(codepoint)) { + lin++; + col = 0; + } + else { + col++; + } + } + return char; + } + function pushToken(token) { + tokens.push(token); + map.set(token, { ...position }); + position.ind = ind; + position.lin = lin; + position.col = col == 0 ? 1 : col; + // } + } + function consumeWhiteSpace() { + let count = 0; + while (isWhiteSpace(iterator.charAt(count + ind + 1).charCodeAt(0))) { + count++; + } + next(count); + return count; + } + function consumeString(quoteStr) { + const quote = quoteStr; + let value; + let hasNewLine = false; + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + buffer += quoteStr; + while (ind < total) { + value = peek(); + if (ind >= total) { + pushToken({ typ: hasNewLine ? 'Bad-string' : 'Unclosed-string', val: buffer }); + break; + } + if (value == '\\') { + // buffer += value; + if (ind >= total) { + // drop '\\' at the end + pushToken(getType(buffer)); + break; + } + buffer += next(2); + continue; + } + if (value == quote) { + buffer += value; + pushToken({ typ: hasNewLine ? 'Bad-string' : 'String', val: buffer }); + next(); + // i += value.length; + buffer = ''; + break; + } + if (isNewLine(value.charCodeAt(0))) { + hasNewLine = true; + } + if (hasNewLine && value == ';') { + pushToken({ typ: 'Bad-string', val: buffer }); + buffer = ''; + break; + } + buffer += value; + // i += value.length; + next(); + } + } + while (ind < total) { + value = next(); + if (ind >= total) { + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + break; + } + if (isWhiteSpace(value.charCodeAt(0))) { + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + while (ind < total) { + value = next(); + if (ind >= total) { + break; + } + if (!isWhiteSpace(value.charCodeAt(0))) { + break; + } + } + pushToken({ typ: 'Whitespace' }); + buffer = ''; + if (ind >= total) { + break; + } + } + switch (value) { + case '/': + if (buffer.length > 0 && tokens.at(-1)?.typ == 'Whitespace') { + pushToken(getType(buffer)); + buffer = ''; + if (peek() != '*') { + pushToken(getType(value)); + break; + } + } + buffer += value; + if (peek() == '*') { + buffer += '*'; + // i++; + next(); + while (ind < total) { + value = next(); + if (ind >= total) { + pushToken({ + typ: 'Bad-comment', val: buffer + }); + break; + } + if (value == '\\') { + buffer += value; + value = next(); + if (ind >= total) { + pushToken({ + typ: 'Bad-comment', + val: buffer + }); + break; + } + buffer += value; + continue; + } + if (value == '*') { + buffer += value; + value = next(); + if (ind >= total) { + pushToken({ + typ: 'Bad-comment', val: buffer + }); + break; + } + buffer += value; + if (value == '/') { + pushToken({ typ: 'Comment', val: buffer }); + buffer = ''; + break; + } + } + else { + buffer += value; + } + } + } + // else { + // + // pushToken(getType(buffer)); + // buffer = ''; + // } + break; + case '<': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + buffer += value; + value = next(); + if (ind >= total) { + break; + } + if (peek(3) == '!--') { + while (ind < total) { + value = next(); + if (ind >= total) { + break; + } + buffer += value; + if (value == '>' && prev(2) == '--') { + pushToken({ + typ: 'CDOCOMM', + val: buffer + }); + buffer = ''; + break; + } + } + } + if (ind >= total) { + pushToken({ typ: 'BADCDO', val: buffer }); + buffer = ''; + } + break; + case '\\': + value = next(); + // EOF + if (ind + 1 >= total) { + // end of stream ignore \\ + pushToken(getType(buffer)); + buffer = ''; + break; + } + buffer += value; + break; + case '"': + case "'": + consumeString(value); + break; + case '~': + case '|': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + buffer += value; + value = next(); + if (ind >= total) { + pushToken(getType(buffer)); + buffer = ''; + break; + } + if (value == '=') { + buffer += value; + pushToken({ + typ: buffer[0] == '~' ? 'Includes' : 'Dash-matches', + val: buffer + }); + buffer = ''; + break; + } + pushToken(getType(buffer)); + buffer = value; + break; + case '>': + if (tokens[tokens.length - 1]?.typ == 'Whitespace') { + tokens.pop(); + } + pushToken({ typ: 'Gt' }); + consumeWhiteSpace(); + break; + case ':': + case ',': + case '=': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + if (value == ':' && ':' == peek()) { + buffer += value + next(); + break; + } + // if (value == ',' && tokens[tokens.length - 1]?.typ == 'Whitespace') { + // + // tokens.pop(); + // } + pushToken(getType(value)); + buffer = ''; + while (isWhiteSpace(peek().charCodeAt(0))) { + next(); + } + break; + case ')': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + pushToken({ typ: 'End-parens' }); + break; + case '(': + if (buffer.length == 0) { + pushToken({ typ: 'Start-parens' }); + } + else { + buffer += value; + pushToken(getType(buffer)); + buffer = ''; + const token = tokens[tokens.length - 1]; + if (token.typ == 'UrlFunc' /* && token.chi == 'url' */) { + // consume either string or url token + let whitespace = ''; + value = peek(); + while (isWhiteSpace(value.charCodeAt(0))) { + whitespace += value; + } + if (whitespace.length > 0) { + next(whitespace.length); + } + value = peek(); + if (value == '"' || value == "'") { + consumeString(next()); + let token = tokens[tokens.length - 1]; + if (['String', 'Literal'].includes(token.typ) && /^(["']?)[a-zA-Z0-9_/-][a-zA-Z0-9_/:.-]+(\1)$/.test(token.val)) { + if (token.typ == 'String') { + token.val = token.val.slice(1, -1); } - else { - if (current == tokens[property].length) { - tokens[property].push([acc[i]]); - // tokens[property][current].push(); + // @ts-ignore + // token.typ = 'Url-token'; + } + break; + } + else { + buffer = ''; + do { + let cp = value.charCodeAt(0); + // EOF - + if (cp == null) { + pushToken({ typ: 'Bad-url-token', val: buffer }); + break; + } + // ')' + if (cp == 0x29 || cp == null) { + if (buffer.length == 0) { + pushToken({ typ: 'Bad-url-token', val: '' }); } else { - tokens[property][current].push({ typ: 'Whitespace' }, acc[i]); + pushToken({ typ: 'Url-token', val: buffer }); } - } - acc.splice(i, 1); - i--; - // @ts-ignore - if ('prefix' in props && acc[i]?.typ == props.prefix.typ) { - // @ts-ignore - if (eq(acc[i], this.config.properties[property].prefix)) { - acc.splice(i, 1); - i--; + if (cp != null) { + pushToken(getType(next())); } + break; } - if (props.multiple) { + if (isWhiteSpace(cp)) { + whitespace = next(); + while (true) { + value = peek(); + cp = value.charCodeAt(0); + if (isWhiteSpace(cp)) { + whitespace += value; + continue; + } + break; + } + if (cp == null || cp == 0x29) { + continue; + } + // bad url token + buffer += next(whitespace.length); + do { + value = peek(); + cp = value.charCodeAt(0); + if (cp == null || cp == 0x29) { + break; + } + buffer += next(); + } while (true); + pushToken({ typ: 'Bad-url-token', val: buffer }); continue; } - return acc; - } - else { - if (property in tokens && tokens[property].length > current) { - return acc; - } - } - } - if (property in tokens && tokens[property].length > current) { - return acc; - } - // default - if (props.default.length > 0) { - const defaults = parseString(props.default[0]); - if (!(property in tokens)) { - tokens[property] = [ - [...defaults - ] - ]; - } - else { - if (current == tokens[property].length) { - tokens[property].push([]); - tokens[property][current].push(...defaults); - } - else { - tokens[property][current].push({ typ: 'Whitespace' }, ...defaults); - } - } + buffer += next(); + value = peek(); + } while (true); + buffer = ''; } - return acc; - }, list)); - return values; - }, []); - if (values.length == 0) { - this.declarations = Object.entries(tokens).reduce((acc, curr) => { - acc.set(curr[0], { - typ: 'Declaration', - nam: curr[0], - val: curr[1].reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ ...separator }); - } - acc.push(...curr); - return acc; - }, []) - }); - return acc; - }, new Map); - } - } - this.declarations.set(declaration.nam, declaration); - } - return this; - } - [Symbol.iterator]() { - let requiredCount = Object.keys(this.config.properties).reduce((acc, curr) => this.declarations.has(curr) && this.config.properties[curr].required ? ++acc : acc, 0); - if (requiredCount == 0) { - requiredCount = this.declarations.size; - } - if (requiredCount < this.requiredCount) { - return this.declarations.values(); - } - let count = 0; - const separator = this.config.separator; - const tokens = {}; - // @ts-ignore - const valid = Object.entries(this.config.properties).reduce((acc, curr) => { - if (!this.declarations.has(curr[0])) { - if (curr[1].required) { - acc.push(curr[0]); - } - return acc; - } - let current = 0; - const props = this.config.properties[curr[0]]; - // @ts-ignore - for (const val of this.declarations.get(curr[0]).val) { - if (separator != null && separator.typ == val.typ && eq(separator, val)) { - current++; - if (tokens[curr[0]].length == current) { - tokens[curr[0]].push([]); - } - continue; - } - if (val.typ == 'Whitespace' || val.typ == 'Comment') { - continue; - } - if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(val, props.separator)) { - continue; - } - if (matchType(val, curr[1])) { - if (!(curr[0] in tokens)) { - tokens[curr[0]] = [[]]; } - // is default value - tokens[curr[0]][current].push(val); - continue; } - acc.push(curr[0]); break; - } - if (count == 0) { - count = current; - } - return acc; - }, []); - if (valid.length > 0 || Object.values(tokens).every(v => v.every(v => v.length == count))) { - return this.declarations.values(); - } - const values = Object.entries(tokens).reduce((acc, curr) => { - const props = this.config.properties[curr[0]]; - for (let i = 0; i < curr[1].length; i++) { - if (acc.length == i) { - acc.push([]); + case '[': + case ']': + case '{': + case '}': + case ';': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; } - let values = curr[1][i].reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ typ: 'Whitespace' }); + pushToken(getBlockType(value)); + let node = null; + if (value == '{' || value == ';') { + node = parseNode(tokens); + if (node != null) { + stack.push(node); + // @ts-ignore + context = node; } - acc.push(curr); - return acc; - }, []); - if (props.default.includes(curr[1][i].reduce((acc, curr) => acc + renderToken(curr) + ' ', '').trimEnd())) { - continue; + else if (value == '{') { + // node == null + // consume and throw away until the closing '}' or EOF + consume('{', '}'); + } + tokens.length = 0; + map.clear(); } - values = values.filter((val) => { - if (val.typ == 'Whitespace' || val.typ == 'Comment') { - return false; + else if (value == '}') { + parseNode(tokens); + const previousNode = stack.pop(); + // @ts-ignore + context = stack[stack.length - 1] || ast; + // @ts-ignore + if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { + context.chi.pop(); } - return !(val.typ == 'Iden' && props.default.includes(val.val)); - }); - if (values.length > 0) { - if ('mapping' in props) { - if (!('constraints' in props) || !('max' in props.constraints) || values.length <= props.constraints.mapping.max) { - let i = values.length; - while (i--) { - if (values[i].typ == 'Iden' && values[i].val in props.mapping) { - values.splice(i, 1, ...parseString(props.mapping[values[i].val])); - } - } + else if (previousNode != null && previousNode != ast && options.compress) { + // @ts-ignore + if (hasDeclaration(previousNode)) { + deduplicateRule(previousNode); } - } - if ('prefix' in props) { - acc[i].push({ ...props.prefix }); - } - else if (acc[i].length > 0) { - acc[i].push({ typ: 'Whitespace' }); - } - acc[i].push(...values.reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ ...(props.separator ?? { typ: 'Whitespace' }) }); + else { + deduplicate(previousNode, options); } - acc.push(curr); - return acc; - }, [])); + } + tokens.length = 0; + map.clear(); + buffer = ''; + } + break; + case '!': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + const important = peek(9); + if (important == 'important') { + if (tokens[tokens.length - 1]?.typ == 'Whitespace') { + tokens.pop(); + } + pushToken({ typ: 'Important' }); + next(9); + buffer = ''; + break; } + buffer = '!'; + break; + default: + buffer += value; + break; + } + } + if (buffer.length > 0) { + pushToken(getType(buffer)); + } + if (options.compress) { + while (stack.length > 0) { + const node = stack.pop(); + if (hasDeclaration(node)) { + deduplicateRule(node, options); } - return acc; - }, []).reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ ...separator }); - } - if (curr.length == 0) { - curr.push(...this.config.default[0].split(/\s/).map(getTokenType).reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ typ: 'Whitespace' }); - } - acc.push(curr); - return acc; - }, [])); + else { + deduplicate(node, options); } - acc.push(...curr); - return acc; - }, []); - return [{ - typ: 'Declaration', - nam: this.config.shorthand, - val: values - }][Symbol.iterator](); + } + if (ast.chi.length > 0) { + deduplicate(ast, options); + } } + return { ast, errors }; } - - const config = getConfig(); - class PropertyList { - declarations; - constructor() { - this.declarations = new Map; - } - add(declaration) { - if (declaration.typ != 'Declaration') { - this.declarations.set(Number(Math.random().toString().slice(2)).toString(36), declaration); - return this; + function parseTokens(tokens, options = {}) { + for (let i = 0; i < tokens.length; i++) { + const t = tokens[i]; + if (t.typ == 'Whitespace' && ((i == 0 || + i + 1 == tokens.length || + ['Comma', 'Start-parens'].includes(tokens[i + 1].typ) || + (i > 0 && funcLike.includes(tokens[i - 1].typ))))) { + tokens.splice(i--, 1); + continue; } - const propertyName = declaration.nam; - if (propertyName in config.properties) { - const shorthand = config.properties[propertyName].shorthand; - if (!this.declarations.has(shorthand)) { - this.declarations.set(shorthand, new PropertySet(config.properties[shorthand])); + if (t.typ == 'Colon') { + const typ = tokens[i + 1]?.typ; + if (typ != null) { + if (typ == 'Func') { + tokens[i + 1].val = ':' + tokens[i + 1].val; + tokens[i + 1].typ = 'Pseudo-class-func'; + } + else if (typ == 'Iden') { + tokens[i + 1].val = ':' + tokens[i + 1].val; + tokens[i + 1].typ = 'Pseudo-class'; + } + if (typ == 'Func' || typ == 'Iden') { + tokens.splice(i, 1); + i--; + continue; + } } - this.declarations.get(shorthand).add(declaration); - return this; } - if (propertyName in config.map) { - const shorthand = config.map[propertyName].shorthand; - if (!this.declarations.has(shorthand)) { - this.declarations.set(shorthand, new PropertyMap(config.map[shorthand])); + if (t.typ == 'Attr-start') { + let k = i; + let inAttr = 1; + while (++k < tokens.length) { + if (tokens[k].typ == 'Attr-end') { + inAttr--; + } + else if (tokens[k].typ == 'Attr-start') { + inAttr++; + } + if (inAttr == 0) { + break; + } } - this.declarations.get(shorthand).add(declaration); - return this; - } - this.declarations.set(propertyName, declaration); - return this; - } - [Symbol.iterator]() { - let iterator = this.declarations.values(); - const iterators = []; - return { - next() { - let value = iterator.next(); - while ((value.done && iterators.length > 0) || - value.value instanceof PropertySet || - value.value instanceof PropertyMap) { - if (value.value instanceof PropertySet || value.value instanceof PropertyMap) { - iterators.unshift(iterator); - // @ts-ignore - iterator = value.value[Symbol.iterator](); - value = iterator.next(); + Object.assign(t, { typ: 'Attr', chi: tokens.splice(i + 1, k - i) }); + // @ts-ignore + if (t.chi.at(-1).typ == 'Attr-end') { + // @ts-ignore + t.chi.pop(); + // @ts-ignore + if (t.chi.length > 1) { + /*(t).chi =*/ + // @ts-ignore + parseTokens(t.chi, options); + } + // @ts-ignore + t.chi.forEach(val => { + if (val.typ == 'String') { + const slice = val.val.slice(1, -1); + if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) { + Object.assign(val, { typ: 'Iden', val: slice }); + } } - if (value.done && iterators.length > 0) { - iterator = iterators.shift(); - value = iterator.next(); + }); + } + continue; + } + if (funcLike.includes(t.typ)) { + let parens = 1; + let k = i; + while (++k < tokens.length) { + if (tokens[k].typ == 'Colon') { + const typ = tokens[k + 1]?.typ; + if (typ != null) { + if (typ == 'Iden') { + tokens[k + 1].typ = 'Pseudo-class'; + tokens[k + 1].val = ':' + tokens[k + 1].val; + } + else if (typ == 'Func') { + tokens[k + 1].typ = 'Pseudo-class-func'; + tokens[k + 1].val = ':' + tokens[k + 1].val; + } + if (typ == 'Func' || typ == 'Iden') { + tokens.splice(k, 1); + k--; + continue; + } } } - return value; + if (funcLike.includes(tokens[k].typ)) { + parens++; + } + else if (tokens[k].typ == 'End-parens') { + parens--; + } + if (parens == 0) { + break; + } } - }; - } - } - - function parse(css, opt = {}) { - const errors = []; - const options = { - src: '', - location: false, - processImport: false, - deduplicate: false, - removeEmpty: true, - ...opt - }; - if (css.length == 0) { - // @ts-ignore - return null; - } - // @ts-ignore - const ast = tokenize(css, errors, options); - if (options.deduplicate) { - deduplicate(ast); - } - return { ast, errors }; - } - function diff(node1, node2) { - // @ts-ignore - return node1.chi.every((val) => { - if (val.typ == 'Comment') { - return true; - } - if (val.typ != 'Declaration') { - return false; - } - return node2.chi.some(v => eq(v, val)); - }); - } - function deduplicate(ast) { - // @ts-ignore - if (('chi' in ast) && ast.chi?.length > 0) { - let i = 0; - let previous; - let node; - let nodeIndex; - // @ts-ignore - for (; i < ast.chi.length; i++) { // @ts-ignore - if (ast.chi[i].typ == 'Comment') { - continue; - } + t.chi = tokens.splice(i + 1, k - i); // @ts-ignore - node = ast.chi[i]; - if (node.typ == 'AtRule' && node.val == 'all') { + if (t.chi.at(-1)?.typ == 'End-parens') { // @ts-ignore - ast.chi?.splice(i, 1, ...node.chi); - i--; - continue; + t.chi.pop(); } // @ts-ignore - if (previous != null && 'chi' in previous && ('chi' in node)) { + if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { + let isColor = true; // @ts-ignore - if (previous.typ == node.typ) { - let shouldMerge = true; - // @ts-ignore - let k = previous.chi.length; - while (k-- > 0) { - // @ts-ignore - if (previous.chi[k].typ == 'Comment') { - continue; - } - // @ts-ignore - shouldMerge = previous.chi[k].typ == 'Declaration'; + for (const v of t.chi) { + if (v.typ == 'Func' && v.val == 'var') { + isColor = false; break; } - if (shouldMerge) { + } + if (!isColor) { + continue; + } + // @ts-ignore + t.typ = 'Color'; + // @ts-ignore + t.kin = t.val; + // @ts-ignore + let m = t.chi.length; + while (m-- > 0) { + // @ts-ignore + if (t.chi[m].typ == 'Literal') { // @ts-ignore - if ((node.typ == 'Rule' && node.sel == previous.sel) || - // @ts-ignore - (node.typ == 'AtRule') && node.val == previous.val) { - // @ts-ignore - node.chi.unshift(...previous.chi); + if (t.chi[m + 1]?.typ == 'Whitespace') { // @ts-ignore - ast.chi.splice(nodeIndex, 1); - i--; - previous = node; - nodeIndex = i; - continue; + t.chi.splice(m + 1, 1); } - else if (node.typ == 'Rule') { - if (diff(node, previous) && diff(previous, node)) { - if (node.typ == 'Rule') { - previous.sel += ',' + node.sel; - } - // @ts-ignore - ast.chi.splice(i, 1); - i--; - // previous = node; - // nodeIndex = i; - } + // @ts-ignore + if (t.chi[m - 1]?.typ == 'Whitespace') { + // @ts-ignore + t.chi.splice(m - 1, 1); + m--; } } } + } + else if (t.typ == 'UrlFunc') { // @ts-ignore - if (previous != node) { + if (t.chi[0]?.typ == 'String') { // @ts-ignore - if (previous.chi.some(n => n.typ == 'Declaration')) { - deduplicateRule(previous); + const value = t.chi[0].val.slice(1, -1); + if (/^[/%.a-zA-Z0-9_-]+$/.test(value)) { + // @ts-ignore + t.chi[0].typ = 'Url-token'; + // @ts-ignore + t.chi[0].val = value; } - else { - deduplicate(previous); + } + } + // @ts-ignore + if (t.chi.length > 0) { + // @ts-ignore + parseTokens(t.chi, options); + if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.compress) { + // + // console.debug({is: t}); + const count = t.chi.filter(t => t.typ != 'Comment').length; + if (count == 1 || + (i == 0 && + (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1)) || + (tokens[i - 1]?.typ == 'Comma' && (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1))) { + // console.debug(tokens[i]); + tokens.splice(i, 1, ...t.chi); + i = Math.max(0, i - t.chi.length); } + // tokens.splice(i, 1, ...t.chi); } } - previous = node; - nodeIndex = i; + continue; } - // @ts-ignore - if (node != null && ('chi' in node)) { - // @ts-ignore - if (node.chi.some(n => n.typ == 'Declaration')) { - deduplicateRule(node); + if (options.parseColor) { + if (t.typ == 'Iden') { + // named color + const value = t.val.toLowerCase(); + if (COLORS_NAMES[value] != null) { + Object.assign(t, { + typ: 'Color', + val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, + kin: 'hex' + }); + } + continue; } - else { - deduplicate(node); + if (t.typ == 'Hash' && isHexColor(t.val)) { + // hex color + // @ts-ignore + t.typ = 'Color'; + // @ts-ignore + t.kin = 'hex'; + continue; } } } - return ast; + return tokens; } - function deduplicateRule(ast) { - if (!('chi' in ast) || ast.chi?.length == 0) { - return ast; + function getBlockType(chr) { + if (chr == ';') { + return { typ: 'Semi-colon' }; } - // @ts-ignore - const j = ast.chi.length; - let k = 0; - const properties = new PropertyList(); - for (; k < j; k++) { + if (chr == '{') { + return { typ: 'Block-start' }; + } + if (chr == '}') { + return { typ: 'Block-end' }; + } + if (chr == '[') { + return { typ: 'Attr-start' }; + } + if (chr == ']') { + return { typ: 'Attr-end' }; + } + throw new Error(`unhandled token: '${chr}'`); + } + + function transform(css, options = {}) { + options = { compress: true, removeEmpty: true, ...options }; + const startTime = performance.now(); + const parseResult = parse(css, options); + if (parseResult == null) { // @ts-ignore - if ('Comment' == ast.chi[k].typ || 'Declaration' == ast.chi[k].typ) { - // @ts-ignore - properties.add(ast.chi[k]); - continue; - } - break; + return null; } - // @ts-ignore - ast.chi = [...properties].concat(ast.chi.slice(k)); - // @ts-ignore - // ast.chi.splice(0, k - 1, ...properties); - return ast; + const renderTime = performance.now(); + const rendered = render(parseResult.ast, options); + const endTime = performance.now(); + return { + ...parseResult, ...rendered, performance: { + bytesIn: css.length, + bytesOut: rendered.code.length, + parse: `${(renderTime - startTime).toFixed(2)}ms`, + render: `${(endTime - renderTime).toFixed(2)}ms`, + total: `${(endTime - startTime).toFixed(2)}ms` + } + }; } exports.deduplicate = deduplicate; exports.deduplicateRule = deduplicateRule; + exports.hasDeclaration = hasDeclaration; exports.parse = parse; exports.render = render; exports.renderToken = renderToken; + exports.transform = transform; })); diff --git a/dist/index-umd.min.js b/dist/index-umd.min.js index d1a6812..eb384cf 100644 --- a/dist/index-umd.min.js +++ b/dist/index-umd.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).CSSParser={})}(this,(function(e){"use strict";const t=92;function r(e){return"unit"in e&&["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"].includes(e.unit.toLowerCase())}function n(e){return 95==e||function(e){return e>=97&&e<=122||e>=65&&e<=90}(e)||function(e){return e>=128}(e)}function a(e){return e>=48&&e<=57}function i(e){return 45==e||a(e)||n(e)}function o(e){const r=e.length-1;let a=0,o=e.charCodeAt(0);if(45==o){const r=e.charCodeAt(1);return null!=r&&(45==r||(r!=t||e.length>2&&!c(e.charCodeAt(2))))}if(!n(o))return!1;for(;a=65&&e<=70||e>=97&&e<=102))return!1}return!0}function c(e){return 10==e||12==e||13==e}function d(e){return 9==e||32==e||c(e)}var h={properties:{inset:{shorthand:"inset",properties:["top","right","bottom","left"],types:["Length","Perc"],multiple:!1,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:!1,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:!0,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",properties:["border-top-width","border-right-width","border-bottom-width","border-left-width"],types:["Length","Perc"],keywords:["thin","medium","thick"]},"border-top-width":{shorthand:"border-width"},"border-right-width":{shorthand:"border-width"},"border-bottom-width":{shorthand:"border-width"},"border-left-width":{shorthand:"border-width"},"border-style":{shorthand:"border-style",properties:["border-top-style","border-right-style","border-bottom-style","border-left-style"],types:[],keywords:["none","hidden","dotted","dashed","solid","double","groove","ridge","inset","outset"]},"border-top-style":{shorthand:"border-style"},"border-right-style":{shorthand:"border-style"},"border-bottom-style":{shorthand:"border-style"},"border-left-style":{shorthand:"border-style"},"border-color":{shorthand:"border-color",properties:["border-top-color","border-right-color","border-bottom-color","border-left-color"],types:["Color"],keywords:[]},"border-top-color":{shorthand:"border-color"},"border-right-color":{shorthand:"border-color"},"border-bottom-color":{shorthand:"border-color"},"border-left-color":{shorthand:"border-color"}},map:{outline:{shorthand:"outline",pattern:"outline-color outline-style outline-width",keywords:["none"],default:["0","none"],properties:{"outline-color":{types:["Color"],default:["currentColor","invert"],keywords:["currentColor","invert"]},"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:!0},"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:!0,multiple:!0,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:!0,separator:{typ:"Comma"},properties:{"background-repeat":{types:[],default:["repeat"],multiple:!0,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"],keywords:[]},"background-image":{types:["UrlFunc"],default:["none"],keywords:["none"]},"background-attachment":{types:[],default:["scroll"],keywords:["scroll","fixed","local"]},"background-clip":{types:[],default:["border-box"],keywords:["border-box","padding-box","content-box","text"]},"background-origin":{types:[],default:["padding-box"],keywords:["border-box","padding-box","content-box"]},"background-position":{multiple:!0,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:!0,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"}}};const f=Object.seal({aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32",rebeccapurple:"#663399",transparent:"#00000000"}),u=Object.seal({"#f0f8ff":"aliceblue","#faebd7":"antiquewhite","#7fffd4":"aquamarine","#f0ffff":"azure","#f5f5dc":"beige","#ffe4c4":"bisque","#000000":"black","#ffebcd":"blanchedalmond","#0000ff":"blue","#8a2be2":"blueviolet","#a52a2a":"brown","#deb887":"burlywood","#5f9ea0":"cadetblue","#7fff00":"chartreuse","#d2691e":"chocolate","#ff7f50":"coral","#6495ed":"cornflowerblue","#fff8dc":"cornsilk","#dc143c":"crimson","#00ffff":"cyan","#00008b":"darkblue","#008b8b":"darkcyan","#b8860b":"darkgoldenrod","#a9a9a9":"darkgrey","#006400":"darkgreen","#bdb76b":"darkkhaki","#8b008b":"darkmagenta","#556b2f":"darkolivegreen","#ff8c00":"darkorange","#9932cc":"darkorchid","#8b0000":"darkred","#e9967a":"darksalmon","#8fbc8f":"darkseagreen","#483d8b":"darkslateblue","#2f4f4f":"darkslategrey","#00ced1":"darkturquoise","#9400d3":"darkviolet","#ff1493":"deeppink","#00bfff":"deepskyblue","#696969":"dimgrey","#1e90ff":"dodgerblue","#b22222":"firebrick","#fffaf0":"floralwhite","#228b22":"forestgreen","#dcdcdc":"gainsboro","#f8f8ff":"ghostwhite","#ffd700":"gold","#daa520":"goldenrod","#808080":"grey","#008000":"green","#adff2f":"greenyellow","#f0fff0":"honeydew","#ff69b4":"hotpink","#cd5c5c":"indianred","#4b0082":"indigo","#fffff0":"ivory","#f0e68c":"khaki","#e6e6fa":"lavender","#fff0f5":"lavenderblush","#7cfc00":"lawngreen","#fffacd":"lemonchiffon","#add8e6":"lightblue","#f08080":"lightcoral","#e0ffff":"lightcyan","#fafad2":"lightgoldenrodyellow","#d3d3d3":"lightgrey","#90ee90":"lightgreen","#ffb6c1":"lightpink","#ffa07a":"lightsalmon","#20b2aa":"lightseagreen","#87cefa":"lightskyblue","#778899":"lightslategrey","#b0c4de":"lightsteelblue","#ffffe0":"lightyellow","#00ff00":"lime","#32cd32":"limegreen","#faf0e6":"linen","#ff00ff":"magenta","#800000":"maroon","#66cdaa":"mediumaquamarine","#0000cd":"mediumblue","#ba55d3":"mediumorchid","#9370d8":"mediumpurple","#3cb371":"mediumseagreen","#7b68ee":"mediumslateblue","#00fa9a":"mediumspringgreen","#48d1cc":"mediumturquoise","#c71585":"mediumvioletred","#191970":"midnightblue","#f5fffa":"mintcream","#ffe4e1":"mistyrose","#ffe4b5":"moccasin","#ffdead":"navajowhite","#000080":"navy","#fdf5e6":"oldlace","#808000":"olive","#6b8e23":"olivedrab","#ffa500":"orange","#ff4500":"orangered","#da70d6":"orchid","#eee8aa":"palegoldenrod","#98fb98":"palegreen","#afeeee":"paleturquoise","#d87093":"palevioletred","#ffefd5":"papayawhip","#ffdab9":"peachpuff","#cd853f":"peru","#ffc0cb":"pink","#dda0dd":"plum","#b0e0e6":"powderblue","#800080":"purple","#ff0000":"red","#bc8f8f":"rosybrown","#4169e1":"royalblue","#8b4513":"saddlebrown","#fa8072":"salmon","#f4a460":"sandybrown","#2e8b57":"seagreen","#fff5ee":"seashell","#a0522d":"sienna","#c0c0c0":"silver","#87ceeb":"skyblue","#6a5acd":"slateblue","#708090":"slategrey","#fffafa":"snow","#00ff7f":"springgreen","#4682b4":"steelblue","#d2b48c":"tan","#008080":"teal","#d8bfd8":"thistle","#ff6347":"tomato","#40e0d0":"turquoise","#ee82ee":"violet","#f5deb3":"wheat","#ffffff":"white","#f5f5f5":"whitesmoke","#ffff00":"yellow","#9acd32":"yellowgreen","#663399":"rebeccapurple","#00000000":"transparent"});function p(e){if("Dimension"==e.typ)switch(e.unit){case"deg":return e.val/360;case"rad":return e.val/(2*Math.PI);case"grad":return e.val/400;case"turn":return+e.val}return e.val/360}function g(e,t,r,n=null){let a=r<=.5?r*(1+t):r+t-r*t,i=r,o=r,l=r;if(a>0){let t=r+r-a,n=(a-t)/a;e*=6;let s=Math.floor(e),c=a*n*(e-s),d=t+c,h=a-c;switch(s){case 0:i=a,o=d,l=t;break;case 1:i=h,o=a,l=t;break;case 2:i=t,o=a,l=d;break;case 3:i=t,o=h,l=a;break;case 4:i=d,o=t,l=a;break;case 5:i=a,o=t,l=h}}const s=[Math.round(255*i),Math.round(255*o),Math.round(255*l)];return null!=n&&1!=n&&s.push(Math.round(255*n)),s}const m=[];function y(e,t,r,n=0){m.length{const a=y(n,t,r);return""===a?e:""===e?a:`${e}${t.newLine}${a}`}),"");case"AtRule":case"Rule":if("AtRule"==e.typ&&!("chi"in e))return`${a}@${e.nam} ${e.val};`;let o=e.chi.reduce(((e,a)=>{let o;return o="Comment"==a.typ?t.removeComments?"":a.val:"Declaration"==a.typ?`${a.nam}:${t.indent}${a.val.reduce(r,"").trimEnd()};`:"AtRule"!=a.typ||"chi"in a?y(a,t,r,n+1):`@${a.nam} ${a.val};`,""===e?o:""===o?e:""!==o?`${e}${t.newLine}${i}${o}`:void 0}),"");return o.endsWith(";")&&(o=o.slice(0,-1)),"AtRule"==e.typ?`@${e.nam} ${e.val?e.val+t.indent:""}{${t.newLine}`+(""===o?"":i+o+t.newLine)+a+"}":e.sel+`${t.indent}{${t.newLine}`+(""===o?"":i+o+t.newLine)+a+"}"}return""}function b(e,t={}){switch(e.typ){case"Color":if(t.compress||t.colorConvert){let t="hex"==e.kin?e.val.toLowerCase():"";"rgb"==e.val||"rgba"==e.val?t=function(e){let t,r="#";for(let n=0;n<6;n+=2)t=e.chi[n],r+=Math.round("Perc"==t.typ?255*t.val/100:t.val).toString(16).padStart(2,"0");return 7==e.chi.length&&(t=e.chi[6],("Number"==t.typ&&t.val<1||"Perc"==t.typ&&t.val<100)&&(r+=Math.round(255*("Perc"==t.typ?t.val/100:t.val)).toString(16).padStart(2,"0"))),r}(e):"hsl"==e.val||"hsla"==e.val?t=function(e){let t,r=p(e.chi[0]);t=e.chi[2];let n="Perc"==t.typ?t.val/100:t.val;t=e.chi[4];let a="Perc"==t.typ?t.val/100:t.val,i=null;return 7==e.chi?.length&&(t=e.chi[6],("Perc"==t.typ&&t.val<100||"Number"==t.typ&&t.val<1)&&(i="Perc"==t.typ?t.val/100:t.val)),`#${g(r,n,a,i).reduce(((e,t)=>e+t.toString(16).padStart(2,"0")),"")}`}(e):"hwb"==e.val?t=function(e){let t,r=p(e.chi[0]);t=e.chi[2];let n="Perc"==t.typ?t.val/100:t.val;t=e.chi[4];let a="Perc"==t.typ?t.val/100:t.val,i=null;7==e.chi?.length&&(t=e.chi[6],("Perc"==t.typ&&t.val<100||"Number"==t.typ&&t.val<1)&&(i="Perc"==t.typ?t.val/100:t.val));const o=g(r,1,.5,i);let l;for(let e=0;e<3;e++)l=o[e]/255,l*=1-n-a,l+=n,o[e]=Math.round(255*l);return`#${o.reduce(((e,t)=>e+t.toString(16).padStart(2,"0")),"")}`}(e):"device-cmyk"==e.val&&(t=function(e){let t=e.chi[0];const r="Perc"==t.typ?t.val/100:t.val;t=e.chi[2];const n="Perc"==t.typ?t.val/100:t.val;t=e.chi[4];const a="Perc"==t.typ?t.val/100:t.val;t=e.chi[6];const i="Perc"==t.typ?t.val/100:t.val,o=[Math.round(255*(1-Math.min(1,r*(1-i)+i))),Math.round(255*(1-Math.min(1,n*(1-i)+i))),Math.round(255*(1-Math.min(1,a*(1-i)+i)))];return e.chi.length>=9&&(t=e.chi[8],o.push(Math.round(255*("Perc"==t.typ?t.val/100:t.val)))),`#${o.reduce(((e,t)=>e+t.toString(16).padStart(2,"0")),"")}`}(e));const r=u[t];if(""!==t)return 7==t.length?t[1]==t[2]&&t[3]==t[4]&&t[5]==t[6]&&(t=`#${t[1]}${t[3]}${t[5]}`):9==t.length&&t[1]==t[2]&&t[3]==t[4]&&t[5]==t[6]&&t[7]==t[8]&&(t=`#${t[1]}${t[3]}${t[5]}${t[7]}`),null!=r&&r.length<=t.length?r:t}if("hex"==e.kin||"lit"==e.kin)return e.val;case"Func":case"UrlFunc":return e.val+"("+e.chi.reduce(((e,r)=>!t.removeComments||"Comment"!=r.typ||t.preserveLicense&&r.val.startsWith("/*!")?e+b(r,t):e),"")+")";case"Includes":return"~=";case"Dash-match":return"|=";case"Lt":return"<";case"Gt":return">";case"Start-parens":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"Time":case"Frequency":case"Angle":case"Length":case"Dimension":const r=(+e.val).toString();if("0"===r)return"Time"==e.typ?"0s":"Frequency"==e.typ?"0Hz":"Resolution"==e.typ?"0x":"0";const n=r.charAt(0);if("-"==n){if("-0"==r.slice(0,2))return(2==r.length?"0":"-"+r.slice(2))+e.unit}else if("0"==n)return r.slice(1)+e.unit;return r+e.unit;case"Perc":return e.val+"%";case"Number":const a=(+e.val).toString();if(e.val.length1)return a.slice(1);return"-0"==a.slice(0,2)?"-"+a.slice(2):a;case"Comment":if(t.removeComments)return"";case"Url-token":case"At-rule":case"Hash":case"Pseudo-class":case"Pseudo-class-func":case"Literal":case"String":case"Iden":case"Delim":return e.val}throw new Error(`unexpected token ${JSON.stringify(e,null,1)}`)}function v(e,t,i){const h=[],u=i.src,p=[],g={typ:"StyleSheet",chi:[]},m={ind:0,lin:1,col:1};let y,v="",w=-1,C=1,x=0,A=e.length,S=new Map,L=g;function W(e){if(""===e)throw new Error("empty string?");return":"==e?{typ:"Colon"}:")"==e?{typ:"End-parens"}:"("==e?{typ:"Start-parens"}:"="==e?{typ:"Delim",val:e}:";"==e?{typ:"Semi-colon"}:","==e?{typ:"Comma"}:"<"==e?{typ:"Lt"}:">"==e?{typ:"Gt"}:":"==(t=e).charAt(0)&&(t.endsWith("(")?o(":"==t.charAt(1)?t.slice(2,-1):t.slice(1,-1)):o(":"==t.charAt(1)?t.slice(2):t.slice(1)))?{typ:e.endsWith("(")?"Pseudo-class-func":"Pseudo-class",val:e}:function(e){return 64==e.charCodeAt(0)&&o(e.slice(1))}(e)?{typ:"At-rule",val:e.slice(1)}:function(e){return e.endsWith("(")&&o(e.slice(0,-1))}(e)?{typ:"url"==(e=e.slice(0,-1))?"UrlFunc":"Func",val:e,chi:[]}:l(e)?{typ:"Number",val:e}:function(e){let t=0;for(;t++3)return!1;const r=e.slice(0,-t);return r.length>0&&n(e.charCodeAt(e.length-t))&&l(r)}(e)?function(e){let t=0;for(;t++0)return t.push({action:"drop",message:"invalid @charset",location:{src:u,...o}}),null;for(;["Whitespace"].includes(e[0]?.typ);)e.shift();if("import"==n.val){if(L.chi.length>0){let e=L.chi.length;for(;e--;){const r=L.chi[e].typ;if("Comment"==r)continue;if("AtRule"!=r)return t.push({action:"drop",message:"invalid @import",location:{src:u,...o}}),null;const n=L.chi[e].nam;if("charset"!=n&&"import"!=n&&"layer"!=n)return t.push({action:"drop",message:"invalid @import",location:{src:u,...o}}),null;break}}if("String"!=e[0]?.typ&&"UrlFunc"!=e[0]?.typ)return t.push({action:"drop",message:"invalid @import",location:{src:u,...o}}),null;if("UrlFunc"==e[0].typ&&"Url-token"!=e[1]?.typ&&"String"!=e[1]?.typ)return t.push({action:"drop",message:"invalid @import",location:{src:u,...o}}),null}"import"==n.val&&"UrlFunc"==e[0].typ&&"Url-token"==e[1].typ&&(e.shift(),e[0].typ="String",e[0].val=`"${e[0].val}"`);const l={typ:"AtRule",nam:b(n,{removeComments:!0}),val:e.reduce(((e,t,r,n)=>"Whitespace"!=t.typ||"Start-parens"!=n[r+1]?.typ&&"End-parens"!=n[r-1]?.typ&&"Func"!=n[r-1]?.typ?e+b(t,{removeComments:!0}):e),"")};if("import"==l.nam&&i.processImport){let t=e["UrlFunc"==e[0].typ?1:0];("String"==t.typ?t.val.slice(1,-1):t.val).startsWith("data:")}return"Block-start"==a.typ&&(l.chi=[]),r={sta:o,src:u},i.location&&(l.loc=r),L.chi.push(l),"Block-start"==a.typ?l:null}if("Block-start"==a.typ){let n=0;const a=S.get(e[0]);if("Rule"==L.typ&&"Iden"==e[0]?.typ)return t.push({action:"drop",message:"invalid nesting rule",location:{src:u,...a}}),null;const l={typ:"Rule",sel:e.reduce(((e,t)=>{if(0==e[e.length-1].length&&"Whitespace"==t.typ)return e;if(n>0&&"String"==t.typ){const e=t.val.slice(1,-1);o(e)&&(t.typ="Iden",t.val=e)}return"Attr-start"==t.typ?n++:"Attr-end"==t.typ&&n--,0==n&&"Comma"==t.typ?e.push([]):e[e.length-1].push(t),e}),[[]]).map((e=>e.reduce(((e,t,r,n)=>"Whitespace"!=t.typ||"Start-parens"!=n[r+1]?.typ&&"End-parens"!=n[r-1]?.typ?e+b(t,{removeComments:!0}):e),""))).join(),chi:[]};return r={sta:a,src:u},i.location&&(l.loc=r),L.chi.push(l),l}{let n=null,a=null;for(let t=0;t0)for(let e=1;e0;)"Literal"==l.chi[e].typ&&("Whitespace"==l.chi[e+1]?.typ&&l.chi.splice(e+1,1),"Whitespace"==l.chi[e-1]?.typ&&(l.chi.splice(e-1,1),e--))}else if((l.typ="UrlFunc")&&"String"==l.chi[0]?.typ){const e=l.chi[0].val.slice(1,-1);/^[/%.a-zA-Z0-9_-]+$/.test(e)&&(l.chi[0].typ="Url-token",l.chi[0].val=e)}}else{const e=l.val.toLowerCase();null!=f[e]&&Object.assign(l,{typ:"Color",val:f[e].length0&&w0&&(j(W(v)),v=""),v+=e;w=A){j({typ:n?"Bad-string":"Unclosed-string",val:v});break}if("\\"!=r){if(r==t){v+=r,j({typ:n?"Bad-string":"String",val:v}),M(),v="";break}if(c(r.charCodeAt(0))&&(n=!0),n&&";"==r){j({typ:"Bad-string",val:v}),v="";break}v+=r,M()}else{if(w>=A){j(W(v));break}v+=M(2)}}}for(i.location&&(g.loc={sta:{ind:0,lin:1,col:1},src:""});w=A){v.length>0&&(j(W(v)),v="");break}if(d(y.charCodeAt(0))){for(v.length>0&&(j(W(v)),v="");w=A))&&d(y.charCodeAt(0)););if(j({typ:"Whitespace"}),v="",w>=A)break}switch(y){case"/":if(v.length>0&&"Whitespace"==h.at(-1)?.typ&&(j(W(v)),v="","*"!=$())){j(W(y));break}if(v+=y,"*"==$())for(v+="*",M();w=A){j({typ:"Bad-comment",val:v});break}if("\\"!=y)if("*"==y){if(v+=y,y=M(),w>=A){j({typ:"Bad-comment",val:v});break}if(v+=y,"/"==y){j({typ:"Comment",val:v}),v="";break}}else v+=y;else{if(v+=y,y=M(),w>=A){j({typ:"Bad-comment",val:v});break}v+=y}}break;case"<":if(v.length>0&&(j(W(v)),v=""),v+=y,y=M(),w>=A)break;if("!--"==$(3))for(;w=A));)if(v+=y,">"==y&&"--"==z(2)){j({typ:"CDOCOMM",val:v}),v="";break}w>=A&&(j({typ:"BADCDO",val:v}),v="");break;case"\\":if(y=M(),w+1>=A){j(W(v)),v="";break}v+=y;break;case'"':case"'":F(y);break;case"~":case"|":if(v.length>0&&(j(W(v)),v=""),v+=y,y=M(),w>=A){j(W(v)),v="";break}if("="==y){v+=y,j({typ:"~"==v[0]?"Includes":"Dash-matches",val:v}),v="";break}j(W(v)),v=y;break;case">":"Whitespace"==h[h.length-1]?.typ&&h.pop(),j({typ:"Gt"}),D();break;case":":case",":case"=":if(v.length>0&&(j(W(v)),v=""),":"==y&&o($())){v+=y;break}for(j(W(y)),v="";d($().charCodeAt(0));)M();break;case")":v.length>0&&(j(W(v)),v=""),j({typ:"End-parens"});break;case"(":if(0==v.length)j({typ:"Start-parens"});else{v+=y,j(W(v)),v="";if("UrlFunc"==h[h.length-1].typ){let e="";for(y=$();d(y.charCodeAt(0));)e+=y;if(e.length>0&&M(e.length),y=$(),'"'==y||"'"==y){F(M());let e=h[h.length-1];["String","Literal"].includes(e.typ)&&/^(["']?)[a-zA-Z0-9_/-][a-zA-Z0-9_/:.-]+(\1)$/.test(e.val)&&"String"==e.typ&&(e.val=e.val.slice(1,-1));break}for(v="";;){let t=y.charCodeAt(0);if(null==t){j({typ:"Bad-url-token",val:v});break}if(41==t||null==t){0==v.length?j({typ:"Bad-url-token",val:""}):j({typ:"Url-token",val:v}),null!=t&&j(W(M()));break}if(d(t)){for(e=M();y=$(),t=y.charCodeAt(0),d(t);)e+=y;if(null==t||41==t)continue;for(v+=M(e.length);;){if(y=$(),t=y.charCodeAt(0),null==t||41==t)break;v+=M()}j({typ:"Bad-url-token",val:v})}else v+=M(),y=$()}v=""}}break;case"[":case"]":case"{":case"}":case";":v.length>0&&(j(W(v)),v=""),j(k(y));let e=null;if("{"==y||";"==y)e=q(h),null!=e?(p.push(e),L=e):"{"==y&&P("{","}"),h.length=0,S.clear();else if("}"==y){e=q(h);const t=p.pop();L=p[p.length-1]||g,i.removeEmpty&&null!=t&&0==t.chi.length&&L.chi[L.chi.length-1]==t&&L.chi.pop(),h.length=0,S.clear(),v=""}break;case"!":v.length>0&&(j(W(v)),v="");if("important"==$(9)){"Whitespace"==h[h.length-1]?.typ&&h.pop(),j({typ:"Important"}),M(9),v="";break}v="!";break;default:v+=y}}return v.length>0&&j(W(v)),h.length>0&&q(h),g}function k(e){if(";"==e)return{typ:"Semi-colon"};if("{"==e)return{typ:"Block-start"};if("}"==e)return{typ:"Block-end"};if("["==e)return{typ:"Attr-start"};if("]"==e)return{typ:"Attr-end"};throw new Error(`unhandled token: '${e}'`)}function w(e,t){if("object"!=typeof e||"object"!=typeof t)return e===t;const r=Object.keys(e),n=Object.keys(t);return r.length==n.length&&r.every((r=>w(e[r],t[r])))}class C{config;declarations;constructor(e){this.config=e,this.declarations=new Map}add(e){if(e.nam==this.config.shorthand)this.declarations.clear(),this.declarations.set(e.nam,e);else{if(e.nam!=this.config.shorthand&&this.declarations.has(this.config.shorthand)){let t=!0,r=-1;const n=[];for(let e of this.declarations.get(this.config.shorthand).val)if(this.config.types.includes(e.typ)||"Number"==e.typ&&"0"==e.val&&(this.config.types.includes("Length")||this.config.types.includes("Angle")||this.config.types.includes("Dimension")))0==n.length&&(n.push([]),r++),n[r].push(e);else if("Whitespace"!=e.typ&&"Comment"!=e.typ){if("Iden"==e.typ&&this.config.keywords.includes(e.val)&&n[r].push(e),"Literal"==e.typ&&e.val==this.config.separator){n.push([]),r++;continue}t=!1;break}if(t&&n.length>0){this.declarations.delete(this.config.shorthand);for(const e of n)this.config.properties.forEach(((t,r)=>{for(this.declarations.has(t)||this.declarations.set(t,{typ:"Declaration",nam:t,val:[]});r>0&&r>=e.length;){if(!(r>1)){r=0;break}r%=2}const n=this.declarations.get(t).val;n.length>0&&n.push({typ:"Whitespace"}),n.push({...e[r]})}))}return this.declarations.set(e.nam,e),this}this.declarations.set(e.nam,e)}return this}[Symbol.iterator](){let e;const t=this.declarations;if(!(t.size!t.has(e)||r>0&&t.get(e).val.length!=t.get(this.config.properties[Math.floor(r/2)]).val.length)))){const t=[];this.config.properties.forEach((e=>{let r=0;for(const n of this.declarations.get(e).val)"Whitespace"!=n.typ&&(t.length==r&&t.push([]),t[r].push(n),r++)}));for(const e of t){let t=e.length;for(;t-- >1;){const n=e[t],a=e[1==t?0:t%2];if(n.val==a.val&&"0"==n.val&&("Number"==n.typ&&r(a)||"Number"==a.typ&&r(n)||r(a)||r(n)))e.splice(t,1);else{if(!w(n,a))break;e.splice(t,1)}}}return e=[{typ:"Declaration",nam:this.config.shorthand,val:t.reduce(((e,t)=>{if(t.length>1){const e=2*t.length-1;let r=1;for(;r0&&e.push({typ:"Literal",val:this.config.separator}),e.push(...t),e}),[])}][Symbol.iterator](),{next:()=>e.next()}}return e=t.values(),{next:()=>e.next()}}}function x(e,t){return!!("Iden"==e.typ&&t.keywords.includes(e.val)||t.types.includes(e.typ))||"Number"==e.typ&&"0"==e.val&&t.types.some((e=>"Length"==e||"Angle"==e))}function A(e){return"transparent"==e||"currentcolor"==e?{typ:"Color",val:e,kin:"lit"}:e.endsWith("%")?{typ:"Perc",val:e.slice(0,-1)}:{typ:l(e)?"Number":"Iden",val:e}}function S(e){return e.split(/\s/).map(A).reduce(((e,t)=>(e.length>0&&e.push({typ:"Whitespace"}),e.push(t),e)),[])}class L{config;declarations;requiredCount;pattern;constructor(e){const t=Object.values(e.properties);this.requiredCount=t.reduce(((e,t)=>t.required?++e:e),0)||t.length,this.config=e,this.declarations=new Map,this.pattern=e.pattern.split(/\s/)}add(e){if(e.nam==this.config.shorthand)this.declarations.clear(),this.declarations.set(e.nam,e);else{const t=this.config.separator;if(e.nam!=this.config.shorthand&&this.declarations.has(this.config.shorthand)){const e={},r=[];this.declarations.get(this.config.shorthand).val.slice().reduce(((e,r)=>null!=t&&t.typ==r.typ&&w(t,r)?(e.push([]),e):(e.at(-1).push(r),e)),[[]]).reduce(((t,n,a)=>(r.push(...this.pattern.reduce(((t,r)=>{const n=this.config.properties[r];for(let i=0;ia)return t}else t.splice(i,1),i--;if(r in e&&e[r].length>a)return t;if(n.default.length>0){const t=S(n.default[0]);r in e?a==e[r].length?(e[r].push([]),e[r][a].push(...t)):e[r][a].push({typ:"Whitespace"},...t):e[r]=[[...t]]}return t}),n)),r)),[]),0==r.length&&(this.declarations=Object.entries(e).reduce(((e,r)=>(e.set(r[0],{typ:"Declaration",nam:r[0],val:r[1].reduce(((e,r)=>(e.length>0&&e.push({...t}),e.push(...r),e)),[])}),e)),new Map))}this.declarations.set(e.nam,e)}return this}[Symbol.iterator](){let e=Object.keys(this.config.properties).reduce(((e,t)=>this.declarations.has(t)&&this.config.properties[t].required?++e:e),0);if(0==e&&(e=this.declarations.size),e{if(!this.declarations.has(a[0]))return a[1].required&&e.push(a[0]),e;let i=0;const o=this.config.properties[a[0]];for(const t of this.declarations.get(a[0]).val)if(null!=r&&r.typ==t.typ&&w(r,t))i++,n[a[0]].length==i&&n[a[0]].push([]);else if(!("Whitespace"==t.typ||"Comment"==t.typ||o.multiple&&null!=o.separator&&o.separator.typ==t.typ&&w(t,o.separator))){if(!x(t,a[1])){e.push(a[0]);break}a[0]in n||(n[a[0]]=[[]]),n[a[0]][i].push(t)}return 0==t&&(t=i),e}),[]).length>0||Object.values(n).every((e=>e.every((e=>e.length==t)))))return this.declarations.values();const a=Object.entries(n).reduce(((e,t)=>{const r=this.config.properties[t[0]];for(let n=0;n(e.length>0&&e.push({typ:"Whitespace"}),e.push(t),e)),[]);if(!r.default.includes(t[1][n].reduce(((e,t)=>e+b(t)+" "),"").trimEnd())&&(a=a.filter((e=>"Whitespace"!=e.typ&&"Comment"!=e.typ&&!("Iden"==e.typ&&r.default.includes(e.val)))),a.length>0)){if("mapping"in r&&(!("constraints"in r)||!("max"in r.constraints)||a.length<=r.constraints.mapping.max)){let e=a.length;for(;e--;)"Iden"==a[e].typ&&a[e].val in r.mapping&&a.splice(e,1,...S(r.mapping[a[e].val]))}"prefix"in r?e[n].push({...r.prefix}):e[n].length>0&&e[n].push({typ:"Whitespace"}),e[n].push(...a.reduce(((e,t)=>(e.length>0&&e.push({...r.separator??{typ:"Whitespace"}}),e.push(t),e)),[]))}}return e}),[]).reduce(((e,t)=>(e.length>0&&e.push({...r}),0==t.length&&t.push(...this.config.default[0].split(/\s/).map(A).reduce(((e,t)=>(e.length>0&&e.push({typ:"Whitespace"}),e.push(t),e)),[])),e.push(...t),e)),[]);return[{typ:"Declaration",nam:this.config.shorthand,val:a}][Symbol.iterator]()}}const W=h;class P{declarations;constructor(){this.declarations=new Map}add(e){if("Declaration"!=e.typ)return this.declarations.set(Number(Math.random().toString().slice(2)).toString(36),e),this;const t=e.nam;if(t in W.properties){const r=W.properties[t].shorthand;return this.declarations.has(r)||this.declarations.set(r,new C(W.properties[r])),this.declarations.get(r).add(e),this}if(t in W.map){const r=W.map[t].shorthand;return this.declarations.has(r)||this.declarations.set(r,new L(W.map[r])),this.declarations.get(r).add(e),this}return this.declarations.set(t,e),this}[Symbol.iterator](){let e=this.declarations.values();const t=[];return{next(){let r=e.next();for(;r.done&&t.length>0||r.value instanceof C||r.value instanceof L;)(r.value instanceof C||r.value instanceof L)&&(t.unshift(e),e=r.value[Symbol.iterator](),r=e.next()),r.done&&t.length>0&&(e=t.shift(),r=e.next());return r}}}}function q(e,t){return e.chi.every((e=>"Comment"==e.typ||"Declaration"==e.typ&&t.chi.some((t=>w(t,e)))))}function $(e){if("chi"in e&&e.chi?.length>0){let t,r,n,a=0;for(;a0;)if("Comment"!=t.chi[o].typ){i="Declaration"==t.chi[o].typ;break}if(i){if("Rule"==r.typ&&r.sel==t.sel||"AtRule"==r.typ&&r.val==t.val){r.chi.unshift(...t.chi),e.chi.splice(n,1),a--,t=r,n=a;continue}"Rule"==r.typ&&q(r,t)&&q(t,r)&&("Rule"==r.typ&&(t.sel+=","+r.sel),e.chi.splice(a,1),a--)}}t!=r&&(t.chi.some((e=>"Declaration"==e.typ))?z(t):$(t))}t=r,n=a}else e.chi?.splice(a,1,...r.chi),a--;null!=r&&"chi"in r&&(r.chi.some((e=>"Declaration"==e.typ))?z(r):$(r))}return e}function z(e){if(!("chi"in e)||0==e.chi?.length)return e;const t=e.chi.length;let r=0;const n=new P;for(;r0&&("Pseudo-class-func"==a[n-1].typ||"End-parens"==a[n-1].typ||"UrlFunc"==a[n-1].typ||"Func"==a[n-1].typ||"Color"==a[n-1].typ&&"hex"!=a[n-1].kin&&"lit"!=a[n-1].kin))?e:e+b(t,r):e}))}},e.renderToken=b})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).CSSParser={})}(this,(function(e){"use strict";const t=92;function r(e){return"unit"in e&&["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"].includes(e.unit.toLowerCase())}function n(e){return 95==e||function(e){return e>=97&&e<=122||e>=65&&e<=90}(e)||function(e){return e>=128}(e)}function a(e){return e>=48&&e<=57}function i(e){return 45==e||a(e)||n(e)}function o(e){const r=e.length-1;let a=0,o=e.charCodeAt(0);if(45==o){const r=e.charCodeAt(1);return null!=r&&(45==r||(r!=t||e.length>2&&!c(e.charCodeAt(2))))}if(!n(o))return!1;for(;a=65&&e<=70||e>=97&&e<=102))return!1}return!0}function c(e){return 10==e||12==e||13==e}function d(e){return 9==e||32==e||c(e)}var h={properties:{inset:{shorthand:"inset",properties:["top","right","bottom","left"],types:["Length","Perc"],multiple:!1,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:!1,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:!0,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",properties:["border-top-width","border-right-width","border-bottom-width","border-left-width"],types:["Length","Perc"],keywords:["thin","medium","thick"]},"border-top-width":{shorthand:"border-width"},"border-right-width":{shorthand:"border-width"},"border-bottom-width":{shorthand:"border-width"},"border-left-width":{shorthand:"border-width"},"border-style":{shorthand:"border-style",properties:["border-top-style","border-right-style","border-bottom-style","border-left-style"],types:[],keywords:["none","hidden","dotted","dashed","solid","double","groove","ridge","inset","outset"]},"border-top-style":{shorthand:"border-style"},"border-right-style":{shorthand:"border-style"},"border-bottom-style":{shorthand:"border-style"},"border-left-style":{shorthand:"border-style"},"border-color":{shorthand:"border-color",properties:["border-top-color","border-right-color","border-bottom-color","border-left-color"],types:["Color"],keywords:[]},"border-top-color":{shorthand:"border-color"},"border-right-color":{shorthand:"border-color"},"border-bottom-color":{shorthand:"border-color"},"border-left-color":{shorthand:"border-color"}},map:{outline:{shorthand:"outline",pattern:"outline-color outline-style outline-width",keywords:["none"],default:["0","none"],properties:{"outline-color":{types:["Color"],default:["currentColor","invert"],keywords:["currentColor","invert"]},"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:!0},"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:!0,multiple:!0,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:!0,separator:{typ:"Comma"},properties:{"background-repeat":{types:[],default:["repeat"],multiple:!0,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"],keywords:[]},"background-image":{types:["UrlFunc"],default:["none"],keywords:["none"]},"background-attachment":{types:[],default:["scroll"],keywords:["scroll","fixed","local"]},"background-clip":{types:[],default:["border-box"],keywords:["border-box","padding-box","content-box","text"]},"background-origin":{types:[],default:["padding-box"],keywords:["border-box","padding-box","content-box"]},"background-position":{multiple:!0,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:!0,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"}}};const f=()=>h,u=Object.seal({aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32",rebeccapurple:"#663399",transparent:"#00000000"}),p=Object.seal({"#f0f8ff":"aliceblue","#faebd7":"antiquewhite","#7fffd4":"aquamarine","#f0ffff":"azure","#f5f5dc":"beige","#ffe4c4":"bisque","#000000":"black","#ffebcd":"blanchedalmond","#0000ff":"blue","#8a2be2":"blueviolet","#a52a2a":"brown","#deb887":"burlywood","#5f9ea0":"cadetblue","#7fff00":"chartreuse","#d2691e":"chocolate","#ff7f50":"coral","#6495ed":"cornflowerblue","#fff8dc":"cornsilk","#dc143c":"crimson","#00ffff":"cyan","#00008b":"darkblue","#008b8b":"darkcyan","#b8860b":"darkgoldenrod","#a9a9a9":"darkgrey","#006400":"darkgreen","#bdb76b":"darkkhaki","#8b008b":"darkmagenta","#556b2f":"darkolivegreen","#ff8c00":"darkorange","#9932cc":"darkorchid","#8b0000":"darkred","#e9967a":"darksalmon","#8fbc8f":"darkseagreen","#483d8b":"darkslateblue","#2f4f4f":"darkslategrey","#00ced1":"darkturquoise","#9400d3":"darkviolet","#ff1493":"deeppink","#00bfff":"deepskyblue","#696969":"dimgrey","#1e90ff":"dodgerblue","#b22222":"firebrick","#fffaf0":"floralwhite","#228b22":"forestgreen","#dcdcdc":"gainsboro","#f8f8ff":"ghostwhite","#ffd700":"gold","#daa520":"goldenrod","#808080":"grey","#008000":"green","#adff2f":"greenyellow","#f0fff0":"honeydew","#ff69b4":"hotpink","#cd5c5c":"indianred","#4b0082":"indigo","#fffff0":"ivory","#f0e68c":"khaki","#e6e6fa":"lavender","#fff0f5":"lavenderblush","#7cfc00":"lawngreen","#fffacd":"lemonchiffon","#add8e6":"lightblue","#f08080":"lightcoral","#e0ffff":"lightcyan","#fafad2":"lightgoldenrodyellow","#d3d3d3":"lightgrey","#90ee90":"lightgreen","#ffb6c1":"lightpink","#ffa07a":"lightsalmon","#20b2aa":"lightseagreen","#87cefa":"lightskyblue","#778899":"lightslategrey","#b0c4de":"lightsteelblue","#ffffe0":"lightyellow","#00ff00":"lime","#32cd32":"limegreen","#faf0e6":"linen","#ff00ff":"magenta","#800000":"maroon","#66cdaa":"mediumaquamarine","#0000cd":"mediumblue","#ba55d3":"mediumorchid","#9370d8":"mediumpurple","#3cb371":"mediumseagreen","#7b68ee":"mediumslateblue","#00fa9a":"mediumspringgreen","#48d1cc":"mediumturquoise","#c71585":"mediumvioletred","#191970":"midnightblue","#f5fffa":"mintcream","#ffe4e1":"mistyrose","#ffe4b5":"moccasin","#ffdead":"navajowhite","#000080":"navy","#fdf5e6":"oldlace","#808000":"olive","#6b8e23":"olivedrab","#ffa500":"orange","#ff4500":"orangered","#da70d6":"orchid","#eee8aa":"palegoldenrod","#98fb98":"palegreen","#afeeee":"paleturquoise","#d87093":"palevioletred","#ffefd5":"papayawhip","#ffdab9":"peachpuff","#cd853f":"peru","#ffc0cb":"pink","#dda0dd":"plum","#b0e0e6":"powderblue","#800080":"purple","#ff0000":"red","#bc8f8f":"rosybrown","#4169e1":"royalblue","#8b4513":"saddlebrown","#fa8072":"salmon","#f4a460":"sandybrown","#2e8b57":"seagreen","#fff5ee":"seashell","#a0522d":"sienna","#c0c0c0":"silver","#87ceeb":"skyblue","#6a5acd":"slateblue","#708090":"slategrey","#fffafa":"snow","#00ff7f":"springgreen","#4682b4":"steelblue","#d2b48c":"tan","#008080":"teal","#d8bfd8":"thistle","#ff6347":"tomato","#40e0d0":"turquoise","#ee82ee":"violet","#f5deb3":"wheat","#ffffff":"white","#f5f5f5":"whitesmoke","#ffff00":"yellow","#9acd32":"yellowgreen","#663399":"rebeccapurple","#00000000":"transparent"});function g(e){if("Dimension"==e.typ)switch(e.unit){case"deg":return e.val/360;case"rad":return e.val/(2*Math.PI);case"grad":return e.val/400;case"turn":return+e.val}return e.val/360}function m(e,t,r,n=null){let a=r<=.5?r*(1+t):r+t-r*t,i=r,o=r,l=r;if(a>0){let t=r+r-a,n=(a-t)/a;e*=6;let s=Math.floor(e),c=a*n*(e-s),d=t+c,h=a-c;switch(s){case 0:i=a,o=d,l=t;break;case 1:i=h,o=a,l=t;break;case 2:i=t,o=a,l=d;break;case 3:i=t,o=h,l=a;break;case 4:i=d,o=t,l=a;break;case 5:i=a,o=t,l=h}}const s=[Math.round(255*i),Math.round(255*o),Math.round(255*l)];return null!=n&&1!=n&&s.push(Math.round(255*n)),s}const y=[];function b(e,t={}){const r=Object.assign(t.compress?{indent:"",newLine:"",preserveLicense:!1,removeComments:!0,colorConvert:!0}:{indent:" ",newLine:"\n",compress:!1,preserveLicense:!1,removeComments:!1,colorConvert:!0},t);return{code:v(e,r,(function(e,t,n,a){return"Comment"!=t.typ||!r.removeComments||r.preserveLicense&&t.val.startsWith("/*!")?r.compress&&"Whitespace"==t.typ&&("Start-parens"==a[n+1]?.typ||n>0&&("Pseudo-class-func"==a[n-1].typ||"End-parens"==a[n-1].typ||"UrlFunc"==a[n-1].typ||"Func"==a[n-1].typ||"Color"==a[n-1].typ&&"hex"!=a[n-1].kin&&"lit"!=a[n-1].kin))?e:e+k(t,r):e}))}}function v(e,t,r,n=0){y.length{const a=v(n,t,r);return""===a?e:""===e?a:`${e}${t.newLine}${a}`}),"");case"AtRule":case"Rule":if("AtRule"==e.typ&&!("chi"in e))return`${a}@${e.nam} ${e.val};`;let o=e.chi.reduce(((e,a)=>{let o;return o="Comment"==a.typ?t.removeComments?"":a.val:"Declaration"==a.typ?`${a.nam}:${t.indent}${a.val.reduce(r,"").trimEnd()};`:"AtRule"!=a.typ||"chi"in a?v(a,t,r,n+1):`@${a.nam} ${a.val};`,""===e?o:""===o?e:""!==o?`${e}${t.newLine}${i}${o}`:void 0}),"");return o.endsWith(";")&&(o=o.slice(0,-1)),"AtRule"==e.typ?`@${e.nam}${e.val?" "+e.val+t.indent:""}{${t.newLine}`+(""===o?"":i+o+t.newLine)+a+"}":e.sel+`${t.indent}{${t.newLine}`+(""===o?"":i+o+t.newLine)+a+"}"}return""}function k(e,t={}){switch(e.typ){case"Color":if(t.compress||t.colorConvert){let t="hex"==e.kin?e.val.toLowerCase():"";"rgb"==e.val||"rgba"==e.val?t=function(e){let t,r="#";for(let n=0;n<6;n+=2)t=e.chi[n],r+=Math.round("Perc"==t.typ?255*t.val/100:t.val).toString(16).padStart(2,"0");return 7==e.chi.length&&(t=e.chi[6],("Number"==t.typ&&t.val<1||"Perc"==t.typ&&t.val<100)&&(r+=Math.round(255*("Perc"==t.typ?t.val/100:t.val)).toString(16).padStart(2,"0"))),r}(e):"hsl"==e.val||"hsla"==e.val?t=function(e){let t,r=g(e.chi[0]);t=e.chi[2];let n="Perc"==t.typ?t.val/100:t.val;t=e.chi[4];let a="Perc"==t.typ?t.val/100:t.val,i=null;return 7==e.chi?.length&&(t=e.chi[6],("Perc"==t.typ&&t.val<100||"Number"==t.typ&&t.val<1)&&(i="Perc"==t.typ?t.val/100:t.val)),`#${m(r,n,a,i).reduce(((e,t)=>e+t.toString(16).padStart(2,"0")),"")}`}(e):"hwb"==e.val?t=function(e){let t,r=g(e.chi[0]);t=e.chi[2];let n="Perc"==t.typ?t.val/100:t.val;t=e.chi[4];let a="Perc"==t.typ?t.val/100:t.val,i=null;7==e.chi?.length&&(t=e.chi[6],("Perc"==t.typ&&t.val<100||"Number"==t.typ&&t.val<1)&&(i="Perc"==t.typ?t.val/100:t.val));const o=m(r,1,.5,i);let l;for(let e=0;e<3;e++)l=o[e]/255,l*=1-n-a,l+=n,o[e]=Math.round(255*l);return`#${o.reduce(((e,t)=>e+t.toString(16).padStart(2,"0")),"")}`}(e):"device-cmyk"==e.val&&(t=function(e){let t=e.chi[0];const r="Perc"==t.typ?t.val/100:t.val;t=e.chi[2];const n="Perc"==t.typ?t.val/100:t.val;t=e.chi[4];const a="Perc"==t.typ?t.val/100:t.val;t=e.chi[6];const i="Perc"==t.typ?t.val/100:t.val,o=[Math.round(255*(1-Math.min(1,r*(1-i)+i))),Math.round(255*(1-Math.min(1,n*(1-i)+i))),Math.round(255*(1-Math.min(1,a*(1-i)+i)))];return e.chi.length>=9&&(t=e.chi[8],o.push(Math.round(255*("Perc"==t.typ?t.val/100:t.val)))),`#${o.reduce(((e,t)=>e+t.toString(16).padStart(2,"0")),"")}`}(e));const r=p[t];if(""!==t)return 7==t.length?t[1]==t[2]&&t[3]==t[4]&&t[5]==t[6]&&(t=`#${t[1]}${t[3]}${t[5]}`):9==t.length&&t[1]==t[2]&&t[3]==t[4]&&t[5]==t[6]&&t[7]==t[8]&&(t=`#${t[1]}${t[3]}${t[5]}${t[7]}`),null!=r&&r.length<=t.length?r:t}if("hex"==e.kin||"lit"==e.kin)return e.val;case"Func":case"UrlFunc":case"Pseudo-class-func":return(t.compress&&"Pseudo-class-func"==e.typ&&"::"==e.val.slice(0,2)?e.val.slice(1):e.val)+"("+e.chi.reduce(((e,r)=>!t.removeComments||"Comment"!=r.typ||t.preserveLicense&&r.val.startsWith("/*!")?e+k(r,t):e),"")+")";case"Includes":return"~=";case"Dash-match":return"|=";case"Lt":return"<";case"Gt":return">";case"Start-parens":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"["+e.chi.reduce(((e,r)=>e+k(r,t)),"")+"]";case"Time":case"Frequency":case"Angle":case"Length":case"Dimension":const r=(+e.val).toString();if("0"===r)return"Time"==e.typ?"0s":"Frequency"==e.typ?"0Hz":"Resolution"==e.typ?"0x":"0";const n=r.charAt(0);if("-"==n){if("-0"==r.slice(0,2))return(2==r.length?"0":"-"+r.slice(2))+e.unit}else if("0"==n)return r.slice(1)+e.unit;return r+e.unit;case"Perc":return e.val+"%";case"Number":const a=(+e.val).toString();if(e.val.length1)return a.slice(1);return"-0"==a.slice(0,2)?"-"+a.slice(2):a;case"Comment":if(t.removeComments)return"";case"Url-token":case"At-rule":case"Hash":case"Pseudo-class":case"Literal":case"String":case"Iden":case"Delim":return t.compress&&"Pseudo-class"==e.typ&&"::"==e.val.slice(0,2)?e.val.slice(1):e.val}throw new Error(`unexpected token ${JSON.stringify(e,null,1)}`)}function w(e,t){if("object"!=typeof e||"object"!=typeof t)return e===t;const r=Object.keys(e),n=Object.keys(t);return r.length==n.length&&r.every((r=>w(e[r],t[r])))}class C{config;declarations;constructor(e){this.config=e,this.declarations=new Map}add(e){if(e.nam==this.config.shorthand)this.declarations.clear(),this.declarations.set(e.nam,e);else{if(e.nam!=this.config.shorthand&&this.declarations.has(this.config.shorthand)){let t=!0,r=-1;const n=[];for(let e of this.declarations.get(this.config.shorthand).val)if(this.config.types.includes(e.typ)||"Number"==e.typ&&"0"==e.val&&(this.config.types.includes("Length")||this.config.types.includes("Angle")||this.config.types.includes("Dimension")))0==n.length&&(n.push([]),r++),n[r].push(e);else if("Whitespace"!=e.typ&&"Comment"!=e.typ){if("Iden"==e.typ&&this.config.keywords.includes(e.val)&&n[r].push(e),"Literal"==e.typ&&e.val==this.config.separator){n.push([]),r++;continue}t=!1;break}if(t&&n.length>0){this.declarations.delete(this.config.shorthand);for(const e of n)this.config.properties.forEach(((t,r)=>{for(this.declarations.has(t)||this.declarations.set(t,{typ:"Declaration",nam:t,val:[]});r>0&&r>=e.length;){if(!(r>1)){r=0;break}r%=2}const n=this.declarations.get(t).val;n.length>0&&n.push({typ:"Whitespace"}),n.push({...e[r]})}))}return this.declarations.set(e.nam,e),this}this.declarations.set(e.nam,e)}return this}[Symbol.iterator](){let e;const t=this.declarations;if(!(t.size!t.has(e)||r>0&&t.get(e).val.length!=t.get(this.config.properties[Math.floor(r/2)]).val.length)))){const t=[];this.config.properties.forEach((e=>{let r=0;for(const n of this.declarations.get(e).val)"Whitespace"!=n.typ&&(t.length==r&&t.push([]),t[r].push(n),r++)}));for(const e of t){let t=e.length;for(;t-- >1;){const n=e[t],a=e[1==t?0:t%2];if(n.val==a.val&&"0"==n.val&&("Number"==n.typ&&r(a)||"Number"==a.typ&&r(n)||r(a)||r(n)))e.splice(t,1);else{if(!w(n,a))break;e.splice(t,1)}}}return e=[{typ:"Declaration",nam:this.config.shorthand,val:t.reduce(((e,t)=>{if(t.length>1){const e=2*t.length-1;let r=1;for(;r0&&e.push({typ:"Literal",val:this.config.separator}),e.push(...t),e}),[])}][Symbol.iterator](),{next:()=>e.next()}}return e=t.values(),{next:()=>e.next()}}}function x(e,t){return!!("Iden"==e.typ&&t.keywords.includes(e.val)||t.types.includes(e.typ))||"Number"==e.typ&&"0"==e.val&&t.types.some((e=>"Length"==e||"Angle"==e))}function A(e){return"transparent"==e||"currentcolor"==e?{typ:"Color",val:e,kin:"lit"}:e.endsWith("%")?{typ:"Perc",val:e.slice(0,-1)}:{typ:l(e)?"Number":"Iden",val:e}}function S(e){return e.split(/\s/).map(A).reduce(((e,t)=>(e.length>0&&e.push({typ:"Whitespace"}),e.push(t),e)),[])}class P{config;declarations;requiredCount;pattern;constructor(e){const t=Object.values(e.properties);this.requiredCount=t.reduce(((e,t)=>t.required?++e:e),0)||t.length,this.config=e,this.declarations=new Map,this.pattern=e.pattern.split(/\s/)}add(e){if(e.nam==this.config.shorthand)this.declarations.clear(),this.declarations.set(e.nam,e);else{const t=this.config.separator;if(e.nam!=this.config.shorthand&&this.declarations.has(this.config.shorthand)){const e={},r=[];this.declarations.get(this.config.shorthand).val.slice().reduce(((e,r)=>null!=t&&t.typ==r.typ&&w(t,r)?(e.push([]),e):(e.at(-1).push(r),e)),[[]]).reduce(((t,n,a)=>(r.push(...this.pattern.reduce(((t,r)=>{const n=this.config.properties[r];for(let i=0;ia)return t}else t.splice(i,1),i--;if(r in e&&e[r].length>a)return t;if(n.default.length>0){const t=S(n.default[0]);r in e?a==e[r].length?(e[r].push([]),e[r][a].push(...t)):e[r][a].push({typ:"Whitespace"},...t):e[r]=[[...t]]}return t}),n)),r)),[]),0==r.length&&(this.declarations=Object.entries(e).reduce(((e,r)=>(e.set(r[0],{typ:"Declaration",nam:r[0],val:r[1].reduce(((e,r)=>(e.length>0&&e.push({...t}),e.push(...r),e)),[])}),e)),new Map))}this.declarations.set(e.nam,e)}return this}[Symbol.iterator](){let e=Object.keys(this.config.properties).reduce(((e,t)=>this.declarations.has(t)&&this.config.properties[t].required?++e:e),0);if(0==e&&(e=this.declarations.size),e{if(!this.declarations.has(a[0]))return a[1].required&&e.push(a[0]),e;let i=0;const o=this.config.properties[a[0]];for(const t of this.declarations.get(a[0]).val)if(null!=r&&r.typ==t.typ&&w(r,t))i++,n[a[0]].length==i&&n[a[0]].push([]);else if(!("Whitespace"==t.typ||"Comment"==t.typ||o.multiple&&null!=o.separator&&o.separator.typ==t.typ&&w(t,o.separator))){if(!x(t,a[1])){e.push(a[0]);break}a[0]in n||(n[a[0]]=[[]]),n[a[0]][i].push(t)}return 0==t&&(t=i),e}),[]).length>0||Object.values(n).every((e=>e.every((e=>e.length==t)))))return this.declarations.values();const a=Object.entries(n).reduce(((e,t)=>{const r=this.config.properties[t[0]];for(let n=0;n(e.length>0&&e.push({typ:"Whitespace"}),e.push(t),e)),[]);if(!r.default.includes(t[1][n].reduce(((e,t)=>e+k(t)+" "),"").trimEnd())&&(a=a.filter((e=>"Whitespace"!=e.typ&&"Comment"!=e.typ&&!("Iden"==e.typ&&r.default.includes(e.val)))),a.length>0)){if("mapping"in r&&(!("constraints"in r)||!("max"in r.constraints)||a.length<=r.constraints.mapping.max)){let e=a.length;for(;e--;)"Iden"==a[e].typ&&a[e].val in r.mapping&&a.splice(e,1,...S(r.mapping[a[e].val]))}"prefix"in r?e[n].push({...r.prefix}):e[n].length>0&&e[n].push({typ:"Whitespace"}),e[n].push(...a.reduce(((e,t)=>(e.length>0&&e.push({...r.separator??{typ:"Whitespace"}}),e.push(t),e)),[]))}}return e}),[]).reduce(((e,t)=>(e.length>0&&e.push({...r}),0==t.length&&t.push(...this.config.default[0].split(/\s/).map(A).reduce(((e,t)=>(e.length>0&&e.push({typ:"Whitespace"}),e.push(t),e)),[])),e.push(...t),e)),[]);return[{typ:"Declaration",nam:this.config.shorthand,val:a}][Symbol.iterator]()}}const L=f();class W{declarations;constructor(){this.declarations=new Map}add(e){if("Declaration"!=e.typ)return this.declarations.set(Number(Math.random().toString().slice(2)).toString(36),e),this;const t=e.nam;if(t in L.properties){const r=L.properties[t].shorthand;return this.declarations.has(r)||this.declarations.set(r,new C(L.properties[r])),this.declarations.get(r).add(e),this}if(t in L.map){const r=L.map[t].shorthand;return this.declarations.has(r)||this.declarations.set(r,new P(L.map[r])),this.declarations.get(r).add(e),this}return this.declarations.set(t,e),this}[Symbol.iterator](){let e=this.declarations.values();const t=[];return{next(){let r=e.next();for(;r.done&&t.length>0||r.value instanceof C||r.value instanceof P;)(r.value instanceof C||r.value instanceof P)&&(t.unshift(e),e=r.value[Symbol.iterator](),r=e.next()),r.done&&t.length>0&&(e=t.shift(),r=e.next());return r}}}}const $=f();function q(e,t={},r=!1){if("chi"in e&&e.chi?.length>0){let n,a,i,o=0;for(;o0;)if("Comment"!=n.chi[s].typ){l="Declaration"==n.chi[s].typ;break}if(l){if("Rule"==a.typ&&a.sel==n.sel||"AtRule"==a.typ&&a.val==n.val){a.chi.unshift(...n.chi),e.chi.splice(i,1),M(a)?j(a):q(a,t,r),o--,n=a,i=o;continue}if("Rule"==a.typ&&"Rule"==n?.typ){const r=F(n,a,t);null!=r&&(0==r.node1.chi.length?e.chi.splice(o,1):e.chi.splice(o,1,r.node1),0==r.node2.chi.length?e.chi.splice(i,1,r.result):e.chi.splice(i,1,r.result,r.node2))}}}r&&n!=a&&(M(n)?j(n):q(n,t,r))}n=a,i=o}else e.chi?.splice(o,1,...a.chi),o--;r&&null!=a&&"chi"in a&&(a.chi.some((e=>"Declaration"==e.typ))?j(a):q(a,t,r))}return e}function M(e){for(let t=0;ta.chi.length){const e=n;n=a,a=e,i=!0}let o=n.chi.length,l=a.chi.length;if(0==o||0==l)return null;n={...n,chi:n.chi.slice()},a={...a,chi:a.chi.slice()};const s=[];for(;o--;)if("Comment"!=n.chi[o].typ){if(l=a.chi.length,0==l)break;for(;l--;)if("Comment"!=a.chi[l].typ&&n.chi[o].nam==a.chi[l].nam&&w(n.chi[o],a.chi[l])){s.push(n.chi[o]),n.chi.splice(o,1),a.chi.splice(l,1);break}}const c=0==s.length?null:{...n,sel:[...new Set([...(e.raw||z(e.sel)).concat(t.raw||z(t.sel))])].join(),chi:s.reverse()};return null==c||[e,t].reduce(((e,t)=>0==t.chi.length?e:e+b(t,r).code.length),0)<=[n,a,c].reduce(((e,t)=>0==t.chi.length?e:e+b(t,r).code.length),0)?null:{result:c,node1:i?a:n,node2:a}}const D=["Start-parens","Func","UrlFunc","Pseudo-class-func"];function I(e,t={}){const i=[],s={src:"",location:!1,compress:!1,processImport:!1,removeEmpty:!0,...t};if(0==e.length)return null;let h=-1,f=1,u=0;const p=[],g=s.src,m=[],y={typ:"StyleSheet",chi:[]},b={ind:Math.max(h,0),lin:f,col:Math.max(u,1)};let v,w="",C=e.length,x=new Map,A=y;function S(e){if(""===e)throw new Error("empty string?");return":"==e?{typ:"Colon"}:")"==e?{typ:"End-parens"}:"("==e?{typ:"Start-parens"}:"="==e?{typ:"Delim",val:e}:";"==e?{typ:"Semi-colon"}:","==e?{typ:"Comma"}:"<"==e?{typ:"Lt"}:">"==e?{typ:"Gt"}:":"==(t=e).charAt(0)&&(t.endsWith("(")?o(":"==t.charAt(1)?t.slice(2,-1):t.slice(1,-1)):o(":"==t.charAt(1)?t.slice(2):t.slice(1)))?e.endsWith("(")?{typ:"Pseudo-class-func",val:e.slice(0,-1),chi:[]}:{typ:"Pseudo-class",val:e}:function(e){return 64==e.charCodeAt(0)&&o(e.slice(1))}(e)?{typ:"At-rule",val:e.slice(1)}:function(e){return e.endsWith("(")&&o(e.slice(0,-1))}(e)?{typ:"url"==(e=e.slice(0,-1))?"UrlFunc":"Func",val:e,chi:[]}:l(e)?{typ:"Number",val:e}:function(e){let t=0;for(;t++3)return!1;const r=e.slice(0,-t);return r.length>0&&n(e.charCodeAt(e.length-t))&&l(r)}(e)?function(e){let t=0;for(;t++0)return i.push({action:"drop",message:"invalid @charset",location:{src:g,...a}}),null;for(;["Whitespace"].includes(e[0]?.typ);)e.shift();if("import"==r.val){if(A.chi.length>0){let e=A.chi.length;for(;e--;){const t=A.chi[e].typ;if("Comment"==t)continue;if("AtRule"!=t)return i.push({action:"drop",message:"invalid @import",location:{src:g,...a}}),null;const r=A.chi[e].nam;if("charset"!=r&&"import"!=r&&"layer"!=r)return i.push({action:"drop",message:"invalid @import",location:{src:g,...a}}),null;break}}if("String"!=e[0]?.typ&&"UrlFunc"!=e[0]?.typ)return i.push({action:"drop",message:"invalid @import",location:{src:g,...a}}),null;if("UrlFunc"==e[0].typ&&"Url-token"!=e[1]?.typ&&"String"!=e[1]?.typ)return i.push({action:"drop",message:"invalid @import",location:{src:g,...a}}),null}"import"==r.val&&"UrlFunc"==e[0].typ&&"Url-token"==e[1].typ&&(e.shift(),e[0].typ="String",e[0].val=`"${e[0].val}"`);const o={typ:"AtRule",nam:k(r,{removeComments:!0}),val:e.reduce(((e,t,r,n)=>"Whitespace"!=t.typ||"Start-parens"!=n[r+1]?.typ&&"End-parens"!=n[r-1]?.typ&&"Func"!=n[r-1]?.typ?e+k(t,{removeComments:!0}):e),"")};if("import"==o.nam&&s.processImport){let t=e["UrlFunc"==e[0].typ?1:0];("String"==t.typ?t.val.slice(1,-1):t.val).startsWith("data:")}return"Block-start"==n.typ&&(o.chi=[]),t={sta:a,src:g},s.location&&(o.loc=t),A.chi.push(o),"Block-start"==n.typ?o:null}if("Block-start"==n.typ){const r=x.get(e[0]);if("Rule"==A.typ&&"Iden"==e[0]?.typ)return i.push({action:"drop",message:"invalid nesting rule",location:{src:g,...r}}),null;const n=O(e,{compress:s.compress}).map((e=>k(e,{compress:!0}))),a=[...new Set(n.reduce(((e,t)=>(","==t?e.push(""):e[e.length-1]+=t,e)),[""]))],o={typ:"Rule",sel:a.join(","),chi:[]};return Object.defineProperty(o,"raw",{enumerable:!1,get:()=>a}),t={sta:r,src:g},s.location&&(o.loc=t),A.chi.push(o),o}{let r=null,n=null;for(let t=0;t0)for(let e=1;e0&&h0&&(F(S(w)),w=""),w+=e;h=C){F({typ:n?"Bad-string":"Unclosed-string",val:w});break}if("\\"!=r){if(r==t){w+=r,F({typ:n?"Bad-string":"String",val:w}),z(),w="";break}if(c(r.charCodeAt(0))&&(n=!0),n&&";"==r){F({typ:"Bad-string",val:w}),w="";break}w+=r,z()}else{if(h>=C){F(S(w));break}w+=z(2)}}}for(s.location&&(y.loc={sta:{ind:h,lin:f,col:u},src:""});h=C){w.length>0&&(F(S(w)),w="");break}if(d(v.charCodeAt(0))){for(w.length>0&&(F(S(w)),w="");h=C))&&d(v.charCodeAt(0)););if(F({typ:"Whitespace"}),w="",h>=C)break}switch(v){case"/":if(w.length>0&&"Whitespace"==p.at(-1)?.typ&&(F(S(w)),w="","*"!=W())){F(S(v));break}if(w+=v,"*"==W())for(w+="*",z();h=C){F({typ:"Bad-comment",val:w});break}if("\\"!=v)if("*"==v){if(w+=v,v=z(),h>=C){F({typ:"Bad-comment",val:w});break}if(w+=v,"/"==v){F({typ:"Comment",val:w}),w="";break}}else w+=v;else{if(w+=v,v=z(),h>=C){F({typ:"Bad-comment",val:w});break}w+=v}}break;case"<":if(w.length>0&&(F(S(w)),w=""),w+=v,v=z(),h>=C)break;if("!--"==W(3))for(;h=C));)if(w+=v,">"==v&&"--"==$(2)){F({typ:"CDOCOMM",val:w}),w="";break}h>=C&&(F({typ:"BADCDO",val:w}),w="");break;case"\\":if(v=z(),h+1>=C){F(S(w)),w="";break}w+=v;break;case'"':case"'":I(v);break;case"~":case"|":if(w.length>0&&(F(S(w)),w=""),w+=v,v=z(),h>=C){F(S(w)),w="";break}if("="==v){w+=v,F({typ:"~"==w[0]?"Includes":"Dash-matches",val:w}),w="";break}F(S(w)),w=v;break;case">":"Whitespace"==p[p.length-1]?.typ&&p.pop(),F({typ:"Gt"}),D();break;case":":case",":case"=":if(w.length>0&&(F(S(w)),w=""),":"==v&&":"==W()){w+=v+z();break}for(F(S(v)),w="";d(W().charCodeAt(0));)z();break;case")":w.length>0&&(F(S(w)),w=""),F({typ:"End-parens"});break;case"(":if(0==w.length)F({typ:"Start-parens"});else{w+=v,F(S(w)),w="";if("UrlFunc"==p[p.length-1].typ){let e="";for(v=W();d(v.charCodeAt(0));)e+=v;if(e.length>0&&z(e.length),v=W(),'"'==v||"'"==v){I(z());let e=p[p.length-1];["String","Literal"].includes(e.typ)&&/^(["']?)[a-zA-Z0-9_/-][a-zA-Z0-9_/:.-]+(\1)$/.test(e.val)&&"String"==e.typ&&(e.val=e.val.slice(1,-1));break}for(w="";;){let t=v.charCodeAt(0);if(null==t){F({typ:"Bad-url-token",val:w});break}if(41==t||null==t){0==w.length?F({typ:"Bad-url-token",val:""}):F({typ:"Url-token",val:w}),null!=t&&F(S(z()));break}if(d(t)){for(e=z();v=W(),t=v.charCodeAt(0),d(t);)e+=v;if(null==t||41==t)continue;for(w+=z(e.length);;){if(v=W(),t=v.charCodeAt(0),null==t||41==t)break;w+=z()}F({typ:"Bad-url-token",val:w})}else w+=z(),v=W()}w=""}}break;case"[":case"]":case"{":case"}":case";":w.length>0&&(F(S(w)),w=""),F(E(v));let e=null;if("{"==v||";"==v)e=L(p),null!=e?(m.push(e),A=e):"{"==v&&P("{","}"),p.length=0,x.clear();else if("}"==v){L(p);const e=m.pop();A=m[m.length-1]||y,s.removeEmpty&&null!=e&&0==e.chi.length&&A.chi[A.chi.length-1]==e?A.chi.pop():null!=e&&e!=y&&s.compress&&(M(e)?j(e):q(e,s)),p.length=0,x.clear(),w=""}break;case"!":w.length>0&&(F(S(w)),w="");if("important"==W(9)){"Whitespace"==p[p.length-1]?.typ&&p.pop(),F({typ:"Important"}),z(9),w="";break}w="!";break;default:w+=v}}if(w.length>0&&F(S(w)),s.compress){for(;m.length>0;){const e=m.pop();M(e)?j(e,s):q(e,s)}y.chi.length>0&&q(y,s)}return{ast:y,errors:i}}function O(e,t={}){for(let r=0;r0&&D.includes(e[r-1].typ)))e.splice(r--,1);else{if("Colon"==a.typ){const t=e[r+1]?.typ;if(null!=t&&("Func"==t?(e[r+1].val=":"+e[r+1].val,e[r+1].typ="Pseudo-class-func"):"Iden"==t&&(e[r+1].val=":"+e[r+1].val,e[r+1].typ="Pseudo-class"),"Func"==t||"Iden"==t)){e.splice(r,1),r--;continue}}if("Attr-start"!=a.typ){if(D.includes(a.typ)){let n=1,i=r;for(;++i0;)"Literal"==a.chi[t].typ&&("Whitespace"==a.chi[t+1]?.typ&&a.chi.splice(t+1,1),"Whitespace"==a.chi[t-1]?.typ&&(a.chi.splice(t-1,1),t--))}else if("UrlFunc"==a.typ&&"String"==a.chi[0]?.typ){const e=a.chi[0].val.slice(1,-1);/^[/%.a-zA-Z0-9_-]+$/.test(e)&&(a.chi[0].typ="Url-token",a.chi[0].val=e)}if(a.chi.length>0&&(O(a.chi,t),"Pseudo-class-func"==a.typ&&":is"==a.val&&t.compress)){1!=a.chi.filter((e=>"Comment"!=e.typ)).length&&(0!=r||"Comma"!=e[r+1]?.typ&&e.length!=r+1)&&("Comma"!=e[r-1]?.typ||"Comma"!=e[r+1]?.typ&&e.length!=r+1)||(e.splice(r,1,...a.chi),r=Math.max(0,r-a.chi.length))}}else if(t.parseColor){if("Iden"==a.typ){const e=a.val.toLowerCase();null!=u[e]&&Object.assign(a,{typ:"Color",val:u[e].length1&&O(a.chi,t),a.chi.forEach((e=>{if("String"==e.typ){const t=e.val.slice(1,-1);("-"!=t.charAt(0)||"-"==t.charAt(0)&&n(t.charCodeAt(1)))&&o(t)&&Object.assign(e,{typ:"Iden",val:t})}})))}}}return e}function E(e){if(";"==e)return{typ:"Semi-colon"};if("{"==e)return{typ:"Block-start"};if("}"==e)return{typ:"Block-end"};if("["==e)return{typ:"Attr-start"};if("]"==e)return{typ:"Attr-end"};throw new Error(`unhandled token: '${e}'`)}e.deduplicate=q,e.deduplicateRule=j,e.hasDeclaration=M,e.parse=I,e.render=b,e.renderToken=k,e.transform=function(e,t={}){t={compress:!0,removeEmpty:!0,...t};const r=performance.now(),n=I(e,t);if(null==n)return null;const a=performance.now(),i=b(n.ast,t),o=performance.now();return{...n,...i,performance:{bytesIn:e.length,bytesOut:i.code.length,parse:`${(a-r).toFixed(2)}ms`,render:`${(o-a).toFixed(2)}ms`,total:`${(o-r).toFixed(2)}ms`}}}})); diff --git a/dist/index.d.ts b/dist/index.d.ts index 888b021..7823827 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -141,6 +141,7 @@ interface PseudoClassToken { interface PseudoClassFunctionToken { typ: 'Pseudo-class-func'; val: string; + chi: Token[]; } interface DelimToken { typ: 'Delim'; @@ -166,7 +167,11 @@ interface ColorToken { kin: 'lit' | 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hwb' | 'device-cmyk'; chi?: Token[]; } -declare type Token = LiteralToken | IdentToken | CommaToken | ColonToken | SemiColonToken | NumberToken | AtRuleToken | PercentageToken | FunctionURLToken | FunctionToken | DimensionToken | LengthToken | AngleToken | StringToken | TimeToken | FrequencyToken | ResolutionToken | UnclosedStringToken | HashToken | BadStringToken | BlockStartToken | BlockEndToken | AttrStartToken | AttrEndToken | ParensStartToken | ParensEndToken | CDOCommentToken | BadCDOCommentToken | CommentToken | BadCommentToken | WhitespaceToken | IncludesToken | DashMatchToken | LessThanToken | GreaterThanToken | PseudoClassToken | PseudoClassFunctionToken | DelimToken | BadUrlToken | UrlToken | ImportantToken | ColorToken | EOFToken; +interface AttrToken { + typ: 'Attr'; + chi: Token[]; +} +declare type Token = LiteralToken | IdentToken | CommaToken | ColonToken | SemiColonToken | NumberToken | AtRuleToken | PercentageToken | FunctionURLToken | FunctionToken | DimensionToken | LengthToken | AngleToken | StringToken | TimeToken | FrequencyToken | ResolutionToken | UnclosedStringToken | HashToken | BadStringToken | BlockStartToken | BlockEndToken | AttrStartToken | AttrEndToken | ParensStartToken | ParensEndToken | CDOCommentToken | BadCDOCommentToken | CommentToken | BadCommentToken | WhitespaceToken | IncludesToken | DashMatchToken | LessThanToken | GreaterThanToken | PseudoClassToken | PseudoClassFunctionToken | DelimToken | BadUrlToken | UrlToken | ImportantToken | ColorToken | AttrToken | EOFToken; interface ErrorDescription { @@ -185,8 +190,8 @@ interface ParserOptions { src?: string; location?: boolean; + compress?: boolean; processImport?: boolean; - deduplicate?: boolean; removeEmpty?: boolean; nodeEventFilter?: NodeType[] } @@ -201,6 +206,30 @@ interface RenderOptions { colorConvert?: boolean; } +interface TransformOptions extends ParserOptions, RenderOptions { + +} + +interface ParseResult { + ast: AstRuleStyleSheet; + errors: ErrorDescription[] +} + +interface RenderResult { + code: string ; +} + +interface TransformResult extends ParseResult, RenderResult { + + performance: { + bytesIn: number; + bytesOut: number; + parse: string; + // deduplicate: string; + render: string; + total: string; + } +} interface Position { ind: number; @@ -267,16 +296,15 @@ type AstNode = | AstRule | AstDeclaration; -declare function parse(css: string, opt?: ParserOptions): { - ast: AstRuleStyleSheet; - errors: ErrorDescription[]; -}; -declare function deduplicate(ast: AstNode): AstNode; -declare function deduplicateRule(ast: AstNode): AstNode; - -declare function render(data: AstNode, opt?: RenderOptions): { - code: string; -}; +declare function parse(iterator: string, opt?: ParserOptions): ParseResult; + +declare function deduplicate(ast: AstNode, options?: ParserOptions, recursive?: boolean): AstNode; +declare function hasDeclaration(node: AstAtRule | AstRule | AstRuleList): boolean; +declare function deduplicateRule(ast: AstNode, options?: ParserOptions): AstNode; + +declare function render(data: AstNode, opt?: RenderOptions): RenderResult; declare function renderToken(token: Token, options?: RenderOptions): string; -export { deduplicate, deduplicateRule, parse, render, renderToken }; +declare function transform(css: string, options?: TransformOptions): TransformResult; + +export { deduplicate, deduplicateRule, hasDeclaration, parse, render, renderToken, transform }; diff --git a/dist/index.js b/dist/index.js index 5879873..f5c6736 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,2 +1,4 @@ -export { deduplicate, deduplicateRule, parse } from './parser/parse.js'; +export { parse } from './parser/parse.js'; +export { deduplicate, deduplicateRule, hasDeclaration } from './parser/deduplicate.js'; export { render, renderToken } from './renderer/renderer.js'; +export { transform } from './transform.js'; diff --git a/dist/parser/declaration/list.js b/dist/parser/declaration/list.js index 6b49d1a..ddaa76a 100644 --- a/dist/parser/declaration/list.js +++ b/dist/parser/declaration/list.js @@ -15,16 +15,20 @@ class PropertyList { } const propertyName = declaration.nam; if (propertyName in config.properties) { + // @ts-ignore const shorthand = config.properties[propertyName].shorthand; if (!this.declarations.has(shorthand)) { + // @ts-ignore this.declarations.set(shorthand, new PropertySet(config.properties[shorthand])); } this.declarations.get(shorthand).add(declaration); return this; } if (propertyName in config.map) { + // @ts-ignore const shorthand = config.map[propertyName].shorthand; if (!this.declarations.has(shorthand)) { + // @ts-ignore this.declarations.set(shorthand, new PropertyMap(config.map[shorthand])); } this.declarations.get(shorthand).add(declaration); diff --git a/dist/parser/declaration/map.js b/dist/parser/declaration/map.js index 0ae04c8..10d5f41 100644 --- a/dist/parser/declaration/map.js +++ b/dist/parser/declaration/map.js @@ -54,16 +54,20 @@ class PropertyMap { if (declaration.nam != this.config.shorthand && this.declarations.has(this.config.shorthand)) { const tokens = {}; const values = []; + // @ts-ignore this.declarations.get(this.config.shorthand).val.slice().reduce((acc, curr) => { if (separator != null && separator.typ == curr.typ && eq(separator, curr)) { acc.push([]); return acc; } // else { + // @ts-ignore acc.at(-1).push(curr); // } return acc; - }, [[]]).reduce((acc, list, current) => { + }, [[]]). + // @ts-ignore + reduce((acc, list, current) => { values.push(...this.pattern.reduce((acc, property) => { // let current: number = 0; const props = this.config.properties[property]; @@ -163,6 +167,10 @@ class PropertyMap { requiredCount = this.declarations.size; } if (requiredCount < this.requiredCount) { + // if (this.declarations.size == 1 && this.declarations.has(this.config.shorthand)) { + // + // this.declarations + // } return this.declarations.values(); } let count = 0; @@ -236,16 +244,20 @@ class PropertyMap { }); if (values.length > 0) { if ('mapping' in props) { + // @ts-ignore if (!('constraints' in props) || !('max' in props.constraints) || values.length <= props.constraints.mapping.max) { let i = values.length; while (i--) { + // @ts-ignore if (values[i].typ == 'Iden' && values[i].val in props.mapping) { + // @ts-ignore values.splice(i, 1, ...parseString(props.mapping[values[i].val])); } } } } if ('prefix' in props) { + // @ts-ignore acc[i].push({ ...props.prefix }); } else if (acc[i].length > 0) { @@ -253,8 +265,10 @@ class PropertyMap { } acc[i].push(...values.reduce((acc, curr) => { if (acc.length > 0) { + // @ts-ignore acc.push({ ...(props.separator ?? { typ: 'Whitespace' }) }); } + // @ts-ignore acc.push(curr); return acc; }, [])); diff --git a/dist/parser/declaration/set.js b/dist/parser/declaration/set.js index b5d168c..9d96d8d 100644 --- a/dist/parser/declaration/set.js +++ b/dist/parser/declaration/set.js @@ -81,12 +81,12 @@ class PropertySet { this.declarations.set(declaration.nam, declaration); return this; } - // declaration.val = declaration.val.reduce((acc: Token[], token: Token) => { + // declaration.chi = declaration.chi.reduce((acc: Token[], token: Token) => { // - // if (this.config.types.includes(token.typ) || ('0' == (token).val && ( + // if (this.config.types.includes(token.typ) || ('0' == (token).chi && ( // this.config.types.includes('Length') || // this.config.types.includes('Angle') || - // this.config.types.includes('Dimension'))) || (token.typ == 'Iden' && this.config.keywords.includes(token.val))) { + // this.config.types.includes('Dimension'))) || (token.typ == 'Iden' && this.config.keywords.includes(token.chi))) { // // acc.push(token); // } diff --git a/dist/parser/parse.js b/dist/parser/parse.js index cb76e6d..a6306e1 100644 --- a/dist/parser/parse.js +++ b/dist/parser/parse.js @@ -1,155 +1,1103 @@ -import { tokenize } from './tokenize.js'; -import { PropertyList } from './declaration/list.js'; -import { eq } from './utils/eq.js'; -import '../renderer/utils/color.js'; +import { isWhiteSpace, isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHash, isNewLine, isIdentStart, isHexColor } from './utils/syntax.js'; +import { renderToken } from '../renderer/renderer.js'; +import { COLORS_NAMES } from '../renderer/utils/color.js'; +import { hasDeclaration, deduplicateRule, deduplicate } from './deduplicate.js'; -function parse(css, opt = {}) { +const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func']; +function parse(iterator, opt = {}) { const errors = []; const options = { src: '', location: false, + compress: false, processImport: false, - deduplicate: false, removeEmpty: true, ...opt }; - if (css.length == 0) { + if (iterator.length == 0) { // @ts-ignore return null; } - // @ts-ignore - const ast = tokenize(css, errors, options); - if (options.deduplicate) { - deduplicate(ast); + let ind = -1; + let lin = 1; + let col = 0; + const tokens = []; + const src = options.src; + const stack = []; + const ast = { + typ: "StyleSheet", + chi: [] + }; + const position = { + ind: Math.max(ind, 0), + lin: lin, + col: Math.max(col, 1) + }; + let value; + let buffer = ''; + let total = iterator.length; + let map = new Map; + let context = ast; + if (options.location) { + ast.loc = { + sta: { + ind: ind, + lin: lin, + col: col + }, + src: '' + }; } - return { ast, errors }; -} -function diff(node1, node2) { - // @ts-ignore - return node1.chi.every((val) => { - if (val.typ == 'Comment') { - return true; + function getType(val) { + if (val === '') { + throw new Error('empty string?'); } - if (val.typ != 'Declaration') { - return false; + if (val == ':') { + return { typ: 'Colon' }; } - return node2.chi.some(v => eq(v, val)); - }); -} -function deduplicate(ast) { - // @ts-ignore - if (('chi' in ast) && ast.chi?.length > 0) { - let i = 0; - let previous; - let node; - let nodeIndex; - // @ts-ignore - for (; i < ast.chi.length; i++) { - // @ts-ignore - if (ast.chi[i].typ == 'Comment') { + if (val == ')') { + return { typ: 'End-parens' }; + } + if (val == '(') { + return { typ: 'Start-parens' }; + } + if (val == '=') { + return { typ: 'Delim', val }; + } + if (val == ';') { + return { typ: 'Semi-colon' }; + } + if (val == ',') { + return { typ: 'Comma' }; + } + if (val == '<') { + return { typ: 'Lt' }; + } + if (val == '>') { + return { typ: 'Gt' }; + } + if (isPseudo(val)) { + return val.endsWith('(') ? { + typ: 'Pseudo-class-func', + val: val.slice(0, -1), + chi: [] + } + : { + typ: 'Pseudo-class', + val + }; + } + if (isAtKeyword(val)) { + return { + typ: 'At-rule', + val: val.slice(1) + }; + } + if (isFunction(val)) { + val = val.slice(0, -1); + return { + typ: val == 'url' ? 'UrlFunc' : 'Func', + val, + chi: [] + }; + } + if (isNumber(val)) { + return { + typ: 'Number', + val + }; + } + if (isDimension(val)) { + return parseDimension(val); + } + if (isPercentage(val)) { + return { + typ: 'Perc', + val: val.slice(0, -1) + }; + } + if (val == 'currentColor') { + return { + typ: 'Color', + val, + kin: 'lit' + }; + } + if (isIdent(val)) { + return { + typ: 'Iden', + val + }; + } + if (val.charAt(0) == '#' && isHash(val)) { + return { + typ: 'Hash', + val + }; + } + if ('"\''.includes(val.charAt(0))) { + return { + typ: 'Unclosed-string', + val + }; + } + return { + typ: 'Literal', + val + }; + } + // consume and throw away + function consume(open, close) { + let count = 1; + let chr; + while (true) { + chr = next(); + if (chr == '\\') { + if (next() === '') { + break; + } continue; } - // @ts-ignore - node = ast.chi[i]; - if (node.typ == 'AtRule' && node.val == 'all') { + else if (chr == '/' && peek() == '*') { + next(); + while (true) { + chr = next(); + if (chr === '') { + break; + } + if (chr == '*' && peek() == '/') { + next(); + break; + } + } + } + else if (chr == close) { + count--; + } + else if (chr == open) { + count++; + } + if (chr === '' || count == 0) { + break; + } + } + } + function parseNode(tokens) { + let i = 0; + let loc; + for (i = 0; i < tokens.length; i++) { + if (tokens[i].typ == 'Comment') { // @ts-ignore - ast.chi?.splice(i, 1, ...node.chi); - i--; - continue; + context.chi.push(tokens[i]); + const position = map.get(tokens[i]); + loc = { + sta: position, + src + }; + if (options.location) { + tokens[i].loc = loc; + } } - // @ts-ignore - if (previous != null && 'chi' in previous && ('chi' in node)) { + else if (tokens[i].typ != 'Whitespace') { + break; + } + } + tokens = tokens.slice(i); + const delim = tokens.pop(); + while (['Whitespace', 'Bad-string', 'Bad-comment'].includes(tokens[tokens.length - 1]?.typ)) { + tokens.pop(); + } + if (tokens.length == 0) { + return null; + } + if (tokens[0]?.typ == 'At-rule') { + const atRule = tokens.shift(); + const position = map.get(atRule); + if (atRule.val == 'charset' && position.ind > 0) { + errors.push({ action: 'drop', message: 'invalid @charset', location: { src, ...position } }); + return null; + } + while (['Whitespace'].includes(tokens[0]?.typ)) { + tokens.shift(); + } + if (atRule.val == 'import') { + // only @charset and @layer are accepted before @import + if (context.chi.length > 0) { + let i = context.chi.length; + while (i--) { + const type = context.chi[i].typ; + if (type == 'Comment') { + continue; + } + if (type != 'AtRule') { + errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); + return null; + } + const name = context.chi[i].nam; + if (name != 'charset' && name != 'import' && name != 'layer') { + errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); + return null; + } + break; + } + } // @ts-ignore - if (previous.typ == node.typ) { - let shouldMerge = true; + if (tokens[0]?.typ != 'String' && tokens[0]?.typ != 'UrlFunc') { + errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); + return null; + } + // @ts-ignore + if (tokens[0].typ == 'UrlFunc' && tokens[1]?.typ != 'Url-token' && tokens[1]?.typ != 'String') { + errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); + return null; + } + } + if (atRule.val == 'import') { + // @ts-ignore + if (tokens[0].typ == 'UrlFunc' && tokens[1].typ == 'Url-token') { + tokens.shift(); + // const token: Token = tokens.shift(); // @ts-ignore - let k = previous.chi.length; - while (k-- > 0) { - // @ts-ignore - if (previous.chi[k].typ == 'Comment') { - continue; + tokens[0].typ = 'String'; + // @ts-ignore + tokens[0].val = `"${tokens[0].val}"`; + // tokens[0] = token; + } + } + // https://www.w3.org/TR/css-nesting-1/#conditionals + // allowed nesting at-rules + // there must be a top level rule in the stack + const node = { + typ: 'AtRule', + nam: renderToken(atRule, { removeComments: true }), + val: tokens.reduce((acc, curr, index, array) => { + if (curr.typ == 'Whitespace') { + if (array[index + 1]?.typ == 'Start-parens' || + array[index - 1]?.typ == 'End-parens' || + array[index - 1]?.typ == 'Func') { + return acc; } - // @ts-ignore - shouldMerge = previous.chi[k].typ == 'Declaration'; + } + return acc + renderToken(curr, { removeComments: true }); + }, '') + }; + if (node.nam == 'import') { + if (options.processImport) { + // @ts-ignore + let fileToken = tokens[tokens[0].typ == 'UrlFunc' ? 1 : 0]; + let file = fileToken.typ == 'String' ? fileToken.val.slice(1, -1) : fileToken.val; + if (!file.startsWith('data:')) ; + } + } + if (delim.typ == 'Block-start') { + node.chi = []; + } + loc = { + sta: position, + src + }; + if (options.location) { + node.loc = loc; + } + // @ts-ignore + context.chi.push(node); + return delim.typ == 'Block-start' ? node : null; + } + else { + // rule + if (delim.typ == 'Block-start') { + const position = map.get(tokens[0]); + if (context.typ == 'Rule') { + if (tokens[0]?.typ == 'Iden') { + errors.push({ action: 'drop', message: 'invalid nesting rule', location: { src, ...position } }); + return null; + } + } + const sel = parseTokens(tokens, { compress: options.compress }).map(curr => renderToken(curr, { compress: true })); + const raw = [...new Set(sel.reduce((acc, curr) => { + if (curr == ',') { + acc.push(''); + } + else { + acc[acc.length - 1] += curr; + } + return acc; + }, ['']))]; + const node = { + typ: 'Rule', + // @ts-ignore + sel: raw.join(','), + chi: [] + }; + Object.defineProperty(node, 'raw', { enumerable: false, get: () => raw }); + loc = { + sta: position, + src + }; + if (options.location) { + node.loc = loc; + } + // @ts-ignore + context.chi.push(node); + return node; + } + else { + // declaration + // @ts-ignore + let name = null; + // @ts-ignore + let value = null; + for (let i = 0; i < tokens.length; i++) { + if (tokens[i].typ == 'Comment') { + continue; + } + if (tokens[i].typ == 'Colon') { + name = tokens.slice(0, i); + value = parseTokens(tokens.slice(i + 1), { parseColor: true }); + } + } + if (name == null) { + name = tokens; + } + const position = map.get(name[0]); + if (name.length > 0) { + for (let i = 1; i < name.length; i++) { + if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') { + errors.push({ + action: 'drop', + message: 'invalid declaration', + location: { src, ...position } + }); + return null; + } + } + } + // if (name.length == 0) { + // + // errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); + // return null; + // } + if (value == null) { + errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); + return null; + } + if (value.length == 0) { + errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); + return null; + } + const node = { + typ: 'Declaration', + // @ts-ignore + nam: renderToken(name.shift(), { removeComments: true }), + // @ts-ignore + val: value + }; + while (node.val[0]?.typ == 'Whitespace') { + node.val.shift(); + } + if (node.val.length == 0) { + errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); + return null; + } + loc = { + sta: position, + src + }; + if (options.location) { + node.loc = loc; + } + // @ts-ignore + context.chi.push(node); + return null; + } + } + } + function peek(count = 1) { + if (count == 1) { + return iterator.charAt(ind + 1); + } + return iterator.slice(ind + 1, ind + count + 1); + } + function prev(count = 1) { + if (count == 1) { + return ind == 0 ? '' : iterator.charAt(ind - 1); + } + return iterator.slice(ind - 1 - count, ind - 1); + } + function next(count = 1) { + let char = ''; + while (count-- > 0 && ind < total) { + const codepoint = iterator.charCodeAt(++ind); + if (isNaN(codepoint)) { + return char; + } + char += iterator.charAt(ind); + if (isNewLine(codepoint)) { + lin++; + col = 0; + } + else { + col++; + } + } + return char; + } + function pushToken(token) { + tokens.push(token); + map.set(token, { ...position }); + position.ind = ind; + position.lin = lin; + position.col = col == 0 ? 1 : col; + // } + } + function consumeWhiteSpace() { + let count = 0; + while (isWhiteSpace(iterator.charAt(count + ind + 1).charCodeAt(0))) { + count++; + } + next(count); + return count; + } + function consumeString(quoteStr) { + const quote = quoteStr; + let value; + let hasNewLine = false; + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + buffer += quoteStr; + while (ind < total) { + value = peek(); + if (ind >= total) { + pushToken({ typ: hasNewLine ? 'Bad-string' : 'Unclosed-string', val: buffer }); + break; + } + if (value == '\\') { + // buffer += value; + if (ind >= total) { + // drop '\\' at the end + pushToken(getType(buffer)); + break; + } + buffer += next(2); + continue; + } + if (value == quote) { + buffer += value; + pushToken({ typ: hasNewLine ? 'Bad-string' : 'String', val: buffer }); + next(); + // i += value.length; + buffer = ''; + break; + } + if (isNewLine(value.charCodeAt(0))) { + hasNewLine = true; + } + if (hasNewLine && value == ';') { + pushToken({ typ: 'Bad-string', val: buffer }); + buffer = ''; + break; + } + buffer += value; + // i += value.length; + next(); + } + } + while (ind < total) { + value = next(); + if (ind >= total) { + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + break; + } + if (isWhiteSpace(value.charCodeAt(0))) { + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + while (ind < total) { + value = next(); + if (ind >= total) { + break; + } + if (!isWhiteSpace(value.charCodeAt(0))) { + break; + } + } + pushToken({ typ: 'Whitespace' }); + buffer = ''; + if (ind >= total) { + break; + } + } + switch (value) { + case '/': + if (buffer.length > 0 && tokens.at(-1)?.typ == 'Whitespace') { + pushToken(getType(buffer)); + buffer = ''; + if (peek() != '*') { + pushToken(getType(value)); break; } - if (shouldMerge) { - // @ts-ignore - if ((node.typ == 'Rule' && node.sel == previous.sel) || - // @ts-ignore - (node.typ == 'AtRule') && node.val == previous.val) { - // @ts-ignore - node.chi.unshift(...previous.chi); - // @ts-ignore - ast.chi.splice(nodeIndex, 1); - i--; - previous = node; - nodeIndex = i; + } + buffer += value; + if (peek() == '*') { + buffer += '*'; + // i++; + next(); + while (ind < total) { + value = next(); + if (ind >= total) { + pushToken({ + typ: 'Bad-comment', val: buffer + }); + break; + } + if (value == '\\') { + buffer += value; + value = next(); + if (ind >= total) { + pushToken({ + typ: 'Bad-comment', + val: buffer + }); + break; + } + buffer += value; continue; } - else if (node.typ == 'Rule') { - if (diff(node, previous) && diff(previous, node)) { - if (node.typ == 'Rule') { - previous.sel += ',' + node.sel; + if (value == '*') { + buffer += value; + value = next(); + if (ind >= total) { + pushToken({ + typ: 'Bad-comment', val: buffer + }); + break; + } + buffer += value; + if (value == '/') { + pushToken({ typ: 'Comment', val: buffer }); + buffer = ''; + break; + } + } + else { + buffer += value; + } + } + } + // else { + // + // pushToken(getType(buffer)); + // buffer = ''; + // } + break; + case '<': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + buffer += value; + value = next(); + if (ind >= total) { + break; + } + if (peek(3) == '!--') { + while (ind < total) { + value = next(); + if (ind >= total) { + break; + } + buffer += value; + if (value == '>' && prev(2) == '--') { + pushToken({ + typ: 'CDOCOMM', + val: buffer + }); + buffer = ''; + break; + } + } + } + if (ind >= total) { + pushToken({ typ: 'BADCDO', val: buffer }); + buffer = ''; + } + break; + case '\\': + value = next(); + // EOF + if (ind + 1 >= total) { + // end of stream ignore \\ + pushToken(getType(buffer)); + buffer = ''; + break; + } + buffer += value; + break; + case '"': + case "'": + consumeString(value); + break; + case '~': + case '|': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + buffer += value; + value = next(); + if (ind >= total) { + pushToken(getType(buffer)); + buffer = ''; + break; + } + if (value == '=') { + buffer += value; + pushToken({ + typ: buffer[0] == '~' ? 'Includes' : 'Dash-matches', + val: buffer + }); + buffer = ''; + break; + } + pushToken(getType(buffer)); + buffer = value; + break; + case '>': + if (tokens[tokens.length - 1]?.typ == 'Whitespace') { + tokens.pop(); + } + pushToken({ typ: 'Gt' }); + consumeWhiteSpace(); + break; + case ':': + case ',': + case '=': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + if (value == ':' && ':' == peek()) { + buffer += value + next(); + break; + } + // if (value == ',' && tokens[tokens.length - 1]?.typ == 'Whitespace') { + // + // tokens.pop(); + // } + pushToken(getType(value)); + buffer = ''; + while (isWhiteSpace(peek().charCodeAt(0))) { + next(); + } + break; + case ')': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + pushToken({ typ: 'End-parens' }); + break; + case '(': + if (buffer.length == 0) { + pushToken({ typ: 'Start-parens' }); + } + else { + buffer += value; + pushToken(getType(buffer)); + buffer = ''; + const token = tokens[tokens.length - 1]; + if (token.typ == 'UrlFunc' /* && token.chi == 'url' */) { + // consume either string or url token + let whitespace = ''; + value = peek(); + while (isWhiteSpace(value.charCodeAt(0))) { + whitespace += value; + } + if (whitespace.length > 0) { + next(whitespace.length); + } + value = peek(); + if (value == '"' || value == "'") { + consumeString(next()); + let token = tokens[tokens.length - 1]; + if (['String', 'Literal'].includes(token.typ) && /^(["']?)[a-zA-Z0-9_/-][a-zA-Z0-9_/:.-]+(\1)$/.test(token.val)) { + if (token.typ == 'String') { + token.val = token.val.slice(1, -1); } // @ts-ignore - ast.chi.splice(i, 1); - i--; - // previous = node; - // nodeIndex = i; + // token.typ = 'Url-token'; } + break; + } + else { + buffer = ''; + do { + let cp = value.charCodeAt(0); + // EOF - + if (cp == null) { + pushToken({ typ: 'Bad-url-token', val: buffer }); + break; + } + // ')' + if (cp == 0x29 || cp == null) { + if (buffer.length == 0) { + pushToken({ typ: 'Bad-url-token', val: '' }); + } + else { + pushToken({ typ: 'Url-token', val: buffer }); + } + if (cp != null) { + pushToken(getType(next())); + } + break; + } + if (isWhiteSpace(cp)) { + whitespace = next(); + while (true) { + value = peek(); + cp = value.charCodeAt(0); + if (isWhiteSpace(cp)) { + whitespace += value; + continue; + } + break; + } + if (cp == null || cp == 0x29) { + continue; + } + // bad url token + buffer += next(whitespace.length); + do { + value = peek(); + cp = value.charCodeAt(0); + if (cp == null || cp == 0x29) { + break; + } + buffer += next(); + } while (true); + pushToken({ typ: 'Bad-url-token', val: buffer }); + continue; + } + buffer += next(); + value = peek(); + } while (true); + buffer = ''; } } } - // @ts-ignore - if (previous != node) { + break; + case '[': + case ']': + case '{': + case '}': + case ';': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + pushToken(getBlockType(value)); + let node = null; + if (value == '{' || value == ';') { + node = parseNode(tokens); + if (node != null) { + stack.push(node); + // @ts-ignore + context = node; + } + else if (value == '{') { + // node == null + // consume and throw away until the closing '}' or EOF + consume('{', '}'); + } + tokens.length = 0; + map.clear(); + } + else if (value == '}') { + parseNode(tokens); + const previousNode = stack.pop(); // @ts-ignore - if (previous.chi.some(n => n.typ == 'Declaration')) { - deduplicateRule(previous); + context = stack[stack.length - 1] || ast; + // @ts-ignore + if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { + context.chi.pop(); } - else { - deduplicate(previous); + else if (previousNode != null && previousNode != ast && options.compress) { + // @ts-ignore + if (hasDeclaration(previousNode)) { + deduplicateRule(previousNode); + } + else { + deduplicate(previousNode, options); + } } + tokens.length = 0; + map.clear(); + buffer = ''; } - } - previous = node; - nodeIndex = i; + break; + case '!': + if (buffer.length > 0) { + pushToken(getType(buffer)); + buffer = ''; + } + const important = peek(9); + if (important == 'important') { + if (tokens[tokens.length - 1]?.typ == 'Whitespace') { + tokens.pop(); + } + pushToken({ typ: 'Important' }); + next(9); + buffer = ''; + break; + } + buffer = '!'; + break; + default: + buffer += value; + break; } - // @ts-ignore - if (node != null && ('chi' in node)) { - // @ts-ignore - if (node.chi.some(n => n.typ == 'Declaration')) { - deduplicateRule(node); + } + if (buffer.length > 0) { + pushToken(getType(buffer)); + } + if (options.compress) { + while (stack.length > 0) { + const node = stack.pop(); + if (hasDeclaration(node)) { + deduplicateRule(node, options); } else { - deduplicate(node); + deduplicate(node, options); } } + if (ast.chi.length > 0) { + deduplicate(ast, options); + } } - return ast; + return { ast, errors }; } -function deduplicateRule(ast) { - if (!('chi' in ast) || ast.chi?.length == 0) { - return ast; - } - // @ts-ignore - const j = ast.chi.length; - let k = 0; - const properties = new PropertyList(); - for (; k < j; k++) { - // @ts-ignore - if ('Comment' == ast.chi[k].typ || 'Declaration' == ast.chi[k].typ) { +function parseTokens(tokens, options = {}) { + for (let i = 0; i < tokens.length; i++) { + const t = tokens[i]; + if (t.typ == 'Whitespace' && ((i == 0 || + i + 1 == tokens.length || + ['Comma', 'Start-parens'].includes(tokens[i + 1].typ) || + (i > 0 && funcLike.includes(tokens[i - 1].typ))))) { + tokens.splice(i--, 1); + continue; + } + if (t.typ == 'Colon') { + const typ = tokens[i + 1]?.typ; + if (typ != null) { + if (typ == 'Func') { + tokens[i + 1].val = ':' + tokens[i + 1].val; + tokens[i + 1].typ = 'Pseudo-class-func'; + } + else if (typ == 'Iden') { + tokens[i + 1].val = ':' + tokens[i + 1].val; + tokens[i + 1].typ = 'Pseudo-class'; + } + if (typ == 'Func' || typ == 'Iden') { + tokens.splice(i, 1); + i--; + continue; + } + } + } + if (t.typ == 'Attr-start') { + let k = i; + let inAttr = 1; + while (++k < tokens.length) { + if (tokens[k].typ == 'Attr-end') { + inAttr--; + } + else if (tokens[k].typ == 'Attr-start') { + inAttr++; + } + if (inAttr == 0) { + break; + } + } + Object.assign(t, { typ: 'Attr', chi: tokens.splice(i + 1, k - i) }); + // @ts-ignore + if (t.chi.at(-1).typ == 'Attr-end') { + // @ts-ignore + t.chi.pop(); + // @ts-ignore + if (t.chi.length > 1) { + /*(t).chi =*/ + // @ts-ignore + parseTokens(t.chi, options); + } + // @ts-ignore + t.chi.forEach(val => { + if (val.typ == 'String') { + const slice = val.val.slice(1, -1); + if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) { + Object.assign(val, { typ: 'Iden', val: slice }); + } + } + }); + } + continue; + } + if (funcLike.includes(t.typ)) { + let parens = 1; + let k = i; + while (++k < tokens.length) { + if (tokens[k].typ == 'Colon') { + const typ = tokens[k + 1]?.typ; + if (typ != null) { + if (typ == 'Iden') { + tokens[k + 1].typ = 'Pseudo-class'; + tokens[k + 1].val = ':' + tokens[k + 1].val; + } + else if (typ == 'Func') { + tokens[k + 1].typ = 'Pseudo-class-func'; + tokens[k + 1].val = ':' + tokens[k + 1].val; + } + if (typ == 'Func' || typ == 'Iden') { + tokens.splice(k, 1); + k--; + continue; + } + } + } + if (funcLike.includes(tokens[k].typ)) { + parens++; + } + else if (tokens[k].typ == 'End-parens') { + parens--; + } + if (parens == 0) { + break; + } + } + // @ts-ignore + t.chi = tokens.splice(i + 1, k - i); + // @ts-ignore + if (t.chi.at(-1)?.typ == 'End-parens') { + // @ts-ignore + t.chi.pop(); + } + // @ts-ignore + if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { + let isColor = true; + // @ts-ignore + for (const v of t.chi) { + if (v.typ == 'Func' && v.val == 'var') { + isColor = false; + break; + } + } + if (!isColor) { + continue; + } + // @ts-ignore + t.typ = 'Color'; + // @ts-ignore + t.kin = t.val; + // @ts-ignore + let m = t.chi.length; + while (m-- > 0) { + // @ts-ignore + if (t.chi[m].typ == 'Literal') { + // @ts-ignore + if (t.chi[m + 1]?.typ == 'Whitespace') { + // @ts-ignore + t.chi.splice(m + 1, 1); + } + // @ts-ignore + if (t.chi[m - 1]?.typ == 'Whitespace') { + // @ts-ignore + t.chi.splice(m - 1, 1); + m--; + } + } + } + } + else if (t.typ == 'UrlFunc') { + // @ts-ignore + if (t.chi[0]?.typ == 'String') { + // @ts-ignore + const value = t.chi[0].val.slice(1, -1); + if (/^[/%.a-zA-Z0-9_-]+$/.test(value)) { + // @ts-ignore + t.chi[0].typ = 'Url-token'; + // @ts-ignore + t.chi[0].val = value; + } + } + } // @ts-ignore - properties.add(ast.chi[k]); + if (t.chi.length > 0) { + // @ts-ignore + parseTokens(t.chi, options); + if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.compress) { + // + // console.debug({is: t}); + const count = t.chi.filter(t => t.typ != 'Comment').length; + if (count == 1 || + (i == 0 && + (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1)) || + (tokens[i - 1]?.typ == 'Comma' && (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1))) { + // console.debug(tokens[i]); + tokens.splice(i, 1, ...t.chi); + i = Math.max(0, i - t.chi.length); + } + // tokens.splice(i, 1, ...t.chi); + } + } continue; } - break; + if (options.parseColor) { + if (t.typ == 'Iden') { + // named color + const value = t.val.toLowerCase(); + if (COLORS_NAMES[value] != null) { + Object.assign(t, { + typ: 'Color', + val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, + kin: 'hex' + }); + } + continue; + } + if (t.typ == 'Hash' && isHexColor(t.val)) { + // hex color + // @ts-ignore + t.typ = 'Color'; + // @ts-ignore + t.kin = 'hex'; + continue; + } + } + } + return tokens; +} +function getBlockType(chr) { + if (chr == ';') { + return { typ: 'Semi-colon' }; + } + if (chr == '{') { + return { typ: 'Block-start' }; + } + if (chr == '}') { + return { typ: 'Block-end' }; + } + if (chr == '[') { + return { typ: 'Attr-start' }; + } + if (chr == ']') { + return { typ: 'Attr-end' }; } - // @ts-ignore - ast.chi = [...properties].concat(ast.chi.slice(k)); - // @ts-ignore - // ast.chi.splice(0, k - 1, ...properties); - return ast; + throw new Error(`unhandled token: '${chr}'`); } -export { deduplicate, deduplicateRule, parse }; +export { parse }; diff --git a/dist/parser/tokenize.js b/dist/parser/tokenize.js index d89b71d..66c5478 100644 --- a/dist/parser/tokenize.js +++ b/dist/parser/tokenize.js @@ -1,34 +1,35 @@ -import { isWhiteSpace, isIdent, isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isHash, isHexColor, isNewLine } from './utils/syntax.js'; +import { isWhiteSpace, isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHash, isNewLine, isIdentStart, isHexColor } from './utils/syntax.js'; import { renderToken } from '../renderer/renderer.js'; import { COLORS_NAMES } from '../renderer/utils/color.js'; -function tokenize(iterator, errors, options) { +const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func']; +function tokenize(iterator, errors, options, offsetInd = 0, offsetLin = 1, offsetCol = 0) { + let ind = -1; + let lin = offsetLin; + let col = offsetCol; const tokens = []; const src = options.src; const stack = []; - const root = { + const ast = { typ: "StyleSheet", chi: [] }; const position = { - ind: 0, - lin: 1, - col: 1 + ind: Math.max(ind, 0), + lin: lin, + col: Math.max(col, 1) }; let value; let buffer = ''; - let ind = -1; - let lin = 1; - let col = 0; let total = iterator.length; let map = new Map; - let context = root; + let context = ast; if (options.location) { - root.loc = { + ast.loc = { sta: { - ind: 0, - lin: 1, - col: 1 + ind: ind + offsetInd, + lin: lin, + col: col }, src: '' }; @@ -62,16 +63,20 @@ function tokenize(iterator, errors, options) { return { typ: 'Gt' }; } if (isPseudo(val)) { - return { - typ: val.endsWith('(') ? 'Pseudo-class-func' : 'Pseudo-class', - val - }; + return val.endsWith('(') ? { + typ: 'Pseudo-class-func', + val: val.slice(0, -1), + chi: [] + } + : { + typ: 'Pseudo-class', + val + }; } if (isAtKeyword(val)) { return { typ: 'At-rule', val: val.slice(1) - // buffer: buffer.slice() }; } if (isFunction(val)) { @@ -287,7 +292,6 @@ function tokenize(iterator, errors, options) { else { // rule if (delim.typ == 'Block-start') { - let inAttr = 0; const position = map.get(tokens[0]); if (context.typ == 'Rule') { if (tokens[0]?.typ == 'Iden') { @@ -295,43 +299,11 @@ function tokenize(iterator, errors, options) { return null; } } + const sel = parseTokens(tokens).map(curr => renderToken(curr, { compress: true })); const node = { typ: 'Rule', // @ts-ignore - sel: tokens.reduce((acc, curr) => { - if (acc[acc.length - 1].length == 0 && curr.typ == 'Whitespace') { - return acc; - } - if (inAttr > 0 && curr.typ == 'String') { - const ident = curr.val.slice(1, -1); - if (isIdent(ident)) { - // @ts-ignore - curr.typ = 'Iden'; - curr.val = ident; - } - } - if (curr.typ == 'Attr-start') { - inAttr++; - } - else if (curr.typ == 'Attr-end') { - inAttr--; - } - if (inAttr == 0 && curr.typ == "Comma") { - acc.push([]); - } - else { - acc[acc.length - 1].push(curr); - } - return acc; - }, [[]]).map(part => part.reduce((acc, p, index, array) => { - if (p.typ == 'Whitespace') { - if (array[index + 1]?.typ == 'Start-parens' || - array[index - 1]?.typ == 'End-parens') { - return acc; - } - } - return acc + renderToken(p, { removeComments: true }); - }, '')).join(), + sel: sel.join(''), chi: [] }; loc = { @@ -357,22 +329,13 @@ function tokenize(iterator, errors, options) { } if (tokens[i].typ == 'Colon') { name = tokens.slice(0, i); - value = tokens.slice(i + 1); - } - else if (['Func', 'Pseudo-class'].includes(tokens[i].typ) && tokens[i].val.startsWith(':')) { - tokens[i].val = tokens[i].val.slice(1); - if (tokens[i].typ == 'Pseudo-class') { - tokens[i].typ = 'Iden'; - } - name = tokens.slice(0, i); - value = tokens.slice(i); + value = parseTokens(tokens.slice(i + 1), { parseColor: true }); } } if (name == null) { name = tokens; } const position = map.get(name[0]); - // const rawName: string = (name.shift())?.val; if (name.length > 0) { for (let i = 1; i < name.length; i++) { if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') { @@ -394,92 +357,6 @@ function tokenize(iterator, errors, options) { errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); return null; } - // let j: number = value.length - let i = 0; - let t; - for (; i < value.length; i++) { - t = value[i]; - if (t.typ == 'Iden') { - // named color - const value = t.val.toLowerCase(); - if (COLORS_NAMES[value] != null) { - Object.assign(t, { - typ: 'Color', - val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, - kin: 'hex' - }); - } - continue; - } - if (t.typ == 'Hash' && isHexColor(t.val)) { - // hex color - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = 'hex'; - continue; - } - if (t.typ == 'Func' || t.typ == 'UrlFunc') { - // func color - let parens = 1; - let k = i; - let j = value.length; - let isScalar = true; - while (++k < j) { - switch (value[k].typ) { - case 'Start-parens': - case 'Func': - case 'UrlFunc': - parens++; - isScalar = false; - break; - case 'End-parens': - parens--; - break; - } - if (parens == 0) { - break; - } - } - t.chi = value.splice(i + 1, k - i); - if (t.chi.at(-1).typ == 'End-parens') { - t.chi.pop(); - } - if (isScalar) { - if (['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = t.val; - let i = t.chi.length; - while (i-- > 0) { - if (t.chi[i].typ == 'Literal') { - if (t.chi[i + 1]?.typ == 'Whitespace') { - t.chi.splice(i + 1, 1); - } - if (t.chi[i - 1]?.typ == 'Whitespace') { - t.chi.splice(i - 1, 1); - i--; - } - } - } - } - else if (t.typ = 'UrlFunc') { - if (t.chi[0]?.typ == 'String') { - const value = t.chi[0].val.slice(1, -1); - if (/^[/%.a-zA-Z0-9_-]+$/.test(value)) { - t.chi[0].typ = 'Url-token'; - t.chi[0].val = value; - } - } - } - } - continue; - } - if (t.typ == 'Whitespace' || t.typ == 'Comment') { - value.slice(i, 1); - } - } if (value.length == 0) { errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); return null; @@ -527,40 +404,23 @@ function tokenize(iterator, errors, options) { let char = ''; while (count-- > 0 && ind < total) { const codepoint = iterator.charCodeAt(++ind); - if (codepoint == null) { + if (isNaN(codepoint)) { return char; } - // if (codepoint < 0x80) { char += iterator.charAt(ind); - // } - // else { - // - // const chr: string = String.fromCodePoint(codepoint); - // - // ind += chr.length - 1; - // char += chr; - // } - // ind += codepoint < 256 ? 1 : String.fromCodePoint(codepoint).length; if (isNewLine(codepoint)) { lin++; col = 0; - // \r\n - // if (codepoint == 0xd && iterator.charCodeAt(i + 1) == 0xa) { - // offset++; - // ind++; - // } } else { col++; } - // ind++; - // i += offset; } return char; } function pushToken(token) { tokens.push(token); - map.set(token, { ...position }); + map.set(token, { ...position, ind: position.ind + offsetInd }); position.ind = ind; position.lin = lin; position.col = col == 0 ? 1 : col; @@ -798,8 +658,8 @@ function tokenize(iterator, errors, options) { pushToken(getType(buffer)); buffer = ''; } - if (value == ':' && isIdent(peek())) { - buffer += value; + if (value == ':' && ':' == peek()) { + buffer += value + next(); break; } // if (value == ',' && tokens[tokens.length - 1]?.typ == 'Whitespace') { @@ -828,7 +688,7 @@ function tokenize(iterator, errors, options) { pushToken(getType(buffer)); buffer = ''; const token = tokens[tokens.length - 1]; - if (token.typ == 'UrlFunc' /* && token.val == 'url' */) { + if (token.typ == 'UrlFunc' /* && token.chi == 'url' */) { // consume either string or url token let whitespace = ''; value = peek(); @@ -938,7 +798,7 @@ function tokenize(iterator, errors, options) { node = parseNode(tokens); const previousNode = stack.pop(); // @ts-ignore - context = stack[stack.length - 1] || root; + context = stack[stack.length - 1] || ast; // if (options.location && context != root) { // @ts-ignore // context.loc.end = {ind, lin, col: col == 0 ? 1 : col} @@ -986,23 +846,185 @@ function tokenize(iterator, errors, options) { parseNode(tokens); } // pushToken({typ: 'EOF'}); - // - // if (col == 0) { - // - // col = 1; - // } - // if (options.location) { - // - // // @ts-ignore - // root.loc.end = {ind, lin, col}; - // - // for (const context of stack) { - // - // // @ts-ignore - // context.loc.end = {ind, lin, col}; - // } - // } - return root; + return ast; +} +function parseTokens(tokens, options = {}) { + for (let i = 0; i < tokens.length; i++) { + const t = tokens[i]; + if (tokens[i].typ == 'Colon' && i) { + const typ = tokens[i + 1]?.typ; + if (typ != null) { + if (typ == 'Func') { + tokens[i + 1].val = ':' + tokens[i + 1].val; + tokens[i + 1].typ = 'Pseudo-class-func'; + } + else if (typ == 'Iden') { + tokens[i + 1].val = ':' + tokens[i + 1].val; + tokens[i + 1].typ = 'Pseudo-class'; + } + if (typ == 'Func' || typ == 'Iden') { + tokens.splice(i, 1); + i--; + continue; + } + } + } + if (t.typ == 'Attr-start') { + let k = i; + let inAttr = 1; + while (++k < tokens.length) { + if (tokens[k].typ == 'Attr-end') { + inAttr--; + } + else if (tokens[k].typ == 'Attr-start') { + inAttr++; + } + if (inAttr == 0) { + break; + } + } + Object.assign(t, { typ: 'Attr', chi: tokens.splice(i + 1, k - i) }); + // @ts-ignore + if (t.chi.at(-1).typ == 'Attr-end') { + // @ts-ignore + t.chi.pop(); + // @ts-ignore + if (t.chi.length > 1) { + /*(t).chi =*/ + // @ts-ignore + parseTokens(t.chi, options); + } + // @ts-ignore + t.chi.forEach(val => { + if (val.typ == 'String') { + const slice = val.val.slice(1, -1); + if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) { + Object.assign(val, { typ: 'Iden', val: slice }); + } + } + }); + } + continue; + } + if (funcLike.includes(t.typ)) { + let parens = 1; + let k = i; + while (++k < tokens.length) { + if (tokens[k].typ == 'Colon') { + const typ = tokens[k + 1]?.typ; + if (typ != null) { + if (typ == 'Iden') { + tokens[k + 1].typ = 'Pseudo-class'; + tokens[k + 1].val = ':' + tokens[k + 1].val; + } + else if (typ == 'Func') { + tokens[k + 1].typ = 'Pseudo-class-func'; + tokens[k + 1].val = ':' + tokens[k + 1].val; + } + if (typ == 'Func' || typ == 'Iden') { + tokens.splice(k, 1); + k--; + continue; + } + } + } + if (funcLike.includes(tokens[k].typ)) { + parens++; + } + else if (tokens[k].typ == 'End-parens') { + parens--; + } + if (parens == 0) { + break; + } + } + // @ts-ignore + t.chi = tokens.splice(i + 1, k - i); + // @ts-ignore + if (t.chi.at(-1)?.typ == 'End-parens') { + // @ts-ignore + t.chi.pop(); + } + // @ts-ignore + if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { + let isColor = true; + // @ts-ignore + for (const v of t.chi) { + if (v.typ == 'Func' && v.val == 'var') { + isColor = false; + break; + } + } + if (!isColor) { + continue; + } + // @ts-ignore + t.typ = 'Color'; + // @ts-ignore + t.kin = t.val; + // @ts-ignore + let m = t.chi.length; + while (m-- > 0) { + // @ts-ignore + if (t.chi[m].typ == 'Literal') { + // @ts-ignore + if (t.chi[m + 1]?.typ == 'Whitespace') { + // @ts-ignore + t.chi.splice(m + 1, 1); + } + // @ts-ignore + if (t.chi[m - 1]?.typ == 'Whitespace') { + // @ts-ignore + t.chi.splice(m - 1, 1); + m--; + } + } + } + } + else if (t.typ == 'UrlFunc') { + // @ts-ignore + if (t.chi[0]?.typ == 'String') { + // @ts-ignore + const value = t.chi[0].val.slice(1, -1); + if (/^[/%.a-zA-Z0-9_-]+$/.test(value)) { + // @ts-ignore + t.chi[0].typ = 'Url-token'; + // @ts-ignore + t.chi[0].val = value; + } + } + } + // @ts-ignore + if (t.chi.length > 0) { + // @ts-ignore + parseTokens(t.chi, options); + } + continue; + } + if (options.parseColor) { + if (t.typ == 'Iden') { + // named color + const value = t.val.toLowerCase(); + if (COLORS_NAMES[value] != null) { + Object.assign(t, { + typ: 'Color', + val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, + kin: 'hex' + }); + } + continue; + } + if (t.typ == 'Hash' && isHexColor(t.val)) { + // hex color + // @ts-ignore + t.typ = 'Color'; + // @ts-ignore + t.kin = 'hex'; + continue; + } + } + } + return tokens; } function getBlockType(chr) { if (chr == ';') { diff --git a/dist/renderer/renderer.js b/dist/renderer/renderer.js index 60d859b..08c34a6 100644 --- a/dist/renderer/renderer.js +++ b/dist/renderer/renderer.js @@ -94,7 +94,7 @@ function doRender(data, options, reducer, level = 0) { children = children.slice(0, -1); } if (data.typ == 'AtRule') { - return `@${data.nam} ${data.val ? data.val + options.indent : ''}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`; + return `@${data.nam}${data.val ? ' ' + 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 + `}`; } @@ -142,8 +142,9 @@ function renderToken(token, options = {}) { } case 'Func': case 'UrlFunc': + case 'Pseudo-class-func': // @ts-ignore - return token.val + '(' + token.chi.reduce((acc, curr) => { + return (options.compress && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) : token.val) + '(' + token.chi.reduce((acc, curr) => { if (options.removeComments && curr.typ == 'Comment') { if (!options.preserveLicense || !curr.val.startsWith('/*!')) { return acc; @@ -177,6 +178,8 @@ function renderToken(token, options = {}) { return ','; case 'Important': return '!important'; + case 'Attr': + return '[' + token.chi.reduce((acc, curr) => acc + renderToken(curr, options), '') + ']'; case 'Time': case 'Frequency': case 'Angle': @@ -230,12 +233,11 @@ function renderToken(token, options = {}) { case 'At-rule': case 'Hash': case 'Pseudo-class': - case 'Pseudo-class-func': case 'Literal': case 'String': case 'Iden': case 'Delim': - return token.val; + return options.compress && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : token.val; } throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`); } diff --git a/dist/transform.js b/dist/transform.js new file mode 100644 index 0000000..57818f1 --- /dev/null +++ b/dist/transform.js @@ -0,0 +1,26 @@ +import { parse } from './parser/parse.js'; +import { render } from './renderer/renderer.js'; + +function transform(css, options = {}) { + options = { compress: true, removeEmpty: true, ...options }; + const startTime = performance.now(); + const parseResult = parse(css, options); + if (parseResult == null) { + // @ts-ignore + return null; + } + const renderTime = performance.now(); + const rendered = render(parseResult.ast, options); + const endTime = performance.now(); + return { + ...parseResult, ...rendered, performance: { + bytesIn: css.length, + bytesOut: rendered.code.length, + parse: `${(renderTime - startTime).toFixed(2)}ms`, + render: `${(endTime - renderTime).toFixed(2)}ms`, + total: `${(endTime - startTime).toFixed(2)}ms` + } + }; +} + +export { transform }; diff --git a/package.json b/package.json index dab151e..7362865 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { - "version": "0.0.1-alpha1", - "main": "dist/index-umd.js", - "browser": "dist/index-umd.js", - "module": "dist/index.js", + "version": "0.0.1-alpha2", + "exports": { + ".": "./dist/index.js", + "./umd": "./dist/index-umd.js" +}, "typings": "dist/index.d.ts", "scripts": { "build": "rollup -c", @@ -19,9 +20,14 @@ "@rollup/plugin-typescript": "^11.0.0", "@types/mocha": "^10.0.1", "@types/node": "^18.15.10", + "@webref/css": "^6.5.9", + "glob": "^10.3.0", "mocha": "^10.2.0", "rollup": "^3.20.1", "rollup-plugin-dts": "^5.3.0", "tslib": "^2.5.0" + }, + "dependencies": { + "@tbela99/workerize": "github:tbela99/workerize" } } diff --git a/rollup.config.mjs b/rollup.config.mjs index 680b00d..ab110f9 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,36 +1,25 @@ import dts from 'rollup-plugin-dts'; import typescript from "@rollup/plugin-typescript"; import nodeResolve from "@rollup/plugin-node-resolve"; -import glob from "glob"; +import {glob} from "glob"; import terser from "@rollup/plugin-terser"; import json from "@rollup/plugin-json"; -export default [...await new Promise((resolve, reject) => { - - glob('./test/specs/**/*.test.ts', (err, files) => { - - if (err) { - - reject(err); - } - - resolve(files.map(input => { - return { - input, - plugins: [nodeResolve(), json(), typescript()], - output: - { - banner: `/* generate from ${input} */`, - file: `./test/js/${input.replace(/^\.\/test\/specs/, '').replace(/\.ts$/, '.mjs')}`, - // entryFileNames: '[name].mjs', - // chunkFileNames: '[name].[hash].mjs', - format: 'es' - } +export default [...await glob('./test/specs/**/*.test.ts').then(files => files.map(input => { + return { + input, + plugins: [nodeResolve(), json(), typescript()], + output: + { + banner: `/* generate from ${input} */`, + file: `${input.replace(/\/test\/specs/, '/test/js/').replace(/\.ts$/, '.mjs')}`, + // entryFileNames: '[name].mjs', + // chunkFileNames: '[name].[hash].mjs', + format: 'es' } - })); - }) - -})].concat([ + } +})) +].concat([ { input: 'src/index.ts', plugins: [nodeResolve(), json(), typescript()], diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index ed7efbd..498cdae 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -29,8 +29,8 @@ export interface ParserOptions { src?: string; location?: boolean; + compress?: boolean; processImport?: boolean; - deduplicate?: boolean; removeEmpty?: boolean; nodeEventFilter?: NodeType[] } @@ -45,6 +45,30 @@ export interface RenderOptions { colorConvert?: boolean; } +export interface TransformOptions extends ParserOptions, RenderOptions { + +} + +export interface ParseResult { + ast: AstRuleStyleSheet; + errors: ErrorDescription[] +} + +export interface RenderResult { + code: string ; +} + +export interface TransformResult extends ParseResult, RenderResult { + + performance: { + bytesIn: number; + bytesOut: number; + parse: string; + // deduplicate: string; + render: string; + total: string; + } +} export interface Position { ind: number; diff --git a/src/@types/tokenize.ts b/src/@types/tokenize.ts index 75d9910..2523e2b 100644 --- a/src/@types/tokenize.ts +++ b/src/@types/tokenize.ts @@ -214,6 +214,7 @@ export interface PseudoClassFunctionToken { typ: 'Pseudo-class-func'; val: string; + chi: Token[]; } export interface DelimToken { @@ -252,7 +253,21 @@ export interface ColorToken { chi?: Token[]; } -export declare type TokenType = 'Dimension' | 'Number' | 'Perc' | 'Angle' | 'Length' | 'Time' | 'Frequency' | 'Resolution'; +export interface AttrToken { + + typ: 'Attr', + chi: Token[] +} + +export interface TokenStream { + buffer: string; + ind: number; + lin: number; + col: number; +} + +export declare type TokenType = 'Dimension' | 'Number' | 'Perc' | 'Angle' | 'Length' | 'Time' | 'Frequency' | + 'Resolution' | 'Attr'; export declare type Token = LiteralToken | IdentToken | CommaToken | ColonToken | SemiColonToken | NumberToken | AtRuleToken | PercentageToken | FunctionURLToken | FunctionToken | DimensionToken | LengthToken | @@ -261,4 +276,4 @@ export declare type Token = LiteralToken | IdentToken | CommaToken | ColonToken AttrStartToken | AttrEndToken | ParensStartToken | ParensEndToken | CDOCommentToken | BadCDOCommentToken | CommentToken | BadCommentToken | WhitespaceToken | IncludesToken | DashMatchToken | LessThanToken | GreaterThanToken | PseudoClassToken | PseudoClassFunctionToken | DelimToken | - BadUrlToken | UrlToken | ImportantToken | ColorToken | EOFToken; \ No newline at end of file + BadUrlToken | UrlToken | ImportantToken | ColorToken | AttrToken | EOFToken; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 315ad2b..55a480c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ export * from './parser'; -export * from './renderer'; \ No newline at end of file +export * from './renderer'; +export * from './transform'; \ No newline at end of file diff --git a/src/parser/declaration/list.ts b/src/parser/declaration/list.ts index 759d788..be4a89e 100644 --- a/src/parser/declaration/list.ts +++ b/src/parser/declaration/list.ts @@ -1,4 +1,4 @@ -import {AstDeclaration, AstNode, ShorthandMapType} from "../../@types"; +import {AstDeclaration, AstNode, ShorthandMapType, ShorthandPropertyType} from "../../@types"; import {PropertySet} from "./set"; import {getConfig} from "../utils"; import {PropertyMap} from "./map"; @@ -11,7 +11,7 @@ export class PropertyList { constructor() { - this.declarations = new Map; + this.declarations = new Map; } add(declaration: AstNode) { @@ -26,11 +26,13 @@ export class PropertyList { if (propertyName in config.properties) { + // @ts-ignore const shorthand: string = config.properties[propertyName].shorthand; if (!this.declarations.has(shorthand)) { - this.declarations.set(shorthand, new PropertySet(config.properties[shorthand])); + // @ts-ignore + this.declarations.set(shorthand, new PropertySet(config.properties[shorthand])); } (this.declarations.get(shorthand)).add(declaration); @@ -39,10 +41,12 @@ export class PropertyList { if (propertyName in config.map) { + // @ts-ignore const shorthand: string = config.map[propertyName].shorthand; if (!this.declarations.has(shorthand)) { + // @ts-ignore this.declarations.set(shorthand, new PropertyMap(config.map[shorthand])); } @@ -79,7 +83,7 @@ export class PropertyList { if (value.done && iterators.length > 0) { - iterator = iterators.shift(); + iterator = >iterators.shift(); value = iterator.next(); } } diff --git a/src/parser/declaration/map.ts b/src/parser/declaration/map.ts index 7ff5cd4..f85072a 100644 --- a/src/parser/declaration/map.ts +++ b/src/parser/declaration/map.ts @@ -84,7 +84,8 @@ export class PropertyMap { const tokens: { [key: string]: Token[][] } = {}; const values: Token[] = []; - this.declarations.get(this.config.shorthand).val.slice().reduce((acc, curr) => { + // @ts-ignore + this.declarations.get(this.config.shorthand).val.slice().reduce((acc: Token[][], curr: Token) => { if (separator != null && separator.typ == curr.typ && eq(separator, curr)) { @@ -93,11 +94,14 @@ export class PropertyMap { } // else { + // @ts-ignore acc.at(-1).push(curr); // } return acc; - }, [[]]).reduce((acc, list, current) => { + }, [[]]). + // @ts-ignore + reduce((acc: Token[][], list: Token[], current: number) => { values.push(...this.pattern.reduce((acc: Token[], property: string) => { @@ -244,6 +248,11 @@ export class PropertyMap { if (requiredCount < this.requiredCount) { + // if (this.declarations.size == 1 && this.declarations.has(this.config.shorthand)) { + // + // this.declarations + // } + return this.declarations.values(); } @@ -366,13 +375,16 @@ export class PropertyMap { if ('mapping' in props) { + // @ts-ignore if (!('constraints' in props) || !('max' in props.constraints) || values.length <= props.constraints.mapping.max) { let i = values.length; while (i--) { + // @ts-ignore if (values[i].typ == 'Iden' && values[i].val in props.mapping) { + // @ts-ignore values.splice(i, 1, ...parseString(props.mapping[values[i].val])); } } @@ -382,6 +394,7 @@ export class PropertyMap { if ('prefix' in props) { + // @ts-ignore acc[i].push({...props.prefix}); } @@ -394,9 +407,11 @@ export class PropertyMap { if (acc.length > 0) { + // @ts-ignore acc.push({...(props.separator ?? {typ: 'Whitespace'})}); } + // @ts-ignore acc.push(curr); return acc; }, [])) diff --git a/src/parser/declaration/set.ts b/src/parser/declaration/set.ts index 5d916b6..73679d6 100644 --- a/src/parser/declaration/set.ts +++ b/src/parser/declaration/set.ts @@ -117,12 +117,12 @@ export class PropertySet { return this; } - // declaration.val = declaration.val.reduce((acc: Token[], token: Token) => { + // declaration.chi = declaration.chi.reduce((acc: Token[], token: Token) => { // - // if (this.config.types.includes(token.typ) || ('0' == (token).val && ( + // if (this.config.types.includes(token.typ) || ('0' == (token).chi && ( // this.config.types.includes('Length') || // this.config.types.includes('Angle') || - // this.config.types.includes('Dimension'))) || (token.typ == 'Iden' && this.config.keywords.includes(token.val))) { + // this.config.types.includes('Dimension'))) || (token.typ == 'Iden' && this.config.keywords.includes(token.chi))) { // // acc.push(token); // } diff --git a/src/parser/deduplicate.ts b/src/parser/deduplicate.ts new file mode 100644 index 0000000..b78ce17 --- /dev/null +++ b/src/parser/deduplicate.ts @@ -0,0 +1,461 @@ +import {AstAtRule, AstDeclaration, AstNode, AstRule, AstRuleList, ParserOptions, TransformOptions} from "../@types"; +import {PropertyList} from "./declaration"; +import {eq} from "./utils/eq"; +import {render} from "../renderer"; +import {getConfig} from "./utils"; + +const configuration = getConfig(); + +export function deduplicate(ast: AstNode, options: ParserOptions = {}, recursive: boolean = false) { + + // @ts-ignore + if (('chi' in ast) && ast.chi?.length > 0) { + + let i: number = 0; + let previous: AstNode; + let node: AstNode; + let nodeIndex: number; + + // @ts-ignore + for (; i < ast.chi.length; i++) { + + // @ts-ignore + if (ast.chi[i].typ == 'Comment') { + + continue; + } + + // @ts-ignore + node = ast.chi[i]; + + if (node.typ == 'AtRule' && (node).nam == 'font-face') { + + continue; + } + + if (node.typ == 'AtRule' && (node).val == 'all') { + + // @ts-ignore + ast.chi?.splice(i, 1, ...(node).chi); + i--; + continue; + } + + // @ts-ignore + if (previous != null && 'chi' in previous && ('chi' in node)) { + + // @ts-ignore + if (previous.typ == node.typ) { + + let shouldMerge = true; + + // @ts-ignore + let k = previous.chi.length; + + while (k-- > 0) { + + // @ts-ignore + if (previous.chi[k].typ == 'Comment') { + + continue; + } + + // @ts-ignore + shouldMerge = previous.chi[k].typ == 'Declaration'; + break; + } + + if (shouldMerge) { + + // @ts-ignore + if ((node.typ == 'Rule' && (node).sel == (previous).sel) || + // @ts-ignore + (node.typ == 'AtRule') && (node).val == (previous).val) { + + // @ts-ignore + node.chi.unshift(...previous.chi); + // @ts-ignore + ast.chi.splice(nodeIndex, 1); + + // @ts-ignore + if (hasDeclaration(node)) { + + deduplicateRule(node); + } else { + + deduplicate(node, options, recursive); + } + i--; + previous = node; + nodeIndex = i; + continue; + } else if (node.typ == 'Rule' && previous?.typ == 'Rule') { + + const intersect = diff(previous, node, options); + + if (intersect != null) { + + if (intersect.node1.chi.length == 0) { + // @ts-ignore + ast.chi.splice(i, 1) + } else { + // @ts-ignore + ast.chi.splice(i, 1, intersect.node1); + } + + if (intersect.node2.chi.length == 0) { + // @ts-ignore + ast.chi.splice(nodeIndex, 1, intersect.result); + } else { + + // @ts-ignore + ast.chi.splice(nodeIndex, 1, intersect.result, intersect.node2); + } + } + } + } + } + + // @ts-ignore + if (recursive && previous != node) { + + // @ts-ignore + if (hasDeclaration(previous)) { + + deduplicateRule(previous); + } else { + + deduplicate(previous, options, recursive); + } + } + } + + previous = node; + nodeIndex = i; + } + + // @ts-ignore + if (recursive && node != null && ('chi' in node)) { + + // @ts-ignore + if (node.chi.some(n => n.typ == 'Declaration')) { + + deduplicateRule(node); + } else { + + deduplicate(node, options, recursive); + } + } + } + + return ast; +} + +export function hasDeclaration(node: AstAtRule | AstRule | AstRuleList): boolean { + + // @ts-ignore + for (let i = 0; i < node.chi?.length; i++) { + + // @ts-ignore + if (node.chi[i].typ == 'Comment') { + + continue; + } + + // @ts-ignore + return node.chi[i].typ == 'Declaration'; + } + + + return true; +} + +export function deduplicateRule(ast: AstNode, options: ParserOptions = {}): AstNode { + + // @ts-ignore + if (!('chi' in ast) || ast.chi?.length <= 1) { + + return ast; + } + + // @ts-ignore + const j: number = ast.chi.length; + let k: number = 0; + + let map: Map = new Map; + + // @ts-ignore + for (; k < j; k++) { + + // @ts-ignore + const node = ast.chi[k]; + + if (node.typ == 'Comment') { + + // @ts-ignore + map.set(node, node); + continue; + } else if (node.typ != 'Declaration') { + + break; + } + + if ((node).nam in configuration.map || + (node).nam in configuration.properties) { + + // @ts-ignore + const shorthand: string = (node).nam in configuration.map ? configuration.map[(node).nam].shorthand : configuration.properties[(node).nam].shorthand; + + if (!map.has(shorthand)) { + + map.set(shorthand, new PropertyList()); + } + + (map.get(shorthand)).add(node); + } + + else { + + map.set((node).nam, node); + } + } + + const children: AstNode[] = []; + + for (let child of map.values()) { + + if (child instanceof PropertyList) { + + // @ts-ignore + children.push(...child); + } else { + + // @ts-ignore + children.push(child); + } + } + + // @ts-ignore + ast.chi = children.concat(ast.chi?.slice(k)) + + + /* + // @ts-ignore + + const properties: PropertyList = new PropertyList(); + + for (; k < j; k++) { + + // @ts-ignore + if ('Comment' == ast.chi[k].typ || 'Declaration' == ast.chi[k].typ) { + + // @ts-ignore + properties.add(ast.chi[k]); + continue; + } + + break; + } + + // @ts-ignore + ast.chi = [...properties].concat(ast.chi.slice(k)); + */ + + // + // @ts-ignore + // ast.chi.splice(0, k - 1, ...properties); + + + return ast; +} + + +function splitRule(buffer: string): string[] { + + const result: string[] = []; + + let str = ''; + + for (let i = 0; i < buffer.length; i++) { + + let chr = buffer.charAt(i); + + + if (chr == ',') { + + if (str !== '') { + + result.push(str); + str = ''; + } + + continue; + } + + str += chr; + + if (chr == '\\') { + + str += buffer.charAt(++i); + continue; + } + + if (chr == '"' || chr == "'") { + + let inStr = 1; + let k = i; + + while (++k < buffer.length) { + + chr = buffer.charAt(k); + str += chr; + + if (chr == '//') { + + str += buffer.charAt(++k); + continue; + } + + if (chr == buffer.charAt(i)) { + + break; + } + } + + continue; + } + + if (chr == '(' || chr == '[') { + + const open = chr; + const close = chr == '(' ? ')' : ']'; + + let inParens = 1; + + let k = i; + + while (++k < buffer.length) { + + chr = buffer.charAt(k); + + if (chr == '\\') { + + str += buffer.slice(k, k + 2); + k++; + continue; + } + + str += chr; + + if (chr == open) { + + inParens++; + } else if (chr == close) { + + inParens--; + } + + if (inParens == 0) { + + break; + } + } + + i = k; + continue; + } + } + + if (str !== '') { + + result.push(str); + } + + return result; +} + +function diff(n1: AstRule, n2: AstRule, options: TransformOptions = {}): null | { + result: AstRule, + node1: AstRule, + node2: AstRule +} { + + let node1 = n1; + let node2 = n2; + let exchanged = false; + + if (node1.chi.length > node2.chi.length) { + + const t = node1; + + node1 = node2; + node2 = t; + exchanged = true; + } + + let i: number = node1.chi.length; + let j: number = node2.chi.length; + + + if (i == 0 || j == 0) { + + // @ts-ignore + return null; + } + + node1 = {...node1, chi: node1.chi.slice()}; + node2 = {...node2, chi: node2.chi.slice()}; + + const intersect = []; + + while (i--) { + + if (node1.chi[i].typ == 'Comment') { + + continue; + } + + j = node2.chi.length; + + if (j == 0) { + + break; + } + + while (j--) { + + if (node2.chi[j].typ == 'Comment') { + + continue; + } + + if ((node1.chi[i]).nam == (node2.chi[j]).nam) { + + if (eq(node1.chi[i], node2.chi[j])) { + + intersect.push(node1.chi[i]); + + node1.chi.splice(i, 1); + node2.chi.splice(j, 1); + break; + } + } + } + } + + // @ts-ignore + const result = (intersect.length == 0 ? null : { + ...node1, + // @ts-ignore + sel: [...new Set([...(n1.raw || splitRule(n1.sel)).concat(n2.raw || splitRule(n2.sel))])].join(), + chi: intersect.reverse() + }); + + if (result == null || [n1, n2].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + render(curr, options).code.length, 0) <= [node1, node2, result].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + render(curr, options).code.length, 0)) { + + // @ts-ignore + return null; + } + + return {result, node1: exchanged ? node2 : node1, node2: exchanged ? node2 : node2}; +} diff --git a/src/parser/index.ts b/src/parser/index.ts index ae2f49e..3b8ec1e 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -1,2 +1,3 @@ -export * from './parse'; \ No newline at end of file +export * from './parse'; +export * from './deduplicate'; \ No newline at end of file diff --git a/src/parser/parse.ts b/src/parser/parse.ts index b1a2054..9b2de1c 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -1,220 +1,1650 @@ import { - AstAtRule, AstComment, AstDeclaration, AstNode, AstRule, - AstRuleStyleSheet, - ErrorDescription, - ParserOptions, Token + AstAtRule, AstComment, AstDeclaration, AstNode, AstRule, AstRuleList, + AstRuleStyleSheet, AtRuleToken, AttrToken, ColorToken, DashMatchToken, + ErrorDescription, FunctionToken, IncludesToken, Location, ParseResult, + ParserOptions, Position, PseudoClassFunctionToken, PseudoClassToken, StringToken, Token, TransformOptions, UrlToken } from "../@types"; -import {tokenize} from "./tokenize"; -import {PropertyList} from "./declaration"; -import {eq} from "./utils/eq"; +import { + isAtKeyword, + isDimension, + isFunction, + isHash, isHexColor, + isIdent, isIdentStart, isNewLine, + isNumber, + isPercentage, + isPseudo, isWhiteSpace, + parseDimension +} from "./utils"; +import {renderToken} from "../renderer"; +import {COLORS_NAMES} from "../renderer/utils"; +import {deduplicate, deduplicateRule, hasDeclaration} from "./deduplicate"; + +const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func']; -export function parse(css: string, opt: ParserOptions = {}): { ast: AstRuleStyleSheet; errors: ErrorDescription[] } { +export function parse(iterator: string, opt: ParserOptions = {}): ParseResult { const errors: ErrorDescription[] = []; const options: ParserOptions = { src: '', location: false, + compress: false, processImport: false, - deduplicate: false, removeEmpty: true, ...opt }; - if (css.length == 0) { + if (iterator.length == 0) { // @ts-ignore return null; } - // @ts-ignore - const ast: AstRuleStyleSheet = tokenize(css, errors, options); + let ind: number = -1; + let lin: number = 1; + let col: number = 0; + const tokens: Token[] = []; + const src: string = options.src; + const stack: Array = []; + const ast: AstRuleStyleSheet = { + typ: "StyleSheet", + chi: [] + } + + const position = { + ind: Math.max(ind, 0), + lin: lin, + col: Math.max(col, 1) + }; + + let value: string; + let buffer: string = ''; + let total: number = iterator.length; + let map: Map = new Map; + + let context: AstRuleList = ast; + + if (options.location) { - if (options.deduplicate) { + ast.loc = { + sta: { - deduplicate(ast); + ind: ind, + lin: lin, + col: col + }, + src: '' + } } - return {ast, errors}; -} + function getType(val: string): Token { -function diff(node1: AstRule, node2: AstRule) { + if (val === '') { - // @ts-ignore - return node1.chi.every((val: AstDeclaration | AstComment) => { + throw new Error('empty string?'); + } - if (val.typ == 'Comment') { + if (val == ':') { - return true; + return {typ: 'Colon'}; } - if (val.typ != 'Declaration') { + if (val == ')') { - return false; + return {typ: 'End-parens'}; } - return node2.chi.some(v => eq(v, val)) - }) -} + if (val == '(') { -export function deduplicate(ast: AstNode) { + return {typ: 'Start-parens'}; + } - // @ts-ignore - if (('chi' in ast) && ast.chi?.length > 0) { + if (val == '=') { - let i: number = 0; - let previous: AstNode; - let node: AstNode; - let nodeIndex: number; + return {typ: 'Delim', val}; + } + if (val == ';') { - // @ts-ignore - for (; i < ast.chi.length; i++) { + return {typ: 'Semi-colon'}; + } - // @ts-ignore - if (ast.chi[i].typ == 'Comment') { + if (val == ',') { - continue; + return {typ: 'Comma'}; + } + + if (val == '<') { + + return {typ: 'Lt'}; + } + + if (val == '>') { + + return {typ: 'Gt'}; + } + + if (isPseudo(val)) { + + return val.endsWith('(') ? { + typ: 'Pseudo-class-func', + val: val.slice(0, -1), + chi: [] + } + : { + typ: 'Pseudo-class', + val + } + } + + if (isAtKeyword(val)) { + + return { + typ: 'At-rule', + val: val.slice(1) } + } - // @ts-ignore - node = ast.chi[i]; + if (isFunction(val)) { - if (node.typ == 'AtRule' && (node).nam == 'font-face') { + val = val.slice(0, -1) - continue; + return { + typ: val == 'url' ? 'UrlFunc' : 'Func', + val, + chi: [] } + } - if (node.typ == 'AtRule' && (node).val == 'all') { + if (isNumber(val)) { + + return { + typ: 'Number', + val + } + } + + if (isDimension(val)) { + + return parseDimension(val); + } + + if (isPercentage(val)) { + + return { + typ: 'Perc', + val: val.slice(0, -1) + } + } + + if (val == 'currentColor') { + + return { + typ: 'Color', + val, + kin: 'lit' + } + } + + if (isIdent(val)) { + + return { + typ: 'Iden', + val + } + } + + if (val.charAt(0) == '#' && isHash(val)) { + + return { + typ: 'Hash', + val + } + } + + if ('"\''.includes(val.charAt(0))) { + + return { + typ: 'Unclosed-string', + val + } + } + + return { + typ: 'Literal', + val + } + } + + // consume and throw away + function consume(open: string, close: string) { + + let count: number = 1; + let chr: string; + + while (true) { + + chr = next(); + + if (chr == '\\') { + + if (next() === '') { + + break; + } - // @ts-ignore - ast.chi?.splice(i, 1, ...(node).chi); - i--; continue; + } else if (chr == '/' && peek() == '*') { + + next(); + + while (true) { + + chr = next(); + + if (chr === '') { + + break; + } + + if (chr == '*' && peek() == '/') { + + next(); + break; + } + } + + } else if (chr == close) { + + count--; + } else if (chr == open) { + + count++; } - // @ts-ignore - if (previous != null && 'chi' in previous && ('chi' in node)) { + if (chr === '' || count == 0) { + + break; + } + } + } + + function parseNode(tokens: Token[]) { + + let i: number = 0; + let loc: Location; + + for (i = 0; i < tokens.length; i++) { + + if (tokens[i].typ == 'Comment') { // @ts-ignore - if (previous.typ == node.typ) { + context.chi.push(tokens[i]); - let shouldMerge = true; + const position: Position = map.get(tokens[i]); - // @ts-ignore - let k = previous.chi.length; + loc = { + sta: position, + src + }; - while (k-- > 0) { + if (options.location) { - // @ts-ignore - if (previous.chi[k].typ == 'Comment') { + (tokens[i]).loc = loc + } + + } else if (tokens[i].typ != 'Whitespace') { + + break; + } + } + + tokens = tokens.slice(i); + + const delim: Token = tokens.pop(); + + while (['Whitespace', 'Bad-string', 'Bad-comment'].includes(tokens[tokens.length - 1]?.typ)) { + + tokens.pop(); + } + + if (tokens.length == 0) { + + return null; + } + + if (tokens[0]?.typ == 'At-rule') { + + const atRule: AtRuleToken = tokens.shift(); + const position: Position = map.get(atRule); + + if (atRule.val == 'charset' && position.ind > 0) { + + errors.push({action: 'drop', message: 'invalid @charset', location: {src, ...position}}); + return null; + } + + while (['Whitespace'].includes(tokens[0]?.typ)) { + + tokens.shift(); + } + + if (atRule.val == 'import') { + + // only @charset and @layer are accepted before @import + if (context.chi.length > 0) { + + let i: number = context.chi.length; + + while (i--) { + + const type = context.chi[i].typ; + + if (type == 'Comment') { continue; } - // @ts-ignore - shouldMerge = previous.chi[k].typ == 'Declaration'; + if (type != 'AtRule') { + + errors.push({action: 'drop', message: 'invalid @import', location: {src, ...position}}); + return null; + } + + const name: string = (context.chi[i]).nam; + + if (name != 'charset' && name != 'import' && name != 'layer') { + + errors.push({action: 'drop', message: 'invalid @import', location: {src, ...position}}); + return null; + } + break; } + } - if (shouldMerge) { + // @ts-ignore + if (tokens[0]?.typ != 'String' && tokens[0]?.typ != 'UrlFunc') { - // @ts-ignore - if ((node.typ == 'Rule' && (node).sel == (previous).sel) || - // @ts-ignore - (node.typ == 'AtRule') && (node).val == (previous).val) { + errors.push({action: 'drop', message: 'invalid @import', location: {src, ...position}}); + return null; + } - // @ts-ignore - node.chi.unshift(...previous.chi); - // @ts-ignore - ast.chi.splice(nodeIndex, 1); - i--; - previous = node; - nodeIndex = i; - continue; - } else if (node.typ == 'Rule') { + // @ts-ignore + if (tokens[0].typ == 'UrlFunc' && tokens[1]?.typ != 'Url-token' && tokens[1]?.typ != 'String') { - if (diff(node, previous) && diff(previous, node)) { + errors.push({action: 'drop', message: 'invalid @import', location: {src, ...position}}); + return null; + } + } - if (node.typ == 'Rule') { + if (atRule.val == 'import') { - (previous).sel += ',' + (node).sel; - } + // @ts-ignore + if (tokens[0].typ == 'UrlFunc' && tokens[1].typ == 'Url-token') { - // @ts-ignore - ast.chi.splice(i, 1); - i--; - // previous = node; - // nodeIndex = i; - } + tokens.shift(); + // const token: Token = tokens.shift(); + + // @ts-ignore + tokens[0].typ = 'String'; + // @ts-ignore + tokens[0].val = `"${tokens[0].val}"`; + + // tokens[0] = token; + } + } + + // https://www.w3.org/TR/css-nesting-1/#conditionals + // allowed nesting at-rules + // there must be a top level rule in the stack + + const node: AstAtRule = { + typ: 'AtRule', + nam: renderToken(atRule, {removeComments: true}), + val: tokens.reduce((acc: string, curr: Token, index: number, array: Token[]) => { + + if (curr.typ == 'Whitespace') { + + if (array[index + 1]?.typ == 'Start-parens' || + array[index - 1]?.typ == 'End-parens' || + array[index - 1]?.typ == 'Func') { + + return acc; } } - } - // @ts-ignore - if (previous != node) { + return acc + renderToken(curr, {removeComments: true}) + }, '') + } + + if (node.nam == 'import') { + + if (options.processImport) { // @ts-ignore - if (previous.chi.some(n => n.typ == 'Declaration')) { + let fileToken: Token = tokens[tokens[0].typ == 'UrlFunc' ? 1 : 0]; + + let file: string = fileToken.typ == 'String' ? fileToken.val.slice(1, -1) : fileToken.val; + + if (!file.startsWith('data:')) { - deduplicateRule(previous); - } else { - deduplicate(previous); } } } - previous = node; - nodeIndex = i; - } + if (delim.typ == 'Block-start') { - // @ts-ignore - if (node != null && ('chi' in node)) { + node.chi = []; + } + + loc = { + sta: position, + src + }; + + if (options.location) { + + node.loc = loc + } // @ts-ignore - if (node.chi.some(n => n.typ == 'Declaration')) { + context.chi.push(node); + + return delim.typ == 'Block-start' ? node : null; + + } else { + + // rule + if (delim.typ == 'Block-start') { + const position: Position = map.get(tokens[0]); + + if (context.typ == 'Rule') { + + if (tokens[0]?.typ == 'Iden') { + + errors.push({action: 'drop', message: 'invalid nesting rule', location: {src, ...position}}); + return null; + } + } + + const sel: string[] = parseTokens(tokens, {compress: options.compress}).map(curr => renderToken(curr, {compress: true})); + + const raw: string[] = [...new Set(sel.reduce((acc, curr) => { + + if (curr == ',') { + + acc.push(''); + } else { + + acc[acc.length - 1] += curr; + } + + return acc; + + }, ['']))]; + + const node: AstRule = { + + typ: 'Rule', + // @ts-ignore + sel: raw.join(','), + chi: [] + } + + Object.defineProperty(node, 'raw', {enumerable: false, get: () => raw}); + + loc = { + sta: position, + src + }; + + if (options.location) { + + node.loc = loc + } + + // @ts-ignore + context.chi.push(node); + return node; - deduplicateRule(node); } else { - deduplicate(node); - } - } - } + // declaration + // @ts-ignore + let name: Token[] = null; + // @ts-ignore + let value: Token[] = null; - return ast; -} + for (let i = 0; i < tokens.length; i++) { + + if (tokens[i].typ == 'Comment') { -export function deduplicateRule(ast: AstNode): AstNode { + continue; + } - if (!('chi' in ast) || ast.chi?.length == 0) { + if (tokens[i].typ == 'Colon') { - return ast; - } + name = tokens.slice(0, i); + value = parseTokens(tokens.slice(i + 1), {parseColor: true}); + } + } - // @ts-ignore - const j: number = ast.chi.length; - let k: number = 0; + if (name == null) { - const properties: PropertyList = new PropertyList(); + name = tokens; + } - for (; k < j; k++) { + const position: Position = map.get(name[0]); - // @ts-ignore - if ('Comment' == ast.chi[k].typ || 'Declaration' == ast.chi[k].typ) { + if (name.length > 0) { - // @ts-ignore - properties.add(ast.chi[k]); - continue; - } + for (let i = 1; i < name.length; i++) { + + if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') { - break; + errors.push({ + action: 'drop', + message: 'invalid declaration', + location: {src, ...position} + }); + return null; + } + } + } + + // if (name.length == 0) { + // + // errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); + // return null; + // } + + if (value == null) { + + errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); + return null; + } + + // let j: number = value.length + let i: number = 0; + let t: Token; + + if (value.length == 0) { + + errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); + return null; + } + + const node: AstDeclaration = { + + typ: 'Declaration', + // @ts-ignore + nam: renderToken(name.shift(), {removeComments: true}), + // @ts-ignore + val: value + } + + while (node.val[0]?.typ == 'Whitespace') { + + node.val.shift(); + } + + if (node.val.length == 0) { + + errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); + return null; + } + + loc = { + sta: position, + src + }; + + if (options.location) { + + node.loc = loc + } + + // @ts-ignore + context.chi.push(node); + return null; + } + } } - // @ts-ignore - ast.chi = [...properties].concat(ast.chi.slice(k)); + function peek(count: number = 1): string { - // @ts-ignore - // ast.chi.splice(0, k - 1, ...properties); + if (count == 1) { + + return iterator.charAt(ind + 1); + } + + return iterator.slice(ind + 1, ind + count + 1); + } + + function prev(count: number = 1) { + + if (count == 1) { + + return ind == 0 ? '' : iterator.charAt(ind - 1); + } + + return iterator.slice(ind - 1 - count, ind - 1); + } + + function next(count: number = 1) { + + let char: string = ''; + + while (count-- > 0 && ind < total) { + + const codepoint: number = iterator.charCodeAt(++ind); + + if (isNaN(codepoint)) { + + return char; + } + + char += iterator.charAt(ind); + + if (isNewLine(codepoint)) { + + lin++; + col = 0; + + } else { + + col++; + } + } + + return char; + } + + function pushToken(token: Token) { + + tokens.push(token); + + map.set(token, {...position}); + + position.ind = ind; + position.lin = lin; + position.col = col == 0 ? 1 : col; + // } + } + + function consumeWhiteSpace() { + + let count = 0; + + while (isWhiteSpace(iterator.charAt(count + ind + 1).charCodeAt(0))) { + + count++; + } + + next(count); + return count; + } + + function consumeString(quoteStr: '"' | "'") { + + const quote: string = quoteStr; + let value; + let hasNewLine: boolean = false; + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + buffer = ''; + } + + buffer += quoteStr; + + while (ind < total) { + + value = peek(); + + if (ind >= total) { + + pushToken({typ: hasNewLine ? 'Bad-string' : 'Unclosed-string', val: buffer}); + break; + } + + if (value == '\\') { + + // buffer += value; + + if (ind >= total) { + + // drop '\\' at the end + pushToken(getType(buffer)); + break; + } + + buffer += next(2); + continue; + } + + if (value == quote) { + + buffer += value; + pushToken({typ: hasNewLine ? 'Bad-string' : 'String', val: buffer}); + next(); + // i += value.length; + buffer = ''; + break; + } + + if (isNewLine(value.charCodeAt(0))) { + + hasNewLine = true; + } + + if (hasNewLine && value == ';') { + + pushToken({typ: 'Bad-string', val: buffer}); + buffer = ''; + break; + } + + buffer += value; + // i += value.length; + next(); + } + } + + while (ind < total) { + + value = next(); + + if (ind >= total) { + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + buffer = ''; + } + + break; + } + + if (isWhiteSpace(value.charCodeAt(0))) { + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + buffer = ''; + } + + let whitespace: string = value; + + while (ind < total) { + + value = next(); + + if (ind >= total) { + + break; + } + + if (!isWhiteSpace(value.charCodeAt(0))) { + + break; + } + + whitespace += value; + } + + pushToken({typ: 'Whitespace'}); + buffer = ''; + + if (ind >= total) { + + break; + } + } + + switch (value) { + + case '/': + + if (buffer.length > 0 && tokens.at(-1)?.typ == 'Whitespace') { + + pushToken(getType(buffer)); + buffer = ''; + + if (peek() != '*') { + + pushToken(getType(value)); + break; + } + } + + buffer += value; + + if (peek() == '*') { + + buffer += '*'; + // i++; + next(); + + while (ind < total) { + + value = next(); + + if (ind >= total) { + + pushToken({ + typ: 'Bad-comment', val: buffer + }); + + break; + } + + if (value == '\\') { + + buffer += value; + value = next(); + + if (ind >= total) { + + pushToken({ + typ: 'Bad-comment', + val: buffer + }); + + break; + } + + buffer += value; + continue; + } + + if (value == '*') { + + buffer += value; + value = next(); + + if (ind >= total) { + + pushToken({ + typ: 'Bad-comment', val: buffer + }); + + + break; + } + + buffer += value; + + if (value == '/') { + + pushToken({typ: 'Comment', val: buffer}); + buffer = ''; + break; + } + } else { + + buffer += value; + } + } + } + // else { + // + // pushToken(getType(buffer)); + // buffer = ''; + // } + + break; + + case '<': + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + buffer = ''; + } + + buffer += value; + value = next(); + + if (ind >= total) { + + break; + } + + if (peek(3) == '!--') { + + while (ind < total) { + + value = next(); + + if (ind >= total) { + + break; + } + + buffer += value; + + if (value == '>' && prev(2) == '--') { + + pushToken({ + typ: 'CDOCOMM', + val: buffer + }); + + + buffer = ''; + break; + } + } + } + + if (ind >= total) { + + pushToken({typ: 'BADCDO', val: buffer}); + buffer = ''; + } + + break; + + case '\\': + + value = next(); + + // EOF + if (ind + 1 >= total) { + + // end of stream ignore \\ + pushToken(getType(buffer)); + buffer = ''; + break; + } + + buffer += value; + break; + + case '"': + case "'": + + consumeString(value); + break; + + case '~': + case '|': + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + buffer = ''; + } + + buffer += value; + value = next(); + + if (ind >= total) { + + pushToken(getType(buffer)); + buffer = ''; + break; + } + + if (value == '=') { + + buffer += value; + + pushToken({ + typ: buffer[0] == '~' ? 'Includes' : 'Dash-matches', + val: buffer + }); + + buffer = ''; + break; + } + + pushToken(getType(buffer)); + buffer = value; + break; + + case '>': + + if (tokens[tokens.length - 1]?.typ == 'Whitespace') { + + tokens.pop(); + } + + pushToken({typ: 'Gt'}); + consumeWhiteSpace(); + break; + + case ':': + case ',': + case '=': + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + buffer = ''; + } + + if (value == ':' && ':' == peek()) { + + buffer += value + next(); + break; + } + + // if (value == ',' && tokens[tokens.length - 1]?.typ == 'Whitespace') { + // + // tokens.pop(); + // } + + pushToken(getType(value)); + buffer = ''; + + while (isWhiteSpace(peek().charCodeAt(0))) { + + next(); + } + + break; + + case ')': + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + buffer = ''; + } + + pushToken({typ: 'End-parens'}); + break; + + case '(': + + if (buffer.length == 0) { + + pushToken({typ: 'Start-parens'}); + } else { + + buffer += value; + pushToken(getType(buffer)); + buffer = ''; + + const token: Token = tokens[tokens.length - 1]; + + if (token.typ == 'UrlFunc' /* && token.chi == 'url' */) { + + // consume either string or url token + let whitespace = ''; + + value = peek(); + while (isWhiteSpace(value.charCodeAt(0))) { + + whitespace += value; + } + + if (whitespace.length > 0) { + + next(whitespace.length); + } + + value = peek(); + + if (value == '"' || value == "'") { + + consumeString(<'"' | "'">next()); + + let token = tokens[tokens.length - 1]; + + if (['String', 'Literal'].includes(token.typ) && /^(["']?)[a-zA-Z0-9_/-][a-zA-Z0-9_/:.-]+(\1)$/.test(token.val)) { + + if (token.typ == 'String') { + + token.val = token.val.slice(1, -1); + } + + // @ts-ignore + // token.typ = 'Url-token'; + } + break; + } else { + + buffer = ''; + + do { + + let cp: number = value.charCodeAt(0); + + // EOF - + if (cp == null) { + + pushToken({typ: 'Bad-url-token', val: buffer}) + break; + } + + // ')' + if (cp == 0x29 || cp == null) { + + if (buffer.length == 0) { + + pushToken({typ: 'Bad-url-token', val: ''}) + } else { + + pushToken({typ: 'Url-token', val: buffer}) + } + + if (cp != null) { + + pushToken(getType(next())); + } + + break; + } + + if (isWhiteSpace(cp)) { + + whitespace = next(); + + while (true) { + + value = peek(); + cp = value.charCodeAt(0); + + if (isWhiteSpace(cp)) { + + whitespace += value; + continue; + } + + break; + } + + if (cp == null || cp == 0x29) { + + continue; + } + + // bad url token + buffer += next(whitespace.length); + + do { + + value = peek(); + cp = value.charCodeAt(0); + + if (cp == null || cp == 0x29) { + + break; + } + + buffer += next(); + } + + while (true); + + pushToken({typ: 'Bad-url-token', val: buffer}); + continue; + } + + buffer += next(); + value = peek(); + } + + while (true); + + buffer = ''; + } + } + } + + break; + + case '[': + case ']': + case '{': + case '}': + case ';': + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + buffer = ''; + } + + pushToken(getBlockType(value)); + + let node = null; + + if (value == '{' || value == ';') { + + node = parseNode(tokens); + + if (node != null) { + + stack.push(node); + + // @ts-ignore + context = node; + } else if (value == '{') { + + // node == null + // consume and throw away until the closing '}' or EOF + consume('{', '}'); + } + + tokens.length = 0; + map.clear(); + + } else if (value == '}') { + + parseNode(tokens); + const previousNode = stack.pop(); + + // @ts-ignore + context = stack[stack.length - 1] || ast; + + // @ts-ignore + if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { + + context.chi.pop(); + } + else if (previousNode != null && previousNode != ast && options.compress) { + + // @ts-ignore + if (hasDeclaration(previousNode)) { + + deduplicateRule(previousNode); + } else { + + deduplicate(previousNode, options); + } + } + + tokens.length = 0; + map.clear(); + buffer = ''; + } + + break; + + case '!': + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + buffer = ''; + } + + const important: string = peek(9); + + if (important == 'important') { + + if (tokens[tokens.length - 1]?.typ == 'Whitespace') { + + tokens.pop(); + } + + pushToken({typ: 'Important'}); + next(9); + + buffer = ''; + break; + } + + buffer = '!'; + break; + + default: + + buffer += value; + break; + } + } + + if (buffer.length > 0) { + + pushToken(getType(buffer)); + } + + if (options.compress) { + + while (stack.length > 0) { + + const node: AstRuleList = stack.pop(); + + if (hasDeclaration(node)) { + + deduplicateRule(node, options); + } + + else { + + deduplicate(node, options); + } + } + + if (ast.chi.length > 0) { + + deduplicate(ast, options); + } + } + + return {ast, errors}; +} + +function parseTokens(tokens: Token[], options: { parseColor?: boolean, compress?: boolean } = {}): Token[] { + + for (let i = 0; i < tokens.length; i++) { + + const t = tokens[i]; + + if (t.typ == 'Whitespace' && ( + ( + i == 0 || + i + 1 == tokens.length || + ['Comma', 'Start-parens'].includes(tokens[i + 1].typ) || + (i > 0 && funcLike.includes(tokens[i - 1].typ)) + ) + )) { + + tokens.splice(i--, 1); + continue; + } + + if (t.typ == 'Colon') { + + const typ = tokens[i + 1]?.typ; + + if (typ != null) { + + if (typ == 'Func') { + + (tokens[i + 1]).val = ':' + (tokens[i + 1]).val; + tokens[i + 1].typ = 'Pseudo-class-func'; + } else if (typ == 'Iden') { + + (tokens[i + 1]).val = ':' + (tokens[i + 1]).val; + tokens[i + 1].typ = 'Pseudo-class'; + } + + if (typ == 'Func' || typ == 'Iden') { + + tokens.splice(i, 1); + i--; + continue; + } + } + } + + if (t.typ == 'Attr-start') { + + let k = i; + + let inAttr = 1; + + while (++k < tokens.length) { + + if (tokens[k].typ == 'Attr-end') { + + inAttr--; + } else if (tokens[k].typ == 'Attr-start') { + + inAttr++; + } + + if (inAttr == 0) { + + break; + } + } + + Object.assign(t, {typ: 'Attr', chi: tokens.splice(i + 1, k - i)}); + + // @ts-ignore + if ((t).chi.at(-1).typ == 'Attr-end') { + + // @ts-ignore + (t).chi.pop(); + + // @ts-ignore + if ((t).chi.length > 1) { + + /*(t).chi =*/ + // @ts-ignore + parseTokens(t.chi, options); + } + + // @ts-ignore + (t).chi.forEach(val => { + if (val.typ == 'String') { + + const slice: string = val.val.slice(1, -1); + + if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) { + + Object.assign(val, {typ: 'Iden', val: slice}) + } + + } + }) + } + + continue; + } + + if (funcLike.includes(t.typ)) { + + let parens = 1; + let k = i; + + while (++k < tokens.length) { + + if (tokens[k].typ == 'Colon') { + + const typ = tokens[k + 1]?.typ; + + if (typ != null) { + + if (typ == 'Iden') { + + tokens[k + 1].typ = 'Pseudo-class'; + (tokens[k + 1]).val = ':' + (tokens[k + 1]).val; + } else if (typ == 'Func') { + + tokens[k + 1].typ = 'Pseudo-class-func'; + (tokens[k + 1]).val = ':' + (tokens[k + 1]).val; + } + + if (typ == 'Func' || typ == 'Iden') { + + tokens.splice(k, 1); + k--; + continue; + } + } + } + + if (funcLike.includes(tokens[k].typ)) { + + parens++; + } else if (tokens[k].typ == 'End-parens') { + + parens--; + } + + if (parens == 0) { + + break; + } + } + + // @ts-ignore + t.chi = tokens.splice(i + 1, k - i); + + // @ts-ignore + if (t.chi.at(-1)?.typ == 'End-parens') { + + // @ts-ignore + t.chi.pop(); + } + + // @ts-ignore + if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { + + let isColor = true; + // @ts-ignore + for (const v of t.chi) { + + if (v.typ == 'Func' && v.val == 'var') { + + isColor = false; + break; + } + } + + if (!isColor) { + + continue; + } + + // @ts-ignore + t.typ = 'Color'; + // @ts-ignore + t.kin = t.val; + + // @ts-ignore + let m: number = t.chi.length; + let tok: Token; + + while (m-- > 0) { + + // @ts-ignore + if (t.chi[m].typ == 'Literal') { + + // @ts-ignore + if (t.chi[m + 1]?.typ == 'Whitespace') { + + // @ts-ignore + t.chi.splice(m + 1, 1); + } + + // @ts-ignore + if (t.chi[m - 1]?.typ == 'Whitespace') { + + // @ts-ignore + t.chi.splice(m - 1, 1); + m--; + } + } + } + } else if (t.typ == 'UrlFunc') { + + // @ts-ignore + if (t.chi[0]?.typ == 'String') { + + // @ts-ignore + const value = t.chi[0].val.slice(1, -1); + + if (/^[/%.a-zA-Z0-9_-]+$/.test(value)) { + + // @ts-ignore + t.chi[0].typ = 'Url-token'; + // @ts-ignore + t.chi[0].val = value; + } + } + } + + // @ts-ignore + if (t.chi.length > 0) { + + // @ts-ignore + parseTokens(t.chi, options); + + + if (t.typ == 'Pseudo-class-func' && (t).val == ':is' && options.compress) { + // + // console.debug({is: t}); + const count = (t).chi.filter(t => t.typ != 'Comment').length; + if (count == 1 || + (i == 0 && + (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1)) || + (tokens[i - 1]?.typ == 'Comma' && (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1)) + ) { + + // console.debug(tokens[i]); + tokens.splice(i, 1, ...(t).chi); + i = Math.max(0, i - t.chi.length); + } + + // tokens.splice(i, 1, ...t.chi); + } + } + + continue; + } + + if (options.parseColor) { + + if (t.typ == 'Iden') { + // named color + + const value = t.val.toLowerCase(); + + if (COLORS_NAMES[value] != null) { + + Object.assign(t, { + typ: 'Color', + val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, + kin: 'hex' + }); + } + + continue; + } + + if (t.typ == 'Hash' && isHexColor(t.val)) { + + // hex color + + // @ts-ignore + t.typ = 'Color'; + // @ts-ignore + (t).kin = 'hex' + continue; + } + } + } + + return tokens; +} + +function getBlockType(chr: '{' | '}' | '[' | ']' | ';'): Token { + + if (chr == ';') { + + return {typ: 'Semi-colon'}; + } + + if (chr == '{') { + + return {typ: 'Block-start'}; + } + + if (chr == '}') { + + return {typ: 'Block-end'}; + } + + if (chr == '[') { + + return {typ: 'Attr-start'}; + } + + if (chr == ']') { + + return {typ: 'Attr-end'}; + } + + throw new Error(`unhandled token: '${chr}'`); +} - return ast; -} \ No newline at end of file diff --git a/src/parser/tokenize.ts b/src/parser/tokenize.ts deleted file mode 100644 index d875c74..0000000 --- a/src/parser/tokenize.ts +++ /dev/null @@ -1,1544 +0,0 @@ -import { - isAtKeyword, isDimension, isFunction, isHash, isHexColor, - isIdent, isNewLine, isNumber, isPercentage, - isPseudo, isWhiteSpace, parseDimension -} from "./utils"; -import { - AstAtRule, - AstComment, - AstDeclaration, - AstNode, - AstRule, - AstRuleList, - AstRuleStyleSheet, - AtRuleToken, ColorToken, - DashMatchToken, - ErrorDescription, - FunctionToken, IdentToken, IncludesToken, - Location, - NodeParseEventsMap, ParserOptions, - Position, - PseudoClassToken, StringToken, - Token, UrlToken -} from "../@types"; -import {renderToken} from "../renderer"; -import {COLORS_NAMES} from "../renderer/utils"; - -export function tokenize(iterator: string, errors: ErrorDescription[], options: ParserOptions): AstRuleStyleSheet { - - const tokens: Token[] = []; - const src: string = options.src; - const stack: Array = []; - const root: AstRuleStyleSheet = { - typ: "StyleSheet", - chi: [] - } - - const position = { - ind: 0, - lin: 1, - col: 1 - }; - - let value: string; - let buffer: string = ''; - let ind: number = -1; - let lin: number = 1; - let col: number = 0; - let total: number = iterator.length; - let map: Map = new Map; - - let context: AstRuleList = root; - - if (options.location) { - - root.loc = { - - sta: { - - ind: 0, - lin: 1, - col: 1 - }, - src: '' - } - } - - function getType(val: string): Token { - - if (val === '') { - - throw new Error('empty string?'); - } - - if (val == ':') { - - return {typ: 'Colon'}; - } - - if (val == ')') { - - return {typ: 'End-parens'}; - } - - if (val == '(') { - - return {typ: 'Start-parens'}; - } - - if (val == '=') { - - return {typ: 'Delim', val}; - } - if (val == ';') { - - return {typ: 'Semi-colon'}; - } - - if (val == ',') { - - return {typ: 'Comma'}; - } - - if (val == '<') { - - return {typ: 'Lt'}; - } - - if (val == '>') { - - return {typ: 'Gt'}; - } - - if (isPseudo(val)) { - - return { - typ: val.endsWith('(') ? 'Pseudo-class-func' : 'Pseudo-class', - val - } - } - - if (isAtKeyword(val)) { - - return { - typ: 'At-rule', - val: val.slice(1) - } - } - - if (isFunction(val)) { - - val = val.slice(0, -1) - - return { - typ: val == 'url' ? 'UrlFunc' : 'Func', - val, - chi: [] - } - } - - if (isNumber(val)) { - - return { - typ: 'Number', - val - } - } - - if (isDimension(val)) { - - return parseDimension(val); - } - - if (isPercentage(val)) { - - return { - typ: 'Perc', - val: val.slice(0, -1) - } - } - - if (val == 'currentColor') { - - return { - typ: 'Color', - val, - kin: 'lit' - } - } - - if (isIdent(val)) { - - return { - typ: 'Iden', - val - } - } - - if (val.charAt(0) == '#' && isHash(val)) { - - return { - typ: 'Hash', - val - } - } - - if ('"\''.includes(val.charAt(0))) { - - return { - typ: 'Unclosed-string', - val - } - } - - return { - typ: 'Literal', - val - } - } - - // consume and throw away - function consume(open: string, close: string) { - - let count: number = 1; - let chr: string; - - while (true) { - - chr = next(); - - if (chr == '\\') { - - if (next() === '') { - - break; - } - - continue; - } else if (chr == '/' && peek() == '*') { - - next(); - - while (true) { - - chr = next(); - - if (chr === '') { - - break; - } - - if (chr == '*' && peek() == '/') { - - next(); - break; - } - } - - } else if (chr == close) { - - count--; - } else if (chr == open) { - - count++; - } - - if (chr === '' || count == 0) { - - break; - } - } - } - - function parseNode(tokens: Token[]) { - - let i: number = 0; - let loc: Location; - - for (i = 0; i < tokens.length; i++) { - - if (tokens[i].typ == 'Comment') { - - // @ts-ignore - context.chi.push(tokens[i]); - - const position: Position = map.get(tokens[i]); - - loc = { - sta: position, - src - }; - - if (options.location) { - - (tokens[i]).loc = loc - } - - } else if (tokens[i].typ != 'Whitespace') { - - break; - } - } - - tokens = tokens.slice(i); - - const delim: Token = tokens.pop(); - - while (['Whitespace', 'Bad-string', 'Bad-comment'].includes(tokens[tokens.length - 1]?.typ)) { - - tokens.pop(); - } - - if (tokens.length == 0) { - - return null; - } - - if (tokens[0]?.typ == 'At-rule') { - - const atRule: AtRuleToken = tokens.shift(); - const position: Position = map.get(atRule); - - if (atRule.val == 'charset' && position.ind > 0) { - - errors.push({action: 'drop', message: 'invalid @charset', location: {src, ...position}}); - return null; - } - - while (['Whitespace'].includes(tokens[0]?.typ)) { - - tokens.shift(); - } - - if (atRule.val == 'import') { - - // only @charset and @layer are accepted before @import - if (context.chi.length > 0) { - - let i: number = context.chi.length; - - while (i--) { - - const type = context.chi[i].typ; - - if (type == 'Comment') { - - continue; - } - - if (type != 'AtRule') { - - errors.push({action: 'drop', message: 'invalid @import', location: {src, ...position}}); - return null; - } - - const name: string = (context.chi[i]).nam; - - if (name != 'charset' && name != 'import' && name != 'layer') { - - errors.push({action: 'drop', message: 'invalid @import', location: {src, ...position}}); - return null; - } - - break; - } - } - - // @ts-ignore - if (tokens[0]?.typ != 'String' && tokens[0]?.typ != 'UrlFunc') { - - errors.push({action: 'drop', message: 'invalid @import', location: {src, ...position}}); - return null; - } - - // @ts-ignore - if (tokens[0].typ == 'UrlFunc' && tokens[1]?.typ != 'Url-token' && tokens[1]?.typ != 'String') { - - errors.push({action: 'drop', message: 'invalid @import', location: {src, ...position}}); - return null; - } - } - - if (atRule.val == 'import') { - - // @ts-ignore - if (tokens[0].typ == 'UrlFunc' && tokens[1].typ == 'Url-token') { - - tokens.shift(); - // const token: Token = tokens.shift(); - - // @ts-ignore - tokens[0].typ = 'String'; - // @ts-ignore - tokens[0].val = `"${tokens[0].val}"`; - - // tokens[0] = token; - } - } - - // https://www.w3.org/TR/css-nesting-1/#conditionals - // allowed nesting at-rules - // there must be a top level rule in the stack - - const node: AstAtRule = { - typ: 'AtRule', - nam: renderToken(atRule, {removeComments: true}), - val: tokens.reduce((acc: string, curr: Token, index: number, array: Token[]) => { - - if (curr.typ == 'Whitespace') { - - if (array[index + 1]?.typ == 'Start-parens' || - array[index - 1]?.typ == 'End-parens' || - array[index - 1]?.typ == 'Func') { - - return acc; - } - } - - return acc + renderToken(curr, {removeComments: true}) - }, '') - } - - if (node.nam == 'import') { - - if (options.processImport) { - - // @ts-ignore - let fileToken: Token = tokens[tokens[0].typ == 'UrlFunc' ? 1 : 0]; - - let file: string = fileToken.typ == 'String' ? fileToken.val.slice(1, -1) : fileToken.val; - - if (!file.startsWith('data:')) { - - - } - } - } - - if (delim.typ == 'Block-start') { - - node.chi = []; - } - - loc = { - sta: position, - src - }; - - if (options.location) { - - node.loc = loc - } - - // @ts-ignore - context.chi.push(node); - - return delim.typ == 'Block-start' ? node : null; - - } else { - - // rule - if (delim.typ == 'Block-start') { - - let inAttr: number = 0; - - const position: Position = map.get(tokens[0]); - - if (context.typ == 'Rule') { - - if (tokens[0]?.typ == 'Iden') { - - errors.push({action: 'drop', message: 'invalid nesting rule', location: {src, ...position}}); - return null; - } - } - - const node: AstRule = { - - typ: 'Rule', - // @ts-ignore - sel: tokens.reduce((acc: Token[][], curr: Token) => { - - if (acc[acc.length - 1].length == 0 && curr.typ == 'Whitespace') { - - return acc; - } - - if (inAttr > 0 && curr.typ == 'String') { - - const ident: string = curr.val.slice(1, -1); - - if (isIdent(ident)) { - - // @ts-ignore - curr.typ = 'Iden'; - curr.val = ident; - } - } - - if (curr.typ == 'Attr-start') { - - inAttr++; - } else if (curr.typ == 'Attr-end') { - - inAttr--; - } - - if (inAttr == 0 && curr.typ == "Comma") { - - acc.push([]); - } else { - - acc[acc.length - 1].push(curr); - } - - return acc; - }, >>[[]]).map(part => part.reduce((acc: string, p: Token, index: number, array: Token[]) => { - - if (p.typ == 'Whitespace') { - - if (array[index + 1]?.typ == 'Start-parens' || - array[index - 1]?.typ == 'End-parens') { - - return acc; - } - } - - return acc + renderToken(p, {removeComments: true}) - }, '')).join(), - chi: [] - } - - loc = { - sta: position, - src - }; - - if (options.location) { - - node.loc = loc - } - - // @ts-ignore - context.chi.push(node); - return node; - - } else { - - // declaration - // @ts-ignore - let name: Token[] = null; - // @ts-ignore - let value: Token[] = null; - - for (let i = 0; i < tokens.length; i++) { - - if (tokens[i].typ == 'Comment') { - - continue; - } - - if (tokens[i].typ == 'Colon') { - - name = tokens.slice(0, i); - value = tokens.slice(i + 1); - } else if (['Func', 'Pseudo-class'].includes(tokens[i].typ) && (tokens[i]).val.startsWith(':')) { - - (tokens[i]).val = (tokens[i]).val.slice(1); - - if (tokens[i].typ == 'Pseudo-class') { - - tokens[i].typ = 'Iden'; - } - - name = tokens.slice(0, i); - value = tokens.slice(i); - } - } - - if (name == null) { - - name = tokens; - } - - const position: Position = map.get(name[0]); - // const rawName: string = (name.shift())?.val; - - if (name.length > 0) { - - for (let i = 1; i < name.length; i++) { - - if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') { - - errors.push({ - action: 'drop', - message: 'invalid declaration', - location: {src, ...position} - }); - return null; - } - } - } - - // if (name.length == 0) { - // - // errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); - // return null; - // } - - if (value == null) { - - errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); - return null; - } - - // let j: number = value.length - let i: number = 0; - let t: Token; - - for (; i < value.length; i++) { - - t = value[i]; - - if (t.typ == 'Iden') { - // named color - - const value = t.val.toLowerCase(); - - if (COLORS_NAMES[value] != null) { - - Object.assign(t, { - typ: 'Color', - val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, - kin: 'hex' - }); - } - - continue; - } - - if (t.typ == 'Hash' && isHexColor(t.val)) { - - // hex color - - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - (t).kin = 'hex' - continue; - } - - if (t.typ == 'Func' || t.typ == 'UrlFunc') { - - // func color - let parens: number = 1; - let k: number = i; - let p: Token; - let j: number = value.length; - let isScalar: boolean = true; - - - while (++k < j) { - - switch (value[k].typ) { - - case 'Start-parens': - case 'Func': - case 'UrlFunc': - parens++; - isScalar = false; - break; - - case 'End-parens': - - parens--; - break; - } - - if (parens == 0) { - - break; - } - } - - t.chi = value.splice(i + 1, k - i); - - if (t.chi.at(-1).typ == 'End-parens') { - - t.chi.pop(); - } - - if (isScalar) { - - if (['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { - - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = t.val; - - let i: number = t.chi.length; - let tok: Token; - - while (i-- > 0) { - - if (t.chi[i].typ == 'Literal') { - - if (t.chi[i + 1]?.typ == 'Whitespace') { - - t.chi.splice(i + 1, 1); - } - - if (t.chi[i - 1]?.typ == 'Whitespace') { - - t.chi.splice(i - 1, 1); - i--; - } - } - } - } - - else if (t.typ == 'UrlFunc') { - - if(t.chi[0]?.typ == 'String') { - - const value = t.chi[0].val.slice(1, -1); - - if (/^[/%.a-zA-Z0-9_-]+$/.test(value)) { - - t.chi[0].typ = 'Url-token'; - t.chi[0].val = value; - } - } - } - } - - continue; - } - - if (t.typ == 'Whitespace' || t.typ == 'Comment') { - - value.slice(i, 1); - } - } - - if (value.length == 0) { - - errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); - return null; - } - - const node: AstDeclaration = { - - typ: 'Declaration', - // @ts-ignore - nam: renderToken(name.shift(), {removeComments: true}), - // @ts-ignore - val: value - } - - while (node.val[0]?.typ == 'Whitespace') { - - node.val.shift(); - } - - if (node.val.length == 0) { - - errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); - return null; - } - - loc = { - sta: position, - src - }; - - if (options.location) { - - node.loc = loc - } - - // @ts-ignore - context.chi.push(node); - return null; - } - } - } - - function peek(count: number = 1): string { - - if (count == 1) { - - return iterator.charAt(ind + 1); - } - - return iterator.slice(ind + 1, ind + count + 1); - } - - function prev(count: number = 1) { - - if (count == 1) { - - return ind == 0 ? '' : iterator.charAt(ind - 1); - } - - return iterator.slice(ind - 1 - count, ind - 1); - } - - function next(count: number = 1) { - - let char: string = ''; - let offset: number; - - while (count-- > 0 && ind < total) { - - const codepoint: number = iterator.charCodeAt(++ind); - - if (codepoint == null) { - - return char; - } - - // if (codepoint < 0x80) { - - char += iterator.charAt(ind); - // } - // else { - // - // const chr: string = String.fromCodePoint(codepoint); - // - // ind += chr.length - 1; - // char += chr; - // } - - // ind += codepoint < 256 ? 1 : String.fromCodePoint(codepoint).length; - - if (isNewLine(codepoint)) { - - lin++; - col = 0; - - // \r\n - // if (codepoint == 0xd && iterator.charCodeAt(i + 1) == 0xa) { - - // offset++; - // ind++; - // } - - } else { - - col++; - } - - // ind++; - // i += offset; - } - - return char; - } - - function pushToken(token: Token) { - - tokens.push(token); - - map.set(token, {...position}); - - position.ind = ind; - position.lin = lin; - position.col = col == 0 ? 1 : col; - // } - } - - function consumeWhiteSpace() { - - let count = 0; - - while (isWhiteSpace(iterator.charAt(count + ind + 1).charCodeAt(0))) { - - count++; - } - - next(count); - return count; - } - - function consumeString(quoteStr: '"' | "'") { - - const quote: string = quoteStr; - let value; - let hasNewLine: boolean = false; - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - buffer = ''; - } - - buffer += quoteStr; - - while (ind < total) { - - value = peek(); - - if (ind >= total) { - - pushToken({typ: hasNewLine ? 'Bad-string' : 'Unclosed-string', val: buffer}); - break; - } - - if (value == '\\') { - - // buffer += value; - - if (ind >= total) { - - // drop '\\' at the end - pushToken(getType(buffer)); - - - break; - } - - buffer += next(2); - continue; - } - - if (value == quote) { - - buffer += value; - pushToken({typ: hasNewLine ? 'Bad-string' : 'String', val: buffer}); - next(); - // i += value.length; - buffer = ''; - break; - } - - if (isNewLine(value.charCodeAt(0))) { - - hasNewLine = true; - } - - if (hasNewLine && value == ';') { - - pushToken({typ: 'Bad-string', val: buffer}); - buffer = ''; - break; - } - - buffer += value; - // i += value.length; - next(); - } - } - - while (ind < total) { - - value = next(); - - if (ind >= total) { - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - buffer = ''; - } - - break; - } - - if (isWhiteSpace(value.charCodeAt(0))) { - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - buffer = ''; - } - - let whitespace: string = value; - - while (ind < total) { - - value = next(); - - if (ind >= total) { - - break; - } - - if (!isWhiteSpace(value.charCodeAt(0))) { - - break; - } - - whitespace += value; - } - - pushToken({typ: 'Whitespace'}); - buffer = ''; - - if (ind >= total) { - - break; - } - } - - switch (value) { - - case '/': - - if (buffer.length > 0 && tokens.at(-1)?.typ == 'Whitespace') { - - pushToken(getType(buffer)); - buffer = ''; - - if (peek() != '*') { - - pushToken(getType(value)); - break; - } - } - - buffer += value; - - if (peek() == '*') { - - buffer += '*'; - // i++; - next(); - - while (ind < total) { - - value = next(); - - if (ind >= total) { - - pushToken({ - typ: 'Bad-comment', val: buffer - }); - - break; - } - - if (value == '\\') { - - buffer += value; - value = next(); - - if (ind >= total) { - - pushToken({ - typ: 'Bad-comment', - val: buffer - }); - - break; - } - - buffer += value; - continue; - } - - if (value == '*') { - - buffer += value; - value = next(); - - if (ind >= total) { - - pushToken({ - typ: 'Bad-comment', val: buffer - }); - - - break; - } - - buffer += value; - - if (value == '/') { - - pushToken({typ: 'Comment', val: buffer}); - buffer = ''; - break; - } - } else { - - buffer += value; - } - } - } - // else { - // - // pushToken(getType(buffer)); - // buffer = ''; - // } - - break; - - case '<': - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - buffer = ''; - } - - buffer += value; - value = next(); - - if (ind >= total) { - - break; - } - - if (peek(3) == '!--') { - - while (ind < total) { - - value = next(); - - if (ind >= total) { - - break; - } - - buffer += value; - - if (value == '>' && prev(2) == '--') { - - pushToken({ - typ: 'CDOCOMM', - val: buffer - }); - - - buffer = ''; - break; - } - } - } - - if (ind >= total) { - - pushToken({typ: 'BADCDO', val: buffer}); - buffer = ''; - } - - break; - - case '\\': - - value = next(); - - // EOF - if (ind + 1 >= total) { - - // end of stream ignore \\ - pushToken(getType(buffer)); - buffer = ''; - break; - } - - buffer += value; - break; - - case '"': - case "'": - - consumeString(value); - break; - - case '~': - case '|': - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - buffer = ''; - } - - buffer += value; - value = next(); - - if (ind >= total) { - - pushToken(getType(buffer)); - buffer = ''; - break; - } - - if (value == '=') { - - buffer += value; - - pushToken({ - typ: buffer[0] == '~' ? 'Includes' : 'Dash-matches', - val: buffer - }); - - buffer = ''; - break; - } - - pushToken(getType(buffer)); - buffer = value; - break; - - case '>': - - if (tokens[tokens.length - 1]?.typ == 'Whitespace') { - - tokens.pop(); - } - - pushToken({typ: 'Gt'}); - consumeWhiteSpace(); - break; - - case ':': - case ',': - case '=': - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - buffer = ''; - } - - if (value == ':' && isIdent(peek())) { - - buffer += value; - break; - } - - // if (value == ',' && tokens[tokens.length - 1]?.typ == 'Whitespace') { - // - // tokens.pop(); - // } - - pushToken(getType(value)); - buffer = ''; - - while (isWhiteSpace(peek().charCodeAt(0))) { - - next(); - } - - break; - - case ')': - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - buffer = ''; - } - - pushToken({typ: 'End-parens'}); - break; - - case '(': - - if (buffer.length == 0) { - - pushToken({typ: 'Start-parens'}); - } else { - - buffer += value; - pushToken(getType(buffer)); - buffer = ''; - - const token: Token = tokens[tokens.length - 1]; - - if (token.typ == 'UrlFunc' /* && token.val == 'url' */) { - - // consume either string or url token - let whitespace = ''; - - value = peek(); - while (isWhiteSpace(value.charCodeAt(0))) { - - whitespace += value; - } - - if (whitespace.length > 0) { - - next(whitespace.length); - } - - value = peek(); - - if (value == '"' || value == "'") { - - consumeString(<'"' | "'">next()); - - let token = tokens[tokens.length - 1]; - - if (['String', 'Literal'].includes(token.typ) && /^(["']?)[a-zA-Z0-9_/-][a-zA-Z0-9_/:.-]+(\1)$/.test(token.val)) { - - if (token.typ == 'String') { - - token.val = token.val.slice(1, -1); - } - - // @ts-ignore - // token.typ = 'Url-token'; - } - break; - } else { - - buffer = ''; - - do { - - let cp: number = value.charCodeAt(0); - - // EOF - - if (cp == null) { - - pushToken({typ: 'Bad-url-token', val: buffer}) - break; - } - - // ')' - if (cp == 0x29 || cp == null) { - - if (buffer.length == 0) { - - pushToken({typ: 'Bad-url-token', val: ''}) - } else { - - pushToken({typ: 'Url-token', val: buffer}) - } - - if (cp != null) { - - pushToken(getType(next())); - } - - break; - } - - if (isWhiteSpace(cp)) { - - whitespace = next(); - - while (true) { - - value = peek(); - cp = value.charCodeAt(0); - - if (isWhiteSpace(cp)) { - - whitespace += value; - continue; - } - - break; - } - - if (cp == null || cp == 0x29) { - - continue; - } - - // bad url token - buffer += next(whitespace.length); - - do { - - value = peek(); - cp = value.charCodeAt(0); - - if (cp == null || cp == 0x29) { - - break; - } - - buffer += next(); - } - - while (true); - - pushToken({typ: 'Bad-url-token', val: buffer}); - continue; - } - - buffer += next(); - value = peek(); - } - - while (true); - - buffer = ''; - } - } - } - - break; - - case '[': - case ']': - case '{': - case '}': - case ';': - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - buffer = ''; - } - - pushToken(getBlockType(value)); - - let node = null; - - if (value == '{' || value == ';') { - - node = parseNode(tokens); - - if (node != null) { - - stack.push(node); - - // @ts-ignore - context = node; - } else if (value == '{') { - - // node == null - // consume and throw away until the closing '}' or EOF - consume('{', '}'); - } - - tokens.length = 0; - map.clear(); - - } else if (value == '}') { - - node = parseNode(tokens); - const previousNode = stack.pop(); - - // @ts-ignore - context = stack[stack.length - 1] || root; - - // if (options.location && context != root) { - - // @ts-ignore - // context.loc.end = {ind, lin, col: col == 0 ? 1 : col} - // } - - // @ts-ignore - if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { - - context.chi.pop(); - } - - tokens.length = 0; - map.clear(); - buffer = ''; - } - - // @ts-ignore - // if (node != null && options.location && ['}', ';'].includes(value) && context.chi[context.chi.length - 1].loc.end == null) { - - // @ts-ignore - // context.chi[context.chi.length - 1].loc.end = {ind, lin, col}; - // } - - break; - - case '!': - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - buffer = ''; - } - - const important: string = peek(9); - - if (important == 'important') { - - if (tokens[tokens.length - 1]?.typ == 'Whitespace') { - - tokens.pop(); - } - - pushToken({typ: 'Important'}); - next(9); - - buffer = ''; - break; - } - - buffer = '!'; - break; - - default: - - buffer += value; - break; - } - } - - if (buffer.length > 0) { - - pushToken(getType(buffer)); - } - - if (tokens.length > 0) { - - parseNode(tokens); - } - - // pushToken({typ: 'EOF'}); - // - // if (col == 0) { - // - // col = 1; - // } - - // if (options.location) { - // - // // @ts-ignore - // root.loc.end = {ind, lin, col}; - // - // for (const context of stack) { - // - // // @ts-ignore - // context.loc.end = {ind, lin, col}; - // } - // } - - return root; -} - -function getBlockType(chr: '{' | '}' | '[' | ']' | ';'): Token { - - if (chr == ';') { - - return {typ: 'Semi-colon'}; - } - - if (chr == '{') { - - return {typ: 'Block-start'}; - } - - if (chr == '}') { - - return {typ: 'Block-end'}; - } - - if (chr == '[') { - - return {typ: 'Attr-start'}; - } - - if (chr == ']') { - - return {typ: 'Attr-end'}; - } - - throw new Error(`unhandled token: '${chr}'`); -} \ No newline at end of file diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 2e0e9a9..4838d39 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -4,8 +4,8 @@ import { AstDeclaration, AstNode, AstRule, - AstRuleStyleSheet, ColorToken, DimensionToken, - RenderOptions, + AstRuleStyleSheet, AttrToken, ColorToken, DimensionToken, + RenderOptions, RenderResult, Token } from "../@types"; import {cmyk2hex, hsl2Hex, hwb2hex, NAMES_COLORS, rgb2Hex} from "./utils"; @@ -13,7 +13,7 @@ import {isLength} from "../parser/utils"; const indents: string[] = []; -export function render(data: AstNode, opt: RenderOptions = {}) { +export function render(data: AstNode, opt: RenderOptions = {}): RenderResult { const options = Object.assign(opt.compress ? { indent: '', @@ -152,7 +152,7 @@ function doRender(data: AstNode, options: RenderOptions, reducer: Function, leve if (data.typ == 'AtRule') { - return `@${(data).nam} ${(data).val ? (data).val + options.indent : ''}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}` + return `@${(data).nam}${(data).val ? ' ' + (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 + `}` @@ -161,7 +161,7 @@ function doRender(data: AstNode, options: RenderOptions, reducer: Function, leve return ''; } -export function renderToken(token: Token, options: RenderOptions = {}) { +export function renderToken(token: Token, options: RenderOptions = {}): string { switch (token.typ) { @@ -219,9 +219,10 @@ export function renderToken(token: Token, options: RenderOptions = {}) { case 'Func': case 'UrlFunc': + case 'Pseudo-class-func': // @ts-ignore - return token.val + '(' + token.chi.reduce((acc: string, curr: Token) => { + return (options.compress && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) : token.val) + '(' + token.chi.reduce((acc: string, curr: Token) => { if (options.removeComments && curr.typ == 'Comment') { @@ -274,6 +275,10 @@ export function renderToken(token: Token, options: RenderOptions = {}) { case 'Important': return '!important'; + case 'Attr': + + return '[' + (token).chi.reduce((acc, curr) => acc + renderToken(curr, options), '') + ']'; + case 'Time': case 'Frequency': case 'Angle': @@ -360,12 +365,11 @@ export function renderToken(token: Token, options: RenderOptions = {}) { case 'Hash': case 'Pseudo-class': - case 'Pseudo-class-func': case 'Literal': case 'String': case 'Iden': case 'Delim': - return token.val; + return options.compress && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : token.val; } throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`); diff --git a/src/renderer/utils/color.ts b/src/renderer/utils/color.ts index 2565b3c..e40963c 100644 --- a/src/renderer/utils/color.ts +++ b/src/renderer/utils/color.ts @@ -322,6 +322,7 @@ export function rgb2Hex(token: ColorToken) { // console.debug({token}) } + // @ts-ignore value += Math.round(t.typ == 'Perc' ? 255 * t.val / 100 : t.val).toString(16).padStart(2, '0') } diff --git a/src/transform.ts b/src/transform.ts new file mode 100644 index 0000000..b64ca92 --- /dev/null +++ b/src/transform.ts @@ -0,0 +1,31 @@ +import {ParseResult, RenderResult, TransformOptions, TransformResult} from "./@types"; +import {parse} from "./parser"; +import {render} from "./renderer"; + +export function transform(css: string, options: TransformOptions = {}): TransformResult { + + options = {compress: true, removeEmpty: true, ...options}; + + const startTime: number = performance.now(); + const parseResult: ParseResult = parse(css, options); + + if (parseResult == null) { + + // @ts-ignore + return null; + } + + const renderTime: number = performance.now(); + const rendered: RenderResult = render(parseResult.ast, options); + const endTime: number = performance.now(); + + return { + ...parseResult, ...rendered, performance: { + bytesIn: css.length, + bytesOut: rendered.code.length, + parse: `${(renderTime - startTime).toFixed(2)}ms`, + render: `${(endTime - renderTime).toFixed(2)}ms`, + total: `${(endTime - startTime).toFixed(2)}ms` + } + }; +} diff --git a/src/worker.ts b/src/worker.ts new file mode 100644 index 0000000..02541e1 --- /dev/null +++ b/src/worker.ts @@ -0,0 +1,57 @@ +import {TokenStream, TransformOptions} from "./@types"; +import {transform} from "./transform"; +import {stream} from "./parser/stream"; +import {node} from "@tbela99/workerize"; + +export async function worker(css: string, options: TransformOptions = {}, maxSize: number = 10000) { + + if (css.length < maxSize) { + + return transform(css, options); + } + + let token = null; + + const queue: Array<{token: TokenStream, index: number}> = []; + const workers = []; + const concurrent = 10; + let active = 0; + let index = 0; + + + function enQueue(token: TokenStream, index: number) { + + queue.push({token, index}); + + // + if (workers.length + 1 < concurrent) { + + } + } + + function start() { + + } + + for (const part of stream(css)) { + + if (token == null) { + + token = part; + } else { + + token.buffer += part.buffer; + } + + if (token.buffer.length >= maxSize) { + + enQueue(token, index++); + token = null; + } + } + + if (token != null) { + + enQueue(token, index); + } +} \ No newline at end of file diff --git a/test/files/json/smalli.json b/test/files/json/smalli.json index 06b9cad..f4f04f6 100644 --- a/test/files/json/smalli.json +++ b/test/files/json/smalli.json @@ -16,7 +16,7 @@ }, { "typ": "Rule", - "sel": "div>:is(.inert),:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]", + "sel": "div>.inert,:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]", "chi": [ { "typ": "Declaration", diff --git a/test/js/block.test.mjs b/test/js/block.test.mjs deleted file mode 100644 index 1d762ab..0000000 --- a/test/js/block.test.mjs +++ /dev/null @@ -1,4002 +0,0 @@ -/* generate from ./test/specs/block.test.ts */ -import { readFile } from 'fs/promises'; -import { dirname } from 'path'; - -var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function t(e){throw new Error('Could not dynamically require "'+e+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var o=function(){function e(n,o,r){function i(a,c){if(!o[a]){if(!n[a]){if(!c&&t)return t(a);if(s)return s(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var f=o[a]={exports:{}};n[a][0].call(f.exports,(function(e){return i(n[a][1][e]||e)}),f,f.exports,e,n,o,r);}return o[a].exports}for(var s=t,a=0;a - * MIT Licensed - */ -var o=[]; -/*! - * Chai version - */n.version="4.3.3", -/*! - * Assertion Error - */ -n.AssertionError=e("assertion-error"); -/*! - * Utils for plugins (not exported) - */ -var r=e("./chai/utils");n.use=function(e){return ~o.indexOf(e)||(e(n,r),o.push(e)),n}, -/*! - * Utility Functions - */ -n.util=r; -/*! - * Configuration - */ -var i=e("./chai/config");n.config=i; -/*! - * Primary `Assertion` prototype - */ -var s=e("./chai/assertion");n.use(s); -/*! - * Core Assertions - */ -var a=e("./chai/core/assertions");n.use(a); -/*! - * Expect interface - */ -var c=e("./chai/interface/expect");n.use(c); -/*! - * Should interface - */ -var u=e("./chai/interface/should");n.use(u); -/*! - * Assert interface - */ -var f=e("./chai/interface/assert");n.use(f);},{"./chai/assertion":3,"./chai/config":4,"./chai/core/assertions":5,"./chai/interface/assert":6,"./chai/interface/expect":7,"./chai/interface/should":8,"./chai/utils":23,"assertion-error":34}],3:[function(e,t,n){ -/*! - * chai - * http://chaijs.com - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ -var o=e("./config");t.exports=function(e,t){ -/*! - * Module dependencies. - */ -var n=e.AssertionError,r=t.flag; -/*! - * Module export. - */ -/*! - * Assertion Constructor - * - * Creates object for chaining. - * - * `Assertion` objects contain metadata in the form of flags. Three flags can - * be assigned during instantiation by passing arguments to this constructor: - * - * - `object`: This flag contains the target of the assertion. For example, in - * the assertion `expect(numKittens).to.equal(7);`, the `object` flag will - * contain `numKittens` so that the `equal` assertion can reference it when - * needed. - * - * - `message`: This flag contains an optional custom error message to be - * prepended to the error message that's generated by the assertion when it - * fails. - * - * - `ssfi`: This flag stands for "start stack function indicator". It - * contains a function reference that serves as the starting point for - * removing frames from the stack trace of the error that's created by the - * assertion when it fails. The goal is to provide a cleaner stack trace to - * end users by removing Chai's internal functions. Note that it only works - * in environments that support `Error.captureStackTrace`, and only when - * `Chai.config.includeStack` hasn't been set to `false`. - * - * - `lockSsfi`: This flag controls whether or not the given `ssfi` flag - * should retain its current value, even as assertions are chained off of - * this object. This is usually set to `true` when creating a new assertion - * from within another assertion. It's also temporarily set to `true` before - * an overwritten assertion gets called by the overwriting assertion. - * - * @param {Mixed} obj target of the assertion - * @param {String} msg (optional) custom error message - * @param {Function} ssfi (optional) starting point for removing stack frames - * @param {Boolean} lockSsfi (optional) whether or not the ssfi flag is locked - * @api private - */ -function i(e,n,o,s){return r(this,"ssfi",o||i),r(this,"lockSsfi",s),r(this,"object",e),r(this,"message",n),t.proxify(this)}e.Assertion=i,Object.defineProperty(i,"includeStack",{get:function(){return console.warn("Assertion.includeStack is deprecated, use chai.config.includeStack instead."),o.includeStack},set:function(e){console.warn("Assertion.includeStack is deprecated, use chai.config.includeStack instead."),o.includeStack=e;}}),Object.defineProperty(i,"showDiff",{get:function(){return console.warn("Assertion.showDiff is deprecated, use chai.config.showDiff instead."),o.showDiff},set:function(e){console.warn("Assertion.showDiff is deprecated, use chai.config.showDiff instead."),o.showDiff=e;}}),i.addProperty=function(e,n){t.addProperty(this.prototype,e,n);},i.addMethod=function(e,n){t.addMethod(this.prototype,e,n);},i.addChainableMethod=function(e,n,o){t.addChainableMethod(this.prototype,e,n,o);},i.overwriteProperty=function(e,n){t.overwriteProperty(this.prototype,e,n);},i.overwriteMethod=function(e,n){t.overwriteMethod(this.prototype,e,n);},i.overwriteChainableMethod=function(e,n,o){t.overwriteChainableMethod(this.prototype,e,n,o);},i.prototype.assert=function(e,i,s,a,c,u){var f=t.test(this,arguments);if(!1!==u&&(u=!0),void 0===a&&void 0===c&&(u=!1),!0!==o.showDiff&&(u=!1),!f){i=t.getMessage(this,arguments);var p={actual:t.getActual(this,arguments),expected:a,showDiff:u},l=t.getOperator(this,arguments);throw l&&(p.operator=l),new n(i,p,o.includeStack?this.assert:r(this,"ssfi"))}}, -/*! - * ### ._obj - * - * Quick reference to stored `actual` value for plugin developers. - * - * @api private - */ -Object.defineProperty(i.prototype,"_obj",{get:function(){return r(this,"object")},set:function(e){r(this,"object",e);}});};},{"./config":4}],4:[function(e,t,n){t.exports={includeStack:!1,showDiff:!0,truncateThreshold:40,useProxy:!0,proxyExcludedKeys:["then","catch","inspect","toJSON"]};},{}],5:[function(e,t,n){ -/*! - * chai - * http://chaijs.com - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ -t.exports=function(e,t){var n=e.Assertion,o=e.AssertionError,r=t.flag;function i(e,n){n&&r(this,"message",n),e=e.toLowerCase();var o=r(this,"object"),i=~["a","e","i","o","u"].indexOf(e.charAt(0))?"an ":"a ";this.assert(e===t.type(o).toLowerCase(),"expected #{this} to be "+i+e,"expected #{this} not to be "+i+e);}function s(e,n){return t.isNaN(e)&&t.isNaN(n)||e===n}function a(){r(this,"contains",!0);}function c(e,i){i&&r(this,"message",i);var a=r(this,"object"),c=t.type(a).toLowerCase(),u=r(this,"message"),f=r(this,"negate"),p=r(this,"ssfi"),l=r(this,"deep"),h=l?"deep ":"";u=u?u+": ":"";var d=!1;switch(c){case"string":d=-1!==a.indexOf(e);break;case"weakset":if(l)throw new o(u+"unable to use .deep.include with WeakSet",void 0,p);d=a.has(e);break;case"map":var y=l?t.eql:s;a.forEach((function(t){d=d||y(t,e);}));break;case"set":l?a.forEach((function(n){d=d||t.eql(n,e);})):d=a.has(e);break;case"array":d=l?a.some((function(n){return t.eql(n,e)})):-1!==a.indexOf(e);break;default:if(e!==Object(e))throw new o(u+"the given combination of arguments ("+c+" and "+t.type(e).toLowerCase()+") is invalid for this assertion. You can use an array, a map, an object, a set, a string, or a weakset instead of a "+t.type(e).toLowerCase(),void 0,p);var b=Object.keys(e),g=null,w=0;if(b.forEach((function(i){var s=new n(a);if(t.transferFlags(this,s,!0),r(s,"lockSsfi",!0),f&&1!==b.length)try{s.property(i,e[i]);}catch(e){if(!t.checkError.compatibleConstructor(e,o))throw e;null===g&&(g=e),w++;}else s.property(i,e[i]);}),this),f&&b.length>1&&w===b.length)throw g;return}this.assert(d,"expected #{this} to "+h+"include "+t.inspect(e),"expected #{this} to not "+h+"include "+t.inspect(e));}function u(){var e=r(this,"object");this.assert(null!=e,"expected #{this} to exist","expected #{this} to not exist");}function f(){var e=r(this,"object"),n=t.type(e);this.assert("Arguments"===n,"expected #{this} to be arguments but got "+n,"expected #{this} to not be arguments");}function p(e,t){t&&r(this,"message",t);var n=r(this,"object");if(r(this,"deep")){var o=r(this,"lockSsfi");r(this,"lockSsfi",!0),this.eql(e),r(this,"lockSsfi",o);}else this.assert(e===n,"expected #{this} to equal #{exp}","expected #{this} to not equal #{exp}",e,this._obj,!0);}function l(e,n){n&&r(this,"message",n),this.assert(t.eql(e,r(this,"object")),"expected #{this} to deeply equal #{exp}","expected #{this} to not deeply equal #{exp}",e,this._obj,!0);}function h(e,i){i&&r(this,"message",i);var s,a=r(this,"object"),c=r(this,"doLength"),u=r(this,"message"),f=u?u+": ":"",p=r(this,"ssfi"),l=t.type(a).toLowerCase(),h=t.type(e).toLowerCase(),d=!0;if(c&&"map"!==l&&"set"!==l&&new n(a,u,p,!0).to.have.property("length"),c||"date"!==l||"date"===h?"number"===h||!c&&"number"!==l?c||"date"===l||"number"===l?d=!1:s=f+"expected "+("string"===l?"'"+a+"'":a)+" to be a number or a date":s=f+"the argument to above must be a number":s=f+"the argument to above must be a date",d)throw new o(s,void 0,p);if(c){var y,b="length";"map"===l||"set"===l?(b="size",y=a.size):y=a.length,this.assert(y>e,"expected #{this} to have a "+b+" above #{exp} but got #{act}","expected #{this} to not have a "+b+" above #{exp}",e,y);}else this.assert(a>e,"expected #{this} to be above #{exp}","expected #{this} to be at most #{exp}",e);}function d(e,i){i&&r(this,"message",i);var s,a=r(this,"object"),c=r(this,"doLength"),u=r(this,"message"),f=u?u+": ":"",p=r(this,"ssfi"),l=t.type(a).toLowerCase(),h=t.type(e).toLowerCase(),d=!0;if(c&&"map"!==l&&"set"!==l&&new n(a,u,p,!0).to.have.property("length"),c||"date"!==l||"date"===h?"number"===h||!c&&"number"!==l?c||"date"===l||"number"===l?d=!1:s=f+"expected "+("string"===l?"'"+a+"'":a)+" to be a number or a date":s=f+"the argument to least must be a number":s=f+"the argument to least must be a date",d)throw new o(s,void 0,p);if(c){var y,b="length";"map"===l||"set"===l?(b="size",y=a.size):y=a.length,this.assert(y>=e,"expected #{this} to have a "+b+" at least #{exp} but got #{act}","expected #{this} to have a "+b+" below #{exp}",e,y);}else this.assert(a>=e,"expected #{this} to be at least #{exp}","expected #{this} to be below #{exp}",e);}function y(e,i){i&&r(this,"message",i);var s,a=r(this,"object"),c=r(this,"doLength"),u=r(this,"message"),f=u?u+": ":"",p=r(this,"ssfi"),l=t.type(a).toLowerCase(),h=t.type(e).toLowerCase(),d=!0;if(c&&"map"!==l&&"set"!==l&&new n(a,u,p,!0).to.have.property("length"),c||"date"!==l||"date"===h?"number"===h||!c&&"number"!==l?c||"date"===l||"number"===l?d=!1:s=f+"expected "+("string"===l?"'"+a+"'":a)+" to be a number or a date":s=f+"the argument to below must be a number":s=f+"the argument to below must be a date",d)throw new o(s,void 0,p);if(c){var y,b="length";"map"===l||"set"===l?(b="size",y=a.size):y=a.length,this.assert(y1&&this.assert(l&&(h?t.eql(n,b):n===b),"expected #{this} to have "+g+t.inspect(e)+" of #{exp}, but got #{act}","expected #{this} to not have "+g+t.inspect(e)+" of #{act}",n,b),r(this,"object",b);}function m(e,t,n){r(this,"own",!0),w.apply(this,arguments);}function v(e,n,o){"string"==typeof n&&(o=n,n=null),o&&r(this,"message",o);var i=r(this,"object"),s=Object.getOwnPropertyDescriptor(Object(i),e);s&&n?this.assert(t.eql(n,s),"expected the own property descriptor for "+t.inspect(e)+" on #{this} to match "+t.inspect(n)+", got "+t.inspect(s),"expected the own property descriptor for "+t.inspect(e)+" on #{this} to not match "+t.inspect(n),n,s,!0):this.assert(s,"expected #{this} to have an own property descriptor for "+t.inspect(e),"expected #{this} to not have an own property descriptor for "+t.inspect(e)),r(this,"object",s);}function x(){r(this,"doLength",!0);}function O(e,o){o&&r(this,"message",o);var i,s=r(this,"object"),a=t.type(s).toLowerCase(),c=r(this,"message"),u=r(this,"ssfi"),f="length";switch(a){case"map":case"set":f="size",i=s.size;break;default:new n(s,c,u,!0).to.have.property("length"),i=s.length;}this.assert(i==e,"expected #{this} to have a "+f+" of #{exp} but got #{act}","expected #{this} to not have a "+f+" of #{act}",e,i);}function j(e,t){t&&r(this,"message",t);var n=r(this,"object");this.assert(e.exec(n),"expected #{this} to match "+e,"expected #{this} not to match "+e);}function M(e){var n,i,s=r(this,"object"),a=t.type(s),c=t.type(e),u=r(this,"ssfi"),f=r(this,"deep"),p="",l=!0,h=r(this,"message"),d=(h=h?h+": ":"")+"when testing keys against an object or an array you must give a single Array|Object|String argument or multiple String arguments";if("Map"===a||"Set"===a)p=f?"deeply ":"",i=[],s.forEach((function(e,t){i.push(t);})),"Array"!==c&&(e=Array.prototype.slice.call(arguments));else {switch(i=t.getOwnEnumerableProperties(s),c){case"Array":if(arguments.length>1)throw new o(d,void 0,u);break;case"Object":if(arguments.length>1)throw new o(d,void 0,u);e=Object.keys(e);break;default:e=Array.prototype.slice.call(arguments);}e=e.map((function(e){return "symbol"==typeof e?e:String(e)}));}if(!e.length)throw new o(h+"keys required",void 0,u);var y=e.length,b=r(this,"any"),g=r(this,"all"),w=e;if(b||g||(g=!0),b&&(l=w.some((function(e){return i.some((function(n){return f?t.eql(e,n):e===n}))}))),g&&(l=w.every((function(e){return i.some((function(n){return f?t.eql(e,n):e===n}))})),r(this,"contains")||(l=l&&e.length==i.length)),y>1){var m=(e=e.map((function(e){return t.inspect(e)}))).pop();g&&(n=e.join(", ")+", and "+m),b&&(n=e.join(", ")+", or "+m);}else n=t.inspect(e[0]);n=(y>1?"keys ":"key ")+n,n=(r(this,"contains")?"contain ":"have ")+n,this.assert(l,"expected #{this} to "+p+n,"expected #{this} to not "+p+n,w.slice(0).sort(t.compareByInspect),i.sort(t.compareByInspect),!0);}function P(e,o,i){i&&r(this,"message",i);var s,a=r(this,"object"),c=r(this,"ssfi"),u=r(this,"message"),f=r(this,"negate")||!1;new n(a,u,c,!0).is.a("function"),(e instanceof RegExp||"string"==typeof e)&&(o=e,e=null);try{a();}catch(e){s=e;}var p=void 0===e&&void 0===o,l=Boolean(e&&o),h=!1,d=!1;if(p||!p&&!f){var y="an error";e instanceof Error?y="#{exp}":e&&(y=t.checkError.getConstructorName(e)),this.assert(s,"expected #{this} to throw "+y,"expected #{this} to not throw an error but #{act} was thrown",e&&e.toString(),s instanceof Error?s.toString():"string"==typeof s?s:s&&t.checkError.getConstructorName(s));}if(e&&s&&(e instanceof Error&&t.checkError.compatibleInstance(s,e)===f&&(l&&f?h=!0:this.assert(f,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}"+(s&&!f?" but #{act} was thrown":""),e.toString(),s.toString())),t.checkError.compatibleConstructor(s,e)===f&&(l&&f?h=!0:this.assert(f,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}"+(s?" but #{act} was thrown":""),e instanceof Error?e.toString():e&&t.checkError.getConstructorName(e),s instanceof Error?s.toString():s&&t.checkError.getConstructorName(s)))),s&&null!=o){var b="including";o instanceof RegExp&&(b="matching"),t.checkError.compatibleMessage(s,o)===f&&(l&&f?d=!0:this.assert(f,"expected #{this} to throw error "+b+" #{exp} but got #{act}","expected #{this} to throw error not "+b+" #{exp}",o,t.checkError.getMessage(s)));}h&&d&&this.assert(f,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}"+(s?" but #{act} was thrown":""),e instanceof Error?e.toString():e&&t.checkError.getConstructorName(e),s instanceof Error?s.toString():s&&t.checkError.getConstructorName(s)),r(this,"object",s);}function N(e,n){n&&r(this,"message",n);var o=r(this,"object"),i=r(this,"itself"),s="function"!=typeof o||i?o[e]:o.prototype[e];this.assert("function"==typeof s,"expected #{this} to respond to "+t.inspect(e),"expected #{this} to not respond to "+t.inspect(e));}function E(e,n){n&&r(this,"message",n);var o=e(r(this,"object"));this.assert(o,"expected #{this} to satisfy "+t.objDisplay(e),"expected #{this} to not satisfy"+t.objDisplay(e),!r(this,"negate"),o);}function S(e,t,i){i&&r(this,"message",i);var s=r(this,"object"),a=r(this,"message"),c=r(this,"ssfi");if(new n(s,a,c,!0).is.a("number"),"number"!=typeof e||"number"!=typeof t)throw new o((a=a?a+": ":"")+"the arguments to closeTo or approximately must be numbers"+(void 0===t?", and a delta is required":""),void 0,c);this.assert(Math.abs(s-e)<=t,"expected #{this} to be close to "+e+" +/- "+t,"expected #{this} not to be close to "+e+" +/- "+t);}function k(e,t,n,o,r){if(!o){if(e.length!==t.length)return !1;t=t.slice();}return e.every((function(e,i){if(r)return n?n(e,t[i]):e===t[i];if(!n){var s=t.indexOf(e);return -1!==s&&(o||t.splice(s,1),!0)}return t.some((function(r,i){return !!n(e,r)&&(o||t.splice(i,1),!0)}))}))}function A(e,o){o&&r(this,"message",o);var i=r(this,"object"),s=r(this,"message"),a=r(this,"ssfi"),c=r(this,"contains"),u=r(this,"deep");new n(e,s,a,!0).to.be.an("array"),c?this.assert(e.some((function(e){return i.indexOf(e)>-1})),"expected #{this} to contain one of #{exp}","expected #{this} to not contain one of #{exp}",e,i):u?this.assert(e.some((function(e){return t.eql(i,e)})),"expected #{this} to deeply equal one of #{exp}","expected #{this} to deeply equal one of #{exp}",e,i):this.assert(e.indexOf(i)>-1,"expected #{this} to be one of #{exp}","expected #{this} to not be one of #{exp}",e,i);}function D(e,t,o){o&&r(this,"message",o);var i,s=r(this,"object"),a=r(this,"message"),c=r(this,"ssfi");new n(s,a,c,!0).is.a("function"),t?(new n(e,a,c,!0).to.have.property(t),i=e[t]):(new n(e,a,c,!0).is.a("function"),i=e()),s();var u=null==t?e():e[t],f=null==t?i:"."+t;r(this,"deltaMsgObj",f),r(this,"initialDeltaValue",i),r(this,"finalDeltaValue",u),r(this,"deltaBehavior","change"),r(this,"realDelta",u!==i),this.assert(i!==u,"expected "+f+" to change","expected "+f+" to not change");}function T(e,t,o){o&&r(this,"message",o);var i,s=r(this,"object"),a=r(this,"message"),c=r(this,"ssfi");new n(s,a,c,!0).is.a("function"),t?(new n(e,a,c,!0).to.have.property(t),i=e[t]):(new n(e,a,c,!0).is.a("function"),i=e()),new n(i,a,c,!0).is.a("number"),s();var u=null==t?e():e[t],f=null==t?i:"."+t;r(this,"deltaMsgObj",f),r(this,"initialDeltaValue",i),r(this,"finalDeltaValue",u),r(this,"deltaBehavior","increase"),r(this,"realDelta",u-i),this.assert(u-i>0,"expected "+f+" to increase","expected "+f+" to not increase");}function q(e,t,o){o&&r(this,"message",o);var i,s=r(this,"object"),a=r(this,"message"),c=r(this,"ssfi");new n(s,a,c,!0).is.a("function"),t?(new n(e,a,c,!0).to.have.property(t),i=e[t]):(new n(e,a,c,!0).is.a("function"),i=e()),new n(i,a,c,!0).is.a("number"),s();var u=null==t?e():e[t],f=null==t?i:"."+t;r(this,"deltaMsgObj",f),r(this,"initialDeltaValue",i),r(this,"finalDeltaValue",u),r(this,"deltaBehavior","decrease"),r(this,"realDelta",i-u),this.assert(u-i<0,"expected "+f+" to decrease","expected "+f+" to not decrease");}function C(e,t){t&&r(this,"message",t);var n,o=r(this,"deltaMsgObj"),i=r(this,"initialDeltaValue"),s=r(this,"finalDeltaValue"),a=r(this,"deltaBehavior"),c=r(this,"realDelta");n="change"===a?Math.abs(s-i)===Math.abs(e):c===Math.abs(e),this.assert(n,"expected "+o+" to "+a+" by "+e,"expected "+o+" to not "+a+" by "+e);}["to","be","been","is","and","has","have","with","that","which","at","of","same","but","does","still","also"].forEach((function(e){n.addProperty(e);})),n.addProperty("not",(function(){r(this,"negate",!0);})),n.addProperty("deep",(function(){r(this,"deep",!0);})),n.addProperty("nested",(function(){r(this,"nested",!0);})),n.addProperty("own",(function(){r(this,"own",!0);})),n.addProperty("ordered",(function(){r(this,"ordered",!0);})),n.addProperty("any",(function(){r(this,"any",!0),r(this,"all",!1);})),n.addProperty("all",(function(){r(this,"all",!0),r(this,"any",!1);})),n.addChainableMethod("an",i),n.addChainableMethod("a",i),n.addChainableMethod("include",c,a),n.addChainableMethod("contain",c,a),n.addChainableMethod("contains",c,a),n.addChainableMethod("includes",c,a),n.addProperty("ok",(function(){this.assert(r(this,"object"),"expected #{this} to be truthy","expected #{this} to be falsy");})),n.addProperty("true",(function(){this.assert(!0===r(this,"object"),"expected #{this} to be true","expected #{this} to be false",!r(this,"negate"));})),n.addProperty("false",(function(){this.assert(!1===r(this,"object"),"expected #{this} to be false","expected #{this} to be true",!!r(this,"negate"));})),n.addProperty("null",(function(){this.assert(null===r(this,"object"),"expected #{this} to be null","expected #{this} not to be null");})),n.addProperty("undefined",(function(){this.assert(void 0===r(this,"object"),"expected #{this} to be undefined","expected #{this} not to be undefined");})),n.addProperty("NaN",(function(){this.assert(t.isNaN(r(this,"object")),"expected #{this} to be NaN","expected #{this} not to be NaN");})),n.addProperty("exist",u),n.addProperty("exists",u),n.addProperty("empty",(function(){var e,n=r(this,"object"),i=r(this,"ssfi"),s=r(this,"message");switch(s=s?s+": ":"",t.type(n).toLowerCase()){case"array":case"string":e=n.length;break;case"map":case"set":e=n.size;break;case"weakmap":case"weakset":throw new o(s+".empty was passed a weak collection",void 0,i);case"function":var a=s+".empty was passed a function "+t.getName(n);throw new o(a.trim(),void 0,i);default:if(n!==Object(n))throw new o(s+".empty was passed non-string primitive "+t.inspect(n),void 0,i);e=Object.keys(n).length;}this.assert(0===e,"expected #{this} to be empty","expected #{this} not to be empty");})),n.addProperty("arguments",f),n.addProperty("Arguments",f),n.addMethod("equal",p),n.addMethod("equals",p),n.addMethod("eq",p),n.addMethod("eql",l),n.addMethod("eqls",l),n.addMethod("above",h),n.addMethod("gt",h),n.addMethod("greaterThan",h),n.addMethod("least",d),n.addMethod("gte",d),n.addMethod("greaterThanOrEqual",d),n.addMethod("below",y),n.addMethod("lt",y),n.addMethod("lessThan",y),n.addMethod("most",b),n.addMethod("lte",b),n.addMethod("lessThanOrEqual",b),n.addMethod("within",(function(e,i,s){s&&r(this,"message",s);var a,c=r(this,"object"),u=r(this,"doLength"),f=r(this,"message"),p=f?f+": ":"",l=r(this,"ssfi"),h=t.type(c).toLowerCase(),d=t.type(e).toLowerCase(),y=t.type(i).toLowerCase(),b=!0,g="date"===d&&"date"===y?e.toUTCString()+".."+i.toUTCString():e+".."+i;if(u&&"map"!==h&&"set"!==h&&new n(c,f,l,!0).to.have.property("length"),u||"date"!==h||"date"===d&&"date"===y?"number"===d&&"number"===y||!u&&"number"!==h?u||"date"===h||"number"===h?b=!1:a=p+"expected "+("string"===h?"'"+c+"'":c)+" to be a number or a date":a=p+"the arguments to within must be numbers":a=p+"the arguments to within must be dates",b)throw new o(a,void 0,l);if(u){var w,m="length";"map"===h||"set"===h?(m="size",w=c.size):w=c.length,this.assert(w>=e&&w<=i,"expected #{this} to have a "+m+" within "+g,"expected #{this} to not have a "+m+" within "+g);}else this.assert(c>=e&&c<=i,"expected #{this} to be within "+g,"expected #{this} to not be within "+g);})),n.addMethod("instanceof",g),n.addMethod("instanceOf",g),n.addMethod("property",w),n.addMethod("ownProperty",m),n.addMethod("haveOwnProperty",m),n.addMethod("ownPropertyDescriptor",v),n.addMethod("haveOwnPropertyDescriptor",v),n.addChainableMethod("length",O,x),n.addChainableMethod("lengthOf",O,x),n.addMethod("match",j),n.addMethod("matches",j),n.addMethod("string",(function(e,o){o&&r(this,"message",o);var i=r(this,"object"),s=r(this,"message"),a=r(this,"ssfi");new n(i,s,a,!0).is.a("string"),this.assert(~i.indexOf(e),"expected #{this} to contain "+t.inspect(e),"expected #{this} to not contain "+t.inspect(e));})),n.addMethod("keys",M),n.addMethod("key",M),n.addMethod("throw",P),n.addMethod("throws",P),n.addMethod("Throw",P),n.addMethod("respondTo",N),n.addMethod("respondsTo",N),n.addProperty("itself",(function(){r(this,"itself",!0);})),n.addMethod("satisfy",E),n.addMethod("satisfies",E),n.addMethod("closeTo",S),n.addMethod("approximately",S),n.addMethod("members",(function(e,o){o&&r(this,"message",o);var i=r(this,"object"),s=r(this,"message"),a=r(this,"ssfi");new n(i,s,a,!0).to.be.an("array"),new n(e,s,a,!0).to.be.an("array");var c,u,f,p=r(this,"contains"),l=r(this,"ordered");p?(u="expected #{this} to be "+(c=l?"an ordered superset":"a superset")+" of #{exp}",f="expected #{this} to not be "+c+" of #{exp}"):(u="expected #{this} to have the same "+(c=l?"ordered members":"members")+" as #{exp}",f="expected #{this} to not have the same "+c+" as #{exp}");var h=r(this,"deep")?t.eql:void 0;this.assert(k(e,i,h,p,l),u,f,e,i,!0);})),n.addMethod("oneOf",A),n.addMethod("change",D),n.addMethod("changes",D),n.addMethod("increase",T),n.addMethod("increases",T),n.addMethod("decrease",q),n.addMethod("decreases",q),n.addMethod("by",C),n.addProperty("extensible",(function(){var e=r(this,"object"),t=e===Object(e)&&Object.isExtensible(e);this.assert(t,"expected #{this} to be extensible","expected #{this} to not be extensible");})),n.addProperty("sealed",(function(){var e=r(this,"object"),t=e!==Object(e)||Object.isSealed(e);this.assert(t,"expected #{this} to be sealed","expected #{this} to not be sealed");})),n.addProperty("frozen",(function(){var e=r(this,"object"),t=e!==Object(e)||Object.isFrozen(e);this.assert(t,"expected #{this} to be frozen","expected #{this} to not be frozen");})),n.addProperty("finite",(function(e){var t=r(this,"object");this.assert("number"==typeof t&&isFinite(t),"expected #{this} to be a finite number","expected #{this} to not be a finite number");}));};},{}],6:[function(e,t,n){ -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ -t.exports=function(e,t){ -/*! - * Chai dependencies. - */ -var n=e.Assertion,o=t.flag,r=e.assert=function(t,o){new n(null,null,e.assert,!0).assert(t,o,"[ negation message unavailable ]");}; -/*! - * Module export. - */r.fail=function(t,n,o,i){throw arguments.length<2&&(o=t,t=void 0),o=o||"assert.fail()",new e.AssertionError(o,{actual:t,expected:n,operator:i},r.fail)},r.isOk=function(e,t){new n(e,t,r.isOk,!0).is.ok;},r.isNotOk=function(e,t){new n(e,t,r.isNotOk,!0).is.not.ok;},r.equal=function(e,t,i){var s=new n(e,i,r.equal,!0);s.assert(t==o(s,"object"),"expected #{this} to equal #{exp}","expected #{this} to not equal #{act}",t,e,!0);},r.notEqual=function(e,t,i){var s=new n(e,i,r.notEqual,!0);s.assert(t!=o(s,"object"),"expected #{this} to not equal #{exp}","expected #{this} to equal #{act}",t,e,!0);},r.strictEqual=function(e,t,o){new n(e,o,r.strictEqual,!0).to.equal(t);},r.notStrictEqual=function(e,t,o){new n(e,o,r.notStrictEqual,!0).to.not.equal(t);},r.deepEqual=r.deepStrictEqual=function(e,t,o){new n(e,o,r.deepEqual,!0).to.eql(t);},r.notDeepEqual=function(e,t,o){new n(e,o,r.notDeepEqual,!0).to.not.eql(t);},r.isAbove=function(e,t,o){new n(e,o,r.isAbove,!0).to.be.above(t);},r.isAtLeast=function(e,t,o){new n(e,o,r.isAtLeast,!0).to.be.least(t);},r.isBelow=function(e,t,o){new n(e,o,r.isBelow,!0).to.be.below(t);},r.isAtMost=function(e,t,o){new n(e,o,r.isAtMost,!0).to.be.most(t);},r.isTrue=function(e,t){new n(e,t,r.isTrue,!0).is.true;},r.isNotTrue=function(e,t){new n(e,t,r.isNotTrue,!0).to.not.equal(!0);},r.isFalse=function(e,t){new n(e,t,r.isFalse,!0).is.false;},r.isNotFalse=function(e,t){new n(e,t,r.isNotFalse,!0).to.not.equal(!1);},r.isNull=function(e,t){new n(e,t,r.isNull,!0).to.equal(null);},r.isNotNull=function(e,t){new n(e,t,r.isNotNull,!0).to.not.equal(null);},r.isNaN=function(e,t){new n(e,t,r.isNaN,!0).to.be.NaN;},r.isNotNaN=function(e,t){new n(e,t,r.isNotNaN,!0).not.to.be.NaN;},r.exists=function(e,t){new n(e,t,r.exists,!0).to.exist;},r.notExists=function(e,t){new n(e,t,r.notExists,!0).to.not.exist;},r.isUndefined=function(e,t){new n(e,t,r.isUndefined,!0).to.equal(void 0);},r.isDefined=function(e,t){new n(e,t,r.isDefined,!0).to.not.equal(void 0);},r.isFunction=function(e,t){new n(e,t,r.isFunction,!0).to.be.a("function");},r.isNotFunction=function(e,t){new n(e,t,r.isNotFunction,!0).to.not.be.a("function");},r.isObject=function(e,t){new n(e,t,r.isObject,!0).to.be.a("object");},r.isNotObject=function(e,t){new n(e,t,r.isNotObject,!0).to.not.be.a("object");},r.isArray=function(e,t){new n(e,t,r.isArray,!0).to.be.an("array");},r.isNotArray=function(e,t){new n(e,t,r.isNotArray,!0).to.not.be.an("array");},r.isString=function(e,t){new n(e,t,r.isString,!0).to.be.a("string");},r.isNotString=function(e,t){new n(e,t,r.isNotString,!0).to.not.be.a("string");},r.isNumber=function(e,t){new n(e,t,r.isNumber,!0).to.be.a("number");},r.isNotNumber=function(e,t){new n(e,t,r.isNotNumber,!0).to.not.be.a("number");},r.isFinite=function(e,t){new n(e,t,r.isFinite,!0).to.be.finite;},r.isBoolean=function(e,t){new n(e,t,r.isBoolean,!0).to.be.a("boolean");},r.isNotBoolean=function(e,t){new n(e,t,r.isNotBoolean,!0).to.not.be.a("boolean");},r.typeOf=function(e,t,o){new n(e,o,r.typeOf,!0).to.be.a(t);},r.notTypeOf=function(e,t,o){new n(e,o,r.notTypeOf,!0).to.not.be.a(t);},r.instanceOf=function(e,t,o){new n(e,o,r.instanceOf,!0).to.be.instanceOf(t);},r.notInstanceOf=function(e,t,o){new n(e,o,r.notInstanceOf,!0).to.not.be.instanceOf(t);},r.include=function(e,t,o){new n(e,o,r.include,!0).include(t);},r.notInclude=function(e,t,o){new n(e,o,r.notInclude,!0).not.include(t);},r.deepInclude=function(e,t,o){new n(e,o,r.deepInclude,!0).deep.include(t);},r.notDeepInclude=function(e,t,o){new n(e,o,r.notDeepInclude,!0).not.deep.include(t);},r.nestedInclude=function(e,t,o){new n(e,o,r.nestedInclude,!0).nested.include(t);},r.notNestedInclude=function(e,t,o){new n(e,o,r.notNestedInclude,!0).not.nested.include(t);},r.deepNestedInclude=function(e,t,o){new n(e,o,r.deepNestedInclude,!0).deep.nested.include(t);},r.notDeepNestedInclude=function(e,t,o){new n(e,o,r.notDeepNestedInclude,!0).not.deep.nested.include(t);},r.ownInclude=function(e,t,o){new n(e,o,r.ownInclude,!0).own.include(t);},r.notOwnInclude=function(e,t,o){new n(e,o,r.notOwnInclude,!0).not.own.include(t);},r.deepOwnInclude=function(e,t,o){new n(e,o,r.deepOwnInclude,!0).deep.own.include(t);},r.notDeepOwnInclude=function(e,t,o){new n(e,o,r.notDeepOwnInclude,!0).not.deep.own.include(t);},r.match=function(e,t,o){new n(e,o,r.match,!0).to.match(t);},r.notMatch=function(e,t,o){new n(e,o,r.notMatch,!0).to.not.match(t);},r.property=function(e,t,o){new n(e,o,r.property,!0).to.have.property(t);},r.notProperty=function(e,t,o){new n(e,o,r.notProperty,!0).to.not.have.property(t);},r.propertyVal=function(e,t,o,i){new n(e,i,r.propertyVal,!0).to.have.property(t,o);},r.notPropertyVal=function(e,t,o,i){new n(e,i,r.notPropertyVal,!0).to.not.have.property(t,o);},r.deepPropertyVal=function(e,t,o,i){new n(e,i,r.deepPropertyVal,!0).to.have.deep.property(t,o);},r.notDeepPropertyVal=function(e,t,o,i){new n(e,i,r.notDeepPropertyVal,!0).to.not.have.deep.property(t,o);},r.ownProperty=function(e,t,o){new n(e,o,r.ownProperty,!0).to.have.own.property(t);},r.notOwnProperty=function(e,t,o){new n(e,o,r.notOwnProperty,!0).to.not.have.own.property(t);},r.ownPropertyVal=function(e,t,o,i){new n(e,i,r.ownPropertyVal,!0).to.have.own.property(t,o);},r.notOwnPropertyVal=function(e,t,o,i){new n(e,i,r.notOwnPropertyVal,!0).to.not.have.own.property(t,o);},r.deepOwnPropertyVal=function(e,t,o,i){new n(e,i,r.deepOwnPropertyVal,!0).to.have.deep.own.property(t,o);},r.notDeepOwnPropertyVal=function(e,t,o,i){new n(e,i,r.notDeepOwnPropertyVal,!0).to.not.have.deep.own.property(t,o);},r.nestedProperty=function(e,t,o){new n(e,o,r.nestedProperty,!0).to.have.nested.property(t);},r.notNestedProperty=function(e,t,o){new n(e,o,r.notNestedProperty,!0).to.not.have.nested.property(t);},r.nestedPropertyVal=function(e,t,o,i){new n(e,i,r.nestedPropertyVal,!0).to.have.nested.property(t,o);},r.notNestedPropertyVal=function(e,t,o,i){new n(e,i,r.notNestedPropertyVal,!0).to.not.have.nested.property(t,o);},r.deepNestedPropertyVal=function(e,t,o,i){new n(e,i,r.deepNestedPropertyVal,!0).to.have.deep.nested.property(t,o);},r.notDeepNestedPropertyVal=function(e,t,o,i){new n(e,i,r.notDeepNestedPropertyVal,!0).to.not.have.deep.nested.property(t,o);},r.lengthOf=function(e,t,o){new n(e,o,r.lengthOf,!0).to.have.lengthOf(t);},r.hasAnyKeys=function(e,t,o){new n(e,o,r.hasAnyKeys,!0).to.have.any.keys(t);},r.hasAllKeys=function(e,t,o){new n(e,o,r.hasAllKeys,!0).to.have.all.keys(t);},r.containsAllKeys=function(e,t,o){new n(e,o,r.containsAllKeys,!0).to.contain.all.keys(t);},r.doesNotHaveAnyKeys=function(e,t,o){new n(e,o,r.doesNotHaveAnyKeys,!0).to.not.have.any.keys(t);},r.doesNotHaveAllKeys=function(e,t,o){new n(e,o,r.doesNotHaveAllKeys,!0).to.not.have.all.keys(t);},r.hasAnyDeepKeys=function(e,t,o){new n(e,o,r.hasAnyDeepKeys,!0).to.have.any.deep.keys(t);},r.hasAllDeepKeys=function(e,t,o){new n(e,o,r.hasAllDeepKeys,!0).to.have.all.deep.keys(t);},r.containsAllDeepKeys=function(e,t,o){new n(e,o,r.containsAllDeepKeys,!0).to.contain.all.deep.keys(t);},r.doesNotHaveAnyDeepKeys=function(e,t,o){new n(e,o,r.doesNotHaveAnyDeepKeys,!0).to.not.have.any.deep.keys(t);},r.doesNotHaveAllDeepKeys=function(e,t,o){new n(e,o,r.doesNotHaveAllDeepKeys,!0).to.not.have.all.deep.keys(t);},r.throws=function(e,t,i,s){("string"==typeof t||t instanceof RegExp)&&(i=t,t=null);var a=new n(e,s,r.throws,!0).to.throw(t,i);return o(a,"object")},r.doesNotThrow=function(e,t,o,i){("string"==typeof t||t instanceof RegExp)&&(o=t,t=null),new n(e,i,r.doesNotThrow,!0).to.not.throw(t,o);},r.operator=function(i,s,a,c){var u;switch(s){case"==":u=i==a;break;case"===":u=i===a;break;case">":u=i>a;break;case">=":u=i>=a;break;case"<":u=i - * MIT Licensed - */ -t.exports=function(e,t){e.expect=function(t,n){return new e.Assertion(t,n)},e.expect.fail=function(t,n,o,r){throw arguments.length<2&&(o=t,t=void 0),o=o||"expect.fail()",new e.AssertionError(o,{actual:t,expected:n,operator:r},e.expect.fail)};};},{}],8:[function(e,t,n){ -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ -t.exports=function(e,t){var n=e.Assertion;function o(){function t(){return this instanceof String||this instanceof Number||this instanceof Boolean||"function"==typeof Symbol&&this instanceof Symbol||"function"==typeof BigInt&&this instanceof BigInt?new n(this.valueOf(),null,t):new n(this,null,t)}function o(e){Object.defineProperty(this,"should",{value:e,enumerable:!0,configurable:!0,writable:!0});}Object.defineProperty(Object.prototype,"should",{set:o,get:t,configurable:!0});var r={fail:function(t,n,o,i){throw arguments.length<2&&(o=t,t=void 0),o=o||"should.fail()",new e.AssertionError(o,{actual:t,expected:n,operator:i},r.fail)},equal:function(e,t,o){new n(e,o).to.equal(t);},Throw:function(e,t,o,r){new n(e,r).to.Throw(t,o);},exist:function(e,t){new n(e,t).to.exist;},not:{}};return r.not.equal=function(e,t,o){new n(e,o).to.not.equal(t);},r.not.Throw=function(e,t,o,r){new n(e,r).to.not.Throw(t,o);},r.not.exist=function(e,t){new n(e,t).to.not.exist;},r.throw=r.Throw,r.not.throw=r.not.Throw,r}e.should=o,e.Should=o;};},{}],9:[function(e,t,n){ -/*! - * Chai - addChainingMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -/*! - * Module dependencies - */ -var o=e("./addLengthGuard"),r=e("../../chai"),i=e("./flag"),s=e("./proxify"),a=e("./transferFlags"),c="function"==typeof Object.setPrototypeOf,u=function(){},f=Object.getOwnPropertyNames(u).filter((function(e){var t=Object.getOwnPropertyDescriptor(u,e);return "object"!=typeof t||!t.configurable})),p=Function.prototype.call,l=Function.prototype.apply;t.exports=function(e,t,n,u){"function"!=typeof u&&(u=function(){});var h={method:n,chainingBehavior:u};e.__methods||(e.__methods={}),e.__methods[t]=h,Object.defineProperty(e,t,{get:function(){h.chainingBehavior.call(this);var n=function(){i(this,"lockSsfi")||i(this,"ssfi",n);var e=h.method.apply(this,arguments);if(void 0!==e)return e;var t=new r.Assertion;return a(this,t),t};if(o(n,t,!0),c){var u=Object.create(this);u.call=p,u.apply=l,Object.setPrototypeOf(n,u);}else Object.getOwnPropertyNames(e).forEach((function(t){if(-1===f.indexOf(t)){var o=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(n,t,o);}}));return a(this,n),s(n)},configurable:!0});};},{"../../chai":2,"./addLengthGuard":10,"./flag":15,"./proxify":31,"./transferFlags":33}],10:[function(e,t,n){var o=Object.getOwnPropertyDescriptor((function(){}),"length"); -/*! - * Chai - addLengthGuard utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */t.exports=function(e,t,n){return o.configurable?(Object.defineProperty(e,"length",{get:function(){if(n)throw Error("Invalid Chai property: "+t+'.length. Due to a compatibility issue, "length" cannot directly follow "'+t+'". Use "'+t+'.lengthOf" instead.');throw Error("Invalid Chai property: "+t+'.length. See docs for proper usage of "'+t+'".')}}),e):e};},{}],11:[function(e,t,n){ -/*! - * Chai - addMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -var o=e("./addLengthGuard"),r=e("../../chai"),i=e("./flag"),s=e("./proxify"),a=e("./transferFlags");t.exports=function(e,t,n){var c=function(){i(this,"lockSsfi")||i(this,"ssfi",c);var e=n.apply(this,arguments);if(void 0!==e)return e;var t=new r.Assertion;return a(this,t),t};o(c,t,!1),e[t]=s(c,t);};},{"../../chai":2,"./addLengthGuard":10,"./flag":15,"./proxify":31,"./transferFlags":33}],12:[function(e,t,n){ -/*! - * Chai - addProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -var o=e("../../chai"),r=e("./flag"),i=e("./isProxyEnabled"),s=e("./transferFlags");t.exports=function(e,t,n){n=void 0===n?function(){}:n,Object.defineProperty(e,t,{get:function e(){i()||r(this,"lockSsfi")||r(this,"ssfi",e);var t=n.call(this);if(void 0!==t)return t;var a=new o.Assertion;return s(this,a),a},configurable:!0});};},{"../../chai":2,"./flag":15,"./isProxyEnabled":26,"./transferFlags":33}],13:[function(e,t,n){ -/*! - * Chai - compareByInspect utility - * Copyright(c) 2011-2016 Jake Luer - * MIT Licensed - */ -/*! - * Module dependencies - */ -var o=e("./inspect");t.exports=function(e,t){return o(e) - * MIT Licensed - */ -var o=e("assertion-error"),r=e("./flag"),i=e("type-detect");t.exports=function(e,t){var n=r(e,"message"),s=r(e,"ssfi");n=n?n+": ":"",e=r(e,"object"),(t=t.map((function(e){return e.toLowerCase()}))).sort();var a=t.map((function(e,n){var o=~["a","e","i","o","u"].indexOf(e.charAt(0))?"an":"a";return (t.length>1&&n===t.length-1?"or ":"")+o+" "+e})).join(", "),c=i(e).toLowerCase();if(!t.some((function(e){return c===e})))throw new o(n+"object tested must be "+a+", but "+c+" given",void 0,s)};},{"./flag":15,"assertion-error":34,"type-detect":39}],15:[function(e,t,n){ -/*! - * Chai - flag utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -t.exports=function(e,t,n){var o=e.__flags||(e.__flags=Object.create(null));if(3!==arguments.length)return o[t];o[t]=n;};},{}],16:[function(e,t,n){ -/*! - * Chai - getActual utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -t.exports=function(e,t){return t.length>4?t[4]:e._obj};},{}],17:[function(e,t,n){ -/*! - * Chai - getEnumerableProperties utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -t.exports=function(e){var t=[];for(var n in e)t.push(n);return t};},{}],18:[function(e,t,n){ -/*! - * Chai - message composition utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -/*! - * Module dependencies - */ -var o=e("./flag"),r=e("./getActual"),i=e("./objDisplay");t.exports=function(e,t){var n=o(e,"negate"),s=o(e,"object"),a=t[3],c=r(e,t),u=n?t[2]:t[1],f=o(e,"message");return "function"==typeof u&&(u=u()),u=(u=u||"").replace(/#\{this\}/g,(function(){return i(s)})).replace(/#\{act\}/g,(function(){return i(c)})).replace(/#\{exp\}/g,(function(){return i(a)})),f?f+": "+u:u};},{"./flag":15,"./getActual":16,"./objDisplay":27}],19:[function(e,t,n){var o=e("type-detect"),r=e("./flag");function i(e){var t=o(e);return -1!==["Array","Object","function"].indexOf(t)}t.exports=function(e,t){var n=r(e,"operator"),o=r(e,"negate"),s=t[3],a=o?t[2]:t[1];if(n)return n;if("function"==typeof a&&(a=a()),(a=a||"")&&!/\shave\s/.test(a)){var c=i(s);return /\snot\s/.test(a)?c?"notDeepStrictEqual":"notStrictEqual":c?"deepStrictEqual":"strictEqual"}};},{"./flag":15,"type-detect":39}],20:[function(e,t,n){ -/*! - * Chai - getOwnEnumerableProperties utility - * Copyright(c) 2011-2016 Jake Luer - * MIT Licensed - */ -/*! - * Module dependencies - */ -var o=e("./getOwnEnumerablePropertySymbols");t.exports=function(e){return Object.keys(e).concat(o(e))};},{"./getOwnEnumerablePropertySymbols":21}],21:[function(e,t,n){ -/*! - * Chai - getOwnEnumerablePropertySymbols utility - * Copyright(c) 2011-2016 Jake Luer - * MIT Licensed - */ -t.exports=function(e){return "function"!=typeof Object.getOwnPropertySymbols?[]:Object.getOwnPropertySymbols(e).filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))};},{}],22:[function(e,t,n){ -/*! - * Chai - getProperties utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -t.exports=function(e){var t=Object.getOwnPropertyNames(e);function n(e){-1===t.indexOf(e)&&t.push(e);}for(var o=Object.getPrototypeOf(e);null!==o;)Object.getOwnPropertyNames(o).forEach(n),o=Object.getPrototypeOf(o);return t};},{}],23:[function(e,t,n){ -/*! - * chai - * Copyright(c) 2011 Jake Luer - * MIT Licensed - */ -/*! - * Dependencies that are used for multiple exports are required here only once - */ -var o=e("pathval"); -/*! - * test utility - */n.test=e("./test"), -/*! - * type utility - */ -n.type=e("type-detect"), -/*! - * expectTypes utility - */ -n.expectTypes=e("./expectTypes"), -/*! - * message utility - */ -n.getMessage=e("./getMessage"), -/*! - * actual utility - */ -n.getActual=e("./getActual"), -/*! - * Inspect util - */ -n.inspect=e("./inspect"), -/*! - * Object Display util - */ -n.objDisplay=e("./objDisplay"), -/*! - * Flag utility - */ -n.flag=e("./flag"), -/*! - * Flag transferring utility - */ -n.transferFlags=e("./transferFlags"), -/*! - * Deep equal utility - */ -n.eql=e("deep-eql"), -/*! - * Deep path info - */ -n.getPathInfo=o.getPathInfo, -/*! - * Check if a property exists - */ -n.hasProperty=o.hasProperty, -/*! - * Function name - */ -n.getName=e("get-func-name"), -/*! - * add Property - */ -n.addProperty=e("./addProperty"), -/*! - * add Method - */ -n.addMethod=e("./addMethod"), -/*! - * overwrite Property - */ -n.overwriteProperty=e("./overwriteProperty"), -/*! - * overwrite Method - */ -n.overwriteMethod=e("./overwriteMethod"), -/*! - * Add a chainable method - */ -n.addChainableMethod=e("./addChainableMethod"), -/*! - * Overwrite chainable method - */ -n.overwriteChainableMethod=e("./overwriteChainableMethod"), -/*! - * Compare by inspect method - */ -n.compareByInspect=e("./compareByInspect"), -/*! - * Get own enumerable property symbols method - */ -n.getOwnEnumerablePropertySymbols=e("./getOwnEnumerablePropertySymbols"), -/*! - * Get own enumerable properties method - */ -n.getOwnEnumerableProperties=e("./getOwnEnumerableProperties"), -/*! - * Checks error against a given set of criteria - */ -n.checkError=e("check-error"), -/*! - * Proxify util - */ -n.proxify=e("./proxify"), -/*! - * addLengthGuard util - */ -n.addLengthGuard=e("./addLengthGuard"), -/*! - * isProxyEnabled helper - */ -n.isProxyEnabled=e("./isProxyEnabled"), -/*! - * isNaN method - */ -n.isNaN=e("./isNaN"), -/*! - * getOperator method - */ -n.getOperator=e("./getOperator");},{"./addChainableMethod":9,"./addLengthGuard":10,"./addMethod":11,"./addProperty":12,"./compareByInspect":13,"./expectTypes":14,"./flag":15,"./getActual":16,"./getMessage":18,"./getOperator":19,"./getOwnEnumerableProperties":20,"./getOwnEnumerablePropertySymbols":21,"./inspect":24,"./isNaN":25,"./isProxyEnabled":26,"./objDisplay":27,"./overwriteChainableMethod":28,"./overwriteMethod":29,"./overwriteProperty":30,"./proxify":31,"./test":32,"./transferFlags":33,"check-error":35,"deep-eql":36,"get-func-name":37,pathval:38,"type-detect":39}],24:[function(e,t,n){var o=e("get-func-name"),r=e("./getProperties"),i=e("./getEnumerableProperties"),s=e("../config");function a(e,t,n,o){return u({showHidden:t,seen:[],stylize:function(e){return e}},e,void 0===n?2:n)}t.exports=a;var c=function(e){return "object"==typeof HTMLElement?e instanceof HTMLElement:e&&"object"==typeof e&&"nodeType"in e&&1===e.nodeType&&"string"==typeof e.nodeName};function u(e,t,s){if(t&&"function"==typeof t.inspect&&t.inspect!==n.inspect&&(!t.constructor||t.constructor.prototype!==t)){var a=t.inspect(s,e);return "string"!=typeof a&&(a=u(e,a,s)),a}var x=f(e,t);if(x)return x;if(c(t)){if("outerHTML"in t)return t.outerHTML;try{if(document.xmlVersion)return (new XMLSerializer).serializeToString(t);var O="http://www.w3.org/1999/xhtml",j=document.createElementNS(O,"_");j.appendChild(t.cloneNode(!1));var M=j.innerHTML.replace("><",">"+t.innerHTML+"<");return j.innerHTML="",M}catch(e){}}var P,N,E=i(t),S=e.showHidden?r(t):E;if(0===S.length||v(t)&&(1===S.length&&"stack"===S[0]||2===S.length&&"description"===S[0]&&"stack"===S[1])){if("function"==typeof t)return N=(P=o(t))?": "+P:"",e.stylize("[Function"+N+"]","special");if(w(t))return e.stylize(RegExp.prototype.toString.call(t),"regexp");if(m(t))return e.stylize(Date.prototype.toUTCString.call(t),"date");if(v(t))return p(t)}var k,A="",D=!1,T=!1,q=["{","}"];if(b(t)&&(T=!0,q=["[","]"]),g(t)&&(D=!0,q=["[","]"]),"function"==typeof t&&(A=" [Function"+(N=(P=o(t))?": "+P:"")+"]"),w(t)&&(A=" "+RegExp.prototype.toString.call(t)),m(t)&&(A=" "+Date.prototype.toUTCString.call(t)),v(t))return p(t);if(0===S.length&&(!D||0==t.length))return q[0]+A+q[1];if(s<0)return w(t)?e.stylize(RegExp.prototype.toString.call(t),"regexp"):e.stylize("[Object]","special");if(e.seen.push(t),D)k=l(e,t,s,E,S);else {if(T)return h(t);k=S.map((function(n){return d(e,t,s,E,n,D)}));}return e.seen.pop(),y(k,A,q)}function f(e,t){switch(typeof t){case"undefined":return e.stylize("undefined","undefined");case"string":var n="'"+JSON.stringify(t).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return e.stylize(n,"string");case"number":return 0===t&&1/t==-1/0?e.stylize("-0","number"):e.stylize(""+t,"number");case"boolean":return e.stylize(""+t,"boolean");case"symbol":return e.stylize(t.toString(),"symbol");case"bigint":return e.stylize(t.toString()+"n","bigint")}if(null===t)return e.stylize("null","null")}function p(e){return "["+Error.prototype.toString.call(e)+"]"}function l(e,t,n,o,r){for(var i=[],s=0,a=t.length;s=s.truncateThreshold-7){t+="...";break}t+=e[n]+", ";}return -1!==(t+=" ]").indexOf(", ]")&&(t=t.replace(", ]"," ]")),t}function d(e,t,n,o,r,i){var s,a,c=Object.getOwnPropertyDescriptor(t,r);if(c&&(c.get?a=c.set?e.stylize("[Getter/Setter]","special"):e.stylize("[Getter]","special"):c.set&&(a=e.stylize("[Setter]","special"))),o.indexOf(r)<0&&(s="["+r+"]"),a||(e.seen.indexOf(t[r])<0?(a=u(e,t[r],null===n?null:n-1)).indexOf("\n")>-1&&(a=i?a.split("\n").map((function(e){return " "+e})).join("\n").substr(2):"\n"+a.split("\n").map((function(e){return " "+e})).join("\n")):a=e.stylize("[Circular]","special")),void 0===s){if(i&&r.match(/^\d+$/))return a;(s=JSON.stringify(""+r)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(s=s.substr(1,s.length-2),s=e.stylize(s,"name")):(s=s.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),s=e.stylize(s,"string"));}return s+": "+a}function y(e,t,n){return e.reduce((function(e,t){return e+t.length+1}),0)>60?n[0]+(""===t?"":t+"\n ")+" "+e.join(",\n ")+" "+n[1]:n[0]+t+" "+e.join(", ")+" "+n[1]}function b(e){return "object"==typeof e&&/\w+Array]$/.test(x(e))}function g(e){return Array.isArray(e)||"object"==typeof e&&"[object Array]"===x(e)}function w(e){return "object"==typeof e&&"[object RegExp]"===x(e)}function m(e){return "object"==typeof e&&"[object Date]"===x(e)}function v(e){return "object"==typeof e&&"[object Error]"===x(e)}function x(e){return Object.prototype.toString.call(e)}},{"../config":4,"./getEnumerableProperties":17,"./getProperties":22,"get-func-name":37}],25:[function(e,t,n){ -/*! - * Chai - isNaN utility - * Copyright(c) 2012-2015 Sakthipriyan Vairamani - * MIT Licensed - */ -function o(e){return e!=e}t.exports=Number.isNaN||o;},{}],26:[function(e,t,n){var o=e("../config"); -/*! - * Chai - isProxyEnabled helper - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */t.exports=function(){return o.useProxy&&"undefined"!=typeof Proxy&&"undefined"!=typeof Reflect};},{"../config":4}],27:[function(e,t,n){ -/*! - * Chai - flag utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -/*! - * Module dependencies - */ -var o=e("./inspect"),r=e("../config");t.exports=function(e){var t=o(e),n=Object.prototype.toString.call(e);if(r.truncateThreshold&&t.length>=r.truncateThreshold){if("[object Function]"===n)return e.name&&""!==e.name?"[Function: "+e.name+"]":"[Function]";if("[object Array]"===n)return "[ Array("+e.length+") ]";if("[object Object]"===n){var i=Object.keys(e);return "{ Object ("+(i.length>2?i.splice(0,2).join(", ")+", ...":i.join(", "))+") }"}return t}return t};},{"../config":4,"./inspect":24}],28:[function(e,t,n){ -/*! - * Chai - overwriteChainableMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -var o=e("../../chai"),r=e("./transferFlags");t.exports=function(e,t,n,i){var s=e.__methods[t],a=s.chainingBehavior;s.chainingBehavior=function(){var e=i(a).call(this);if(void 0!==e)return e;var t=new o.Assertion;return r(this,t),t};var c=s.method;s.method=function(){var e=n(c).apply(this,arguments);if(void 0!==e)return e;var t=new o.Assertion;return r(this,t),t};};},{"../../chai":2,"./transferFlags":33}],29:[function(e,t,n){ -/*! - * Chai - overwriteMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -var o=e("./addLengthGuard"),r=e("../../chai"),i=e("./flag"),s=e("./proxify"),a=e("./transferFlags");t.exports=function(e,t,n){var c=e[t],u=function(){throw new Error(t+" is not a function")};c&&"function"==typeof c&&(u=c);var f=function(){i(this,"lockSsfi")||i(this,"ssfi",f);var e=i(this,"lockSsfi");i(this,"lockSsfi",!0);var t=n(u).apply(this,arguments);if(i(this,"lockSsfi",e),void 0!==t)return t;var o=new r.Assertion;return a(this,o),o};o(f,t,!1),e[t]=s(f,t);};},{"../../chai":2,"./addLengthGuard":10,"./flag":15,"./proxify":31,"./transferFlags":33}],30:[function(e,t,n){ -/*! - * Chai - overwriteProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -var o=e("../../chai"),r=e("./flag"),i=e("./isProxyEnabled"),s=e("./transferFlags");t.exports=function(e,t,n){var a=Object.getOwnPropertyDescriptor(e,t),c=function(){};a&&"function"==typeof a.get&&(c=a.get),Object.defineProperty(e,t,{get:function e(){i()||r(this,"lockSsfi")||r(this,"ssfi",e);var t=r(this,"lockSsfi");r(this,"lockSsfi",!0);var a=n(c).call(this);if(r(this,"lockSsfi",t),void 0!==a)return a;var u=new o.Assertion;return s(this,u),u},configurable:!0});};},{"../../chai":2,"./flag":15,"./isProxyEnabled":26,"./transferFlags":33}],31:[function(e,t,n){var o=e("../config"),r=e("./flag"),i=e("./getProperties"),s=e("./isProxyEnabled"),a=["__flags","__methods","_obj","assert"];function c(e,t,n){if(Math.abs(e.length-t.length)>=n)return n;for(var o=[],r=0;r<=e.length;r++)o[r]=Array(t.length+1).fill(0),o[r][0]=r;for(var i=0;i=n?o[r][i]=n:o[r][i]=Math.min(o[r-1][i]+1,o[r][i-1]+1,o[r-1][i-1]+(s===t.charCodeAt(i-1)?0:1));}return o[e.length][t.length]}t.exports=function(e,t){return s()?new Proxy(e,{get:function e(n,s){if("string"==typeof s&&-1===o.proxyExcludedKeys.indexOf(s)&&!Reflect.has(n,s)){if(t)throw Error("Invalid Chai property: "+t+"."+s+'. See docs for proper usage of "'+t+'".');var u=null,f=4;throw i(n).forEach((function(e){if(!Object.prototype.hasOwnProperty(e)&&-1===a.indexOf(e)){var t=c(s,e,f);t - * MIT Licensed - */ -/*! - * Module dependencies - */ -var o=e("./flag");t.exports=function(e,t){var n=o(e,"negate"),r=t[0];return n?!r:r};},{"./flag":15}],33:[function(e,t,n){ -/*! - * Chai - transferFlags utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ -t.exports=function(e,t,n){var o=e.__flags||(e.__flags=Object.create(null));for(var r in t.__flags||(t.__flags=Object.create(null)),n=3!==arguments.length||n,o)(n||"object"!==r&&"ssfi"!==r&&"lockSsfi"!==r&&"message"!=r)&&(t.__flags[r]=o[r]);};},{}],34:[function(e,t,n){ -/*! - * assertion-error - * Copyright(c) 2013 Jake Luer - * MIT Licensed - */ -/*! - * Return a function that will copy properties from - * one object to another excluding any originally - * listed. Returned function will create a new `{}`. - * - * @param {String} excluded properties ... - * @return {Function} - */ -function o(){var e=[].slice.call(arguments);function t(t,n){Object.keys(n).forEach((function(o){~e.indexOf(o)||(t[o]=n[o]);}));}return function(){for(var e=[].slice.call(arguments),n=0,o={};n - * MIT Licensed - */ -var o=e("type-detect");function r(){this._key="chai/deep-eql__"+Math.random()+Date.now();}r.prototype={get:function(e){return e[this._key]},set:function(e,t){Object.isExtensible(e)&&Object.defineProperty(e,this._key,{value:t,configurable:!0});}};var i="function"==typeof WeakMap?WeakMap:r; -/*! - * Check to see if the MemoizeMap has recorded a result of the two operands - * - * @param {Mixed} leftHandOperand - * @param {Mixed} rightHandOperand - * @param {MemoizeMap} memoizeMap - * @returns {Boolean|null} result -*/function s(e,t,n){if(!n||O(e)||O(t))return null;var o=n.get(e);if(o){var r=o.get(t);if("boolean"==typeof r)return r}return null} -/*! - * Set the result of the equality into the MemoizeMap - * - * @param {Mixed} leftHandOperand - * @param {Mixed} rightHandOperand - * @param {MemoizeMap} memoizeMap - * @param {Boolean} result -*/function a(e,t,n,o){if(n&&!O(e)&&!O(t)){var r=n.get(e);r?r.set(t,o):((r=new i).set(t,o),n.set(e,r));}} -/*! - * Primary Export - */function c(e,t,n){if(n&&n.comparator)return f(e,t,n);var o=u(e,t);return null!==o?o:f(e,t,n)}function u(e,t){return e===t?0!==e||1/e==1/t:e!=e&&t!=t||!O(e)&&!O(t)&&null} -/*! - * The main logic of the `deepEqual` function. - * - * @param {Mixed} leftHandOperand - * @param {Mixed} rightHandOperand - * @param {Object} [options] (optional) Additional options - * @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality. - * @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of - complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular - references to blow the stack. - * @return {Boolean} equal match -*/function f(e,t,n){(n=n||{}).memoize=!1!==n.memoize&&(n.memoize||new i);var r=n&&n.comparator,c=s(e,t,n.memoize);if(null!==c)return c;var f=s(t,e,n.memoize);if(null!==f)return f;if(r){var l=r(e,t);if(!1===l||!0===l)return a(e,t,n.memoize,l),l;var h=u(e,t);if(null!==h)return h}var d=o(e);if(d!==o(t))return a(e,t,n.memoize,!1),!1;a(e,t,n.memoize,!0);var y=p(e,t,d,n);return a(e,t,n.memoize,y),y}function p(e,t,n,o){switch(n){case"String":case"Number":case"Boolean":case"Date":return c(e.valueOf(),t.valueOf());case"Promise":case"Symbol":case"function":case"WeakMap":case"WeakSet":case"Error":return e===t;case"Arguments":case"Int8Array":case"Uint8Array":case"Uint8ClampedArray":case"Int16Array":case"Uint16Array":case"Int32Array":case"Uint32Array":case"Float32Array":case"Float64Array":case"Array":return d(e,t,o);case"RegExp":return l(e,t);case"Generator":return y(e,t,o);case"DataView":return d(new Uint8Array(e.buffer),new Uint8Array(t.buffer),o);case"ArrayBuffer":return d(new Uint8Array(e),new Uint8Array(t),o);case"Set":case"Map":return h(e,t,o);default:return x(e,t,o)}} -/*! - * Compare two Regular Expressions for equality. - * - * @param {RegExp} leftHandOperand - * @param {RegExp} rightHandOperand - * @return {Boolean} result - */function l(e,t){return e.toString()===t.toString()} -/*! - * Compare two Sets/Maps for equality. Faster than other equality functions. - * - * @param {Set} leftHandOperand - * @param {Set} rightHandOperand - * @param {Object} [options] (Optional) - * @return {Boolean} result - */function h(e,t,n){if(e.size!==t.size)return !1;if(0===e.size)return !0;var o=[],r=[];return e.forEach((function(e,t){o.push([e,t]);})),t.forEach((function(e,t){r.push([e,t]);})),d(o.sort(),r.sort(),n)} -/*! - * Simple equality for flat iterable objects such as Arrays, TypedArrays or Node.js buffers. - * - * @param {Iterable} leftHandOperand - * @param {Iterable} rightHandOperand - * @param {Object} [options] (Optional) - * @return {Boolean} result - */function d(e,t,n){var o=e.length;if(o!==t.length)return !1;if(0===o)return !0;for(var r=-1;++r1?i(e,n,n.length-1):e,name:s.p||s.i,value:i(e,n)};return a.exists=o(a.parent,a.name),a}function c(e,t){return a(e,t).value}function u(e,t,n){return s(e,n,r(t)),e}t.exports={hasProperty:o,getPathInfo:a,getPathValue:c,setPathValue:u};},{}],39:[function(t,n,o){!function(e,t){"object"==typeof o&&void 0!==n?n.exports=t():e.typeDetect=t();}(this,(function(){var t="function"==typeof Promise,n="object"==typeof self?self:e,o="undefined"!=typeof Symbol,r="undefined"!=typeof Map,i="undefined"!=typeof Set,s="undefined"!=typeof WeakMap,a="undefined"!=typeof WeakSet,c="undefined"!=typeof DataView,u=o&&void 0!==Symbol.iterator,f=o&&void 0!==Symbol.toStringTag,p=i&&"function"==typeof Set.prototype.entries,l=r&&"function"==typeof Map.prototype.entries,h=p&&Object.getPrototypeOf((new Set).entries()),d=l&&Object.getPrototypeOf((new Map).entries()),y=u&&"function"==typeof Array.prototype[Symbol.iterator],b=y&&Object.getPrototypeOf([][Symbol.iterator]()),g=u&&"function"==typeof String.prototype[Symbol.iterator],w=g&&Object.getPrototypeOf(""[Symbol.iterator]()),m=8,v=-1;function x(e){var o=typeof e;if("object"!==o)return o;if(null===e)return "null";if(e===n)return "global";if(Array.isArray(e)&&(!1===f||!(Symbol.toStringTag in e)))return "Array";if("object"==typeof window&&null!==window){if("object"==typeof window.location&&e===window.location)return "Location";if("object"==typeof window.document&&e===window.document)return "Document";if("object"==typeof window.navigator){if("object"==typeof window.navigator.mimeTypes&&e===window.navigator.mimeTypes)return "MimeTypeArray";if("object"==typeof window.navigator.plugins&&e===window.navigator.plugins)return "PluginArray"}if(("function"==typeof window.HTMLElement||"object"==typeof window.HTMLElement)&&e instanceof window.HTMLElement){if("BLOCKQUOTE"===e.tagName)return "HTMLQuoteElement";if("TD"===e.tagName)return "HTMLTableDataCellElement";if("TH"===e.tagName)return "HTMLTableHeaderCellElement"}}var u=f&&e[Symbol.toStringTag];if("string"==typeof u)return u;var p=Object.getPrototypeOf(e);return p===RegExp.prototype?"RegExp":p===Date.prototype?"Date":t&&p===Promise.prototype?"Promise":i&&p===Set.prototype?"Set":r&&p===Map.prototype?"Map":a&&p===WeakSet.prototype?"WeakSet":s&&p===WeakMap.prototype?"WeakMap":c&&p===DataView.prototype?"DataView":r&&p===d?"Map Iterator":i&&p===h?"Set Iterator":y&&p===b?"Array Iterator":g&&p===w?"String Iterator":null===p?"Object":Object.prototype.toString.call(e).slice(m,v)}return x}));},{}]},{},[1])(1);o.version;o.AssertionError;o.use;o.util;o.config;o.Assertion;var f=o.expect;o.should;o.Should;o.assert; - -// 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; -function isLength(dimension) { - return 'unit' in dimension && [ - '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' - ].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 (nextCodepoint == null) { - 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; - } - if (isIdent(name.charAt(1))) { - return true; - } - return true; -} -function isNumber(name) { - if (name.length == 0) { - return false; - } - let codepoint = name.charCodeAt(0); - let i = 0; - const j = name.length; - // '+' '-' - 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 = 0; - while (index++ < name.length) { - if (isDigit(name.charCodeAt(name.length - index))) { - index--; - break; - } - if (index == 3) { - break; - } - } - if (index == 0 || index > 3) { - return false; - } - const number = name.slice(0, -index); - return number.length > 0 && isIdentStart(name.charCodeAt(name.length - index)) && isNumber(number); -} -function isPercentage(name) { - return name.endsWith('%') && isNumber(name.slice(0, -1)); -} -function parseDimension(name) { - let index = 0; - while (index++ < name.length) { - if (isDigit(name.charCodeAt(name.length - index))) { - index--; - break; - } - if (index == 3) { - 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 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); -} - -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", - properties: [ - "border-top-width", - "border-right-width", - "border-bottom-width", - "border-left-width" - ], - types: [ - "Length", - "Perc" - ], - keywords: [ - "thin", - "medium", - "thick" - ] - }, - "border-top-width": { - shorthand: "border-width" - }, - "border-right-width": { - shorthand: "border-width" - }, - "border-bottom-width": { - shorthand: "border-width" - }, - "border-left-width": { - shorthand: "border-width" - }, - "border-style": { - shorthand: "border-style", - properties: [ - "border-top-style", - "border-right-style", - "border-bottom-style", - "border-left-style" - ], - types: [ - ], - keywords: [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ] - }, - "border-top-style": { - shorthand: "border-style" - }, - "border-right-style": { - shorthand: "border-style" - }, - "border-bottom-style": { - shorthand: "border-style" - }, - "border-left-style": { - shorthand: "border-style" - }, - "border-color": { - shorthand: "border-color", - properties: [ - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color" - ], - types: [ - "Color" - ], - keywords: [ - ] - }, - "border-top-color": { - shorthand: "border-color" - }, - "border-right-color": { - shorthand: "border-color" - }, - "border-bottom-color": { - shorthand: "border-color" - }, - "border-left-color": { - shorthand: "border-color" - } -}; -var map = { - outline: { - shorthand: "outline", - pattern: "outline-color outline-style outline-width", - keywords: [ - "none" - ], - "default": [ - "0", - "none" - ], - properties: { - "outline-color": { - types: [ - "Color" - ], - "default": [ - "currentColor", - "invert" - ], - keywords: [ - "currentColor", - "invert" - ] - }, - "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" - ], - keywords: [ - ] - }, - "background-image": { - types: [ - "UrlFunc" - ], - "default": [ - "none" - ], - keywords: [ - "none" - ] - }, - "background-attachment": { - types: [ - ], - "default": [ - "scroll" - ], - keywords: [ - "scroll", - "fixed", - "local" - ] - }, - "background-clip": { - types: [ - ], - "default": [ - "border-box" - ], - keywords: [ - "border-box", - "padding-box", - "content-box", - "text" - ] - }, - "background-origin": { - types: [ - ], - "default": [ - "padding-box" - ], - 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; - -// name to color -const COLORS_NAMES = Object.seal({ - 'aliceblue': '#f0f8ff', - 'antiquewhite': '#faebd7', - 'aqua': '#00ffff', - 'aquamarine': '#7fffd4', - 'azure': '#f0ffff', - 'beige': '#f5f5dc', - 'bisque': '#ffe4c4', - 'black': '#000000', - 'blanchedalmond': '#ffebcd', - 'blue': '#0000ff', - 'blueviolet': '#8a2be2', - 'brown': '#a52a2a', - 'burlywood': '#deb887', - 'cadetblue': '#5f9ea0', - 'chartreuse': '#7fff00', - 'chocolate': '#d2691e', - 'coral': '#ff7f50', - 'cornflowerblue': '#6495ed', - 'cornsilk': '#fff8dc', - 'crimson': '#dc143c', - 'cyan': '#00ffff', - 'darkblue': '#00008b', - 'darkcyan': '#008b8b', - 'darkgoldenrod': '#b8860b', - 'darkgray': '#a9a9a9', - 'darkgrey': '#a9a9a9', - 'darkgreen': '#006400', - 'darkkhaki': '#bdb76b', - 'darkmagenta': '#8b008b', - 'darkolivegreen': '#556b2f', - 'darkorange': '#ff8c00', - 'darkorchid': '#9932cc', - 'darkred': '#8b0000', - 'darksalmon': '#e9967a', - 'darkseagreen': '#8fbc8f', - 'darkslateblue': '#483d8b', - 'darkslategray': '#2f4f4f', - 'darkslategrey': '#2f4f4f', - 'darkturquoise': '#00ced1', - 'darkviolet': '#9400d3', - 'deeppink': '#ff1493', - 'deepskyblue': '#00bfff', - 'dimgray': '#696969', - 'dimgrey': '#696969', - 'dodgerblue': '#1e90ff', - 'firebrick': '#b22222', - 'floralwhite': '#fffaf0', - 'forestgreen': '#228b22', - 'fuchsia': '#ff00ff', - 'gainsboro': '#dcdcdc', - 'ghostwhite': '#f8f8ff', - 'gold': '#ffd700', - 'goldenrod': '#daa520', - 'gray': '#808080', - 'grey': '#808080', - 'green': '#008000', - 'greenyellow': '#adff2f', - 'honeydew': '#f0fff0', - 'hotpink': '#ff69b4', - 'indianred': '#cd5c5c', - 'indigo': '#4b0082', - 'ivory': '#fffff0', - 'khaki': '#f0e68c', - 'lavender': '#e6e6fa', - 'lavenderblush': '#fff0f5', - 'lawngreen': '#7cfc00', - 'lemonchiffon': '#fffacd', - 'lightblue': '#add8e6', - 'lightcoral': '#f08080', - 'lightcyan': '#e0ffff', - 'lightgoldenrodyellow': '#fafad2', - 'lightgray': '#d3d3d3', - 'lightgrey': '#d3d3d3', - 'lightgreen': '#90ee90', - 'lightpink': '#ffb6c1', - 'lightsalmon': '#ffa07a', - 'lightseagreen': '#20b2aa', - 'lightskyblue': '#87cefa', - 'lightslategray': '#778899', - 'lightslategrey': '#778899', - 'lightsteelblue': '#b0c4de', - 'lightyellow': '#ffffe0', - 'lime': '#00ff00', - 'limegreen': '#32cd32', - 'linen': '#faf0e6', - 'magenta': '#ff00ff', - 'maroon': '#800000', - 'mediumaquamarine': '#66cdaa', - 'mediumblue': '#0000cd', - 'mediumorchid': '#ba55d3', - 'mediumpurple': '#9370d8', - 'mediumseagreen': '#3cb371', - 'mediumslateblue': '#7b68ee', - 'mediumspringgreen': '#00fa9a', - 'mediumturquoise': '#48d1cc', - 'mediumvioletred': '#c71585', - 'midnightblue': '#191970', - 'mintcream': '#f5fffa', - 'mistyrose': '#ffe4e1', - 'moccasin': '#ffe4b5', - 'navajowhite': '#ffdead', - 'navy': '#000080', - 'oldlace': '#fdf5e6', - 'olive': '#808000', - 'olivedrab': '#6b8e23', - 'orange': '#ffa500', - 'orangered': '#ff4500', - 'orchid': '#da70d6', - 'palegoldenrod': '#eee8aa', - 'palegreen': '#98fb98', - 'paleturquoise': '#afeeee', - 'palevioletred': '#d87093', - 'papayawhip': '#ffefd5', - 'peachpuff': '#ffdab9', - 'peru': '#cd853f', - 'pink': '#ffc0cb', - 'plum': '#dda0dd', - 'powderblue': '#b0e0e6', - 'purple': '#800080', - 'red': '#ff0000', - 'rosybrown': '#bc8f8f', - 'royalblue': '#4169e1', - 'saddlebrown': '#8b4513', - 'salmon': '#fa8072', - 'sandybrown': '#f4a460', - 'seagreen': '#2e8b57', - 'seashell': '#fff5ee', - 'sienna': '#a0522d', - 'silver': '#c0c0c0', - 'skyblue': '#87ceeb', - 'slateblue': '#6a5acd', - 'slategray': '#708090', - 'slategrey': '#708090', - 'snow': '#fffafa', - 'springgreen': '#00ff7f', - 'steelblue': '#4682b4', - 'tan': '#d2b48c', - 'teal': '#008080', - 'thistle': '#d8bfd8', - 'tomato': '#ff6347', - 'turquoise': '#40e0d0', - 'violet': '#ee82ee', - 'wheat': '#f5deb3', - 'white': '#ffffff', - 'whitesmoke': '#f5f5f5', - 'yellow': '#ffff00', - 'yellowgreen': '#9acd32', - 'rebeccapurple': '#663399', - 'transparent': '#00000000' -}); -// color to name -const NAMES_COLORS = Object.seal({ - '#f0f8ff': 'aliceblue', - '#faebd7': 'antiquewhite', - // '#00ffff': 'aqua', - '#7fffd4': 'aquamarine', - '#f0ffff': 'azure', - '#f5f5dc': 'beige', - '#ffe4c4': 'bisque', - '#000000': 'black', - '#ffebcd': 'blanchedalmond', - '#0000ff': 'blue', - '#8a2be2': 'blueviolet', - '#a52a2a': 'brown', - '#deb887': 'burlywood', - '#5f9ea0': 'cadetblue', - '#7fff00': 'chartreuse', - '#d2691e': 'chocolate', - '#ff7f50': 'coral', - '#6495ed': 'cornflowerblue', - '#fff8dc': 'cornsilk', - '#dc143c': 'crimson', - '#00ffff': 'cyan', - '#00008b': 'darkblue', - '#008b8b': 'darkcyan', - '#b8860b': 'darkgoldenrod', - // '#a9a9a9': 'darkgray', - '#a9a9a9': 'darkgrey', - '#006400': 'darkgreen', - '#bdb76b': 'darkkhaki', - '#8b008b': 'darkmagenta', - '#556b2f': 'darkolivegreen', - '#ff8c00': 'darkorange', - '#9932cc': 'darkorchid', - '#8b0000': 'darkred', - '#e9967a': 'darksalmon', - '#8fbc8f': 'darkseagreen', - '#483d8b': 'darkslateblue', - // '#2f4f4f': 'darkslategray', - '#2f4f4f': 'darkslategrey', - '#00ced1': 'darkturquoise', - '#9400d3': 'darkviolet', - '#ff1493': 'deeppink', - '#00bfff': 'deepskyblue', - // '#696969': 'dimgray', - '#696969': 'dimgrey', - '#1e90ff': 'dodgerblue', - '#b22222': 'firebrick', - '#fffaf0': 'floralwhite', - '#228b22': 'forestgreen', - // '#ff00ff': 'fuchsia', - '#dcdcdc': 'gainsboro', - '#f8f8ff': 'ghostwhite', - '#ffd700': 'gold', - '#daa520': 'goldenrod', - // '#808080': 'gray', - '#808080': 'grey', - '#008000': 'green', - '#adff2f': 'greenyellow', - '#f0fff0': 'honeydew', - '#ff69b4': 'hotpink', - '#cd5c5c': 'indianred', - '#4b0082': 'indigo', - '#fffff0': 'ivory', - '#f0e68c': 'khaki', - '#e6e6fa': 'lavender', - '#fff0f5': 'lavenderblush', - '#7cfc00': 'lawngreen', - '#fffacd': 'lemonchiffon', - '#add8e6': 'lightblue', - '#f08080': 'lightcoral', - '#e0ffff': 'lightcyan', - '#fafad2': 'lightgoldenrodyellow', - // '#d3d3d3': 'lightgray', - '#d3d3d3': 'lightgrey', - '#90ee90': 'lightgreen', - '#ffb6c1': 'lightpink', - '#ffa07a': 'lightsalmon', - '#20b2aa': 'lightseagreen', - '#87cefa': 'lightskyblue', - // '#778899': 'lightslategray', - '#778899': 'lightslategrey', - '#b0c4de': 'lightsteelblue', - '#ffffe0': 'lightyellow', - '#00ff00': 'lime', - '#32cd32': 'limegreen', - '#faf0e6': 'linen', - '#ff00ff': 'magenta', - '#800000': 'maroon', - '#66cdaa': 'mediumaquamarine', - '#0000cd': 'mediumblue', - '#ba55d3': 'mediumorchid', - '#9370d8': 'mediumpurple', - '#3cb371': 'mediumseagreen', - '#7b68ee': 'mediumslateblue', - '#00fa9a': 'mediumspringgreen', - '#48d1cc': 'mediumturquoise', - '#c71585': 'mediumvioletred', - '#191970': 'midnightblue', - '#f5fffa': 'mintcream', - '#ffe4e1': 'mistyrose', - '#ffe4b5': 'moccasin', - '#ffdead': 'navajowhite', - '#000080': 'navy', - '#fdf5e6': 'oldlace', - '#808000': 'olive', - '#6b8e23': 'olivedrab', - '#ffa500': 'orange', - '#ff4500': 'orangered', - '#da70d6': 'orchid', - '#eee8aa': 'palegoldenrod', - '#98fb98': 'palegreen', - '#afeeee': 'paleturquoise', - '#d87093': 'palevioletred', - '#ffefd5': 'papayawhip', - '#ffdab9': 'peachpuff', - '#cd853f': 'peru', - '#ffc0cb': 'pink', - '#dda0dd': 'plum', - '#b0e0e6': 'powderblue', - '#800080': 'purple', - '#ff0000': 'red', - '#bc8f8f': 'rosybrown', - '#4169e1': 'royalblue', - '#8b4513': 'saddlebrown', - '#fa8072': 'salmon', - '#f4a460': 'sandybrown', - '#2e8b57': 'seagreen', - '#fff5ee': 'seashell', - '#a0522d': 'sienna', - '#c0c0c0': 'silver', - '#87ceeb': 'skyblue', - '#6a5acd': 'slateblue', - // '#708090': 'slategray', - '#708090': 'slategrey', - '#fffafa': 'snow', - '#00ff7f': 'springgreen', - '#4682b4': 'steelblue', - '#d2b48c': 'tan', - '#008080': 'teal', - '#d8bfd8': 'thistle', - '#ff6347': 'tomato', - '#40e0d0': 'turquoise', - '#ee82ee': 'violet', - '#f5deb3': 'wheat', - '#ffffff': 'white', - '#f5f5f5': 'whitesmoke', - '#ffff00': 'yellow', - '#9acd32': 'yellowgreen', - '#663399': 'rebeccapurple', - '#00000000': 'transparent' -}); -function rgb2Hex(token) { - let value = '#'; - let t; - // @ts-ignore - for (let i = 0; i < 6; i += 2) { - // @ts-ignore - t = token.chi[i]; - // @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 ((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 == 'Dimension') { - 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 indents = []; -function render(data, opt = {}) { - const options = Object.assign(opt.compress ? { - indent: '', - newLine: '', - preserveLicense: false, - removeComments: true, - colorConvert: true - } : { - indent: ' ', - newLine: '\n', - compress: false, - preserveLicense: false, - removeComments: false, - colorConvert: true - }, opt); - function reducer(acc, curr, index, original) { - if (curr.typ == 'Comment' && options.removeComments) { - if (!options.preserveLicense || !curr.val.startsWith('/*!')) { - return acc; - } - } - if (options.compress && curr.typ == 'Whitespace') { - if (original[index + 1]?.typ == 'Start-parens' || - (index > 0 && (original[index - 1].typ == 'Pseudo-class-func' || - original[index - 1].typ == 'End-parens' || - original[index - 1].typ == 'UrlFunc' || - original[index - 1].typ == 'Func' || - (original[index - 1].typ == 'Color' && - original[index - 1].kin != 'hex' && - original[index - 1].kin != 'lit')))) { - return acc; - } - } - return acc + renderToken(curr, options); - } - return { code: doRender(data, options, reducer) }; -} -function doRender(data, options, reducer, level = 0) { - 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 'Comment': - return options.removeComments ? '' : data.val; - case 'StyleSheet': - return data.chi.reduce((css, node) => { - const str = doRender(node, options, reducer); - 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};`; - } - // @ts-ignore - let children = data.chi.reduce((css, node) => { - let str; - if (node.typ == 'Comment') { - str = options.removeComments ? '' : node.val; - } - else if (node.typ == 'Declaration') { - str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`; - } - else if (node.typ == 'AtRule' && !('chi' in node)) { - str = `@${node.nam} ${node.val};`; - } - else { - str = doRender(node, options, reducer, level + 1); - } - if (css === '') { - return str; - } - if (str === '') { - return css; - } - if (str !== '') - return `${css}${options.newLine}${indentSub}${str}`; - }, ''); - if (children.endsWith(';')) { - children = children.slice(0, -1); - } - if (data.typ == 'AtRule') { - return `@${data.nam} ${data.val ? 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 = {}) { - switch (token.typ) { - case 'Color': - if (options.compress || options.colorConvert) { - let value = token.kin == 'hex' ? 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 'Func': - case 'UrlFunc': - // @ts-ignore - return token.val + '(' + token.chi.reduce((acc, curr) => { - if (options.removeComments && curr.typ == 'Comment') { - if (!options.preserveLicense || !curr.val.startsWith('/*!')) { - return acc; - } - } - return acc + renderToken(curr, options); - }, '') + ')'; - case 'Includes': - return '~='; - case 'Dash-match': - return '|='; - case 'Lt': - return '<'; - case 'Gt': - return '>'; - case 'Start-parens': - 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 'Time': - case 'Frequency': - case 'Angle': - case 'Length': - case 'Dimension': - const val = (+token.val).toString(); - 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'; - } - const chr = val.charAt(0); - if (chr == '-') { - const slice = val.slice(0, 2); - if (slice == '-0') { - return (val.length == 2 ? '0' : '-' + val.slice(2)) + token.unit; - } - } - else if (chr == '0') { - return val.slice(1) + token.unit; - } - return val + token.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) { - return ''; - } - case 'Url-token': - case 'At-rule': - case 'Hash': - case 'Pseudo-class': - case 'Pseudo-class-func': - case 'Literal': - case 'String': - case 'Iden': - case 'Delim': - return token.val; - } - throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`); -} - -function tokenize(iterator, errors, options) { - const tokens = []; - const src = options.src; - const stack = []; - const root = { - typ: "StyleSheet", - chi: [] - }; - const position = { - ind: 0, - lin: 1, - col: 1 - }; - let value; - let buffer = ''; - let ind = -1; - let lin = 1; - let col = 0; - let total = iterator.length; - let map = new Map; - let context = root; - if (options.location) { - root.loc = { - sta: { - ind: 0, - lin: 1, - col: 1 - }, - src: '' - }; - } - function getType(val) { - if (val === '') { - throw new Error('empty string?'); - } - if (val == ':') { - return { typ: 'Colon' }; - } - if (val == ')') { - return { typ: 'End-parens' }; - } - if (val == '(') { - return { typ: 'Start-parens' }; - } - if (val == '=') { - return { typ: 'Delim', val }; - } - if (val == ';') { - return { typ: 'Semi-colon' }; - } - if (val == ',') { - return { typ: 'Comma' }; - } - if (val == '<') { - return { typ: 'Lt' }; - } - if (val == '>') { - return { typ: 'Gt' }; - } - if (isPseudo(val)) { - return { - typ: val.endsWith('(') ? 'Pseudo-class-func' : 'Pseudo-class', - val - }; - } - if (isAtKeyword(val)) { - return { - typ: 'At-rule', - val: val.slice(1) - // buffer: buffer.slice() - }; - } - if (isFunction(val)) { - val = val.slice(0, -1); - return { - typ: val == 'url' ? 'UrlFunc' : 'Func', - val, - chi: [] - }; - } - if (isNumber(val)) { - return { - typ: 'Number', - val - }; - } - if (isDimension(val)) { - return parseDimension(val); - } - if (isPercentage(val)) { - return { - typ: 'Perc', - val: val.slice(0, -1) - }; - } - if (val == 'currentColor') { - return { - typ: 'Color', - val, - kin: 'lit' - }; - } - if (isIdent(val)) { - return { - typ: 'Iden', - val - }; - } - if (val.charAt(0) == '#' && isHash(val)) { - return { - typ: 'Hash', - val - }; - } - if ('"\''.includes(val.charAt(0))) { - return { - typ: 'Unclosed-string', - val - }; - } - return { - typ: 'Literal', - val - }; - } - // consume and throw away - function consume(open, close) { - let count = 1; - let chr; - while (true) { - chr = next(); - if (chr == '\\') { - if (next() === '') { - break; - } - continue; - } - else if (chr == '/' && peek() == '*') { - next(); - while (true) { - chr = next(); - if (chr === '') { - break; - } - if (chr == '*' && peek() == '/') { - next(); - break; - } - } - } - else if (chr == close) { - count--; - } - else if (chr == open) { - count++; - } - if (chr === '' || count == 0) { - break; - } - } - } - function parseNode(tokens) { - let i = 0; - let loc; - for (i = 0; i < tokens.length; i++) { - if (tokens[i].typ == 'Comment') { - // @ts-ignore - context.chi.push(tokens[i]); - const position = map.get(tokens[i]); - loc = { - sta: position, - src - }; - if (options.location) { - tokens[i].loc = loc; - } - } - else if (tokens[i].typ != 'Whitespace') { - break; - } - } - tokens = tokens.slice(i); - const delim = tokens.pop(); - while (['Whitespace', 'Bad-string', 'Bad-comment'].includes(tokens[tokens.length - 1]?.typ)) { - tokens.pop(); - } - if (tokens.length == 0) { - return null; - } - if (tokens[0]?.typ == 'At-rule') { - const atRule = tokens.shift(); - const position = map.get(atRule); - if (atRule.val == 'charset' && position.ind > 0) { - errors.push({ action: 'drop', message: 'invalid @charset', location: { src, ...position } }); - return null; - } - while (['Whitespace'].includes(tokens[0]?.typ)) { - tokens.shift(); - } - if (atRule.val == 'import') { - // only @charset and @layer are accepted before @import - if (context.chi.length > 0) { - let i = context.chi.length; - while (i--) { - const type = context.chi[i].typ; - if (type == 'Comment') { - continue; - } - if (type != 'AtRule') { - errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); - return null; - } - const name = context.chi[i].nam; - if (name != 'charset' && name != 'import' && name != 'layer') { - errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); - return null; - } - break; - } - } - // @ts-ignore - if (tokens[0]?.typ != 'String' && tokens[0]?.typ != 'UrlFunc') { - errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); - return null; - } - // @ts-ignore - if (tokens[0].typ == 'UrlFunc' && tokens[1]?.typ != 'Url-token' && tokens[1]?.typ != 'String') { - errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } }); - return null; - } - } - if (atRule.val == 'import') { - // @ts-ignore - if (tokens[0].typ == 'UrlFunc' && tokens[1].typ == 'Url-token') { - tokens.shift(); - // const token: Token = tokens.shift(); - // @ts-ignore - tokens[0].typ = 'String'; - // @ts-ignore - tokens[0].val = `"${tokens[0].val}"`; - // tokens[0] = token; - } - } - // https://www.w3.org/TR/css-nesting-1/#conditionals - // allowed nesting at-rules - // there must be a top level rule in the stack - const node = { - typ: 'AtRule', - nam: renderToken(atRule, { removeComments: true }), - val: tokens.reduce((acc, curr, index, array) => { - if (curr.typ == 'Whitespace') { - if (array[index + 1]?.typ == 'Start-parens' || - array[index - 1]?.typ == 'End-parens' || - array[index - 1]?.typ == 'Func') { - return acc; - } - } - return acc + renderToken(curr, { removeComments: true }); - }, '') - }; - if (node.nam == 'import') { - if (options.processImport) { - // @ts-ignore - let fileToken = tokens[tokens[0].typ == 'UrlFunc' ? 1 : 0]; - let file = fileToken.typ == 'String' ? fileToken.val.slice(1, -1) : fileToken.val; - if (!file.startsWith('data:')) ; - } - } - if (delim.typ == 'Block-start') { - node.chi = []; - } - loc = { - sta: position, - src - }; - if (options.location) { - node.loc = loc; - } - // @ts-ignore - context.chi.push(node); - return delim.typ == 'Block-start' ? node : null; - } - else { - // rule - if (delim.typ == 'Block-start') { - let inAttr = 0; - const position = map.get(tokens[0]); - if (context.typ == 'Rule') { - if (tokens[0]?.typ == 'Iden') { - errors.push({ action: 'drop', message: 'invalid nesting rule', location: { src, ...position } }); - return null; - } - } - const node = { - typ: 'Rule', - // @ts-ignore - sel: tokens.reduce((acc, curr) => { - if (acc[acc.length - 1].length == 0 && curr.typ == 'Whitespace') { - return acc; - } - if (inAttr > 0 && curr.typ == 'String') { - const ident = curr.val.slice(1, -1); - if (isIdent(ident)) { - // @ts-ignore - curr.typ = 'Iden'; - curr.val = ident; - } - } - if (curr.typ == 'Attr-start') { - inAttr++; - } - else if (curr.typ == 'Attr-end') { - inAttr--; - } - if (inAttr == 0 && curr.typ == "Comma") { - acc.push([]); - } - else { - acc[acc.length - 1].push(curr); - } - return acc; - }, [[]]).map(part => part.reduce((acc, p, index, array) => { - if (p.typ == 'Whitespace') { - if (array[index + 1]?.typ == 'Start-parens' || - array[index - 1]?.typ == 'End-parens') { - return acc; - } - } - return acc + renderToken(p, { removeComments: true }); - }, '')).join(), - chi: [] - }; - loc = { - sta: position, - src - }; - if (options.location) { - node.loc = loc; - } - // @ts-ignore - context.chi.push(node); - return node; - } - else { - // declaration - // @ts-ignore - let name = null; - // @ts-ignore - let value = null; - for (let i = 0; i < tokens.length; i++) { - if (tokens[i].typ == 'Comment') { - continue; - } - if (tokens[i].typ == 'Colon') { - name = tokens.slice(0, i); - value = tokens.slice(i + 1); - } - else if (['Func', 'Pseudo-class'].includes(tokens[i].typ) && tokens[i].val.startsWith(':')) { - tokens[i].val = tokens[i].val.slice(1); - if (tokens[i].typ == 'Pseudo-class') { - tokens[i].typ = 'Iden'; - } - name = tokens.slice(0, i); - value = tokens.slice(i); - } - } - if (name == null) { - name = tokens; - } - const position = map.get(name[0]); - // const rawName: string = (name.shift())?.val; - if (name.length > 0) { - for (let i = 1; i < name.length; i++) { - if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') { - errors.push({ - action: 'drop', - message: 'invalid declaration', - location: { src, ...position } - }); - return null; - } - } - } - // if (name.length == 0) { - // - // errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}}); - // return null; - // } - if (value == null) { - errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); - return null; - } - // let j: number = value.length - let i = 0; - let t; - for (; i < value.length; i++) { - t = value[i]; - if (t.typ == 'Iden') { - // named color - const value = t.val.toLowerCase(); - if (COLORS_NAMES[value] != null) { - Object.assign(t, { - typ: 'Color', - val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value, - kin: 'hex' - }); - } - continue; - } - if (t.typ == 'Hash' && isHexColor(t.val)) { - // hex color - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = 'hex'; - continue; - } - if (t.typ == 'Func' || t.typ == 'UrlFunc') { - // func color - let parens = 1; - let k = i; - let j = value.length; - let isScalar = true; - while (++k < j) { - switch (value[k].typ) { - case 'Start-parens': - case 'Func': - case 'UrlFunc': - parens++; - isScalar = false; - break; - case 'End-parens': - parens--; - break; - } - if (parens == 0) { - break; - } - } - t.chi = value.splice(i + 1, k - i); - if (t.chi.at(-1).typ == 'End-parens') { - t.chi.pop(); - } - if (isScalar) { - if (['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) { - // @ts-ignore - t.typ = 'Color'; - // @ts-ignore - t.kin = t.val; - let i = t.chi.length; - while (i-- > 0) { - if (t.chi[i].typ == 'Literal') { - if (t.chi[i + 1]?.typ == 'Whitespace') { - t.chi.splice(i + 1, 1); - } - if (t.chi[i - 1]?.typ == 'Whitespace') { - t.chi.splice(i - 1, 1); - i--; - } - } - } - } - else if (t.typ = 'UrlFunc') { - if (t.chi[0]?.typ == 'String') { - const value = t.chi[0].val.slice(1, -1); - if (/^[/%.a-zA-Z0-9_-]+$/.test(value)) { - t.chi[0].typ = 'Url-token'; - t.chi[0].val = value; - } - } - } - } - continue; - } - if (t.typ == 'Whitespace' || t.typ == 'Comment') { - value.slice(i, 1); - } - } - if (value.length == 0) { - errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); - return null; - } - const node = { - typ: 'Declaration', - // @ts-ignore - nam: renderToken(name.shift(), { removeComments: true }), - // @ts-ignore - val: value - }; - while (node.val[0]?.typ == 'Whitespace') { - node.val.shift(); - } - if (node.val.length == 0) { - errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } }); - return null; - } - loc = { - sta: position, - src - }; - if (options.location) { - node.loc = loc; - } - // @ts-ignore - context.chi.push(node); - return null; - } - } - } - function peek(count = 1) { - if (count == 1) { - return iterator.charAt(ind + 1); - } - return iterator.slice(ind + 1, ind + count + 1); - } - function prev(count = 1) { - if (count == 1) { - return ind == 0 ? '' : iterator.charAt(ind - 1); - } - return iterator.slice(ind - 1 - count, ind - 1); - } - function next(count = 1) { - let char = ''; - while (count-- > 0 && ind < total) { - const codepoint = iterator.charCodeAt(++ind); - if (codepoint == null) { - return char; - } - // if (codepoint < 0x80) { - char += iterator.charAt(ind); - // } - // else { - // - // const chr: string = String.fromCodePoint(codepoint); - // - // ind += chr.length - 1; - // char += chr; - // } - // ind += codepoint < 256 ? 1 : String.fromCodePoint(codepoint).length; - if (isNewLine(codepoint)) { - lin++; - col = 0; - // \r\n - // if (codepoint == 0xd && iterator.charCodeAt(i + 1) == 0xa) { - // offset++; - // ind++; - // } - } - else { - col++; - } - // ind++; - // i += offset; - } - return char; - } - function pushToken(token) { - tokens.push(token); - map.set(token, { ...position }); - position.ind = ind; - position.lin = lin; - position.col = col == 0 ? 1 : col; - // } - } - function consumeWhiteSpace() { - let count = 0; - while (isWhiteSpace(iterator.charAt(count + ind + 1).charCodeAt(0))) { - count++; - } - next(count); - return count; - } - function consumeString(quoteStr) { - const quote = quoteStr; - let value; - let hasNewLine = false; - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - buffer += quoteStr; - while (ind < total) { - value = peek(); - if (ind >= total) { - pushToken({ typ: hasNewLine ? 'Bad-string' : 'Unclosed-string', val: buffer }); - break; - } - if (value == '\\') { - // buffer += value; - if (ind >= total) { - // drop '\\' at the end - pushToken(getType(buffer)); - break; - } - buffer += next(2); - continue; - } - if (value == quote) { - buffer += value; - pushToken({ typ: hasNewLine ? 'Bad-string' : 'String', val: buffer }); - next(); - // i += value.length; - buffer = ''; - break; - } - if (isNewLine(value.charCodeAt(0))) { - hasNewLine = true; - } - if (hasNewLine && value == ';') { - pushToken({ typ: 'Bad-string', val: buffer }); - buffer = ''; - break; - } - buffer += value; - // i += value.length; - next(); - } - } - while (ind < total) { - value = next(); - if (ind >= total) { - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - break; - } - if (isWhiteSpace(value.charCodeAt(0))) { - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - while (ind < total) { - value = next(); - if (ind >= total) { - break; - } - if (!isWhiteSpace(value.charCodeAt(0))) { - break; - } - } - pushToken({ typ: 'Whitespace' }); - buffer = ''; - if (ind >= total) { - break; - } - } - switch (value) { - case '/': - if (buffer.length > 0 && tokens.at(-1)?.typ == 'Whitespace') { - pushToken(getType(buffer)); - buffer = ''; - if (peek() != '*') { - pushToken(getType(value)); - break; - } - } - buffer += value; - if (peek() == '*') { - buffer += '*'; - // i++; - next(); - while (ind < total) { - value = next(); - if (ind >= total) { - pushToken({ - typ: 'Bad-comment', val: buffer - }); - break; - } - if (value == '\\') { - buffer += value; - value = next(); - if (ind >= total) { - pushToken({ - typ: 'Bad-comment', - val: buffer - }); - break; - } - buffer += value; - continue; - } - if (value == '*') { - buffer += value; - value = next(); - if (ind >= total) { - pushToken({ - typ: 'Bad-comment', val: buffer - }); - break; - } - buffer += value; - if (value == '/') { - pushToken({ typ: 'Comment', val: buffer }); - buffer = ''; - break; - } - } - else { - buffer += value; - } - } - } - // else { - // - // pushToken(getType(buffer)); - // buffer = ''; - // } - break; - case '<': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - buffer += value; - value = next(); - if (ind >= total) { - break; - } - if (peek(3) == '!--') { - while (ind < total) { - value = next(); - if (ind >= total) { - break; - } - buffer += value; - if (value == '>' && prev(2) == '--') { - pushToken({ - typ: 'CDOCOMM', - val: buffer - }); - buffer = ''; - break; - } - } - } - if (ind >= total) { - pushToken({ typ: 'BADCDO', val: buffer }); - buffer = ''; - } - break; - case '\\': - value = next(); - // EOF - if (ind + 1 >= total) { - // end of stream ignore \\ - pushToken(getType(buffer)); - buffer = ''; - break; - } - buffer += value; - break; - case '"': - case "'": - consumeString(value); - break; - case '~': - case '|': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - buffer += value; - value = next(); - if (ind >= total) { - pushToken(getType(buffer)); - buffer = ''; - break; - } - if (value == '=') { - buffer += value; - pushToken({ - typ: buffer[0] == '~' ? 'Includes' : 'Dash-matches', - val: buffer - }); - buffer = ''; - break; - } - pushToken(getType(buffer)); - buffer = value; - break; - case '>': - if (tokens[tokens.length - 1]?.typ == 'Whitespace') { - tokens.pop(); - } - pushToken({ typ: 'Gt' }); - consumeWhiteSpace(); - break; - case ':': - case ',': - case '=': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - if (value == ':' && isIdent(peek())) { - buffer += value; - break; - } - // if (value == ',' && tokens[tokens.length - 1]?.typ == 'Whitespace') { - // - // tokens.pop(); - // } - pushToken(getType(value)); - buffer = ''; - while (isWhiteSpace(peek().charCodeAt(0))) { - next(); - } - break; - case ')': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - pushToken({ typ: 'End-parens' }); - break; - case '(': - if (buffer.length == 0) { - pushToken({ typ: 'Start-parens' }); - } - else { - buffer += value; - pushToken(getType(buffer)); - buffer = ''; - const token = tokens[tokens.length - 1]; - if (token.typ == 'UrlFunc' /* && token.val == 'url' */) { - // consume either string or url token - let whitespace = ''; - value = peek(); - while (isWhiteSpace(value.charCodeAt(0))) { - whitespace += value; - } - if (whitespace.length > 0) { - next(whitespace.length); - } - value = peek(); - if (value == '"' || value == "'") { - consumeString(next()); - let token = tokens[tokens.length - 1]; - if (['String', 'Literal'].includes(token.typ) && /^(["']?)[a-zA-Z0-9_/-][a-zA-Z0-9_/:.-]+(\1)$/.test(token.val)) { - if (token.typ == 'String') { - token.val = token.val.slice(1, -1); - } - // @ts-ignore - // token.typ = 'Url-token'; - } - break; - } - else { - buffer = ''; - do { - let cp = value.charCodeAt(0); - // EOF - - if (cp == null) { - pushToken({ typ: 'Bad-url-token', val: buffer }); - break; - } - // ')' - if (cp == 0x29 || cp == null) { - if (buffer.length == 0) { - pushToken({ typ: 'Bad-url-token', val: '' }); - } - else { - pushToken({ typ: 'Url-token', val: buffer }); - } - if (cp != null) { - pushToken(getType(next())); - } - break; - } - if (isWhiteSpace(cp)) { - whitespace = next(); - while (true) { - value = peek(); - cp = value.charCodeAt(0); - if (isWhiteSpace(cp)) { - whitespace += value; - continue; - } - break; - } - if (cp == null || cp == 0x29) { - continue; - } - // bad url token - buffer += next(whitespace.length); - do { - value = peek(); - cp = value.charCodeAt(0); - if (cp == null || cp == 0x29) { - break; - } - buffer += next(); - } while (true); - pushToken({ typ: 'Bad-url-token', val: buffer }); - continue; - } - buffer += next(); - value = peek(); - } while (true); - buffer = ''; - } - } - } - break; - case '[': - case ']': - case '{': - case '}': - case ';': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - pushToken(getBlockType(value)); - let node = null; - if (value == '{' || value == ';') { - node = parseNode(tokens); - if (node != null) { - stack.push(node); - // @ts-ignore - context = node; - } - else if (value == '{') { - // node == null - // consume and throw away until the closing '}' or EOF - consume('{', '}'); - } - tokens.length = 0; - map.clear(); - } - else if (value == '}') { - node = parseNode(tokens); - const previousNode = stack.pop(); - // @ts-ignore - context = stack[stack.length - 1] || root; - // if (options.location && context != root) { - // @ts-ignore - // context.loc.end = {ind, lin, col: col == 0 ? 1 : col} - // } - // @ts-ignore - if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - } - tokens.length = 0; - map.clear(); - buffer = ''; - } - // @ts-ignore - // if (node != null && options.location && ['}', ';'].includes(value) && context.chi[context.chi.length - 1].loc.end == null) { - // @ts-ignore - // context.chi[context.chi.length - 1].loc.end = {ind, lin, col}; - // } - break; - case '!': - if (buffer.length > 0) { - pushToken(getType(buffer)); - buffer = ''; - } - const important = peek(9); - if (important == 'important') { - if (tokens[tokens.length - 1]?.typ == 'Whitespace') { - tokens.pop(); - } - pushToken({ typ: 'Important' }); - next(9); - buffer = ''; - break; - } - buffer = '!'; - break; - default: - buffer += value; - break; - } - } - if (buffer.length > 0) { - pushToken(getType(buffer)); - } - if (tokens.length > 0) { - parseNode(tokens); - } - // pushToken({typ: 'EOF'}); - // - // if (col == 0) { - // - // col = 1; - // } - // if (options.location) { - // - // // @ts-ignore - // root.loc.end = {ind, lin, col}; - // - // for (const context of stack) { - // - // // @ts-ignore - // context.loc.end = {ind, lin, col}; - // } - // } - return root; -} -function getBlockType(chr) { - if (chr == ';') { - return { typ: 'Semi-colon' }; - } - if (chr == '{') { - return { typ: 'Block-start' }; - } - if (chr == '}') { - return { typ: 'Block-end' }; - } - if (chr == '[') { - return { typ: 'Attr-start' }; - } - if (chr == ']') { - return { typ: 'Attr-end' }; - } - throw new Error(`unhandled token: '${chr}'`); -} - -function eq(a, b) { - if ((typeof a != 'object') || typeof b != 'object') { - return a === b; - } - const k1 = Object.keys(a); - const k2 = Object.keys(b); - return k1.length == k2.length && - k1.every((key) => { - return eq(a[key], b[key]); - }); -} - -class PropertySet { - config; - declarations; - constructor(config) { - this.config = config; - this.declarations = new Map; - } - add(declaration) { - if (declaration.nam == this.config.shorthand) { - this.declarations.clear(); - this.declarations.set(declaration.nam, declaration); - } - else { - // expand shorthand - if (declaration.nam != this.config.shorthand && this.declarations.has(this.config.shorthand)) { - let isValid = true; - let current = -1; - const tokens = []; - // @ts-ignore - for (let token of this.declarations.get(this.config.shorthand).val) { - if (this.config.types.includes(token.typ) || (token.typ == 'Number' && token.val == '0' && - (this.config.types.includes('Length') || - this.config.types.includes('Angle') || - this.config.types.includes('Dimension')))) { - if (tokens.length == 0) { - tokens.push([]); - current++; - } - tokens[current].push(token); - continue; - } - if (token.typ != 'Whitespace' && token.typ != 'Comment') { - if (token.typ == 'Iden' && this.config.keywords.includes(token.val)) { - tokens[current].push(token); - } - if (token.typ == 'Literal' && token.val == this.config.separator) { - tokens.push([]); - current++; - continue; - } - isValid = false; - break; - } - } - if (isValid && tokens.length > 0) { - this.declarations.delete(this.config.shorthand); - for (const values of tokens) { - this.config.properties.forEach((property, index) => { - // if (property == declaration.nam) { - // - // return; - // } - if (!this.declarations.has(property)) { - this.declarations.set(property, { - typ: 'Declaration', - nam: property, - val: [] - }); - } - while (index > 0 && index >= values.length) { - if (index > 1) { - index %= 2; - } - else { - index = 0; - break; - } - } - // @ts-ignore - const val = this.declarations.get(property).val; - if (val.length > 0) { - val.push({ typ: 'Whitespace' }); - } - val.push({ ...values[index] }); - }); - } - } - this.declarations.set(declaration.nam, declaration); - return this; - } - // declaration.val = declaration.val.reduce((acc: Token[], token: Token) => { - // - // if (this.config.types.includes(token.typ) || ('0' == (token).val && ( - // this.config.types.includes('Length') || - // this.config.types.includes('Angle') || - // this.config.types.includes('Dimension'))) || (token.typ == 'Iden' && this.config.keywords.includes(token.val))) { - // - // acc.push(token); - // } - // - // return acc; - // }, []); - this.declarations.set(declaration.nam, declaration); - } - return this; - } - [Symbol.iterator]() { - let iterator; - const declarations = this.declarations; - if (declarations.size < this.config.properties.length || this.config.properties.some((property, index) => { - return !declarations.has(property) || (index > 0 && - // @ts-ignore - declarations.get(property).val.length != declarations.get(this.config.properties[Math.floor(index / 2)]).val.length); - })) { - iterator = declarations.values(); - } - else { - const values = []; - this.config.properties.forEach((property) => { - let index = 0; - // @ts-ignore - for (const token of this.declarations.get(property).val) { - if (token.typ == 'Whitespace') { - continue; - } - if (values.length == index) { - values.push([]); - } - values[index].push(token); - index++; - } - }); - for (const value of values) { - let i = value.length; - while (i-- > 1) { - const t = value[i]; - const k = value[i == 1 ? 0 : i % 2]; - if (t.val == k.val && t.val == '0') { - if ((t.typ == 'Number' && isLength(k)) || - (k.typ == 'Number' && isLength(t)) || - (isLength(k) || isLength(t))) { - value.splice(i, 1); - continue; - } - } - if (eq(t, k)) { - value.splice(i, 1); - continue; - } - break; - } - } - iterator = [{ - typ: 'Declaration', - nam: this.config.shorthand, - val: values.reduce((acc, curr) => { - if (curr.length > 1) { - const k = curr.length * 2 - 1; - let i = 1; - while (i < k) { - curr.splice(i, 0, { typ: 'Whitespace' }); - i += 2; - } - } - if (acc.length > 0) { - acc.push({ typ: 'Literal', val: this.config.separator }); - } - acc.push(...curr); - return acc; - }, []) - }][Symbol.iterator](); - return { - next() { - return iterator.next(); - } - }; - } - return { - next() { - return iterator.next(); - } - }; - } -} - -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'); - } - return false; -} - -function getTokenType(val) { - if (val == 'transparent' || val == 'currentcolor') { - return { - typ: 'Color', - val, - kin: 'lit' - }; - } - if (val.endsWith('%')) { - return { - typ: 'Perc', - val: val.slice(0, -1) - }; - } - return { - typ: isNumber(val) ? 'Number' : 'Iden', - val - }; -} -function parseString(val) { - return val.split(/\s/).map(getTokenType).reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ typ: 'Whitespace' }); - } - acc.push(curr); - return acc; - }, []); -} -class PropertyMap { - config; - declarations; - requiredCount; - pattern; - constructor(config) { - const values = Object.values(config.properties); - this.requiredCount = values.reduce((acc, curr) => curr.required ? ++acc : acc, 0) || values.length; - this.config = config; - this.declarations = new Map; - this.pattern = config.pattern.split(/\s/); - } - add(declaration) { - if (declaration.nam == this.config.shorthand) { - this.declarations.clear(); - this.declarations.set(declaration.nam, declaration); - } - else { - const separator = this.config.separator; - // expand shorthand - if (declaration.nam != this.config.shorthand && this.declarations.has(this.config.shorthand)) { - const tokens = {}; - const values = []; - this.declarations.get(this.config.shorthand).val.slice().reduce((acc, curr) => { - if (separator != null && separator.typ == curr.typ && eq(separator, curr)) { - acc.push([]); - return acc; - } - // else { - acc.at(-1).push(curr); - // } - return acc; - }, [[]]).reduce((acc, list, current) => { - values.push(...this.pattern.reduce((acc, property) => { - // let current: number = 0; - const props = this.config.properties[property]; - for (let i = 0; i < acc.length; i++) { - if (acc[i].typ == 'Comment' || acc[i].typ == 'Whitespace') { - acc.splice(i, 1); - i--; - continue; - } - if (matchType(acc[i], props)) { - if ('prefix' in props && props.previous != null && !(props.previous in tokens)) { - return acc; - } - if (!(property in tokens)) { - tokens[property] = [[acc[i]]]; - } - else { - if (current == tokens[property].length) { - tokens[property].push([acc[i]]); - // tokens[property][current].push(); - } - else { - tokens[property][current].push({ typ: 'Whitespace' }, acc[i]); - } - } - acc.splice(i, 1); - i--; - // @ts-ignore - if ('prefix' in props && acc[i]?.typ == props.prefix.typ) { - // @ts-ignore - if (eq(acc[i], this.config.properties[property].prefix)) { - acc.splice(i, 1); - i--; - } - } - if (props.multiple) { - continue; - } - return acc; - } - else { - if (property in tokens && tokens[property].length > current) { - return acc; - } - } - } - if (property in tokens && tokens[property].length > current) { - return acc; - } - // default - if (props.default.length > 0) { - const defaults = parseString(props.default[0]); - if (!(property in tokens)) { - tokens[property] = [ - [...defaults - ] - ]; - } - else { - if (current == tokens[property].length) { - tokens[property].push([]); - tokens[property][current].push(...defaults); - } - else { - tokens[property][current].push({ typ: 'Whitespace' }, ...defaults); - } - } - } - return acc; - }, list)); - return values; - }, []); - if (values.length == 0) { - this.declarations = Object.entries(tokens).reduce((acc, curr) => { - acc.set(curr[0], { - typ: 'Declaration', - nam: curr[0], - val: curr[1].reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ ...separator }); - } - acc.push(...curr); - return acc; - }, []) - }); - return acc; - }, new Map); - } - } - this.declarations.set(declaration.nam, declaration); - } - return this; - } - [Symbol.iterator]() { - let requiredCount = Object.keys(this.config.properties).reduce((acc, curr) => this.declarations.has(curr) && this.config.properties[curr].required ? ++acc : acc, 0); - if (requiredCount == 0) { - requiredCount = this.declarations.size; - } - if (requiredCount < this.requiredCount) { - return this.declarations.values(); - } - let count = 0; - const separator = this.config.separator; - const tokens = {}; - // @ts-ignore - const valid = Object.entries(this.config.properties).reduce((acc, curr) => { - if (!this.declarations.has(curr[0])) { - if (curr[1].required) { - acc.push(curr[0]); - } - return acc; - } - let current = 0; - const props = this.config.properties[curr[0]]; - // @ts-ignore - for (const val of this.declarations.get(curr[0]).val) { - if (separator != null && separator.typ == val.typ && eq(separator, val)) { - current++; - if (tokens[curr[0]].length == current) { - tokens[curr[0]].push([]); - } - continue; - } - if (val.typ == 'Whitespace' || val.typ == 'Comment') { - continue; - } - if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(val, props.separator)) { - continue; - } - if (matchType(val, curr[1])) { - if (!(curr[0] in tokens)) { - tokens[curr[0]] = [[]]; - } - // is default value - tokens[curr[0]][current].push(val); - continue; - } - acc.push(curr[0]); - break; - } - if (count == 0) { - count = current; - } - return acc; - }, []); - if (valid.length > 0 || Object.values(tokens).every(v => v.every(v => v.length == count))) { - return this.declarations.values(); - } - const values = Object.entries(tokens).reduce((acc, curr) => { - const props = this.config.properties[curr[0]]; - for (let i = 0; i < curr[1].length; i++) { - if (acc.length == i) { - acc.push([]); - } - let values = curr[1][i].reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ typ: 'Whitespace' }); - } - acc.push(curr); - return acc; - }, []); - if (props.default.includes(curr[1][i].reduce((acc, curr) => acc + renderToken(curr) + ' ', '').trimEnd())) { - continue; - } - values = values.filter((val) => { - if (val.typ == 'Whitespace' || val.typ == 'Comment') { - return false; - } - return !(val.typ == 'Iden' && props.default.includes(val.val)); - }); - if (values.length > 0) { - if ('mapping' in props) { - if (!('constraints' in props) || !('max' in props.constraints) || values.length <= props.constraints.mapping.max) { - let i = values.length; - while (i--) { - if (values[i].typ == 'Iden' && values[i].val in props.mapping) { - values.splice(i, 1, ...parseString(props.mapping[values[i].val])); - } - } - } - } - if ('prefix' in props) { - acc[i].push({ ...props.prefix }); - } - else if (acc[i].length > 0) { - acc[i].push({ typ: 'Whitespace' }); - } - acc[i].push(...values.reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ ...(props.separator ?? { typ: 'Whitespace' }) }); - } - acc.push(curr); - return acc; - }, [])); - } - } - return acc; - }, []).reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ ...separator }); - } - if (curr.length == 0) { - curr.push(...this.config.default[0].split(/\s/).map(getTokenType).reduce((acc, curr) => { - if (acc.length > 0) { - acc.push({ typ: 'Whitespace' }); - } - acc.push(curr); - return acc; - }, [])); - } - acc.push(...curr); - return acc; - }, []); - return [{ - typ: 'Declaration', - nam: this.config.shorthand, - val: values - }][Symbol.iterator](); - } -} - -const config = getConfig(); -class PropertyList { - declarations; - constructor() { - this.declarations = new Map; - } - add(declaration) { - if (declaration.typ != 'Declaration') { - this.declarations.set(Number(Math.random().toString().slice(2)).toString(36), declaration); - return this; - } - const propertyName = declaration.nam; - if (propertyName in config.properties) { - const shorthand = config.properties[propertyName].shorthand; - if (!this.declarations.has(shorthand)) { - this.declarations.set(shorthand, new PropertySet(config.properties[shorthand])); - } - this.declarations.get(shorthand).add(declaration); - return this; - } - if (propertyName in config.map) { - const shorthand = config.map[propertyName].shorthand; - if (!this.declarations.has(shorthand)) { - this.declarations.set(shorthand, new PropertyMap(config.map[shorthand])); - } - this.declarations.get(shorthand).add(declaration); - return this; - } - this.declarations.set(propertyName, declaration); - return this; - } - [Symbol.iterator]() { - let iterator = this.declarations.values(); - const iterators = []; - return { - next() { - let value = iterator.next(); - while ((value.done && iterators.length > 0) || - value.value instanceof PropertySet || - value.value instanceof PropertyMap) { - if (value.value instanceof PropertySet || value.value instanceof PropertyMap) { - iterators.unshift(iterator); - // @ts-ignore - iterator = value.value[Symbol.iterator](); - value = iterator.next(); - } - if (value.done && iterators.length > 0) { - iterator = iterators.shift(); - value = iterator.next(); - } - } - return value; - } - }; - } -} - -function parse(css, opt = {}) { - const errors = []; - const options = { - src: '', - location: false, - processImport: false, - deduplicate: false, - removeEmpty: true, - ...opt - }; - if (css.length == 0) { - // @ts-ignore - return null; - } - // @ts-ignore - const ast = tokenize(css, errors, options); - if (options.deduplicate) { - deduplicate(ast); - } - return { ast, errors }; -} -function diff(node1, node2) { - // @ts-ignore - return node1.chi.every((val) => { - if (val.typ == 'Comment') { - return true; - } - if (val.typ != 'Declaration') { - return false; - } - return node2.chi.some(v => eq(v, val)); - }); -} -function deduplicate(ast) { - // @ts-ignore - if (('chi' in ast) && ast.chi?.length > 0) { - let i = 0; - let previous; - let node; - let nodeIndex; - // @ts-ignore - for (; i < ast.chi.length; i++) { - // @ts-ignore - if (ast.chi[i].typ == 'Comment') { - continue; - } - // @ts-ignore - node = ast.chi[i]; - if (node.typ == 'AtRule' && node.val == 'all') { - // @ts-ignore - ast.chi?.splice(i, 1, ...node.chi); - i--; - continue; - } - // @ts-ignore - if (previous != null && 'chi' in previous && ('chi' in node)) { - // @ts-ignore - if (previous.typ == node.typ) { - let shouldMerge = true; - // @ts-ignore - let k = previous.chi.length; - while (k-- > 0) { - // @ts-ignore - if (previous.chi[k].typ == 'Comment') { - continue; - } - // @ts-ignore - shouldMerge = previous.chi[k].typ == 'Declaration'; - break; - } - if (shouldMerge) { - // @ts-ignore - if ((node.typ == 'Rule' && node.sel == previous.sel) || - // @ts-ignore - (node.typ == 'AtRule') && node.val == previous.val) { - // @ts-ignore - node.chi.unshift(...previous.chi); - // @ts-ignore - ast.chi.splice(nodeIndex, 1); - i--; - previous = node; - nodeIndex = i; - continue; - } - else if (node.typ == 'Rule') { - if (diff(node, previous) && diff(previous, node)) { - if (node.typ == 'Rule') { - previous.sel += ',' + node.sel; - } - // @ts-ignore - ast.chi.splice(i, 1); - i--; - // previous = node; - // nodeIndex = i; - } - } - } - } - // @ts-ignore - if (previous != node) { - // @ts-ignore - if (previous.chi.some(n => n.typ == 'Declaration')) { - deduplicateRule(previous); - } - else { - deduplicate(previous); - } - } - } - previous = node; - nodeIndex = i; - } - // @ts-ignore - if (node != null && ('chi' in node)) { - // @ts-ignore - if (node.chi.some(n => n.typ == 'Declaration')) { - deduplicateRule(node); - } - else { - deduplicate(node); - } - } - } - return ast; -} -function deduplicateRule(ast) { - if (!('chi' in ast) || ast.chi?.length == 0) { - return ast; - } - // @ts-ignore - const j = ast.chi.length; - let k = 0; - const properties = new PropertyList(); - for (; k < j; k++) { - // @ts-ignore - if ('Comment' == ast.chi[k].typ || 'Declaration' == ast.chi[k].typ) { - // @ts-ignore - properties.add(ast.chi[k]); - continue; - } - break; - } - // @ts-ignore - ast.chi = [...properties].concat(ast.chi.slice(k)); - // @ts-ignore - // ast.chi.splice(0, k - 1, ...properties); - return ast; -} - -const dir = dirname(new URL(import.meta.url).pathname) + '/../files'; -describe('parse block', function () { - it('parse file', async function () { - const file = (await readFile(`${dir}/css/smalli.css`)).toString(); - f(parse(file, { deduplicate: true }).ast).deep.equals(JSON.parse((await readFile(dir + '/json/smalli.json')).toString())); - }); - it('parse file #2', async function () { - const file = (await readFile(`${dir}/css/small.css`)).toString(); - f(parse(file, { deduplicate: true }).ast).deep.equals(JSON.parse((await readFile(dir + '/json/small.json')).toString())); - }); - it('parse file #3', async function () { - const file = (await readFile(`${dir}/css/invalid-1.css`)).toString(); - f(parse(file, { deduplicate: true }).ast).deep.equals(JSON.parse((await readFile(dir + '/json/invalid-1.json')).toString())); - }); - it('parse file #4', async function () { - const file = (await readFile(`${dir}/css/invalid-2.css`)).toString(); - f(parse(file, { deduplicate: true }).ast).deep.equals(JSON.parse((await readFile(dir + '/json/invalid-2.json')).toString())); - }); - it('similar rules #5', async function () { - const file = ` -.clear { - width: 0; - height: 0; -} - -.clearfix:before { - - height: 0; - width: 0; -}`; - f(render(parse(file, { deduplicate: true }).ast, { compress: true }).code).equals(`.clear,.clearfix:before{width:0;height:0}`); - }); -}); diff --git a/test/specs/block.test.ts b/test/specs/block.test.ts index 51990ba..9b005a2 100644 --- a/test/specs/block.test.ts +++ b/test/specs/block.test.ts @@ -1,6 +1,6 @@ import {expect} from "@esm-bundle/chai"; import {readFile} from "fs/promises"; -import {parse, render} from "../../src"; +import {deduplicate, parse, render, transform} from "../../src"; import {dirname} from "path"; const dir = dirname(new URL(import.meta.url).pathname) + '/../files'; @@ -35,28 +35,28 @@ describe('parse block', function () { const file = (await readFile(`${dir}/css/smalli.css`)).toString(); - expect(parse(file, {deduplicate: true}).ast).deep.equals(JSON.parse((await readFile(dir + '/json/smalli.json')).toString())) + expect(transform(file).ast).deep.equals(JSON.parse((await readFile(dir + '/json/smalli.json')).toString())) }); it('parse file #2', async function () { const file = (await readFile(`${dir}/css/small.css`)).toString(); - expect(parse(file, {deduplicate: true}).ast).deep.equals(JSON.parse((await readFile(dir + '/json/small.json')).toString())) + expect(transform(file).ast).deep.equals(JSON.parse((await readFile(dir + '/json/small.json')).toString())) }); it('parse file #3', async function () { const file = (await readFile(`${dir}/css/invalid-1.css`)).toString(); - expect(parse(file, {deduplicate: true}).ast).deep.equals(JSON.parse((await readFile(dir + '/json/invalid-1.json')).toString())) + expect(transform(file).ast).deep.equals(JSON.parse((await readFile(dir + '/json/invalid-1.json')).toString())) }); it('parse file #4', async function () { const file = (await readFile(`${dir}/css/invalid-2.css`)).toString(); - expect(parse(file, {deduplicate: true}).ast).deep.equals(JSON.parse((await readFile(dir + '/json/invalid-2.json')).toString())) + expect(transform(file).ast).deep.equals(JSON.parse((await readFile(dir + '/json/invalid-2.json')).toString())) }); it('similar rules #5', async function () { @@ -73,7 +73,9 @@ describe('parse block', function () { width: 0; }`; - expect(render(parse(file, {deduplicate: true}).ast, {compress: true}).code).equals(`.clear,.clearfix:before{width:0;height:0}`) + expect(transform(file, { + compress: true + }).code).equals(`.clear,.clearfix:before{width:0;height:0}`) }); it('similar rules #5', async function () { @@ -90,6 +92,24 @@ describe('parse block', function () { width: 0; }`; - expect(render(parse(file, {deduplicate: true}).ast, {compress: true}).code).equals(`.clear,.clearfix:before{width:0;height:0}`) + expect(transform(file, { + compress: true + }).code).equals(`.clear,.clearfix:before{width:0;height:0}`) + }); + + it('duplicated selector components #6', async function () { + + const file = ` + +:is(.test input[type="text"]), .test input[type="text"], :is(.test input[type="text"], a) { +border-top-color: gold; +border-right-color: red; +border-bottom-color: gold; +border-left-color: red; +`; + + expect(transform(file, { + compress: true + }).code).equals(`.test input[type=text],a{border-color:gold red}`) }); }); \ No newline at end of file diff --git a/test/specs/shorthand.test.ts b/test/specs/shorthand.test.ts index 13e3f16..9b7a902 100644 --- a/test/specs/shorthand.test.ts +++ b/test/specs/shorthand.test.ts @@ -1,9 +1,14 @@ import {expect} from "@esm-bundle/chai"; -import {parse, render} from "../../src"; +import {parse, render, transform} from "../../src"; import {dirname} from "path"; const dir = dirname(new URL(import.meta.url).pathname) + '/../files'; +const options = { + compress: true, + removeEmpty: true +}; + const marginPadding = ` .test { @@ -147,105 +152,66 @@ describe('shorthand', function () { it('margin padding', async function () { - expect(render(parse(marginPadding, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('.test{margin:0 0 0 5px;top:4px;padding:0;text-align:justify}') + expect(transform(marginPadding, options).code).equals('.test{margin:0 0 0 5px;top:4px;padding:0;text-align:justify}') }); it('border-radius #1', async function () { - expect(render(parse(borderRadius1, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('.test{border-radius:4px 3px 6px/5px 4px 2px}') + expect(transform(borderRadius1, options).code).equals('.test{border-radius:4px 3px 6px/5px 4px 2px}') }); it('border-radius #2', async function () { - expect(render(parse(borderRadius2, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('.test{border-radius:4px 3px 6px/2px 4px}') + expect(transform(borderRadius2, options).code).equals('.test{border-radius:4px 3px 6px/2px 4px}') }); it('border-width #3', async function () { - expect(render(parse(borderRadius3, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('.test input[type=text]{border-width:2px thin}') + expect(transform(borderRadius3, options).code).equals('.test input[type=text]{border-width:2px thin}') }); it('border-color #4', async function () { - expect(render(parse(borderColor, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('.test input[type=text]{border-color:gold red}') + expect(transform(borderColor, options).code).equals('.test input[type=text]{border-color:gold red}') }); it('outline #5', async function () { - expect(render(parse(outline1, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('a:focus{outline:0}') + expect(transform(outline1, options).code).equals('a:focus{outline:0}') }); it('outline #6', async function () { - expect(render(parse(outline2, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('a:focus{outline:#dedede dotted thin}') + expect(transform(outline2, options).code).equals('a:focus{outline:#dedede dotted thin}') }); it('inset #6', async function () { - expect(render(parse(inset1, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('a:focus{inset:auto}') + expect(transform(inset1, options).code).equals('a:focus{inset:auto}') }); it('font #7', async function () { - expect(render(parse(font1, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('html,body{font:700 15px/1.5 Verdana,sans-serif}') + expect(transform(font1, options).code).equals('html,body{font:700 15px/1.5 Verdana,sans-serif}') }); it('font #8', async function () { - expect(render(parse(font2, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('samp{font:700 1em/1.19em small-caps monospace,serif}') + expect(transform(font2, options).code).equals('samp{font:700 1em/1.19em small-caps monospace,serif}') }); it('background #9', async function () { - expect(render(parse(background1, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('p{background:no-repeat red url(images/bg.gif)}') + expect(transform(background1, options).code).equals('p{background:no-repeat red url(images/bg.gif)}') }); it('background #10', async function () { - expect(render(parse(background2, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('a:focus{background:repeat-x url(../../media/examples/star.png)0 5%/cover}') + expect(transform(background2, options).code).equals('a:focus{background:repeat-x url(../../media/examples/star.png)0 5%/cover}') }); it('background #11', async function () { - expect(render(parse(background3, { - deduplicate: true, - removeEmpty: true - }).ast, {compress: true}).code).equals('a{background:no-repeat url(../../media/examples/firefox-logo.svg)50%/cover,#eee url(../../media/examples/lizard.png)35%/contain}') + expect(transform(background3, options).code).equals('a{background:no-repeat url(../../media/examples/firefox-logo.svg)50%/cover,#eee url(../../media/examples/lizard.png)35%/contain}') }); }); \ No newline at end of file