ailint_core/parser/
source_comments.rs1use ailint_extractor::{extract, Comment, CommentKind, Language};
10
11use crate::parser::markdown::{ListItem, MarkdownDoc, Paragraph};
12
13pub fn synthesize(source: &str, language: Language) -> MarkdownDoc {
19 let mut doc = MarkdownDoc::default();
20 for c in extract(source, language) {
21 let byte_range = c.byte_range.clone();
22 let line = c.line;
23 let Some(text) = comment_prose(c) else {
24 continue;
25 };
26 doc.paragraphs.push(Paragraph {
27 text: text.clone(),
28 byte_range: byte_range.clone(),
29 line,
30 });
31 doc.list_items.push(ListItem {
32 text,
33 byte_range,
34 line,
35 });
36 }
37 doc
38}
39
40fn comment_prose(c: Comment) -> Option<String> {
41 let text = normalize_body(c.body(), c.kind);
42 if text.is_empty() {
43 None
44 } else {
45 Some(text)
46 }
47}
48
49fn normalize_body(body: &str, kind: CommentKind) -> String {
56 match kind {
57 CommentKind::Line => body.trim().to_string(),
58 CommentKind::Doc | CommentKind::Block | CommentKind::Docstring => body
59 .lines()
60 .map(strip_star_gutter)
61 .map(str::trim)
62 .filter(|s| !s.is_empty())
63 .collect::<Vec<_>>()
64 .join(" "),
65 }
66}
67
68fn strip_star_gutter(line: &str) -> &str {
69 let trimmed = line.trim_start();
70 trimmed
71 .strip_prefix("* ")
72 .unwrap_or_else(|| if trimmed == "*" { "" } else { line })
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn line_comments_become_paragraphs() {
81 let doc = synthesize("// hello\n// world\n", Language::Rust);
82 assert_eq!(doc.paragraphs.len(), 2);
83 assert_eq!(doc.list_items.len(), 2);
84 assert_eq!(doc.paragraphs[0].text, "hello");
85 assert_eq!(doc.paragraphs[0].line, 1);
86 assert_eq!(doc.paragraphs[1].text, "world");
87 assert_eq!(doc.paragraphs[1].line, 2);
88 assert_eq!(doc.list_items[0].text, "hello");
89 }
90
91 #[test]
92 fn jsdoc_block_gutter_stripped_and_joined() {
93 let src = "\
94/**
95 * First line.
96 * Second line.
97 */
98const x = 1;
99";
100 let doc = synthesize(src, Language::TypeScript);
101 assert_eq!(doc.paragraphs.len(), 1);
102 assert_eq!(doc.paragraphs[0].text, "First line. Second line.");
103 assert_eq!(doc.paragraphs[0].line, 1);
104 }
105
106 #[test]
107 fn python_docstring_folded_to_single_paragraph() {
108 let src = "\
109def f():
110 \"\"\"
111 First sentence.
112 Second sentence.
113 \"\"\"
114 return 1
115";
116 let doc = synthesize(src, Language::Python);
117 assert_eq!(doc.paragraphs.len(), 1);
118 assert_eq!(doc.paragraphs[0].text, "First sentence. Second sentence.");
119 }
120
121 #[test]
122 fn empty_comment_is_skipped() {
123 let doc = synthesize("//\n// real\n// \n", Language::Rust);
124 assert_eq!(doc.paragraphs.len(), 1);
125 assert_eq!(doc.paragraphs[0].text, "real");
126 }
127}