baobao_codegen_typescript/ast/
fns.rs1use baobao_codegen::builder::{CodeBuilder, CodeFragment, Renderable};
4
5#[derive(Debug, Clone)]
7pub struct Param {
8 pub name: String,
9 pub ty: String,
10 pub optional: bool,
11}
12
13impl Param {
14 pub fn new(name: impl Into<String>, ty: impl Into<String>) -> Self {
15 Self {
16 name: name.into(),
17 ty: ty.into(),
18 optional: false,
19 }
20 }
21
22 pub fn optional(mut self) -> Self {
23 self.optional = true;
24 self
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct Fn {
31 name: String,
32 doc: Option<String>,
33 exported: bool,
34 is_async: bool,
35 params: Vec<Param>,
36 return_type: Option<String>,
37 body: Vec<String>,
38}
39
40impl Fn {
41 pub fn new(name: impl Into<String>) -> Self {
42 Self {
43 name: name.into(),
44 doc: None,
45 exported: true,
46 is_async: false,
47 params: Vec::new(),
48 return_type: None,
49 body: Vec::new(),
50 }
51 }
52
53 pub fn doc(mut self, doc: impl Into<String>) -> Self {
54 self.doc = Some(doc.into());
55 self
56 }
57
58 pub fn private(mut self) -> Self {
59 self.exported = false;
60 self
61 }
62
63 pub fn async_(mut self) -> Self {
64 self.is_async = true;
65 self
66 }
67
68 pub fn param(mut self, param: Param) -> Self {
69 self.params.push(param);
70 self
71 }
72
73 pub fn returns(mut self, ty: impl Into<String>) -> Self {
74 self.return_type = Some(ty.into());
75 self
76 }
77
78 pub fn body_line(mut self, line: impl Into<String>) -> Self {
80 self.body.push(line.into());
81 self
82 }
83
84 pub fn body(mut self, content: impl Into<String>) -> Self {
86 for line in content.into().lines() {
87 self.body.push(line.to_string());
88 }
89 self
90 }
91
92 pub fn render(&self, builder: CodeBuilder) -> CodeBuilder {
94 let builder = if let Some(doc) = &self.doc {
95 builder.jsdoc(doc)
96 } else {
97 builder
98 };
99
100 let export = if self.exported { "export " } else { "" };
101 let async_kw = if self.is_async { "async " } else { "" };
102
103 let params_str = self
104 .params
105 .iter()
106 .map(|p| {
107 let optional = if p.optional { "?" } else { "" };
108 format!("{}{}: {}", p.name, optional, p.ty)
109 })
110 .collect::<Vec<_>>()
111 .join(", ");
112
113 let signature = match &self.return_type {
114 Some(ret) => format!(
115 "{}{}function {}({}): {} {{",
116 export, async_kw, self.name, params_str, ret
117 ),
118 None => format!(
119 "{}{}function {}({}) {{",
120 export, async_kw, self.name, params_str
121 ),
122 };
123
124 let builder = builder.line(&signature).indent();
125 let builder = self.body.iter().fold(builder, |b, line| b.line(line));
126 builder.dedent().line("}")
127 }
128
129 pub fn build(&self) -> String {
131 self.render(CodeBuilder::typescript()).build()
132 }
133
134 fn format_signature(&self) -> String {
136 let export = if self.exported { "export " } else { "" };
137 let async_kw = if self.is_async { "async " } else { "" };
138
139 let params_str = self
140 .params
141 .iter()
142 .map(|p| {
143 let optional = if p.optional { "?" } else { "" };
144 format!("{}{}: {}", p.name, optional, p.ty)
145 })
146 .collect::<Vec<_>>()
147 .join(", ");
148
149 match &self.return_type {
150 Some(ret) => format!(
151 "{}{}function {}({}): {} {{",
152 export, async_kw, self.name, params_str, ret
153 ),
154 None => format!(
155 "{}{}function {}({}) {{",
156 export, async_kw, self.name, params_str
157 ),
158 }
159 }
160}
161
162impl Renderable for Fn {
163 fn to_fragments(&self) -> Vec<CodeFragment> {
164 let mut fragments = Vec::new();
165
166 if let Some(doc) = &self.doc {
168 fragments.push(CodeFragment::JsDoc(doc.clone()));
169 }
170
171 let body: Vec<CodeFragment> = self
173 .body
174 .iter()
175 .map(|line| CodeFragment::Line(line.clone()))
176 .collect();
177
178 fragments.push(CodeFragment::Block {
179 header: self.format_signature(),
180 body,
181 close: Some("}".to_string()),
182 });
183
184 fragments
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn test_simple_fn() {
194 let f = Fn::new("greet").build();
195 assert!(f.contains("export function greet() {"));
196 }
197
198 #[test]
199 fn test_fn_with_params() {
200 let f = Fn::new("add")
201 .param(Param::new("a", "number"))
202 .param(Param::new("b", "number"))
203 .returns("number")
204 .body_line("return a + b;")
205 .build();
206 assert!(f.contains("export function add(a: number, b: number): number {"));
207 assert!(f.contains("return a + b;"));
208 }
209
210 #[test]
211 fn test_async_fn() {
212 let f = Fn::new("fetch").async_().returns("Promise<string>").build();
213 assert!(f.contains("export async function fetch(): Promise<string> {"));
214 }
215
216 #[test]
217 fn test_private_fn() {
218 let f = Fn::new("helper").private().build();
219 assert!(f.contains("function helper() {"));
220 assert!(!f.contains("export"));
221 }
222
223 #[test]
224 fn test_fn_with_doc() {
225 let f = Fn::new("run").doc("Execute the command").build();
226 assert!(f.contains("/** Execute the command */"));
227 }
228
229 #[test]
230 fn test_fn_with_optional_param() {
231 let f = Fn::new("greet")
232 .param(Param::new("name", "string").optional())
233 .build();
234 assert!(f.contains("export function greet(name?: string) {"));
235 }
236}