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 {
215 let mut result = String::with_capacity(s.len());
216 let mut chars = s.chars().peekable();
217 while let Some(ch) = chars.next() {
218 if ch == '`' {
219 let mut code = String::new();
220 for c in chars.by_ref() {
221 if c == '`' {
222 break;
223 }
224 code.push(c);
225 }
226 result.push_str("{@code ");
227 result.push_str(&escape_javadoc_html_entities(&code));
228 result.push('}');
229 } else if ch == '<' {
230 result.push_str("<");
231 } else if ch == '>' {
232 result.push_str(">");
233 } else if ch == '&' {
234 result.push_str("&");
235 } else {
236 result.push(ch);
237 }
238 }
239 result
240}
241
242fn escape_javadoc_html_entities(s: &str) -> String {
245 let mut out = String::with_capacity(s.len());
246 for ch in s.chars() {
247 match ch {
248 '<' => out.push_str("<"),
249 '>' => out.push_str(">"),
250 '&' => out.push_str("&"),
251 other => out.push(other),
252 }
253 }
254 out
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_emit_phpdoc() {
263 let mut out = String::new();
264 emit_phpdoc(&mut out, "Simple documentation", " ");
265 assert!(out.contains("/**"));
266 assert!(out.contains("Simple documentation"));
267 assert!(out.contains("*/"));
268 }
269
270 #[test]
271 fn test_phpdoc_escaping() {
272 let mut out = String::new();
273 emit_phpdoc(&mut out, "Handle */ sequences", "");
274 assert!(out.contains("Handle * / sequences"));
275 }
276
277 #[test]
278 fn test_emit_csharp_doc() {
279 let mut out = String::new();
280 emit_csharp_doc(&mut out, "C# documentation", " ");
281 assert!(out.contains("<summary>"));
282 assert!(out.contains("C# documentation"));
283 assert!(out.contains("</summary>"));
284 }
285
286 #[test]
287 fn test_csharp_xml_escaping() {
288 let mut out = String::new();
289 emit_csharp_doc(&mut out, "foo < bar & baz > qux", "");
290 assert!(out.contains("foo < bar & baz > qux"));
291 }
292
293 #[test]
294 fn test_emit_elixir_doc() {
295 let mut out = String::new();
296 emit_elixir_doc(&mut out, "Elixir documentation");
297 assert!(out.contains("@doc \"\"\""));
298 assert!(out.contains("Elixir documentation"));
299 assert!(out.contains("\"\"\""));
300 }
301
302 #[test]
303 fn test_elixir_heredoc_escaping() {
304 let mut out = String::new();
305 emit_elixir_doc(&mut out, "Handle \"\"\" sequences");
306 assert!(out.contains("Handle \"\" \" sequences"));
307 }
308
309 #[test]
310 fn test_emit_roxygen() {
311 let mut out = String::new();
312 emit_roxygen(&mut out, "R documentation");
313 assert!(out.contains("#' R documentation"));
314 }
315
316 #[test]
317 fn test_emit_swift_doc() {
318 let mut out = String::new();
319 emit_swift_doc(&mut out, "Swift documentation", " ");
320 assert!(out.contains("/// Swift documentation"));
321 }
322
323 #[test]
324 fn test_emit_javadoc() {
325 let mut out = String::new();
326 emit_javadoc(&mut out, "Java documentation", " ");
327 assert!(out.contains("/**"));
328 assert!(out.contains("Java documentation"));
329 assert!(out.contains("*/"));
330 }
331
332 #[test]
333 fn test_emit_kdoc() {
334 let mut out = String::new();
335 emit_kdoc(&mut out, "Kotlin documentation", " ");
336 assert!(out.contains("/**"));
337 assert!(out.contains("Kotlin documentation"));
338 assert!(out.contains("*/"));
339 }
340
341 #[test]
342 fn test_emit_dartdoc() {
343 let mut out = String::new();
344 emit_dartdoc(&mut out, "Dart documentation", " ");
345 assert!(out.contains("/// Dart documentation"));
346 }
347
348 #[test]
349 fn test_emit_gleam_doc() {
350 let mut out = String::new();
351 emit_gleam_doc(&mut out, "Gleam documentation", " ");
352 assert!(out.contains("/// Gleam documentation"));
353 }
354
355 #[test]
356 fn test_emit_zig_doc() {
357 let mut out = String::new();
358 emit_zig_doc(&mut out, "Zig documentation", " ");
359 assert!(out.contains("/// Zig documentation"));
360 }
361
362 #[test]
363 fn test_empty_doc_skipped() {
364 let mut out = String::new();
365 emit_phpdoc(&mut out, "", "");
366 emit_csharp_doc(&mut out, "", "");
367 emit_elixir_doc(&mut out, "");
368 emit_roxygen(&mut out, "");
369 emit_kdoc(&mut out, "", "");
370 emit_dartdoc(&mut out, "", "");
371 emit_gleam_doc(&mut out, "", "");
372 emit_zig_doc(&mut out, "", "");
373 assert!(out.is_empty());
374 }
375}