Skip to content

Commit 23dd267

Browse files
crowlKatsclaude
andauthored
fix: improve HTML doc generation (#793)
* fix: improve HTML doc generation for JSR - Handle JS reserved words in import usage identifiers by capitalizing (#1092) - Render interface construct signatures (new()) in docs (#1250) - Inherit JSDoc from implemented interfaces for undocumented class members (#1013) - Use TypeRefResolution to properly link type params and cross-module imports (#922) - Fix partition deduplication to preserve non-reference declarations (#957) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fmt --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 043748c commit 23dd267

13 files changed

Lines changed: 575 additions & 43 deletions

src/html/partition.rs

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ where
3232
) where
3333
F: Fn(&mut IndexMap<T, Vec<DocNodeWithContext>>, &DocNodeWithContext),
3434
{
35-
'outer: for node in doc_nodes {
35+
for node in doc_nodes {
36+
let mut has_reference = false;
37+
3638
for decl in &node.declarations {
3739
if flatten_namespaces
3840
&& matches!(decl.def, DeclarationDef::Namespace(..))
@@ -56,6 +58,7 @@ where
5658
}
5759

5860
if let Some(reference) = decl.reference_def() {
61+
has_reference = true;
5962
if visited_refs.insert(reference.target.clone()) {
6063
partitioner_inner(
6164
ctx,
@@ -68,12 +71,28 @@ where
6871
);
6972
visited_refs.remove(&reference.target);
7073
}
71-
// hack until reference nodes are separate from normal symbols
72-
continue 'outer;
7374
}
7475
}
7576

76-
process(partitions, &node);
77+
if has_reference {
78+
// If the symbol has non-reference declarations alongside
79+
// references, emit a node containing only the non-reference
80+
// declarations so they are not lost.
81+
let non_ref_decls: Vec<_> = node
82+
.declarations
83+
.iter()
84+
.filter(|d| d.reference_def().is_none())
85+
.cloned()
86+
.collect();
87+
if !non_ref_decls.is_empty() {
88+
let mut stripped = (*node).clone();
89+
std::sync::Arc::make_mut(&mut stripped.inner).declarations =
90+
non_ref_decls;
91+
process(partitions, &stripped);
92+
}
93+
} else {
94+
process(partitions, &node);
95+
}
7796
}
7897
}
7998

@@ -227,7 +246,9 @@ pub fn flatten_namespace<'a>(
227246
) {
228247
let nodes: Vec<_> = doc_nodes.collect();
229248

230-
'outer: for node in &nodes {
249+
for node in &nodes {
250+
let mut has_reference = false;
251+
231252
for decl in &node.declarations {
232253
if matches!(decl.def, DeclarationDef::Namespace(..)) {
233254
let children: Vec<_> = node
@@ -247,6 +268,7 @@ pub fn flatten_namespace<'a>(
247268
}
248269

249270
if let Some(reference) = decl.reference_def() {
271+
has_reference = true;
250272
if visited_refs.insert(reference.target.clone()) {
251273
let resolved: Vec<_> = ctx
252274
.resolve_reference(parent_node, &reference.target)
@@ -261,12 +283,28 @@ pub fn flatten_namespace<'a>(
261283
);
262284
visited_refs.remove(&reference.target);
263285
}
264-
// hack until reference nodes are separate from normal symbols
265-
continue 'outer;
266286
}
267287
}
268288

269-
out.push((*node).clone());
289+
if has_reference {
290+
// If the symbol has non-reference declarations alongside
291+
// references, emit a node containing only the non-reference
292+
// declarations so they are not lost.
293+
let non_ref_decls: Vec<_> = node
294+
.declarations
295+
.iter()
296+
.filter(|d| d.reference_def().is_none())
297+
.cloned()
298+
.collect();
299+
if !non_ref_decls.is_empty() {
300+
let mut stripped = (**node).clone();
301+
std::sync::Arc::make_mut(&mut stripped.inner).declarations =
302+
non_ref_decls;
303+
out.push(Cow::Owned(stripped));
304+
}
305+
} else {
306+
out.push((*node).clone());
307+
}
270308
}
271309
}
272310

