Skip to content

Commit 521df9d

Browse files
crowlKatsclaude
andcommitted
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>
1 parent 9d6c889 commit 521df9d

13 files changed

Lines changed: 565 additions & 41 deletions

src/html/partition.rs

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ where
2929
) where
3030
F: Fn(&mut IndexMap<T, Vec<DocNodeWithContext>>, &DocNodeWithContext),
3131
{
32-
'outer: for node in doc_nodes {
32+
for node in doc_nodes {
33+
let mut has_reference = false;
34+
3335
for decl in &node.declarations {
3436
if flatten_namespaces
3537
&& matches!(decl.def, DeclarationDef::Namespace(..))
@@ -52,6 +54,7 @@ where
5254
}
5355

5456
if let Some(reference) = decl.reference_def() {
57+
has_reference = true;
5558
partitioner_inner(
5659
ctx,
5760
partitions,
@@ -60,12 +63,28 @@ where
6063
flatten_namespaces,
6164
process,
6265
);
63-
// hack until reference nodes are separate from normal symbols
64-
continue 'outer;
6566
}
6667
}
6768

68-
process(partitions, &node);
69+
if has_reference {
70+
// If the symbol has non-reference declarations alongside
71+
// references, emit a node containing only the non-reference
72+
// declarations so they are not lost.
73+
let non_ref_decls: Vec<_> = node
74+
.declarations
75+
.iter()
76+
.filter(|d| d.reference_def().is_none())
77+
.cloned()
78+
.collect();
79+
if !non_ref_decls.is_empty() {
80+
let mut stripped = (*node).clone();
81+
std::sync::Arc::make_mut(&mut stripped.inner).declarations =
82+
non_ref_decls;
83+
process(partitions, &stripped);
84+
}
85+
} else {
86+
process(partitions, &node);
87+
}
6988
}
7089
}
7190

@@ -217,7 +236,9 @@ pub fn flatten_namespace<'a>(
217236
) {
218237
let nodes: Vec<_> = doc_nodes.collect();
219238

220-
'outer: for node in &nodes {
239+
for node in &nodes {
240+
let mut has_reference = false;
241+
221242
for decl in &node.declarations {
222243
if matches!(decl.def, DeclarationDef::Namespace(..)) {
223244
let children: Vec<_> = node
@@ -236,6 +257,7 @@ pub fn flatten_namespace<'a>(
236257
}
237258

238259
if let Some(reference) = decl.reference_def() {
260+
has_reference = true;
239261
let resolved: Vec<_> = ctx
240262
.resolve_reference(parent_node, &reference.target)
241263
.map(|c| c.into_owned())
@@ -246,12 +268,28 @@ pub fn flatten_namespace<'a>(
246268
parent_node,
247269
Box::new(resolved.into_iter().map(Cow::Owned)),
248270
);
249-
// hack until reference nodes are separate from normal symbols
250-
continue 'outer;
251271
}
252272
}
253273

254-
out.push((*node).clone());
274+
if has_reference {
275+
// If the symbol has non-reference declarations alongside
276+
// references, emit a node containing only the non-reference
277+
// declarations so they are not lost.
278+
let non_ref_decls: Vec<_> = node
279+
.declarations
280+
.iter()
281+
.filter(|d| d.reference_def().is_none())
282+
.cloned()
283+
.collect();
284+
if !non_ref_decls.is_empty() {
285+
let mut stripped = (**node).clone();
286+
std::sync::Arc::make_mut(&mut stripped.inner).declarations =
287+
non_ref_decls;
288+
out.push(Cow::Owned(stripped));
289+
}
290+
} else {
291+
out.push((*node).clone());
292+
}
255293
}
256294
}
257295

src/html/symbols/class.rs

Lines changed: 91 additions & 5 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,14 +989,15 @@ 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 {
915999
PropertyOrMethod::Property(property) => {
916-
render_class_property(ctx, class_name, &property, property_changes)
1000+
render_class_property(ctx, class_name, &property, property_changes, inherited_docs)
9171001
}
9181002
PropertyOrMethod::Method(method) => {
9191003
let (getter, setter) = if method.kind == MethodKind::Getter {
@@ -945,6 +1029,7 @@ fn render_class_properties(
9451029
getter,
9461030
setter.as_ref(),
9471031
method_changes,
1032+
inherited_docs,
9481033
)
9491034
}
9501035
};
@@ -1044,12 +1129,13 @@ fn render_class_methods(
10441129
class_name: &str,
10451130
methods: BTreeMap<Box<str>, Vec<ClassMethodDef>>,
10461131
method_changes: Option<&MethodsDiff>,
1132+
inherited_docs: &HashMap<String, String>,
10471133
) -> Vec<DocEntryCtx> {
10481134
let mut out: Vec<DocEntryCtx> = methods
10491135
.values()
10501136
.flat_map(|methods| {
10511137
methods.iter().enumerate().filter_map(|(i, method)| {
1052-
render_class_method(ctx, class_name, method, i, method_changes)
1138+
render_class_method(ctx, class_name, method, i, method_changes, inherited_docs)
10531139
})
10541140
})
10551141
.collect();

0 commit comments

Comments
 (0)