swc_css_compat/compiler/
custom_media.rs

1use 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        // Limited support for `modifier` and `media_type`, it is impossible to lowering
45        // syntax for multiple media types, so we handle only case when only one media
46        // type exists.
47        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                        // TODO make me warning, we should keep code as is in such cases
242                        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                        // TODO throw a warning on multiple media types
254                        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}