baobao_codegen_typescript/ast/
consts.rs

1//! TypeScript const declaration builder.
2
3use baobao_codegen::builder::{CodeBuilder, CodeFragment, Renderable};
4
5/// Builder for TypeScript const declarations.
6#[derive(Debug, Clone)]
7pub struct Const {
8    name: String,
9    value: String,
10    ty: Option<String>,
11    exported: bool,
12}
13
14impl Const {
15    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
16        Self {
17            name: name.into(),
18            value: value.into(),
19            ty: None,
20            exported: true,
21        }
22    }
23
24    /// Add a type annotation.
25    pub fn ty(mut self, ty: impl Into<String>) -> Self {
26        self.ty = Some(ty.into());
27        self
28    }
29
30    /// Make this const private (not exported).
31    pub fn private(mut self) -> Self {
32        self.exported = false;
33        self
34    }
35
36    /// Render the const declaration to a CodeBuilder.
37    pub fn render(&self, builder: CodeBuilder) -> CodeBuilder {
38        let export = if self.exported { "export " } else { "" };
39
40        let type_annotation = match &self.ty {
41            Some(ty) => format!(": {}", ty),
42            None => String::new(),
43        };
44
45        // Check if value is multiline
46        if self.value.contains('\n') {
47            let mut lines = self.value.lines();
48            let first = lines.next().unwrap_or("");
49            let builder = builder.line(&format!(
50                "{}const {}{} = {}",
51                export, self.name, type_annotation, first
52            ));
53
54            lines.fold(builder, |b, line| b.line(line))
55        } else {
56            builder.line(&format!(
57                "{}const {}{} = {};",
58                export, self.name, type_annotation, self.value
59            ))
60        }
61    }
62
63    /// Build the const declaration as a string.
64    pub fn build(&self) -> String {
65        self.render(CodeBuilder::typescript()).build()
66    }
67}
68
69impl Renderable for Const {
70    fn to_fragments(&self) -> Vec<CodeFragment> {
71        let export = if self.exported { "export " } else { "" };
72        let type_annotation = match &self.ty {
73            Some(ty) => format!(": {}", ty),
74            None => String::new(),
75        };
76
77        // Handle multiline values
78        if self.value.contains('\n') {
79            let mut fragments = Vec::new();
80            let mut lines = self.value.lines();
81            let first = lines.next().unwrap_or("");
82            fragments.push(CodeFragment::Line(format!(
83                "{}const {}{} = {}",
84                export, self.name, type_annotation, first
85            )));
86            for line in lines {
87                fragments.push(CodeFragment::Line(line.to_string()));
88            }
89            fragments
90        } else {
91            vec![CodeFragment::Line(format!(
92                "{}const {}{} = {};",
93                export, self.name, type_annotation, self.value
94            ))]
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_simple_const() {
105        let c = Const::new("foo", "42").build();
106        assert_eq!(c, "export const foo = 42;\n");
107    }
108
109    #[test]
110    fn test_const_with_type() {
111        let c = Const::new("name", "\"hello\"").ty("string").build();
112        assert_eq!(c, "export const name: string = \"hello\";\n");
113    }
114
115    #[test]
116    fn test_private_const() {
117        let c = Const::new("secret", "123").private().build();
118        assert_eq!(c, "const secret = 123;\n");
119    }
120
121    #[test]
122    fn test_const_with_object() {
123        let c = Const::new("config", "{ debug: true }").build();
124        assert_eq!(c, "export const config = { debug: true };\n");
125    }
126}