From b2ebbb90c30cd806f7c4026993f527b6da945b23 Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Sat, 26 Apr 2025 16:43:36 +0900
Subject: [PATCH 01/10] Fix parser functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* TYPES values ​​have been changed to allow bitwise operations.
* Fixed valueType() function.
* Other parser functions have been modified to match the changes to the valueType() function.
---
 lib/parsers.js                       | 246 ++++++++++---------
 lib/properties/backgroundPosition.js |  13 +-
 lib/properties/fontFamily.js         |   2 +-
 lib/properties/fontSize.js           |   3 +-
 lib/properties/lineHeight.js         |   4 +-
 lib/properties/margin.js             |   7 +-
 lib/properties/padding.js            |   7 +-
 package-lock.json                    |   8 +-
 package.json                         |   2 +-
 test/parsers.js                      | 353 ++++++++++++++++++++++++---
 10 files changed, 470 insertions(+), 175 deletions(-)

diff --git a/lib/parsers.js b/lib/parsers.js
index bcf4e7c0..4bca8d81 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -8,32 +8,27 @@ const { resolve: resolveColor, utils } = require('@asamuzakjp/css-color');
 const { cssCalc, isColor, isGradient, splitValue } = utils;
 
 exports.TYPES = {
-  INTEGER: 1,
-  NUMBER: 2,
-  LENGTH: 3,
-  PERCENT: 4,
-  URL: 5,
-  COLOR: 6,
-  STRING: 7,
-  ANGLE: 8,
-  KEYWORD: 9,
-  NULL_OR_EMPTY_STR: 10,
-  CALC: 11,
-  VAR: 12,
-  GRADIENT: 13,
+  UNDEFINED: 0,
+  NULL_OR_EMPTY_STR: 1,
+  VAR: 2,
+  NUMBER: 4,
+  PERCENT: 8,
+  LENGTH: 0x10,
+  ANGLE: 0x20,
+  CALC: 0x40,
+  COLOR: 0x80,
+  STRING: 0x100,
+  KEYWORD: 0x200,
+  UNIDENT: 0x8000,
 };
 
 // regular expressions
 var DIGIT = '(?:0|[1-9]\\d*)';
 var NUMBER = `[+-]?(?:${DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${DIGIT})?`;
-var integerRegEx = new RegExp(`^[+-]?${DIGIT}$`);
-var numberRegEx = new RegExp(`^${NUMBER}$`);
-var lengthRegEx = new RegExp(
-  `^${NUMBER}(?:[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic))$`
-);
-var percentRegEx = new RegExp(`^${NUMBER}%$`);
+var unitRegEx = new RegExp(`^(${NUMBER})([a-z]+|%)?$`);
 var angleRegEx = new RegExp(`^${NUMBER}(?:deg|g?rad|turn)$`);
 var urlRegEx = /^url\(\s*((?:[^)]|\\\))*)\s*\)$/;
