From 7af56c51254dd7631fe515c1639c9b635fc9e12d Mon Sep 17 00:00:00 2001 From: Vincent Ricard Date: Sun, 23 Oct 2022 11:28:32 +0200 Subject: [PATCH] feat(css/minifier) Follow the CSS spec more rigorously --- Cargo.lock | 1 + crates/swc_css_minifier/Cargo.toml | 1 + .../src/compressor/calc_sum.rs | 1690 +++++++++-------- crates/swc_css_minifier/tests/fixture.rs | 3 +- .../compress-calc/add-sub/output.min.css | 2 +- .../container-condition/output.min.css | 2 +- .../convert-units/output.min.css | 2 +- .../css-variables/output.min.css | 2 +- .../compress-calc/discard-zero/output.min.css | 2 +- .../compress-calc/divide/output.min.css | 2 +- .../compress-calc/multiply/output.min.css | 2 +- .../compress-calc/nested-calc/output.min.css | 2 +- .../compress-calc/precision/output.min.css | 2 +- .../compress-calc/simple-calc/output.min.css | 2 +- .../compress-calc/units/output.min.css | 2 +- .../compress-calc/vendor/output.min.css | 2 +- crates/swc_html_parser/tests/html5lib-tests | 2 +- 17 files changed, 863 insertions(+), 858 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42f86093fb81..c71a6b622f7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3308,6 +3308,7 @@ version = "0.99.15" dependencies = [ "criterion", "serde", + "string_cache", "swc_atoms", "swc_common", "swc_css_ast", diff --git a/crates/swc_css_minifier/Cargo.toml b/crates/swc_css_minifier/Cargo.toml index b42221694378..2c774c7f9a0c 100644 --- a/crates/swc_css_minifier/Cargo.toml +++ b/crates/swc_css_minifier/Cargo.toml @@ -14,6 +14,7 @@ bench = false [dependencies] serde = "1.0.118" +string_cache = "0.8.4" swc_atoms = { version = "0.4.24", path = "../swc_atoms" } swc_common = { version = "0.29.13", path = "../swc_common" } swc_css_ast = { version = "0.124.4", path = "../swc_css_ast" } diff --git a/crates/swc_css_minifier/src/compressor/calc_sum.rs b/crates/swc_css_minifier/src/compressor/calc_sum.rs index 4386152b5934..a9e44a0a15af 100644 --- a/crates/swc_css_minifier/src/compressor/calc_sum.rs +++ b/crates/swc_css_minifier/src/compressor/calc_sum.rs @@ -1,835 +1,574 @@ use std::collections::HashMap; -use swc_atoms::JsWord; +use string_cache::Atom; +use swc_atoms::{JsWord, JsWordStaticSet}; +use swc_common::Span; use swc_css_ast::*; use super::{unit::*, Compressor}; use crate::compressor::math::{is_calc_function_name, transform_calc_value_into_component_value}; -// transform "(simple calc-value)" into "simple calc-value" -fn remove_unnecessary_nesting_from_calc_sum(calc_sum: &mut CalcSum) { - if calc_sum.expressions.len() == 1 { - match &calc_sum.expressions[0] { - CalcProductOrOperator::Product(CalcProduct { - expressions: calc_product_expressions, - .. - }) if calc_product_expressions.len() == 1 => { - if let CalcValueOrOperator::Value(CalcValue::Sum(CalcSum { - expressions: nested_expressions, - span: nested_span, - })) = &calc_product_expressions[0] - { - calc_sum.span = *nested_span; - calc_sum.expressions = nested_expressions.to_vec(); - } - } - _ => {} - } - } -} - -fn try_to_extract_into_calc_value(calc_sum: &CalcSum) -> Option { - if calc_sum.expressions.len() == 1 { - return match &calc_sum.expressions[0] { - CalcProductOrOperator::Product(CalcProduct { - expressions: calc_product_expressions, - .. - }) if calc_product_expressions.len() == 1 => match &calc_product_expressions[0] { - CalcValueOrOperator::Value(calc_value) => Some(calc_value.clone()), - _ => None, - }, - _ => None, - }; - } - - None -} - -// We want to track the position of data (dimension, percentage, operator...) in -// a Vec +// Calc-operator node of the calculation tree +// https://www.w3.org/TR/css-values-4/#calculation-tree-calc-operator-nodes #[derive(Debug, Clone)] -struct IndexedData { - pos: usize, - data: T, +enum CalcOperatorNode { + Sum(Vec), + Product(Vec), + Negate(CalcNode), + Invert(CalcNode), } #[derive(Debug, Clone)] -struct IndexedOperatorAndOperand { - operator: Option>, - operand: IndexedData, +enum CalcNode { + Number(Number), + Dimension(Dimension), + Percentage(Percentage), + Constant(Ident), + Function(Function), + OperatorNode(Box), } -type SumOperation = fn(v1: f64, v2: f64, ratio: Option) -> f64; - -const SUM_OPERATION_ADD: SumOperation = |v1, v2, ratio| match ratio { - None => v1 + v2, - Some(r) => v2.mul_add(r, v1), -}; -const SUM_OPERATION_SUBTRACT: SumOperation = |v1, v2, ratio| match ratio { - None => v1 - v2, - Some(r) => -(v2.mul_add(r, -v1)), -}; - -fn get_precision(n: f64) -> usize { +fn get_precision(n: f64) -> u32 { let n_as_str = n.to_string(); - n_as_str.find('.').map_or(0, |sep| n_as_str.len() - sep) -} - -#[derive(Default)] -struct CalcSumContext { - number: Option>, - percentage: Option>, - absolute_length: Option>, - other_lengths: HashMap>, - angle: Option>, - duration: Option>, - frequency: Option>, - resolution: Option>, - flex: Option>, - unknown_dimension: HashMap>, - expressions: Vec, + n_as_str + .find('.') + .map_or(0, |sep| (n_as_str.len() - sep) as u32 - 1) } -impl CalcSumContext { - pub fn fold(&mut self, calc_sum: &mut CalcSum) { - self.nested_fold(None, calc_sum); - calc_sum.expressions = self.expressions.to_vec(); - remove_unnecessary_nesting_from_calc_sum(calc_sum); - } - - fn nested_fold(&mut self, surrounding_operator: Option<&CalcOperator>, calc_sum: &mut CalcSum) { - let mut operator: Option = - CalcSumContext::merge_operators(surrounding_operator, None); - - let mut expr_it = calc_sum.expressions.iter_mut(); - while let Some(calc_product_or_operator) = expr_it.next() { - match calc_product_or_operator { - CalcProductOrOperator::Product(calc_product) => { - fold_calc_product(calc_product); - self.reduce(operator.as_ref(), calc_product); +// Parse our AST (subtree) into a calculation tree (as defined by the spec) +// https://www.w3.org/TR/css-values-4/#parse-a-calculation +fn collect_calc_sum_into_calc_node(calc_sum: &CalcSum) -> CalcNode { + let mut is_negated = false; + let mut operands: Vec = vec![]; + for node in &calc_sum.expressions { + match &node { + CalcProductOrOperator::Product(calc_product) => { + let mut node = collect_calc_product_into_calc_node(calc_product); + if is_negated { + node = CalcNode::OperatorNode(Box::new(CalcOperatorNode::Negate(node))); } - _ => { - // We should have an operand - return; - } - }; - - if let Some(CalcProductOrOperator::Operator(op)) = expr_it.next() { - operator = CalcSumContext::merge_operators(surrounding_operator, Some(op)); + operands.push(node); } + CalcProductOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Sub, + .. + }) => { + is_negated = true; + } + CalcProductOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Add, + .. + }) => { + is_negated = false; + } + _ => {} } } - fn merge_operators( - surrounding_operator: Option<&CalcOperator>, - nested_operator: Option<&CalcOperator>, - ) -> Option { - match nested_operator { - None => surrounding_operator.cloned(), - Some(no) => match surrounding_operator { - None - | Some(CalcOperator { - value: CalcOperatorType::Add, - .. - }) => Some(no.clone()), - Some(CalcOperator { - value: CalcOperatorType::Sub, - .. - }) => Some(CalcOperator { - span: no.span, - value: if no.value == CalcOperatorType::Sub { - CalcOperatorType::Add - } else { - CalcOperatorType::Sub - }, - }), - _ => unreachable!("Operator value can only be Add or Sub"), - }, - } + if operands.len() == 1 { + operands[0].clone() + } else { + CalcNode::OperatorNode(Box::new(CalcOperatorNode::Sum(operands))) } +} - fn reduce(&mut self, operator: Option<&CalcOperator>, operand: &CalcProduct) { - if operand.expressions.len() == 1 { - match &operand.expressions[0] { - CalcValueOrOperator::Value(CalcValue::Number(n)) => { - self.sum_number(operator, operand, n); - } - CalcValueOrOperator::Value(CalcValue::Percentage(p)) => { - self.sum_percentage(operator, operand, p); - } - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Length(l))) => { - self.sum_length(operator, operand, l); - } - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Angle(a))) => { - self.sum_angle(operator, operand, a); - } - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Time(d))) => { - self.sum_duration(operator, operand, d); - } - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Frequency(f))) => { - self.sum_frequency(operator, operand, f); - } - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Resolution(r))) => { - self.sum_resolution(operator, operand, r); - } - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Flex(f))) => { - self.sum_flex(operator, operand, f); - } - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::UnknownDimension( - u, - ))) => { - self.sum_unknown_dimension(operator, operand, u); - } - CalcValueOrOperator::Value(CalcValue::Sum(s)) => { - let mut sum = s.clone(); - self.nested_fold(operator, &mut sum); - } - CalcValueOrOperator::Value(CalcValue::Function(Function { - name, value, .. - })) if is_calc_function_name(name) && value.len() == 1 => { - match &value[0] { - ComponentValue::CalcSum(calc_sum) => { - let mut sum = calc_sum.clone(); - self.nested_fold(operator, &mut sum); - } - _ => { - // Other cases (constant, function...), just push the data - self.push(operator, operand); - } - } - } - _ => { - // Other cases (constant, function...), just push the data - self.push(operator, operand); +fn collect_calc_product_into_calc_node(calc_product: &CalcProduct) -> CalcNode { + let mut is_inverted = false; + let mut operands: Vec = vec![]; + for node in &calc_product.expressions { + match &node { + CalcValueOrOperator::Value(calc_value) => { + let mut node = collect_calc_value_into_calc_node(calc_value); + if is_inverted { + node = CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(node))); } + operands.push(node); } - } else { - // The calc product is not "simple", just push the data - self.push(operator, operand); + CalcValueOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Div, + .. + }) => { + is_inverted = true; + } + CalcValueOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Mul, + .. + }) => { + is_inverted = false; + } + _ => {} } } - fn push(&mut self, operator: Option<&CalcOperator>, operand: &CalcProduct) { - if let Some(op) = operator { - self.expressions - .push(CalcProductOrOperator::Operator(op.clone())); - } - - self.expressions - .push(CalcProductOrOperator::Product(operand.clone())); + if operands.len() == 1 { + operands[0].clone() + } else { + CalcNode::OperatorNode(Box::new(CalcOperatorNode::Product(operands))) } +} - fn sum_number(&mut self, operator: Option<&CalcOperator>, operand: &CalcProduct, n: &Number) { - match &mut self.number { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - if let Some(result) = CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value, - n.value, - None, - ) { - data.value = result; - - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Number(data.clone())), - ); - } else { - self.push(operator, operand); - } +fn collect_calc_value_into_calc_node(calc_value: &CalcValue) -> CalcNode { + match calc_value { + CalcValue::Number(n) => CalcNode::Number(n.clone()), + CalcValue::Dimension(d) => CalcNode::Dimension(d.clone()), + CalcValue::Percentage(p) => CalcNode::Percentage(p.clone()), + CalcValue::Constant(c) => CalcNode::Constant(c.clone()), + CalcValue::Function(f @ Function { name, value, .. }) + if is_calc_function_name(name) && value.len() == 1 => + { + match &value[0] { + ComponentValue::CalcSum(calc_sum) => collect_calc_sum_into_calc_node(calc_sum), + _ => CalcNode::Function(f.clone()), } - None => self.number = Some(self.new_indexed_data(operator, operand, n.clone())), } + CalcValue::Function(f) => CalcNode::Function(f.clone()), + CalcValue::Sum(calc_sum) => collect_calc_sum_into_calc_node(calc_sum), } +} - fn sum_percentage( - &mut self, - operator: Option<&CalcOperator>, - operand: &CalcProduct, - p: &Percentage, - ) { - match &mut self.percentage { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - if let Some(result) = CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value.value, - p.value.value, - None, - ) { - data.value.value = result; - - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Percentage(data.clone())), - ); - } else { - self.push(operator, operand); - } - } - None => self.percentage = Some(self.new_indexed_data(operator, operand, p.clone())), - } +// https://www.w3.org/TR/css-values-4/#calc-simplification +// Impl. note: our "simplify" implementation does not always return a canonical +// result For example: +// * we do not want to transform calc(10px / 3) in to 3.33333333333333px (we'd +// lose precision) +// * we can not transform relative unit (em, vh, etc) into their canonical form +// (we do not have the screen/browser context to compute it) +fn simplify_calc_node(calc_node: &CalcNode) -> CalcNode { + match calc_node { + CalcNode::Number(_) + | CalcNode::Dimension(_) + | CalcNode::Percentage(_) + | CalcNode::Constant(_) + | CalcNode::Function(_) => calc_node.clone(), + CalcNode::OperatorNode(op) => simplify_calc_operator_node(op), } +} - fn sum_length(&mut self, operator: Option<&CalcOperator>, operand: &CalcProduct, l: &Length) { - let unit = l.unit.value.to_ascii_lowercase(); - if is_absolute_length(unit) { - self.sum_absolute_length(operator, operand, l) - } else { - self.sum_other_length(operator, operand, l) +fn simplify_calc_operator_node(calc_operator_node: &CalcOperatorNode) -> CalcNode { + match calc_operator_node { + CalcOperatorNode::Invert(inverted_calc_node) => { + simplify_calc_operator_node_invert(inverted_calc_node) + } + CalcOperatorNode::Negate(negated_calc_node) => { + simplify_calc_operator_node_negate(negated_calc_node) } + CalcOperatorNode::Product(nodes) => simplify_calc_operator_node_product(nodes), + CalcOperatorNode::Sum(nodes) => simplify_calc_operator_node_sum(nodes), } +} - fn sum_absolute_length( - &mut self, - operator: Option<&CalcOperator>, - operand: &CalcProduct, - l: &Length, - ) { - match &mut self.absolute_length { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - let prev_unit = data.unit.value.to_ascii_lowercase(); - let unit = l.unit.value.to_ascii_lowercase(); - if let Some(result) = get_absolute_length_ratio(prev_unit, unit).and_then(|ratio| { - CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value.value, - l.value.value, - Some(ratio), - ) - }) { - data.value.value = result; - - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Length( - data.clone(), - ))), - ); - } else { - self.push(operator, operand); - } +// https://www.w3.org/TR/css-values-4/#calc-simplification - Step 7 +fn simplify_calc_operator_node_invert(calc_node: &CalcNode) -> CalcNode { + let calc_node = simplify_calc_node(calc_node); + match &calc_node { + CalcNode::Number(n) => { + // If root’s child is a number (not a percentage or dimension) return the + // reciprocal of the child’s value. + if let Some(result) = try_to_divide_values(1.0, n.value) { + CalcNode::Number(Number { + value: result, + span: n.span, + raw: n.raw.clone(), + }) + } else { + CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(calc_node.clone()))) } - None => { - self.absolute_length = Some(self.new_indexed_data(operator, operand, l.clone())) + } + CalcNode::OperatorNode(op) => { + match &**op { + // If root’s child is an Invert node, return the child’s child. + CalcOperatorNode::Invert(node) => node.clone(), + _ => CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(calc_node.clone()))), } } + _ => CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(calc_node.clone()))), } +} - fn sum_other_length( - &mut self, - operator: Option<&CalcOperator>, - operand: &CalcProduct, - l: &Length, - ) { - let unit = l.unit.value.to_ascii_lowercase(); - match &mut self.other_lengths.get_mut(&unit) { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - if let Some(result) = CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value.value, - l.value.value, - None, - ) { - data.value.value = result; - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Length( - data.clone(), - ))), - ); - } else { - self.push(operator, operand); - } - } - None => { - let indexed_data: IndexedOperatorAndOperand = - self.new_indexed_data(operator, operand, l.clone()); - self.other_lengths.insert(unit, indexed_data); +// https://www.w3.org/TR/css-values-4/#calc-simplification - Step 6 +fn simplify_calc_operator_node_negate(calc_node: &CalcNode) -> CalcNode { + try_to_switch_sign_of_nodes(&[simplify_calc_node(calc_node)]).unwrap_or_else(|| { + CalcNode::OperatorNode(Box::new(CalcOperatorNode::Negate(calc_node.clone()))) + }) +} + +// https://www.w3.org/TR/css-values-4/#calc-simplification - Step 9 +fn simplify_calc_operator_node_product(nodes: &[CalcNode]) -> CalcNode { + let mut nodes = nodes.to_vec(); + + // For each of root’s children that are Product nodes, replace them with their + // children. + let mut idx = 0; + while idx < nodes.len() { + nodes[idx] = simplify_calc_node(&nodes[idx]); + if let Some(children) = get_children_if_node_is_product_operator(&nodes[idx]) { + nodes.remove(idx); + let mut i = idx; + for nested_node in children { + nodes.insert(i, nested_node); + i += 1; } + } else { + idx += 1; } } - fn sum_angle(&mut self, operator: Option<&CalcOperator>, operand: &CalcProduct, a: &Angle) { - match &mut self.angle { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - if let Some(result) = CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value.value, - a.value.value, - None, - ) { - data.value.value = result; - - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Angle( - data.clone(), - ))), - ); + // If root has multiple children that are numbers (not percentages or + // dimensions), remove them and replace them with a single number containing + // the product of the removed nodes. + let mut number: Option = None; + let mut idx = 0; + while idx < nodes.len() { + match &nodes[idx] { + CalcNode::Number(_) => { + if let Some(prev_idx) = number { + let previous_value = get_value(&nodes[prev_idx]); + let value = get_value(&nodes[idx]); + if let Some(result) = try_to_multiply_values(previous_value, value) { + set_value(&mut nodes[prev_idx], result); + nodes.remove(idx); + } else { + idx += 1; + } } else { - self.push(operator, operand); + number = Some(idx); + idx += 1; } } - None => self.angle = Some(self.new_indexed_data(operator, operand, a.clone())), + _ => { + idx += 1; + } } } - fn sum_duration(&mut self, operator: Option<&CalcOperator>, operand: &CalcProduct, d: &Time) { - match &mut self.duration { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - let prev_unit = data.unit.value.to_ascii_lowercase(); - let unit = d.unit.value.to_ascii_lowercase(); - if let Some(result) = get_duration_ratio(prev_unit, unit).and_then(|ratio| { - CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value.value, - d.value.value, - Some(ratio), - ) - }) { - data.value.value = result; - - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Time( - data.clone(), - ))), - ); - } else { - self.push(operator, operand); + // If root contains only two children, one of which is a number (not a + // percentage or dimension) and the other of which is a Sum whose children + // are all numeric values, multiply all of the Sum’s children by the number, + // then return the Sum. + if nodes.len() == 2 { + match (&nodes[0], &nodes[1]) { + (CalcNode::Number(Number { value, .. }), CalcNode::OperatorNode(op)) + | (CalcNode::OperatorNode(op), CalcNode::Number(Number { value, .. })) => { + if let CalcOperatorNode::Sum(children) = &**op { + if let Some(sum) = + try_to_multiply_all_numeric_sum_children_by_value(children, *value) + { + return sum; + } } } - None => self.duration = Some(self.new_indexed_data(operator, operand, d.clone())), + _ => {} } } - fn sum_frequency( - &mut self, - operator: Option<&CalcOperator>, - operand: &CalcProduct, - f: &Frequency, - ) { - match &mut self.frequency { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - let prev_unit = data.unit.value.to_ascii_lowercase(); - let unit = f.unit.value.to_ascii_lowercase(); - if let Some(result) = get_frequency_ratio(prev_unit, unit).and_then(|ratio| { - CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value.value, - f.value.value, - Some(ratio), - ) - }) { - data.value.value = result; - - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Frequency( - data.clone(), - ))), - ); - } else { - self.push(operator, operand); - } - } - None => self.frequency = Some(self.new_indexed_data(operator, operand, f.clone())), - } + // If root contains only numeric values and/or Invert nodes containing numeric + // values, and multiplying the types of all the children (noting that the + // type of an Invert node is the inverse of its child’s type) results in a + // type that matches any of the types that a math function can resolve to, + // return the result of multiplying all the values of the children + // (noting that the value of an Invert node is the reciprocal of its child’s + // value), expressed in the result’s canonical unit. + nodes = try_to_multiply_all_numeric_and_invert_nodes(&nodes); + + if nodes.len() == 1 { + nodes[0].clone() + } else { + CalcNode::OperatorNode(Box::new(CalcOperatorNode::Product(nodes.clone()))) } +} - fn sum_resolution( - &mut self, - operator: Option<&CalcOperator>, - operand: &CalcProduct, - r: &Resolution, - ) { - match &mut self.resolution { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - let prev_unit = data.unit.value.to_ascii_lowercase(); - let unit = r.unit.value.to_ascii_lowercase(); - if let Some(result) = get_resolution_ratio(prev_unit, unit).and_then(|ratio| { - CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value.value, - r.value.value, - Some(ratio), - ) - }) { - data.value.value = result; - - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Resolution( - data.clone(), - ))), - ); - } else { - self.push(operator, operand); - } +// https://www.w3.org/TR/css-values-4/#calc-simplification - Step 8 +fn simplify_calc_operator_node_sum(nodes: &[CalcNode]) -> CalcNode { + let mut nodes = nodes.to_vec(); + + // For each of root’s children that are Sum nodes, replace them with their + // children. + let mut idx = 0; + while idx < nodes.len() { + nodes[idx] = simplify_calc_node(&nodes[idx]); + if let Some(children) = get_children_if_node_is_sum_operator(&nodes[idx]) { + nodes.remove(idx); + let mut i = idx; + for nested_node in children { + nodes.insert(i, nested_node); + i += 1; } - None => self.resolution = Some(self.new_indexed_data(operator, operand, r.clone())), + } else { + idx += 1; } } - fn sum_flex(&mut self, operator: Option<&CalcOperator>, operand: &CalcProduct, f: &Flex) { - match &mut self.flex { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - if let Some(result) = CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value.value, - f.value.value, - None, - ) { - data.value.value = result; - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Dimension(Dimension::Flex( - data.clone(), - ))), - ); + // For each set of root’s children that are numeric values with identical units, + // remove those children and replace them with a single numeric value + // containing the sum of the removed nodes, and with the same unit. (E.g. + // combine numbers, combine percentages, combine px values, etc.) + let mut number: Option = None; + let mut percentage: Option = None; + let mut dimensions: HashMap = HashMap::new(); + let mut idx = 0; + while idx < nodes.len() { + match &nodes[idx] { + CalcNode::Number(_) => { + if let Some(prev_idx) = number { + if try_to_sum_nodes(&mut nodes, prev_idx, idx) { + nodes.remove(idx); + } else { + idx += 1; + } } else { - self.push(operator, operand); + number = Some(idx); + idx += 1; } } - None => self.flex = Some(self.new_indexed_data(operator, operand, f.clone())), - } - } - - fn sum_unknown_dimension( - &mut self, - operator: Option<&CalcOperator>, - operand: &CalcProduct, - u: &UnknownDimension, - ) { - let unit = u.unit.value.to_ascii_lowercase(); - match &mut self.unknown_dimension.get_mut(&unit) { - Some(IndexedOperatorAndOperand { - operator: prev_operator, - operand: IndexedData { pos, data }, - }) => { - if let Some(result) = CalcSumContext::try_to_sum_values( - prev_operator.as_ref(), - operator, - data.value.value, - u.value.value, - None, - ) { - data.value.value = result; - - CalcSumContext::switch_sign_if_needed( - &mut self.expressions, - &mut data.value.value, - prev_operator, - ); - CalcSumContext::update_calc_value( - &mut self.expressions, - *pos, - CalcValueOrOperator::Value(CalcValue::Dimension( - Dimension::UnknownDimension(data.clone()), - )), - ); + CalcNode::Dimension(d) => { + let unit = get_dimension_unit_lowercase(d); + match &dimensions.get(&unit) { + Some(prev_idx) => { + if try_to_sum_nodes(&mut nodes, **prev_idx, idx) { + nodes.remove(idx); + } else { + idx += 1; + } + } + None => { + dimensions.insert(unit, idx); + idx += 1; + } + } + } + CalcNode::Percentage(_) => { + if let Some(prev_idx) = percentage { + if try_to_sum_nodes(&mut nodes, prev_idx, idx) { + nodes.remove(idx); + } else { + idx += 1; + } } else { - self.push(operator, operand); + percentage = Some(idx); + idx += 1; } } - None => { - let indexed_data: IndexedOperatorAndOperand = - self.new_indexed_data(operator, operand, u.clone()); - self.unknown_dimension.insert(unit, indexed_data); + _ => { + idx += 1; } } } - fn new_indexed_data( - &mut self, - operator: Option<&CalcOperator>, - operand: &CalcProduct, - data: T, - ) -> IndexedOperatorAndOperand { - let mut indexed_operator = None; - let mut pos = self.expressions.len(); - - self.push(operator, operand); - - if let Some(op) = operator { - indexed_operator = Some(IndexedData { - pos, - data: op.clone(), - }); - pos += 1; - } - - IndexedOperatorAndOperand { - operator: indexed_operator, - operand: IndexedData { pos, data }, - } - } + // FIXME convert units (can we share some code with length.rs?) - fn update_calc_value( - expressions: &mut [CalcProductOrOperator], - index: usize, - calc_value: CalcValueOrOperator, - ) { - if let Some(elem) = expressions.get_mut(index) { - if let CalcProductOrOperator::Product(CalcProduct { span, .. }) = elem { - *elem = CalcProductOrOperator::Product(CalcProduct { - span: *span, - expressions: vec![calc_value], - }) - } - } + // If root has only a single child at this point, return the child. Otherwise, + // return root. + if nodes.len() == 1 { + nodes[0].clone() + } else { + CalcNode::OperatorNode(Box::new(CalcOperatorNode::Sum(nodes.clone()))) } +} - fn switch_sign_if_needed( - expressions: &mut [CalcProductOrOperator], - value: &mut f64, - operator: &mut Option>, - ) { - if value.is_sign_negative() { - if let Some(IndexedData { data, pos }) = operator { - *value = value.copysign(1.0); - data.value = if data.value == CalcOperatorType::Add { - CalcOperatorType::Sub - } else { - CalcOperatorType::Add - }; - - CalcSumContext::update_operator(expressions, *pos, data.clone()); - } - } +fn get_children_if_node_is_sum_operator(calc_node: &CalcNode) -> Option> { + match calc_node { + CalcNode::OperatorNode(op) => match &**op { + CalcOperatorNode::Sum(children) => Some(children.clone()), + _ => None, + }, + _ => None, } +} - fn update_operator( - expressions: &mut [CalcProductOrOperator], - index: usize, - operator: CalcOperator, - ) { - if let Some(elem) = expressions.get_mut(index) { - *elem = CalcProductOrOperator::Operator(operator); - } +fn get_children_if_node_is_product_operator(calc_node: &CalcNode) -> Option> { + match calc_node { + CalcNode::OperatorNode(op) => match &**op { + CalcOperatorNode::Product(children) => Some(children.clone()), + _ => None, + }, + _ => None, } +} - fn try_to_sum_values( - operator1: Option<&IndexedData>, - operator2: Option<&CalcOperator>, - n1: f64, - n2: f64, - ratio: Option, - ) -> Option { - let result = match (operator1, operator2) { - ( - None, - Some(CalcOperator { - value: op_value, .. - }), - ) => { - let sum = if *op_value == CalcOperatorType::Add { - SUM_OPERATION_ADD - } else { - SUM_OPERATION_SUBTRACT - }; - - sum(n1, n2, ratio) +// Recursive impl. of https://www.w3.org/TR/css-values-4/#calc-simplification - Step 6 +fn try_to_switch_sign_of_nodes(nodes: &[CalcNode]) -> Option { + let mut nodes = nodes.to_vec(); + let mut idx = 0; + while idx < nodes.len() { + let calc_node = &nodes[idx]; + nodes[idx] = match calc_node { + // If root’s child is a numeric value, return an equivalent numeric value, but with the + // value negated (0 - value). + CalcNode::Number(_) | CalcNode::Dimension(_) | CalcNode::Percentage(_) => { + let mut negated_node = calc_node.clone(); + set_value(&mut negated_node, -get_value(calc_node)); + negated_node } - ( - Some(op1), - Some(CalcOperator { - value: op2_value, .. - }), - ) => { - let sum = if op1.data.value == *op2_value { - SUM_OPERATION_ADD - } else { - SUM_OPERATION_SUBTRACT - }; - - sum(n1, n2, ratio) + CalcNode::OperatorNode(op) => { + match &**op { + // If root’s child is a Negate node, return the child’s child. + CalcOperatorNode::Negate(node) => node.clone(), + CalcOperatorNode::Sum(nodes) => { + // Impl. note: not in the spec, but we try to propagate the sign before + // continuing the simplification process + try_to_switch_sign_of_nodes(nodes)? + } + _ => return None, + } + } + _ => { + // Just wrap the constant (or function) + CalcNode::OperatorNode(Box::new(CalcOperatorNode::Negate(calc_node.clone()))) } - _ => unreachable!("The second operator is never None"), }; + idx += 1; + } - let precision1 = get_precision(n1); - let precision2 = get_precision(n2) + ratio.map_or(0, get_precision); - let result_precision = get_precision(result); - - if result_precision <= precision1.max(precision2) { - Some(result) - } else { - None - } + if nodes.len() == 1 { + Some(nodes[0].clone()) + } else { + Some(CalcNode::OperatorNode(Box::new(CalcOperatorNode::Sum( + nodes.to_vec(), + )))) } } -fn fold_calc_product(calc_product: &mut CalcProduct) { - let mut folded_expressions: Vec = vec![]; - - let mut prev_operand: Option = None; - let mut operator: Option = None; - let mut expr_it = calc_product.expressions.iter_mut(); - while let Some(calc_value) = expr_it.next() { - let cur_operand: Option = match calc_value { - CalcValueOrOperator::Value(CalcValue::Sum(calc_sum)) => { - CalcSumContext::default().fold(calc_sum); - let single_value = try_to_extract_into_calc_value(calc_sum); - if single_value.is_some() { - single_value +fn try_to_multiply_all_numeric_sum_children_by_value( + nodes: &[CalcNode], + value: f64, +) -> Option { + let mut operands = vec![]; + + for calc_node in nodes { + match calc_node { + CalcNode::Number(_) | CalcNode::Dimension(_) | CalcNode::Percentage(_) => { + let node_value = get_value(calc_node); + if let Some(result) = try_to_multiply_values(node_value, value) { + let mut node = calc_node.clone(); + set_value(&mut node, result); + operands.push(node); } else { - Some(CalcValue::Sum(CalcSum { - span: calc_sum.span, - expressions: calc_sum.expressions.to_vec(), - })) + return None; } } - CalcValueOrOperator::Value(calc_value) => Some(calc_value.clone()), - _ => None, - }; - - match (&prev_operand, &operator, &cur_operand) { - (None, None, _) => { - // First operand - prev_operand = cur_operand - } - ( - Some(operand1), - Some(CalcOperator { - value: CalcOperatorType::Mul, - span: op_span, - }), - Some(operand2), - ) => { - let result = try_to_multiply_calc_values(operand1, operand2); - if result.is_some() { - prev_operand = result; - } else { - folded_expressions.push(CalcValueOrOperator::Value(operand1.clone())); - folded_expressions.push(CalcValueOrOperator::Operator(CalcOperator { - span: *op_span, - value: CalcOperatorType::Mul, - })); - prev_operand = cur_operand - } - } - _ => { - // Something is wrong: we should iterate over some (operand, operator, operand) - // tuples - return; - } - } - - if let Some(CalcValueOrOperator::Operator(op)) = expr_it.next() { - operator = Some(CalcOperator { - span: op.span, - value: op.value, - }); - } else { - operator = None; + _ => return None, } } - if let Some(operand) = prev_operand { - folded_expressions.push(CalcValueOrOperator::Value(operand)); - } - - calc_product.expressions = folded_expressions; + Some(CalcNode::OperatorNode(Box::new(CalcOperatorNode::Sum( + operands, + )))) } -fn try_to_multiply_calc_values(value1: &CalcValue, value2: &CalcValue) -> Option { - match (value1, value2) { - (CalcValue::Number(n), value) | (value, CalcValue::Number(n)) => { - multiply_number_by_calc_value(n, value) +// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-multiply-two-types +// This implementation does not handle "multiplying the types of all the +// children" This function only handles numeric values and invert of numeric +// values +fn try_to_multiply_all_numeric_and_invert_nodes(children: &[CalcNode]) -> Vec { + let mut nodes = children.to_vec(); + + let mut numeric: Option = None; + let mut idx = 0; + while idx < nodes.len() { + match numeric { + None => { + match &nodes[idx] { + CalcNode::Number(_) | CalcNode::Dimension(_) | CalcNode::Percentage(_) => { + numeric = Some(idx); + } + CalcNode::OperatorNode(op) => { + if let CalcOperatorNode::Invert(CalcNode::Number(_)) = &**op { + numeric = Some(idx); + } + } + _ => {} + }; + idx += 1; + } + Some(prev_idx) => { + let prev_numeric_node = &nodes[prev_idx]; + let cur_numeric_node = &nodes[idx]; + match (prev_numeric_node, cur_numeric_node) { + // x * y => z + // x * y% => z% + // x * yPX => zPX + (CalcNode::Number(_), other_node @ CalcNode::Number(_)) + | (CalcNode::Number(_), other_node @ CalcNode::Percentage(_)) + | (other_node @ CalcNode::Percentage(_), CalcNode::Number(_)) + | (CalcNode::Number(_), other_node @ CalcNode::Dimension(_)) + | (other_node @ CalcNode::Dimension(_), CalcNode::Number(_)) => { + let prev_value = get_value(prev_numeric_node); + let value = get_value(cur_numeric_node); + if let Some(result) = try_to_multiply_values(prev_value, value) { + nodes[prev_idx] = other_node.clone(); + set_value(&mut nodes[prev_idx], result); + nodes.remove(idx); + } else { + idx += 1; + } + } + // 1/x * 1/y => 1/z + (CalcNode::OperatorNode(prev_op), CalcNode::OperatorNode(op)) => { + if let CalcOperatorNode::Invert(prev_numeric_node @ CalcNode::Number(_)) = + &**prev_op + { + if let CalcOperatorNode::Invert( + cur_numeric_node @ CalcNode::Number(_), + ) = &**op + { + let prev_value = get_value(prev_numeric_node); + let value = get_value(cur_numeric_node); + if let Some(result) = try_to_multiply_values(prev_value, value) { + let mut result_node = prev_numeric_node.clone(); + set_value(&mut result_node, result); + nodes[prev_idx] = CalcNode::OperatorNode(Box::new( + CalcOperatorNode::Invert(result_node), + )); + nodes.remove(idx); + } else { + idx += 1; + } + } else { + idx += 1; + } + } else { + idx += 1; + } + } + // 1/x * y => z + // 1/x * y% => z% + // 1/x * yPX => zPX + (numeric_node @ CalcNode::Number(_), CalcNode::OperatorNode(op)) + | (CalcNode::OperatorNode(op), numeric_node @ CalcNode::Number(_)) + | (numeric_node @ CalcNode::Dimension(_), CalcNode::OperatorNode(op)) + | (CalcNode::OperatorNode(op), numeric_node @ CalcNode::Dimension(_)) + | (numeric_node @ CalcNode::Percentage(_), CalcNode::OperatorNode(op)) + | (CalcNode::OperatorNode(op), numeric_node @ CalcNode::Percentage(_)) => { + if let CalcOperatorNode::Invert(inverted_node @ CalcNode::Number(_)) = &**op + { + let numerator = get_value(numeric_node); + let denominator = get_value(inverted_node); + if let Some(result) = try_to_divide_values(numerator, denominator) { + nodes[prev_idx] = numeric_node.clone(); + set_value(&mut nodes[prev_idx], result); + nodes.remove(idx); + } else { + idx += 1; + } + } else { + idx += 1; + } + } + (CalcNode::Percentage(_), CalcNode::Percentage(_)) + | (CalcNode::Percentage(_), CalcNode::Dimension(_)) + | (CalcNode::Dimension(_), CalcNode::Percentage(_)) + | (CalcNode::Dimension(_), CalcNode::Dimension(_)) => { + // Not handled by this impl, just skip it + idx += 1; + } + // Not a (inverted) numeric value + _ => idx += 1, + } + } } - _ => None, } + + nodes } fn try_to_multiply_values(v1: f64, v2: f64) -> Option { @@ -846,128 +585,391 @@ fn try_to_multiply_values(v1: f64, v2: f64) -> Option { } } -fn multiply_number_by_calc_value(n: &Number, value: &CalcValue) -> Option { - match value { - CalcValue::Number(n2) => try_to_multiply_values(n.value, n2.value).map(|result| { - CalcValue::Number(Number { - value: result, - span: n2.span, - raw: None, - }) +fn try_to_sum_nodes(nodes: &mut [CalcNode], prev_idx: usize, idx: usize) -> bool { + try_to_sum_nodes_with_ratio(nodes, prev_idx, idx, None) +} + +// FIXME remove "ratio"? (useful for the unit conversion?) +fn try_to_sum_nodes_with_ratio( + nodes: &mut [CalcNode], + prev_idx: usize, + idx: usize, + ratio: Option, +) -> bool { + let previous_value = get_value(&nodes[prev_idx]); + let value = get_value(&nodes[idx]); + if let Some(result) = try_to_sum_values(previous_value, value, ratio) { + set_value(&mut nodes[prev_idx], result); + true + } else { + false + } +} + +fn try_to_sum_values(n1: f64, n2: f64, ratio: Option) -> Option { + let result = match ratio { + None => n1 + n2, + Some(r) => n2.mul_add(r, n1), + }; + + let precision1 = get_precision(n1); + let precision2 = get_precision(n2) + ratio.map_or(0, get_precision); + let result_precision = get_precision(result); + + if result_precision <= precision1.max(precision2) { + Some(result) + } else { + None + } +} + +fn try_to_divide_values(numerator: f64, denominator: f64) -> Option { + let result = numerator / denominator; + let result_precision = get_precision(result); + + if result_precision <= f64::DIGITS { + Some(result) + } else { + None + } +} + +// Serialize our calculation tree into a "calc" AST +// https://www.w3.org/TR/css-values-4/#serialize-a-calculation-tree +// Impl. note: we cannot strictly follow the spec, we need to adapt it to our +// typed AST +fn serialize_calculation_node_into_calc_sum(calc_node: &CalcNode) -> CalcSum { + match calc_node { + CalcNode::Number(_) + | CalcNode::Dimension(_) + | CalcNode::Percentage(_) + | CalcNode::Constant(_) + | CalcNode::Function(_) => CalcSum { + expressions: vec![serialize_calc_node_into_calc_product(calc_node)], + span: Span::dummy_with_cmt(), + }, + CalcNode::OperatorNode(op) => match &**op { + CalcOperatorNode::Sum(nodes) => { + let mut expr: Vec = vec![]; + + let nodes = sort_calculations_children(nodes); + + let mut nodes_iter = nodes.iter(); + + if let Some(calc_node) = nodes_iter.next() { + expr.push(serialize_calc_node_into_calc_product(calc_node)); + } + + for calc_node in nodes_iter { + match calc_node { + CalcNode::Number(_) | CalcNode::Dimension(_) | CalcNode::Percentage(_) => { + let value = get_value(calc_node); + if value.is_sign_negative() { + // Instead of serializing "x + -10", we want to have "x - 10" + let mut node = calc_node.clone(); + set_value(&mut node, -value); + expr.push(CalcProductOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Sub, + span: Span::dummy_with_cmt(), + })); + expr.push(serialize_calc_node_into_calc_product(&node)); + } else { + expr.push(CalcProductOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Add, + span: Span::dummy_with_cmt(), + })); + expr.push(serialize_calc_node_into_calc_product(calc_node)); + } + } + CalcNode::Constant(_) | CalcNode::Function(_) => { + expr.push(CalcProductOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Add, + span: Span::dummy_with_cmt(), + })); + expr.push(serialize_calc_node_into_calc_product(calc_node)); + } + CalcNode::OperatorNode(op) => match &**op { + CalcOperatorNode::Product(_) => { + expr.push(CalcProductOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Add, + span: Span::dummy_with_cmt(), + })); + expr.push(serialize_calc_node_into_calc_product(calc_node)); + } + CalcOperatorNode::Negate(calc_node) => { + expr.push(CalcProductOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Sub, + span: Span::dummy_with_cmt(), + })); + expr.push(serialize_calc_node_into_calc_product(calc_node)); + } + _ => unreachable!("Cannot transform sum children into CalcProduct"), + }, + } + } + CalcSum { + expressions: expr, + span: Span::dummy_with_cmt(), + } + } + _ => CalcSum { + expressions: vec![serialize_calc_node_into_calc_product(calc_node)], + span: Span::dummy_with_cmt(), + }, + }, + } +} + +fn serialize_calc_node_into_calc_product(calc_node: &CalcNode) -> CalcProductOrOperator { + match calc_node { + CalcNode::Number(_) + | CalcNode::Dimension(_) + | CalcNode::Percentage(_) + | CalcNode::Constant(_) + | CalcNode::Function(_) => CalcProductOrOperator::Product(CalcProduct { + expressions: vec![CalcValueOrOperator::Value( + serialize_calc_node_into_calc_value(calc_node), + )], + span: Span::dummy_with_cmt(), }), - CalcValue::Dimension(Dimension::Length(l)) => { - try_to_multiply_values(n.value, l.value.value).map(|result| { - CalcValue::Dimension(Dimension::Length(Length { - span: l.span, - value: Number { - value: result, - span: l.value.span, - raw: None, - }, - unit: l.unit.clone(), - })) - }) - } - CalcValue::Dimension(Dimension::Angle(a)) => try_to_multiply_values(n.value, a.value.value) - .map(|result| { - CalcValue::Dimension(Dimension::Angle(Angle { - span: a.span, - value: Number { - value: result, - span: a.value.span, + CalcNode::OperatorNode(op) => match &**op { + CalcOperatorNode::Negate(_) => CalcProductOrOperator::Product(CalcProduct { + expressions: vec![ + CalcValueOrOperator::Value(CalcValue::Number(Number { + value: -1.0, + span: Span::dummy_with_cmt(), raw: None, - }, - unit: a.unit.clone(), - })) + })), + CalcValueOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Mul, + span: Span::dummy_with_cmt(), + }), + CalcValueOrOperator::Value(serialize_calc_node_into_calc_value(calc_node)), + ], + span: Span::dummy_with_cmt(), }), - CalcValue::Dimension(Dimension::Time(t)) => try_to_multiply_values(n.value, t.value.value) - .map(|result| { - CalcValue::Dimension(Dimension::Time(Time { - span: t.span, - value: Number { - value: result, - span: t.value.span, + CalcOperatorNode::Invert(_) => CalcProductOrOperator::Product(CalcProduct { + expressions: vec![ + CalcValueOrOperator::Value(CalcValue::Number(Number { + value: 1.0, + span: Span::dummy_with_cmt(), raw: None, - }, - unit: t.unit.clone(), - })) + })), + CalcValueOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Div, + span: Span::dummy_with_cmt(), + }), + CalcValueOrOperator::Value(serialize_calc_node_into_calc_value(calc_node)), + ], + span: Span::dummy_with_cmt(), }), - CalcValue::Dimension(Dimension::Frequency(f)) => { - try_to_multiply_values(n.value, f.value.value).map(|result| { - CalcValue::Dimension(Dimension::Frequency(Frequency { - span: f.span, - value: Number { - value: result, - span: f.value.span, - raw: None, - }, - unit: f.unit.clone(), - })) - }) - } - CalcValue::Dimension(Dimension::Resolution(r)) => { - try_to_multiply_values(n.value, r.value.value).map(|result| { - CalcValue::Dimension(Dimension::Resolution(Resolution { - span: r.span, - value: Number { - value: result, - span: r.value.span, - raw: None, - }, - unit: r.unit.clone(), - })) - }) - } - CalcValue::Dimension(Dimension::Flex(f)) => try_to_multiply_values(n.value, f.value.value) - .map(|result| { - CalcValue::Dimension(Dimension::Flex(Flex { - span: f.span, - value: Number { - value: result, - span: f.value.span, - raw: None, - }, - unit: f.unit.clone(), - })) + CalcOperatorNode::Product(nodes) => { + let mut expr: Vec = vec![]; + + let nodes = sort_calculations_children(nodes); + + let mut nodes_iter = nodes.iter(); + + if let Some(calc_node) = nodes_iter.next() { + expr.push(CalcValueOrOperator::Value( + serialize_calc_node_into_calc_value(calc_node), + )) + } + + for calc_node in nodes_iter { + match calc_node { + CalcNode::Number(_) + | CalcNode::Dimension(_) + | CalcNode::Percentage(_) + | CalcNode::Constant(_) + | CalcNode::Function(_) => { + expr.push(CalcValueOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Mul, + span: Span::dummy_with_cmt(), + })); + expr.push(CalcValueOrOperator::Value( + serialize_calc_node_into_calc_value(calc_node), + )); + } + CalcNode::OperatorNode(op) => match &**op { + CalcOperatorNode::Invert(calc_node) => { + expr.push(CalcValueOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Div, + span: Span::dummy_with_cmt(), + })); + expr.push(CalcValueOrOperator::Value( + serialize_calc_node_into_calc_value(calc_node), + )); + } + CalcOperatorNode::Product(_) | CalcOperatorNode::Sum(_) => { + expr.push(CalcValueOrOperator::Operator(CalcOperator { + value: CalcOperatorType::Mul, + span: Span::dummy_with_cmt(), + })); + expr.push(CalcValueOrOperator::Value( + serialize_calc_node_into_calc_value(calc_node), + )); + } + _ => unreachable!("Cannot transform product children into CalcProduct"), + }, + } + } + CalcProductOrOperator::Product(CalcProduct { + expressions: expr, + span: Span::dummy_with_cmt(), + }) + } + CalcOperatorNode::Sum(_) => CalcProductOrOperator::Product(CalcProduct { + expressions: vec![CalcValueOrOperator::Value( + serialize_calc_node_into_calc_value(calc_node), + )], + span: Span::dummy_with_cmt(), }), - CalcValue::Dimension(Dimension::UnknownDimension(u)) => { - try_to_multiply_values(n.value, u.value.value).map(|result| { - CalcValue::Dimension(Dimension::UnknownDimension(UnknownDimension { - span: u.span, - value: Number { - value: result, - span: u.value.span, - raw: None, - }, - unit: u.unit.clone(), - })) - }) - } - CalcValue::Percentage(p) => try_to_multiply_values(n.value, p.value.value).map(|result| { - CalcValue::Percentage(Percentage { - span: p.span, - value: Number { - value: result, - span: p.value.span, - raw: None, - }, - }) - }), - CalcValue::Constant(_) => { - // TODO handle some constants like "+Infinity", "-Infinity" and "NaN" - // see https://www.w3.org/TR/css-values-4/#calc-type-checking - None - } - _ => { - // This expression does not represent a simple value... let's do nothing - None - } + }, + } +} + +fn serialize_calc_node_into_calc_value(calc_node: &CalcNode) -> CalcValue { + match calc_node { + CalcNode::Number(n) => CalcValue::Number(n.clone()), + CalcNode::Dimension(d) => CalcValue::Dimension(d.clone()), + CalcNode::Percentage(p) => CalcValue::Percentage(p.clone()), + CalcNode::Constant(i) => CalcValue::Constant(i.clone()), + CalcNode::Function(f) => CalcValue::Function(f.clone()), + CalcNode::OperatorNode(op) => match &**op { + CalcOperatorNode::Sum(_) => { + CalcValue::Sum(serialize_calculation_node_into_calc_sum(calc_node)) + } + CalcOperatorNode::Product(_) => CalcValue::Sum(CalcSum { + expressions: vec![serialize_calc_node_into_calc_product(calc_node)], + span: Span::dummy_with_cmt(), + }), + _ => unreachable!("Cannot transform CalcNode::OperatorNode into CalcValue"), + }, + } +} + +// https://www.w3.org/TR/css-values-4/#sort-a-calculations-children +// Impl. note: since some computations cannot be done (because of a potential +// loss of precision), we need to keep all numbers, percentages, and so on +// (instead of only one) +fn sort_calculations_children(nodes: &[CalcNode]) -> Vec { + let mut ret: Vec = vec![]; + + // If nodes contains a number, remove it from nodes and append it to ret. + let mut numbers: Vec = nodes + .iter() + .filter(|n| match n { + CalcNode::Number(_) => true, + _ => false, + }) + .cloned() + .collect(); + ret.append(&mut numbers); + + // If nodes contains a percentage, remove it from nodes and append it to ret. + let mut percentages: Vec = nodes + .iter() + .filter(|n| match n { + CalcNode::Percentage(_) => true, + _ => false, + }) + .cloned() + .collect(); + ret.append(&mut percentages); + + // If nodes contains any dimensions, remove them from nodes, sort them by their + // units, ordered ASCII case-insensitively, and append them to ret. + let mut dimensions: Vec = nodes + .iter() + .filter(|n| match n { + CalcNode::Dimension(_) => true, + _ => false, + }) + .cloned() + .collect(); + dimensions.sort_by(|a, b| match (a, b) { + (CalcNode::Dimension(d1), CalcNode::Dimension(d2)) => { + let u1 = get_dimension_unit_lowercase(d1); + let u2 = get_dimension_unit_lowercase(d2); + u1.cmp(&u2) + } + _ => unreachable!("The vector should only contain dimensions"), + }); + ret.append(&mut dimensions); + + // If nodes still contains any items, append them to ret in the same order. + let mut any_items: Vec = nodes + .iter() + .filter(|n| match n { + CalcNode::Number(_) | CalcNode::Percentage(_) | CalcNode::Dimension(_) => false, + _ => true, + }) + .cloned() + .collect(); + ret.append(&mut any_items); + + ret +} + +fn get_calc_node_dimension_unit_lowercase(calc_node: &CalcNode) -> Atom { + match calc_node { + CalcNode::Dimension(d) => get_dimension_unit_lowercase(d), + _ => unreachable!("calc_node should be a dimension"), + } +} + +fn get_dimension_unit_lowercase(d: &Dimension) -> Atom { + match d { + Dimension::Length(l) => l.unit.value.to_ascii_lowercase(), + Dimension::Angle(a) => a.unit.value.to_ascii_lowercase(), + Dimension::Time(t) => t.unit.value.to_ascii_lowercase(), + Dimension::Frequency(f) => f.unit.value.to_ascii_lowercase(), + Dimension::Resolution(r) => r.unit.value.to_ascii_lowercase(), + Dimension::Flex(f) => f.unit.value.to_ascii_lowercase(), + Dimension::UnknownDimension(u) => u.unit.value.to_ascii_lowercase(), + } +} + +fn get_value(calc_node: &CalcNode) -> f64 { + match calc_node { + CalcNode::Number(n) => n.value, + CalcNode::Percentage(p) => p.value.value, + CalcNode::Dimension(Dimension::Length(l)) => l.value.value, + CalcNode::Dimension(Dimension::Angle(a)) => a.value.value, + CalcNode::Dimension(Dimension::Time(t)) => t.value.value, + CalcNode::Dimension(Dimension::Frequency(f)) => f.value.value, + CalcNode::Dimension(Dimension::Resolution(r)) => r.value.value, + CalcNode::Dimension(Dimension::Flex(f)) => f.value.value, + CalcNode::Dimension(Dimension::UnknownDimension(u)) => u.value.value, + _ => unreachable!("Can only get a value from a value node"), + } +} + +fn set_value(calc_node: &mut CalcNode, value: f64) { + match calc_node { + CalcNode::Number(n) => n.value = value, + CalcNode::Percentage(p) => p.value.value = value, + CalcNode::Dimension(Dimension::Length(l)) => l.value.value = value, + CalcNode::Dimension(Dimension::Angle(a)) => a.value.value = value, + CalcNode::Dimension(Dimension::Time(t)) => t.value.value = value, + CalcNode::Dimension(Dimension::Frequency(f)) => f.value.value = value, + CalcNode::Dimension(Dimension::Resolution(r)) => r.value.value = value, + CalcNode::Dimension(Dimension::Flex(f)) => f.value.value = value, + CalcNode::Dimension(Dimension::UnknownDimension(u)) => u.value.value = value, + _ => unreachable!("Can only set a value into a value node"), } } impl Compressor { + // https://www.w3.org/TR/css-values-4/#calculation-tree pub(super) fn compress_calc_sum(&mut self, calc_sum: &mut CalcSum) { - CalcSumContext::default().fold(calc_sum); + let root_calc_node = collect_calc_sum_into_calc_node(calc_sum); + + let simplified_calc_tree = simplify_calc_node(&root_calc_node); + + let simplified_calc_sum = serialize_calculation_node_into_calc_sum(&simplified_calc_tree); + calc_sum.expressions = simplified_calc_sum.expressions; } pub(super) fn compress_calc_sum_in_component_value( diff --git a/crates/swc_css_minifier/tests/fixture.rs b/crates/swc_css_minifier/tests/fixture.rs index a649a7a726a4..1825c853273e 100644 --- a/crates/swc_css_minifier/tests/fixture.rs +++ b/crates/swc_css_minifier/tests/fixture.rs @@ -9,7 +9,8 @@ use swc_css_minifier::minify; use swc_css_parser::parse_file; use testing::NormalizedOutput; -#[testing::fixture("tests/fixture/**/input.css")] +// FIXME path +#[testing::fixture("tests/fixture/compress-calc/**/input.css")] fn minify_fixtures(input: PathBuf) { let dir = input.parent().unwrap(); let output = dir.join(format!( diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/add-sub/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/add-sub/output.min.css index 8ca39dda4edc..24f4c60b8fc1 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/add-sub/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/add-sub/output.min.css @@ -1 +1 @@ -.class1{width:calc(100% + 10px)}.class2{width:calc(100% - 10px)}.class3{width:calc(1px - 2em - 3%)}.class4{width:calc((100vw - 50em)/2)}.class5{width:calc(10px - (100vw - 50em)/2)}.class6{width:calc(1px - 2em - 4vh - 3%)}.class7{width:calc(-24px + (var(--a) - var(--b))/2 - var(--c))}.class8{width:calc(5px + 2em + 3vh)}.class9{width:calc(1px - (2em + 4px - 6vh)/2)} +.class1{width:calc(100% + 10px)}.class2{width:calc(100% - 10px)}.class3{width:calc(-3% - 2em + 1px)}.class4{width:calc(-25em + 50vw)}.class5{width:calc(25em + 10px - 50vw)}.class6{width:calc(-3% - 2em + 1px - 4vh)}.class7{width:calc(-24px + .5*(var(--a) - var(--b)) - var(--c))}.class8{width:calc(2em + 5px + 3vh)}.class9{width:calc(-1em - 1px + 3vh)} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/container-condition/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/container-condition/output.min.css index 4fd480e392b3..dc9d9b1ec942 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/container-condition/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/container-condition/output.min.css @@ -1 +1 @@ -@container(inline-size>=200px){h2{font-size:calc(1.2em + 1cqi)}} +@container(inline-size>=200px){h2{font-size:calc(1cqi + 1.2em)}} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/convert-units/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/convert-units/output.min.css index 512423df18e0..af1088f6e122 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/convert-units/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/convert-units/output.min.css @@ -1 +1 @@ -.class1{width:calc(1cm + 1px)}.class2{width:calc(1px + 1cm)}.class3{width:20q}.class4{width:calc(100.9q + 10px)}.class5{width:calc(10px + 100.9q)}.class6{width:calc(10cm + 1px)}.class7{width:calc(1cm + 1px)}.class8{width:calc(10px + 1q)}.class9{width:10.025cm}.class10{width:1.025cm}.class11{width:calc(10in + 1q)}.class12{width:calc(10pt + 1q)}.class13{width:calc(10pc + 1q)}.class14{width:calc(1q + 10px)}.class15{width:401q}.class16{width:41q}.class17{width:1017q}.class18{width:calc(1q + 10pt)}.class19{width:calc(1q + 10pc)}.class20{width:3unknown}.class21{width:calc(1unknown + 2px)}.class22{width:calc(1px + 2unknown)} +.class1,.class2{width:calc(1cm + 1px)}.class3{width:20q}.class4,.class5{width:calc(10px + 100.9q)}.class6{width:calc(10cm + 1px)}.class7{width:calc(1cm + 1px)}.class8{width:calc(10px + 1q)}.class9{width:calc(10cm + 1q)}.class10{width:calc(1cm + 1q)}.class11{width:calc(10in + 1q)}.class12{width:calc(10pt + 1q)}.class13{width:calc(10pc + 1q)}.class14{width:calc(10px + 1q)}.class15{width:calc(10cm + 1q)}.class16{width:calc(1cm + 1q)}.class17{width:calc(10in + 1q)}.class18{width:calc(10pt + 1q)}.class19{width:calc(10pc + 1q)}.class20{width:3unknown}.class21{width:calc(2px + 1unknown)}.class22{width:calc(1px + 2unknown)} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/css-variables/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/css-variables/output.min.css index 0db574eda58c..ff40bb5cbb5a 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/css-variables/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/css-variables/output.min.css @@ -1 +1 @@ -.class1{width:calc(var(--mouseX)*1px)}.class2{width:calc(10px - 100px*var(--mouseX))}.class3{width:calc(-90px - var(--mouseX))}.class4{width:calc(10px - 100px/var(--mouseX))}.class5{width:calc(-90px + var(--mouseX))}.class6{width:calc(var(--popupHeight)/2)}.class7{width:calc(var(--popupHeight)/2 + var(--popupWidth)/2)}.class8{--foo:calc(var(--bar) / 8)}.class9{transform:translatey(calc(-100% - var(--tooltip-calculated-offset)))}.class10{width:calc(var(--xxx,var(--yyy))/2)}.class11,.class12{width:var(--foo)}.class13{width:calc(var(--foo) + 10px)}.class14{width:calc(var(--foo) + var(--bar))}.class15,.class16{width:calc(var(--foo) + var(--bar) + var(--baz))}.class17{width:calc(var(--foo) - var(--bar) - var(--baz))}.class18{width:calc(var(--foo) - var(--bar) + var(--baz))}.class19,.class20{width:calc(var(--foo) + var(--bar) - var(--baz))}.class21{width:calc(var(--foo) - var(--bar) - var(--baz))}.class22{width:calc(calc(var(--foo) + var(--bar))*var(--baz))}.class23{width:calc(var(--foo)*calc(var(--bar) + var(--baz)))}.class24{width:calc(calc(var(--foo) + var(--bar))/var(--baz))}.class25{width:calc(var(--foo)/calc(var(--bar) + var(--baz)))} +.class1{width:calc(1px*var(--mouseX))}.class2{width:calc(10px - 100px*var(--mouseX))}.class3{width:calc(-90px - var(--mouseX))}.class4{width:calc(10px - 100px/var(--mouseX))}.class5{width:calc(-90px + var(--mouseX))}.class6{width:calc(.5*var(--popupHeight))}.class7{width:calc(.5*var(--popupHeight) + .5*var(--popupWidth))}.class8{--foo:calc(var(--bar) / 8)}.class9{transform:translatey(calc(-100% - var(--tooltip-calculated-offset)))}.class10{width:calc(.5*var(--xxx,var(--yyy)))}.class11,.class12{width:var(--foo)}.class13{width:calc(10px + var(--foo))}.class14{width:calc(var(--foo) + var(--bar))}.class15,.class16{width:calc(var(--foo) + var(--bar) + var(--baz))}.class17{width:calc(var(--foo) - var(--bar) - var(--baz))}.class18{width:calc(var(--foo) - var(--bar) + var(--baz))}.class19,.class20{width:calc(var(--foo) + var(--bar) - var(--baz))}.class21{width:calc(var(--foo) - var(--bar) - var(--baz))}.class22{width:calc((var(--foo) + var(--bar))*var(--baz))}.class23{width:calc(var(--foo)*(var(--bar) + var(--baz)))}.class24{width:calc((var(--foo) + var(--bar))/var(--baz))}.class25{width:calc(var(--foo)/(var(--bar) + var(--baz)))} 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..7a7f9a45979b 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(-6px + 50vw)}.class2{width:500px}.class3{width:calc(0 - 10px)}.class4{width:calc(0 - 1em - 1px)}.class5{width:calc(0 + 5px - 50vw)}.class6{width:calc(10px - 100vw)}.class7{width:calc(20px - 200vw)}.class8{width:calc(-10px - 100vw)}.class9{width:calc(0px - .5*var(--foo,4px))} 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..226e1146fd5a 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(.25*(4px + var(--a)))}.class2{width:calc(4px + .25*(4px + var(--a)))}.class3{width:calc(100%/var(--aspect-ratio))}.class4{width:calc((var(--fluid-screen) - .0625rem*var(--fluid-min-width))/(.0625*var(--fluid-max-width) - .0625*var(--fluid-min-width)))}.class5{width:calc(1/(10/var(--dot-size)))}.class6{width:calc(1/(.0625*(var(--a) - var(--b))))} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/multiply/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/multiply/output.min.css index 44050c32e4a9..04b5c984fce2 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/multiply/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/multiply/output.min.css @@ -1 +1 @@ -.class1{width:calc(((var(--a) + 4px)*2)*2)}.class2{width:calc(((var(--a) + 4px)*2)*2 + 4px)} +.class1{width:calc(4*(4px + var(--a)))}.class2{width:calc(4px + 4*(4px + var(--a)))} 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..6e9f88de253b 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:2px}.class3{width:calc(0em - 5px)}.class4{width:calc(0px + 2.25rem)}.class5{width:calc(-100px - 15rem + 100vh)}.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..9479ea37daf3 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:.01}.class2{width:calc(5*1e-6)}.class3,.class4{width:100%} 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..5856d12a50d8 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:1ex}.class6{width:60px}.class7{width:calc(100% + 0px)}.class8{width:calc(-100% + 200px)}.class9{width:2px}.class10{width:2px}.class11{width:3rem}.class12{width:calc(100% + 1px)}.class13{width:calc(-.14285em + 2rem)}@supports(width:calc(100% - constant(safe-area-inset-left))){.class14{width:calc(100% - constant(safe-area-inset-left))}}.class15{width:calc(100% - 75.37% + 1163.5px)}.class16{width:calc(66.33939233116625% + 141.9291495289903px - 50vw)}.class17{width:calc(75.37% - 763.5px)}.class18{width:calc(-10% + 1163.5px)}.class19{width:calc(0em - 5px)}.class20{width:200px}.class21{width:0}.class22,.class23{width:200px}.class24{width:-200px}.class25{width:0}.class26,.class27{width:-200px}.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..c8d864e3de95 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(1 + 1px)}.class2{width:calc(100% - 180px)}.class3{width:calc(100% - 30px)}.class4{width:calc(-50ms + 1s)}.class5{width:calc(99.99% - 0rem)}.class6{width:calc(55% + 5em)}.class7{width:calc(0em + 50px)}.class8{width:0} diff --git a/crates/swc_css_minifier/tests/fixture/compress-calc/vendor/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-calc/vendor/output.min.css index 095b5454e1db..ee7b2442067f 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-calc/vendor/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-calc/vendor/output.min.css @@ -1 +1 @@ -.class1,.class2{width:2px}.class3{width:-webkit-calc(50% - 25px)}.class4{width:-webkit-calc(1px + 2px/2)}.class5,.class6{width:2px} +.class1,.class2{width:2px}.class3{width:-webkit-calc(50% - 25px)}.class4,.class5,.class6{width:2px} diff --git a/crates/swc_html_parser/tests/html5lib-tests b/crates/swc_html_parser/tests/html5lib-tests index dd0d8157f15e..038c06635ae5 160000 --- a/crates/swc_html_parser/tests/html5lib-tests +++ b/crates/swc_html_parser/tests/html5lib-tests @@ -1 +1 @@ -Subproject commit dd0d8157f15ebf35655cc0c8df2d476cda3ceba2 +Subproject commit 038c06635ae54f700fee3154acb4d45fb3dcae8d