1use std::collections::HashMap;
4
5use ecow::{EcoString, EcoVec, eco_vec};
6use itertools::Itertools;
7use serde::{Deserialize, Serialize};
8use typst::diag::StrResult;
9use typst::syntax::FileId;
10use typst::syntax::package::PackageSpec;
11
12use crate::LocalContext;
13use crate::adt::interner::Interned;
14use crate::docs::file_id_repr;
15use crate::package::{PackageInfo, get_manifest_id};
16use crate::syntax::{Decl, DefKind, Expr, ExprInfo};
17
18use super::DefDocs;
19
20pub fn package_module_docs(ctx: &mut LocalContext, pkg: &PackageInfo) -> StrResult<PackageDefInfo> {
22 let toml_id = get_manifest_id(pkg)?;
23 let manifest = ctx.get_manifest(toml_id)?;
24
25 let entry_point = toml_id.join(&manifest.package.entrypoint);
26 module_docs(ctx, entry_point)
27}
28
29pub fn module_docs(ctx: &mut LocalContext, entry_point: FileId) -> StrResult<PackageDefInfo> {
31 let mut aliases = HashMap::new();
32 let mut extras = vec![];
33
34 let mut scan_ctx = ScanDefCtx {
35 ctx,
36 root: entry_point,
37 for_spec: entry_point.package(),
38 aliases: &mut aliases,
39 extras: &mut extras,
40 };
41
42 let ei = scan_ctx
43 .ctx
44 .expr_stage_by_id(entry_point)
45 .ok_or("entry point not found")?;
46 let mut defs = scan_ctx.defs(eco_vec![], ei);
47
48 let module_uses = aliases
49 .into_iter()
50 .map(|(fid, mut v)| {
51 v.sort_by(|a, b| a.len().cmp(&b.len()).then(a.cmp(b)));
52 (file_id_repr(fid), v.into())
53 })
54 .collect();
55
56 crate::log_debug_ct!("module_uses: {module_uses:#?}",);
57
58 defs.children.extend(extras);
59
60 Ok(PackageDefInfo {
61 root: defs,
62 module_uses,
63 })
64}
65
66#[derive(Debug, Clone, Default, Serialize, Deserialize)]
68pub struct DefInfo {
69 pub id: EcoString,
71 pub name: EcoString,
73 pub kind: DefKind,
75 pub loc: Option<(usize, usize, usize)>,
77 pub is_external: bool,
79 pub module_link: Option<String>,
81 pub symbol_link: Option<String>,
83 pub external_link: Option<String>,
85 pub oneliner: Option<String>,
87 pub docs: Option<EcoString>,
89 pub parsed_docs: Option<DefDocs>,
91 #[serde(skip)]
93 pub constant: Option<EcoString>,
94 #[serde(skip)]
97 pub decl: Option<Interned<Decl>>,
98 pub children: Vec<DefInfo>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct PackageDefInfo {
105 #[serde(flatten)]
107 pub root: DefInfo,
108 pub module_uses: HashMap<String, EcoVec<String>>,
110}
111
112struct ScanDefCtx<'a> {
113 ctx: &'a mut LocalContext,
114 for_spec: Option<&'a PackageSpec>,
115 aliases: &'a mut HashMap<FileId, Vec<String>>,
116 extras: &'a mut Vec<DefInfo>,
117 root: FileId,
118}
119
120impl ScanDefCtx<'_> {
121 fn defs(&mut self, paths: EcoVec<&str>, ei: ExprInfo) -> DefInfo {
122 let name = {
123 let stem = ei.fid.vpath().as_rooted_path().file_stem();
124 stem.and_then(|s| Some(Interned::new_str(s.to_str()?)))
125 .unwrap_or_default()
126 };
127 let module_decl = Decl::module(name.clone(), ei.fid).into();
128 let site = Some(self.root);
129 let paths = paths.clone();
130 self.def(&name, paths, site.as_ref(), &module_decl, None)
131 }
132
133 fn expr(
134 &mut self,
135 key: &str,
136 path: EcoVec<&str>,
137 site: Option<&FileId>,
138 val: &Expr,
139 ) -> DefInfo {
140 match val {
141 Expr::Decl(decl) => self.def(key, path, site, decl, Some(val)),
142 Expr::Ref(r) if r.root.is_some() => {
143 self.expr(key, path, site, r.root.as_ref().unwrap())
144 }
145 Expr::Select(..) => {
147 let mut path = path.clone();
148 path.push(key);
149 DefInfo {
150 name: key.to_string().into(),
151 kind: DefKind::Module,
152 ..Default::default()
153 }
154 }
155 _ => {
157 let mut path = path.clone();
158 path.push(key);
159 DefInfo {
160 name: key.to_string().into(),
161 kind: DefKind::Constant,
162 ..Default::default()
163 }
164 }
165 }
166 }
167
168 fn def(
169 &mut self,
170 key: &str,
171 path: EcoVec<&str>,
172 site: Option<&FileId>,
173 decl: &Interned<Decl>,
174 expr: Option<&Expr>,
175 ) -> DefInfo {
176 let def = self.ctx.def_of_decl(decl);
177 let def_docs = def.and_then(|def| self.ctx.def_docs(&def));
178 let docs = def_docs.as_ref().map(|docs| docs.docs().clone());
179 let children = match decl.as_ref() {
180 Decl::Module(..) => decl.file_id().and_then(|fid| {
181 if fid.package() != self.for_spec {
183 return None;
184 }
185
186 let aliases_vec = self.aliases.entry(fid).or_default();
188 let is_fresh = aliases_vec.is_empty();
189 aliases_vec.push(path.iter().join("."));
190
191 if !is_fresh {
192 crate::log_debug_ct!("found module: {path:?} (reexport)");
193 return None;
194 }
195
196 crate::log_debug_ct!("found module: {path:?}");
197
198 let ei = self.ctx.expr_stage_by_id(fid)?;
199
200 let symbols = ei
201 .exports
202 .iter()
203 .map(|(name, val)| {
204 let mut path = path.clone();
205 path.push(name);
206 self.expr(name, path.clone(), Some(&fid), val)
207 })
208 .collect();
209 Some(symbols)
210 }),
211 _ => None,
212 };
213
214 let mut head = DefInfo {
215 id: EcoString::new(),
216 name: key.to_string().into(),
217 kind: decl.kind(),
218 constant: expr.map(|expr| expr.repr()),
219 docs,
220 parsed_docs: def_docs,
221 decl: Some(decl.clone()),
222 children: children.unwrap_or_default(),
223 loc: None,
224 is_external: false,
225 module_link: None,
226 symbol_link: None,
227 external_link: None,
228 oneliner: None,
229 };
230
231 if let Some((span, mod_fid)) = head.decl.as_ref().and_then(|decl| decl.file_id()).zip(site)
232 && span != *mod_fid
233 {
234 head.is_external = true;
235 head.oneliner = head.docs.map(|docs| oneliner(&docs).to_owned());
236 head.docs = None;
237 }
238
239 if let Some(fid) = head.decl.as_ref().and_then(|del| del.file_id()) {
241 if fid.package() == self.for_spec {
243 let av = self.aliases.entry(fid).or_default();
244 if av.is_empty() {
245 let src = self.ctx.expr_stage_by_id(fid);
246 let mut path = path.clone();
247 path.push("-");
248 path.push(key);
249
250 crate::log_debug_ct!("found internal module: {path:?}");
251 if let Some(m) = src {
252 let msym = self.defs(path, m);
253 self.extras.push(msym)
254 }
255 }
256 }
257 }
258
259 head
260 }
261}
262
263fn oneliner(docs: &str) -> &str {
265 docs.lines().next().unwrap_or_default()
266}