src/html/symbols/class.rs

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use crate::Declaration;
2+
use crate::DeclarationDef;
3+
use crate::class::ClassDef;
24
use crate::class::ClassMethodDef;
35
use crate::class::ClassPropertyDef;
46
use crate::diff::ConstructorDiff;
@@ -12,15 +14,71 @@ use crate::html::parameters::render_params;
1214
use crate::html::render_context::RenderContext;
1315
use crate::html::types::render_type_def_colon;
1416
use crate::html::util::*;
17+
use crate::interface::InterfaceDef;
1518
use crate::js_doc::JsDocTag;
19+
use crate::ts_type::TsTypeDefKind;
1620
use deno_ast::swc::ast::Accessibility;
1721
use deno_ast::swc::ast::MethodKind;
1822
use indexmap::IndexMap;
1923
use serde::Deserialize;
2024
use serde::Serialize;
2125
use std::collections::BTreeMap;
26+
use std::collections::HashMap;
2227
use std::collections::HashSet;
2328

29+
/// Collects method and property documentation from all interfaces
30+
/// implemented by a class, to be used as fallback when the class
31+
/// member lacks its own documentation.
32+
fn collect_inherited_docs(
33+
ctx: &RenderContext,
34+
class_def: &ClassDef,
35+
) -> HashMap<String, String> {
36+
let mut inherited = HashMap::new();
37+
38+
for implement in class_def.implements.iter() {
39+
let interface_name = match &implement.kind {
40+
TsTypeDefKind::TypeRef(type_ref) => &type_ref.type_name,
41+
_ => continue,
42+
};
43+
44+
// Search all doc nodes for a matching interface
45+
for nodes in ctx.ctx.doc_nodes.values() {
46+
for node in nodes {
47+
if node.get_name() != interface_name {
48+
continue;
49+
}
50+
for decl in &node.declarations {
51+
if let DeclarationDef::Interface(iface) = &decl.def {
52+
collect_docs_from_interface(&mut inherited, iface);
53+
}
54+
}
55+
}
56+
}
57+
}
58+
59+
inherited
60+
}
61+
62+
fn collect_docs_from_interface(
63+
inherited: &mut HashMap<String, String>,
64+
iface: &InterfaceDef,
65+
) {
66+
for method in &iface.methods {
67+
if let Some(doc) = &method.js_doc.doc {
68+
inherited
69+
.entry(method.name.clone())
70+
.or_insert_with(|| doc.to_string());
71+
}
72+
}
73+
for property in &iface.properties {
74+
if let Some(doc) = &property.js_doc.doc {
75+
inherited
76+
.entry(property.name.clone())
77+
.or_insert_with(|| doc.to_string());
78+
}
79+
}
80+
}
81+
2482
pub(crate) fn render_class(
2583
ctx: &RenderContext,
2684
symbol: &DocNodeWithContext,
@@ -47,6 +105,8 @@ pub(crate) fn render_class(
47105
.and_then(|d| d.as_class())
48106
});
49107

