parsers.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. "use strict";
  2. const {
  3. resolve: resolveColor,
  4. utils: { cssCalc, resolveGradient, splitValue }
  5. } = require("@asamuzakjp/css-color");
  6. const { next: syntaxes } = require("@csstools/css-syntax-patches-for-csstree");
  7. const csstree = require("css-tree");
  8. const { LRUCache } = require("lru-cache");
  9. const { asciiLowercase } = require("./utils/strings");
  10. // CSS global keywords
  11. // @see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords
  12. const GLOBAL_KEYS = new Set(["initial", "inherit", "unset", "revert", "revert-layer"]);
  13. // System colors
  14. // @see https://drafts.csswg.org/css-color/#css-system-colors
  15. // @see https://drafts.csswg.org/css-color/#deprecated-system-colors
  16. const SYS_COLORS = new Set([
  17. "accentcolor",
  18. "accentcolortext",
  19. "activeborder",
  20. "activecaption",
  21. "activetext",
  22. "appworkspace",
  23. "background",
  24. "buttonborder",
  25. "buttonface",
  26. "buttonhighlight",
  27. "buttonshadow",
  28. "buttontext",
  29. "canvas",
  30. "canvastext",
  31. "captiontext",
  32. "field",
  33. "fieldtext",
  34. "graytext",
  35. "highlight",
  36. "highlighttext",
  37. "inactiveborder",
  38. "inactivecaption",
  39. "inactivecaptiontext",
  40. "infobackground",
  41. "infotext",
  42. "linktext",
  43. "mark",
  44. "marktext",
  45. "menu",
  46. "menutext",
  47. "scrollbar",
  48. "selecteditem",
  49. "selecteditemtext",
  50. "threeddarkshadow",
  51. "threedface",
  52. "threedhighlight",
  53. "threedlightshadow",
  54. "threedshadow",
  55. "visitedtext",
  56. "window",
  57. "windowframe",
  58. "windowtext"
  59. ]);
  60. // AST node types
  61. const AST_TYPES = Object.freeze({
  62. CALC: "Calc",
  63. DIMENSION: "Dimension",
  64. FUNCTION: "Function",
  65. GLOBAL_KEYWORD: "GlobalKeyword",
  66. HASH: "Hash",
  67. IDENTIFIER: "Identifier",
  68. NUMBER: "Number",
  69. PERCENTAGE: "Percentage",
  70. STRING: "String",
  71. URL: "Url"
  72. });
  73. // Regular expressions
  74. const CALC_FUNC_NAMES =
  75. "(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)";
  76. const calcRegEx = new RegExp(`^${CALC_FUNC_NAMES}\\(`);
  77. const calcContainedRegEx = new RegExp(`(?<=[*/\\s(])${CALC_FUNC_NAMES}\\(`);
  78. const calcNameRegEx = new RegExp(`^${CALC_FUNC_NAMES}$`);
  79. const varRegEx = /^var\(/;
  80. const varContainedRegEx = /(?<=[*/\s(])var\(/;
  81. // Patched css-tree
  82. const cssTree = csstree.fork(syntaxes);
  83. // Instance of the LRU Cache. Stores up to 4096 items.
  84. const lruCache = new LRUCache({
  85. max: 4096
  86. });
  87. /**
  88. * Prepares a stringified value.
  89. *
  90. * @param {string|number|null|undefined} value - The value to prepare.
  91. * @returns {string} The prepared value.
  92. */
  93. const prepareValue = (value) => {
  94. // `null` is converted to an empty string.
  95. // @see https://webidl.spec.whatwg.org/#LegacyNullToEmptyString
  96. if (value === null) {
  97. return "";
  98. }
  99. return `${value}`.trim();
  100. };
  101. /**
  102. * Checks if the value is a global keyword.
  103. *
  104. * @param {string} val - The value to check.
  105. * @returns {boolean} True if the value is a global keyword, false otherwise.
  106. */
  107. const isGlobalKeyword = (val) => {
  108. return GLOBAL_KEYS.has(asciiLowercase(val));
  109. };
  110. /**
  111. * Checks if the value starts with or contains a CSS var() function.
  112. *
  113. * @param {string} val - The value to check.
  114. * @returns {boolean} True if the value contains a var() function, false otherwise.
  115. */
  116. const hasVarFunc = (val) => {
  117. return varRegEx.test(val) || varContainedRegEx.test(val);
  118. };
  119. /**
  120. * Checks if the value starts with or contains CSS calc() or math functions.
  121. *
  122. * @param {string} val - The value to check.
  123. * @returns {boolean} True if the value contains calc() or math functions, false otherwise.
  124. */
  125. const hasCalcFunc = (val) => {
  126. return calcRegEx.test(val) || calcContainedRegEx.test(val);
  127. };
  128. /**
  129. * Parses a CSS string into an AST.
  130. *
  131. * @param {string} val - The CSS string to parse.
  132. * @param {object} opt - The options for parsing.
  133. * @param {boolean} [toObject=false] - Whether to return a plain object.
  134. * @returns {object} The AST or a plain object.
  135. */
  136. const parseCSS = (val, opt, toObject = false) => {
  137. val = prepareValue(val);
  138. const ast = cssTree.parse(val, opt);
  139. if (toObject) {
  140. return cssTree.toPlainObject(ast);
  141. }
  142. return ast;
  143. };
  144. /**
  145. * Checks if the value is a valid property value.
  146. * Returns false for custom properties or values containing var().
  147. *
  148. * @param {string} prop - The property name.
  149. * @param {string} val - The property value.
  150. * @returns {boolean} True if the value is valid, false otherwise.
  151. */
  152. const isValidPropertyValue = (prop, val) => {
  153. val = prepareValue(val);
  154. if (val === "") {
  155. return true;
  156. }
  157. // cssTree.lexer does not support deprecated system colors
  158. // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261
  159. // @see https://github.com/w3c/webref/issues/1647
  160. if (SYS_COLORS.has(asciiLowercase(val))) {
  161. if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) {
  162. return true;
  163. }
  164. return false;
  165. }
  166. const cacheKey = `isValidPropertyValue_${prop}_${val}`;
  167. const cachedValue = lruCache.get(cacheKey);
  168. if (typeof cachedValue === "boolean") {
  169. return cachedValue;
  170. }
  171. let result;
  172. try {
  173. const ast = parseCSS(val, {
  174. context: "value"
  175. });
  176. const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
  177. result = error === null && matched !== null;
  178. } catch {
  179. result = false;
  180. }
  181. lruCache.set(cacheKey, result);
  182. return result;
  183. };
  184. /**
  185. * Resolves CSS math functions.
  186. *
  187. * @param {string} val - The value to resolve.
  188. * @param {object} [opt={ format: "specifiedValue" }] - The options for resolving.
  189. * @returns {string|undefined} The resolved value.
  190. */
  191. const resolveCalc = (val, opt = { format: "specifiedValue" }) => {
  192. val = prepareValue(val);
  193. if (val === "" || hasVarFunc(val) || !hasCalcFunc(val)) {
  194. return val;
  195. }
  196. const cacheKey = `resolveCalc_${val}`;
  197. const cachedValue = lruCache.get(cacheKey);
  198. if (typeof cachedValue === "string") {
  199. return cachedValue;
  200. }
  201. const obj = parseCSS(val, { context: "value" }, true);
  202. if (!obj?.children) {
  203. return;
  204. }
  205. const { children: items } = obj;
  206. const values = [];
  207. for (const item of items) {
  208. const { type: itemType, name: itemName, value: itemValue } = item;
  209. if (itemType === AST_TYPES.FUNCTION) {
  210. const value = cssTree
  211. .generate(item)
  212. .replace(/\)(?!\)|\s|,)/g, ") ")
  213. .trim();
  214. if (calcNameRegEx.test(itemName)) {
  215. const newValue = cssCalc(value, opt);
  216. values.push(newValue);
  217. } else {
  218. values.push(value);
  219. }
  220. } else if (itemType === AST_TYPES.STRING) {
  221. values.push(`"${itemValue}"`);
  222. } else {
  223. values.push(itemName ?? itemValue);
  224. }
  225. }
  226. const resolvedValue = values.join(" ");
  227. lruCache.set(cacheKey, resolvedValue);
  228. return resolvedValue;
  229. };
  230. /**
  231. * Parses a property value.
  232. * Returns a string or an array of parsed objects.
  233. *
  234. * @param {string} prop - The property name.
  235. * @param {string} val - The property value.
  236. * @param {object} [opt={}] - The options for parsing.
  237. * @returns {string|Array<object>|undefined} The parsed value.
  238. */
  239. const parsePropertyValue = (prop, val, opt = {}) => {
  240. const { caseSensitive, inArray } = opt;
  241. val = prepareValue(val);
  242. if (val === "" || hasVarFunc(val)) {
  243. return val;
  244. } else if (hasCalcFunc(val)) {
  245. const calculatedValue = resolveCalc(val, {
  246. format: "specifiedValue"
  247. });
  248. if (typeof calculatedValue !== "string") {
  249. return;
  250. }
  251. val = calculatedValue;
  252. }
  253. const cacheKey = `parsePropertyValue_${prop}_${val}_${caseSensitive}`;
  254. const cachedValue = lruCache.get(cacheKey);
  255. if (cachedValue === false) {
  256. return;
  257. } else if (inArray) {
  258. if (Array.isArray(cachedValue)) {
  259. return cachedValue;
  260. }
  261. } else if (typeof cachedValue === "string") {
  262. return cachedValue;
  263. }
  264. let parsedValue;
  265. const lowerCasedValue = asciiLowercase(val);
  266. if (GLOBAL_KEYS.has(lowerCasedValue)) {
  267. if (inArray) {
  268. parsedValue = [
  269. {
  270. type: AST_TYPES.GLOBAL_KEYWORD,
  271. name: lowerCasedValue
  272. }
  273. ];
  274. } else {
  275. parsedValue = lowerCasedValue;
  276. }
  277. } else if (SYS_COLORS.has(lowerCasedValue)) {
  278. if (/^(?:(?:-webkit-)?(?:[a-z][a-z\d]*-)*color|border)$/i.test(prop)) {
  279. if (inArray) {
  280. parsedValue = [
  281. {
  282. type: AST_TYPES.IDENTIFIER,
  283. name: lowerCasedValue
  284. }
  285. ];
  286. } else {
  287. parsedValue = lowerCasedValue;
  288. }
  289. } else {
  290. parsedValue = false;
  291. }
  292. } else {
  293. try {
  294. const ast = parseCSS(val, {
  295. context: "value"
  296. });
  297. const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
  298. if (error || !matched) {
  299. parsedValue = false;
  300. } else if (inArray) {
  301. const obj = cssTree.toPlainObject(ast);
  302. const items = obj.children;
  303. const values = [];
  304. for (const item of items) {
  305. const { children, name, type, value, unit } = item;
  306. switch (type) {
  307. case AST_TYPES.DIMENSION: {
  308. values.push({
  309. type,
  310. value,
  311. unit: asciiLowercase(unit)
  312. });
  313. break;
  314. }
  315. case AST_TYPES.FUNCTION: {
  316. const css = cssTree
  317. .generate(item)
  318. .replace(/\)(?!\)|\s|,)/g, ") ")
  319. .trim();
  320. const raw = items.length === 1 ? val : css;
  321. // Remove "${name}(" from the start and ")" from the end
  322. const itemValue = raw.slice(name.length + 1, -1).trim();
  323. if (name === "calc") {
  324. if (children.length === 1) {
  325. const [child] = children;
  326. if (child.type === AST_TYPES.NUMBER) {
  327. values.push({
  328. type: AST_TYPES.CALC,
  329. isNumber: true,
  330. value: `${parseFloat(child.value)}`,
  331. name,
  332. raw
  333. });
  334. } else {
  335. values.push({
  336. type: AST_TYPES.CALC,
  337. isNumber: false,
  338. value: `${asciiLowercase(itemValue)}`,
  339. name,
  340. raw
  341. });
  342. }
  343. } else {
  344. values.push({
  345. type: AST_TYPES.CALC,
  346. isNumber: false,
  347. value: asciiLowercase(itemValue),
  348. name,
  349. raw
  350. });
  351. }
  352. } else {
  353. values.push({
  354. type,
  355. name,
  356. value: asciiLowercase(itemValue),
  357. raw
  358. });
  359. }
  360. break;
  361. }
  362. case AST_TYPES.IDENTIFIER: {
  363. if (caseSensitive) {
  364. values.push(item);
  365. } else {
  366. values.push({
  367. type,
  368. name: asciiLowercase(name)
  369. });
  370. }
  371. break;
  372. }
  373. default: {
  374. values.push(item);
  375. }
  376. }
  377. }
  378. parsedValue = values;
  379. } else {
  380. parsedValue = val;
  381. }
  382. } catch {
  383. parsedValue = false;
  384. }
  385. }
  386. lruCache.set(cacheKey, parsedValue);
  387. if (parsedValue === false) {
  388. return;
  389. }
  390. return parsedValue;
  391. };
  392. /**
  393. * Parses a numeric value (number, dimension, percentage).
  394. * Helper function for parseNumber, parseLength, etc.
  395. *
  396. * @param {Array<object>} val - The AST value.
  397. * @param {object} [opt={}] - The options for parsing.
  398. * @param {Function} validateType - Function to validate the node type.
  399. * @returns {object|undefined} The parsed result containing num and unit, or undefined.
  400. */
  401. const parseNumericValue = (val, opt, validateType) => {
  402. const [item] = val;
  403. const { type, value, unit } = item ?? {};
  404. if (!validateType(type, value, unit)) {
  405. return;
  406. }
  407. const { clamp } = opt || {};
  408. const max = opt?.max ?? Number.INFINITY;
  409. const min = opt?.min ?? Number.NEGATIVE_INFINITY;
  410. let num = parseFloat(value);
  411. if (clamp) {
  412. if (num > max) {
  413. num = max;
  414. } else if (num < min) {
  415. num = min;
  416. }
  417. } else if (num > max || num < min) {
  418. return;
  419. }
  420. return {
  421. num,
  422. unit: unit ? asciiLowercase(unit) : null,
  423. type
  424. };
  425. };
  426. /**
  427. * Parses a <number> value.
  428. *
  429. * @param {Array<object>} val - The AST value.
  430. * @param {object} [opt={}] - The options for parsing.
  431. * @returns {string|undefined} The parsed number.
  432. */
  433. const parseNumber = (val, opt = {}) => {
  434. const res = parseNumericValue(val, opt, (type) => type === AST_TYPES.NUMBER);
  435. if (!res) {
  436. return;
  437. }
  438. return `${res.num}`;
  439. };
  440. /**
  441. * Parses a <length> value.
  442. *
  443. * @param {Array<object>} val - The AST value.
  444. * @param {object} [opt={}] - The options for parsing.
  445. * @returns {string|undefined} The parsed length.
  446. */
  447. const parseLength = (val, opt = {}) => {
  448. const res = parseNumericValue(
  449. val,
  450. opt,
  451. (type, value) => type === AST_TYPES.DIMENSION || (type === AST_TYPES.NUMBER && value === "0")
  452. );
  453. if (!res) {
  454. return;
  455. }
  456. const { num, unit } = res;
  457. if (num === 0 && !unit) {
  458. return `${num}px`;
  459. } else if (unit) {
  460. return `${num}${unit}`;
  461. }
  462. };
  463. /**
  464. * Parses a <percentage> value.
  465. *
  466. * @param {Array<object>} val - The AST value.
  467. * @param {object} [opt={}] - The options for parsing.
  468. * @returns {string|undefined} The parsed percentage.
  469. */
  470. const parsePercentage = (val, opt = {}) => {
  471. const res = parseNumericValue(
  472. val,
  473. opt,
  474. (type, value) => type === AST_TYPES.PERCENTAGE || (type === AST_TYPES.NUMBER && value === "0")
  475. );
  476. if (!res) {
  477. return;
  478. }
  479. const { num } = res;
  480. return `${num}%`;
  481. };
  482. /**
  483. * Parses an <angle> value.
  484. *
  485. * @param {Array<object>} val - The AST value.
  486. * @param {object} [opt={}] - The options for parsing.
  487. * @returns {string|undefined} The parsed angle.
  488. */
  489. const parseAngle = (val, opt = {}) => {
  490. const res = parseNumericValue(
  491. val,
  492. opt,
  493. (type, value) => type === AST_TYPES.DIMENSION || (type === AST_TYPES.NUMBER && value === "0")
  494. );
  495. if (!res) {
  496. return;
  497. }
  498. const { num, unit } = res;
  499. if (unit) {
  500. if (!/^(?:deg|g?rad|turn)$/i.test(unit)) {
  501. return;
  502. }
  503. return `${num}${unit}`;
  504. } else if (num === 0) {
  505. return `${num}deg`;
  506. }
  507. };
  508. /**
  509. * Parses a <url> value.
  510. *
  511. * @param {Array<object>} val - The AST value.
  512. * @returns {string|undefined} The parsed url.
  513. */
  514. const parseUrl = (val) => {
  515. const [item] = val;
  516. const { type, value } = item ?? {};
  517. if (type !== AST_TYPES.URL) {
  518. return;
  519. }
  520. const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"');
  521. return `url("${str}")`;
  522. };
  523. /**
  524. * Parses a <string> value.
  525. *
  526. * @param {Array<object>} val - The AST value.
  527. * @returns {string|undefined} The parsed string.
  528. */
  529. const parseString = (val) => {
  530. const [item] = val;
  531. const { type, value } = item ?? {};
  532. if (type !== AST_TYPES.STRING) {
  533. return;
  534. }
  535. const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"');
  536. return `"${str}"`;
  537. };
  538. /**
  539. * Parses a <color> value.
  540. *
  541. * @param {Array<object>} val - The AST value.
  542. * @returns {string|undefined} The parsed color.
  543. */
  544. const parseColor = (val) => {
  545. const [item] = val;
  546. const { name, type, value } = item ?? {};
  547. switch (type) {
  548. case AST_TYPES.FUNCTION: {
  549. const res = resolveColor(`${name}(${value})`, {
  550. format: "specifiedValue"
  551. });
  552. if (res) {
  553. return res;
  554. }
  555. break;
  556. }
  557. case AST_TYPES.HASH: {
  558. const res = resolveColor(`#${value}`, {
  559. format: "specifiedValue"
  560. });
  561. if (res) {
  562. return res;
  563. }
  564. break;
  565. }
  566. case AST_TYPES.IDENTIFIER: {
  567. if (SYS_COLORS.has(name)) {
  568. return name;
  569. }
  570. const res = resolveColor(name, {
  571. format: "specifiedValue"
  572. });
  573. if (res) {
  574. return res;
  575. }
  576. break;
  577. }
  578. default:
  579. }
  580. };
  581. /**
  582. * Parses a <gradient> value.
  583. *
  584. * @param {Array<object>} val - The AST value.
  585. * @returns {string|undefined} The parsed gradient.
  586. */
  587. const parseGradient = (val) => {
  588. const [item] = val;
  589. const { name, type, value } = item ?? {};
  590. if (type !== AST_TYPES.FUNCTION) {
  591. return;
  592. }
  593. const res = resolveGradient(`${name}(${value})`, {
  594. format: "specifiedValue"
  595. });
  596. if (res) {
  597. return res;
  598. }
  599. };
  600. /**
  601. * Resolves a keyword value.
  602. *
  603. * @param {Array<object>} value - The AST node array containing the keyword value.
  604. * @param {object} [opt={}] - The options for parsing.
  605. * @returns {string|undefined} The resolved keyword or undefined.
  606. */
  607. const resolveKeywordValue = (value, opt = {}) => {
  608. const [{ name, type }] = value;
  609. const { length } = opt;
  610. switch (type) {
  611. case AST_TYPES.GLOBAL_KEYWORD: {
  612. if (length > 1) {
  613. return;
  614. }
  615. return name;
  616. }
  617. case AST_TYPES.IDENTIFIER: {
  618. return name;
  619. }
  620. default:
  621. }
  622. };
  623. /**
  624. * Resolves a function value.
  625. *
  626. * @param {Array<object>} value - The AST node array containing the function value.
  627. * @param {object} [opt={}] - The options for parsing.
  628. * @returns {string|undefined} The resolved function or undefined.
  629. */
  630. const resolveFunctionValue = (value, opt = {}) => {
  631. const [{ name, type, value: itemValue }] = value;
  632. const { length } = opt;
  633. switch (type) {
  634. case AST_TYPES.FUNCTION: {
  635. return `${name}(${itemValue})`;
  636. }
  637. case AST_TYPES.GLOBAL_KEYWORD: {
  638. if (length > 1) {
  639. return;
  640. }
  641. return name;
  642. }
  643. case AST_TYPES.IDENTIFIER: {
  644. return name;
  645. }
  646. default:
  647. }
  648. };
  649. /**
  650. * Resolves a length or percentage or number value.
  651. *
  652. * @param {Array<object>} value - The AST node array containing the value.
  653. * @param {object} [opt={}] - The options for parsing.
  654. * @returns {string|undefined} The resolved length/percentage/number or undefined.
  655. */
  656. const resolveNumericValue = (value, opt = {}) => {
  657. const [{ name, type: itemType, value: itemValue }] = value;
  658. const { length, type } = opt;
  659. switch (itemType) {
  660. case AST_TYPES.CALC: {
  661. return `${name}(${itemValue})`;
  662. }
  663. case AST_TYPES.DIMENSION: {
  664. if (type === "angle") {
  665. return parseAngle(value, opt);
  666. }
  667. return parseLength(value, opt);
  668. }
  669. case AST_TYPES.GLOBAL_KEYWORD: {
  670. if (length > 1) {
  671. return;
  672. }
  673. return name;
  674. }
  675. case AST_TYPES.IDENTIFIER: {
  676. return name;
  677. }
  678. case AST_TYPES.NUMBER: {
  679. switch (type) {
  680. case "angle": {
  681. return parseAngle(value, opt);
  682. }
  683. case "length": {
  684. return parseLength(value, opt);
  685. }
  686. case "percentage": {
  687. return parsePercentage(value, opt);
  688. }
  689. default: {
  690. return parseNumber(value, opt);
  691. }
  692. }
  693. }
  694. case AST_TYPES.PERCENTAGE: {
  695. return parsePercentage(value, opt);
  696. }
  697. default:
  698. }
  699. };
  700. /**
  701. * Resolves a color value.
  702. *
  703. * @param {Array<object>} value - The AST node array containing the color value.
  704. * @param {object} [opt={}] - The options for parsing.
  705. * @returns {string|undefined} The resolved color or undefined.
  706. */
  707. const resolveColorValue = (value, opt = {}) => {
  708. const [{ name, type }] = value;
  709. const { length } = opt;
  710. switch (type) {
  711. case AST_TYPES.GLOBAL_KEYWORD: {
  712. if (length > 1) {
  713. return;
  714. }
  715. return name;
  716. }
  717. default: {
  718. return parseColor(value, opt);
  719. }
  720. }
  721. };
  722. /**
  723. * Resolves a gradient or URL value.
  724. *
  725. * @param {Array<object>} value - The AST node array containing the color value.
  726. * @param {object} [opt={}] - The options for parsing.
  727. * @returns {string|undefined} The resolved gradient/url or undefined.
  728. */
  729. const resolveGradientUrlValue = (value, opt = {}) => {
  730. const [{ name, type }] = value;
  731. const { length } = opt;
  732. switch (type) {
  733. case AST_TYPES.GLOBAL_KEYWORD: {
  734. if (length > 1) {
  735. return;
  736. }
  737. return name;
  738. }
  739. case AST_TYPES.IDENTIFIER: {
  740. return name;
  741. }
  742. case AST_TYPES.URL: {
  743. return parseUrl(value, opt);
  744. }
  745. default: {
  746. return parseGradient(value, opt);
  747. }
  748. }
  749. };
  750. /**
  751. * Resolves a border shorthand value.
  752. *
  753. * @param {Array<object>} value - The AST node array containing the shorthand value.
  754. * @param {object} subProps - The sub properties object.
  755. * @param {Map} parsedValues - The Map of parsed values.
  756. * @returns {Array|string|undefined} - The resolved [prop, value] pair, keyword or undefined.
  757. */
  758. const resolveBorderShorthandValue = (value, subProps, parsedValues) => {
  759. const [{ isNumber, name, type, value: itemValue }] = value;
  760. const { color: colorProp, style: styleProp, width: widthProp } = subProps;
  761. switch (type) {
  762. case AST_TYPES.CALC: {
  763. if (isNumber || parsedValues.has(widthProp)) {
  764. return;
  765. }
  766. return [widthProp, `${name}(${itemValue}`];
  767. }
  768. case AST_TYPES.DIMENSION:
  769. case AST_TYPES.NUMBER: {
  770. if (parsedValues.has(widthProp)) {
  771. return;
  772. }
  773. const parsedValue = parseLength(value, { min: 0 });
  774. if (!parsedValue) {
  775. return;
  776. }
  777. return [widthProp, parsedValue];
  778. }
  779. case AST_TYPES.FUNCTION:
  780. case AST_TYPES.HASH: {
  781. if (parsedValues.has(colorProp)) {
  782. return;
  783. }
  784. const parsedValue = parseColor(value);
  785. if (!parsedValue) {
  786. return;
  787. }
  788. return [colorProp, parsedValue];
  789. }
  790. case AST_TYPES.GLOBAL_KEYWORD: {
  791. return name;
  792. }
  793. case AST_TYPES.IDENTIFIER: {
  794. if (isValidPropertyValue(widthProp, name)) {
  795. if (parsedValues.has(widthProp)) {
  796. return;
  797. }
  798. return [widthProp, name];
  799. } else if (isValidPropertyValue(styleProp, name)) {
  800. if (parsedValues.has(styleProp)) {
  801. return;
  802. }
  803. return [styleProp, name];
  804. } else if (isValidPropertyValue(colorProp, name)) {
  805. if (parsedValues.has(colorProp)) {
  806. return;
  807. }
  808. return [colorProp, name];
  809. }
  810. break;
  811. }
  812. default:
  813. }
  814. };
  815. module.exports = {
  816. AST_TYPES,
  817. hasCalcFunc,
  818. hasVarFunc,
  819. isGlobalKeyword,
  820. isValidPropertyValue,
  821. parseAngle,
  822. parseCSS,
  823. parseColor,
  824. parseGradient,
  825. parseLength,
  826. parseNumber,
  827. parsePercentage,
  828. parsePropertyValue,
  829. parseString,
  830. parseUrl,
  831. prepareValue,
  832. resolveBorderShorthandValue,
  833. resolveCalc,
  834. resolveColorValue,
  835. resolveFunctionValue,
  836. resolveGradientUrlValue,
  837. resolveKeywordValue,
  838. resolveNumericValue,
  839. splitValue
  840. };