|
| 1 | +import { dimensions } from './utils/dimensions' |
| 2 | +import { isLength } from './utils/infer-data-type' |
| 3 | +import * as ValueParser from './value-parser' |
| 4 | + |
| 5 | +// Assumption: We already assume that we receive somewhat valid `calc()` |
| 6 | +// expressions. So we will see `calc(1 + 1)` and not `calc(1+1)` |
| 7 | +export function constantFoldDeclaration(input: string): string { |
| 8 | + let folded = false |
| 9 | + let valueAst = ValueParser.parse(input) |
| 10 | + |
| 11 | + ValueParser.walkDepth(valueAst, (valueNode, { replaceWith }) => { |
| 12 | + // Convert `-0`, `+0`, `0.0`, … to `0` |
| 13 | + // Convert `-0px`, `+0em`, `0.0rem`, … to `0` |
| 14 | + if ( |
| 15 | + valueNode.kind === 'word' && |
| 16 | + valueNode.value !== '0' && // Already `0`, nothing to do |
| 17 | + ((valueNode.value[0] === '-' && valueNode.value[1] === '0') || // `-0…` |
| 18 | + (valueNode.value[0] === '+' && valueNode.value[1] === '0') || // `+0…` |
| 19 | + valueNode.value[0] === '0') // `0…` |
| 20 | + ) { |
| 21 | + let dimension = dimensions.get(valueNode.value) |
| 22 | + if (dimension === null) return // This shouldn't happen |
| 23 | + |
| 24 | + if (dimension[0] !== 0) return // Not a zero value, nothing to do |
| 25 | + |
| 26 | + // Replace length units with just `0` |
| 27 | + if (dimension[1] === null || isLength(valueNode.value)) { |
| 28 | + folded = true |
| 29 | + replaceWith(ValueParser.word('0')) |
| 30 | + return |
| 31 | + } |
| 32 | + |
| 33 | + // Replace other units with `0<unit>`, e.g. `0%`, `0fr`, `0s`, … |
| 34 | + else if (valueNode.value !== `0${dimension[1]}`) { |
| 35 | + folded = true |
| 36 | + replaceWith(ValueParser.word(`0${dimension[1]}`)) |
| 37 | + return |
| 38 | + } |
| 39 | + } |
| 40 | + |
| 41 | + // Constant fold `calc()` expressions with two operands and one operator |
| 42 | + else if ( |
| 43 | + valueNode.kind === 'function' && |
| 44 | + (valueNode.value === 'calc' || valueNode.value === '') |
| 45 | + ) { |
| 46 | + // [ |
| 47 | + // { kind: 'word', value: '0.25rem' }, 0 |
| 48 | + // { kind: 'separator', value: ' ' }, 1 |
| 49 | + // { kind: 'word', value: '*' }, 2 |
| 50 | + // { kind: 'separator', value: ' ' }, 3 |
| 51 | + // { kind: 'word', value: '256' } 4 |
| 52 | + // ] |
| 53 | + if (valueNode.nodes.length !== 5) return |
| 54 | + |
| 55 | + let lhs = dimensions.get(valueNode.nodes[0].value) |
| 56 | + let operator = valueNode.nodes[2].value |
| 57 | + let rhs = dimensions.get(valueNode.nodes[4].value) |
| 58 | + |
| 59 | + // Nullify entire expression when multiplying by `0`, e.g.: `calc(0 * 100vw)` -> `0` |
| 60 | + // |
| 61 | + // TODO: Ensure it's safe to do so based on the data types? |
| 62 | + if ( |
| 63 | + operator === '*' && |
| 64 | + ((lhs?.[0] === 0 && lhs?.[1] === null) || // 0 * something |
| 65 | + (rhs?.[0] === 0 && rhs?.[1] === null)) // something * 0 |
| 66 | + ) { |
| 67 | + folded = true |
| 68 | + replaceWith(ValueParser.word('0')) |
| 69 | + return |
| 70 | + } |
| 71 | + |
| 72 | + // We're not dealing with dimensions, so we can't fold this |
| 73 | + if (lhs === null || rhs === null) { |
| 74 | + return |
| 75 | + } |
| 76 | + |
| 77 | + switch (operator) { |
| 78 | + case '*': { |
| 79 | + if ( |
| 80 | + lhs[1] === rhs[1] || // Same Units, e.g.: `1rem * 2rem`, `8 * 6` |
| 81 | + (lhs[1] === null && rhs[1] !== null) || // Unitless * Unit, e.g.: `2 * 1rem` |
| 82 | + (lhs[1] !== null && rhs[1] === null) // Unit * Unitless, e.g.: `1rem * 2` |
| 83 | + ) { |
| 84 | + folded = true |
| 85 | + replaceWith(ValueParser.word(`${lhs[0] * rhs[0]}${lhs[1] ?? ''}`)) |
| 86 | + } |
| 87 | + break |
| 88 | + } |
| 89 | + |
| 90 | + case '+': { |
| 91 | + if ( |
| 92 | + lhs[1] === rhs[1] // Same unit or unitless, e.g.: `1rem + 2rem`, `8 + 6` |
| 93 | + ) { |
| 94 | + folded = true |
| 95 | + replaceWith(ValueParser.word(`${lhs[0] + rhs[0]}${lhs[1] ?? ''}`)) |
| 96 | + } |
| 97 | + break |
| 98 | + } |
| 99 | + |
| 100 | + case '-': { |
| 101 | + if ( |
| 102 | + lhs[1] === rhs[1] // Same unit or unitless, e.g.: `2rem - 1rem`, `8 - 6` |
| 103 | + ) { |
| 104 | + folded = true |
| 105 | + replaceWith(ValueParser.word(`${lhs[0] - rhs[0]}${lhs[1] ?? ''}`)) |
| 106 | + } |
| 107 | + break |
| 108 | + } |
| 109 | + |
| 110 | + case '/': { |
| 111 | + if ( |
| 112 | + rhs[0] !== 0 && // Don't divide by zero |
| 113 | + ((lhs[1] === null && rhs[1] === null) || // Unitless / Unitless, e.g.: `8 / 2` |
| 114 | + (lhs[1] !== null && rhs[1] === null)) // Unit / Unitless, e.g.: `1rem / 2` |
| 115 | + ) { |
| 116 | + folded = true |
| 117 | + replaceWith(ValueParser.word(`${lhs[0] / rhs[0]}${lhs[1] ?? ''}`)) |
| 118 | + } |
| 119 | + break |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + }) |
| 124 | + |
| 125 | + return folded ? ValueParser.toCss(valueAst) : input |
| 126 | +} |
0 commit comments