gather_all_code_from_crates/
parse_impl_block_item.rs

1crate::ix!();
2
3pub fn parse_impl_block_item(imp: ast::Impl, remove_doc_comments: bool) -> ItemInfo {
4    let (attributes, _is_test) = extract_attributes(imp.attrs());
5    let is_public = false;
6
7    // Build a clean signature
8    let mut signature_parts = Vec::new();
9    signature_parts.push("impl".to_string());
10
11    if let Some(generics) = imp.generic_param_list() {
12        signature_parts.push(generics.syntax().text().to_string());
13    }
14
15    if let Some(tr) = imp.trait_() {
16        signature_parts.push(tr.syntax().text().to_string());
17        signature_parts.push("for".to_string());
18    }
19
20    let self_ty_str = imp.self_ty().map(|t| t.syntax().text().to_string());
21    if let Some(ref s) = self_ty_str {
22        signature_parts.push(s.clone());
23    }
24
25    if let Some(where_clause) = imp.where_clause() {
26        signature_parts.push(where_clause.syntax().text().to_string());
27    }
28
29    let clean_signature = signature_parts.join(" ");
30
31    let mut methods = Vec::new();
32    if let Some(list) = imp.assoc_item_list() {
33        for item in list.assoc_items() {
34            if let Some(m_fn) = ast::Fn::cast(item.syntax().clone()) {
35                let (m_attrs, m_is_test) = extract_attributes(m_fn.attrs());
36                let m_is_public = m_fn.visibility().map_or(false, |v|
37                    v.syntax().children_with_tokens().any(|child| child.kind() == ra_ap_syntax::SyntaxKind::PUB_KW)
38                );
39
40                let m_name = m_fn.name().map(|n| n.text().to_string()).unwrap_or_default();
41                // Use extract_signature for a cleaner function signature line
42                let m_signature = extract_signature(&m_fn, remove_doc_comments);
43                let m_body = m_fn.body().map(|b| b.syntax().text().to_string());
44
45                let fi = FunctionInfoBuilder::default()
46                    .name(m_name)
47                    .is_public(m_is_public)
48                    .is_test(m_is_test)
49                    .attributes(m_attrs)
50                    .signature(m_signature)
51                    .body(m_body)
52                    .build()
53                    .expect("expected to build FunctionInfo");
54
55                methods.push(fi);
56            }
57        }
58    }
59
60    ItemInfo::ImplBlock {
61        name: self_ty_str,
62        attributes,
63        is_public,
64        signature: clean_signature,
65        methods,
66    }
67}
68
69
70#[cfg(test)]
71mod parse_impl_block_item_tests {
72    use super::*;
73
74    #[test]
75    fn test_parse_impl_block_item() {
76        let code = r#"
77#[some_attr]
78impl MyStruct {
79    #[inline]
80    fn method(&self) {}
81}
82"#;
83        let syntax = parse_source(code);
84        let imp = syntax.descendants().find_map(ast::Impl::cast).unwrap();
85        let item = parse_impl_block_item(imp, false);
86        if let ItemInfo::ImplBlock { name, attributes, is_public, signature, methods } = item {
87            assert!(name.as_ref().unwrap() == "MyStruct");
88            assert!(!is_public);
89            assert!(attributes.iter().any(|a| a.contains("#[some_attr]")));
90            assert!(signature.contains("impl MyStruct {"));
91            assert_eq!(methods.len(), 1);
92            let method = &methods[0];
93            println!("method: {:#?}", method);
94            assert!(method.attributes().iter().any(|a| a.contains("#[inline]")));
95            assert!(method.signature().contains("fn method(&self) {}"));
96        } else {
97            panic!("Expected an impl block item");
98        }
99    }
100
101    #[test]
102    fn test_parse_impl_block_item_basic() {
103        let code = r#"
104#[some_attr]
105impl MyStruct {
106    #[inline]
107    fn method(&self) {}
108}
109"#;
110        let syntax = parse_source(code);
111        let imp = syntax.descendants().find_map(ast::Impl::cast).unwrap();
112        let item = parse_impl_block_item(imp, false);
113
114        if let ItemInfo::ImplBlock { name, attributes, is_public, signature, methods } = item {
115            assert_eq!(name.as_ref().unwrap(), "MyStruct");
116            assert!(!is_public);
117            assert!(attributes.iter().any(|a| a.contains("#[some_attr]")));
118            assert!(signature.contains("impl MyStruct {"));
119            assert_eq!(methods.len(), 1);
120
121            let method = &methods[0];
122            println!("method: {:#?}", method);
123            assert!(method.attributes().iter().any(|a| a.contains("#[inline]")));
124            assert!(method.signature().contains("fn method(&self) {}"));
125            assert_eq!(method.name(), "method");
126            assert!(!method.is_public());
127            assert!(!method.is_test());
128        } else {
129            panic!("Expected an impl block item");
130        }
131    }
132
133    #[test]
134    fn test_parse_impl_block_item_multiple_methods() {
135        let code = r#"
136impl Foo {
137    fn one() {}
138    pub fn two() {}
139    #[test]
140    fn three() {}
141}
142"#;
143        let syntax = parse_source(code);
144        let imp = syntax.descendants().find_map(ast::Impl::cast).unwrap();
145        let item = parse_impl_block_item(imp, false);
146
147        if let ItemInfo::ImplBlock { name, attributes, is_public, signature, methods } = item {
148            assert_eq!(name.as_ref().unwrap(), "Foo");
149            assert!(!is_public);
150            assert!(attributes.is_empty());
151            assert!(signature.contains("impl Foo {"));
152            assert_eq!(methods.len(), 3);
153
154            let one = methods.iter().find(|m| m.name() == "one").unwrap();
155            println!("one: {:#?}", one);
156            assert!(!one.is_public());
157            assert!(!one.is_test());
158
159            let two = methods.iter().find(|m| m.name() == "two").unwrap();
160            println!("two: {:#?}", two);
161            assert!(two.is_public());
162            assert!(!two.is_test());
163
164            let three = methods.iter().find(|m| m.name() == "three").unwrap();
165            println!("three: {:#?}", three);
166            assert!(!three.is_public());
167            assert!(three.is_test());
168        } else {
169            panic!("Expected an impl block item");
170        }
171    }
172
173    #[test]
174    fn test_parse_impl_block_item_no_methods() {
175        let code = r#"
176#[doc = "Some docs"]
177impl Empty {}
178"#;
179        let syntax = parse_source(code);
180        let imp = syntax.descendants().find_map(ast::Impl::cast).unwrap();
181        let item = parse_impl_block_item(imp, false);
182
183        if let ItemInfo::ImplBlock { name, attributes, is_public, signature, methods } = item {
184            assert_eq!(name.as_ref().unwrap(), "Empty");
185            // doc is an attribute, captured as a normal attribute line
186            assert!(attributes.iter().any(|a| a.contains("#[doc = \"Some docs\"]")));
187            assert!(!is_public);
188            assert!(signature.contains("impl Empty {"));
189            // Even if empty, rebuild_impl likely adds braces. Check methods are empty.
190            assert!(methods.is_empty());
191        } else {
192            panic!("Expected an impl block item");
193        }
194    }
195
196    #[test]
197    fn test_parse_impl_block_item_remove_doc_comments() {
198        let code = r#"
199/// Doc comment
200impl WithDocs {
201    /// doc on method
202    fn documented(&self) {}
203}
204"#;
205        let syntax = parse_source(code);
206        let imp = syntax.descendants().find_map(ast::Impl::cast).unwrap();
207
208        // remove_doc_comments = true
209        let item = parse_impl_block_item(imp.clone(), true);
210        if let ItemInfo::ImplBlock { signature, methods, .. } = item {
211            println!("signature: {:#?}", signature);
212            println!("methods: {:#?}", methods);
213            // The doc comments on impl and method should be removed from their signatures.
214            assert!(!signature.contains("/// Doc comment"));
215            let method = &methods[0];
216            assert!(!method.signature().contains("/// doc on method"));
217        } else {
218            panic!("Expected an impl block item");
219        }
220
221        // remove_doc_comments = false
222        let item = parse_impl_block_item(imp.clone(), false);
223        if let ItemInfo::ImplBlock { signature, methods, .. } = item {
224            assert!(signature.contains("/// Doc comment"));
225            let method = &methods[0];
226            assert!(method.signature().contains("/// doc on method"));
227        } else {
228            panic!("Expected an impl block item");
229        }
230    }
231
232    #[test]
233    fn test_parse_impl_block_item_trait_impl() {
234        let code = r#"
235impl SomeTrait for MyStruct {
236    fn trait_method(&self) {}
237}
238"#;
239        let syntax = parse_source(code);
240        let imp = syntax.descendants().find_map(ast::Impl::cast).unwrap();
241        let item = parse_impl_block_item(imp.clone(), false);
242
243        if let ItemInfo::ImplBlock { name, attributes, is_public, signature, methods } = item {
244            // Trait impls also appear as impl blocks, but have a trait ref.
245            // By current code, `name` is the self type name, not the trait name.
246            // name should be "MyStruct".
247            assert_eq!(name.as_ref().unwrap(), "MyStruct");
248            assert!(attributes.is_empty());
249            assert!(!is_public);
250            println!("signature: {:#?}", signature);
251            assert!(signature.contains("impl SomeTrait for MyStruct {"));
252            assert_eq!(methods.len(), 1);
253
254            let tm = &methods[0];
255            assert_eq!(tm.name(), "trait_method");
256        } else {
257            panic!("Expected an impl block item");
258        }
259    }
260}