108+
let inherited_docs = collect_inherited_docs(ctx, class_def);
109+
50110
let class_items = partition_class_items(
51111
class_def.properties.clone(),
52112
class_def.methods.clone(),
@@ -89,6 +149,7 @@ pub(crate) fn render_class(
89149
class_items.static_properties,
90150
class_items.static_property_changes.as_ref(),
91151
class_items.static_method_changes.as_ref(),
152+
&inherited_docs,
92153
);
93154

94155
if !static_properties.is_empty() {
@@ -104,6 +165,7 @@ pub(crate) fn render_class(
104165
name,
105166
class_items.static_methods,
106167
class_items.static_method_changes.as_ref(),
168+
&inherited_docs,
107169
);
108170

109171
if !static_methods.is_empty() {
@@ -120,6 +182,7 @@ pub(crate) fn render_class(
120182
class_items.properties,
121183
class_items.property_changes.as_ref(),
122184
class_items.method_changes.as_ref(),
185+
&inherited_docs,
123186
);
124187

125188
if !properties.is_empty() {
@@ -135,6 +198,7 @@ pub(crate) fn render_class(
135198
name,
136199
class_items.methods,
137200
class_items.method_changes.as_ref(),
201+
&inherited_docs,
138202
);
139203

140204
if !methods.is_empty() {
@@ -585,6 +649,7 @@ fn render_class_accessor(
585649
getter: Option<&ClassMethodDef>,
586650
setter: Option<&ClassMethodDef>,
587651
method_changes: Option<&MethodsDiff>,
652+
inherited_docs: &HashMap<String, String>,
588653
) -> DocEntryCtx {
589654
let getter_or_setter = getter.or(setter).unwrap();
590655

@@ -605,7 +670,11 @@ fn render_class_accessor(
605670
})
606671
})
607672
.map_or_else(String::new, |ts_type| render_type_def_colon(ctx, ts_type));
608-
let js_doc = getter_or_setter.js_doc.doc.as_deref();
673+
let js_doc = getter_or_setter
674+
.js_doc
675+
.doc
676+
.as_deref()
677+
.or_else(|| inherited_docs.get(&**name).map(|s| s.as_str()));
609678

610679
let mut tags = Tag::from_js_doc(&getter_or_setter.js_doc);
611680
if let Some(tag) = Tag::from_accessibility(getter_or_setter.accessibility) {
@@ -689,6 +758,7 @@ fn render_class_method(
689758
method: &ClassMethodDef,
690759
i: usize,
691760
method_changes: Option<&MethodsDiff>,
761+
inherited_docs: &HashMap<String, String>,
692762
) -> Option<DocEntryCtx> {
693763
if method.function_def.has_body && i != 0 {
694764
return None;
@@ -750,6 +820,12 @@ fn render_class_method(
750820
(None, None, None)
751821
};
752822

823+
let doc = method
824+
.js_doc
825+
.doc
826+
.as_deref()
827+
.or_else(|| inherited_docs.get(&*method.name).map(|s| s.as_str()));
828+
753829
Some(DocEntryCtx::new(
754830
ctx,
755831
id,
@@ -766,7 +842,7 @@ fn render_class_method(
766842
&method.function_def.return_type,
767843
),
768844
tags,
769-
method.js_doc.doc.as_deref(),
845+
doc,
770846
&method.location,
771847
diff_status,
772848
old_content,
@@ -780,6 +856,7 @@ fn render_class_property(
780856
class_name: &str,
781857
property: &ClassPropertyDef,
782858
property_changes: Option<&PropertiesDiff>,
859+
inherited_docs: &HashMap<String, String>,
783860
) -> DocEntryCtx {
784861
let id = IdBuilder::new(ctx)
785862
.kind(IdKind::Property)
@@ -835,6 +912,12 @@ fn render_class_property(
835912
(None, None, None)
836913
};
837914

915+
let doc = property
916+
.js_doc
917+
.doc
918+
.as_deref()
919+
.or_else(|| inherited_docs.get(&*property.name).map(|s| s.as_str()));
920+
838921
DocEntryCtx::new(
839922
ctx,
840923
id,
@@ -846,7 +929,7 @@ fn render_class_property(
846929
)),
847930
&ts_type,
848931
tags,
849-
property.js_doc.doc.as_deref(),
932+
doc,
850933
&property.location,
851934
diff_status,
852935
old_content,
@@ -906,15 +989,20 @@ fn render_class_properties(
906989
properties: Vec<PropertyOrMethod>,
907990
property_changes: Option<&PropertiesDiff>,
908991
method_changes: Option<&MethodsDiff>,
992+
inherited_docs: &HashMap<String, String>,
909993
) -> Vec<DocEntryCtx> {
910994
let mut properties = properties.into_iter().peekable();
911995
let mut out = vec![];
912996

913997
while let Some(property) = properties.next() {
914998
let content = match property {
915-
PropertyOrMethod::Property(property) => {
916-
render_class_property(ctx, class_name, &property, property_changes)
917-
}
999+
PropertyOrMethod::Property(property) => render_class_property(
1000+
ctx,
1001+
class_name,
1002+
&property,
1003+
property_changes,
1004+
inherited_docs,
1005+
),
9181006
PropertyOrMethod::Method(method) => {
9191007
let (getter, setter) = if method.kind == MethodKind::Getter {
9201008
let next_is_setter = properties
@@ -945,6 +1033,7 @@ fn render_class_properties(
9451033
getter,
9461034
setter.as_ref(),
9471035
method_changes,
1036+
inherited_docs,
9481037
)
9491038
}
9501039
};
@@ -1044,12 +1133,20 @@ fn render_class_methods(
10441133
class_name: &str,
10451134
methods: BTreeMap<Box<str>, Vec<ClassMethodDef>>,
10461135
method_changes: Option<&MethodsDiff>,
1136+
inherited_docs: &HashMap<String, String>,
10471137
) -> Vec<DocEntryCtx> {
10481138
let mut out: Vec<DocEntryCtx> = methods
10491139
.values()
10501140
.flat_map(|methods| {
10511141
methods.iter().enumerate().filter_map(|(i, method)| {
1052-
render_class_method(ctx, class_name, method, i, method_changes)
1142+
render_class_method(
1143+
ctx,
1144+
class_name,
1145+
method,
1146+
i,
1147+
method_changes,
1148+
inherited_docs,
1149+
)
10531150
})
10541151
})
10551152
.collect();

0 commit comments

Comments
 (0)