mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-02 17:32:16 +00:00
feat: merge func and variable docs parser (#812)
* feat: merge func and variable docs parser * test: update snapshot
This commit is contained in:
parent
7ab125bbc2
commit
c128f633d4
12 changed files with 80 additions and 141 deletions
|
@ -410,7 +410,7 @@ impl LocalContext {
|
|||
match def.decl.kind() {
|
||||
DefKind::Function => {
|
||||
let sig = self.sig_of_def(def.clone())?;
|
||||
let docs = crate::docs::sig_docs(&sig, None)?;
|
||||
let docs = crate::docs::sig_docs(&sig)?;
|
||||
Some(DefDocs::Function(Box::new(docs)))
|
||||
}
|
||||
DefKind::Struct | DefKind::Constant | DefKind::Variable => {
|
||||
|
|
|
@ -13,8 +13,11 @@ use crate::ty::Ty;
|
|||
use crate::ty::{DocSource, Interned};
|
||||
use crate::upstream::plain_docs_sentence;
|
||||
|
||||
type TypeRepr = Option<(/* short */ String, /* long */ String)>;
|
||||
type ShowTypeRepr<'a> = &'a mut dyn FnMut(Option<&Ty>) -> TypeRepr;
|
||||
type TypeRepr = Option<(
|
||||
/* short */ String,
|
||||
/* long */ String,
|
||||
/* value */ String,
|
||||
)>;
|
||||
|
||||
/// Documentation about a definition (without type information).
|
||||
pub type UntypedDefDocs = DefDocsT<()>;
|
||||
|
@ -86,13 +89,13 @@ impl SignatureDocsT<TypeRepr> {
|
|||
/// Get full documentation for the signature.
|
||||
pub fn hover_docs(&self) -> &EcoString {
|
||||
self.hover_docs
|
||||
.get_or_init(|| plain_docs_sentence(&format!("{}", SigDefDocs(self))))
|
||||
.get_or_init(|| plain_docs_sentence(&format!("{}", SigHoverDocs(self))))
|
||||
}
|
||||
}
|
||||
|
||||
struct SigDefDocs<'a>(&'a SignatureDocs);
|
||||
struct SigHoverDocs<'a>(&'a SignatureDocs);
|
||||
|
||||
impl fmt::Display for SigDefDocs<'_> {
|
||||
impl fmt::Display for SigHoverDocs<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let docs = self.0;
|
||||
let base_docs = docs.docs.trim();
|
||||
|
@ -213,6 +216,31 @@ impl SignatureDocs {
|
|||
}
|
||||
}
|
||||
|
||||
/// Documentation about a variable (without type information).
|
||||
pub type UntypedVarDocs = VarDocsT<()>;
|
||||
/// Documentation about a variable.
|
||||
pub type VarDocs = VarDocsT<Option<(String, String, String)>>;
|
||||
|
||||
/// Describes a primary pattern binding.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VarDocsT<T> {
|
||||
/// Documentation for the pattern binding.
|
||||
pub docs: EcoString,
|
||||
/// The inferred type of the pattern binding source.
|
||||
pub return_ty: T,
|
||||
/// Cached documentation for the definition.
|
||||
#[serde(skip)]
|
||||
pub def_docs: OnceLock<String>,
|
||||
}
|
||||
|
||||
impl VarDocs {
|
||||
/// Get the markdown representation of the documentation.
|
||||
pub fn def_docs(&self) -> &String {
|
||||
self.def_docs
|
||||
.get_or_init(|| plain_docs_sentence(&self.docs).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Documentation about a parameter (without type information).
|
||||
pub type TypelessParamDocs = ParamDocsT<()>;
|
||||
/// Documentation about a parameter.
|
||||
|
@ -235,24 +263,24 @@ pub struct ParamDocsT<T> {
|
|||
}
|
||||
|
||||
impl ParamDocs {
|
||||
fn new(param: &ParamSpec, ty: Option<&Ty>, doc_ty: Option<&mut ShowTypeRepr>) -> Self {
|
||||
fn new(param: &ParamSpec, ty: Option<&Ty>) -> Self {
|
||||
Self {
|
||||
name: param.name.as_ref().into(),
|
||||
docs: param.docs.clone().unwrap_or_default(),
|
||||
cano_type: format_ty(ty.or(Some(¶m.ty)), doc_ty),
|
||||
cano_type: format_ty(ty.or(Some(¶m.ty))),
|
||||
default: param.default.clone(),
|
||||
attrs: param.attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_ty(ty: Option<&Ty>, doc_ty: Option<&mut ShowTypeRepr>) -> TypeRepr {
|
||||
match doc_ty {
|
||||
Some(doc_ty) => doc_ty(ty),
|
||||
None => ty
|
||||
.and_then(|ty| ty.repr())
|
||||
.map(|short| (short, format!("{ty:?}"))),
|
||||
}
|
||||
fn format_ty(ty: Option<&Ty>) -> TypeRepr {
|
||||
let ty = ty?;
|
||||
let short = ty.repr().unwrap_or_else(|| "any".to_owned());
|
||||
let long = format!("{ty:?}");
|
||||
let value = ty.value_repr().unwrap_or_else(|| "".to_owned());
|
||||
|
||||
Some((short, long, value))
|
||||
}
|
||||
|
||||
pub(crate) fn var_docs(ctx: &mut LocalContext, pos: Span) -> Option<VarDocs> {
|
||||
|
@ -266,7 +294,7 @@ pub(crate) fn var_docs(ctx: &mut LocalContext, pos: Span) -> Option<VarDocs> {
|
|||
log::info!("check variable docs of ty: {ty:?} => {srcs:?}");
|
||||
let doc_source = srcs.into_iter().next()?;
|
||||
|
||||
let return_ty = ty.describe().map(|short| (short, format!("{ty:?}")));
|
||||
let return_ty = format_ty(Some(&ty));
|
||||
match doc_source {
|
||||
DocSource::Var(var) => {
|
||||
let docs = type_info
|
||||
|
@ -291,7 +319,7 @@ pub(crate) fn var_docs(ctx: &mut LocalContext, pos: Span) -> Option<VarDocs> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sig_docs(sig: &Signature, mut doc_ty: Option<ShowTypeRepr>) -> Option<SignatureDocs> {
|
||||
pub(crate) fn sig_docs(sig: &Signature) -> Option<SignatureDocs> {
|
||||
let type_sig = sig.type_sig().clone();
|
||||
|
||||
let pos_in = sig
|
||||
|
@ -310,19 +338,14 @@ pub(crate) fn sig_docs(sig: &Signature, mut doc_ty: Option<ShowTypeRepr>) -> Opt
|
|||
let ret_in = type_sig.body.as_ref();
|
||||
|
||||
let pos = pos_in
|
||||
.map(|(param, ty)| ParamDocs::new(param, ty, doc_ty.as_mut()))
|
||||
.map(|(param, ty)| ParamDocs::new(param, ty))
|
||||
.collect();
|
||||
let named = named_in
|
||||
.map(|(param, ty)| {
|
||||
(
|
||||
param.name.clone(),
|
||||
ParamDocs::new(param, ty, doc_ty.as_mut()),
|
||||
)
|
||||
})
|
||||
.map(|(param, ty)| (param.name.clone(), ParamDocs::new(param, ty)))
|
||||
.collect();
|
||||
let rest = rest_in.map(|(param, ty)| ParamDocs::new(param, ty, doc_ty.as_mut()));
|
||||
let rest = rest_in.map(|(param, ty)| ParamDocs::new(param, ty));
|
||||
|
||||
let ret_ty = format_ty(ret_in, doc_ty.as_mut());
|
||||
let ret_ty = format_ty(ret_in);
|
||||
|
||||
Some(SignatureDocs {
|
||||
docs: sig.primary().docs.clone().unwrap_or_default(),
|
||||
|
|
|
@ -196,7 +196,7 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
|
|||
{
|
||||
let _ = writeln!(md, "<!-- begin:param {} -->", param.name);
|
||||
let ty = match ¶m.cano_type {
|
||||
Some((short, _)) => short,
|
||||
Some((short, _, _)) => short,
|
||||
None => "unknown",
|
||||
};
|
||||
let _ = writeln!(
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
use std::sync::OnceLock;
|
||||
|
||||
use ecow::EcoString;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typst::diag::StrResult;
|
||||
|
||||
use crate::upstream::plain_docs_sentence;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TidyParamDocs {
|
||||
pub name: EcoString,
|
||||
|
@ -16,38 +12,18 @@ pub struct TidyParamDocs {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TidyFuncDocs {
|
||||
pub struct TidyPatDocs {
|
||||
pub docs: EcoString,
|
||||
pub return_ty: Option<EcoString>,
|
||||
pub params: Vec<TidyParamDocs>,
|
||||
}
|
||||
|
||||
/// Documentation about a variable (without type information).
|
||||
pub type UntypedVarDocs = VarDocsT<()>;
|
||||
/// Documentation about a variable.
|
||||
pub type VarDocs = VarDocsT<Option<(String, String)>>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VarDocsT<T> {
|
||||
pub docs: EcoString,
|
||||
pub return_ty: T,
|
||||
#[serde(skip)]
|
||||
pub def_docs: OnceLock<String>,
|
||||
}
|
||||
|
||||
impl VarDocs {
|
||||
pub fn def_docs(&self) -> &String {
|
||||
self.def_docs
|
||||
.get_or_init(|| plain_docs_sentence(&self.docs).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TidyModuleDocs {
|
||||
pub docs: EcoString,
|
||||
}
|
||||
|
||||
pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
|
||||
pub fn identify_pat_docs(converted: &str) -> StrResult<TidyPatDocs> {
|
||||
let lines = converted.lines().collect::<Vec<_>>();
|
||||
|
||||
let mut matching_return_ty = true;
|
||||
|
@ -153,54 +129,13 @@ pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
|
|||
};
|
||||
|
||||
params.reverse();
|
||||
Ok(TidyFuncDocs {
|
||||
Ok(TidyPatDocs {
|
||||
docs,
|
||||
return_ty,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn identify_var_docs(converted: EcoString) -> StrResult<VarDocs> {
|
||||
let lines = converted.lines().collect::<Vec<_>>();
|
||||
|
||||
let mut return_ty = None;
|
||||
let mut break_line = None;
|
||||
|
||||
let mut i = lines.len();
|
||||
loop {
|
||||
if i == 0 {
|
||||
break;
|
||||
}
|
||||
i -= 1;
|
||||
|
||||
let line = lines[i];
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(w) = line.trim_start().strip_prefix("->") else {
|
||||
break_line = Some(i + 1);
|
||||
break;
|
||||
};
|
||||
|
||||
// todo: convert me
|
||||
return_ty = Some((w.trim().into(), String::new()));
|
||||
break_line = Some(i);
|
||||
break;
|
||||
}
|
||||
|
||||
let docs = match break_line {
|
||||
Some(line_no) => (lines[..line_no]).iter().copied().join("\n").into(),
|
||||
None => converted,
|
||||
};
|
||||
|
||||
Ok(VarDocs {
|
||||
docs,
|
||||
return_ty,
|
||||
def_docs: OnceLock::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn identify_tidy_module_docs(docs: EcoString) -> StrResult<TidyModuleDocs> {
|
||||
Ok(TidyModuleDocs { docs })
|
||||
}
|
||||
|
@ -236,7 +171,7 @@ mod tests {
|
|||
use super::TidyParamDocs;
|
||||
|
||||
fn func(s: &str) -> String {
|
||||
let f = super::identify_func_docs(s).unwrap();
|
||||
let f = super::identify_pat_docs(s).unwrap();
|
||||
let mut res = format!(">> docs:\n{}\n<< docs", f.docs);
|
||||
if let Some(t) = f.return_ty {
|
||||
res.push_str(&format!("\n>>return\n{t}\n<<return"));
|
||||
|
@ -254,10 +189,10 @@ mod tests {
|
|||
}
|
||||
|
||||
fn var(s: &str) -> String {
|
||||
let f = super::identify_var_docs(s.into()).unwrap();
|
||||
let f = super::identify_pat_docs(s).unwrap();
|
||||
let mut res = format!(">> docs:\n{}\n<< docs", f.docs);
|
||||
if let Some(t) = f.return_ty {
|
||||
res.push_str(&format!("\n>>return\n{}\n<<return", t.0));
|
||||
res.push_str(&format!("\n>>return\n{t}\n<<return"));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
|||
input_file: crates/tinymist-query/src/fixtures/hover/annotate_docs_error.typ
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet speaker-note(\n note,\n mode: str = \"typ\",\n setting: (any) => any = Closure(..),\n) = none;\n```\n\n---\nSpeaker notes are a way to add additional information to your slides that is not visible to the audience. This can be useful for providing additional context or reminders to yourself.\n\n ## Example\n\n ```typ\n#speaker-note[This is a speaker note]\n\n```\n```\nRender Error\ncompiling node: error: unknown variable: speaker-note at \"/__render__.typ\":201..213\nHint: if you meant to use subtraction, try adding spaces around the minus sign: \\`speaker - note\\`\n\n```\n\n## Parameters\n\n@positional `note`\n\n@named `mode`\n\n@named `setting`",
|
||||
"contents": "```typc\nlet speaker-note(\n note: any,\n mode: str = \"typ\",\n setting: (any) => any = Closure(..),\n) = none;\n```\n\n---\nSpeaker notes are a way to add additional information to your slides that is not visible to the audience. This can be useful for providing additional context or reminders to yourself.\n\n ## Example\n\n ```typ\n#speaker-note[This is a speaker note]\n\n```\n```\nRender Error\ncompiling node: error: unknown variable: speaker-note at \"/__render__.typ\":201..213\nHint: if you meant to use subtraction, try adding spaces around the minus sign: \\`speaker - note\\`\n\n```\n\n## Parameters\n\n@positional `note`\n\n@named `mode`\n\n@named `setting`",
|
||||
"range": "11:20:11:32"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
|||
input_file: crates/tinymist-query/src/fixtures/hover/annotate_ret.typ
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet _delayed-wrapper(\n body,\n) = content;\n```\n\n---\n## Parameters\n\n@positional `body`",
|
||||
"contents": "```typc\nlet _delayed-wrapper(\n body: any,\n) = content;\n```\n\n---\n## Parameters\n\n@positional `body`",
|
||||
"range": "6:20:6:36"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
|||
input_file: crates/tinymist-query/src/fixtures/hover/param.typ
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet param;\n```\n\n---\nThe `parameter`.",
|
||||
"contents": "```typc\nlet param = any;\n```\n\n---\nThe `parameter`.",
|
||||
"range": "3:25:3:30"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
|||
input_file: crates/tinymist-query/src/fixtures/hover/value_repr.typ
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet f(\n x,\n y,\n z,\n w01: int = 1,\n w02: str = \"test\",\n w03: any = 1 + 2,\n w04 = Label(test),\n w05: function = box,\n w06: any = list.item,\n w07: content = Expr(..),\n w08: any = Expr(..),\n w09: any = 1 + 2,\n w10: array = (\n 1,\n 2,\n ),\n w11: array = (),\n w12: dict = (:),\n w13: dict = (a: 1),\n w14: dict = (a: box),\n w15: dict = (a: list.item),\n) = int;\n```\n\n---\n## Parameters\n\n@positional `x`\n\n@positional `y`\n\n@positional `z`\n\n@named `w01`\n\n@named `w02`\n\n@named `w03`\n\n@named `w04`\n\n@named `w05`\n\n@named `w06`\n\n@named `w07`\n\n@named `w08`\n\n@named `w09`\n\n@named `w10`\n\n@named `w11`\n\n@named `w12`\n\n@named `w13`\n\n@named `w14`\n\n@named `w15`",
|
||||
"contents": "```typc\nlet f(\n x: any,\n y: any,\n z: any,\n w01: int = 1,\n w02: str = \"test\",\n w03: any = 1 + 2,\n w04: any = Label(test),\n w05: function = box,\n w06: any = list.item,\n w07: content = Expr(..),\n w08: any = Expr(..),\n w09: any = 1 + 2,\n w10: array = (\n 1,\n 2,\n ),\n w11: array = (),\n w12: dict = (:),\n w13: dict = (a: 1),\n w14: dict = (a: box),\n w15: dict = (a: list.item),\n) = int;\n```\n\n---\n## Parameters\n\n@positional `x`\n\n@positional `y`\n\n@positional `z`\n\n@named `w01`\n\n@named `w02`\n\n@named `w03`\n\n@named `w04`\n\n@named `w05`\n\n@named `w06`\n\n@named `w07`\n\n@named `w08`\n\n@named `w09`\n\n@named `w10`\n\n@named `w11`\n\n@named `w12`\n\n@named `w13`\n\n@named `w14`\n\n@named `w15`",
|
||||
"range": "23:20:23:21"
|
||||
}
|
||||
|
|
|
@ -272,8 +272,8 @@ fn link_tooltip(
|
|||
Some(HoverContents::Array(results))
|
||||
}
|
||||
|
||||
fn push_result_ty(name: &str, ty_repr: Option<&(String, String)>, type_doc: &mut String) {
|
||||
let Some((short, _)) = ty_repr else {
|
||||
fn push_result_ty(name: &str, ty_repr: Option<&(String, String, String)>, type_doc: &mut String) {
|
||||
let Some((short, _, _)) = ty_repr else {
|
||||
return;
|
||||
};
|
||||
if short == name {
|
||||
|
|
|
@ -10,10 +10,7 @@ use typst::foundations::{IntoValue, Module, Str, Type};
|
|||
use crate::{
|
||||
adt::snapshot_map::SnapshotMap,
|
||||
analysis::SharedContext,
|
||||
docs::{
|
||||
convert_docs, identify_func_docs, identify_tidy_module_docs, identify_var_docs,
|
||||
UntypedDefDocs, VarDocsT,
|
||||
},
|
||||
docs::{convert_docs, identify_pat_docs, identify_tidy_module_docs, UntypedDefDocs, VarDocsT},
|
||||
prelude::*,
|
||||
syntax::{Decl, DefKind},
|
||||
ty::{BuiltinTy, Interned, PackageId, SigTy, StrRef, Ty, TypeBounds, TypeVar, TypeVarBounds},
|
||||
|
@ -87,13 +84,11 @@ pub(crate) fn compute_docstring(
|
|||
locals: SnapshotMap::default(),
|
||||
next_id: 0,
|
||||
};
|
||||
use DefKind::*;
|
||||
match kind {
|
||||
DefKind::Function => checker.check_func_docs(docs),
|
||||
DefKind::Variable => checker.check_var_docs(docs),
|
||||
DefKind::Module => checker.check_module_docs(docs),
|
||||
DefKind::Constant => None,
|
||||
DefKind::Struct => None,
|
||||
DefKind::Reference => None,
|
||||
Function | Variable => checker.check_pat_docs(docs),
|
||||
Module => checker.check_module_docs(docs),
|
||||
Constant | Struct | Reference => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,9 +105,9 @@ struct DocsChecker<'a> {
|
|||
static EMPTY_MODULE: LazyLock<Module> =
|
||||
LazyLock::new(|| Module::new("stub", typst::foundations::Scope::new()));
|
||||
impl<'a> DocsChecker<'a> {
|
||||
pub fn check_func_docs(mut self, docs: String) -> Option<DocString> {
|
||||
pub fn check_pat_docs(mut self, docs: String) -> Option<DocString> {
|
||||
let converted =
|
||||
convert_docs(self.ctx, &docs).and_then(|converted| identify_func_docs(&converted));
|
||||
convert_docs(self.ctx, &docs).and_then(|converted| identify_pat_docs(&converted));
|
||||
|
||||
let converted = match Self::fallback_docs(converted, &docs) {
|
||||
Ok(c) => c,
|
||||
|
@ -145,29 +140,6 @@ impl<'a> DocsChecker<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn check_var_docs(mut self, docs: String) -> Option<DocString> {
|
||||
let converted = convert_docs(self.ctx, &docs).and_then(identify_var_docs);
|
||||
|
||||
let converted = match Self::fallback_docs(converted, &docs) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Some(e),
|
||||
};
|
||||
|
||||
let module = self.ctx.module_by_str(docs);
|
||||
let module = module.as_ref().unwrap_or(EMPTY_MODULE.deref());
|
||||
|
||||
let res_ty = converted
|
||||
.return_ty
|
||||
.and_then(|ty| self.check_type_strings(module, &ty.0));
|
||||
|
||||
Some(DocString {
|
||||
docs: Some(converted.docs),
|
||||
var_bounds: self.vars,
|
||||
vars: BTreeMap::new(),
|
||||
res_ty,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn check_module_docs(self, docs: String) -> Option<DocString> {
|
||||
let converted = convert_docs(self.ctx, &docs).and_then(identify_tidy_module_docs);
|
||||
|
||||
|
|
|
@ -21,6 +21,15 @@ impl Ty {
|
|||
worker.describe_root(self)
|
||||
}
|
||||
|
||||
/// Describe available value instances of the given type.
|
||||
pub fn value_repr(&self) -> Option<String> {
|
||||
let mut worker = TypeDescriber {
|
||||
repr: true,
|
||||
..Default::default()
|
||||
};
|
||||
worker.describe_root(self)
|
||||
}
|
||||
|
||||
/// Describe the given type.
|
||||
pub fn describe(&self) -> Option<String> {
|
||||
let mut worker = TypeDescriber::default();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue