Skip to content

Commit da680de

Browse files
committed
Update parsers.js
1 parent d53baf3 commit da680de

File tree

2 files changed

+70
-76
lines changed

2 files changed

+70
-76
lines changed

lib/parsers.js

Lines changed: 65 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ const {
77
const { next: syntaxes } = require("@csstools/css-syntax-patches-for-csstree");
88
const csstree = require("css-tree");
99
const { getCache, setCache } = require("./utils/cache");
10+
const {
11+
CALC_FUNC_NAMES,
12+
DEFAULT_OPTS,
13+
GLOBAL_KEY,
14+
NODE_TYPES,
15+
SYS_COLOR
16+
} = require("./utils/constants");
1017
const { asciiLowercase } = require("./utils/strings");
11-
const { CALC_FUNC_NAMES, GLOBAL_KEY, NODE_TYPES, SYS_COLOR } = require("./utils/constants");
1218

1319
// Regular expressions
1420
const calcRegEx = new RegExp(`^${CALC_FUNC_NAMES}\\(`);
@@ -60,29 +66,23 @@ const prepareValue = (value, globalObject = globalThis) => {
6066
* @param {string} val - The value to check.
6167
* @returns {boolean} - True if the value is a global keyword, false otherwise.
6268
*/
63-
const isGlobalKeyword = (val) => {
64-
return GLOBAL_KEY.includes(asciiLowercase(val));
65-
};
69+
const isGlobalKeyword = (val) => GLOBAL_KEY.includes(asciiLowercase(val));
6670

6771
/**
6872
* Check if the value starts with and/or contains CSS var() function.
6973
*
7074
* @param {string} val - The value to check.
7175
* @returns {boolean} - True if the value contains var(), false otherwise.
7276
*/
73-
const hasVarFunc = (val) => {
74-
return varRegEx.test(val) || varContainedRegEx.test(val);
75-
};
77+
const hasVarFunc = (val) => varRegEx.test(val) || varContainedRegEx.test(val);
7678

7779
/**
7880
* Check if the value starts with and/or contains CSS calc() related functions.
7981
*
8082
* @param {string} val - The value to check.
8183
* @returns {boolean} - True if the value contains calc() related functions, false otherwise.
8284
*/
83-
const hasCalcFunc = (val) => {
84-
return calcRegEx.test(val) || calcContainedRegEx.test(val);
85-
};
85+
const hasCalcFunc = (val) => calcRegEx.test(val) || calcContainedRegEx.test(val);
8686

8787
/**
8888
* Parse CSS to AST or plain object.
@@ -129,36 +129,28 @@ const isValidPropertyValue = (prop, val, globalObject) => {
129129
}
130130
return false;
131131
}
132-
133-
// Use cache for expensive parsing
134132
const cacheKey = `isValidPropertyValue_${prop}_${val}`;
135133
const cachedValue = getCache(cacheKey);
136134
if (typeof cachedValue === "boolean") {
137135
return cachedValue;
138136
}
139-
140-
let ast;
137+
let result;
141138
try {
142-
ast = parseCSS(val, {
139+
const ast = parseCSS(val, {
143140
globalObject,
144141
options: {
145142
context: "value"
146143
}
147144
});
145+
const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
146+
result = error === null && matched !== null;
148147
} catch {
149-
setCache(cacheKey, false);
150-
return false;
148+
result = false;
151149
}
152-
const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
153-
const result = error === null && matched !== null;
154150
setCache(cacheKey, result);
155151
return result;
156152
};
157153

158-
const defaultOptions = {
159-
format: "specifiedValue"
160-
};
161-
162154
/**
163155
* Process numeric value.
164156
*
@@ -169,24 +161,20 @@ const defaultOptions = {
169161
*/
170162
const processNumericValue = (item, allowedTypes, opt = {}) => {
171163
const { type, value } = item ?? {};
172-
173164
// Allow "Number" with value "0" if "Dimension" or "Percentage" is allowed.
174165
const isZero = type === NODE_TYPES.NUMBER && parseFloat(value) === 0;
175166
const isValidType =
176167
allowedTypes.includes(type) ||
177168
(isZero &&
178169
(allowedTypes.includes(NODE_TYPES.DIMENSION) ||
179170
allowedTypes.includes(NODE_TYPES.PERCENTAGE)));
180-
181171
if (!isValidType) {
182172
return;
183173
}
184-
185174
const { clamp } = opt;
186175
const max = opt.max ?? Number.INFINITY;
187176
const min = opt.min ?? Number.NEGATIVE_INFINITY;
188177
let num = parseFloat(value);
189-
190178
if (clamp) {
191179
if (num > max) {
192180
num = max;
@@ -196,7 +184,6 @@ const processNumericValue = (item, allowedTypes, opt = {}) => {
196184
} else if (num > max || num < min) {
197185
return;
198186
}
199-
200187
return num;
201188
};
202189

@@ -215,14 +202,11 @@ const resolveCalc = (val, opt = {}) => {
215202
if (val === "" || hasVarFunc(val) || !hasCalcFunc(val)) {
216203
return val;
217204
}
218-
219-
// Use cache for expensive calculation
220205
const cacheKey = `resolveCalc_${val}`;
221206
const cachedValue = getCache(cacheKey);
222207
if (typeof cachedValue === "string") {
223208
return cachedValue;
224209
}
225-
226210
const obj = parseCSS(
227211
val,
228212
{
@@ -247,7 +231,7 @@ const resolveCalc = (val, opt = {}) => {
247231
.replace(/,(?!\s)/g, ", ")
248232
.trim();
249233
if (calcNameRegEx.test(itemName)) {
250-
const newValue = cssCalc(value, options ?? defaultOptions);
234+
const newValue = cssCalc(value, options ?? DEFAULT_OPTS);
251235
values.push(newValue);
252236
} else {
253237
values.push(value);
@@ -274,16 +258,13 @@ const resolveCalc = (val, opt = {}) => {
274258
const createCalcNode = (children, value, raw) => {
275259
let isNumber = false;
276260
let nodeValue = value;
277-
278-
// calc(number) の場合のみ特別扱いして数値としてパースする
279261
if (children.length === 1) {
280262
const [child] = children;
281263
if (child.type === NODE_TYPES.NUMBER) {
282264
isNumber = true;
283265
nodeValue = `${parseFloat(child.value)}`;
284266
}
285267
}
286-
287268
return {
288269
type: "Calc",
289270
name: "calc",
@@ -375,62 +356,71 @@ const parsePropertyValue = (prop, val, opt = {}) => {
375356
}
376357
val = calculatedValue;
377358
}
359+
const cacheKey = `parsePropertyValue_${prop}_${val}_${caseSensitive}`;
360+
const cachedValue = getCache(cacheKey);
361+
if (cachedValue === false) {
362+
return;
363+
} else if (inArray) {
364+
if (Array.isArray(cachedValue)) {
365+
return cachedValue;
366+
}
367+
} else if (typeof cachedValue === "string") {
368+
return cachedValue;
369+
}
370+
let parsedValue;
378371
const lowerCasedValue = asciiLowercase(val);
379372
if (GLOBAL_KEY.includes(lowerCasedValue)) {
380373
if (inArray) {
381-
return [
374+
parsedValue = [
382375
{
383376
type: "GlobalKeyword",
384377
name: lowerCasedValue
385378
}
386379
];
380+
} else {
381+
parsedValue = lowerCasedValue;
387382
}
388-
return lowerCasedValue;
389383
} else if (SYS_COLOR.includes(lowerCasedValue)) {
390384
if (/^(?:(?:-webkit-)?(?:[a-z][a-z\d]*-)*color|border)$/i.test(prop)) {
391385
if (inArray) {
392-
return [
386+
parsedValue = [
393387
{
394388
type: NODE_TYPES.IDENTIFIER,
395389
name: lowerCasedValue
396390
}
397391
];
392+
} else {
393+
parsedValue = lowerCasedValue;
398394
}
399-
return lowerCasedValue;
400-
}
401-
return;
402-
}
403-
try {
404-
let cacheKey = "";
405-
if (inArray) {
406-
cacheKey = `parsePropertyValue_${prop}_${val}_${caseSensitive}`;
407-
const cachedValues = getCache(cacheKey);
408-
if (Array.isArray(cachedValues)) {
409-
return cachedValues;
410-
}
411-
}
412-
const ast = parseCSS(val, {
413-
globalObject,
414-
options: {
415-
context: "value"
416-
}
417-
});
418-
const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
419-
if (error || !matched) {
420-
return;
395+
} else {
396+
parsedValue = false;
421397
}
422-
if (inArray) {
423-
const obj = cssTree.toPlainObject(ast);
424-
const parsedValues = normalizeAstNodes(obj.children, val, caseSensitive);
425-
if (cacheKey) {
426-
setCache(cacheKey, parsedValues);
398+
} else {
399+
try {
400+
const ast = parseCSS(val, {
401+
globalObject,
402+
options: {
403+
context: "value"
404+
}
405+
});
406+
const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
407+
if (error || !matched) {
408+
parsedValue = false;
409+
} else if (inArray) {
410+
const obj = cssTree.toPlainObject(ast);
411+
parsedValue = normalizeAstNodes(obj.children, val, caseSensitive);
412+
} else {
413+
parsedValue = val;
427414
}
428-
return parsedValues;
415+
} catch {
416+
parsedValue = false;
429417
}
430-
} catch {
418+
}
419+
setCache(cacheKey, parsedValue);
420+
if (parsedValue === false) {
431421
return;
432422
}
433-
return val;
423+
return parsedValue;
434424
};
435425

436426
/**
@@ -514,13 +504,12 @@ const parseLengthPercentage = (val, opt = {}) => {
514504
* Parse <angle>.
515505
*
516506
* @param {Array} val - The parsed AST children.
507+
* @param {object} [opt={}] - The options for parsing.
517508
* @returns {string|undefined} - The parsed angle string, or undefined if parsing failed.
518509
*/
519-
const parseAngle = (val) => {
510+
const parseAngle = (val, opt = {}) => {
520511
const [item] = val;
521-
// NOTE: parseAngle signature in source doesn't accept opt, but implementation
522-
// should ideally be consistent. For now, matching existing logic via helper without opts.
523-
const num = processNumericValue(item, [NODE_TYPES.DIMENSION]);
512+
const num = processNumericValue(item, [NODE_TYPES.DIMENSION], opt);
524513
if (typeof num !== "number") {
525514
return;
526515
}
@@ -571,10 +560,10 @@ const parseString = (val) => {
571560
* Parse <color>.
572561
*
573562
* @param {Array} val - The parsed AST children.
574-
* @param {object} [opt=defaultOptions] - The options for parsing.
563+
* @param {object} [opt=DEFAULT_OPTS] - The options for parsing.
575564
* @returns {string|undefined} - The parsed color string, or undefined if parsing failed.
576565
*/
577-
const parseColor = (val, opt = defaultOptions) => {
566+
const parseColor = (val, opt = DEFAULT_OPTS) => {
578567
const [item] = val;
579568
const { name, type, value } = item ?? {};
580569
switch (type) {
@@ -610,10 +599,10 @@ const parseColor = (val, opt = defaultOptions) => {
610599
* Parse <gradient>.
611600
*
612601
* @param {Array} val - The parsed AST children.
613-
* @param {object} [opt=defaultOptions] - The options for parsing.
602+
* @param {object} [opt=DEFAULT_OPTS] - The options for parsing.
614603
* @returns {string|undefined} - The parsed gradient string, or undefined if parsing failed.
615604
*/
616-
const parseGradient = (val, opt = defaultOptions) => {
605+
const parseGradient = (val, opt = DEFAULT_OPTS) => {
617606
const [item] = val;
618607
const { name, type, value } = item ?? {};
619608
if (type !== NODE_TYPES.FUNCTION) {

lib/utils/constants.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ exports.CALC_FUNC_NAMES =
77
// Node.ELEMENT_NODE
88
exports.ELEMENT_NODE = 1;
99

10+
// Default options
11+
exports.DEFAULT_OPTS = Object.freeze({
12+
format: "specifiedValue"
13+
});
14+
1015
// CSS global keywords
1116
// @see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords
1217
exports.GLOBAL_KEY = Object.freeze(["initial", "inherit", "unset", "revert", "revert-layer"]);

0 commit comments

Comments
 (0)