Skip to main content

stylex_css/css/normalizers/
whitespace_normalizer.rs

1use stylex_regex::regex::{
2  CSS_RULE_REGEX, CSS_URL_REGEX, HASH_WHITESPACE_NORMALIZER_REGEX,
3  WHITESPACE_BRACKET_NORMALIZER_REGEX, WHITESPACE_FUNC_NORMALIZER_REGEX,
4  WHITESPACE_NORMALIZER_EXTRA_SPACES_REGEX, WHITESPACE_NORMALIZER_MATH_SIGNS_REGEX,
5};
6
7pub fn whitespace_normalizer(content: String) -> String {
8  // Early return: If content is a URL, return it as-is
9  if let Ok(Some(captures)) = CSS_URL_REGEX.captures(&content)
10    && let Some(url) = captures.get(0)
11  {
12    return url.as_str().to_string();
13  }
14
15  // Extract CSS value from rule if present (e.g., "color: red;" -> "red")
16  let mut css = if content.contains('{') {
17    CSS_RULE_REGEX
18      .captures(&content)
19      .ok()
20      .flatten()
21      .and_then(|c| c.get(1))
22      .map(|m| m.as_str().to_string())
23      .unwrap_or(content)
24  } else {
25    content
26  };
27
28  // Normalize whitespace around math operators (+, -, *, /, %)
29  css = WHITESPACE_NORMALIZER_MATH_SIGNS_REGEX
30    .replace_all(&css, |caps: &fancy_regex::Captures| {
31      // Using named groups for better readability
32      let left = caps.name("left").map(|m| m.as_str()).unwrap_or("");
33      let op = caps.name("op").map(|m| m.as_str()).unwrap_or("");
34      let right_space = caps.name("rspace").map(|m| m.as_str()).unwrap_or("");
35      let right = caps.name("right").map(|m| m.as_str()).unwrap_or("");
36
37      match op {
38        // Percent: attach to left, space before right (e.g., "50% 10" not "50 % 10")
39        "%" => format!("{}{} {}", left, op, right),
40        // Minus: preserve negative numbers (e.g., "5 -3" vs "5- 3")
41        "-" => {
42          if right_space.is_empty() {
43            format!("{} {}{}", left, op, right)
44          } else {
45            format!("{} {} {}", left, op, right)
46          }
47        },
48        // Other operators: always add space around
49        _ => format!("{} {} {}", left, op, right),
50      }
51    })
52    .to_string();
53
54  // Normalize brackets and quotes: ")a" -> ") a", """" -> ""
55  css = WHITESPACE_BRACKET_NORMALIZER_REGEX
56    .replace_all(&css, "$1$3 $2$4")
57    .to_string();
58
59  // Remove extra spaces in specific cases (empty quotes, multiple parens)
60  css = WHITESPACE_NORMALIZER_EXTRA_SPACES_REGEX
61    .replace_all(&css, |caps: &fancy_regex::Captures| {
62      // Match empty string quotes: "" -> ""
63      if let (Some(q1), Some(q2)) = (caps.get(1), caps.get(2)) {
64        format!("{}{}", q1.as_str(), q2.as_str())
65      }
66      // Match multiple closing parens: ") )" -> "))"
67      else if let (Some(p1), Some(p2)) = (caps.get(3), caps.get(4)) {
68        format!("{}{}", p1.as_str(), p2.as_str())
69      } else {
70        caps[0].to_string()
71      }
72    })
73    .to_string();
74
75  // Add space before hash in colors: "a#fff" -> "a #fff"
76  css = HASH_WHITESPACE_NORMALIZER_REGEX
77    .replace_all(&css, "$1 #")
78    .to_string();
79
80  // Normalize function arguments: "( arg )" -> "(arg)"
81  css = WHITESPACE_FUNC_NORMALIZER_REGEX
82    .replace_all(&css, "($1),")
83    .to_string();
84
85  css.trim().to_string()
86}