swc_css_minifier/compressor/
selector.rs1use 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 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 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 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 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 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 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}