From 3cb48477de916fc7d1bd1d7ddbb6640b36bc00af Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 14 Jun 2021 18:37:06 +0200 Subject: [PATCH] feat: custom var() Fixes https://github.com/jsdom/cssstyle/issues/125 --- lib/parsers.js | 148 +++++++++++++++++++------- scripts/download_latest_properties.js | 1 - 2 files changed, 112 insertions(+), 37 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 47a1f7cb..acaf25e4 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -81,7 +81,7 @@ exports.parseInteger = function parseInteger(val) { if (numberRegEx.test(val)) { return String(parseInt(val, 10)); } - return null; + return exports.parseCustomVariable(val); }; exports.parseNumber = function parseNumber(val) { @@ -92,7 +92,7 @@ exports.parseNumber = function parseNumber(val) { if (numberRegEx.test(val)) { return String(parseFloat(val)); } - return null; + return exports.parseCustomVariable(val); }; exports.parseLength = function parseLength(val, resolve = false) { @@ -140,7 +140,7 @@ exports.parseLength = function parseLength(val, resolve = false) { } return number + unit; } - return null; + return exports.parseCustomVariable(val); }; exports.parsePercentage = function parsePercentage(val, resolve = false) { @@ -157,7 +157,7 @@ exports.parsePercentage = function parsePercentage(val, resolve = false) { if (percentageRegEx.test(val)) { return val; } - return null; + return exports.parseCustomVariable(val); }; exports.parseLengthOrPercentage = function parseLengthOrPercentage(val, resolve) { @@ -189,7 +189,7 @@ exports.parseAngle = function parseAngle(val, resolve = false) { } return number + unit; } - return null; + return exports.parseCustomVariable(val); }; exports.parseTime = function parseTime(val, resolve = false) { @@ -210,10 +210,15 @@ exports.parseTime = function parseTime(val, resolve = false) { } return number + unit; } - return null; + return exports.parseCustomVariable(val); }; exports.parseCalc = function parseCalc(val, parseOperand = exports.parseNumber) { + const variable = exports.parseCustomVariable(val); + if (variable) { + return variable; + } + const res = calcRegEx.exec(val); if (!res) { return null; @@ -347,49 +352,56 @@ exports.parseKeyword = function parseKeyword( if (valid.includes(lowerCaseValue)) { return lowerCaseValue; } - return null; + return exports.parseCustomVariable(val); }; exports.parseCustomIdentifier = function parseCustomIdentifier(val) { if (identRegEx.test(val) && !exports.parseKeyword(val) && val !== 'default') { return val; } - return null; + return exports.parseCustomVariable(val); +}; + +exports.parseDashedIdentifier = function parseDashedIdentifier(val) { + if (identRegEx.test(val) && val.startsWith('--')) { + return val; + } + return exports.parseCustomVariable(val); }; exports.parseUrl = function parseUrl(val) { var res = urlRegEx.exec(val); // does it match the regex? - if (!res) { - return null; - } - var str = res[1]; - // if it starts with single or double quotes, does it end with the same? - if ((str[0] === '"' || str[0] === "'") && str[0] !== str[str.length - 1]) { - return null; - } - if (str[0] === '"' || str[0] === "'") { - str = str.substr(1, str.length - 2); - } + if (res) { + var str = res[1]; + // if it starts with single or double quotes, does it end with the same? + if ((str[0] === '"' || str[0] === "'") && str[0] !== str[str.length - 1]) { + return null; + } + if (str[0] === '"' || str[0] === "'") { + str = str.substr(1, str.length - 2); + } - var i; - for (i = 0; i < str.length; i++) { - switch (str[i]) { - case '(': - case ')': - case ' ': - case '\t': - case '\n': - case "'": - case '"': - return null; - case '\\': - i++; - break; + var i; + for (i = 0; i < str.length; i++) { + switch (str[i]) { + case '(': + case ')': + case ' ': + case '\t': + case '\n': + case "'": + case '"': + return null; + case '\\': + i++; + break; + } } - } - return 'url(' + str + ')'; + return 'url(' + str + ')'; + } + return exports.parseCustomVariable(val); }; exports.parseString = function parseString(val) { @@ -412,10 +424,15 @@ exports.parseString = function parseString(val) { } return val; } - return null; + return exports.parseCustomVariable(val); }; exports.parseColor = function parseColor(val) { + const variable = exports.parseCustomVariable(val); + if (variable) { + return variable; + } + var red, green, blue, @@ -527,6 +544,65 @@ exports.parseColor = function parseColor(val) { return exports.parseKeyword(val, namedColors); }; +exports.parseCustomVariable = function parseCustomVariable(val) { + let before = ''; + let call = ''; + let i = -1; + let char; + while ((char = val[++i]) && call !== 'var(') { + if (/[var(]/i.test(char)) { + if ('var('.startsWith(call)) { + call += char.toLowerCase(); + } else { + before += call; + call = char; + } + } else { + before += call; + call = ''; + before += char; + } + } + + if (call !== 'var(') { + return null; + } + + let open = 1; + let j = i; + while ((char = val[++j]) && open > 0) { + if (char === '(') { + open++; + } else if (char === ')') { + open--; + } + } + + const splitIndex = open ? j : j - 1; + const stringArgs = val.slice(i, splitIndex); + const after = val.slice(splitIndex) || ''; + const [args] = exports.splitTokens(stringArgs, /,/); + const property = exports.parseDashedIdentifier(args.shift()); + + if (!property) { + return null; + } + + const parsedArgs = [property]; + for (let i = 0; i < args.length; i++) { + let arg = args[i]; + if (arg.toLowerCase().startsWith('var(')) { + arg = parseCustomVariable(arg); + if (arg === null) { + return null; + } + } + parsedArgs.push(arg); + } + + return `${before}var(${parsedArgs.join(', ')}${after}`; +}; + /** * utility to translate from border-width to borderWidth * diff --git a/scripts/download_latest_properties.js b/scripts/download_latest_properties.js index febfbdeb..a7199895 100644 --- a/scripts/download_latest_properties.js +++ b/scripts/download_latest_properties.js @@ -47,7 +47,6 @@ const request = https.request(url, response => { // Filter out all properties newer than Working Draft status !== 'ED' && status !== 'FPWD' && - // TODO: --* css Needs additional logic to this module, so filter it out for now property !== '--*' && // Properties are often in multiple specifications !properties.includes(property)