rune/doc/
visitor.rs
1use crate::alloc;
2use crate::alloc::hash_map::{self, HashMap};
3use crate::alloc::prelude::*;
4use crate::alloc::{Box, String, Vec};
5use crate::compile::meta;
6use crate::compile::{CompileVisitor, Located, MetaError, MetaRef, Names};
7use crate::item::IntoComponent;
8use crate::{Hash, Item, ItemBuf};
9
10pub(crate) struct VisitorData {
11 #[cfg_attr(not(feature = "cli"), allow(dead_code))]
12 pub(crate) item: ItemBuf,
13 #[cfg_attr(not(feature = "cli"), allow(dead_code))]
14 pub(crate) hash: Hash,
15 pub(crate) kind: Option<meta::Kind>,
16 #[cfg_attr(not(feature = "cli"), allow(dead_code))]
17 pub(crate) deprecated: Option<String>,
18 pub(crate) docs: Vec<String>,
19 pub(crate) field_docs: HashMap<Box<str>, Vec<String>>,
20}
21
22impl VisitorData {
23 fn new(item: ItemBuf, hash: Hash, kind: Option<meta::Kind>) -> Self {
24 Self {
25 item,
26 hash,
27 kind,
28 deprecated: None,
29 docs: Vec::new(),
30 field_docs: HashMap::new(),
31 }
32 }
33}
34
35pub struct Visitor {
37 pub(crate) base: ItemBuf,
38 pub(crate) names: Names,
39 pub(crate) data: HashMap<Hash, VisitorData>,
40 pub(crate) item_to_hash: HashMap<ItemBuf, Hash>,
41 pub(crate) associated: HashMap<Hash, Vec<Hash>>,
43}
44
45impl Visitor {
46 pub fn new(base: impl IntoIterator<Item: IntoComponent>) -> alloc::Result<Self> {
48 let mut this = Self {
49 base: base.into_iter().try_collect::<ItemBuf>()?,
50 names: Names::default(),
51 data: HashMap::default(),
52 item_to_hash: HashMap::new(),
53 associated: HashMap::new(),
54 };
55
56 this.names.insert(&this.base)?;
57
58 let mut it = this.base.iter();
59
60 while !it.as_item().is_empty() {
61 let hash = Hash::type_hash(it.as_item());
62
63 this.data.try_insert(
64 hash,
65 VisitorData::new(it.as_item().try_to_owned()?, hash, Some(meta::Kind::Module)),
66 )?;
67
68 this.item_to_hash
69 .try_insert(it.as_item().try_to_owned()?, hash)?;
70 it.next_back();
71 }
72
73 Ok(this)
74 }
75
76 #[cfg(feature = "cli")]
78 pub(crate) fn get(&self, item: &Item) -> Option<&VisitorData> {
79 let hash = self.item_to_hash.get(item)?;
80 self.data.get(hash)
81 }
82
83 pub(crate) fn get_by_hash(&self, hash: Hash) -> Option<&VisitorData> {
85 self.data.get(&hash)
86 }
87
88 fn get_or_insert(&mut self, item: &Item) -> Result<&mut VisitorData, MetaError> {
89 let item = self.base.join(item)?;
90 let hash = Hash::type_hash(&item);
91
92 tracing::trace!(?item, ?hash, "getting");
93
94 let data = match self.data.entry(hash) {
95 hash_map::Entry::Occupied(e) => e.into_mut(),
96 hash_map::Entry::Vacant(e) => {
97 e.try_insert(VisitorData::new(item.try_to_owned()?, hash, None))?
98 }
99 };
100
101 Ok(data)
102 }
103}
104
105impl CompileVisitor for Visitor {
106 fn register_meta(&mut self, meta: MetaRef<'_>) -> Result<(), MetaError> {
107 if meta.context {
109 return Ok(());
110 }
111
112 let item = self.base.join(meta.item)?;
113 let hash = Hash::type_hash(&item);
114
115 tracing::trace!(base = ?self.base, meta = ?meta.item, ?item, ?hash, "register meta");
116
117 self.names.insert(&item)?;
118 self.item_to_hash.try_insert(item.try_to_owned()?, hash)?;
119
120 match self.data.entry(hash) {
121 hash_map::Entry::Occupied(e) => {
122 e.into_mut().kind = Some(meta.kind.try_clone()?);
123 }
124 hash_map::Entry::Vacant(e) => {
125 e.try_insert(VisitorData::new(item, hash, Some(meta.kind.try_clone()?)))?;
126 }
127 }
128
129 if let Some(container) = meta.kind.associated_container() {
130 self.associated
131 .entry(container)
132 .or_try_default()?
133 .try_push(hash)?;
134 }
135
136 Ok(())
137 }
138
139 fn visit_doc_comment(
140 &mut self,
141 _location: &dyn Located,
142 item: &Item,
143 _: Hash,
144 string: &str,
145 ) -> Result<(), MetaError> {
146 let data = self.get_or_insert(item)?;
147
148 data.docs
149 .try_push(string.trim_end_matches(newlines).try_to_owned()?)?;
150
151 Ok(())
152 }
153
154 fn visit_field_doc_comment(
155 &mut self,
156 _location: &dyn Located,
157 item: &Item,
158 _: Hash,
159 field: &str,
160 string: &str,
161 ) -> Result<(), MetaError> {
162 let data = self.get_or_insert(item)?;
163
164 data.field_docs
165 .entry(field.try_into()?)
166 .or_try_default()?
167 .try_push(string.trim_end_matches(newlines).try_to_owned()?)?;
168
169 Ok(())
170 }
171}
172
173fn newlines(c: char) -> bool {
177 matches!(c, '\n' | '\r')
178}