+var keywordRegEx = /^[a-z]+(?:\-[a-z]+)*$/i;
 var stringRegEx = /^("[^"]*"|'[^']*')$/;
 var varRegEx = /^var\(|(?<=[*/\s(])var\(/;
 var calcRegEx =
@@ -48,25 +43,7 @@ exports.valueType = function valueType(val) {
     val = val.toString();
   }
   if (typeof val !== 'string') {
-    return undefined;
-  }
-  if (integerRegEx.test(val)) {
-    return exports.TYPES.INTEGER;
-  }
-  if (numberRegEx.test(val)) {
-    return exports.TYPES.NUMBER;
-  }
-  if (lengthRegEx.test(val)) {
-    return exports.TYPES.LENGTH;
-  }
-  if (percentRegEx.test(val)) {
-    return exports.TYPES.PERCENT;
-  }
-  if (val.startsWith('url(') && val.endsWith(')')) {
-    if (urlRegEx.test(val)) {
-      return exports.TYPES.URL;
-    }
-    return undefined;
+    return exports.TYPES.UNDEFINED;
   }
   if (varRegEx.test(val)) {
     return exports.TYPES.VAR;
@@ -74,20 +51,47 @@ exports.valueType = function valueType(val) {
   if (calcRegEx.test(val)) {
     return exports.TYPES.CALC;
   }
-  if (stringRegEx.test(val)) {
-    return exports.TYPES.STRING;
-  }
-  if (angleRegEx.test(val)) {
-    return exports.TYPES.ANGLE;
+  if (unitRegEx.test(val)) {
+    const [, , unit] = unitRegEx.exec(val);
+    if (!unit) {
+      return exports.TYPES.NUMBER;
+    }
+    if (unit === '%') {
+      return exports.TYPES.PERCENT;
+    }
+    if (/^(?:[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic))$/.test(unit)) {
+      return exports.TYPES.LENGTH;
+    }
+    if (/^(?:deg|g?rad|turn)$/.test(unit)) {
+      return exports.TYPES.ANGLE;
+    }
   }
   if (isColor(val)) {
     return exports.TYPES.COLOR;
   }
-  if (isGradient(val)) {
-    return exports.TYPES.GRADIENT;
+  if (stringRegEx.test(val)) {
+    return exports.TYPES.STRING;
   }
 
   switch (val.toLowerCase()) {
+    // system color keywords
+    case 'accentcolor':
+    case 'accentcolortext':
+    case 'activetext':
+    case 'buttonborder':
+    case 'buttonface':
+    case 'buttontext':
+    case 'canvas':
+    case 'canvastext':
+    case 'field':
+    case 'fieldtext':
+    case 'graytext':
+    case 'highlight':
+    case 'highlighttext':
+    case 'linktext':
+    case 'mark':
+    case 'marktext':
+    case 'visitedtext':
     // the following are deprecated in CSS3
     case 'activeborder':
     case 'activecaption':
@@ -119,87 +123,97 @@ exports.valueType = function valueType(val) {
     case 'windowtext':
       return exports.TYPES.COLOR;
     default:
-      return exports.TYPES.KEYWORD;
-  }
-};
-
-exports.parseInteger = function parseInteger(val) {
-  var type = exports.valueType(val);
-  if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
-    return val;
-  }
-  if (type !== exports.TYPES.INTEGER) {
-    return undefined;
+      if (keywordRegEx.test(val)) {
+        return exports.TYPES.KEYWORD;
+      }
+      return exports.TYPES.UNIDENT;
   }
-  return String(parseInt(val, 10));
 };
 
 exports.parseNumber = function parseNumber(val) {
   var type = exports.valueType(val);
-  if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
-    return val;
-  }
-  if (type !== exports.TYPES.NUMBER && type !== exports.TYPES.INTEGER) {
-    return undefined;
+  switch (type) {
+    case exports.TYPES.NULL_OR_EMPTY_STR:
+    case exports.TYPES.VAR:
+      return val;
+    case exports.TYPES.NUMBER:
+      return `${parseFloat(val)}`;
+    default:
+      return undefined;
   }
-  return String(parseFloat(val));
 };
 
 exports.parseLength = function parseLength(val) {
-  if (val === 0 || val === '0') {
-    return '0px';
-  }
   var type = exports.valueType(val);
-  if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
-    return val;
-  }
-  if (type !== exports.TYPES.LENGTH) {
-    return undefined;
+  switch (type) {
+    case exports.TYPES.NULL_OR_EMPTY_STR:
+    case exports.TYPES.VAR:
+      return val;
+    case exports.TYPES.CALC:
+      return cssCalc(val, {
+        format: 'specifiedValue',
+      });
+    case exports.TYPES.LENGTH: {
+      const [, numVal, unit] = unitRegEx.exec(val);
+      return `${parseFloat(numVal)}${unit}`;
+    }
+    default:
+      if (type === exports.TYPES.NUMBER && parseFloat(val) === 0) {
+        return '0px';
+      }
+      return undefined;
   }
-  return val;
 };
 
 exports.parsePercent = function parsePercent(val) {
-  if (val === 0 || val === '0') {
-    return '0%';
-  }
   var type = exports.valueType(val);
-  if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
-    return val;
-  }
-  if (type !== exports.TYPES.PERCENT) {
-    return undefined;
+  switch (type) {
+    case exports.TYPES.NULL_OR_EMPTY_STR:
+    case exports.TYPES.VAR:
+      return val;
+    case exports.TYPES.CALC:
+      return cssCalc(val, {
+        format: 'specifiedValue',
+      });
+    case exports.TYPES.PERCENT:
+      const [, numVal, unit] = unitRegEx.exec(val);
+      return `${parseFloat(numVal)}${unit}`;
+    default:
+      if (type === exports.TYPES.NUMBER && parseFloat(val) === 0) {
+        return '0%';
+      }
+      return undefined;
   }
-  return val;
 };
 
 // either a length or a percent
 exports.parseMeasurement = function parseMeasurement(val) {
   var type = exports.valueType(val);
-  if (type === exports.TYPES.VAR) {
-    return val;
-  }
-  if (type === exports.TYPES.CALC) {
-    return cssCalc(val, {
-      format: 'specifiedValue',
-    });
-  }
-
-  var length = exports.parseLength(val);
-  if (length !== undefined) {
-    return length;
+  switch (type) {
+    case exports.TYPES.NULL_OR_EMPTY_STR:
+    case exports.TYPES.VAR:
+      return val;
+    case exports.TYPES.CALC:
+      return cssCalc(val, {
+        format: 'specifiedValue',
+      });
+    case exports.TYPES.LENGTH:
+    case exports.TYPES.PERCENT:
+      const [, numVal, unit] = unitRegEx.exec(val);
+      return `${parseFloat(numVal)}${unit}`;
+    default:
+      if (type === exports.TYPES.NUMBER && parseFloat(val) === 0) {
+        return '0px';
+      }
+      return undefined;
   }
-  return exports.parsePercent(val);
 };
 
-exports.parseInheritingMeasurement = function parseInheritingMeasurement(v) {
-  if (String(v).toLowerCase() === 'auto') {
-    return 'auto';
-  }
-  if (String(v).toLowerCase() === 'inherit') {
-    return 'inherit';
+exports.parseInheritingMeasurement = function parseInheritingMeasurement(val) {
+  if (/^(?:auto|inherit)$/i.test(val)) {
+    return val.toLowerCase();
   }
-  return exports.parseMeasurement(v);
+  return exports.parseMeasurement(val);
 };
 
 exports.parseUrl = function parseUrl(val) {
@@ -291,9 +305,12 @@ exports.parseString = function parseString(val) {
 
 exports.parseColor = function parseColor(val) {
   var type = exports.valueType(val);
-  if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
+  if (type & (exports.TYPES.NULL_OR_EMPTY_STR | exports.TYPES.VAR)) {
     return val;
   }
+  if (type === exports.TYPES.UNDEFINED) {
+    return undefined;
+  }
   if (/^[a-z]+$/i.test(val) && type === exports.TYPES.COLOR) {
     return val;
   }
@@ -306,6 +323,9 @@ exports.parseColor = function parseColor(val) {
   return undefined;
 };
 
+// FIXME:
+// This function seems to be incorrect.
+// However, this has no impact so far, as this function is only used by the deprecated `azimuth` property.
 exports.parseAngle = function parseAngle(val) {
   var type = exports.valueType(val);
   if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
@@ -331,7 +351,7 @@ exports.parseAngle = function parseAngle(val) {
   return flt + 'deg';
 };
 
-exports.parseKeyword = function parseKeyword(val, valid_keywords) {
+exports.parseKeyword = function parseKeyword(val, validKeywords) {
   var type = exports.valueType(val);
   if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
     return val;
@@ -341,9 +361,9 @@ exports.parseKeyword = function parseKeyword(val, valid_keywords) {
   }
   val = val.toString().toLowerCase();
   var i;
-  for (i = 0; i < valid_keywords.length; i++) {
-    if (valid_keywords[i].toLowerCase() === val) {
-      return valid_keywords[i];
+  for (i = 0; i < validKeywords.length; i++) {
+    if (validKeywords[i].toLowerCase() === val) {
+      return validKeywords[i];
     }
   }
   return undefined;
@@ -351,26 +371,28 @@ exports.parseKeyword = function parseKeyword(val, valid_keywords) {
 
 exports.parseImage = function parseImage(val) {
   if (/^(?:none|inherit)$/i.test(val)) {
-    return val;
+    return val.toLowerCase();
   }
   var type = exports.valueType(val);
-  if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.VAR) {
+  if (type & (exports.TYPES.NULL_OR_EMPTY_STR | exports.TYPES.VAR)) {
     return val;
   }
+  if (type === exports.TYPES.UNDEFINED) {
+    return undefined;
+  }
   var values = splitValue(val, ',');
   var isImage = !!values.length;
   var i;
   for (i = 0; i < values.length; i++) {
     var image = values[i];
-    var t = exports.valueType(image);
-    if (t === exports.TYPES.NULL_OR_EMPTY_STR) {
+    if (exports.valueType(image) === exports.TYPES.NULL_OR_EMPTY_STR) {
       return image;
     }
-    if (t === exports.TYPES.GRADIENT || /^(?:none|inherit)$/i.test(image)) {
+    if (isGradient(image) || /^(?:none|inherit)$/i.test(image)) {
       continue;
     }
     var imageUrl = exports.parseUrl(image);
-    if (exports.valueType(imageUrl) === exports.TYPES.URL) {
+    if (imageUrl) {
       values[i] = imageUrl;
     } else {
       isImage = false;
diff --git a/lib/properties/backgroundPosition.js b/lib/properties/backgroundPosition.js
index 9fb9b19a..edd89a47 100644
--- a/lib/properties/backgroundPosition.js
+++ b/lib/properties/backgroundPosition.js
@@ -13,27 +13,26 @@ var parse = function parse(v) {
     return undefined;
   }
   var types = [];
+  var typeLengthOrPercent = parsers.TYPES.LENGTH | parsers.TYPES.PERCENT;
+  var typeKeyword = parsers.TYPES.KEYWORD;
   parts.forEach(function (part, index) {
     types[index] = parsers.valueType(part);
   });
   if (parts.length === 1) {
-    if (types[0] === parsers.TYPES.LENGTH || types[0] === parsers.TYPES.PERCENT) {
+    if (types[0] & typeLengthOrPercent) {
       return v;
     }
-    if (types[0] === parsers.TYPES.KEYWORD) {
+    if (types[0] === typeKeyword) {
       if (valid_keywords.indexOf(v.toLowerCase()) !== -1 || v.toLowerCase() === 'inherit') {
         return v;
       }
     }
     return undefined;
   }
-  if (
-    (types[0] === parsers.TYPES.LENGTH || types[0] === parsers.TYPES.PERCENT) &&
-    (types[1] === parsers.TYPES.LENGTH || types[1] === parsers.TYPES.PERCENT)
-  ) {
+  if (types[0] & typeLengthOrPercent && types[1] & typeLengthOrPercent) {
     return v;
   }
-  if (types[0] !== parsers.TYPES.KEYWORD || types[1] !== parsers.TYPES.KEYWORD) {
+  if (types[0] !== typeKeyword || types[1] !== typeKeyword) {
     return undefined;
   }
   if (valid_keywords.indexOf(parts[0]) !== -1 && valid_keywords.indexOf(parts[1]) !== -1) {
diff --git a/lib/properties/fontFamily.js b/lib/properties/fontFamily.js
index 6cf0c597..4ece5d89 100644
--- a/lib/properties/fontFamily.js
+++ b/lib/properties/fontFamily.js
@@ -14,7 +14,7 @@ module.exports.isValid = function isValid(v) {
   var type;
   for (i = 0; i < len; i++) {
     type = valueType(parts[i]);
-    if (type === TYPES.STRING || type === TYPES.KEYWORD) {
+    if (type & (TYPES.STRING | TYPES.KEYWORD)) {
       return true;
     }
   }
diff --git a/lib/properties/fontSize.js b/lib/properties/fontSize.js
index 060ff02f..7e598e40 100644
--- a/lib/properties/fontSize.js
+++ b/lib/properties/fontSize.js
@@ -10,8 +10,7 @@ var relativeSizes = ['larger', 'smaller'];
 module.exports.isValid = function (v) {
   var type = valueType(v.toLowerCase());
   return (
-    type === TYPES.LENGTH ||
-    type === TYPES.PERCENT ||
+    type & (TYPES.LENGTH | TYPES.PERCENT) ||
     (type === TYPES.KEYWORD && absoluteSizes.indexOf(v.toLowerCase()) !== -1) ||
     (type === TYPES.KEYWORD && relativeSizes.indexOf(v.toLowerCase()) !== -1)
   );
diff --git a/lib/properties/lineHeight.js b/lib/properties/lineHeight.js
index a359bb62..b25f0419 100644
--- a/lib/properties/lineHeight.js
+++ b/lib/properties/lineHeight.js
@@ -8,9 +8,7 @@ module.exports.isValid = function isValid(v) {
   return (
     (type === TYPES.KEYWORD && v.toLowerCase() === 'normal') ||
     v.toLowerCase() === 'inherit' ||
-    type === TYPES.NUMBER ||
-    type === TYPES.LENGTH ||
-    type === TYPES.PERCENT
+    type & (TYPES.NUMBER | TYPES.LENGTH | TYPES.PERCENT | TYPES.CALC)
   );
 };
 
diff --git a/lib/properties/margin.js b/lib/properties/margin.js
index fc6f0314..0aa23e51 100644
--- a/lib/properties/margin.js
+++ b/lib/properties/margin.js
@@ -9,11 +9,8 @@ var isValid = function (v) {
   }
   var type = parsers.valueType(v);
   return (
-    type === TYPES.NULL_OR_EMPTY_STR ||
-    type === TYPES.LENGTH ||
-    type === TYPES.PERCENT ||
-    type === TYPES.CALC ||
-    (type === TYPES.INTEGER && (v === '0' || v === 0))
+    type & (TYPES.NULL_OR_EMPTY_STR | TYPES.LENGTH | TYPES.PERCENT | TYPES.CALC) ||
+    (type === TYPES.NUMBER && parseFloat(v) === 0)
   );
 };
 
diff --git a/lib/properties/padding.js b/lib/properties/padding.js
index a82900b3..a76d95f9 100644
--- a/lib/properties/padding.js
+++ b/lib/properties/padding.js
@@ -6,11 +6,8 @@ var TYPES = parsers.TYPES;
 var isValid = function (v) {
   var type = parsers.valueType(v);
   return (
-    type === TYPES.NULL_OR_EMPTY_STR ||
-    type === TYPES.LENGTH ||
-    type === TYPES.PERCENT ||
-    type === TYPES.CALC ||
-    (type === TYPES.INTEGER && (v === '0' || v === 0))
+    type & (TYPES.NULL_OR_EMPTY_STR | TYPES.LENGTH | TYPES.PERCENT | TYPES.CALC) ||
+    (type === TYPES.NUMBER && parseFloat(v) === 0)
   );
 };
 
diff --git a/package-lock.json b/package-lock.json
index dc40f2a6..dd12f361 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
       "version": "4.3.1",
       "license": "MIT",
       "dependencies": {
-        "@asamuzakjp/css-color": "^3.1.3",
+        "@asamuzakjp/css-color": "^3.1.4",
         "rrweb-cssom": "^0.8.0"
       },
       "devDependencies": {
@@ -30,9 +30,9 @@
       }
     },
     "node_modules/@asamuzakjp/css-color": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.3.tgz",
-      "integrity": "sha512-u25AyjuNrRFGb1O7KmWEu0ExN6iJMlUmDSlOPW/11JF8khOrIGG6oCoYpC+4mZlthNVhFUahk68lNrNI91f6Yg==",
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.4.tgz",
+      "integrity": "sha512-SeuBV4rnjpFNjI8HSgKUwteuFdkHwkboq31HWzznuqgySQir+jSTczoWVVL4jvOjKjuH80fMDG0Fvg1Sb+OJsA==",
       "license": "MIT",
       "dependencies": {
         "@csstools/css-calc": "^2.1.3",
diff --git a/package.json b/package.json
index 05b41e2e..0b24ae53 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
   ],
   "main": "./lib/CSSStyleDeclaration.js",
   "dependencies": {
-    "@asamuzakjp/css-color": "^3.1.3",
+    "@asamuzakjp/css-color": "^3.1.4",
     "rrweb-cssom": "^0.8.0"
   },
   "devDependencies": {
diff --git a/test/parsers.js b/test/parsers.js
index de16abdb..0fcca83f 100644
--- a/test/parsers.js
+++ b/test/parsers.js
@@ -23,14 +23,14 @@ describe('valueType', () => {
     let input = undefined;
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, undefined);
+    assert.strictEqual(output, parsers.TYPES.UNDEFINED);
   });
 
-  it('returns integer for 1', () => {
+  it('returns number for 1', () => {
     let input = 1;
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, parsers.TYPES.INTEGER);
+    assert.strictEqual(output, parsers.TYPES.NUMBER);
   });
 
   it('returns number for 1.1', () => {
@@ -40,6 +40,13 @@ describe('valueType', () => {
     assert.strictEqual(output, parsers.TYPES.NUMBER);
   });
 
+  it('returns number for ".1"', () => {
+    let input = '.1';
+    let output = parsers.valueType(input);
+
+    assert.strictEqual(output, parsers.TYPES.NUMBER);
+  });
+
   it('returns length for 100ch', () => {
     let input = '100ch';
     let output = parsers.valueType(input);
@@ -54,39 +61,39 @@ describe('valueType', () => {
     assert.strictEqual(output, parsers.TYPES.PERCENT);
   });
 
-  it('returns url for url(https://example.com)', () => {
+  it('returns unidentified for url(https://example.com)', () => {
     let input = 'url(https://example.com)';
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, parsers.TYPES.URL);
+    assert.strictEqual(output, parsers.TYPES.UNIDENT);
   });
 
-  it('returns url for url("https://example.com")', () => {
+  it('returns unidentified for url("https://example.com")', () => {
     let input = 'url("https://example.com")';
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, parsers.TYPES.URL);
+    assert.strictEqual(output, parsers.TYPES.UNIDENT);
   });
 
-  it('returns url for url(foo.png)', () => {
+  it('returns unidentified for url(foo.png)', () => {
     let input = 'url(foo.png)';
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, parsers.TYPES.URL);
+    assert.strictEqual(output, parsers.TYPES.UNIDENT);
   });
 
-  it('returns url for url("foo.png")', () => {
+  it('returns unidentified for url("foo.png")', () => {
     let input = 'url("foo.png")';
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, parsers.TYPES.URL);
+    assert.strictEqual(output, parsers.TYPES.UNIDENT);
   });
 
-  it('returns undefined for url(var(--foo))', () => {
+  it('returns var for url(var(--foo))', () => {
     let input = 'url(var(--foo))';
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, undefined);
+    assert.strictEqual(output, parsers.TYPES.VAR);
   });
 
   it('returns var from calc(100px *  var(--foo))', () => {
@@ -215,11 +222,18 @@ describe('valueType', () => {
     assert.strictEqual(output, parsers.TYPES.COLOR);
   });
 
-  it('returns gradient for linear-gradient(red, blue)', () => {
+  it('returns unidentified for linear-gradient(red, blue)', () => {
     let input = 'linear-gradient(red, blue)';
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, parsers.TYPES.GRADIENT);
+    assert.strictEqual(output, parsers.TYPES.UNIDENT);
+  });
+
+  it('returns color for accentcolor', () => {
+    let input = 'AccentColor';
+    let output = parsers.valueType(input);
+
+    assert.strictEqual(output, parsers.TYPES.COLOR);
   });
 
   it('returns color for legacy activeborder', () => {
@@ -229,27 +243,189 @@ describe('valueType', () => {
     assert.strictEqual(output, parsers.TYPES.COLOR);
   });
 
-  it('returns keyword for else', () => {
+  it('returns keyword for foo', () => {
     let input = 'foo';
     let output = parsers.valueType(input);
 
     assert.strictEqual(output, parsers.TYPES.KEYWORD);
   });
-});
 
-describe('parseInteger', () => {
-  it.todo('test');
+  it('returns keyword for foo-bar', () => {
+    let input = 'foo-bar';
+    let output = parsers.valueType(input);
+
+    assert.strictEqual(output, parsers.TYPES.KEYWORD);
+  });
+
+  it('returns unidentified for foo(bar)', () => {
+    let input = 'foo(bar)';
+    let output = parsers.valueType(input);
+
+    assert.strictEqual(output, parsers.TYPES.UNIDENT);
+  });
 });
+
 describe('parseNumber', () => {
-  it.todo('test');
+  it('should return null', () => {
+    let input = null;
+    let output = parsers.parseNumber(input);
+
+    assert.strictEqual(output, null);
+  });
+
+  it('should return empty string', () => {
+    let input = '';
+    let output = parsers.parseNumber(input);
+
+    assert.strictEqual(output, '');
+  });
+
+  it('should return undefined', () => {
+    let input = 'foo';
+    let output = parsers.parseNumber(input);
+
+    assert.strictEqual(output, undefined);
+  });
+
+  it('should return undefined', () => {
+    let input = undefined;
+    let output = parsers.parseNumber(input);
+
+    assert.strictEqual(output, undefined);
+  });
+
+  it('should return "1"', () => {
+    let input = 1;
+    let output = parsers.parseNumber(input);
+
+    assert.strictEqual(output, '1');
+  });
+
+  it('should return "1"', () => {
+    let input = '1';
+    let output = parsers.parseNumber(input);
+
+    assert.strictEqual(output, '1');
+  });
+
+  it('should return "0.5"', () => {
+    let input = 0.5;
+    let output = parsers.parseNumber(input);
+
+    assert.strictEqual(output, '0.5');
+  });
+
+  it('should return "0.5"', () => {
+    let input = '0.5';
+    let output = parsers.parseNumber(input);
+
+    assert.strictEqual(output, '0.5');
+  });
+
+  it('should return "0.5"', () => {
+    let input = '.5';
+    let output = parsers.parseNumber(input);
+
+    assert.strictEqual(output, '0.5');
+  });
 });
+
 describe('parseLength', () => {
-  it.todo('test');
+  it('should return null', () => {
+    let input = null;
+    let output = parsers.parseLength(input);
+
+    assert.strictEqual(output, null);
+  });
+
+  it('should return empty string', () => {
+    let input = '';
+    let output = parsers.parseLength(input);
+
+    assert.strictEqual(output, '');
+  });
+
+  it('should return value as is', () => {
+    let input = 'var(/* comment */ --foo)';
+    let output = parsers.parseLength(input);
+
+    assert.strictEqual(output, 'var(/* comment */ --foo)');
+  });
+
+  it('should return calculated value', () => {
+    let input = 'calc(2em / 3)';
+    let output = parsers.parseLength(input);
+
+    assert.strictEqual(output, 'calc(0.666667em)');
+  });
+
+  it('should return serialized value', () => {
+    let input = 'calc(10px + 20%)';
+    let output = parsers.parseLength(input);
+
+    assert.strictEqual(output, 'calc(20% + 10px)');
+  });
+
+  it('should return serialized value', () => {
+    let input = 'calc(100vh + 10px)';
+    let output = parsers.parseLength(input);
+
+    assert.strictEqual(output, 'calc(10px + 100vh)');
+  });
 });
+
 describe('parsePercent', () => {
-  it.todo('test');
+  it('should return null', () => {
+    let input = null;
+    let output = parsers.parsePercent(input);
+
+    assert.strictEqual(output, null);
+  });
+
+  it('should return empty string', () => {
+    let input = '';
+    let output = parsers.parsePercent(input);
+
+    assert.strictEqual(output, '');
+  });
+
+  it('should return value as is', () => {
+    let input = 'var(/* comment */ --foo)';
+    let output = parsers.parsePercent(input);
+
+    assert.strictEqual(output, 'var(/* comment */ --foo)');
+  });
+
+  it('should return calculated value', () => {
+    let input = 'calc(100% / 3)';
+    let output = parsers.parsePercent(input);
+
+    assert.strictEqual(output, 'calc(33.3333%)');
+  });
+
+  it('should return serialized value', () => {
+    let input = 'calc(10px + 20%)';
+    let output = parsers.parsePercent(input);
+
+    assert.strictEqual(output, 'calc(20% + 10px)');
+  });
 });
+
 describe('parseMeasurement', () => {
+  it('should return null', () => {
+    let input = null;
+    let output = parsers.parseMeasurement(input);
+
+    assert.strictEqual(output, null);
+  });
+
+  it('should return empty string', () => {
+    let input = '';
+    let output = parsers.parseMeasurement(input);
+
+    assert.strictEqual(output, '');
+  });
+
   it('should return value with em unit', () => {
     let input = '1em';
     let output = parsers.parseMeasurement(input);
@@ -293,15 +469,86 @@ describe('parseMeasurement', () => {
   });
 
   it('should return serialized value', () => {
-    let input = 'calc(10px + 100vh)';
+    let input = 'calc(100vh + 10px)';
     let output = parsers.parseMeasurement(input);
 
     assert.strictEqual(output, 'calc(10px + 100vh)');
   });
 
-  it.todo('test');
+  it('should return 0px for 0', () => {
+    let input = 0;
+    let output = parsers.parseMeasurement(input);
+
+    assert.strictEqual(output, '0px');
+  });
+
+  it('should return 0px for "0"', () => {
+    let input = '0';
+    let output = parsers.parseMeasurement(input);
+
+    assert.strictEqual(output, '0px');
+  });
+});
+
+describe('parseInheritingMeasurement', () => {
+  it('should return auto', () => {
+    let input = 'auto';
+    let output = parsers.parseInheritingMeasurement(input);
+
+    assert.strictEqual(output, 'auto');
+  });
+
+  it('should return auto', () => {
+    let input = 'AUTO';
+    let output = parsers.parseInheritingMeasurement(input);
+
+    assert.strictEqual(output, 'auto');
+  });
+
+  it('should return inherit', () => {
+    let input = 'inherit';
+    let output = parsers.parseInheritingMeasurement(input);
+
+    assert.strictEqual(output, 'inherit');
+  });
+
+  it('should return inherit', () => {
+    let input = 'INHERIT';
+    let output = parsers.parseInheritingMeasurement(input);
+
+    assert.strictEqual(output, 'inherit');
+  });
+
+  it('should return value with em unit', () => {
+    let input = '1em';
+    let output = parsers.parseInheritingMeasurement(input);
+
+    assert.strictEqual(output, '1em');
+  });
+
+  it('should return value with percent', () => {
+    let input = '100%';
+    let output = parsers.parseInheritingMeasurement(input);
+
+    assert.strictEqual(output, '100%');
+  });
 });
+
 describe('parseUrl', () => {
+  it('should return null', () => {
+    let input = null;
+    let output = parsers.parseUrl(input);
+
+    assert.strictEqual(output, null);
+  });
+
+  it('should return empty string', () => {
+    let input = '';
+    let output = parsers.parseUrl(input);
+
+    assert.strictEqual(output, '');
+  });
+
   it('should return undefined', () => {
     let input = 'url(var(--foo))';
     let output = parsers.parseUrl(input);
@@ -309,6 +556,13 @@ describe('parseUrl', () => {
     assert.strictEqual(output, undefined);
   });
 
+  it('should return undefined', () => {
+    let input = undefined;
+    let output = parsers.parseUrl(input);
+
+    assert.strictEqual(output, undefined);
+  });
+
   it('should return quoted url string', () => {
     let input = 'url(sample.png)';
     let output = parsers.parseUrl(input);
@@ -428,13 +682,34 @@ describe('parseUrl', () => {
 
     assert.strictEqual(output, 'url("")');
   });
-
-  it.todo('test');
 });
+
 describe('parseString', () => {
   it.todo('test');
 });
+
 describe('parseColor', () => {
+  it('should return null', () => {
+    let input = null;
+    let output = parsers.parseColor(input);
+
+    assert.strictEqual(output, null);
+  });
+
+  it('should return empty string', () => {
+    let input = '';
+    let output = parsers.parseColor(input);
+
+    assert.strictEqual(output, '');
+  });
+
+  it('should return undefined', () => {
+    let input = undefined;
+    let output = parsers.parseColor(input);
+
+    assert.strictEqual(output, undefined);
+  });
+
   it('should convert hsl to rgb values', () => {
     let input = 'hsla(0, 1%, 2%)';
     let output = parsers.parseColor(input);
@@ -518,15 +793,16 @@ describe('parseColor', () => {
 
     assert.strictEqual(output, 'transparent');
   });
-
-  it.todo('Add more tests');
 });
+
 describe('parseAngle', () => {
   it.todo('test');
 });
+
 describe('parseKeyword', () => {
   it.todo('test');
 });
+
 describe('parseImage', () => {
   it('should return value', () => {
     let input = 'none';
@@ -542,6 +818,20 @@ describe('parseImage', () => {
     assert.strictEqual(output, 'inherit');
   });
 
+  it('should return empty string', () => {
+    let input = '';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, '');
+  });
+
+  it('should return null', () => {
+    let input = null;
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, null);
+  });
+
   it('should return undefined', () => {
     let input = 'foo';
     let output = parsers.parseImage(input);
@@ -556,13 +846,6 @@ describe('parseImage', () => {
     assert.strictEqual(output, undefined);
   });
 
-  it('should return empty string', () => {
-    let input = '';
-    let output = parsers.parseImage(input);
-
-    assert.strictEqual(output, '');
-  });
-
   it('should return value', () => {
     let input = 'url(example.png)';
     let output = parsers.parseImage(input);

From 9a77222d6bcdd48d7cd28250d15c41accc47e46e Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Sun, 27 Apr 2025 07:51:09 +0900
Subject: [PATCH 02/10] Update parseNumber()

---
 lib/parsers.js    | 4 ++++
 package-lock.json | 8 ++++----
 package.json      | 2 +-
 test/parsers.js   | 7 +++++++
 4 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/lib/parsers.js b/lib/parsers.js
index 4bca8d81..269020ab 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -138,6 +138,10 @@ exports.parseNumber = function parseNumber(val) {
       return val;
     case exports.TYPES.NUMBER:
       return `${parseFloat(val)}`;
+    case exports.TYPES.CALC:
+      return cssCalc(val, {
+        format: 'specifiedValue',
+      });
     default:
       return undefined;
   }
diff --git a/package-lock.json b/package-lock.json
index dd12f361..4567cbd8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
       "version": "4.3.1",
       "license": "MIT",
       "dependencies": {
-        "@asamuzakjp/css-color": "^3.1.4",
+        "@asamuzakjp/css-color": "^3.1.5",
         "rrweb-cssom": "^0.8.0"
       },
       "devDependencies": {
@@ -30,9 +30,9 @@
       }
     },
     "node_modules/@asamuzakjp/css-color": {
-      "version": "3.1.4",
-      "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.4.tgz",
-      "integrity": "sha512-SeuBV4rnjpFNjI8HSgKUwteuFdkHwkboq31HWzznuqgySQir+jSTczoWVVL4jvOjKjuH80fMDG0Fvg1Sb+OJsA==",
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.5.tgz",
+      "integrity": "sha512-w7AmVyTTiU41fNLsFDf+gA2Dwtbx2EJtn2pbJNAGSRAg50loXy1uLXA3hEpD8+eydcomTurw09tq5/AyceCaGg==",
       "license": "MIT",
       "dependencies": {
         "@csstools/css-calc": "^2.1.3",
diff --git a/package.json b/package.json
index 0b24ae53..18846c34 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
   ],
   "main": "./lib/CSSStyleDeclaration.js",
   "dependencies": {
-    "@asamuzakjp/css-color": "^3.1.4",
+    "@asamuzakjp/css-color": "^3.1.5",
     "rrweb-cssom": "^0.8.0"
   },
   "devDependencies": {
diff --git a/test/parsers.js b/test/parsers.js
index 0fcca83f..bd939d7d 100644
--- a/test/parsers.js
+++ b/test/parsers.js
@@ -328,6 +328,13 @@ describe('parseNumber', () => {
 
     assert.strictEqual(output, '0.5');
   });
+
+  it('should return calculated value', () => {
+    let input = 'calc(2 / 3)';
+    let output = parsers.parseLength(input);
+
+    assert.strictEqual(output, 'calc(0.666667)');
+  });
 });
 
 describe('parseLength', () => {

From dca851dd1230d6a7025bbe3bb2bbe9bb9dc2d5d3 Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Sun, 27 Apr 2025 08:43:24 +0900
Subject: [PATCH 03/10] Camelize

---
 lib/parsers.js | 103 ++++++++++++++++++++++++-------------------------
 1 file changed, 50 insertions(+), 53 deletions(-)

diff --git a/lib/parsers.js b/lib/parsers.js
index 269020ab..f5f3c3ec 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -426,49 +426,46 @@ var dashedToCamelCase = function (dashed) {
 };
 exports.dashedToCamelCase = dashedToCamelCase;
 
-var is_space = /\s/;
-var opening_deliminators = ['"', "'", '('];
-var closing_deliminators = ['"', "'", ')'];
+var isSpace = /\s/;
+var openingDeliminators = ['"', "'", '('];
+var closingDeliminators = ['"', "'", ')'];
 // this splits on whitespace, but keeps quoted and parened parts together
 var getParts = function (str) {
-  var deliminator_stack = [];
+  var deliminatorStack = [];
   var length = str.length;
   var i;
   var parts = [];
-  var current_part = '';
-  var opening_index;
-  var closing_index;
+  var currentPart = '';
+  var openingIndex;
+  var closingIndex;
   for (i = 0; i < length; i++) {
-    opening_index = opening_deliminators.indexOf(str[i]);
-    closing_index = closing_deliminators.indexOf(str[i]);
-    if (is_space.test(str[i])) {
-      if (deliminator_stack.length === 0) {
-        if (current_part !== '') {
-          parts.push(current_part);
+    openingIndex = openingDeliminators.indexOf(str[i]);
+    closingIndex = closingDeliminators.indexOf(str[i]);
+    if (isSpace.test(str[i])) {
+      if (deliminatorStack.length === 0) {
+        if (currentPart !== '') {
+          parts.push(currentPart);
         }
-        current_part = '';
+        currentPart = '';
       } else {
-        current_part += str[i];
+        currentPart += str[i];
       }
     } else {
       if (str[i] === '\\') {
         i++;
-        current_part += str[i];
+        currentPart += str[i];
       } else {
-        current_part += str[i];
-        if (
-          closing_index !== -1 &&
-          closing_index === deliminator_stack[deliminator_stack.length - 1]
-        ) {
-          deliminator_stack.pop();
-        } else if (opening_index !== -1) {
-          deliminator_stack.push(opening_index);
+        currentPart += str[i];
+        if (closingIndex !== -1 && closingIndex === deliminatorStack[deliminatorStack.length - 1]) {
+          deliminatorStack.pop();
+        } else if (openingIndex !== -1) {
+          deliminatorStack.push(openingIndex);
         }
       }
     }
   }
-  if (current_part !== '') {
-    parts.push(current_part);
+  if (currentPart !== '') {
+    parts.push(currentPart);
   }
   return parts;
 };
@@ -479,11 +476,11 @@ var getParts = function (str) {
  * hand properties and the values are the values to set
  * on them
  */
-exports.shorthandParser = function parse(v, shorthand_for) {
+exports.shorthandParser = function parse(v, shorthandFor) {
   var obj = {};
   var type = exports.valueType(v);
   if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
-    Object.keys(shorthand_for).forEach(function (property) {
+    Object.keys(shorthandFor).forEach(function (property) {
       obj[property] = '';
     });
     return obj;
@@ -503,14 +500,14 @@ exports.shorthandParser = function parse(v, shorthand_for) {
   var parts = getParts(v);
   var valid = true;
   parts.forEach(function (part, i) {
-    var part_valid = false;
-    Object.keys(shorthand_for).forEach(function (property) {
-      if (shorthand_for[property].isValid(part, i)) {
-        part_valid = true;
+    var partValid = false;
+    Object.keys(shorthandFor).forEach(function (property) {
+      if (shorthandFor[property].isValid(part, i)) {
+        partValid = true;
         obj[property] = part;
       }
     });
-    valid = valid && part_valid;
+    valid = valid && partValid;
   });
   if (!valid) {
     return undefined;
@@ -518,9 +515,9 @@ exports.shorthandParser = function parse(v, shorthand_for) {
   return obj;
 };
 
-exports.shorthandSetter = function (property, shorthand_for) {
+exports.shorthandSetter = function (property, shorthandFor) {
   return function (v) {
-    var obj = exports.shorthandParser(v, shorthand_for);
+    var obj = exports.shorthandParser(v, shorthandFor);
     if (obj === undefined) {
       return;
     }
@@ -538,7 +535,7 @@ exports.shorthandSetter = function (property, shorthand_for) {
         this._values[subprop] = obj[subprop];
       }
     }, this);
-    Object.keys(shorthand_for).forEach(function (subprop) {
+    Object.keys(shorthandFor).forEach(function (subprop) {
       if (!obj.hasOwnProperty(subprop)) {
         this.removeProperty(subprop);
         delete this._values[subprop];
@@ -549,19 +546,19 @@ exports.shorthandSetter = function (property, shorthand_for) {
     // if it already exists, then call the shorthandGetter, if it's an empty
     // string, don't set the property
     this.removeProperty(property);
-    var calculated = exports.shorthandGetter(property, shorthand_for).call(this);
+    var calculated = exports.shorthandGetter(property, shorthandFor).call(this);
     if (calculated !== '') {
       this._setProperty(property, calculated);
     }
   };
 };
 
-exports.shorthandGetter = function (property, shorthand_for) {
+exports.shorthandGetter = function (property, shorthandFor) {
   return function () {
     if (this._values[property] !== undefined) {
       return this.getPropertyValue(property);
     }
-    return Object.keys(shorthand_for)
+    return Object.keys(shorthandFor)
       .map(function (subprop) {
         return this.getPropertyValue(subprop);
       }, this)
@@ -577,12 +574,12 @@ exports.shorthandGetter = function (property, shorthand_for) {
 // if two, the first applies to the top and bottom, and the second to left and right
 // if three, the first applies to the top, the second to left and right, the third bottom
 // if four, top, right, bottom, left
-exports.implicitSetter = function (property_before, property_after, isValid, parser) {
-  property_after = property_after || '';
-  if (property_after !== '') {
-    property_after = '-' + property_after;
+exports.implicitSetter = function (propertyBefore, propertyAfter, isValid, parser) {
+  propertyAfter = propertyAfter || '';
+  if (propertyAfter !== '') {
+    propertyAfter = '-' + propertyAfter;
   }
-  var part_names = ['top', 'right', 'bottom', 'left'];
+  var partNames = ['top', 'right', 'bottom', 'left'];
 
   return function (v) {
     if (typeof v === 'number') {
@@ -608,7 +605,7 @@ exports.implicitSetter = function (property_before, property_after, isValid, par
     parts = parts.map(function (part) {
       return parser(part);
     });
-    this._setProperty(property_before + property_after, parts.join(' '));
+    this._setProperty(propertyBefore + propertyAfter, parts.join(' '));
     if (parts.length === 1) {
       parts[1] = parts[0];
     }
@@ -620,7 +617,7 @@ exports.implicitSetter = function (property_before, property_after, isValid, par
     }
 
     for (var i = 0; i < 4; i++) {
-      var property = property_before + '-' + part_names[i] + property_after;
+      var property = propertyBefore + '-' + partNames[i] + propertyAfter;
       this.removeProperty(property);
       if (parts[i] !== '') {
         this._values[property] = parts[i];
@@ -682,14 +679,14 @@ exports.subImplicitSetter = function (prefix, part, isValid, parser) {
   };
 };
 
-var camel_to_dashed = /[A-Z]/g;
-var first_segment = /^\([^-]\)-/;
-var vendor_prefixes = ['o', 'moz', 'ms', 'webkit'];
-exports.camelToDashed = function (camel_case) {
+var camelToDashed = /[A-Z]/g;
+var firstSegment = /^\([^-]\)-/;
+var vendorPrefixes = ['o', 'moz', 'ms', 'webkit'];
+exports.camelToDashed = function (camelCase) {
   var match;
-  var dashed = camel_case.replace(camel_to_dashed, '-$&').toLowerCase();
-  match = dashed.match(first_segment);
-  if (match && vendor_prefixes.indexOf(match[1]) !== -1) {
+  var dashed = camelCase.replace(camelToDashed, '-$&').toLowerCase();
+  match = dashed.match(firstSegment);
+  if (match && vendorPrefixes.indexOf(match[1]) !== -1) {
     dashed = '-' + dashed;
   }
   return dashed;

From 94c4dd9d49881e57cb53066d86101fabfb5950d8 Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Sun, 27 Apr 2025 16:09:46 +0900
Subject: [PATCH 04/10] Revert bitwise

---
 lib/parsers.js                       |  4 ++--
 lib/properties/backgroundPosition.js | 13 +++++++------
 lib/properties/fontFamily.js         |  2 +-
 lib/properties/fontSize.js           |  3 ++-
 lib/properties/lineHeight.js         |  4 +++-
 lib/properties/margin.js             |  5 ++++-
 lib/properties/padding.js            |  5 ++++-
 7 files changed, 23 insertions(+), 13 deletions(-)

diff --git a/lib/parsers.js b/lib/parsers.js
index f5f3c3ec..e0be48be 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -309,7 +309,7 @@ exports.parseString = function parseString(val) {
 
 exports.parseColor = function parseColor(val) {
   var type = exports.valueType(val);
-  if (type & (exports.TYPES.NULL_OR_EMPTY_STR | exports.TYPES.VAR)) {
+  if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.VAR) {
     return val;
   }
   if (type === exports.TYPES.UNDEFINED) {
@@ -378,7 +378,7 @@ exports.parseImage = function parseImage(val) {
     return val.toLowerCase();
   }
   var type = exports.valueType(val);
-  if (type & (exports.TYPES.NULL_OR_EMPTY_STR | exports.TYPES.VAR)) {
+  if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.VAR) {
     return val;
   }
   if (type === exports.TYPES.UNDEFINED) {
diff --git a/lib/properties/backgroundPosition.js b/lib/properties/backgroundPosition.js
index edd89a47..9fb9b19a 100644
--- a/lib/properties/backgroundPosition.js
+++ b/lib/properties/backgroundPosition.js
@@ -13,26 +13,27 @@ var parse = function parse(v) {
     return undefined;
   }
   var types = [];
-  var typeLengthOrPercent = parsers.TYPES.LENGTH | parsers.TYPES.PERCENT;
-  var typeKeyword = parsers.TYPES.KEYWORD;
   parts.forEach(function (part, index) {
     types[index] = parsers.valueType(part);
   });
   if (parts.length === 1) {
-    if (types[0] & typeLengthOrPercent) {
+    if (types[0] === parsers.TYPES.LENGTH || types[0] === parsers.TYPES.PERCENT) {
       return v;
     }
-    if (types[0] === typeKeyword) {
+    if (types[0] === parsers.TYPES.KEYWORD) {
       if (valid_keywords.indexOf(v.toLowerCase()) !== -1 || v.toLowerCase() === 'inherit') {
         return v;
       }
     }
     return undefined;
   }
-  if (types[0] & typeLengthOrPercent && types[1] & typeLengthOrPercent) {
+  if (
+    (types[0] === parsers.TYPES.LENGTH || types[0] === parsers.TYPES.PERCENT) &&
+    (types[1] === parsers.TYPES.LENGTH || types[1] === parsers.TYPES.PERCENT)
+  ) {
     return v;
   }
-  if (types[0] !== typeKeyword || types[1] !== typeKeyword) {
+  if (types[0] !== parsers.TYPES.KEYWORD || types[1] !== parsers.TYPES.KEYWORD) {
     return undefined;
   }
   if (valid_keywords.indexOf(parts[0]) !== -1 && valid_keywords.indexOf(parts[1]) !== -1) {
diff --git a/lib/properties/fontFamily.js b/lib/properties/fontFamily.js
index 4ece5d89..6cf0c597 100644
--- a/lib/properties/fontFamily.js
+++ b/lib/properties/fontFamily.js
@@ -14,7 +14,7 @@ module.exports.isValid = function isValid(v) {
   var type;
   for (i = 0; i < len; i++) {
     type = valueType(parts[i]);
-    if (type & (TYPES.STRING | TYPES.KEYWORD)) {
+    if (type === TYPES.STRING || type === TYPES.KEYWORD) {
       return true;
     }
   }
diff --git a/lib/properties/fontSize.js b/lib/properties/fontSize.js
index 7e598e40..060ff02f 100644
--- a/lib/properties/fontSize.js
+++ b/lib/properties/fontSize.js
@@ -10,7 +10,8 @@ var relativeSizes = ['larger', 'smaller'];
 module.exports.isValid = function (v) {
   var type = valueType(v.toLowerCase());
   return (
-    type & (TYPES.LENGTH | TYPES.PERCENT) ||
+    type === TYPES.LENGTH ||
+    type === TYPES.PERCENT ||
     (type === TYPES.KEYWORD && absoluteSizes.indexOf(v.toLowerCase()) !== -1) ||
     (type === TYPES.KEYWORD && relativeSizes.indexOf(v.toLowerCase()) !== -1)
   );
diff --git a/lib/properties/lineHeight.js b/lib/properties/lineHeight.js
index b25f0419..a359bb62 100644
--- a/lib/properties/lineHeight.js
+++ b/lib/properties/lineHeight.js
@@ -8,7 +8,9 @@ module.exports.isValid = function isValid(v) {
   return (
     (type === TYPES.KEYWORD && v.toLowerCase() === 'normal') ||
     v.toLowerCase() === 'inherit' ||
-    type & (TYPES.NUMBER | TYPES.LENGTH | TYPES.PERCENT | TYPES.CALC)
+    type === TYPES.NUMBER ||
+    type === TYPES.LENGTH ||
+    type === TYPES.PERCENT
   );
 };
 
diff --git a/lib/properties/margin.js b/lib/properties/margin.js
index 0aa23e51..339d65b4 100644
--- a/lib/properties/margin.js
+++ b/lib/properties/margin.js
@@ -9,7 +9,10 @@ var isValid = function (v) {
   }
   var type = parsers.valueType(v);
   return (
-    type & (TYPES.NULL_OR_EMPTY_STR | TYPES.LENGTH | TYPES.PERCENT | TYPES.CALC) ||
+    type === TYPES.NULL_OR_EMPTY_STR ||
+    type === TYPES.LENGTH ||
+    type === TYPES.PERCENT ||
+    type === TYPES.CALC ||
     (type === TYPES.NUMBER && parseFloat(v) === 0)
   );
 };
diff --git a/lib/properties/padding.js b/lib/properties/padding.js
index a76d95f9..792f65f0 100644
--- a/lib/properties/padding.js
+++ b/lib/properties/padding.js
@@ -6,7 +6,10 @@ var TYPES = parsers.TYPES;
 var isValid = function (v) {
   var type = parsers.valueType(v);
   return (
-    type & (TYPES.NULL_OR_EMPTY_STR | TYPES.LENGTH | TYPES.PERCENT | TYPES.CALC) ||
+    type === TYPES.NULL_OR_EMPTY_STR ||
+    type === TYPES.LENGTH ||
+    type === TYPES.PERCENT ||
+    type === TYPES.CALC ||
     (type === TYPES.NUMBER && parseFloat(v) === 0)
   );
 };

From 973ba1107d614c8717a36aa8c1f95daee7757ccd Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Sat, 3 May 2025 09:31:50 +0900
Subject: [PATCH 05/10] Update dashedToCamelCase

---
 lib/parsers.js  | 13 ++++++++-----
 test/parsers.js | 30 +++++++++++++++++++++++++++++-
 2 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/lib/parsers.js b/lib/parsers.js
index e0be48be..d72792cf 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -410,11 +410,15 @@ exports.parseImage = function parseImage(val) {
 };
 
 // utility to translate from border-width to borderWidth
-var dashedToCamelCase = function (dashed) {
-  var i;
+exports.dashedToCamelCase = function (dashed) {
+  if (dashed.startsWith('--')) {
+    return dashed;
+  }
+  // skip leading hyphen in vendor prefixed value, e.g. -webkit-foo
+  var i = /^\-[a-z]/.test(dashed) ? 1 : 0;
   var camel = '';
   var nextCap = false;
-  for (i = 0; i < dashed.length; i++) {
+  for (; i < dashed.length; i++) {
     if (dashed[i] !== '-') {
       camel += nextCap ? dashed[i].toUpperCase() : dashed[i];
       nextCap = false;
@@ -424,7 +428,6 @@ var dashedToCamelCase = function (dashed) {
   }
   return camel;
 };
-exports.dashedToCamelCase = dashedToCamelCase;
 
 var isSpace = /\s/;
 var openingDeliminators = ['"', "'", '('];
@@ -525,7 +528,7 @@ exports.shorthandSetter = function (property, shorthandFor) {
     Object.keys(obj).forEach(function (subprop) {
       // in case subprop is an implicit property, this will clear
       // *its* subpropertiesX
-      var camel = dashedToCamelCase(subprop);
+      var camel = exports.dashedToCamelCase(subprop);
       this[camel] = obj[subprop];
       // in case it gets translated into something else (0 -> 0px)
       obj[subprop] = this[camel];
diff --git a/test/parsers.js b/test/parsers.js
index bd939d7d..05cacef3 100644
--- a/test/parsers.js
+++ b/test/parsers.js
@@ -900,9 +900,37 @@ describe('parseImage', () => {
 
   it.todo('test');
 });
+
 describe('dashedToCamelCase', () => {
-  it.todo('test');
+  it('should not camelize custom property', () => {
+    let input = '--foo-bar-baz';
+    let output = parsers.dashedToCamelCase(input);
+
+    assert.strictEqual(output, '--foo-bar-baz');
+  });
+
+  it('should camelize value', () => {
+    let input = 'foo-bar-baz';
+    let output = parsers.dashedToCamelCase(input);
+
+    assert.strictEqual(output, 'fooBarBaz');
+  });
+
+  it('should camelize vendor prefixed value', () => {
+    let input = '-webkit-foo';
+    let output = parsers.dashedToCamelCase(input);
+
+    assert.strictEqual(output, 'webkitFoo');
+  });
+
+  it('should not camelize snake cased value', () => {
+    let input = 'foo_bar_baz';
+    let output = parsers.dashedToCamelCase(input);
+
+    assert.strictEqual(output, 'foo_bar_baz');
+  });
 });
+
 describe('shorthandParser', () => {
   it.todo('test');
 });

From 4d19a925ba50c01b8c1705ab807078c18492c5c2 Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Sat, 3 May 2025 17:32:16 +0900
Subject: [PATCH 06/10] Update regex for var()

Fix #199
---
 lib/parsers.js    | 20 ++++++++++++++++++--
 package-lock.json |  8 ++++----
 package.json      |  2 +-
 test/parsers.js   | 28 ++++++++++++++++++++--------
 4 files changed, 43 insertions(+), 15 deletions(-)

diff --git a/lib/parsers.js b/lib/parsers.js
index d72792cf..92c72bae 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -30,7 +30,8 @@ var angleRegEx = new RegExp(`^${NUMBER}(?:deg|g?rad|turn)$`);
 var urlRegEx = /^url\(\s*((?:[^)]|\\\))*)\s*\)$/;
 var keywordRegEx = /^[a-z]+(?:\-[a-z]+)*$/i;
 var stringRegEx = /^("[^"]*"|'[^']*')$/;
-var varRegEx = /^var\(|(?<=[*/\s(])var\(/;
+var varRegEx = /^var\(/;
+var varContainedRegEx = /(?<=[*/\s(])var\(/;
 var calcRegEx =
   /^(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)\(/;
 
@@ -143,6 +144,9 @@ exports.parseNumber = function parseNumber(val) {
         format: 'specifiedValue',
       });
     default:
+      if (varContainedRegEx.test(val)) {
+        return val;
+      }
       return undefined;
   }
 };
@@ -162,6 +166,9 @@ exports.parseLength = function parseLength(val) {
       return `${parseFloat(numVal)}${unit}`;
     }
     default:
+      if (varContainedRegEx.test(val)) {
+        return val;
+      }
       if (type === exports.TYPES.NUMBER && parseFloat(val) === 0) {
         return '0px';
       }
@@ -183,6 +190,9 @@ exports.parsePercent = function parsePercent(val) {
       const [, numVal, unit] = unitRegEx.exec(val);
       return `${parseFloat(numVal)}${unit}`;
     default:
+      if (varContainedRegEx.test(val)) {
+        return val;
+      }
       if (type === exports.TYPES.NUMBER && parseFloat(val) === 0) {
         return '0%';
       }
@@ -206,6 +216,9 @@ exports.parseMeasurement = function parseMeasurement(val) {
       const [, numVal, unit] = unitRegEx.exec(val);
       return `${parseFloat(numVal)}${unit}`;
     default:
+      if (varContainedRegEx.test(val)) {
+        return val;
+      }
       if (type === exports.TYPES.NUMBER && parseFloat(val) === 0) {
         return '0px';
       }
@@ -384,7 +397,10 @@ exports.parseImage = function parseImage(val) {
   if (type === exports.TYPES.UNDEFINED) {
     return undefined;
   }
-  var values = splitValue(val, ',');
+  var values = splitValue(val, {
+    delimiter: ',',
+    preserveComment: varContainedRegEx.test(val),
+  });
   var isImage = !!values.length;
   var i;
   for (i = 0; i < values.length; i++) {
diff --git a/package-lock.json b/package-lock.json
index 4567cbd8..ba94145f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
       "version": "4.3.1",
       "license": "MIT",
       "dependencies": {
-        "@asamuzakjp/css-color": "^3.1.5",
+        "@asamuzakjp/css-color": "^3.1.7",
         "rrweb-cssom": "^0.8.0"
       },
       "devDependencies": {
@@ -30,9 +30,9 @@
       }
     },
     "node_modules/@asamuzakjp/css-color": {
-      "version": "3.1.5",
-      "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.5.tgz",
-      "integrity": "sha512-w7AmVyTTiU41fNLsFDf+gA2Dwtbx2EJtn2pbJNAGSRAg50loXy1uLXA3hEpD8+eydcomTurw09tq5/AyceCaGg==",
+      "version": "3.1.7",
+      "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.7.tgz",
+      "integrity": "sha512-Ok5fYhtwdyJQmU1PpEv6Si7Y+A4cYb8yNM9oiIJC9TzXPMuN9fvdonKJqcnz9TbFqV6bQ8z0giRq0iaOpGZV2g==",
       "license": "MIT",
       "dependencies": {
         "@csstools/css-calc": "^2.1.3",
diff --git a/package.json b/package.json
index 18846c34..bf4b3221 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
   ],
   "main": "./lib/CSSStyleDeclaration.js",
   "dependencies": {
-    "@asamuzakjp/css-color": "^3.1.5",
+    "@asamuzakjp/css-color": "^3.1.7",
     "rrweb-cssom": "^0.8.0"
   },
   "devDependencies": {
diff --git a/test/parsers.js b/test/parsers.js
index 05cacef3..8efe1334 100644
--- a/test/parsers.js
+++ b/test/parsers.js
@@ -89,18 +89,18 @@ describe('valueType', () => {
     assert.strictEqual(output, parsers.TYPES.UNIDENT);
   });
 
-  it('returns var for url(var(--foo))', () => {
+  it('returns unidentified for url(var(--foo))', () => {
     let input = 'url(var(--foo))';
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, parsers.TYPES.VAR);
+    assert.strictEqual(output, parsers.TYPES.UNIDENT);
   });
 
-  it('returns var from calc(100px *  var(--foo))', () => {
+  it('returns calc from calc(100px *  var(--foo))', () => {
     let input = 'calc(100px *  var(--foo))';
     let output = parsers.valueType(input);
 
-    assert.strictEqual(output, parsers.TYPES.VAR);
+    assert.strictEqual(output, parsers.TYPES.CALC);
   });
 
   it('returns var from var(--foo)', () => {
@@ -800,6 +800,13 @@ describe('parseColor', () => {
 
     assert.strictEqual(output, 'transparent');
   });
+
+  it('should return value as is with var()', () => {
+    let input = 'rgb(var(--my-var, 0, 0, 0))';
+    let output = parsers.parseColor(input);
+
+    assert.strictEqual(output, 'rgb(var(--my-var, 0, 0, 0))');
+  });
 });
 
 describe('parseAngle', () => {
@@ -887,18 +894,23 @@ describe('parseImage', () => {
 
     assert.strictEqual(
       output,
-      'radial-gradient(transparent, /* comment */ var(--custom-color)), url(example.png)'
+      'radial-gradient(transparent, /* comment */ var(--custom-color)), url("example.png")'
     );
   });
 
-  it('should return value as is if var() is included and even if invalid image type is included', () => {
+  it('should return undefined if invalid image type is included', () => {
     let input = 'radial-gradient(transparent, var(--custom-color)), red';
     let output = parsers.parseImage(input);
 
-    assert.strictEqual(output, 'radial-gradient(transparent, var(--custom-color)), red');
+    assert.strictEqual(output, undefined);
   });
 
-  it.todo('test');
+  it('should return undefined if value is not image type', () => {
+    let input = 'rgb(var(--my-var, 0, 0, 0))';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, undefined);
+  });
 });
 
 describe('dashedToCamelCase', () => {

From ce524fe9d4fd5d76ceb06bc56d48aee5dbb061d5 Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Sat, 3 May 2025 22:32:10 +0900
Subject: [PATCH 07/10] Update parser

* add global values
* scope variables
* fix some functions
---
 lib/parsers.js  | 180 +++++++++++++++++++++---------------------------
 test/parsers.js | 177 ++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 240 insertions(+), 117 deletions(-)

diff --git a/lib/parsers.js b/lib/parsers.js
index 92c72bae..1290cc11 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -4,6 +4,9 @@
  ********************************************************************/
 'use strict';
 
+// FIXME: should move shorthandGetter(), shorthandSetter(), implicitSetter() and
+// subImplicitSetter() to CSSStyleDeclaration()
+
 const { resolve: resolveColor, utils } = require('@asamuzakjp/css-color');
 const { cssCalc, isColor, isGradient, splitValue } = utils;
 
@@ -22,6 +25,9 @@ exports.TYPES = {
   UNIDENT: 0x8000,
 };
 
+// CSS global values
+exports.GLOBAL_VALUES = Object.freeze(['initial', 'inherit', 'unset', 'revert', 'revert-layer']);
+
 // regular expressions
 var DIGIT = '(?:0|[1-9]\\d*)';
 var NUMBER = `[+-]?(?:${DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${DIGIT})?`;
@@ -37,6 +43,7 @@ var calcRegEx =
 
 // This will return one of the above types based on the passed in string
 exports.valueType = function valueType(val) {
+  // see https://webidl.spec.whatwg.org/#LegacyNullToEmptyString
   if (val === '' || val === null) {
     return exports.TYPES.NULL_OR_EMPTY_STR;
   }
@@ -53,7 +60,7 @@ exports.valueType = function valueType(val) {
     return exports.TYPES.CALC;
   }
   if (unitRegEx.test(val)) {
-    const [, , unit] = unitRegEx.exec(val);
+    var [, , unit] = unitRegEx.exec(val);
     if (!unit) {
       return exports.TYPES.NUMBER;
     }
@@ -162,7 +169,7 @@ exports.parseLength = function parseLength(val) {
         format: 'specifiedValue',
       });
     case exports.TYPES.LENGTH: {
-      const [, numVal, unit] = unitRegEx.exec(val);
+      var [, numVal, unit] = unitRegEx.exec(val);
       return `${parseFloat(numVal)}${unit}`;
     }
     default:
@@ -186,9 +193,10 @@ exports.parsePercent = function parsePercent(val) {
       return cssCalc(val, {
         format: 'specifiedValue',
       });
-    case exports.TYPES.PERCENT:
-      const [, numVal, unit] = unitRegEx.exec(val);
+    case exports.TYPES.PERCENT: {
+      var [, numVal, unit] = unitRegEx.exec(val);
       return `${parseFloat(numVal)}${unit}`;
+    }
     default:
       if (varContainedRegEx.test(val)) {
         return val;
@@ -212,9 +220,10 @@ exports.parseMeasurement = function parseMeasurement(val) {
         format: 'specifiedValue',
       });
     case exports.TYPES.LENGTH:
-    case exports.TYPES.PERCENT:
-      const [, numVal, unit] = unitRegEx.exec(val);
+    case exports.TYPES.PERCENT: {
+      var [, numVal, unit] = unitRegEx.exec(val);
       return `${parseFloat(numVal)}${unit}`;
+    }
     default:
       if (varContainedRegEx.test(val)) {
         return val;
@@ -236,7 +245,7 @@ exports.parseInheritingMeasurement = function parseInheritingMeasurement(val) {
 exports.parseUrl = function parseUrl(val) {
   var type = exports.valueType(val);
   if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
-    return val;
+    return '';
   }
   var res = urlRegEx.exec(val);
   // does it match the regex?
@@ -293,10 +302,11 @@ exports.parseUrl = function parseUrl(val) {
   return 'url("' + urlstr + '")';
 };
 
+// NOTE: seems not in use?
 exports.parseString = function parseString(val) {
   var type = exports.valueType(val);
   if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
-    return val;
+    return '';
   }
   if (type !== exports.TYPES.STRING) {
     return undefined;
@@ -320,14 +330,38 @@ exports.parseString = function parseString(val) {
   return val;
 };
 
-exports.parseColor = function parseColor(val) {
+exports.parseKeyword = function parseKeyword(val, validKeywords = []) {
   var type = exports.valueType(val);
-  if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.VAR) {
+  if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
+    return '';
+  }
+  if (type === exports.TYPES.VAR) {
     return val;
   }
+  if (type !== exports.TYPES.KEYWORD) {
+    return undefined;
+  }
+  val = val.toString().toLowerCase();
+  if (validKeywords.includes(val) || exports.GLOBAL_VALUES.includes(val)) {
+    return val;
+  }
+  return undefined;
+};
+
+exports.parseColor = function parseColor(val) {
+  var type = exports.valueType(val);
+  if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
+    return '';
+  }
   if (type === exports.TYPES.UNDEFINED) {
     return undefined;
   }
+  if (type === exports.TYPES.VAR) {
+    return val;
+  }
+  if (type === exports.TYPES.KEYWORD) {
+    return exports.parseKeyword(val);
+  }
   if (/^[a-z]+$/i.test(val) && type === exports.TYPES.COLOR) {
     return val;
   }
@@ -346,7 +380,7 @@ exports.parseColor = function parseColor(val) {
 exports.parseAngle = function parseAngle(val) {
   var type = exports.valueType(val);
   if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
-    return val;
+    return '';
   }
   if (type !== exports.TYPES.ANGLE) {
     return undefined;
@@ -368,34 +402,19 @@ exports.parseAngle = function parseAngle(val) {
   return flt + 'deg';
 };
 
-exports.parseKeyword = function parseKeyword(val, validKeywords) {
+exports.parseImage = function parseImage(val) {
   var type = exports.valueType(val);
   if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
-    return val;
+    return '';
   }
-  if (type !== exports.TYPES.KEYWORD) {
+  if (type === exports.TYPES.UNDEFINED) {
     return undefined;
   }
-  val = val.toString().toLowerCase();
-  var i;
-  for (i = 0; i < validKeywords.length; i++) {
-    if (validKeywords[i].toLowerCase() === val) {
-      return validKeywords[i];
-    }
-  }
-  return undefined;
-};
-
-exports.parseImage = function parseImage(val) {
-  if (/^(?:none|inherit)$/i.test(val)) {
-    return val.toLowerCase();
-  }
-  var type = exports.valueType(val);
-  if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.VAR) {
+  if (type === exports.TYPES.VAR) {
     return val;
   }
-  if (type === exports.TYPES.UNDEFINED) {
-    return undefined;
+  if (type === exports.TYPES.KEYWORD) {
+    return exports.parseKeyword(val, ['none']);
   }
   var values = splitValue(val, {
     delimiter: ',',
@@ -445,56 +464,22 @@ exports.dashedToCamelCase = function (dashed) {
   return camel;
 };
 
-var isSpace = /\s/;
-var openingDeliminators = ['"', "'", '('];
-var closingDeliminators = ['"', "'", ')'];
-// this splits on whitespace, but keeps quoted and parened parts together
-var getParts = function (str) {
-  var deliminatorStack = [];
-  var length = str.length;
-  var i;
-  var parts = [];
-  var currentPart = '';
-  var openingIndex;
-  var closingIndex;
-  for (i = 0; i < length; i++) {
-    openingIndex = openingDeliminators.indexOf(str[i]);
-    closingIndex = closingDeliminators.indexOf(str[i]);
-    if (isSpace.test(str[i])) {
-      if (deliminatorStack.length === 0) {
-        if (currentPart !== '') {
-          parts.push(currentPart);
-        }
-        currentPart = '';
-      } else {
-        currentPart += str[i];
-      }
-    } else {
-      if (str[i] === '\\') {
-        i++;
-        currentPart += str[i];
-      } else {
-        currentPart += str[i];
-        if (closingIndex !== -1 && closingIndex === deliminatorStack[deliminatorStack.length - 1]) {
-          deliminatorStack.pop();
-        } else if (openingIndex !== -1) {
-          deliminatorStack.push(openingIndex);
-        }
-      }
-    }
-  }
-  if (currentPart !== '') {
-    parts.push(currentPart);
+exports.camelToDashed = function (camelCase) {
+  var dashed = camelCase.replace(/(?<=[a-z])[A-Z]/g, '-$&').toLowerCase();
+  var vendorPrefixes = ['o', 'moz', 'ms', 'webkit'];
+  var match = dashed.match(/^([a-z]+)\-/);
+  if (match && vendorPrefixes.includes(match[1])) {
+    dashed = '-' + dashed;
   }
-  return parts;
+  return dashed;
 };
 
-/*
- * this either returns undefined meaning that it isn't valid
- * or returns an object where the keys are dashed short
- * hand properties and the values are the values to set
- * on them
- */
+// this either returns undefined meaning that it isn't valid
+// or returns an object where the keys are dashed short
+// hand properties and the values are the values to set
+// on them
+// FIXME: need additional argument which indicates syntax
+// and/or use Map() for shorthandFor to ensure order of the longhand properties
 exports.shorthandParser = function parse(v, shorthandFor) {
   var obj = {};
   var type = exports.valueType(v);
@@ -504,19 +489,19 @@ exports.shorthandParser = function parse(v, shorthandFor) {
     });
     return obj;
   }
-
+  if (type === exports.TYPES.UNDEFINED) {
+    return undefined;
+  }
   if (typeof v === 'number') {
     v = v.toString();
   }
-
   if (typeof v !== 'string') {
     return undefined;
   }
-
   if (v.toLowerCase() === 'inherit') {
     return {};
   }
-  var parts = getParts(v);
+  var parts = splitValue(v);
   var valid = true;
   parts.forEach(function (part, i) {
     var partValid = false;
@@ -526,7 +511,9 @@ exports.shorthandParser = function parse(v, shorthandFor) {
         obj[property] = part;
       }
     });
-    valid = valid && partValid;
+    if (valid) {
+      valid = partValid;
+    }
   });
   if (!valid) {
     return undefined;
@@ -534,13 +521,19 @@ exports.shorthandParser = function parse(v, shorthandFor) {
   return obj;
 };
 
+// FIXME: check against shorthandParser and reduce Object.keys().forEach() loops
 exports.shorthandSetter = function (property, shorthandFor) {
   return function (v) {
+    if (v === undefined) {
+      return;
+    }
+    if (v === null) {
+      v = '';
+    }
     var obj = exports.shorthandParser(v, shorthandFor);
     if (obj === undefined) {
       return;
     }
-    //console.log('shorthandSetter for:', property, 'obj:', obj);
     Object.keys(obj).forEach(function (subprop) {
       // in case subprop is an implicit property, this will clear
       // *its* subpropertiesX
@@ -611,7 +604,7 @@ exports.implicitSetter = function (propertyBefore, propertyAfter, isValid, parse
     if (v.toLowerCase() === 'inherit' || v === '') {
       parts = [v];
     } else {
-      parts = getParts(v);
+      parts = splitValue(v);
     }
     if (parts.length < 1 || parts.length > 4) {
       return undefined;
@@ -646,12 +639,10 @@ exports.implicitSetter = function (propertyBefore, propertyAfter, isValid, parse
   };
 };
 
-//
 //  Companion to implicitSetter, but for the individual parts.
 //  This sets the individual value, and checks to see if all four
 //  sub-parts are set.  If so, it sets the shorthand version and removes
 //  the individual parts from the cssText.
-//
 exports.subImplicitSetter = function (prefix, part, isValid, parser) {
   var property = prefix + '-' + part;
   var subparts = [prefix + '-top', prefix + '-right', prefix + '-bottom', prefix + '-left'];
@@ -697,16 +688,3 @@ exports.subImplicitSetter = function (prefix, part, isValid, parser) {
     return v;
   };
 };
-
-var camelToDashed = /[A-Z]/g;
-var firstSegment = /^\([^-]\)-/;
-var vendorPrefixes = ['o', 'moz', 'ms', 'webkit'];
-exports.camelToDashed = function (camelCase) {
-  var match;
-  var dashed = camelCase.replace(camelToDashed, '-$&').toLowerCase();
-  match = dashed.match(firstSegment);
-  if (match && vendorPrefixes.indexOf(match[1]) !== -1) {
-    dashed = '-' + dashed;
-  }
-  return dashed;
-};
diff --git a/test/parsers.js b/test/parsers.js
index 8efe1334..7a061e82 100644
--- a/test/parsers.js
+++ b/test/parsers.js
@@ -542,11 +542,11 @@ describe('parseInheritingMeasurement', () => {
 });
 
 describe('parseUrl', () => {
-  it('should return null', () => {
+  it('should return empty string', () => {
     let input = null;
     let output = parsers.parseUrl(input);
 
-    assert.strictEqual(output, null);
+    assert.strictEqual(output, '');
   });
 
   it('should return empty string', () => {
@@ -696,11 +696,11 @@ describe('parseString', () => {
 });
 
 describe('parseColor', () => {
-  it('should return null', () => {
+  it('should return empty string', () => {
     let input = null;
     let output = parsers.parseColor(input);
 
-    assert.strictEqual(output, null);
+    assert.strictEqual(output, '');
   });
 
   it('should return empty string', () => {
@@ -717,6 +717,13 @@ describe('parseColor', () => {
     assert.strictEqual(output, undefined);
   });
 
+  it('should return inherit', () => {
+    let input = 'inherit';
+    let output = parsers.parseColor(input);
+
+    assert.strictEqual(output, 'inherit');
+  });
+
   it('should convert hsl to rgb values', () => {
     let input = 'hsla(0, 1%, 2%)';
     let output = parsers.parseColor(input);
@@ -814,24 +821,36 @@ describe('parseAngle', () => {
 });
 
 describe('parseKeyword', () => {
-  it.todo('test');
-});
+  it('should return value', () => {
+    let input = 'inherit';
+    let output = parsers.parseKeyword(input);
+
+    assert.strictEqual(output, 'inherit');
+  });
 
-describe('parseImage', () => {
   it('should return value', () => {
-    let input = 'none';
-    let output = parsers.parseImage(input);
+    let input = 'foo';
+    let output = parsers.parseKeyword(input, ['foo', 'bar']);
 
-    assert.strictEqual(output, 'none');
+    assert.strictEqual(output, 'foo');
   });
 
   it('should return value', () => {
-    let input = 'inherit';
-    let output = parsers.parseImage(input);
+    let input = 'Bar';
+    let output = parsers.parseKeyword(input, ['foo', 'bar']);
 
-    assert.strictEqual(output, 'inherit');
+    assert.strictEqual(output, 'bar');
   });
 
+  it('should return undefined', () => {
+    let input = 'baz';
+    let output = parsers.parseKeyword(input, ['foo', 'bar']);
+
+    assert.strictEqual(output, undefined);
+  });
+});
+
+describe('parseImage', () => {
   it('should return empty string', () => {
     let input = '';
     let output = parsers.parseImage(input);
@@ -839,11 +858,11 @@ describe('parseImage', () => {
     assert.strictEqual(output, '');
   });
 
-  it('should return null', () => {
+  it('should return empty string', () => {
     let input = null;
     let output = parsers.parseImage(input);
 
-    assert.strictEqual(output, null);
+    assert.strictEqual(output, '');
   });
 
   it('should return undefined', () => {
@@ -853,6 +872,20 @@ describe('parseImage', () => {
     assert.strictEqual(output, undefined);
   });
 
+  it('should return none', () => {
+    let input = 'none';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, 'none');
+  });
+
+  it('should return inherit', () => {
+    let input = 'inherit';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, 'inherit');
+  });
+
   it('should return undefined for negative radii', () => {
     let input = 'radial-gradient(circle -10px at center, red, blue)';
     let output = parsers.parseImage(input);
@@ -944,8 +977,93 @@ describe('dashedToCamelCase', () => {
 });
 
 describe('shorthandParser', () => {
+  const flexGrow = require('../lib/properties/flexGrow');
+  const flexShrink = require('../lib/properties/flexShrink');
+  const flexBasis = require('../lib/properties/flexBasis');
+  const shorthandFor = {
+    'flex-grow': flexGrow,
+    'flex-shrink': flexShrink,
+    'flex-basis': flexBasis,
+  };
+
+  it('should return undefined for keyword', () => {
+    let input = 'none';
+    let output = parsers.shorthandParser(input, shorthandFor);
+
+    assert.strictEqual(output, undefined);
+  });
+
+  it('should return object', () => {
+    let input = '0 0 auto';
+    let output = parsers.shorthandParser(input, shorthandFor);
+
+    assert.deepEqual(output, {
+      'flex-grow': '0',
+      'flex-shrink': '0',
+      'flex-basis': 'auto',
+    });
+  });
+
+  it('should return object', () => {
+    let input = '0 1 auto';
+    let output = parsers.shorthandParser(input, shorthandFor);
+
+    assert.deepEqual(output, {
+      'flex-grow': '0',
+      'flex-shrink': '1',
+      'flex-basis': 'auto',
+    });
+  });
+
+  it('should return object', () => {
+    let input = '2';
+    let output = parsers.shorthandParser(input, shorthandFor);
+
+    assert.deepEqual(output, {
+      'flex-grow': '2',
+    });
+  });
+
+  it('should return object', () => {
+    let input = '2 1';
+    let output = parsers.shorthandParser(input, shorthandFor);
+
+    assert.deepEqual(output, {
+      'flex-grow': '2',
+      'flex-shrink': '1',
+    });
+  });
+
+  it('should return object', () => {
+    let input = '10px';
+    let output = parsers.shorthandParser(input, shorthandFor);
+
+    assert.deepEqual(output, {
+      'flex-basis': '10px',
+    });
+  });
+
+  it('should return object', () => {
+    let input = '2 10px';
+    let output = parsers.shorthandParser(input, shorthandFor);
+
+    assert.deepEqual(output, {
+      'flex-grow': '2',
+      'flex-basis': '10px',
+    });
+  });
+
+  // FIXME:
+  it.skip('should return undefined', () => {
+    let input = '2 10px 20px';
+    let output = parsers.shorthandParser(input, shorthandFor);
+
+    assert.deepEqual(output, undefined);
+  });
+
   it.todo('test');
 });
+
 describe('shorthandSetter', () => {
   it.todo('test');
 });
@@ -958,6 +1076,33 @@ describe('implicitSetter', () => {
 describe('subImplicitSetter', () => {
   it.todo('test');
 });
+
 describe('camelToDashed', () => {
-  it.todo('test');
+  it('should return dashed value', () => {
+    let input = 'fooBarBaz';
+    let output = parsers.camelToDashed(input);
+
+    assert.strictEqual(output, 'foo-bar-baz');
+  });
+
+  it('should return dashed value', () => {
+    let input = 'FooBarBaz';
+    let output = parsers.camelToDashed(input);
+
+    assert.strictEqual(output, 'foo-bar-baz');
+  });
+
+  it('should return dashed value', () => {
+    let input = 'webkitFooBar';
+    let output = parsers.camelToDashed(input);
+
+    assert.strictEqual(output, '-webkit-foo-bar');
+  });
+
+  it('should return dashed value', () => {
+    let input = 'WebkitFooBar';
+    let output = parsers.camelToDashed(input);
+
+    assert.strictEqual(output, '-webkit-foo-bar');
+  });
 });

From 2ace3dd845fdaf2b8a19b26985d494357082aaa1 Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Sun, 4 May 2025 08:59:33 +0900
Subject: [PATCH 08/10] Remove vendor prefixes other than `webkit`

Ref https://github.com/jsdom/cssstyle/pull/112#issuecomment-577815667
---
 lib/parsers.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/lib/parsers.js b/lib/parsers.js
index 1290cc11..26d2616f 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -450,7 +450,7 @@ exports.dashedToCamelCase = function (dashed) {
     return dashed;
   }
   // skip leading hyphen in vendor prefixed value, e.g. -webkit-foo
-  var i = /^\-[a-z]/.test(dashed) ? 1 : 0;
+  var i = /^\-webkit/.test(dashed) ? 1 : 0;
   var camel = '';
   var nextCap = false;
   for (; i < dashed.length; i++) {
@@ -466,9 +466,8 @@ exports.dashedToCamelCase = function (dashed) {
 
 exports.camelToDashed = function (camelCase) {
   var dashed = camelCase.replace(/(?<=[a-z])[A-Z]/g, '-$&').toLowerCase();
-  var vendorPrefixes = ['o', 'moz', 'ms', 'webkit'];
-  var match = dashed.match(/^([a-z]+)\-/);
-  if (match && vendorPrefixes.includes(match[1])) {
+  var match = dashed.match(/^webkit\-/);
+  if (match) {
     dashed = '-' + dashed;
   }
   return dashed;

From 0179ca9ea7d9fcba7ed2a3ef57103f7f92da2c29 Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Mon, 5 May 2025 17:47:15 +0900
Subject: [PATCH 09/10] Add note

---
 lib/parsers.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/parsers.js b/lib/parsers.js
index 26d2616f..fd409c55 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -478,7 +478,8 @@ exports.camelToDashed = function (camelCase) {
 // hand properties and the values are the values to set
 // on them
 // FIXME: need additional argument which indicates syntax
-// and/or use Map() for shorthandFor to ensure order of the longhand properties
+// and/or use Map() for shorthandFor to ensure order of the longhand properties.
+// Note that there is `constants.js` that is presumably for this purpose?
 exports.shorthandParser = function parse(v, shorthandFor) {
   var obj = {};
   var type = exports.valueType(v);

From 3b13cdd63f21396394f695805027083f50a097f4 Mon Sep 17 00:00:00 2001
From: "asamuzaK (Kazz)" <kazz@asamuzak.jp>
Date: Tue, 6 May 2025 10:13:44 +0900
Subject: [PATCH 10/10] Import GLOBAL_VALUES from constants.js

---
 lib/constants.js | 10 ++++++++++
 lib/parsers.js   |  7 +++----
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/lib/constants.js b/lib/constants.js
index 1b588695..8f7f6c85 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -4,3 +4,13 @@ module.exports.POSITION_AT_SHORTHAND = {
   first: 0,
   second: 1,
 };
+
+// CSS global values
+// see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords
+module.exports.GLOBAL_VALUES = Object.freeze([
+  'initial',
+  'inherit',
+  'unset',
+  'revert',
+  'revert-layer',
+]);
diff --git a/lib/parsers.js b/lib/parsers.js
index fd409c55..6823ccc3 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -8,6 +8,8 @@
 // subImplicitSetter() to CSSStyleDeclaration()
 
 const { resolve: resolveColor, utils } = require('@asamuzakjp/css-color');
+const { GLOBAL_VALUES } = require('./constants');
+
 const { cssCalc, isColor, isGradient, splitValue } = utils;
 
 exports.TYPES = {
@@ -25,9 +27,6 @@ exports.TYPES = {
   UNIDENT: 0x8000,
 };
 
-// CSS global values
-exports.GLOBAL_VALUES = Object.freeze(['initial', 'inherit', 'unset', 'revert', 'revert-layer']);
-
 // regular expressions
 var DIGIT = '(?:0|[1-9]\\d*)';
 var NUMBER = `[+-]?(?:${DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${DIGIT})?`;
@@ -342,7 +341,7 @@ exports.parseKeyword = function parseKeyword(val, validKeywords = []) {
     return undefined;
   }
   val = val.toString().toLowerCase();
-  if (validKeywords.includes(val) || exports.GLOBAL_VALUES.includes(val)) {
+  if (validKeywords.includes(val) || GLOBAL_VALUES.includes(val)) {
     return val;
   }
   return undefined;