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