diff --git a/crates/swc_css_minifier/src/compressor/calc_sum.rs b/crates/swc_css_minifier/src/compressor/calc_sum.rs index 77bc5ba1d854..4bcb3da5096f 100644 --- a/crates/swc_css_minifier/src/compressor/calc_sum.rs +++ b/crates/swc_css_minifier/src/compressor/calc_sum.rs @@ -93,6 +93,7 @@ struct CalcSumContext { impl CalcSumContext { pub fn fold(&mut self, calc_sum: &mut CalcSum) { self.nested_fold(None, calc_sum); + self.remove_zeroes(); calc_sum.expressions = self.expressions.to_vec(); remove_unnecessary_nesting_from_calc_sum(calc_sum); } @@ -120,6 +121,163 @@ impl CalcSumContext { } } + fn remove_zeroes(&mut self) { + if self.expressions.len() == 1 { + // We do not want to transform "calc(0)" into "calc()", that would be invalid + return; + } + + let mut idx = 0; + while idx < self.expressions.len() { + if let Some(CalcProductOrOperator::Product(calc_product)) = self.expressions.get(idx) { + if CalcSumContext::is_calc_product_zero(calc_product) + && self.try_to_remove_sum_operator_and_term(idx) + { + continue; + } + } + idx += 1; + } + } + + fn try_to_remove_sum_operator_and_term(&mut self, term_index: usize) -> bool { + if term_index == 0 { + if self.expressions.len() > 1 { + // If the next operator is minus ("-"), we try to merge it into its following + // term eg: calc(0 - 3% + 10px) => calc(-3% + 10px) + // ... but it's not always possible: calc(0 - pi) + let can_be_removed = match &self.expressions[1] { + CalcProductOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Sub, + .. + }) => self.try_to_switch_sum_term_sign(term_index + 2), + _ => true, + }; + + if can_be_removed { + // Remove the term + self.expressions.remove(term_index); + // Remove the next operator (the sign has been merged into its term) + self.expressions.remove(term_index); + } + return can_be_removed; + } + false + } else { + // Remove the term + self.expressions.remove(term_index); + // Remove the operator + self.expressions.remove(term_index - 1); + true + } + } + + fn try_to_switch_sum_term_sign(&mut self, term_index: usize) -> bool { + if let Some(CalcProductOrOperator::Product(ref mut calc_product)) = + self.expressions.get_mut(term_index) + { + let mut idx = 0; + while idx < calc_product.expressions.len() { + match &mut calc_product.expressions[idx] { + CalcValueOrOperator::Value(CalcValue::Number(n)) => { + n.value = -n.value; + return true; + } + CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Angle(a))) => { + a.value.value = -a.value.value; + return true; + } + CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Flex(f))) => { + f.value.value = -f.value.value; + return true; + } + CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Frequency(f))) => { + f.value.value = -f.value.value; + return true; + } + CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Length(l))) => { + l.value.value = -l.value.value; + return true; + } + CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Resolution(r))) => { + r.value.value = -r.value.value; + return true; + } + CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Time(d))) => { + d.value.value = -d.value.value; + return true; + } + CalcValueOrOperator::Value(CalcValue::Dimension( + Dimension::UnknownDimension(u), + )) => { + u.value.value = -u.value.value; + return true; + } + CalcValueOrOperator::Value(CalcValue::Percentage(p)) => { + p.value.value = -p.value.value; + return true; + } + _ => {} + } + idx += 1; + } + } + + false + } + + fn is_calc_product_zero(calc_product: &CalcProduct) -> bool { + if calc_product.expressions.len() == 1 { + match &calc_product.expressions[0] { + CalcValueOrOperator::Value(calc_value) => { + CalcSumContext::is_calc_value_zero(calc_value) + } + _ => false, + } + } else { + false + } + } + + fn is_calc_value_zero(calc_value: &CalcValue) -> bool { + match calc_value { + CalcValue::Number(Number { value, .. }) + | CalcValue::Dimension(Dimension::Angle(Angle { + value: Number { value, .. }, + .. + })) + | CalcValue::Dimension(Dimension::Length(Length { + value: Number { value, .. }, + .. + })) + | CalcValue::Dimension(Dimension::Flex(Flex { + value: Number { value, .. }, + .. + })) + | CalcValue::Dimension(Dimension::Frequency(Frequency { + value: Number { value, .. }, + .. + })) + | CalcValue::Dimension(Dimension::Resolution(Resolution { + value: Number { value, .. }, + .. + })) + | CalcValue::Dimension(Dimension::Time(Time { + value: Number { value, .. }, + .. + })) + | CalcValue::Dimension(Dimension::UnknownDimension(UnknownDimension { + value: Number { value, .. }, + .. + })) + | CalcValue::Percentage(Percentage { + value: Number { value, .. }, + .. + }) => *value == 0.0, + _ => false, + } + } + fn merge_operators( surrounding_operator: Option<&CalcOperator>, nested_operator: Option<&CalcOperator>, @@ -799,6 +957,21 @@ fn fold_calc_product(calc_product: &mut CalcProduct) { prev_operand = cur_operand } } + ( + Some(operand1), + Some(CalcOperator { + value: CalcOperatorType::Div, + span: op_span, + }), + Some(_), + ) => { + folded_expressions.push(CalcValueOrOperator::Value(operand1.clone())); + folded_expressions.push(CalcValueOrOperator::Operator(CalcOperator { + span: *op_span, + value: CalcOperatorType::Div, + })); + prev_operand = cur_operand + } _ => { // Something is wrong: we should iterate over some (operand, operator, operand) // tuples diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/discard-zero/input.css b/crates/swc_css_minifier/tests/fixture/compress-calc/discard-zero/input.css index 389c4c5da1df..998f77161521 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/discard-zero/input.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/discard-zero/input.css @@ -33,3 +33,7 @@ .class9 { width: calc( 0px - (var(--foo, 4px) / 2)); } + +.class10 { + width: calc(0 - (pi * 3px) - 0px - 4em); +} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/discard-zero/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/discard-zero/output.min.css index 9e546ff34347..b4831ebb18bd 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/discard-zero/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/discard-zero/output.min.css @@ -1 +1 @@ -.class1{width:calc(100vw/2 - 6px)}.class2{width:500px}.class3{width:calc(0 - 10px)}.class4{width:calc(0 - 1px - 1em)}.class5{width:calc(0 - (100vw - 10px)/2)}.class6{width:calc(10px - 100vw)}.class7{width:calc(0px - (100vw - 10px)*2)}.class8{width:calc(-10px - 100vw)}.class9{width:calc(0px - var(--foo,4px)/2)} +.class1{width:calc(100vw/2 - 6px)}.class2{width:500px}.class3{width:-10px}.class4{width:calc(-1px - 1em)}.class5{width:calc((100vw - 10px)/-2)}.class6{width:calc(10px - 100vw)}.class7{width:calc((100vw - 10px)*-2)}.class8{width:calc(-10px - 100vw)}.class9{width:calc(var(--foo,4px)/-2)}.class10{width:calc(pi*-3px - 4em)} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/divide/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/divide/output.min.css index b13271d09efb..fb0def5b352f 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/divide/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/divide/output.min.css @@ -1 +1 @@ -.class1{width:calc(((var(--a) + 4px)/2)/2)}.class2{width:calc(((var(--a) + 4px)/2)/2 + 4px)}.class3{width:calc(100%/(var(--aspect-ratio)))}.class4{width:calc((var(--fluid-screen) - (var(--fluid-min-width)/16)*1rem)/(var(--fluid-max-width)/16 - var(--fluid-min-width)/16))}.class5{width:calc(1/(10/var(--dot-size)))}.class6{width:calc(1/((var(--a) - var(--b))/16))} +.class1{width:calc(((var(--a) + 4px)/2)/2)}.class2{width:calc(((var(--a) + 4px)/2)/2 + 4px)}.class3{width:calc(100%/var(--aspect-ratio))}.class4{width:calc((var(--fluid-screen) - (var(--fluid-min-width)/16)*1rem)/(var(--fluid-max-width)/16 - var(--fluid-min-width)/16))}.class5{width:calc(1/(10/var(--dot-size)))}.class6{width:calc(1/((var(--a) - var(--b))/16))} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/nested-calc/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/nested-calc/output.min.css index a5f3a6d6a7b5..afbc40465494 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/nested-calc/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/nested-calc/output.min.css @@ -1 +1 @@ -.class1{width:calc(50% - 25px)}.class2{width:calc(1px + 2px/2)}.class3{width:calc((0em - 10px)/2)}.class4{width:calc(2.25rem - 0px)}.class5{width:calc(100vh - 15rem - 100px)}.class6{width:calc(100% - 10px - 2vw)}.class7{width:calc(100% - 10px + 2vw)}.class8{width:300px} +.class1{width:calc(50% - 25px)}.class2{width:calc(1px + 2px/2)}.class3{width:calc(-10px/2)}.class4{width:2.25rem}.class5{width:calc(100vh - 15rem - 100px)}.class6{width:calc(100% - 10px - 2vw)}.class7{width:calc(100% - 10px + 2vw)}.class8{width:300px} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/precision/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/precision/output.min.css index 036578c167e6..90fa36f0b710 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/precision/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/precision/output.min.css @@ -1 +1 @@ -.class1{width:calc(1/100)}.class2{width:calc(5/1e6)}.class3{width:calc(100%/3*3)}.class4{width:calc(calc(100%/3)*3)} +.class1{width:calc(1/100)}.class2{width:calc(5/1e6)}.class3{width:calc(100%/9)}.class4{width:calc(calc(100%/3)*3)} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/simple-calc/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/simple-calc/output.min.css index ef5e6e1a45a0..4392597df0ef 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/simple-calc/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/simple-calc/output.min.css @@ -1 +1 @@ -.class1{width:2px}.class2{width:2px;height:5px}.class3{width:1.5rem}.class4{width:2em}.class5{width:calc(2ex/2)}.class6{width:60px}.class7{width:calc(-0px + 100%)}.class8{width:calc(200px - 100%)}.class9{width:2px}.class10{width:2px}.class11{width:3rem}.class12{width:calc(100% + 1px)}.class13{width:calc(2rem - .14285em)}@supports(width:calc(100% - constant(safe-area-inset-left))){.class14{width:calc(100% - constant(safe-area-inset-left))}}.class15{width:calc(100% + 1163.5px - 75.37%)}.class16{width:calc(((100% + 123.5px)/.7537 - 100vw + 60px)/2 + 30px)}.class17{width:calc(75.37% - 763.5px)}.class18{width:calc(1163.5px - 10%)}.class19{width:calc((0em - 10px)/2)}.class20{width:200px}.class21{width:0}.class22{width:200px}.class23{width:calc(200px/1)}.class24{width:-200px}.class25{width:0}.class26{width:-200px}.class27{width:calc(200px/-1)}.class28,.class29,.class30{width:200px}.class31{width:22px}.class32{width:200px}.class33{width:22e9px}.class34{width:90px}.class35{width:100%}.class36{width:22px}.class37{width:200px} +.class1{width:2px}.class2{width:2px;height:5px}.class3{width:1.5rem}.class4{width:2em}.class5{width:calc(2ex/2)}.class6{width:60px}.class7{width:100%}.class8{width:calc(200px - 100%)}.class9{width:2px}.class10{width:2px}.class11{width:3rem}.class12{width:calc(100% + 1px)}.class13{width:calc(2rem - .14285em)}@supports(width:calc(100% - constant(safe-area-inset-left))){.class14{width:calc(100% - constant(safe-area-inset-left))}}.class15{width:calc(100% + 1163.5px - 75.37%)}.class16{width:calc(((100% + 123.5px)/.7537 - 100vw + 60px)/2 + 30px)}.class17{width:calc(75.37% - 763.5px)}.class18{width:calc(1163.5px - 10%)}.class19{width:calc(-10px/2)}.class20{width:200px}.class21{width:0}.class22{width:200px}.class23{width:calc(200px/1)}.class24{width:-200px}.class25{width:0}.class26{width:-200px}.class27{width:calc(200px/-1)}.class28,.class29,.class30{width:200px}.class31{width:22px}.class32{width:200px}.class33{width:22e9px}.class34{width:90px}.class35{width:100%}.class36{width:22px}.class37{width:200px} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/units/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/units/output.min.css index 98dfc2de3fc3..61561c574ecb 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/units/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/units/output.min.css @@ -1 +1 @@ -.class1{width:calc(1px + 1)}.class2{width:calc(100% - 180px)}.class3{width:calc(100% - 30px)}.class4{width:.95s}.class5{width:calc(99.99%*1/1 - 0rem)}.class6{width:calc(55% + 5em)}.class7{width:calc(50px - 0em)}.class8{width:0} +.class1{width:calc(1px + 1)}.class2{width:calc(100% - 180px)}.class3{width:calc(100% - 30px)}.class4{width:.95s}.class5{width:calc(99.99%/1)}.class6{width:calc(55% + 5em)}.class7{width:50px}.class8{width:0}