From 0b5a54cf0b0b873e9f32d4c53d4aa4cbbef9a26e Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Thu, 18 Dec 2025 09:50:00 +0900 Subject: [PATCH 1/7] Refactor exports to use module.exports object Replaces individual exports assignments with local function declarations and a single module.exports object at the end of the file. This improves code clarity and maintainability by consolidating all exports in one place. --- lib/parsers.js | 81 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 8527e321..5bc0b9c8 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -73,7 +73,7 @@ const varContainedRegEx = /(?<=[*/\s(])var\(/; const cssTree = csstree.fork(syntaxes); // Prepare stringified value. -exports.prepareValue = (value, globalObject = globalThis) => { +const prepareValue = (value, globalObject = globalThis) => { // `null` is converted to an empty string. // @see https://webidl.spec.whatwg.org/#LegacyNullToEmptyString if (value === null) { @@ -100,28 +100,24 @@ exports.prepareValue = (value, globalObject = globalThis) => { }; // Value is a global keyword. -exports.isGlobalKeyword = (val) => { +const isGlobalKeyword = (val) => { return GLOBAL_KEY.includes(asciiLowercase(val)); }; // Value starts with and/or contains CSS var() function. -exports.hasVarFunc = (val) => { +const hasVarFunc = (val) => { return varRegEx.test(val) || varContainedRegEx.test(val); }; // Value starts with and/or contains CSS calc() related functions. -exports.hasCalcFunc = (val) => { +const hasCalcFunc = (val) => { return calcRegEx.test(val) || calcContainedRegEx.test(val); }; -// Splits value into an array. -// @see https://github.com/asamuzaK/cssColor/blob/main/src/js/util.ts -exports.splitValue = splitValue; - // Parse CSS to AST. -exports.parseCSS = (val, opt, toObject = false) => { +const parseCSS = (val, opt, toObject = false) => { if (typeof val !== "string") { - val = exports.prepareValue(val); + val = prepareValue(val); } const ast = cssTree.parse(val, opt); if (toObject) { @@ -132,9 +128,9 @@ exports.parseCSS = (val, opt, toObject = false) => { // Value is a valid property value. // Returns `false` for custom property and/or var(). -exports.isValidPropertyValue = (prop, val) => { +const isValidPropertyValue = (prop, val) => { if (typeof val !== "string") { - val = exports.prepareValue(val); + val = prepareValue(val); } if (val === "") { return true; @@ -150,7 +146,7 @@ exports.isValidPropertyValue = (prop, val) => { } let ast; try { - ast = exports.parseCSS(val, { + ast = parseCSS(val, { context: "value" }); } catch { @@ -161,14 +157,14 @@ exports.isValidPropertyValue = (prop, val) => { }; // Simplify / resolve math functions. -exports.resolveCalc = (val, opt = { format: "specifiedValue" }) => { +const resolveCalc = (val, opt = { format: "specifiedValue" }) => { if (typeof val !== "string") { - val = exports.prepareValue(val); + val = prepareValue(val); } - if (val === "" || exports.hasVarFunc(val) || !exports.hasCalcFunc(val)) { + if (val === "" || hasVarFunc(val) || !hasCalcFunc(val)) { return val; } - const obj = exports.parseCSS(val, { context: "value" }, true); + const obj = parseCSS(val, { context: "value" }, true); if (!obj?.children) { return; } @@ -197,13 +193,13 @@ exports.resolveCalc = (val, opt = { format: "specifiedValue" }) => { }; // Parse property value. Returns string or array of parsed object. -exports.parsePropertyValue = (prop, val, opt = {}) => { +const parsePropertyValue = (prop, val, opt = {}) => { const { caseSensitive, globalObject, inArray } = opt; - val = exports.prepareValue(val, globalObject); - if (val === "" || exports.hasVarFunc(val)) { + val = prepareValue(val, globalObject); + if (val === "" || hasVarFunc(val)) { return val; - } else if (exports.hasCalcFunc(val)) { - const calculatedValue = exports.resolveCalc(val, { + } else if (hasCalcFunc(val)) { + const calculatedValue = resolveCalc(val, { format: "specifiedValue" }); if (typeof calculatedValue !== "string") { @@ -237,7 +233,7 @@ exports.parsePropertyValue = (prop, val, opt = {}) => { return; } try { - const ast = exports.parseCSS(val, { + const ast = parseCSS(val, { context: "value" }); const { error, matched } = cssTree.lexer.matchProperty(prop, ast); @@ -333,7 +329,7 @@ exports.parsePropertyValue = (prop, val, opt = {}) => { }; // Parse . -exports.parseNumber = (val, opt = {}) => { +const parseNumber = (val, opt = {}) => { const [item] = val; const { type, value } = item ?? {}; if (type !== "Number") { @@ -356,7 +352,7 @@ exports.parseNumber = (val, opt = {}) => { }; // Parse . -exports.parseLength = (val, opt = {}) => { +const parseLength = (val, opt = {}) => { const [item] = val; const { type, value, unit } = item ?? {}; if (type !== "Dimension" && !(type === "Number" && value === "0")) { @@ -383,7 +379,7 @@ exports.parseLength = (val, opt = {}) => { }; // Parse . -exports.parsePercentage = (val, opt = {}) => { +const parsePercentage = (val, opt = {}) => { const [item] = val; const { type, value } = item ?? {}; if (type !== "Percentage" && !(type === "Number" && value === "0")) { @@ -409,7 +405,7 @@ exports.parsePercentage = (val, opt = {}) => { }; // Parse . -exports.parseLengthPercentage = (val, opt = {}) => { +const parseLengthPercentage = (val, opt = {}) => { const [item] = val; const { type, value, unit } = item ?? {}; if (type !== "Dimension" && type !== "Percentage" && !(type === "Number" && value === "0")) { @@ -441,7 +437,7 @@ exports.parseLengthPercentage = (val, opt = {}) => { }; // Parse . -exports.parseAngle = (val) => { +const parseAngle = (val) => { const [item] = val; const { type, value, unit } = item ?? {}; if (type !== "Dimension" && !(type === "Number" && value === "0")) { @@ -459,7 +455,7 @@ exports.parseAngle = (val) => { }; // Parse . -exports.parseUrl = (val) => { +const parseUrl = (val) => { const [item] = val; const { type, value } = item ?? {}; if (type !== "Url") { @@ -470,7 +466,7 @@ exports.parseUrl = (val) => { }; // Parse . -exports.parseString = (val) => { +const parseString = (val) => { const [item] = val; const { type, value } = item ?? {}; if (type !== "String") { @@ -481,7 +477,7 @@ exports.parseString = (val) => { }; // Parse . -exports.parseColor = (val) => { +const parseColor = (val) => { const [item] = val; const { name, type, value } = item ?? {}; switch (type) { @@ -520,7 +516,7 @@ exports.parseColor = (val) => { }; // Parse . -exports.parseGradient = (val) => { +const parseGradient = (val) => { const [item] = val; const { name, type, value } = item ?? {}; if (type !== "Function") { @@ -533,3 +529,24 @@ exports.parseGradient = (val) => { return res; } }; + +module.exports = { + prepareValue, + isGlobalKeyword, + hasVarFunc, + hasCalcFunc, + splitValue, + parseCSS, + isValidPropertyValue, + resolveCalc, + parsePropertyValue, + parseNumber, + parseLength, + parsePercentage, + parseLengthPercentage, + parseAngle, + parseUrl, + parseString, + parseColor, + parseGradient +}; From 95ad4a46d5254e4a2dc7219b46c492af505016f0 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Thu, 18 Dec 2025 10:02:03 +0900 Subject: [PATCH 2/7] Add JSDoc comments to parser utility functions Added detailed JSDoc comments to all major functions in lib/parsers.js to improve code readability and provide better context for parameters and return values. This enhances maintainability and developer experience by clarifying function purposes and usage. --- lib/parsers.js | 133 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 115 insertions(+), 18 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 5bc0b9c8..13a11539 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -72,7 +72,13 @@ const varContainedRegEx = /(?<=[*/\s(])var\(/; // Patched css-tree const cssTree = csstree.fork(syntaxes); -// Prepare stringified value. +/** + * Prepares a stringified value. + * + * @param {string|number|null|undefined} value - The value to prepare. + * @param {object} [globalObject=globalThis] - The global object. + * @returns {string} The prepared value. + */ const prepareValue = (value, globalObject = globalThis) => { // `null` is converted to an empty string. // @see https://webidl.spec.whatwg.org/#LegacyNullToEmptyString @@ -99,22 +105,44 @@ const prepareValue = (value, globalObject = globalThis) => { } }; -// Value is a global keyword. +/** + * Checks if the value is a global keyword. + * + * @param {string} val - The value to check. + * @returns {boolean} True if the value is a global keyword, false otherwise. + */ const isGlobalKeyword = (val) => { return GLOBAL_KEY.includes(asciiLowercase(val)); }; -// Value starts with and/or contains CSS var() function. +/** + * Checks if the value starts with or contains a CSS var() function. + * + * @param {string} val - The value to check. + * @returns {boolean} True if the value contains a var() function, false otherwise. + */ const hasVarFunc = (val) => { return varRegEx.test(val) || varContainedRegEx.test(val); }; -// Value starts with and/or contains CSS calc() related functions. +/** + * Checks if the value starts with or contains CSS calc() or math functions. + * + * @param {string} val - The value to check. + * @returns {boolean} True if the value contains calc() or math functions, false otherwise. + */ const hasCalcFunc = (val) => { return calcRegEx.test(val) || calcContainedRegEx.test(val); }; -// Parse CSS to AST. +/** + * Parses a CSS string into an AST. + * + * @param {string} val - The CSS string to parse. + * @param {object} opt - The options for parsing. + * @param {boolean} [toObject=false] - Whether to return a plain object. + * @returns {object} The AST or a plain object. + */ const parseCSS = (val, opt, toObject = false) => { if (typeof val !== "string") { val = prepareValue(val); @@ -126,8 +154,14 @@ const parseCSS = (val, opt, toObject = false) => { return ast; }; -// Value is a valid property value. -// Returns `false` for custom property and/or var(). +/** + * Checks if the value is a valid property value. + * Returns false for custom properties or values containing var(). + * + * @param {string} prop - The property name. + * @param {string} val - The property value. + * @returns {boolean} True if the value is valid, false otherwise. + */ const isValidPropertyValue = (prop, val) => { if (typeof val !== "string") { val = prepareValue(val); @@ -156,7 +190,13 @@ const isValidPropertyValue = (prop, val) => { return error === null && matched !== null; }; -// Simplify / resolve math functions. +/** + * Resolves CSS math functions. + * + * @param {string} val - The value to resolve. + * @param {object} [opt={ format: "specifiedValue" }] - The options for resolving. + * @returns {string|undefined} The resolved value. + */ const resolveCalc = (val, opt = { format: "specifiedValue" }) => { if (typeof val !== "string") { val = prepareValue(val); @@ -192,7 +232,15 @@ const resolveCalc = (val, opt = { format: "specifiedValue" }) => { return values.join(" "); }; -// Parse property value. Returns string or array of parsed object. +/** + * Parses a property value. + * Returns a string or an array of parsed objects. + * + * @param {string} prop - The property name. + * @param {string} val - The property value. + * @param {object} [opt={}] - The options for parsing. + * @returns {string|Array|undefined} The parsed value. + */ const parsePropertyValue = (prop, val, opt = {}) => { const { caseSensitive, globalObject, inArray } = opt; val = prepareValue(val, globalObject); @@ -328,7 +376,13 @@ const parsePropertyValue = (prop, val, opt = {}) => { return val; }; -// Parse . +/** + * Parses a value. + * + * @param {Array} val - The AST value. + * @param {object} [opt={}] - The options for parsing. + * @returns {string|undefined} The parsed number. + */ const parseNumber = (val, opt = {}) => { const [item] = val; const { type, value } = item ?? {}; @@ -351,7 +405,13 @@ const parseNumber = (val, opt = {}) => { return `${num}`; }; -// Parse . +/** + * Parses a value. + * + * @param {Array} val - The AST value. + * @param {object} [opt={}] - The options for parsing. + * @returns {string|undefined} The parsed length. + */ const parseLength = (val, opt = {}) => { const [item] = val; const { type, value, unit } = item ?? {}; @@ -378,7 +438,13 @@ const parseLength = (val, opt = {}) => { } }; -// Parse . +/** + * Parses a value. + * + * @param {Array} val - The AST value. + * @param {object} [opt={}] - The options for parsing. + * @returns {string|undefined} The parsed percentage. + */ const parsePercentage = (val, opt = {}) => { const [item] = val; const { type, value } = item ?? {}; @@ -404,7 +470,13 @@ const parsePercentage = (val, opt = {}) => { return `${num}%`; }; -// Parse . +/** + * Parses a value. + * + * @param {Array} val - The AST value. + * @param {object} [opt={}] - The options for parsing. + * @returns {string|undefined} The parsed length-percentage. + */ const parseLengthPercentage = (val, opt = {}) => { const [item] = val; const { type, value, unit } = item ?? {}; @@ -436,7 +508,12 @@ const parseLengthPercentage = (val, opt = {}) => { } }; -// Parse . +/** + * Parses an value. + * + * @param {Array} val - The AST value. + * @returns {string|undefined} The parsed angle. + */ const parseAngle = (val) => { const [item] = val; const { type, value, unit } = item ?? {}; @@ -454,7 +531,12 @@ const parseAngle = (val) => { } }; -// Parse . +/** + * Parses a value. + * + * @param {Array} val - The AST value. + * @returns {string|undefined} The parsed url. + */ const parseUrl = (val) => { const [item] = val; const { type, value } = item ?? {}; @@ -465,7 +547,12 @@ const parseUrl = (val) => { return `url("${str}")`; }; -// Parse . +/** + * Parses a value. + * + * @param {Array} val - The AST value. + * @returns {string|undefined} The parsed string. + */ const parseString = (val) => { const [item] = val; const { type, value } = item ?? {}; @@ -476,7 +563,12 @@ const parseString = (val) => { return `"${str}"`; }; -// Parse . +/** + * Parses a value. + * + * @param {Array} val - The AST value. + * @returns {string|undefined} The parsed color. + */ const parseColor = (val) => { const [item] = val; const { name, type, value } = item ?? {}; @@ -515,7 +607,12 @@ const parseColor = (val) => { } }; -// Parse . +/** + * Parses a value. + * + * @param {Array} val - The AST value. + * @returns {string|undefined} The parsed gradient. + */ const parseGradient = (val) => { const [item] = val; const { name, type, value } = item ?? {}; From b25e48a887fbe4f712fd9a460cc60cf5bd97549d Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Thu, 18 Dec 2025 10:08:48 +0900 Subject: [PATCH 3/7] Refactor keyword and color lists to use Set Replaces arrays for global CSS keywords and system colors with Set objects for improved lookup performance. Updates all relevant checks from .includes() to .has(). --- lib/parsers.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 13a11539..6f740b52 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -10,12 +10,12 @@ const { asciiLowercase } = require("./utils/strings"); // CSS global keywords // @see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords -const GLOBAL_KEY = Object.freeze(["initial", "inherit", "unset", "revert", "revert-layer"]); +const GLOBAL_KEYS = new Set(["initial", "inherit", "unset", "revert", "revert-layer"]); // System colors // @see https://drafts.csswg.org/css-color/#css-system-colors // @see https://drafts.csswg.org/css-color/#deprecated-system-colors -const SYS_COLOR = Object.freeze([ +const SYS_COLORS = new Set([ "accentcolor", "accentcolortext", "activeborder", @@ -112,7 +112,7 @@ const prepareValue = (value, globalObject = globalThis) => { * @returns {boolean} True if the value is a global keyword, false otherwise. */ const isGlobalKeyword = (val) => { - return GLOBAL_KEY.includes(asciiLowercase(val)); + return GLOBAL_KEYS.has(asciiLowercase(val)); }; /** @@ -172,7 +172,7 @@ const isValidPropertyValue = (prop, val) => { // cssTree.lexer does not support deprecated system colors // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261 // @see https://github.com/w3c/webref/issues/1647 - if (SYS_COLOR.includes(asciiLowercase(val))) { + if (SYS_COLORS.has(asciiLowercase(val))) { if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { return true; } @@ -256,7 +256,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { val = calculatedValue; } const lowerCasedValue = asciiLowercase(val); - if (GLOBAL_KEY.includes(lowerCasedValue)) { + if (GLOBAL_KEYS.has(lowerCasedValue)) { if (inArray) { return [ { @@ -266,7 +266,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { ]; } return lowerCasedValue; - } else if (SYS_COLOR.includes(lowerCasedValue)) { + } else if (SYS_COLORS.has(lowerCasedValue)) { if (/^(?:(?:-webkit-)?(?:[a-z][a-z\d]*-)*color|border)$/i.test(prop)) { if (inArray) { return [ @@ -592,7 +592,7 @@ const parseColor = (val) => { break; } case "Identifier": { - if (SYS_COLOR.includes(name)) { + if (SYS_COLORS.has(name)) { return name; } const res = resolveColor(name, { From d69339476fdc7ad034921d935748e4fc0cd6d72f Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Thu, 18 Dec 2025 10:21:06 +0900 Subject: [PATCH 4/7] Refactor AST node type checks to use constants Introduces an AST_TYPES constant object to centralize AST node type strings and replaces hardcoded type string comparisons throughout the file with references to AST_TYPES. This improves maintainability and reduces the risk of typos or inconsistencies in type checks. --- lib/parsers.js | 65 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 6f740b52..82143650 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -60,6 +60,21 @@ const SYS_COLORS = new Set([ "windowtext" ]); +// AST node types +// TODO: Export and use in properties/*.js in the future +const AST_TYPES = Object.freeze({ + CALC: "Calc", + DIMENSION: "Dimension", + FUNCTION: "Function", + GLOBAL_KEYWORD: "GlobalKeyword", + HASH: "Hash", + IDENTIFIER: "Identifier", + NUMBER: "Number", + PERCENTAGE: "Percentage", + STRING: "String", + URL: "Url" +}); + // Regular expressions const CALC_FUNC_NAMES = "(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)"; @@ -212,7 +227,7 @@ const resolveCalc = (val, opt = { format: "specifiedValue" }) => { const values = []; for (const item of items) { const { type: itemType, name: itemName, value: itemValue } = item; - if (itemType === "Function") { + if (itemType === AST_TYPES.FUNCTION) { const value = cssTree .generate(item) .replace(/\)(?!\)|\s|,)/g, ") ") @@ -223,7 +238,7 @@ const resolveCalc = (val, opt = { format: "specifiedValue" }) => { } else { values.push(value); } - } else if (itemType === "String") { + } else if (itemType === AST_TYPES.STRING) { values.push(`"${itemValue}"`); } else { values.push(itemName ?? itemValue); @@ -260,7 +275,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { if (inArray) { return [ { - type: "GlobalKeyword", + type: AST_TYPES.GLOBAL_KEYWORD, name: lowerCasedValue } ]; @@ -271,7 +286,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { if (inArray) { return [ { - type: "Identifier", + type: AST_TYPES.IDENTIFIER, name: lowerCasedValue } ]; @@ -295,7 +310,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { for (const item of items) { const { children, name, type, value, unit } = item; switch (type) { - case "Dimension": { + case AST_TYPES.DIMENSION: { parsedValues.push({ type, value, @@ -303,7 +318,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { }); break; } - case "Function": { + case AST_TYPES.FUNCTION: { const css = cssTree .generate(item) .replace(/\)(?!\)|\s|,)/g, ") ") @@ -316,9 +331,9 @@ const parsePropertyValue = (prop, val, opt = {}) => { if (name === "calc") { if (children.length === 1) { const [child] = children; - if (child.type === "Number") { + if (child.type === AST_TYPES.NUMBER) { parsedValues.push({ - type: "Calc", + type: AST_TYPES.CALC, name: "calc", isNumber: true, value: `${parseFloat(child.value)}`, @@ -326,7 +341,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { }); } else { parsedValues.push({ - type: "Calc", + type: AST_TYPES.CALC, name: "calc", isNumber: false, value: `${asciiLowercase(itemValue)}`, @@ -335,7 +350,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { } } else { parsedValues.push({ - type: "Calc", + type: AST_TYPES.CALC, name: "calc", isNumber: false, value: asciiLowercase(itemValue), @@ -352,7 +367,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { } break; } - case "Identifier": { + case AST_TYPES.IDENTIFIER: { if (caseSensitive) { parsedValues.push(item); } else { @@ -386,7 +401,7 @@ const parsePropertyValue = (prop, val, opt = {}) => { const parseNumber = (val, opt = {}) => { const [item] = val; const { type, value } = item ?? {}; - if (type !== "Number") { + if (type !== AST_TYPES.NUMBER) { return; } const { clamp } = opt; @@ -415,7 +430,7 @@ const parseNumber = (val, opt = {}) => { const parseLength = (val, opt = {}) => { const [item] = val; const { type, value, unit } = item ?? {}; - if (type !== "Dimension" && !(type === "Number" && value === "0")) { + if (type !== AST_TYPES.DIMENSION && !(type === AST_TYPES.NUMBER && value === "0")) { return; } const { clamp } = opt; @@ -448,7 +463,7 @@ const parseLength = (val, opt = {}) => { const parsePercentage = (val, opt = {}) => { const [item] = val; const { type, value } = item ?? {}; - if (type !== "Percentage" && !(type === "Number" && value === "0")) { + if (type !== AST_TYPES.PERCENTAGE && !(type === AST_TYPES.NUMBER && value === "0")) { return; } const { clamp } = opt; @@ -480,7 +495,11 @@ const parsePercentage = (val, opt = {}) => { const parseLengthPercentage = (val, opt = {}) => { const [item] = val; const { type, value, unit } = item ?? {}; - if (type !== "Dimension" && type !== "Percentage" && !(type === "Number" && value === "0")) { + if ( + type !== AST_TYPES.DIMENSION && + type !== AST_TYPES.PERCENTAGE && + !(type === AST_TYPES.NUMBER && value === "0") + ) { return; } const { clamp } = opt; @@ -501,7 +520,7 @@ const parseLengthPercentage = (val, opt = {}) => { return; } return `${num}${asciiLowercase(unit)}`; - } else if (type === "Percentage") { + } else if (type === AST_TYPES.PERCENTAGE) { return `${num}%`; } else if (num === 0) { return `${num}px`; @@ -517,7 +536,7 @@ const parseLengthPercentage = (val, opt = {}) => { const parseAngle = (val) => { const [item] = val; const { type, value, unit } = item ?? {}; - if (type !== "Dimension" && !(type === "Number" && value === "0")) { + if (type !== AST_TYPES.DIMENSION && !(type === AST_TYPES.NUMBER && value === "0")) { return; } const num = parseFloat(value); @@ -540,7 +559,7 @@ const parseAngle = (val) => { const parseUrl = (val) => { const [item] = val; const { type, value } = item ?? {}; - if (type !== "Url") { + if (type !== AST_TYPES.URL) { return; } const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"'); @@ -556,7 +575,7 @@ const parseUrl = (val) => { const parseString = (val) => { const [item] = val; const { type, value } = item ?? {}; - if (type !== "String") { + if (type !== AST_TYPES.STRING) { return; } const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"'); @@ -573,7 +592,7 @@ const parseColor = (val) => { const [item] = val; const { name, type, value } = item ?? {}; switch (type) { - case "Function": { + case AST_TYPES.FUNCTION: { const res = resolveColor(`${name}(${value})`, { format: "specifiedValue" }); @@ -582,7 +601,7 @@ const parseColor = (val) => { } break; } - case "Hash": { + case AST_TYPES.HASH: { const res = resolveColor(`#${value}`, { format: "specifiedValue" }); @@ -591,7 +610,7 @@ const parseColor = (val) => { } break; } - case "Identifier": { + case AST_TYPES.IDENTIFIER: { if (SYS_COLORS.has(name)) { return name; } @@ -616,7 +635,7 @@ const parseColor = (val) => { const parseGradient = (val) => { const [item] = val; const { name, type, value } = item ?? {}; - if (type !== "Function") { + if (type !== AST_TYPES.FUNCTION) { return; } const res = resolveGradient(`${name}(${value})`, { From 4597e1ceeede5801ca54f8d279e05f443dae14dd Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Thu, 18 Dec 2025 10:28:30 +0900 Subject: [PATCH 5/7] Refactor property value parsing for clarity Simplified the extraction of itemValue by replacing regex-based string replacement with a more direct slice operation. This improves readability and maintains the same functionality. --- lib/parsers.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 82143650..0ad9de75 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -324,10 +324,9 @@ const parsePropertyValue = (prop, val, opt = {}) => { .replace(/\)(?!\)|\s|,)/g, ") ") .trim(); const raw = items.length === 1 ? val : css; - const itemValue = raw - .replace(new RegExp(`^${name}\\(`), "") - .replace(/\)$/, "") - .trim(); + // Remove "${name}(" from the start and ")" from the end + const itemValue = raw.slice(name.length + 1, -1).trim(); + if (name === "calc") { if (children.length === 1) { const [child] = children; From 34e5bd926ec3801be10760a2663985efde354986 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Fri, 19 Dec 2025 08:01:44 +0900 Subject: [PATCH 6/7] Refactor numeric value parsing logic Introduces a shared parseNumericValue helper to unify and simplify parsing of numbers, lengths, percentages, and angles. This reduces code duplication and improves maintainability across parseNumber, parseLength, parsePercentage, parseLengthPercentage, and parseAngle. --- lib/parsers.js | 138 +++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 72 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 0ad9de75..b746deb4 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -391,21 +391,23 @@ const parsePropertyValue = (prop, val, opt = {}) => { }; /** - * Parses a value. + * Parses a numeric value (number, dimension, percentage). + * Helper function for parseNumber, parseLength, etc. * * @param {Array} val - The AST value. * @param {object} [opt={}] - The options for parsing. - * @returns {string|undefined} The parsed number. + * @param {Function} validateType - Function to validate the node type. + * @returns {object|undefined} The parsed result containing num and unit, or undefined. */ -const parseNumber = (val, opt = {}) => { +const parseNumericValue = (val, opt, validateType) => { const [item] = val; - const { type, value } = item ?? {}; - if (type !== AST_TYPES.NUMBER) { + const { type, value, unit } = item ?? {}; + if (!validateType(type, value, unit)) { return; } - const { clamp } = opt; - const max = opt.max ?? Number.INFINITY; - const min = opt.min ?? Number.NEGATIVE_INFINITY; + const { clamp } = opt || {}; + const max = opt?.max ?? Number.INFINITY; + const min = opt?.min ?? Number.NEGATIVE_INFINITY; let num = parseFloat(value); if (clamp) { if (num > max) { @@ -416,7 +418,26 @@ const parseNumber = (val, opt = {}) => { } else if (num > max || num < min) { return; } - return `${num}`; + return { + num, + unit: unit ? asciiLowercase(unit) : null, + type + }; +}; + +/** + * Parses a value. + * + * @param {Array} val - The AST value. + * @param {object} [opt={}] - The options for parsing. + * @returns {string|undefined} The parsed number. + */ +const parseNumber = (val, opt = {}) => { + const res = parseNumericValue(val, opt, (type) => type === AST_TYPES.NUMBER); + if (!res) { + return; + } + return `${res.num}`; }; /** @@ -427,28 +448,19 @@ const parseNumber = (val, opt = {}) => { * @returns {string|undefined} The parsed length. */ const parseLength = (val, opt = {}) => { - const [item] = val; - const { type, value, unit } = item ?? {}; - if (type !== AST_TYPES.DIMENSION && !(type === AST_TYPES.NUMBER && value === "0")) { - return; - } - const { clamp } = opt; - const max = opt.max ?? Number.INFINITY; - const min = opt.min ?? Number.NEGATIVE_INFINITY; - let num = parseFloat(value); - if (clamp) { - if (num > max) { - num = max; - } else if (num < min) { - num = min; - } - } else if (num > max || num < min) { + const res = parseNumericValue( + val, + opt, + (type, value) => type === AST_TYPES.DIMENSION || (type === AST_TYPES.NUMBER && value === "0") + ); + if (!res) { return; } + const { num, unit } = res; if (num === 0 && !unit) { return `${num}px`; } else if (unit) { - return `${num}${asciiLowercase(unit)}`; + return `${num}${unit}`; } }; @@ -460,27 +472,15 @@ const parseLength = (val, opt = {}) => { * @returns {string|undefined} The parsed percentage. */ const parsePercentage = (val, opt = {}) => { - const [item] = val; - const { type, value } = item ?? {}; - if (type !== AST_TYPES.PERCENTAGE && !(type === AST_TYPES.NUMBER && value === "0")) { + const res = parseNumericValue( + val, + opt, + (type, value) => type === AST_TYPES.PERCENTAGE || (type === AST_TYPES.NUMBER && value === "0") + ); + if (!res) { return; } - const { clamp } = opt; - const max = opt.max ?? Number.INFINITY; - const min = opt.min ?? Number.NEGATIVE_INFINITY; - let num = parseFloat(value); - if (clamp) { - if (num > max) { - num = max; - } else if (num < min) { - num = min; - } - } else if (num > max || num < min) { - return; - } - if (num === 0) { - return `${num}%`; - } + const { num } = res; return `${num}%`; }; @@ -492,33 +492,23 @@ const parsePercentage = (val, opt = {}) => { * @returns {string|undefined} The parsed length-percentage. */ const parseLengthPercentage = (val, opt = {}) => { - const [item] = val; - const { type, value, unit } = item ?? {}; - if ( - type !== AST_TYPES.DIMENSION && - type !== AST_TYPES.PERCENTAGE && - !(type === AST_TYPES.NUMBER && value === "0") - ) { - return; - } - const { clamp } = opt; - const max = opt.max ?? Number.INFINITY; - const min = opt.min ?? Number.NEGATIVE_INFINITY; - let num = parseFloat(value); - if (clamp) { - if (num > max) { - num = max; - } else if (num < min) { - num = min; - } - } else if (num > max || num < min) { + const res = parseNumericValue( + val, + opt, + (type, value) => + type === AST_TYPES.DIMENSION || + type === AST_TYPES.PERCENTAGE || + (type === AST_TYPES.NUMBER && value === "0") + ); + if (!res) { return; } + const { num, unit, type } = res; if (unit) { if (/deg|g?rad|turn/i.test(unit)) { return; } - return `${num}${asciiLowercase(unit)}`; + return `${num}${unit}`; } else if (type === AST_TYPES.PERCENTAGE) { return `${num}%`; } else if (num === 0) { @@ -530,20 +520,24 @@ const parseLengthPercentage = (val, opt = {}) => { * Parses an value. * * @param {Array} val - The AST value. + * @param {object} [opt={}] - The options for parsing. * @returns {string|undefined} The parsed angle. */ -const parseAngle = (val) => { - const [item] = val; - const { type, value, unit } = item ?? {}; - if (type !== AST_TYPES.DIMENSION && !(type === AST_TYPES.NUMBER && value === "0")) { +const parseAngle = (val, opt = {}) => { + const res = parseNumericValue( + val, + opt, + (type, value) => type === AST_TYPES.DIMENSION || (type === AST_TYPES.NUMBER && value === "0") + ); + if (!res) { return; } - const num = parseFloat(value); + const { num, unit } = res; if (unit) { if (!/^(?:deg|g?rad|turn)$/i.test(unit)) { return; } - return `${num}${asciiLowercase(unit)}`; + return `${num}${unit}`; } else if (num === 0) { return `${num}deg`; } From 732d89ce476e719e3bb6fe668d610e4962c82259 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 27 Dec 2025 10:20:25 +0900 Subject: [PATCH 7/7] Refactor calc name assignment in parsePropertyValue Replaces hardcoded 'calc' string with the variable 'name' when assigning the 'name' property in parsedValues. This improves code maintainability and consistency for function parsing. --- lib/parsers.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index b746deb4..9f25fca5 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -326,33 +326,32 @@ const parsePropertyValue = (prop, val, opt = {}) => { const raw = items.length === 1 ? val : css; // Remove "${name}(" from the start and ")" from the end const itemValue = raw.slice(name.length + 1, -1).trim(); - if (name === "calc") { if (children.length === 1) { const [child] = children; if (child.type === AST_TYPES.NUMBER) { parsedValues.push({ type: AST_TYPES.CALC, - name: "calc", isNumber: true, value: `${parseFloat(child.value)}`, + name, raw }); } else { parsedValues.push({ type: AST_TYPES.CALC, - name: "calc", isNumber: false, value: `${asciiLowercase(itemValue)}`, + name, raw }); } } else { parsedValues.push({ type: AST_TYPES.CALC, - name: "calc", isNumber: false, value: asciiLowercase(itemValue), + name, raw }); }