ide/annotations/
fn_references.rs

1//! This module implements a methods and free functions search in the specified file.
2//! We have to skip tests, so cannot reuse file_structure module.
3
4use hir::Semantics;
5use ide_assists::utils::test_related_attribute_syn;
6use ide_db::RootDatabase;
7use syntax::{AstNode, SyntaxNode, TextRange, ast, ast::HasName};
8
9use crate::FileId;
10
11pub(super) fn find_all_methods(
12    db: &RootDatabase,
13    file_id: FileId,
14) -> Vec<(TextRange, Option<TextRange>)> {
15    let sema = Semantics::new(db);
16    let source_file = sema.parse_guess_edition(file_id);
17    source_file.syntax().descendants().filter_map(method_range).collect()
18}
19
20fn method_range(item: SyntaxNode) -> Option<(TextRange, Option<TextRange>)> {
21    ast::Fn::cast(item).and_then(|fn_def| {
22        if test_related_attribute_syn(&fn_def).is_some() {
23            None
24        } else {
25            Some((
26                fn_def.syntax().text_range(),
27                fn_def.name().map(|name| name.syntax().text_range()),
28            ))
29        }
30    })
31}
32
33#[cfg(test)]
34mod tests {
35    use syntax::TextRange;
36
37    use crate::TextSize;
38    use crate::fixture;
39    use std::ops::RangeInclusive;
40
41    #[test]
42    fn test_find_all_methods() {
43        let (analysis, pos) = fixture::position(
44            r#"
45            fn private_fn() {$0}
46
47            pub fn pub_fn() {}
48
49            pub fn generic_fn<T>(arg: T) {}
50        "#,
51        );
52
53        let refs = super::find_all_methods(&analysis.db, pos.file_id);
54        check_result(&refs, &[3..=13, 27..=33, 47..=57]);
55    }
56
57    #[test]
58    fn test_find_trait_methods() {
59        let (analysis, pos) = fixture::position(
60            r#"
61            trait Foo {
62                fn bar() {$0}
63                fn baz() {}
64            }
65        "#,
66        );
67
68        let refs = super::find_all_methods(&analysis.db, pos.file_id);
69        check_result(&refs, &[19..=22, 35..=38]);
70    }
71
72    #[test]
73    fn test_skip_tests() {
74        let (analysis, pos) = fixture::position(
75            r#"
76            //- /lib.rs
77            #[test]
78            fn foo() {$0}
79
80            pub fn pub_fn() {}
81
82            mod tests {
83                #[test]
84                fn bar() {}
85            }
86        "#,
87        );
88
89        let refs = super::find_all_methods(&analysis.db, pos.file_id);
90        check_result(&refs, &[28..=34]);
91    }
92
93    fn check_result(refs: &[(TextRange, Option<TextRange>)], expected: &[RangeInclusive<u32>]) {
94        assert_eq!(refs.len(), expected.len());
95
96        for (i, &(full, focus)) in refs.iter().enumerate() {
97            let range = &expected[i];
98            let item = focus.unwrap_or(full);
99            assert_eq!(TextSize::from(*range.start()), item.start());
100            assert_eq!(TextSize::from(*range.end()), item.end());
101        }
102    }
103}