Skip to main content

alef_codegen/
doc_emission.rs

1//! Language-native documentation comment emission.
2//! Provides standardized functions for emitting doc comments in different languages.
3
4/// Emit PHPDoc-style comments (/** ... */)
5/// Used for PHP classes, methods, and properties.
6pub 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
22/// Escape PHPDoc line: handle */ sequences that could close the comment early.
23fn escape_phpdoc_line(s: &str) -> String {
24    s.replace("*/", "* /")
25}
26
27/// Emit C# XML documentation comments (/// <summary> ... </summary>)
28/// Used for C# classes, structs, methods, and properties.
29pub 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
45/// Escape C# XML doc line: handle XML special characters.
46fn escape_csharp_doc_line(s: &str) -> String {
47    s.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;")
48}
49
50/// Emit Elixir documentation comments (@doc)
51/// Used for Elixir modules and functions.
52pub 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
64/// Emit Rust `///` documentation comments.
65///
66/// Used by alef backends that emit Rust source (e.g., the Rustler NIF crate,
67/// the swift-bridge wrapper crate, the FRB Dart bridge crate). Distinct from
68/// `emit_swift_doc` only by intent — the syntax is identical (`/// ` per line).
69pub 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
81/// Escape Elixir doc line: handle triple-quote sequences that could close the heredoc early.
82fn escape_elixir_doc_line(s: &str) -> String {
83    s.replace("\"\"\"", "\"\" \"")
84}
85
86/// Emit R roxygen2-style documentation comments (#')
87/// Used for R functions.
88pub 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
99/// Emit Swift-style documentation comments (///)
100/// Used for Swift structs, enums, and functions.
101pub 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
113/// Emit Javadoc-style documentation comments (/** ... */)
114/// Used for Java classes, methods, and fields.
115/// Handles XML escaping and Javadoc tag formatting.
116pub 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
139/// Emit KDoc-style documentation comments (/** ... */)
140/// Used for Kotlin classes, methods, and properties.
141pub 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
163/// Emit Dartdoc-style documentation comments (///)
164/// Used for Dart classes, methods, and properties.
165pub 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
177/// Emit Gleam documentation comments (///)
178/// Used for Gleam functions and types.
179pub 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
191/// Emit Zig documentation comments (///)
192/// Used for Zig functions, types, and declarations.
193pub 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
205/// Escape Javadoc line: handle XML special chars and backtick code blocks.
206fn 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("&lt;");
223        } else if ch == '>' {
224            result.push_str("&gt;");
225        } else if ch == '&' {
226            result.push_str("&amp;");
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 &lt; bar &amp; baz &gt; 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}