tinymist_analysis/docs/
def.rs

1use core::fmt;
2use std::collections::{BTreeMap, HashMap};
3use std::sync::{Arc, OnceLock};
4
5use ecow::{EcoString, eco_format};
6use serde::{Deserialize, Serialize};
7
8use super::tidy::*;
9use crate::syntax::DeclExpr;
10use crate::ty::{Interned, ParamAttrs, ParamTy, StrRef, Ty, TypeVarBounds};
11use crate::upstream::plain_docs_sentence;
12
13/// The documentation string of an item
14#[derive(Debug, Clone, Default)]
15pub struct DocString {
16    /// The documentation of the item
17    pub docs: Option<EcoString>,
18    /// The typing on definitions
19    pub var_bounds: HashMap<DeclExpr, TypeVarBounds>,
20    /// The variable doc associated with the item
21    pub vars: BTreeMap<StrRef, VarDoc>,
22    /// The type of the resultant type
23    pub res_ty: Option<Ty>,
24}
25
26impl DocString {
27    /// Gets the docstring as a variable doc
28    pub fn as_var(&self) -> VarDoc {
29        VarDoc {
30            docs: self.docs.clone().unwrap_or_default(),
31            ty: self.res_ty.clone(),
32        }
33    }
34
35    /// Get the documentation of a variable associated with the item
36    pub fn get_var(&self, name: &StrRef) -> Option<&VarDoc> {
37        self.vars.get(name)
38    }
39
40    /// Get the type of a variable associated with the item
41    pub fn var_ty(&self, name: &StrRef) -> Option<&Ty> {
42        self.get_var(name).and_then(|v| v.ty.as_ref())
43    }
44}
45
46/// The documentation string of a variable associated with some item.
47#[derive(Debug, Clone, Default)]
48pub struct VarDoc {
49    /// The documentation of the variable
50    pub docs: EcoString,
51    /// The type of the variable
52    pub ty: Option<Ty>,
53}
54
55impl VarDoc {
56    /// Convert the variable doc to an untyped version
57    pub fn to_untyped(&self) -> Arc<UntypedDefDocs> {
58        Arc::new(UntypedDefDocs::Variable(VarDocsT {
59            docs: self.docs.clone(),
60            return_ty: (),
61            def_docs: OnceLock::new(),
62        }))
63    }
64}
65
66type TypeRepr = Option<(
67    /* short */ EcoString,
68    /* long */ EcoString,
69    /* value */ EcoString,
70)>;
71
72/// Documentation about a definition (without type information).
73pub type UntypedDefDocs = DefDocsT<()>;
74/// Documentation about a definition.
75pub type DefDocs = DefDocsT<TypeRepr>;
76
77/// Documentation about a definition.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(tag = "kind")]
80pub enum DefDocsT<T> {
81    /// Documentation about a function.
82    #[serde(rename = "func")]
83    Function(Box<SignatureDocsT<T>>),
84    /// Documentation about a variable.
85    #[serde(rename = "var")]
86    Variable(VarDocsT<T>),
87    /// Documentation about a module.
88    #[serde(rename = "module")]
89    Module(TidyModuleDocs),
90    /// Other kinds of documentation.
91    #[serde(rename = "plain")]
92    Plain {
93        /// The content of the documentation.
94        docs: EcoString,
95    },
96}
97
98impl<T> DefDocsT<T> {
99    /// Get the markdown representation of the documentation.
100    pub fn docs(&self) -> &EcoString {
101        match self {
102            Self::Function(docs) => &docs.docs,
103            Self::Variable(docs) => &docs.docs,
104            Self::Module(docs) => &docs.docs,
105            Self::Plain { docs } => docs,
106        }
107    }
108}
109
110impl DefDocs {
111    /// Get full documentation for the signature.
112    pub fn hover_docs(&self) -> EcoString {
113        match self {
114            DefDocs::Function(docs) => docs.hover_docs().clone(),
115            _ => plain_docs_sentence(self.docs()),
116        }
117    }
118}
119
120/// Describes a primary function signature.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct SignatureDocsT<T> {
123    /// Documentation for the function.
124    pub docs: EcoString,
125    /// The positional parameters.
126    pub pos: Vec<ParamDocsT<T>>,
127    /// The named parameters.
128    pub named: BTreeMap<Interned<str>, ParamDocsT<T>>,
129    /// The rest parameter.
130    pub rest: Option<ParamDocsT<T>>,
131    /// The return type.
132    pub ret_ty: T,
133    /// The full documentation for the signature.
134    #[serde(skip)]
135    pub hover_docs: OnceLock<EcoString>,
136}
137
138impl SignatureDocsT<TypeRepr> {
139    /// Get full documentation for the signature.
140    pub fn hover_docs(&self) -> &EcoString {
141        self.hover_docs
142            .get_or_init(|| plain_docs_sentence(&format!("{}", SigHoverDocs(self))))
143    }
144}
145
146struct SigHoverDocs<'a>(&'a SignatureDocs);
147
148impl fmt::Display for SigHoverDocs<'_> {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        let docs = self.0;
151        let base_docs = docs.docs.trim();
152
153        if !base_docs.is_empty() {
154            f.write_str(base_docs)?;
155        }
156
157        fn write_param_docs(
158            f: &mut fmt::Formatter<'_>,
159            docs: &ParamDocsT<TypeRepr>,
160            kind: &str,
161            is_first: &mut bool,
162        ) -> fmt::Result {
163            if *is_first {
164                *is_first = false;
165                write!(f, "\n\n## {}\n\n", docs.name)?;
166            } else {
167                write!(f, "\n\n## {} ({kind})\n\n", docs.name)?;
168            }
169
170            // p.cano_type.0
171            if let Some(t) = &docs.cano_type {
172                write!(f, "```typc\ntype: {}\n```\n\n", t.2)?;
173            }
174
175            f.write_str(docs.docs.trim())?;
176
177            Ok(())
178        }
179
180        if !docs.pos.is_empty() {
181            f.write_str("\n\n# Positional Parameters")?;
182
183            let mut is_first = true;
184            for pos_docs in &docs.pos {
185                write_param_docs(f, pos_docs, "positional", &mut is_first)?;
186            }
187        }
188
189        if docs.rest.is_some() {
190            f.write_str("\n\n# Rest Parameters")?;
191
192            let mut is_first = true;
193            if let Some(rest) = &docs.rest {
194                write_param_docs(f, rest, "spread right", &mut is_first)?;
195            }
196        }
197
198        if !docs.named.is_empty() {
199            f.write_str("\n\n# Named Parameters")?;
200
201            let mut is_first = true;
202            for named_docs in docs.named.values() {
203                write_param_docs(f, named_docs, "named", &mut is_first)?;
204            }
205        }
206
207        Ok(())
208    }
209}
210
211/// Documentation about a signature.
212pub type UntypedSignatureDocs = SignatureDocsT<()>;
213/// Documentation about a signature.
214pub type SignatureDocs = SignatureDocsT<TypeRepr>;
215
216impl SignatureDocs {
217    /// Get the markdown representation of the documentation.
218    pub fn print(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
219        let mut is_first = true;
220        let mut write_sep = |f: &mut dyn std::fmt::Write| {
221            if is_first {
222                is_first = false;
223                return f.write_str("\n  ");
224            }
225            f.write_str(",\n  ")
226        };
227
228        f.write_char('(')?;
229        for pos_docs in &self.pos {
230            write_sep(f)?;
231            f.write_str(&pos_docs.name)?;
232            if let Some(t) = &pos_docs.cano_type {
233                write!(f, ": {}", t.0)?;
234            }
235        }
236        if let Some(rest) = &self.rest {
237            write_sep(f)?;
238            f.write_str("..")?;
239            f.write_str(&rest.name)?;
240            if let Some(t) = &rest.cano_type {
241                write!(f, ": {}", t.0)?;
242            }
243        }
244
245        if !self.named.is_empty() {
246            let mut name_prints = vec![];
247            for v in self.named.values() {
248                let ty = v.cano_type.as_ref().map(|t| &t.0);
249                name_prints.push((v.name.clone(), ty, v.default.clone()))
250            }
251            name_prints.sort();
252            for (name, ty, val) in name_prints {
253                write_sep(f)?;
254                let val = val.as_deref().unwrap_or("any");
255                let mut default = val.trim();
256                if default.starts_with('{') && default.ends_with('}') && default.len() > 30 {
257                    default = "{ .. }"
258                }
259                if default.starts_with('`') && default.ends_with('`') && default.len() > 30 {
260                    default = "raw"
261                }
262                if default.starts_with('[') && default.ends_with(']') && default.len() > 30 {
263                    default = "content"
264                }
265                f.write_str(&name)?;
266                if let Some(ty) = ty {
267                    write!(f, ": {ty}")?;
268                }
269                if default.contains('\n') {
270                    write!(f, " = {}", default.replace("\n", "\n  "))?;
271                } else {
272                    write!(f, " = {default}")?;
273                }
274            }
275        }
276        if !is_first {
277            f.write_str(",\n")?;
278        }
279        f.write_char(')')?;
280
281        Ok(())
282    }
283}
284
285/// Documentation about a variable (without type information).
286pub type UntypedVarDocs = VarDocsT<()>;
287/// Documentation about a variable.
288pub type VarDocs = VarDocsT<Option<(EcoString, EcoString, EcoString)>>;
289
290/// Describes a primary pattern binding.
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct VarDocsT<T> {
293    /// Documentation for the pattern binding.
294    pub docs: EcoString,
295    /// The inferred type of the pattern binding source.
296    pub return_ty: T,
297    /// Cached documentation for the definition.
298    #[serde(skip)]
299    pub def_docs: OnceLock<String>,
300}
301
302impl VarDocs {
303    /// Get the markdown representation of the documentation.
304    pub fn def_docs(&self) -> &String {
305        self.def_docs
306            .get_or_init(|| plain_docs_sentence(&self.docs).into())
307    }
308}
309
310/// Documentation about a parameter (without type information).
311pub type TypelessParamDocs = ParamDocsT<()>;
312/// Documentation about a parameter.
313pub type ParamDocs = ParamDocsT<TypeRepr>;
314
315/// Describes a function parameter.
316#[derive(Debug, Clone, Serialize, Deserialize, Default)]
317pub struct ParamDocsT<T> {
318    /// The parameter's name.
319    pub name: Interned<str>,
320    /// Documentation for the parameter.
321    pub docs: EcoString,
322    /// Inferred type of the parameter.
323    pub cano_type: T,
324    /// The parameter's default name as value.
325    pub default: Option<EcoString>,
326    /// The attribute of the parameter.
327    #[serde(flatten)]
328    pub attrs: ParamAttrs,
329}
330
331impl ParamDocs {
332    /// Create a new parameter documentation.
333    pub fn new(param: &ParamTy, ty: Option<&Ty>) -> Self {
334        Self {
335            name: param.name.as_ref().into(),
336            docs: param.docs.clone().unwrap_or_default(),
337            cano_type: format_ty(ty.or(Some(&param.ty))),
338            default: param.default.clone(),
339            attrs: param.attrs,
340        }
341    }
342}
343
344/// Formats the type.
345pub fn format_ty(ty: Option<&Ty>) -> TypeRepr {
346    let ty = ty?;
347    let short = ty.repr().unwrap_or_else(|| "any".into());
348    let long = eco_format!("{ty:?}");
349    let value = ty.value_repr().unwrap_or_else(|| "".into());
350
351    Some((short, long, value))
352}