swc_css_minifier/compressor/
selector.rs

1use swc_atoms::atom;
2use swc_common::DUMMY_SP;
3use swc_css_ast::*;
4
5use super::Compressor;
6use crate::util::dedup;
7
8impl Compressor {
9    pub(super) fn compress_selector_list(&mut self, selector_list: &mut SelectorList) {
10        dedup(&mut selector_list.children);
11    }
12
13    pub(super) fn compress_forgiving_selector_list(
14        &mut self,
15        forgiving_selector_list: &mut ForgivingSelectorList,
16    ) {
17        dedup(&mut forgiving_selector_list.children);
18    }
19
20    pub(super) fn compress_relative_selector_list(
21        &mut self,
22        relative_selector_list: &mut RelativeSelectorList,
23    ) {
24        dedup(&mut relative_selector_list.children);
25    }
26
27    pub(super) fn compress_forgiving_relative_selector_list(
28        &mut self,
29        forgiving_relative_selector_list: &mut ForgivingRelativeSelectorList,
30    ) {
31        dedup(&mut forgiving_relative_selector_list.children);
32    }
33
34    pub(super) fn compress_an_plus_b(&mut self, an_plus_b: &mut AnPlusB) {
35        match &an_plus_b {
36            // `2n+1`, `2n-1`, `2n-3`, etc => `odd`
37            AnPlusB::AnPlusBNotation(AnPlusBNotation {
38                a: Some(a),
39                b: Some(b),
40                span,
41                ..
42            }) if *a == 2 && (*b == 1 || b % 2 == -1) => {
43                *an_plus_b = AnPlusB::Ident(Ident {
44                    span: *span,
45                    value: atom!("odd"),
46                    raw: None,
47                });
48            }
49            // `2n-0`, `2n-2`, `2n-4`, etc => `2n`
50            AnPlusB::AnPlusBNotation(AnPlusBNotation {
51                a: Some(a),
52                b: Some(b),
53                span,
54                ..
55            }) if *a == 2 && *b < 0 && b % 2 == 0 => {
56                *an_plus_b = AnPlusB::AnPlusBNotation(AnPlusBNotation {
57                    span: *span,
58                    a: Some(2),
59                    a_raw: None,
60                    b: None,
61                    b_raw: None,
62                });
63            }
64            // `even` => `2n`
65            AnPlusB::Ident(Ident { value, span, .. }) if *value == "even" => {
66                *an_plus_b = AnPlusB::AnPlusBNotation(AnPlusBNotation {
67                    span: *span,
68                    a: Some(2),
69                    a_raw: None,
70                    b: None,
71                    b_raw: None,
72                });
73            }
74            // `0n+5` => `5`, `0n-5` => `-5`, etc
75            AnPlusB::AnPlusBNotation(AnPlusBNotation {
76                a: Some(a),
77                b,
78                span,
79                ..
80            }) if *a == 0 => {
81                *an_plus_b = AnPlusB::AnPlusBNotation(AnPlusBNotation {
82                    span: *span,
83                    a: None,
84                    a_raw: None,
85                    b: *b,
86                    b_raw: None,
87                });
88            }
89            // `-5n+0` => `-5n`, etc
90            AnPlusB::AnPlusBNotation(AnPlusBNotation {
91                a,
92                b: Some(b),
93                span,
94                ..
95            }) if *b == 0 => {
96                *an_plus_b = AnPlusB::AnPlusBNotation(AnPlusBNotation {
97                    span: *span,
98                    a: *a,
99                    a_raw: None,
100                    b: None,
101                    b_raw: None,
102                });
103            }
104            _ => {}
105        }
106    }
107
108    pub(super) fn compress_subclass_selector(&mut self, subclass_selector: &mut SubclassSelector) {
109        match &subclass_selector {
110            SubclassSelector::PseudoElement(PseudoElementSelector { name, span, .. }) => {
111                if matches!(
112                    &*name.value,
113                    "before" | "after" | "first-letter" | "first-line"
114                ) {
115                    *subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
116                        span: *span,
117                        name: name.clone(),
118                        children: None,
119                    })
120                }
121            }
122            SubclassSelector::PseudoClass(PseudoClassSelector {
123                name,
124                children: Some(children),
125                span,
126                ..
127            }) if name.value == "nth-child" && children.len() == 1 => match children.first() {
128                Some(PseudoClassSelectorChildren::AnPlusB(AnPlusB::AnPlusBNotation(
129                    AnPlusBNotation {
130                        a: None,
131                        b: Some(b),
132                        ..
133                    },
134                ))) if *b == 1 => {
135                    *subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
136                        span: *span,
137                        name: Ident {
138                            span: DUMMY_SP,
139                            value: atom!("first-child"),
140                            raw: None,
141                        },
142                        children: None,
143                    })
144                }
145                _ => {}
146            },
147            SubclassSelector::PseudoClass(PseudoClassSelector {
148                name,
149                children: Some(children),
150                span,
151                ..
152            }) if name.value == "nth-last-child" && children.len() == 1 => match children.first() {
153                Some(PseudoClassSelectorChildren::AnPlusB(AnPlusB::AnPlusBNotation(
154                    AnPlusBNotation {
155                        a: None,
156                        b: Some(b),
157                        ..
158                    },
159                ))) if *b == 1 => {
160                    *subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
161                        span: *span,
162                        name: Ident {
163                            span: DUMMY_SP,
164                            value: atom!("last-child"),
165                            raw: None,
166                        },
167                        children: None,
168                    })
169                }
170                _ => {}
171            },
172            SubclassSelector::PseudoClass(PseudoClassSelector {
173                name,
174                children: Some(children),
175                span,
176                ..
177            }) if name.value == "nth-of-type" && children.len() == 1 => match children.first() {
178                Some(PseudoClassSelectorChildren::AnPlusB(AnPlusB::AnPlusBNotation(
179                    AnPlusBNotation {
180                        a: None,
181                        b: Some(b),
182                        ..
183                    },
184                ))) if *b == 1 => {
185                    *subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
186                        span: *span,
187                        name: Ident {
188                            span: DUMMY_SP,
189                            value: atom!("first-of-type"),
190                            raw: None,
191                        },
192                        children: None,
193                    })
194                }
195                _ => {}
196            },
197            SubclassSelector::PseudoClass(PseudoClassSelector {
198                name,
199                children: Some(children),
200                span,
201                ..
202            }) if name.value == "nth-last-of-type" && children.len() == 1 => {
203                match children.first() {
204                    Some(PseudoClassSelectorChildren::AnPlusB(AnPlusB::AnPlusBNotation(
205                        AnPlusBNotation {
206                            a: None,
207                            b: Some(b),
208                            ..
209                        },
210                    ))) if *b == 1 => {
211                        *subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
212                            span: *span,
213                            name: Ident {
214                                span: DUMMY_SP,
215                                value: atom!("last-of-type"),
216                                raw: None,
217                            },
218                            children: None,
219                        })
220                    }
221                    _ => {}
222                }
223            }
224            _ => {}
225        }
226    }
227
228    pub(super) fn compress_compound_selector(&mut self, compound_selector: &mut CompoundSelector) {
229        if self.ctx.in_logic_combinator_selector {
230            return;
231        }
232
233        if !compound_selector.subclass_selectors.is_empty() {
234            if let Some(TypeSelector::Universal(UniversalSelector { prefix: None, .. })) =
235                compound_selector.type_selector.as_deref()
236            {
237                compound_selector.type_selector = None;
238            }
239        }
240    }
241
242    pub(super) fn compress_attribute_selector(
243        &mut self,
244        attribute_selector: &mut AttributeSelector,
245    ) {
246        if let Some(AttributeSelectorValue::Str(Str { value, span, .. })) =
247            &attribute_selector.value
248        {
249            // A valid unquoted attribute value in CSS is any string of text that is not the
250            // empty string, is not just a hyphen (-), consists of escaped characters and/or
251            // characters matching [-_a-zA-Z0-9\u00A0-\u10FFFF] entirely, and doesn’t start
252            // with a digit or a hyphen followed by a digit.
253
254            // is any string of text that is not the empty string, is not just a hyphen (-)
255            if value.is_empty() || value == "-" {
256                return;
257            }
258
259            let chars = value.chars();
260            let mut starts_with_hyphen = false;
261
262            for (idx, c) in chars.enumerate() {
263                match c {
264                    '0'..='9' if idx == 0 || (starts_with_hyphen && idx == 1) => {
265                        return;
266                    }
267                    '-' => {
268                        if idx == 0 {
269                            starts_with_hyphen = true;
270                        }
271                    }
272                    _ if !matches!(c, '-' | '_' | 'a'..='z' | 'A'..='Z' | '0'..='9' | '\u{00a0}'..='\u{10FFFF}') =>
273                    {
274                        return;
275                    }
276                    _ => {}
277                }
278            }
279
280            attribute_selector.value = Some(AttributeSelectorValue::Ident(Ident {
281                span: *span,
282                value: value.clone(),
283                raw: None,
284            }));
285        }
286    }
287}