alef_codegen/
doc_emission.rs1pub fn emit_phpdoc(out: &mut String, doc: &str, indent: &str) {
7 if doc.is_empty() {
8 return;
9 }
10 out.push_str(indent);
11 out.push_str("/**\n");
12 for line in doc.lines() {
13 out.push_str(indent);
14 out.push_str(" * ");
15 out.push_str(&escape_phpdoc_line(line));
16 out.push('\n');
17 }
18 out.push_str(indent);
19 out.push_str(" */\n");
20}
21
22fn escape_phpdoc_line(s: &str) -> String {
24 s.replace("*/", "* /")
25}
26
27pub fn emit_csharp_doc(out: &mut String, doc: &str, indent: &str) {
30 if doc.is_empty() {
31 return;
32 }
33 out.push_str(indent);
34 out.push_str("/// <summary>\n");
35 for line in doc.lines() {
36 out.push_str(indent);
37 out.push_str("/// ");
38 out.push_str(&escape_csharp_doc_line(line));
39 out.push('\n');
40 }
41 out.push_str(indent);
42 out.push_str("/// </summary>\n");
43}
44
45fn escape_csharp_doc_line(s: &str) -> String {
47 s.replace('&', "&").replace('<', "<").replace('>', ">")
48}
49
50pub fn emit_elixir_doc(out: &mut String, doc: &str) {
53 if doc.is_empty() {
54 return;
55 }
56 out.push_str("@doc \"\"\"\n");
57 for line in doc.lines() {
58 out.push_str(&escape_elixir_doc_line(line));
59 out.push('\n');
60 }
61 out.push_str("\"\"\"\n");
62}
63
64pub fn emit_rustdoc(out: &mut String, doc: &str, indent: &str) {
70 if doc.is_empty() {
71 return;
72 }
73 for line in doc.lines() {
74 out.push_str(indent);
75 out.push_str("/// ");
76 out.push_str(line);
77 out.push('\n');
78 }
79}
80
81fn escape_elixir_doc_line(s: &str) -> String {
83 s.replace("\"\"\"", "\"\" \"")
84}
85
86pub fn emit_roxygen(out: &mut String, doc: &str) {
89 if doc.is_empty() {
90 return;
91 }
92 for line in doc.lines() {
93 out.push_str("#' ");
94 out.push_str(line);
95 out.push('\n');
96 }
97}
98
99pub fn emit_swift_doc(out: &mut String, doc: &str, indent: &str) {
102 if doc.is_empty() {
103 return;
104 }
105 for line in doc.lines() {
106 out.push_str(indent);
107 out.push_str("/// ");
108 out.push_str(line);
109 out.push('\n');
110 }
111}
112
113pub fn emit_javadoc(out: &mut String, doc: &str, indent: &str) {
117 if doc.is_empty() {
118 return;
119 }
120 out.push_str(indent);
121 out.push_str("/**\n");
122 for line in doc.lines() {
123 let escaped = escape_javadoc_line(line);
124 let trimmed = escaped.trim_end();
125 if trimmed.is_empty() {
126 out.push_str(indent);
127 out.push_str(" *\n");
128 } else {
129 out.push_str(indent);
130 out.push_str(" * ");
131 out.push_str(trimmed);
132 out.push('\n');
133 }
134 }
135 out.push_str(indent);
136 out.push_str(" */\n");
137}
138
139pub fn emit_kdoc(out: &mut String, doc: &str, indent: &str) {
142 if doc.is_empty() {
143 return;
144 }
145 out.push_str(indent);
146 out.push_str("/**\n");
147 for line in doc.lines() {
148 let trimmed = line.trim_end();
149 if trimmed.is_empty() {
150 out.push_str(indent);
151 out.push_str(" *\n");
152 } else {
153 out.push_str(indent);
154 out.push_str(" * ");
155 out.push_str(trimmed);
156 out.push('\n');
157 }
158 }
159 out.push_str(indent);
160 out.push_str(" */\n");
161}
162
163pub fn emit_dartdoc(out: &mut String, doc: &str, indent: &str) {
166 if doc.is_empty() {
167 return;
168 }
169 for line in doc.lines() {
170 out.push_str(indent);
171 out.push_str("/// ");
172 out.push_str(line);
173 out.push('\n');
174 }
175}
176
177pub fn emit_gleam_doc(out: &mut String, doc: &str, indent: &str) {
180 if doc.is_empty() {
181 return;
182 }
183 for line in doc.lines() {
184 out.push_str(indent);
185 out.push_str("/// ");
186 out.push_str(line);
187 out.push('\n');
188 }
189}
190
191pub fn emit_zig_doc(out: &mut String, doc: &str, indent: &str) {
194 if doc.is_empty() {
195 return;
196 }
197 for line in doc.lines() {
198 out.push_str(indent);
199 out.push_str("/// ");
200 out.push_str(line);
201 out.push('\n');
202 }
203}
204
205fn escape_javadoc_line(s: &str) -> String {
207 let mut result = String::with_capacity(s.len());
208 let mut chars = s.chars().peekable();
209 while let Some(ch) = chars.next() {
210 if ch == '`' {
211 let mut code = String::new();
212 for c in chars.by_ref() {
213 if c == '`' {
214 break;
215 }
216 code.push(c);
217 }
218 result.push_str("{@code ");
219 result.push_str(&code);
220 result.push('}');
221 } else if ch == '<' {
222 result.push_str("<");
223 } else if ch == '>' {
224 result.push_str(">");
225 } else if ch == '&' {
226 result.push_str("&");
227 } else {
228 result.push(ch);
229 }
230 }
231 result
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_emit_phpdoc() {
240 let mut out = String::new();
241 emit_phpdoc(&mut out, "Simple documentation", " ");
242 assert!(out.contains("/**"));
243 assert!(out.contains("Simple documentation"));
244 assert!(out.contains("*/"));
245 }
246
247 #[test]
248 fn test_phpdoc_escaping() {
249 let mut out = String::new();
250 emit_phpdoc(&mut out, "Handle */ sequences", "");
251 assert!(out.contains("Handle * / sequences"));
252 }
253
254 #[test]
255 fn test_emit_csharp_doc() {
256 let mut out = String::new();
257 emit_csharp_doc(&mut out, "C# documentation", " ");
258 assert!(out.contains("<summary>"));
259 assert!(out.contains("C# documentation"));
260 assert!(out.contains("</summary>"));
261 }
262
263 #[test]
264 fn test_csharp_xml_escaping() {
265 let mut out = String::new();
266 emit_csharp_doc(&mut out, "foo < bar & baz > qux", "");
267 assert!(out.contains("foo < bar & baz > qux"));
268 }
269
270 #[test]
271 fn test_emit_elixir_doc() {
272 let mut out = String::new();
273 emit_elixir_doc(&mut out, "Elixir documentation");
274 assert!(out.contains("@doc \"\"\""));
275 assert!(out.contains("Elixir documentation"));
276 assert!(out.contains("\"\"\""));
277 }
278
279 #[test]
280 fn test_elixir_heredoc_escaping() {
281 let mut out = String::new();
282 emit_elixir_doc(&mut out, "Handle \"\"\" sequences");
283 assert!(out.contains("Handle \"\" \" sequences"));
284 }
285
286 #[test]
287 fn test_emit_roxygen() {
288 let mut out = String::new();
289 emit_roxygen(&mut out, "R documentation");
290 assert!(out.contains("#' R documentation"));
291 }
292
293 #[test]
294 fn test_emit_swift_doc() {
295 let mut out = String::new();
296 emit_swift_doc(&mut out, "Swift documentation", " ");
297 assert!(out.contains("/// Swift documentation"));
298 }
299
300 #[test]
301 fn test_emit_javadoc() {
302 let mut out = String::new();
303 emit_javadoc(&mut out, "Java documentation", " ");
304 assert!(out.contains("/**"));
305 assert!(out.contains("Java documentation"));
306 assert!(out.contains("*/"));
307 }
308
309 #[test]
310 fn test_emit_kdoc() {
311 let mut out = String::new();
312 emit_kdoc(&mut out, "Kotlin documentation", " ");
313 assert!(out.contains("/**"));
314 assert!(out.contains("Kotlin documentation"));
315 assert!(out.contains("*/"));
316 }
317
318 #[test]
319 fn test_emit_dartdoc() {
320 let mut out = String::new();
321 emit_dartdoc(&mut out, "Dart documentation", " ");
322 assert!(out.contains("/// Dart documentation"));
323 }
324
325 #[test]
326 fn test_emit_gleam_doc() {
327 let mut out = String::new();
328 emit_gleam_doc(&mut out, "Gleam documentation", " ");
329 assert!(out.contains("/// Gleam documentation"));
330 }
331
332 #[test]
333 fn test_emit_zig_doc() {
334 let mut out = String::new();
335 emit_zig_doc(&mut out, "Zig documentation", " ");
336 assert!(out.contains("/// Zig documentation"));
337 }
338
339 #[test]
340 fn test_empty_doc_skipped() {
341 let mut out = String::new();
342 emit_phpdoc(&mut out, "", "");
343 emit_csharp_doc(&mut out, "", "");
344 emit_elixir_doc(&mut out, "");
345 emit_roxygen(&mut out, "");
346 emit_kdoc(&mut out, "", "");
347 emit_dartdoc(&mut out, "", "");
348 emit_gleam_doc(&mut out, "", "");
349 emit_zig_doc(&mut out, "", "");
350 assert!(out.is_empty());
351 }
352}