workspacer_syntax/
extract_docs_from_ast_node.rs1crate::ix!();
3
4pub fn extract_docs(node: &SyntaxNode) -> Option<String> {
7 let mut doc_lines = Vec::new();
8
9 for child in node.children_with_tokens() {
10 if let Some(token) = child.into_token() {
11 if token.kind() == SyntaxKind::COMMENT {
12 let text = token.text();
13 if text.starts_with("///") || text.starts_with("/**") {
15 debug!(?text, "Found doc-comment token");
16 for line in text.lines() {
18 doc_lines.push(line.trim_start().to_string());
20 }
21 }
22 }
23 }
24 }
25
26 if doc_lines.is_empty() {
27 None
28 } else {
29 Some(doc_lines.join("\n"))
30 }
31}
32
33#[cfg(test)]
34mod test_extract_docs_exhaustive {
35 use super::*;
36
37 fn parse_first_item_node(code: &str) -> SyntaxNode {
39 let file = SourceFile::parse(code, Edition::Edition2021);
40 let syntax = file.syntax_node();
41 syntax
42 .children()
43 .find(|child| {
44 !matches!(child.kind(), SyntaxKind::WHITESPACE | SyntaxKind::COMMENT)
46 })
47 .unwrap_or_else(|| panic!("No top-level item node found in snippet:\n{}", code))
48 }
49
50 #[traced_test]
51 fn test_extract_docs_none_when_no_doc_comments() {
52 info!("Testing no doc comments exist.");
53 let code = r#"
54 fn example() {}
55 "#;
56 let node = parse_first_item_node(code);
57 let docs = extract_docs(&node);
58 assert!(docs.is_none(), "Expected no doc comments, got: {:?}", docs);
59 }
60
61 #[traced_test]
62 fn test_extract_docs_none_with_only_normal_comment() {
63 info!("Testing only normal comments appear, no doc comment style recognized.");
64 let code = r#"
65 // This is a regular comment, not a doc comment.
66 fn example() {}
67 "#;
68 let node = parse_first_item_node(code);
69 let docs = extract_docs(&node);
70 assert!(docs.is_none(), "Expected None because only normal // comment was present.");
71 }
72
73 #[traced_test]
74 fn test_extract_docs_single_line_doc_comment() {
75 info!("Testing single line triple-slash doc comment recognized.");
76 let code = r#"
77 /// This is a doc comment
78 fn example() {}
79 "#;
80 let node = parse_first_item_node(code);
81 let docs = extract_docs(&node);
82 assert!(docs.is_some(), "Expected Some(doc) for triple-slash comment.");
83 let doc_text = docs.unwrap();
84 assert_eq!(doc_text.trim(), "/// This is a doc comment");
85 }
86
87 #[traced_test]
88 fn test_extract_docs_multiple_line_doc_comments() {
89 info!("Testing multiple triple-slash doc comments recognized.");
90 let code = r#"
91 /// Line one
92 /// Line two
93 fn example() {}
94 "#;
95 let node = parse_first_item_node(code);
96 let docs = extract_docs(&node).expect("Expected multiple doc lines");
97 let lines: Vec<&str> = docs.lines().collect();
98 assert_eq!(lines.len(), 2, "Should have two doc comment lines");
99 assert_eq!(lines[0].trim(), "/// Line one");
100 assert_eq!(lines[1].trim(), "/// Line two");
101 }
102
103 #[traced_test]
104 fn test_extract_docs_block_doc_comment() {
105 info!("Testing block doc comment recognized as multiple lines if needed.");
106 let code = r#"
107 /**
108 * This is a block doc comment
109 */
110 fn example() {}
111 "#;
112 let node = parse_first_item_node(code);
113 let docs = extract_docs(&node);
114 assert!(docs.is_some(), "Expected Some(doc) for block doc comment.");
115 let doc_text = docs.unwrap();
116 assert!(
117 doc_text.contains("/**"),
118 "Should contain block doc syntax in the collected lines:\n{doc_text}"
119 );
120 }
121
122 #[traced_test]
123 fn test_extract_docs_combination_block_and_line() {
124 info!("Testing combination of block doc and triple-slash doc.");
125 let code = r#"
126 /** Block doc */
127 /// Line doc
128 fn example() {}
129 "#;
130 let node = parse_first_item_node(code);
131 let docs = extract_docs(&node)
132 .expect("Expected doc lines since doc comments are present.");
133 let lines: Vec<&str> = docs.lines().collect();
134 assert_eq!(lines.len(), 2, "Should have two doc lines total.");
135 assert!(lines[0].starts_with("/** Block doc"));
136 assert!(lines[1].starts_with("/// Line doc"));
137 }
138
139 #[traced_test]
140 fn test_extract_docs_with_mixed_normal_comments_ignored() {
141 info!("Testing normal // comments are ignored, doc comments extracted.");
142 let code = r#"
143 // normal comment
144 /// doc comment
145 // another normal comment
146 fn example() {}
147 "#;
148 let node = parse_first_item_node(code);
149 let docs = extract_docs(&node)
150 .expect("Expected doc comment to be extracted despite normal comments.");
151 let doc_text = docs.trim();
152 assert_eq!(doc_text, "/// doc comment");
153 }
154
155 #[traced_test]
156 fn test_extract_docs_returns_all_doc_comments_in_joined_string() {
157 info!("Testing we join all doc lines in order.");
158 let code = r#"
159 /// first line
160 /** second line */
161 /// third line
162 fn example() {}
163 "#;
164 let node = parse_first_item_node(code);
165 let docs = extract_docs(&node).expect("Expected doc comments");
166 let parts: Vec<&str> = docs.lines().collect();
167 assert_eq!(parts.len(), 3, "Should collect three doc comment lines.");
168 assert!(parts[0].starts_with("/// first line"));
169 assert!(parts[1].starts_with("/** second line"));
170 assert!(parts[2].starts_with("/// third line"));
171 }
172
173 #[traced_test]
174 fn test_extract_docs_on_struct_with_no_docs() {
175 info!("Testing no doc lines on a doc-less struct.");
176 let code = r#"
177 struct MyStruct {
178 field: i32
179 }
180 "#;
181 let node = parse_first_item_node(code);
182 let docs = extract_docs(&node);
183 assert!(docs.is_none(), "Should return None for a struct with no doc comments.");
184 }
185
186 #[traced_test]
187 fn test_extract_docs_on_struct_with_doc_comment() {
188 info!("Testing triple-slash doc lines on a struct.");
189 let code = r#"
190 /// A structure for demonstration
191 struct MyStruct {
192 field: i32
193 }
194 "#;
195 let node = parse_first_item_node(code);
196 let docs = extract_docs(&node).expect("Expected doc string on struct");
197 assert!(docs.contains("A structure for demonstration"));
198 }
199}