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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 2x 2x 2x 3606x 3602x 3600x 23x 23x 3616x 3616x 3616x 3616x 3584x 3584x 3584x 3583x 3583x 3616x 3720x 3719x 10163x 10163x 10162x 3706x 2x 2x 2x 2x 2x 3704x 3600x 3600x 4x 3596x 3600x 3591x 3591x 3582x 3581x 104x 3598x 3616x 3595x 3595x 23x | import valueParser from 'postcss-value-parser';
import type { StylingHookMetadata } from '../types.js';
type Theme = 'slds' | 'cosmos';
type ParsedNode = ReturnType<typeof valueParser>['nodes'][number];
type FunctionNode = Extract<ParsedNode, { type: 'function' }>;
const MAX_FUNCTION_DEPTH = 100;
const MAX_RESOLUTION_WORK = 10_000;
const MAX_RESOLVED_LENGTH = 65_536;
function firstArgument(nodes: ParsedNode[]): ParsedNode[] {
const comma = nodes.findIndex((node) => node.type === 'div' && node.value === ',');
return nodes.slice(0, comma < 0 ? nodes.length : comma);
}
function customProperty(node: FunctionNode): string | undefined {
return firstArgument(node.nodes).find((child) => child.type === 'word')?.value;
}
/**
* Resolve a styling-hook value to its theme-specific "pivot" (concrete) form.
*
* High-level flow:
* 1. Parse `original` as a CSS value (via postcss-value-parser).
* 2. Walk every function node:
* - `light-dark(A, B)` → keep only A (light), then resolve A further.
* - `var(--token)` → look up `tokens[--token].values[theme]` and resolve that.
* - other functions (e.g. `calc(...)`) → recurse into their arguments.
* 3. Replace each resolved node in place; if anything fails (missing token, cycle,
* depth/work/length limits), return the original string unchanged.
*
* Example:
* tokens = {
* '--slds-r-base': { values: { slds: '#123456' } },
* '--slds-g-middle': { values: { slds: 'var(--slds-r-base)' } },
* '--slds-g-top': { values: { slds: 'var(--slds-g-middle)' } },
* }
* resolvePivotValue('var(--slds-g-top)', 'slds', tokens)
* → var(--slds-g-top)
* → var(--slds-g-middle)
* → var(--slds-r-base)
* → '#123456'
*
* resolvePivotValue('light-dark(var(--slds-r-base), #000)', 'slds', tokens)
* → light-dark picks light arg → var(--slds-r-base) → '#123456'
*/
export function resolvePivotValue(
original: string,
theme: Theme,
tokens: Record<string, StylingHookMetadata>,
maxDepth = 20
): string {
let remainingWork = MAX_RESOLUTION_WORK;
const resolveExpression = (
expression: string,
depth: number,
visited: Set<string>,
functionDepth = 0
): string | null => {
Iif (expression.length > MAX_RESOLVED_LENGTH || functionDepth > MAX_FUNCTION_DEPTH) return null;
const parsed = valueParser(expression);
let projectedLength = expression.length;
const replaceNode = (nodes: ParsedNode[], index: number, value: string): boolean => {
const originalLength = valueParser.stringify(nodes[index]).length;
projectedLength += value.length - originalLength;
if (projectedLength > MAX_RESOLVED_LENGTH) return false;
nodes[index] = { type: 'word', value } as ParsedNode;
return true;
};
const resolveNodes = (nodes: ParsedNode[], nestedDepth: number): boolean => {
if (nestedDepth > MAX_FUNCTION_DEPTH) return false;
for (const [index, node] of nodes.entries()) {
remainingWork -= 1;
if (remainingWork < 0) return false;
if (node.type !== 'function') continue;
if (node.value.toLowerCase() === 'light-dark') {
const lightValue = valueParser.stringify(firstArgument(node.nodes)).trim();
const resolved = resolveExpression(lightValue, depth, visited, nestedDepth + 1);
Iif (resolved === null) return false;
Iif (!replaceNode(nodes, index, resolved)) return false;
continue;
}
if (node.value.toLowerCase() === 'var') {
const property = customProperty(node);
if (!property || property.startsWith('--lwc-') || depth >= maxDepth || visited.has(property)) {
return false;
}
const value = tokens[property]?.values[theme];
if (!value) return false;
const resolved = resolveExpression(
value,
depth + 1,
new Set(visited).add(property),
nestedDepth + 1
);
if (resolved === null) return false;
if (!replaceNode(nodes, index, resolved)) return false;
continue;
}
if (!resolveNodes(node.nodes, nestedDepth + 1)) return false;
}
return true;
};
if (!resolveNodes(parsed.nodes, functionDepth)) return null;
const resolved = parsed.toString();
return resolved.length <= MAX_RESOLVED_LENGTH ? resolved : null;
};
return resolveExpression(original, 0, new Set()) ?? original;
}
|