1use rustc_hash::FxHashMap;
2use swc_atoms::Atom;
3use swc_common::{util::take::Take, Span};
4use swc_css_ast::{
5 ComplexSelector, ComplexSelectorChildren, ComponentValue, Declaration, DeclarationName,
6 Delimiter, DelimiterValue, FunctionName, Ident, KeyframesName, PseudoClassSelectorChildren,
7 QualifiedRule, QualifiedRulePrelude, Stylesheet, SubclassSelector,
8};
9use swc_css_visit::{VisitMut, VisitMutWith};
10
11pub mod imports;
12
13pub trait TransformConfig {
20 fn new_name_for(&self, local: &Atom) -> Atom;
22
23 }
27
28#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub enum CssClassName {
30 Local {
31 name: Ident,
33 },
34 Global {
35 name: Ident,
36 },
37 Import {
38 name: Ident,
40 from: Atom,
42 },
43}
44
45#[derive(Debug, Clone)]
46pub struct TransformResult {
47 pub renamed: FxHashMap<Atom, Vec<CssClassName>>,
49}
50
51pub fn compile<'a>(ss: &mut Stylesheet, config: impl 'a + TransformConfig) -> TransformResult {
53 let mut compiler = Compiler {
54 config,
55 data: Default::default(),
56 result: TransformResult {
57 renamed: Default::default(),
58 },
59 };
60
61 ss.visit_mut_with(&mut compiler);
62
63 fn add(result: &mut TransformResult, data: &Data, key: &Atom, composes: &[CssClassName]) {
64 let mut extra_classes = Vec::new();
65 {
66 let class_names = result.renamed.entry(key.clone()).or_default();
67
68 class_names.extend(composes.iter().cloned());
69 }
70
71 for composed_class_name in composes.iter() {
72 if let CssClassName::Local { name } = composed_class_name {
73 if let Some(original_class_name) = data.renamed_to_orig.get(&name.value) {
74 extra_classes.extend(
75 result
76 .renamed
77 .entry(original_class_name.clone())
78 .or_default()
79 .split_at(1)
80 .1
81 .to_vec(),
82 );
83 }
84 }
85 }
86
87 {
88 let class_names = result.renamed.entry(key.clone()).or_default();
89
90 class_names.extend(extra_classes);
91 }
92 }
93
94 let composes = compiler.data.composes_inherit.take();
95
96 for (key, composes) in &composes {
97 add(&mut compiler.result, &compiler.data, key, composes);
98 }
99 for (key, composes) in &composes {
100 add(&mut compiler.result, &compiler.data, key, composes);
101 }
102 compiler.result.renamed.iter_mut().for_each(|(_, v)| {
103 v.sort();
104 v.dedup();
105 });
106
107 compiler.result
108}
109
110struct Compiler<C>
111where
112 C: TransformConfig,
113{
114 config: C,
115 data: Data,
116 result: TransformResult,
117}
118
119#[derive(Default)]
120struct Data {
121 composes_for_current: Option<Vec<CssClassName>>,
123 composes_inherit: Vec<(Atom, Vec<CssClassName>)>,
124
125 renamed_to_orig: FxHashMap<Atom, Atom>,
126 orig_to_renamed: FxHashMap<Atom, Atom>,
127
128 is_global_mode: bool,
129 is_in_local_pseudo_class: bool,
130}
131
132impl<C> VisitMut for Compiler<C>
133where
134 C: TransformConfig,
135{
136 fn visit_mut_keyframes_name(&mut self, n: &mut KeyframesName) {
139 match n {
140 KeyframesName::CustomIdent(n) if !self.data.is_global_mode => {
141 n.raw = None;
142
143 rename(
144 n.span,
145 &mut self.config,
146 &mut self.result,
147 &mut self.data.orig_to_renamed,
148 &mut self.data.renamed_to_orig,
149 &mut n.value,
150 );
151 }
152 KeyframesName::Str(n) if !self.data.is_global_mode => {
153 n.raw = None;
154
155 rename(
156 n.span,
157 &mut self.config,
158 &mut self.result,
159 &mut self.data.orig_to_renamed,
160 &mut self.data.renamed_to_orig,
161 &mut n.value,
162 );
163 }
164 KeyframesName::PseudoFunction(pseudo_function)
165 if pseudo_function.pseudo.value == "local" =>
166 {
167 match &pseudo_function.name {
168 KeyframesName::CustomIdent(custom_ident) => {
169 *n = KeyframesName::CustomIdent(custom_ident.clone());
170 }
171 KeyframesName::Str(string) => {
172 *n = KeyframesName::Str(string.clone());
173 }
174 _ => {
175 unreachable!();
176 }
177 }
178
179 n.visit_mut_with(self);
180
181 return;
182 }
183 KeyframesName::PseudoPrefix(pseudo_prefix) if pseudo_prefix.pseudo.value == "local" => {
184 match &pseudo_prefix.name {
185 KeyframesName::CustomIdent(custom_ident) => {
186 *n = KeyframesName::CustomIdent(custom_ident.clone());
187 }
188 KeyframesName::Str(string) => {
189 *n = KeyframesName::Str(string.clone());
190 }
191 _ => {
192 unreachable!();
193 }
194 }
195
196 n.visit_mut_with(self);
197
198 return;
199 }
200 KeyframesName::PseudoFunction(pseudo_function)
201 if pseudo_function.pseudo.value == "global" =>
202 {
203 match &pseudo_function.name {
204 KeyframesName::CustomIdent(custom_ident) => {
205 *n = KeyframesName::CustomIdent(custom_ident.clone());
206 }
207 KeyframesName::Str(string) => {
208 *n = KeyframesName::Str(string.clone());
209 }
210 _ => {
211 unreachable!();
212 }
213 }
214
215 return;
216 }
217 KeyframesName::PseudoPrefix(pseudo_prefix)
218 if pseudo_prefix.pseudo.value == "global" =>
219 {
220 match &pseudo_prefix.name {
221 KeyframesName::CustomIdent(custom_ident) => {
222 *n = KeyframesName::CustomIdent(custom_ident.clone());
223 }
224 KeyframesName::Str(string) => {
225 *n = KeyframesName::Str(string.clone());
226 }
227 _ => {
228 unreachable!();
229 }
230 }
231
232 return;
233 }
234 _ => {}
235 }
236
237 n.visit_mut_children_with(self);
238 }
239
240 fn visit_mut_qualified_rule(&mut self, n: &mut QualifiedRule) {
241 let old_compose_stack = self.data.composes_for_current.take();
242
243 self.data.composes_for_current = Some(Default::default());
244
245 n.visit_mut_children_with(self);
246
247 if let QualifiedRulePrelude::SelectorList(sel) = &n.prelude {
248 let composes = self.data.composes_for_current.take();
249
250 for child in &sel.children {
251 if let ComplexSelectorChildren::CompoundSelector(sel) = &child.children[0] {
252 for subclass_sel in &sel.subclass_selectors {
253 if let SubclassSelector::Class(class_sel) = &subclass_sel {
254 if let Some(composes) = &composes {
255 let key = self
256 .data
257 .renamed_to_orig
258 .get(&class_sel.text.value)
259 .cloned();
260
261 if let Some(key) = key {
262 self.data.composes_inherit.push((key, composes.clone()));
263 }
264 }
265 }
266 }
267 }
268 }
269 }
270
271 self.data.composes_for_current = old_compose_stack;
272 }
273
274 fn visit_mut_component_values(&mut self, n: &mut Vec<ComponentValue>) {
275 n.visit_mut_children_with(self);
276
277 n.retain(|v| match v {
278 ComponentValue::Declaration(d) => {
279 if let DeclarationName::Ident(ident) = &d.name {
280 if &*ident.value == "composes" {
281 return false;
282 }
283 }
284
285 true
286 }
287 _ => true,
288 });
289 }
290
291 fn visit_mut_declaration(&mut self, n: &mut Declaration) {
293 n.visit_mut_children_with(self);
294
295 if let Some(composes_for_current) = &mut self.data.composes_for_current {
296 if let DeclarationName::Ident(name) = &n.name {
297 if &*name.value == "composes" {
298 if n.value.len() >= 3 {
300 match (&n.value[n.value.len() - 2], &n.value[n.value.len() - 1]) {
301 (ComponentValue::Ident(ident), ComponentValue::Str(import_source))
302 if ident.value == "from" =>
303 {
304 for class_name in n.value.iter().take(n.value.len() - 2) {
305 if let ComponentValue::Ident(value) = class_name {
306 composes_for_current.push(CssClassName::Import {
307 name: *value.clone(),
308 from: import_source.value.clone(),
309 });
310 }
311 }
312
313 return;
314 }
315 (ComponentValue::Ident(from), ComponentValue::Ident(global))
316 if from.value == "from" && global.value == "global" =>
317 {
318 for class_name in n.value.iter().take(n.value.len() - 2) {
319 if let ComponentValue::Ident(value) = class_name {
320 composes_for_current.push(CssClassName::Global {
321 name: *value.clone(),
322 });
323 }
324 }
325 return;
326 }
327 _ => (),
328 }
329 }
330
331 for class_name in n.value.iter_mut() {
332 if let ComponentValue::Ident(ident) = class_name {
333 let Ident { span, value, .. } = &mut **ident;
334 let orig = value.clone();
335 rename(
336 *span,
337 &mut self.config,
338 &mut self.result,
339 &mut self.data.orig_to_renamed,
340 &mut self.data.renamed_to_orig,
341 value,
342 );
343
344 if let Some(new_name) = self.data.orig_to_renamed.get(&orig) {
345 composes_for_current.push(CssClassName::Local {
346 name: Ident {
347 span: *span,
348 value: new_name.clone(),
349 raw: None,
350 },
351 });
352 }
353 }
354 }
355 }
356 }
357 }
358
359 if let DeclarationName::Ident(name) = &n.name {
360 match &*name.value {
361 "animation" => {
362 let mut can_change = true;
363
364 let mut iteration_count_visited = false;
365 let mut fill_mode_visited = false;
366 let mut direction_visited = false;
367 let mut easing_function_visited = false;
368 let mut play_state_visited = false;
369
370 for v in &mut n.value {
371 match v {
372 ComponentValue::Ident(ident) => {
373 if !can_change {
374 continue;
375 }
376
377 let Ident {
378 span, value, raw, ..
379 } = &mut **ident;
380
381 match &**value {
382 "infinite" => {
384 if !iteration_count_visited {
385 iteration_count_visited = true;
386 continue;
387 }
388 }
389 "none" | "forwards" | "backwards" | "both" => {
392 if !fill_mode_visited {
393 fill_mode_visited = true;
394 continue;
395 }
396 }
397 "normal" | "reverse" | "alternate" | "alternate-reverse" => {
399 if !direction_visited {
400 direction_visited = true;
401 continue;
402 }
403 }
404 "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out"
406 | "step-start" | "step-end" => {
407 if !easing_function_visited {
408 easing_function_visited = true;
409 continue;
410 }
411 }
412 "running" | "paused" => {
414 if !play_state_visited {
415 play_state_visited = true;
416 continue;
417 }
418 }
419 _ => {}
420 }
421
422 *raw = None;
423
424 rename(
425 *span,
426 &mut self.config,
427 &mut self.result,
428 &mut self.data.orig_to_renamed,
429 &mut self.data.renamed_to_orig,
430 value,
431 );
432 can_change = false;
433 }
434 ComponentValue::Integer(_) => {
435 iteration_count_visited = true;
436 }
437 ComponentValue::Function(f) => {
438 if let FunctionName::Ident(ident) = &f.name {
439 match &*ident.value {
440 "steps" | "cubic-bezier" | "linear" => {
442 easing_function_visited = true;
443 }
444 _ => {
445 }
447 }
448 }
449 }
450 ComponentValue::Delimiter(delimiter) => {
451 if matches!(
452 &**delimiter,
453 Delimiter {
454 value: DelimiterValue::Comma,
455 ..
456 }
457 ) {
458 can_change = true;
459
460 iteration_count_visited = false;
462 fill_mode_visited = false;
463 direction_visited = false;
464 easing_function_visited = false;
465 play_state_visited = false;
466 }
467 }
468 _ => (),
469 }
470 }
471 }
472 "animation-name" => {
473 for v in &mut n.value {
474 if let ComponentValue::Ident(ident) = v {
475 let Ident {
476 span, value, raw, ..
477 } = &mut **ident;
478 *raw = None;
479
480 rename(
481 *span,
482 &mut self.config,
483 &mut self.result,
484 &mut self.data.orig_to_renamed,
485 &mut self.data.renamed_to_orig,
486 value,
487 );
488 }
489 }
490 }
491 _ => {}
492 }
493 }
494 }
495
496 fn visit_mut_complex_selector(&mut self, n: &mut ComplexSelector) {
497 let mut new_children = Vec::with_capacity(n.children.len());
498
499 let old_is_global_mode = self.data.is_global_mode;
500
501 'complex: for mut n in n.children.take() {
502 if let ComplexSelectorChildren::CompoundSelector(selector) = &mut n {
503 for sel in selector.subclass_selectors.iter_mut() {
504 match sel {
505 SubclassSelector::Class(..) | SubclassSelector::Id(..) => {
506 if !self.data.is_global_mode {
507 process_local(
508 &mut self.config,
509 &mut self.result,
510 &mut self.data.orig_to_renamed,
511 &mut self.data.renamed_to_orig,
512 sel,
513 );
514 }
515 }
516
517 _ => {}
518 }
519 }
520
521 for (sel_index, sel) in selector.subclass_selectors.iter_mut().enumerate() {
522 if let SubclassSelector::PseudoClass(class_sel) = sel {
523 match &*class_sel.name.value {
524 "local" => {
525 if let Some(children) = &mut class_sel.children {
526 if let Some(PseudoClassSelectorChildren::ComplexSelector(
527 complex_selector,
528 )) = children.get_mut(0)
529 {
530 let old_is_global_mode = self.data.is_global_mode;
531 let old_inside = self.data.is_global_mode;
532
533 self.data.is_global_mode = false;
534 self.data.is_in_local_pseudo_class = true;
535
536 complex_selector.visit_mut_with(self);
537
538 let mut complex_selector_children =
539 complex_selector.children.clone();
540 prepend_left_subclass_selectors(
541 &mut complex_selector_children,
542 &mut selector.subclass_selectors,
543 sel_index,
544 );
545 new_children.extend(complex_selector_children);
546
547 self.data.is_global_mode = old_is_global_mode;
548 self.data.is_in_local_pseudo_class = old_inside;
549 }
550 } else {
551 if sel_index > 0 {
552 if let Some(n) = n.as_mut_compound_selector() {
553 n.subclass_selectors.remove(sel_index);
554 }
555 new_children.push(n);
556 }
557 self.data.is_global_mode = false;
558 }
559
560 continue 'complex;
561 }
562 "global" => {
563 if let Some(children) = &mut class_sel.children {
564 if let Some(PseudoClassSelectorChildren::ComplexSelector(
565 complex_selector,
566 )) = children.get_mut(0)
567 {
568 let mut complex_selector_children =
569 complex_selector.children.clone();
570 prepend_left_subclass_selectors(
571 &mut complex_selector_children,
572 &mut selector.subclass_selectors,
573 sel_index,
574 );
575 new_children.extend(complex_selector_children);
576 }
577 } else {
578 if sel_index > 0 {
579 if let Some(n) = n.as_mut_compound_selector() {
580 n.subclass_selectors.remove(sel_index);
581 }
582 new_children.push(n);
583 }
584 self.data.is_global_mode = true;
585 }
586
587 continue 'complex;
588 }
589 _ => {}
590 }
591 }
592 }
593 }
594
595 new_children.push(n);
596 }
597
598 n.children = new_children;
599
600 self.data.is_global_mode = old_is_global_mode;
601
602 if self.data.is_in_local_pseudo_class {
603 return;
604 }
605
606 n.visit_mut_children_with(self);
607 }
608
609 fn visit_mut_complex_selectors(&mut self, n: &mut Vec<ComplexSelector>) {
610 n.visit_mut_children_with(self);
611
612 n.retain_mut(|s| !s.children.is_empty());
613 }
614}
615
616fn rename<C>(
617 span: Span,
618 config: &mut C,
619 result: &mut TransformResult,
620 orig_to_renamed: &mut FxHashMap<Atom, Atom>,
621 renamed_to_orig: &mut FxHashMap<Atom, Atom>,
622 name: &mut Atom,
623) where
624 C: TransformConfig,
625{
626 if let Some(renamed) = orig_to_renamed.get(name) {
627 *name = renamed.clone();
628 return;
629 }
630
631 let new = config.new_name_for(name);
632
633 orig_to_renamed.insert(name.clone(), new.clone());
634 renamed_to_orig.insert(new.clone(), name.clone());
635
636 {
637 let e = result.renamed.entry(name.clone()).or_default();
638
639 let v = CssClassName::Local {
640 name: Ident {
641 span,
642 value: new.clone(),
643 raw: None,
644 },
645 };
646 if !e.contains(&v) {
647 e.push(v);
648 }
649 }
650
651 *name = new;
652}
653
654fn process_local<C>(
655 config: &mut C,
656 result: &mut TransformResult,
657 orig_to_renamed: &mut FxHashMap<Atom, Atom>,
658 renamed_to_orig: &mut FxHashMap<Atom, Atom>,
659 sel: &mut SubclassSelector,
660) where
661 C: TransformConfig,
662{
663 match sel {
664 SubclassSelector::Id(sel) => {
665 sel.text.raw = None;
666
667 rename(
668 sel.span,
669 config,
670 result,
671 orig_to_renamed,
672 renamed_to_orig,
673 &mut sel.text.value,
674 );
675 }
676 SubclassSelector::Class(sel) => {
677 sel.text.raw = None;
678
679 rename(
680 sel.span,
681 config,
682 result,
683 orig_to_renamed,
684 renamed_to_orig,
685 &mut sel.text.value,
686 );
687 }
688 SubclassSelector::Attribute(_) => {}
689 SubclassSelector::PseudoClass(_) => {}
690 SubclassSelector::PseudoElement(_) => {}
691 }
692}
693
694fn prepend_left_subclass_selectors(
695 complex_selector_children: &mut [ComplexSelectorChildren],
696 sels: &mut Vec<SubclassSelector>,
697 mut sel_index: usize,
698) {
699 sels.remove(sel_index);
700
701 for c in complex_selector_children
702 .iter_mut()
703 .filter_map(|c| c.as_mut_compound_selector())
704 {
705 c.subclass_selectors.splice(0..0, sels.drain(..sel_index));
706
707 if !sels.is_empty() {
708 c.subclass_selectors.extend(sels[..].iter().cloned());
709 }
710
711 sel_index = 0;
712 }
713}