swc_css_compat/compiler/
custom_media.rs1use std::mem::take;
2
3use swc_common::{util::take::Take, DUMMY_SP};
4use swc_css_ast::{
5 AtRule, AtRuleName, AtRulePrelude, CustomMediaQuery, CustomMediaQueryMediaType, Ident,
6 MediaCondition, MediaConditionAllType, MediaConditionType, MediaConditionWithoutOr,
7 MediaConditionWithoutOrType, MediaFeatureBoolean, MediaFeatureName, MediaInParens, MediaOr,
8 MediaQuery, MediaType, Rule,
9};
10
11#[derive(Debug, Default)]
12pub(super) struct CustomMediaHandler {
13 modifier_and_media_type: Option<(Option<Ident>, Option<MediaType>)>,
14 medias: Vec<CustomMediaQuery>,
15}
16
17impl CustomMediaHandler {
18 pub(crate) fn store_custom_media(&mut self, n: &mut AtRule) {
19 if let AtRuleName::Ident(..) = &n.name {
20 if let Some(AtRulePrelude::CustomMediaPrelude(prelude)) = &mut n.prelude.as_deref_mut()
21 {
22 self.medias.push(prelude.take());
23 }
24 }
25 }
26
27 pub(crate) fn process_rules(&mut self, n: &mut Vec<Rule>) {
28 n.retain(|n| match n {
29 Rule::AtRule(n) => {
30 if matches!(
31 n.prelude.as_deref(),
32 Some(AtRulePrelude::CustomMediaPrelude(..))
33 ) {
34 return false;
35 }
36
37 true
38 }
39 _ => true,
40 });
41 }
42
43 pub(crate) fn process_media_query(&mut self, n: &mut MediaQuery) {
44 if let Some((modifier, media_type)) = self.modifier_and_media_type.take() {
48 n.modifier = modifier;
49 n.media_type = media_type;
50
51 if let Some(condition) = &mut n.condition {
52 match &mut **condition {
53 MediaConditionType::WithoutOr(condition) => {
54 if condition.conditions.is_empty() {
55 n.condition = None;
56 }
57 }
58 MediaConditionType::All(condition) => {
59 if condition.conditions.is_empty() {
60 n.condition = None;
61 }
62 }
63 }
64 }
65 }
66
67 self.modifier_and_media_type = None;
68 }
69
70 pub(crate) fn process_media_condition(&mut self, media_condition: &mut MediaCondition) {
71 let mut remove_rules_list = Vec::new();
72
73 for (i, node) in media_condition.conditions.iter_mut().enumerate() {
74 match node {
75 MediaConditionAllType::Not(media_not) => {
76 if let Some(new_media_in_parens) =
77 self.process_media_in_parens(&media_not.condition)
78 {
79 if self.is_empty_media_parens(&new_media_in_parens) {
80 remove_rules_list.push(i);
81 } else {
82 media_not.condition = new_media_in_parens;
83 }
84 }
85 }
86 MediaConditionAllType::And(media_and) => {
87 if let Some(new_media_in_parens) =
88 self.process_media_in_parens(&media_and.condition)
89 {
90 if self.is_empty_media_parens(&new_media_in_parens) {
91 remove_rules_list.push(i);
92 } else {
93 media_and.condition = new_media_in_parens;
94 }
95 }
96 }
97 MediaConditionAllType::Or(media_or) => {
98 if let Some(new_media_in_parens) =
99 self.process_media_in_parens(&media_or.condition)
100 {
101 if self.is_empty_media_parens(&new_media_in_parens) {
102 remove_rules_list.push(i);
103 } else {
104 media_or.condition = new_media_in_parens;
105 }
106 }
107 }
108 MediaConditionAllType::MediaInParens(media_in_parens) => {
109 if let Some(new_media_in_parens) = self.process_media_in_parens(media_in_parens)
110 {
111 if self.is_empty_media_parens(&new_media_in_parens) {
112 remove_rules_list.push(i);
113 } else {
114 *media_in_parens = new_media_in_parens;
115 }
116 }
117 }
118 }
119 }
120
121 if !remove_rules_list.is_empty() {
122 let mut need_change_next = false;
123
124 media_condition.conditions = take(&mut media_condition.conditions)
125 .into_iter()
126 .enumerate()
127 .filter_map(|(idx, value)| {
128 if remove_rules_list.contains(&idx) {
129 if idx == 0 {
130 need_change_next = true;
131 }
132
133 None
134 } else if need_change_next {
135 need_change_next = false;
136
137 match value {
138 MediaConditionAllType::And(media_and) => {
139 Some(MediaConditionAllType::MediaInParens(media_and.condition))
140 }
141 MediaConditionAllType::Or(media_and) => {
142 Some(MediaConditionAllType::MediaInParens(media_and.condition))
143 }
144 _ => Some(value),
145 }
146 } else {
147 Some(value)
148 }
149 })
150 .collect::<Vec<_>>();
151 }
152 }
153
154 pub(crate) fn process_media_condition_without_or(
155 &mut self,
156 media_condition: &mut MediaConditionWithoutOr,
157 ) {
158 let mut remove_rules_list = Vec::new();
159
160 for (i, node) in media_condition.conditions.iter_mut().enumerate() {
161 match node {
162 MediaConditionWithoutOrType::Not(media_not) => {
163 if let Some(new_media_in_parens) =
164 self.process_media_in_parens(&media_not.condition)
165 {
166 if self.is_empty_media_parens(&new_media_in_parens) {
167 remove_rules_list.push(i);
168 } else {
169 media_not.condition = new_media_in_parens;
170 }
171 }
172 }
173 MediaConditionWithoutOrType::And(media_and) => {
174 if let Some(new_media_in_parens) =
175 self.process_media_in_parens(&media_and.condition)
176 {
177 if self.is_empty_media_parens(&new_media_in_parens) {
178 remove_rules_list.push(i);
179 } else {
180 media_and.condition = new_media_in_parens;
181 }
182 }
183 }
184 MediaConditionWithoutOrType::MediaInParens(media_in_parens) => {
185 if let Some(new_media_in_parens) = self.process_media_in_parens(media_in_parens)
186 {
187 if self.is_empty_media_parens(&new_media_in_parens) {
188 remove_rules_list.push(i);
189 } else {
190 *media_in_parens = new_media_in_parens;
191 }
192 }
193 }
194 }
195 }
196
197 if !remove_rules_list.is_empty() {
198 let mut need_change_next = false;
199
200 media_condition.conditions = take(&mut media_condition.conditions)
201 .into_iter()
202 .enumerate()
203 .filter_map(|(idx, value)| {
204 if remove_rules_list.contains(&idx) {
205 if idx == 0 {
206 need_change_next = true;
207 }
208
209 None
210 } else if need_change_next {
211 need_change_next = false;
212
213 match value {
214 MediaConditionWithoutOrType::And(media_and) => Some(
215 MediaConditionWithoutOrType::MediaInParens(media_and.condition),
216 ),
217 _ => Some(value),
218 }
219 } else {
220 Some(value)
221 }
222 })
223 .collect::<Vec<_>>();
224 }
225 }
226
227 pub(crate) fn process_media_in_parens(&mut self, n: &MediaInParens) -> Option<MediaInParens> {
228 if let Some(MediaFeatureBoolean {
229 name: MediaFeatureName::ExtensionName(name),
230 ..
231 }) = n.as_feature().and_then(|feature| feature.as_boolean())
232 {
233 if let Some(custom_media) = self.medias.iter().find(|m| m.name.value == name.value) {
234 let mut new_media_condition = MediaCondition {
235 span: DUMMY_SP,
236 conditions: Vec::new(),
237 };
238
239 let queries = match &custom_media.media {
240 CustomMediaQueryMediaType::Ident(_) => {
241 unimplemented!(
243 "Boolean logic in @custom-media at-rules is not supported by swc"
244 );
245 }
246 CustomMediaQueryMediaType::MediaQueryList(media_query_list) => {
247 &media_query_list.queries
248 }
249 };
250
251 for query in queries {
252 if query.media_type.is_some() || query.modifier.is_some() {
253 self.modifier_and_media_type =
255 Some((query.modifier.clone(), query.media_type.clone()));
256 }
257
258 if let Some(condition) = &query.condition {
259 match &**condition {
260 MediaConditionType::All(media_condition) => {
261 if new_media_condition.conditions.is_empty() {
262 if media_condition.conditions.len() == 1 {
263 let media_in_parens = if let Some(
264 MediaConditionAllType::MediaInParens(inner),
265 ) =
266 media_condition.conditions.first()
267 {
268 inner.clone()
269 } else {
270 MediaInParens::MediaCondition(media_condition.clone())
271 };
272
273 new_media_condition.conditions.push(
274 MediaConditionAllType::MediaInParens(media_in_parens),
275 );
276 } else {
277 new_media_condition.conditions.push(
278 MediaConditionAllType::MediaInParens(
279 MediaInParens::MediaCondition(
280 media_condition.clone(),
281 ),
282 ),
283 );
284 }
285 } else if let Some(MediaConditionAllType::MediaInParens(inner)) =
286 media_condition.conditions.first()
287 {
288 new_media_condition
289 .conditions
290 .push(MediaConditionAllType::Or(MediaOr {
291 span: DUMMY_SP,
292 keyword: None,
293 condition: inner.clone(),
294 }));
295 } else {
296 new_media_condition
297 .conditions
298 .push(MediaConditionAllType::Or(MediaOr {
299 span: DUMMY_SP,
300 keyword: None,
301 condition: MediaInParens::MediaCondition(
302 media_condition.clone(),
303 ),
304 }));
305 }
306 }
307 MediaConditionType::WithoutOr(media_condition) => {
308 let mut media_condition = self
309 .media_condition_without_or_to_media_condition(media_condition);
310
311 if new_media_condition.conditions.is_empty() {
312 let media_in_parens = if matches!(
313 media_condition.conditions.first(),
314 Some(MediaConditionAllType::MediaInParens(_))
315 ) {
316 match media_condition.conditions.pop() {
317 Some(MediaConditionAllType::MediaInParens(inner)) => {
318 inner
319 }
320 _ => {
321 unreachable!();
322 }
323 }
324 } else {
325 MediaInParens::MediaCondition(media_condition)
326 };
327
328 new_media_condition.conditions.push(
329 MediaConditionAllType::MediaInParens(media_in_parens),
330 );
331 } else {
332 new_media_condition
333 .conditions
334 .push(MediaConditionAllType::Or(MediaOr {
335 span: DUMMY_SP,
336 keyword: None,
337 condition: MediaInParens::MediaCondition(
338 media_condition,
339 ),
340 }));
341 }
342 }
343 }
344 }
345 }
346
347 if new_media_condition.conditions.len() == 1
348 && matches!(
349 new_media_condition.conditions.first(),
350 Some(MediaConditionAllType::MediaInParens(_))
351 )
352 {
353 let only_one = new_media_condition.conditions.pop().unwrap();
354
355 if let MediaConditionAllType::MediaInParens(media_in_parens) = only_one {
356 return Some(media_in_parens);
357 }
358 }
359
360 return Some(MediaInParens::MediaCondition(new_media_condition));
361 }
362 }
363
364 None
365 }
366
367 fn media_condition_without_or_to_media_condition(
368 &self,
369 media_condition: &MediaConditionWithoutOr,
370 ) -> MediaCondition {
371 let mut new_media_condition = MediaCondition {
372 span: DUMMY_SP,
373 conditions: Vec::new(),
374 };
375
376 for n in &media_condition.conditions {
377 let condition = match n {
378 MediaConditionWithoutOrType::MediaInParens(n) => {
379 MediaConditionAllType::MediaInParens(n.clone())
380 }
381 MediaConditionWithoutOrType::And(n) => MediaConditionAllType::And(n.clone()),
382 MediaConditionWithoutOrType::Not(n) => MediaConditionAllType::Not(n.clone()),
383 };
384
385 new_media_condition.conditions.push(condition);
386 }
387
388 new_media_condition
389 }
390
391 fn is_empty_media_parens(&self, media_in_parens: &MediaInParens) -> bool {
392 if let MediaInParens::MediaCondition(MediaCondition { conditions, .. }) = media_in_parens {
393 if conditions.is_empty() {
394 return true;
395 }
396 }
397
398 false
399 }
400}