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 if line.is_empty() {
124 out.push_str(indent);
125 out.push_str(" *\n");
126 } else {
127 let escaped = escape_javadoc_line(line);
128 out.push_str(indent);
129 out.push_str(" * ");
130 out.push_str(&escaped);
131 out.push('\n');
132 }
133 }
134 out.push_str(indent);
135 out.push_str(" */\n");
136}
137
138pub fn emit_kdoc(out: &mut String, doc: &str, indent: &str) {
141 if doc.is_empty() {
142 return;
143 }
144 out.push_str(indent);
145 out.push_str("/**\n");
146 for line in doc.lines() {
147 if line.is_empty() {
148 out.push_str(indent);
149 out.push_str(" *\n");
150 } else {
151 out.push_str(indent);
152 out.push_str(" * ");
153 out.push_str(line);
154 out.push('\n');
155 }
156 }
157 out.push_str(indent);
158 out.push_str(" */\n");
159}
160
161pub fn emit_dartdoc(out: &mut String, doc: &str, indent: &str) {
164 if doc.is_empty() {
165 return;
166 }
167 for line in doc.lines() {
168 out.push_str(indent);
169 out.push_str("/// ");
170 out.push_str(line);
171 out.push('\n');
172 }
173}
174
175pub fn emit_gleam_doc(out: &mut String, doc: &str, indent: &str) {
178 if doc.is_empty() {
179 return;
180 }
181 for line in doc.lines() {
182 out.push_str(indent);
183 out.push_str("/// ");
184 out.push_str(line);
185 out.push('\n');
186 }
187}
188
189pub fn emit_zig_doc(out: &mut String, doc: &str, indent: &str) {
192 if doc.is_empty() {
193 return;
194 }
195 for line in doc.lines() {
196 out.push_str(indent);
197 out.push_str("/// ");
198 out.push_str(line);
199 out.push('\n');
200 }
201}
202
203fn escape_javadoc_line(s: &str) -> String {
205 let mut result = String::with_capacity(s.len());
206 let mut chars = s.chars().peekable();
207 while let Some(ch) = chars.next() {
208 if ch == '`' {
209 let mut code = String::new();
210 for c in chars.by_ref() {
211 if c == '`' {
212 break;
213 }
214 code.push(c);
215 }
216 result.push_str("{@code ");
217 result.push_str(&code);
218 result.push('}');
219 } else if ch == '<' {
220 result.push_str("<");
221 } else if ch == '>' {
222 result.push_str(">");
223 } else if ch == '&' {
224 result.push_str("&");
225 } else {
226 result.push(ch);
227 }
228 }
229 result
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_emit_phpdoc() {
238 let mut out = String::new();
239 emit_phpdoc(&mut out, "Simple documentation", " ");
240 assert!(out.contains("/**"));
241 assert!(out.contains("Simple documentation"));
242 assert!(out.contains("*/"));
243 }
244
245 #[test]
246 fn test_phpdoc_escaping() {
247 let mut out = String::new();
248 emit_phpdoc(&mut out, "Handle */ sequences", "");
249 assert!(out.contains("Handle * / sequences"));
250 }
251
252 #[test]
253 fn test_emit_csharp_doc() {
254 let mut out = String::new();
255 emit_csharp_doc(&mut out, "C# documentation", " ");
256 assert!(out.contains("<summary>"));
257 assert!(out.contains("C# documentation"));
258 assert!(out.contains("</summary>"));
259 }
260
261 #[test]
262 fn test_csharp_xml_escaping() {
263 let mut out = String::new();
264 emit_csharp_doc(&mut out, "foo < bar & baz > qux", "");
265 assert!(out.contains("foo < bar & baz > qux"));
266 }
267
268 #[test]
269 fn test_emit_elixir_doc() {
270 let mut out = String::new();
271 emit_elixir_doc(&mut out, "Elixir documentation");
272 assert!(out.contains("@doc \"\"\""));
273 assert!(out.contains("Elixir documentation"));
274 assert!(out.contains("\"\"\""));
275 }
276
277 #[test]
278 fn test_elixir_heredoc_escaping() {
279 let mut out = String::new();
280 emit_elixir_doc(&mut out, "Handle \"\"\" sequences");
281 assert!(out.contains("Handle \"\" \" sequences"));
282 }
283
284 #[test]
285 fn test_emit_roxygen() {
286 let mut out = String::new();
287 emit_roxygen(&mut out, "R documentation");
288 assert!(out.contains("#' R documentation"));
289 }
290
291 #[test]
292 fn test_emit_swift_doc() {
293 let mut out = String::new();
294 emit_swift_doc(&mut out, "Swift documentation", " ");
295 assert!(out.contains("/// Swift documentation"));
296 }
297
298 #[test]
299 fn test_emit_javadoc() {
300 let mut out = String::new();
301 emit_javadoc(&mut out, "Java documentation", " ");
302 assert!(out.contains("/**"));
303 assert!(out.contains("Java documentation"));
304 assert!(out.contains("*/"));
305 }
306
307 #[test]
308 fn test_emit_kdoc() {
309 let mut out = String::new();
310 emit_kdoc(&mut out, "Kotlin documentation", " ");
311 assert!(out.contains("/**"));
312 assert!(out.contains("Kotlin documentation"));
313 assert!(out.contains("*/"));
314 }
315
316 #[test]
317 fn test_emit_dartdoc() {
318 let mut out = String::new();
319 emit_dartdoc(&mut out, "Dart documentation", " ");
320 assert!(out.contains("/// Dart documentation"));
321 }
322
323 #[test]
324 fn test_emit_gleam_doc() {
325 let mut out = String::new();
326 emit_gleam_doc(&mut out, "Gleam documentation", " ");
327 assert!(out.contains("/// Gleam documentation"));
328 }
329
330 #[test]
331 fn test_emit_zig_doc() {
332 let mut out = String::new();
333 emit_zig_doc(&mut out, "Zig documentation", " ");
334 assert!(out.contains("/// Zig documentation"));
335 }
336
337 #[test]
338 fn test_empty_doc_skipped() {
339 let mut out = String::new();
340 emit_phpdoc(&mut out, "", "");
341 emit_csharp_doc(&mut out, "", "");
342 emit_elixir_doc(&mut out, "");
343 emit_roxygen(&mut out, "");
344 emit_kdoc(&mut out, "", "");
345 emit_dartdoc(&mut out, "", "");
346 emit_gleam_doc(&mut out, "", "");
347 emit_zig_doc(&mut out, "", "");
348 assert!(out.is_empty());
349 }
350}