syn_select/
lib.rs

1//! Library to get a specific element by path in Rust code.
2//!
3//! # Usage
4//! ```rust,edition2018
5//! let file: syn::File = syn::parse_str(
6//!     r#"
7//!     mod a {
8//!         mod b {
9//!             trait C {
10//!                 fn d(self) {}
11//!                 fn f() {}
12//!             }
13//!         }
14//!     }"#).unwrap();
15//! let results = syn_select::select("a::b::C::d", &file).unwrap();
16//! assert_eq!(results.len(), 1);
17//! ```
18
19use syn::Item;
20
21mod error;
22mod search;
23mod selector;
24
25pub use self::error::Error;
26pub use self::selector::Selector;
27
28/// Parse a path, then search a file for all results that exactly match the specified
29/// path.
30///
31/// # Returns
32/// This function can find multiple items if:
33///
34/// 1. There is a module and a function of the same name
35/// 2. The same path is declared multiple times, differing by config flags
36pub fn select(path: &str, file: &syn::File) -> Result<Vec<Item>, Error> {
37    Ok(Selector::try_from(path)?.apply_to(file))
38}
39
40#[cfg(test)]
41mod tests {
42    use syn::Item;
43
44    use super::{select, Selector};
45
46    fn sample() -> syn::File {
47        syn::parse_str(
48            "mod a {
49            mod b {
50                trait C {
51                    fn d() {
52                        struct E;
53                    }
54                    fn f(self) {
55                        struct E;
56                    }
57                }
58            }
59            fn b() {}
60        }",
61        )
62        .unwrap()
63    }
64
65    fn sample_with_cfg() -> syn::File {
66        syn::parse_str(
67            r#"
68            /// Outer doc
69            #[cfg(feature = "g")]
70            mod imp {
71                /// Documentation
72                #[serde(skip)]
73                #[cfg(feature = "h")]
74                pub struct H(u8);
75            }
76            #[cfg(not(feature = "g"))]
77            mod imp {
78                pub struct H(u16);
79            }"#,
80        )
81        .unwrap()
82    }
83
84    fn search_sample(path: &str) -> Vec<syn::Item> {
85        select(path, &sample()).unwrap()
86    }
87
88    fn ident(ident: &str) -> syn::Ident {
89        syn::parse_str::<syn::Ident>(ident).unwrap()
90    }
91
92    #[test]
93    fn autotraits() {
94        fn assert_send<T: Send>() {}
95        fn assert_sync<T: Sync>() {}
96        assert_send::<Selector>();
97        assert_sync::<Selector>();
98    }
99
100    #[test]
101    fn example_1() {
102        let result = search_sample("a::b::C");
103        assert_eq!(result.len(), 1);
104        if let Item::Trait(item) = &result[0] {
105            assert_eq!(item.ident, ident("C"));
106        } else {
107            panic!("Result was wrong type");
108        }
109    }
110
111    #[test]
112    fn example_2() {
113        let result = search_sample("a::b::C::d::E");
114        assert_eq!(result.len(), 1);
115        if let Item::Struct(item) = &result[0] {
116            assert_eq!(item.ident, ident("E"));
117        } else {
118            panic!("Result was wrong type");
119        }
120    }
121
122    /// If I query for "a::b::C::f" I should get the trait C filtered down to only function f.
123    /// The trait needs to be included because fn f(self) {} by itself is not a valid top-level
124    /// Item.
125    #[test]
126    fn example_3() {
127        let result = search_sample("a::b::C::f");
128        assert_eq!(result.len(), 1);
129        if let Item::Trait(item) = &result[0] {
130            assert_eq!(item.items.len(), 1);
131            if let syn::TraitItem::Fn(item) = &item.items[0] {
132                assert_eq!(item.sig.ident, ident("f"));
133            }
134        }
135    }
136
137    #[test]
138    fn example_4() {
139        let result = search_sample("a::b");
140        assert_eq!(result.len(), 2);
141    }
142
143    /// Test that `cfg` attributes are intelligently added to search results, and
144    /// that attribute order is idiomatic.
145    #[test]
146    fn example_5() {
147        let result = select("imp::H", &sample_with_cfg()).unwrap();
148        assert_eq!(result.len(), 2);
149        if let Item::Struct(item) = &result[0] {
150            assert_eq!(item.attrs.len(), 4);
151            assert!(item.attrs[0].path().is_ident("doc"));
152            assert!(item.attrs[1].path().is_ident("cfg"));
153            assert!(item.attrs[2].path().is_ident("serde"));
154            assert!(item.attrs[3].path().is_ident("cfg"));
155        } else {
156            panic!("First result should be struct");
157        }
158
159        if let Item::Struct(item) = &result[1] {
160            assert_eq!(item.attrs.len(), 1);
161            assert!(item.attrs[0].path().is_ident("cfg"));
162        } else {
163            panic!("Second result should be struct");
164        }
165    }
166
167    #[test]
168    fn example_6() {
169        let result = search_sample("a::b::C::_::E");
170        assert_eq!(result.len(), 2);
171    }
172}
OSZAR »