noxid-codegen-css 0.2.0

The codegen-css component of the Noxid compiler
Documentation
use noxid_css_ir::{Keyframe, StyleAtRuleBlock, StyleDeclaration, StyleProgram, StyleRule};

pub fn generate(program: &StyleProgram) -> String {
    let mut output = String::new();
    for sheet in &program.sheets {
        for rule in &sheet.rules {
            emit_rule(&mut output, rule, 0);
        }
    }
    output
}

fn emit_rule(output: &mut String, rule: &StyleRule, depth: usize) {
    let indent = "  ".repeat(depth);
    match rule {
        StyleRule::Qualified {
            scoped_selector,
            declarations,
            ..
        } => {
            output.push_str(&format!("{indent}{scoped_selector} {{\n"));
            emit_declarations(output, declarations, depth + 1);
            output.push_str(&format!("{indent}}}\n"));
        }
        StyleRule::AtRule {
            name,
            prelude,
            block,
            ..
        } => {
            let separator = if prelude.is_empty() { "" } else { " " };
            match block {
                None => output.push_str(&format!("{indent}@{name}{separator}{prelude};\n")),
                Some(block) => {
                    output.push_str(&format!("{indent}@{name}{separator}{prelude} {{\n"));
                    match block {
                        StyleAtRuleBlock::Rules(rules) => {
                            for rule in rules {
                                emit_rule(output, rule, depth + 1);
                            }
                        }
                        StyleAtRuleBlock::Declarations(declarations) => {
                            emit_declarations(output, declarations, depth + 1);
                        }
                    }
                    output.push_str(&format!("{indent}}}\n"));
                }
            }
        }
        StyleRule::Keyframes {
            scoped_name,
            vendor_prefix,
            frames,
            ..
        } => {
            output.push_str(&format!(
                "{indent}@{}keyframes {scoped_name} {{\n",
                vendor_prefix.as_deref().unwrap_or("")
            ));
            for frame in frames {
                emit_keyframe(output, frame, depth + 1);
            }
            output.push_str(&format!("{indent}}}\n"));
        }
    }
}

fn emit_keyframe(output: &mut String, frame: &Keyframe, depth: usize) {
    let indent = "  ".repeat(depth);
    output.push_str(&format!("{indent}{} {{\n", frame.selector));
    emit_declarations(output, &frame.declarations, depth + 1);
    output.push_str(&format!("{indent}}}\n"));
}

fn emit_declarations(output: &mut String, declarations: &[StyleDeclaration], depth: usize) {
    let indent = "  ".repeat(depth);
    for declaration in declarations {
        let important = if declaration.important {
            " !important"
        } else {
            ""
        };
        output.push_str(&format!(
            "{indent}{}: {}{important};\n",
            declaration.name, declaration.value
        ));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use noxid_css_ir::{StyleRule, StyleSheet};
    use noxid_ir::SemanticId;
    use noxid_source::Span;

    #[test]
    fn emits_scoped_rules_and_at_rules() {
        let program = StyleProgram {
            sheets: vec![StyleSheet {
                id: SemanticId::style("Panel"),
                component: SemanticId::component("Panel"),
                component_name: "Panel".into(),
                scope: "noxid-test".into(),
                cst_token_count: 4,
                span: Span::new(0, 10),
                rules: vec![StyleRule::Qualified {
                    id: SemanticId::css_rule("Panel", 1),
                    selector: ".panel".into(),
                    scoped_selector: ".panel[data-noxid-scope=\"noxid-test\"]".into(),
                    declarations: vec![StyleDeclaration {
                        id: SemanticId::css_declaration("Panel", 1, 1),
                        name: "color".into(),
                        value: "red".into(),
                        important: false,
                        span: Span::new(0, 10),
                    }],
                    span: Span::new(0, 10),
                }],
            }],
        };
        assert_eq!(
            generate(&program),
            ".panel[data-noxid-scope=\"noxid-test\"] {\n  color: red;\n}\n"
        );
    }
}