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 module_decl = Decl::module(ei.fid);
123 let key = module_decl.name().clone();
124 let site = Some(self.root);
125 let paths = paths.clone();
126 self.def(&key, paths, site.as_ref(), &module_decl.into(), None)
127 }
128
129 fn expr(
130 &mut self,
131 key: &str,
132 path: EcoVec<&str>,
133 site: Option<&FileId>,
134 val: &Expr,
135 ) -> DefInfo {
136 match val {
137 Expr::Decl(decl) => self.def(key, path, site, decl, Some(val)),
138 Expr::Ref(r) if r.root.is_some() => {
139 self.expr(key, path, site, r.root.as_ref().unwrap())
140 }
141 Expr::Select(..) => {
143 let mut path = path.clone();
144 path.push(key);
145 DefInfo {
146 name: key.to_string().into(),
147 kind: DefKind::Module,
148 ..Default::default()
149 }
150 }
151 _ => {
153 let mut path = path.clone();
154 path.push(key);
155 DefInfo {
156 name: key.to_string().into(),
157 kind: DefKind::Constant,
158 ..Default::default()
159 }
160 }
161 }
162 }
163
164 fn def(
165 &mut self,
166 key: &str,
167 path: EcoVec<&str>,
168 site: Option<&FileId>,
169 decl: &Interned<Decl>,
170 expr: Option<&Expr>,
171 ) -> DefInfo {
172 let def = self.ctx.def_of_decl(decl);
173 let def_docs = def.and_then(|def| self.ctx.def_docs(&def));
174 let docs = def_docs.as_ref().map(|docs| docs.docs().clone());
175 let children = match decl.as_ref() {
176 Decl::Module(..) => decl.file_id().and_then(|fid| {
177 if fid.package() != self.for_spec {
179 return None;
180 }
181
182 let aliases_vec = self.aliases.entry(fid).or_default();
184 let is_fresh = aliases_vec.is_empty();
185 aliases_vec.push(path.iter().join("."));
186
187 if !is_fresh {
188 crate::log_debug_ct!("found module: {path:?} (reexport)");
189 return None;
190 }
191
192 crate::log_debug_ct!("found module: {path:?}");
193
194 let ei = self.ctx.expr_stage_by_id(fid)?;
195
196 let symbols = ei
197 .exports
198 .iter()
199 .map(|(name, val)| {
200 let mut path = path.clone();
201 path.push(name);
202 self.expr(name, path.clone(), Some(&fid), val)
203 })
204 .collect();
205 Some(symbols)
206 }),
207 _ => None,
208 };
209
210 let mut head = DefInfo {
211 id: EcoString::new(),
212 name: key.to_string().into(),
213 kind: decl.kind(),
214 constant: expr.map(|expr| expr.repr()),
215 docs,
216 parsed_docs: def_docs,
217 decl: Some(decl.clone()),
218 children: children.unwrap_or_default(),
219 loc: None,
220 is_external: false,
221 module_link: None,
222 symbol_link: None,
223 external_link: None,
224 oneliner: None,
225 };
226
227 if let Some((span, mod_fid)) = head.decl.as_ref().and_then(|decl| decl.file_id()).zip(site)
228 && span != *mod_fid
229 {
230 head.is_external = true;
231 head.oneliner = head.docs.map(|docs| oneliner(&docs).to_owned());
232 head.docs = None;
233 }
234
235 if let Some(fid) = head.decl.as_ref().and_then(|del| del.file_id()) {
237 if fid.package() == self.for_spec {
239 let av = self.aliases.entry(fid).or_default();
240 if av.is_empty() {
241 let src = self.ctx.expr_stage_by_id(fid);
242 let mut path = path.clone();
243 path.push("-");
244 path.push(key);
245
246 crate::log_debug_ct!("found internal module: {path:?}");
247 if let Some(m) = src {
248 let msym = self.defs(path, m);
249 self.extras.push(msym)
250 }
251 }
252 }
253 }
254
255 head
256 }
257}
258
259fn oneliner(docs: &str) -> &str {
261 docs.lines().next().unwrap_or_default()
262}