1use std::hash::{Hash, Hasher};
2
3use rustdoc_types::{
4 ExternalCrate, GenericArg, GenericArgs, Id, Item, ItemEnum, ItemSummary, Path, Type,
5};
6use serde::{Deserialize, Serialize};
7
8use super::item::{
9 field_ids, has_associated_item, has_field, has_variant, is_impl_for, variant_ids,
10};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct GlobalId {
14 pub crate_: String,
15 pub id: u32,
16}
17
18#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
19pub struct CrateNode {
20 pub id: GlobalId,
21 pub crate_: ExternalCrate,
22}
23
24impl Hash for CrateNode {
25 fn hash<H: Hasher>(&self, state: &mut H) {
26 self.id.hash(state);
27 }
28}
29
30impl PartialEq for CrateNode {
31 fn eq(&self, other: &Self) -> bool {
32 self.id == other.id
33 }
34}
35
36impl CrateNode {
37 pub fn new(crate_name: String, id: u32, crate_: ExternalCrate) -> Self {
38 Self {
39 id: GlobalId {
40 crate_: crate_name,
41 id,
42 },
43 crate_,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
49pub struct SummaryNode {
50 pub id: GlobalId,
51 pub summary: ItemSummary,
52}
53
54impl Hash for SummaryNode {
55 fn hash<H: Hasher>(&self, state: &mut H) {
56 self.id.hash(state);
57 }
58}
59
60impl PartialEq for SummaryNode {
61 fn eq(&self, other: &Self) -> bool {
62 self.id == other.id
63 }
64}
65
66impl SummaryNode {
67 pub fn new(crate_: String, id: u32, summary: ItemSummary) -> Self {
68 Self {
69 id: GlobalId { crate_, id },
70 summary,
71 }
72 }
73
74 pub fn in_same_module_as(&self, other: &SummaryNode) -> bool {
75 let this = &self.summary.path;
76 let other = &other.summary.path;
77
78 if this.len() != other.len() {
79 return false;
80 }
81
82 this[..(this.len() - 1)] == other[..(other.len() - 1)]
83 }
84
85 pub fn points_to_crate(&self, crate_: &CrateNode) -> bool {
86 self.id.crate_ == crate_.id.crate_ && self.summary.crate_id == crate_.id.id
87 }
88}
89
90#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
91pub struct ItemNode {
92 pub id: GlobalId,
93 pub item: Item,
94}
95
96impl Hash for ItemNode {
97 fn hash<H: Hasher>(&self, state: &mut H) {
98 self.id.hash(state);
99 }
100}
101
102impl PartialEq for ItemNode {
103 fn eq(&self, other: &Self) -> bool {
104 self.id == other.id
105 }
106}
107
108impl ItemNode {
109 pub fn new(crate_: String, item: Item) -> Self {
110 Self {
111 id: GlobalId {
112 crate_,
113 id: item.id.0,
114 },
115 item,
116 }
117 }
118
119 pub fn name(&self) -> Option<&str> {
120 let mut new_name = "";
121 for attr in &self.item.attrs {
122 if let Some((_, n)) =
123 lazy_regex::regex_captures!(r#"\[serde\(rename\s*=\s*"(\w+)"\)\]"#, attr)
124 {
125 new_name = n;
126 }
127 }
128 if new_name.is_empty() {
129 self.item.name.as_deref()
130 } else {
131 Some(new_name)
132 }
133 }
134
135 pub fn has_summary(&self, summary: &SummaryNode) -> bool {
136 self.id == summary.id
137 }
138
139 pub fn is_impl_for(&self, for_: &ItemNode, trait_name: &str) -> bool {
140 if self.id.crate_ != for_.id.crate_ {
141 return false;
142 }
143
144 is_impl_for(&self.item, &for_.item, trait_name)
145 }
146
147 pub fn is_range(&self) -> bool {
148 matches!(
149 &self.item,
150 Item {
151 inner: ItemEnum::StructField(Type::ResolvedPath(Path { path, .. })),
152 ..
153 } if path == "std::ops::Range"
154 )
155 }
156
157 fn should_skip(&self) -> bool {
158 self.item
159 .attrs
160 .iter()
161 .any(|attr| lazy_regex::regex_is_match!(r#"\[serde\s*\(\s*skip\s*\)\s*\]"#, attr))
162 }
163
164 pub fn fields(&self, fields: &[(&ItemNode,)]) -> Vec<ItemNode> {
165 field_ids(&self.item)
166 .iter()
167 .filter_map(|id| {
168 fields
169 .iter()
170 .find(|(f,)| !f.should_skip() && id == &f.item.id)
171 .map(|found| found.0.clone())
172 })
173 .collect()
174 }
175
176 pub fn has_field(&self, field: &ItemNode) -> bool {
177 if self.id.crate_ != field.id.crate_ || field.should_skip() {
178 return false;
179 }
180 if field.name() == Some("__private_field") {
181 return false;
182 }
183
184 has_field(&self.item, &field.item)
185 }
186
187 pub fn variants(&self, variants: &[(&ItemNode,)]) -> Vec<ItemNode> {
188 variant_ids(&self.item)
189 .iter()
190 .filter_map(|id| {
191 variants
192 .iter()
193 .find(|(v,)| !v.should_skip() && id == &v.item.id)
194 .map(|found| found.0.clone())
195 })
196 .collect()
197 }
198
199 pub fn has_variant(&self, variant: &ItemNode) -> bool {
200 if self.id.crate_ != variant.id.crate_ || variant.should_skip() {
201 return false;
202 }
203
204 has_variant(&self.item, &variant.item)
205 }
206
207 pub fn is_of_local_type(&self, type_node: &ItemNode) -> bool {
208 self.is_of_type(&type_node.id, false)
209 }
210
211 pub fn is_of_remote_type(&self, type_node: &SummaryNode) -> bool {
212 self.is_of_type(&type_node.id, true)
213 }
214
215 fn is_of_type(&self, id: &GlobalId, is_remote: bool) -> bool {
216 if self.id.crate_ != id.crate_ {
217 return false;
218 }
219
220 match &self.item {
221 Item {
222 inner: ItemEnum::StructField(t),
223 ..
224 } => check_type(id, t, is_remote),
225 Item {
226 inner:
227 ItemEnum::AssocType {
228 type_: Some(Type::ResolvedPath(target)),
229 ..
230 },
231 ..
232 } => target.id.0 == id.id,
233 _ => false,
234 }
235 }
236
237 pub fn has_associated_item(&self, associated_item: &ItemNode, with_name: &str) -> bool {
238 if self.id.crate_ != associated_item.id.crate_ {
239 return false;
240 }
241
242 has_associated_item(&self.item, &associated_item.item, with_name)
243 }
244}
245
246fn check_type(parent: &GlobalId, type_: &Type, is_remote: bool) -> bool {
247 match type_ {
248 Type::ResolvedPath(path) => check_path(parent, path, is_remote),
249 Type::QualifiedPath {
250 self_type, args, ..
251 } => check_type(parent, self_type, is_remote) || check_args(parent, args, is_remote),
252 Type::Tuple(vec) => vec.iter().any(|t| check_type(parent, t, is_remote)),
253 Type::Slice(t) | Type::Array { type_: t, .. } => check_type(parent, t, is_remote),
254 _ => false,
255 }
256}
257
258fn check_path(
259 parent: &GlobalId,
260 Path {
261 path,
262 id: Id(id),
263 args,
264 }: &Path,
265 is_remote: bool,
266) -> bool {
267 if is_remote {
268 if let "Option" | "String" | "Vec" | "std::ops::Range" = path.as_str() {
269 return false;
270 }
271 }
272 id == &parent.id || {
273 if let Some(args) = args {
274 check_args(parent, args, is_remote)
275 } else {
276 false
277 }
278 }
279}
280
281fn check_args(parent: &GlobalId, args: &GenericArgs, is_remote: bool) -> bool {
282 match args {
283 GenericArgs::AngleBracketed { args, .. } => args.iter().any(|arg| match arg {
284 GenericArg::Type(t) => check_type(parent, t, is_remote),
285 _ => false,
286 }),
287 GenericArgs::Parenthesized { inputs, .. } => {
288 inputs.iter().any(|t| check_type(parent, t, is_remote))
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use std::collections::HashMap;
296
297 use pretty_assertions::assert_eq;
298 use rustdoc_types::{Generics, Id, Item, ItemEnum, ItemKind, Struct, StructKind, Visibility};
299
300 use super::*;
301
302 fn make_summary(id: u32, path: Vec<String>) -> SummaryNode {
303 SummaryNode::new(
304 "test".to_string(),
305 id,
306 ItemSummary {
307 crate_id: 0,
308 path,
309 kind: ItemKind::Struct,
310 },
311 )
312 }
313
314 fn make_node(name: Option<String>, attrs: Vec<String>) -> ItemNode {
315 ItemNode::new(
316 "test".to_string(),
317 Item {
318 name,
319 attrs,
320 inner: ItemEnum::Struct(Struct {
321 kind: StructKind::Plain {
322 fields: vec![],
323 has_stripped_fields: false,
324 },
325 generics: Generics {
326 params: vec![],
327 where_predicates: vec![],
328 },
329 impls: vec![],
330 }),
331 id: Id(0),
332 crate_id: 0,
333 span: None,
334 visibility: Visibility::Public,
335 docs: None,
336 links: HashMap::default(),
337 deprecation: None,
338 },
339 )
340 }
341
342 #[test]
343 fn test_in_same_module_as() {
344 let summary1 = make_summary(0, vec!["foo".to_string(), "bar".to_string()]);
345 let summary2 = make_summary(1, vec!["foo".to_string(), "baz".to_string()]);
346 assert!(summary1.in_same_module_as(&summary2));
347 }
348
349 #[test]
350 fn test_in_same_module_as_different_length() {
351 let summary1 = make_summary(0, vec!["foo".to_string(), "bar".to_string()]);
352 let summary2 = make_summary(1, vec!["foo".to_string()]);
353 assert!(!summary1.in_same_module_as(&summary2));
354 }
355
356 #[test]
357 fn test_in_same_module_as_different_module() {
358 let summary1 = make_summary(0, vec!["foo".to_string(), "bar".to_string()]);
359 let summary2 = make_summary(1, vec!["baz".to_string(), "bar".to_string()]);
360 assert!(!summary1.in_same_module_as(&summary2));
361 }
362
363 #[test]
364 fn test_get_name() {
365 let name = Some("Foo".to_string());
366 let attrs = vec![];
367 let node = make_node(name, attrs);
368 assert_eq!(node.name(), Some("Foo"));
369 }
370
371 #[test]
372 fn test_get_name_with_rename() {
373 let name = Some("Foo".to_string());
374 let attrs = vec![r#"#[serde(rename = "Bar")]"#.to_string()];
375 let node = make_node(name, attrs);
376 assert_eq!(node.name(), Some("Bar"));
377 }
378
379 #[test]
380 fn test_get_name_with_rename_no_whitespace() {
381 let name = Some("Foo".to_string());
382 let attrs = vec![r#"#[serde(rename="Bar")]"#.to_string()];
383 let node = make_node(name, attrs);
384 assert_eq!(node.name(), Some("Bar"));
385 }
386
387 #[test]
388 fn test_get_name_with_no_name() {
389 let name = None;
390 let attrs = vec![];
391 let node = make_node(name, attrs);
392 assert_eq!(node.name(), None);
393 }
394}