codama_syn_helpers/extensions/
path.rs1use super::ToTokensExtension;
2use codama_errors::CodamaResult;
3use syn::{Path, PathArguments, PathSegment};
4
5pub trait PathExtension {
6 fn get_self(&self) -> &Path;
7
8 fn idents(&self) -> Vec<String> {
10 let this = self.get_self();
11 this.segments
12 .iter()
13 .map(|segment| segment.ident.to_string())
14 .collect::<Vec<_>>()
15 }
16
17 fn to_string(&self) -> String {
20 self.idents().join("::")
21 }
22
23 fn prefix(&self) -> String {
26 let idents = self.idents();
27 idents[..idents.len() - 1].join("::")
28 }
29
30 fn last(&self) -> &PathSegment {
32 self.get_self().segments.last().unwrap()
33 }
34
35 fn last_str(&self) -> String {
37 self.last().ident.to_string()
38 }
39
40 fn is(&self, path: &str) -> bool {
42 let mut segments = path.split("::").collect::<Vec<_>>();
43 let last = segments.pop().unwrap();
44 let prefix = segments.join("::");
45 let this_prefix = self.prefix();
46 (this_prefix == prefix || this_prefix.is_empty()) && last == self.last_str()
47 }
48
49 fn is_strict(&self, path: &str) -> bool {
51 let mut segments = path.split("::").collect::<Vec<_>>();
52 let last = segments.pop().unwrap();
53 let prefix = segments.join("::");
54 prefix == self.prefix() && last == self.last_str()
55 }
56
57 fn generic_arguments(&self) -> Vec<&syn::GenericArgument> {
61 match &self.last().arguments {
62 PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }) => {
63 args.iter().collect()
64 }
65 _ => vec![],
66 }
67 }
68
69 fn generic_types(&self) -> Vec<&syn::Type> {
72 self.generic_arguments()
73 .iter()
74 .filter_map(|arg| match arg {
75 syn::GenericArgument::Type(ty) => Some(ty),
76 _ => None,
77 })
78 .collect()
79 }
80
81 fn first_generic_type(&self) -> CodamaResult<&syn::Type> {
84 let this = self.get_self();
85 self.generic_types()
86 .first()
87 .copied()
88 .ok_or_else(|| this.error("expected at least one generic type").into())
89 }
90
91 fn single_generic_type(&self) -> CodamaResult<&syn::Type> {
94 let this = self.get_self();
95 if self.generic_types().len() != 1 {
96 return Err(this
97 .error("expected a single generic type".to_string())
98 .into());
99 }
100 self.first_generic_type()
101 }
102}
103
104impl PathExtension for Path {
105 fn get_self(&self) -> &Path {
106 self
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn idents() {
116 let path: Path = syn::parse_quote! { std::option<Foo>::Option<String> };
117 assert_eq!(path.idents(), vec!["std", "option", "Option"]);
118 }
119
120 #[test]
121 fn to_string() {
122 let path: Path = syn::parse_quote! { std::option<Foo>::Option<String> };
123 assert_eq!(path.to_string(), "std::option::Option");
124 }
125
126 #[test]
127 fn prefix() {
128 let path: Path = syn::parse_quote! { std::option<Foo>::Option<String> };
129 assert_eq!(path.prefix(), "std::option");
130 }
131
132 #[test]
133 fn prefix_with_inner_generics() {
134 let path: Path = syn::parse_quote! { a<A>::b<B>::c::Final };
135 assert_eq!(path.prefix(), "a::b::c");
136 }
137
138 #[test]
139 fn prefix_empty() {
140 let path: Path = syn::parse_quote! { Foo };
141 assert_eq!(path.prefix(), "");
142 }
143
144 #[test]
145 fn is() {
146 let path: Path = syn::parse_quote! { prefix::Foo<'a, T> };
147 assert!(path.is("prefix::Foo"));
148 assert!(!path.is("Foo"));
149 assert!(!path.is("wrong::prefix::Foo"));
150 assert!(!path.is("Bar"));
151
152 let path: Path = syn::parse_quote! { Foo<T> };
153 assert!(path.is("Foo"));
154 assert!(path.is("prefix::Foo"));
155 assert!(!path.is("Bar"));
156 }
157
158 #[test]
159 fn is_strict() {
160 let path: Path = syn::parse_quote! { prefix::Foo<'a, T> };
161 assert!(path.is_strict("prefix::Foo"));
162 assert!(!path.is_strict("Foo"));
163 assert!(!path.is_strict("wrong::prefix::Foo"));
164 assert!(!path.is_strict("Bar"));
165
166 let path: Path = syn::parse_quote! { Foo<T> };
167 assert!(path.is_strict("Foo"));
168 assert!(!path.is_strict("prefix::Foo"));
169 assert!(!path.is_strict("Bar"));
170 }
171
172 #[test]
173 fn generic_arguments() {
174 let path: Path = syn::parse_quote! { prefix::Foo<'a, T, U> };
175 assert_eq!(path.generic_arguments().len(), 3);
176 }
177
178 #[test]
179 fn generic_types() {
180 let path: Path = syn::parse_quote! { prefix::Foo<'a, T, U> };
181 assert_eq!(path.generic_types().len(), 2);
182 }
183
184 #[test]
185 fn first_generic_type_ok() {
186 let path: Path = syn::parse_quote! { prefix::Foo<'a, T, U> };
187 assert!(matches!(path.first_generic_type(), Ok(syn::Type::Path(_))));
188 }
189
190 #[test]
191 fn first_generic_type_err() {
192 let path: Path = syn::parse_quote! { prefix::Foo<'a> };
193 assert!(path.first_generic_type().is_err());
194 }
195
196 #[test]
197 fn single_generic_type_ok() {
198 let path: Path = syn::parse_quote! { Foo<'a, String> };
199 assert!(matches!(path.single_generic_type(), Ok(syn::Type::Path(_))));
200 }
201
202 #[test]
203 fn single_generic_type_err() {
204 let path: Path = syn::parse_quote! { Foo<'a, String, u32> };
205 assert!(path.single_generic_type().is_err());
206
207 let path: Path = syn::parse_quote! { Foo<'a> };
208 assert!(path.single_generic_type().is_err());
209 }
210}