Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | // Copyright (c) 2015-present, salesforce.com, inc. All rights reserved // Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license /** * Local, AST-based replacement for `@salesforce-ux/postcss-css-variable-value`. * * The published plugin resolves `var(--name, <fallback>)` down to its fallback * using a REGEX, which mangles three real cases in the SLDS legacy CSS: * 1. digit-containing names — `var(--foo-10-opacity-10, rgba(0,0,0,.16))` * became `10-opacity-10, rgba(0,0,0,.16)` * 2. nested var() in gradients — dropped the outer structure, producing * unbalanced parens (`rgba(0,0,0,.025)) 25%, transparent`) * 3. two var() in one value — `var(--a,4px) var(--b,8px)` became `4px b, 8px` * * That malformed output was tolerated by autoprefixer@9 / cssnano@5's lenient * value parsers, but crashes the stricter parsers in autoprefixer@10 and the * cssnano-family@8 plugins. This implementation walks the postcss-value-parser * AST instead, so every case resolves to a spec-valid string. * * Resolution semantics (matched to the original for unchanged cases): * - `var(--x)` with no fallback is left untouched. * - `var(--x, <fallback>)` is replaced by `<fallback>`. * - Nested `var()` inside a fallback is resolved recursively (drilling to the * innermost fallback), matching the original's behaviour. */ const valueParser = require('postcss-value-parser'); /** * Given the argument nodes of a `var(...)` function, return the fallback nodes * (everything after the first top-level comma `div`), or null when there is no * fallback. postcss-value-parser represents the comma between `--name` and the * fallback as a `div` node with `value === ','`. */ function getFallbackNodes(fnNodes) { const firstCommaIndex = fnNodes.findIndex( (n) => n.type === 'div' && n.value === ',', ); if (firstCommaIndex === -1) return null; // Everything after the first comma is the fallback. Skip the comma node // itself and any leading whitespace the parser attached to it. const fallback = fnNodes.slice(firstCommaIndex + 1); while (fallback.length && fallback[0].type === 'space') fallback.shift(); return fallback.length ? fallback : null; } /** * Recursively resolve every `var(...)` function within a list of value nodes, * mutating them in place so a `var()` becomes its (further-resolved) fallback. */ function resolveNodes(nodes) { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (node.type !== 'function') continue; // Resolve inner functions first (handles var() nested inside gradients). if (node.nodes && node.nodes.length) resolveNodes(node.nodes); if (node.value !== 'var') continue; const fallback = getFallbackNodes(node.nodes); if (!fallback) continue; // `var(--x)` with no fallback — leave as-is. // A single-function fallback (e.g. the whole thing is `linear-gradient(...)` // or another `var(...)`) replaces the var node directly, preserving its // function-ness so stringify emits balanced parens. if (fallback.length === 1 && fallback[0].type === 'function') { nodes[i] = fallback[0]; i--; // re-visit in case the replacement is itself a var() continue; } // Otherwise splice the fallback nodes in where the var() was. nodes.splice(i, 1, ...fallback); i--; // re-visit the first spliced node (it may be a var()/function) } } module.exports = (opts = {}) => { const options = { preserve: true, ...opts }; return { postcssPlugin: 'sds-css-variable-value', Declaration(decl) { // Cheap guard: only touch declarations that actually contain a var(). if (!decl.value || decl.value.indexOf('var(') === -1) return; const parsed = valueParser(decl.value); resolveNodes(parsed.nodes); const resolved = parsed.toString(); if (resolved === decl.value) return; // nothing resolved (e.g. only var() w/o fallback) if (options.preserve) { decl.cloneBefore({ value: resolved }); } else { decl.value = resolved; } }, }; }; module.exports.postcss = true; |