Skip to main content

noxid_codegen_css/
lib.rs

1use noxid_css_ir::{Keyframe, StyleAtRuleBlock, StyleDeclaration, StyleProgram, StyleRule};
2
3pub fn generate(program: &StyleProgram) -> String {
4    let mut output = String::new();
5    for sheet in &program.sheets {
6        for rule in &sheet.rules {
7            emit_rule(&mut output, rule, 0);
8        }
9    }
10    output
11}
12
13fn emit_rule(output: &mut String, rule: &StyleRule, depth: usize) {
14    let indent = "  ".repeat(depth);
15    match rule {
16        StyleRule::Qualified {
17            scoped_selector,
18            declarations,
19            ..
20        } => {
21            output.push_str(&format!("{indent}{scoped_selector} {{\n"));
22            emit_declarations(output, declarations, depth + 1);
23            output.push_str(&format!("{indent}}}\n"));
24        }
25        StyleRule::AtRule {
26            name,
27            prelude,
28            block,
29            ..
30        } => {
31            let separator = if prelude.is_empty() { "" } else { " " };
32            match block {
33                None => output.push_str(&format!("{indent}@{name}{separator}{prelude};\n")),
34                Some(block) => {
35                    output.push_str(&format!("{indent}@{name}{separator}{prelude} {{\n"));
36                    match block {
37                        StyleAtRuleBlock::Rules(rules) => {
38                            for rule in rules {
39                                emit_rule(output, rule, depth + 1);
40                            }
41                        }
42                        StyleAtRuleBlock::Declarations(declarations) => {
43                            emit_declarations(output, declarations, depth + 1);
44                        }
45                    }
46                    output.push_str(&format!("{indent}}}\n"));
47                }
48            }
49        }
50        StyleRule::Keyframes {
51            scoped_name,
52            vendor_prefix,
53            frames,
54            ..
55        } => {
56            output.push_str(&format!(
57                "{indent}@{}keyframes {scoped_name} {{\n",
58                vendor_prefix.as_deref().unwrap_or("")
59            ));
60            for frame in frames {
61                emit_keyframe(output, frame, depth + 1);
62            }
63            output.push_str(&format!("{indent}}}\n"));
64        }
65    }
66}
67
68fn emit_keyframe(output: &mut String, frame: &Keyframe, depth: usize) {
69    let indent = "  ".repeat(depth);
70    output.push_str(&format!("{indent}{} {{\n", frame.selector));
71    emit_declarations(output, &frame.declarations, depth + 1);
72    output.push_str(&format!("{indent}}}\n"));
73}
74
75fn emit_declarations(output: &mut String, declarations: &[StyleDeclaration], depth: usize) {
76    let indent = "  ".repeat(depth);
77    for declaration in declarations {
78        let important = if declaration.important {
79            " !important"
80        } else {
81            ""
82        };
83        output.push_str(&format!(
84            "{indent}{}: {}{important};\n",
85            declaration.name, declaration.value
86        ));
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use noxid_css_ir::{StyleRule, StyleSheet};
94    use noxid_ir::SemanticId;
95    use noxid_source::Span;
96
97    #[test]
98    fn emits_scoped_rules_and_at_rules() {
99        let program = StyleProgram {
100            sheets: vec![StyleSheet {
101                id: SemanticId::style("Panel"),
102                component: SemanticId::component("Panel"),
103                component_name: "Panel".into(),
104                scope: "noxid-test".into(),
105                cst_token_count: 4,
106                span: Span::new(0, 10),
107                rules: vec![StyleRule::Qualified {
108                    id: SemanticId::css_rule("Panel", 1),
109                    selector: ".panel".into(),
110                    scoped_selector: ".panel[data-noxid-scope=\"noxid-test\"]".into(),
111                    declarations: vec![StyleDeclaration {
112                        id: SemanticId::css_declaration("Panel", 1, 1),
113                        name: "color".into(),
114                        value: "red".into(),
115                        important: false,
116                        span: Span::new(0, 10),
117                    }],
118                    span: Span::new(0, 10),
119                }],
120            }],
121        };
122        assert_eq!(
123            generate(&program),
124            ".panel[data-noxid-scope=\"noxid-test\"] {\n  color: red;\n}\n"
125        );
126    }
127}