From b2ebbb90c30cd806f7c4026993f527b6da945b23 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" 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 bcf4e7c..4bca8d8 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 9fb9b19..edd89a4 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 6cf0c59..4ece5d8 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 060ff02..7e598e4 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 a359bb6..b25f041 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 fc6f031..0aa23e5 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 a82900b..a76d95f 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 dc40f2a..dd12f36 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 05b41e2..0b24ae5 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 de16abd..0fcca83 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)" 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 4bca8d8..269020a 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 dd12f36..4567cbd 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 0b24ae5..18846c3 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 0fcca83..bd939d7 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)" 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 269020a..f5f3c3e 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)" 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 f5f3c3e..e0be48b 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 edd89a4..9fb9b19 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 4ece5d8..6cf0c59 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 7e598e4..060ff02 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 b25f041..a359bb6 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 0aa23e5..339d65b 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 a76d95f..792f65f 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)" 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 e0be48b..d72792c 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 bd939d7..05cacef 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)" 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 d72792c..92c72ba 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 4567cbd..ba94145 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 18846c3..bf4b322 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 05cacef..8efe133 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)" 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 92c72ba..1290cc1 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 8efe133..7a061e8 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)" 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 1290cc1..26d2616 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)" 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 26d2616..fd409c5 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)" 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 1b58869..8f7f6c8 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 fd409c5..6823ccc 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;