#![feature(box_patterns)]
use rustc_hash::FxHashMap;
use serde::Serialize;
use swc_atoms::{js_word, JsWord};
use swc_common::util::take::Take;
use swc_css_ast::{
ComplexSelector, ComplexSelectorChildren, ComponentValue, Declaration, DeclarationName,
Delimiter, DelimiterValue, Ident, KeyframesName, PseudoClassSelectorChildren, QualifiedRule,
QualifiedRulePrelude, Stylesheet, SubclassSelector,
};
use swc_css_visit::{VisitMut, VisitMutWith};
pub mod imports;
pub trait TransformConfig {
fn new_name_for(&self, local: &JsWord) -> JsWord;
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum CssClassName {
Local {
name: JsWord,
},
Global {
name: JsWord,
},
Import {
name: JsWord,
from: JsWord,
},
}
#[derive(Debug, Clone)]
pub struct TransformResult {
pub renamed: FxHashMap<JsWord, Vec<CssClassName>>,
}
pub fn compile<'a>(ss: &mut Stylesheet, config: impl 'a + TransformConfig) -> TransformResult {
let mut compiler = Compiler {
config,
data: Default::default(),
result: TransformResult {
renamed: Default::default(),
},
};
ss.visit_mut_with(&mut compiler);
compiler.result
}
struct Compiler<C>
where
C: TransformConfig,
{
config: C,
data: Data,
result: TransformResult,
}
#[derive(Default)]
struct Data {
composes_for_current: Option<Vec<CssClassName>>,
renamed_to_orig: FxHashMap<JsWord, JsWord>,
orig_to_renamed: FxHashMap<JsWord, JsWord>,
is_global_mode: bool,
is_in_local_pseudo_class: bool,
}
impl<C> VisitMut for Compiler<C>
where
C: TransformConfig,
{
fn visit_mut_keyframes_name(&mut self, n: &mut KeyframesName) {
match n {
KeyframesName::CustomIdent(n) if !self.data.is_global_mode => {
n.raw = None;
rename(
&mut self.config,
&mut self.result,
&mut self.data.orig_to_renamed,
&mut self.data.renamed_to_orig,
&mut n.value,
);
}
KeyframesName::Str(n) if !self.data.is_global_mode => {
n.raw = None;
rename(
&mut self.config,
&mut self.result,
&mut self.data.orig_to_renamed,
&mut self.data.renamed_to_orig,
&mut n.value,
);
}
KeyframesName::PseudoFunction(pseudo_function)
if pseudo_function.pseudo.value == js_word!("local") =>
{
match &pseudo_function.name {
KeyframesName::CustomIdent(custom_ident) => {
*n = KeyframesName::CustomIdent(custom_ident.clone());
}
KeyframesName::Str(string) => {
*n = KeyframesName::Str(string.clone());
}
_ => {
unreachable!();
}
}
n.visit_mut_with(self);
return;
}
KeyframesName::PseudoPrefix(pseudo_prefix)
if pseudo_prefix.pseudo.value == js_word!("local") =>
{
match &pseudo_prefix.name {
KeyframesName::CustomIdent(custom_ident) => {
*n = KeyframesName::CustomIdent(custom_ident.clone());
}
KeyframesName::Str(string) => {
*n = KeyframesName::Str(string.clone());
}
_ => {
unreachable!();
}
}
n.visit_mut_with(self);
return;
}
KeyframesName::PseudoFunction(pseudo_function)
if pseudo_function.pseudo.value == js_word!("global") =>
{
match &pseudo_function.name {
KeyframesName::CustomIdent(custom_ident) => {
*n = KeyframesName::CustomIdent(custom_ident.clone());
}
KeyframesName::Str(string) => {
*n = KeyframesName::Str(string.clone());
}
_ => {
unreachable!();
}
}
return;
}
KeyframesName::PseudoPrefix(pseudo_prefix)
if pseudo_prefix.pseudo.value == js_word!("global") =>
{
match &pseudo_prefix.name {
KeyframesName::CustomIdent(custom_ident) => {
*n = KeyframesName::CustomIdent(custom_ident.clone());
}
KeyframesName::Str(string) => {
*n = KeyframesName::Str(string.clone());
}
_ => {
unreachable!();
}
}
return;
}
_ => {}
}
n.visit_mut_children_with(self);
}
fn visit_mut_qualified_rule(&mut self, n: &mut QualifiedRule) {
let old_compose_stack = self.data.composes_for_current.take();
self.data.composes_for_current = Some(Default::default());
n.visit_mut_children_with(self);
if let QualifiedRulePrelude::SelectorList(sel) = &n.prelude {
if sel.children.len() == 1 && sel.children[0].children.len() == 1 {
if let ComplexSelectorChildren::CompoundSelector(sel) = &sel.children[0].children[0]
{
if sel.subclass_selectors.len() == 1 {
if let SubclassSelector::Class(class_sel) = &sel.subclass_selectors[0] {
if let Some(composes) = self.data.composes_for_current.take() {
let key = self
.data
.renamed_to_orig
.get(&class_sel.text.value)
.cloned();
if let Some(key) = key {
self.result.renamed.entry(key).or_default().extend(composes);
}
}
}
}
}
}
}
self.data.composes_for_current = old_compose_stack;
}
fn visit_mut_component_values(&mut self, n: &mut Vec<ComponentValue>) {
n.visit_mut_children_with(self);
n.retain(|v| match v {
ComponentValue::Declaration(d) => {
if let DeclarationName::Ident(ident) = &d.name {
if &*ident.value == "composes" {
return false;
}
}
true
}
_ => true,
});
}
fn visit_mut_declaration(&mut self, n: &mut Declaration) {
n.visit_mut_children_with(self);
if let Some(composes_for_current) = &mut self.data.composes_for_current {
if let DeclarationName::Ident(name) = &n.name {
if &*name.value == "composes" {
if n.value.len() >= 3 {
match (&n.value[n.value.len() - 2], &n.value[n.value.len() - 1]) {
(
ComponentValue::Ident(box Ident {
value: js_word!("from"),
..
}),
ComponentValue::Str(import_source),
) => {
for class_name in n.value.iter().take(n.value.len() - 2) {
if let ComponentValue::Ident(box Ident { value, .. }) =
class_name
{
composes_for_current.push(CssClassName::Import {
name: value.clone(),
from: import_source.value.clone(),
});
}
}
return;
}
(
ComponentValue::Ident(box Ident {
value: js_word!("from"),
..
}),
ComponentValue::Ident(box Ident {
value: js_word!("global"),
..
}),
) => {
for class_name in n.value.iter().take(n.value.len() - 2) {
if let ComponentValue::Ident(box Ident { value, .. }) =
class_name
{
composes_for_current.push(CssClassName::Global {
name: value.clone(),
});
}
}
return;
}
_ => (),
}
}
for class_name in n.value.iter() {
if let ComponentValue::Ident(box Ident { value, .. }) = class_name {
if let Some(value) = self.data.orig_to_renamed.get(value) {
composes_for_current.push(CssClassName::Local {
name: value.clone(),
});
}
}
}
}
}
}
if let DeclarationName::Ident(name) = &n.name {
match name.value {
js_word!("animation") => {
let mut can_change = true;
for v in &mut n.value {
if can_change {
if let ComponentValue::Ident(box Ident { value, raw, .. }) = v {
*raw = None;
rename(
&mut self.config,
&mut self.result,
&mut self.data.orig_to_renamed,
&mut self.data.renamed_to_orig,
value,
);
can_change = false;
}
} else if let ComponentValue::Delimiter(delimiter) = v {
if matches!(
&**delimiter,
Delimiter {
value: DelimiterValue::Comma,
..
}
) {
can_change = true;
}
}
}
}
js_word!("animation-name") => {
for v in &mut n.value {
if let ComponentValue::Ident(box Ident { value, raw, .. }) = v {
*raw = None;
rename(
&mut self.config,
&mut self.result,
&mut self.data.orig_to_renamed,
&mut self.data.renamed_to_orig,
value,
);
}
}
}
_ => {}
}
}
}
fn visit_mut_complex_selector(&mut self, n: &mut ComplexSelector) {
let mut new_children = Vec::with_capacity(n.children.len());
let old_is_global_mode = self.data.is_global_mode;
'complex: for mut n in n.children.take() {
if let ComplexSelectorChildren::CompoundSelector(selector) = &mut n {
for (sel_index, sel) in selector.subclass_selectors.iter_mut().enumerate() {
match sel {
SubclassSelector::Class(..) | SubclassSelector::Id(..) => {
if !self.data.is_global_mode {
process_local(
&mut self.config,
&mut self.result,
&mut self.data.orig_to_renamed,
&mut self.data.renamed_to_orig,
sel,
);
}
}
SubclassSelector::PseudoClass(class_sel) => match &*class_sel.name.value {
"local" => {
if let Some(children) = &mut class_sel.children {
if let Some(PseudoClassSelectorChildren::ComplexSelector(
complex_selector,
)) = children.get_mut(0)
{
let old_is_global_mode = self.data.is_global_mode;
let old_inside = self.data.is_global_mode;
self.data.is_global_mode = false;
self.data.is_in_local_pseudo_class = true;
complex_selector.visit_mut_with(self);
let mut complex_selector_children =
complex_selector.children.clone();
prepend_left_subclass_selectors(
&mut complex_selector_children,
selector
.subclass_selectors
.split_at(sel_index)
.0
.to_vec(),
);
new_children.extend(complex_selector_children);
self.data.is_global_mode = old_is_global_mode;
self.data.is_in_local_pseudo_class = old_inside;
}
} else {
self.data.is_global_mode = false;
}
continue 'complex;
}
"global" => {
if let Some(children) = &mut class_sel.children {
if let Some(PseudoClassSelectorChildren::ComplexSelector(
complex_selector,
)) = children.get_mut(0)
{
let mut complex_selector_children =
complex_selector.children.clone();
prepend_left_subclass_selectors(
&mut complex_selector_children,
selector
.subclass_selectors
.split_at(sel_index)
.0
.to_vec(),
);
new_children.extend(complex_selector_children);
}
} else {
self.data.is_global_mode = true;
}
continue 'complex;
}
_ => {}
},
_ => {}
}
}
}
new_children.push(n);
}
n.children = new_children;
self.data.is_global_mode = old_is_global_mode;
if self.data.is_in_local_pseudo_class {
return;
}
n.visit_mut_children_with(self);
}
fn visit_mut_complex_selectors(&mut self, n: &mut Vec<ComplexSelector>) {
n.visit_mut_children_with(self);
n.retain_mut(|s| !s.children.is_empty());
}
}
fn rename<C>(
config: &mut C,
result: &mut TransformResult,
orig_to_renamed: &mut FxHashMap<JsWord, JsWord>,
renamed_to_orig: &mut FxHashMap<JsWord, JsWord>,
name: &mut JsWord,
) where
C: TransformConfig,
{
if let Some(renamed) = orig_to_renamed.get(name) {
*name = renamed.clone();
return;
}
let new = config.new_name_for(name);
orig_to_renamed.insert(name.clone(), new.clone());
renamed_to_orig.insert(new.clone(), name.clone());
{
let e = result.renamed.entry(name.clone()).or_default();
let v = CssClassName::Local { name: new.clone() };
if !e.contains(&v) {
e.push(v);
}
}
*name = new;
}
fn process_local<C>(
config: &mut C,
result: &mut TransformResult,
orig_to_renamed: &mut FxHashMap<JsWord, JsWord>,
renamed_to_orig: &mut FxHashMap<JsWord, JsWord>,
sel: &mut SubclassSelector,
) where
C: TransformConfig,
{
match sel {
SubclassSelector::Id(sel) => {
sel.text.raw = None;
rename(
config,
result,
orig_to_renamed,
renamed_to_orig,
&mut sel.text.value,
);
}
SubclassSelector::Class(sel) => {
sel.text.raw = None;
rename(
config,
result,
orig_to_renamed,
renamed_to_orig,
&mut sel.text.value,
);
}
SubclassSelector::Attribute(_) => {}
SubclassSelector::PseudoClass(_) => {}
SubclassSelector::PseudoElement(_) => {}
}
}
fn prepend_left_subclass_selectors(
complex_selector_children: &mut [ComplexSelectorChildren],
left_sels: Vec<SubclassSelector>,
) {
if let Some(ComplexSelectorChildren::CompoundSelector(first)) =
complex_selector_children.get_mut(0)
{
first.subclass_selectors = [left_sels, first.subclass_selectors.take()].concat();
}
}