Skip to content

Commit a6ffc81

Browse files
committed
feat: add jsdoc summary and description tags
1 parent 4f30d5f commit a6ffc81

File tree

9 files changed

+136
-10
lines changed

9 files changed

+136
-10
lines changed

src/html/jsdoc.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,20 @@ pub(crate) fn jsdoc_body_to_html(
236236
js_doc: &JsDoc,
237237
summary: bool,
238238
) -> Option<String> {
239-
if let Some(doc) = js_doc.doc.as_deref() {
239+
if summary && let Some(doc) = js_doc.tags.iter().find_map(|tag| if let JsDocTag::Summary { doc } = tag {
240+
Some(doc)
241+
} else {
242+
None
243+
}) {
244+
markdown_to_html(
245+
ctx,
246+
doc,
247+
MarkdownToHTMLOptions {
248+
title_only: false,
249+
no_toc: false,
250+
},
251+
)
252+
} else if let Some(doc) = js_doc.doc.as_deref() {
240253
markdown_to_html(
241254
ctx,
242255
doc,

src/js_doc.rs

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ lazy_static! {
1111
/// @tag maybe_value
1212
static ref JS_DOC_TAG_WITH_MAYBE_VALUE_RE: Regex = Regex::new(r"(?s)^\s*@(deprecated|module)(?:\s+(.+))?").unwrap();
1313
/// @tag value
14-
static ref JS_DOC_TAG_WITH_VALUE_RE: Regex = Regex::new(r"(?s)^\s*@(category|group|see|example|tags|since|priority)(?:\s+(.+))").unwrap();
14+
static ref JS_DOC_TAG_WITH_VALUE_RE: Regex = Regex::new(r"(?s)^\s*@(category|group|see|example|tags|since|priority|summary|description)(?:\s+(.+))").unwrap();
1515
/// @tag name maybe_value
1616
static ref JS_DOC_TAG_NAMED_WITH_MAYBE_VALUE_RE: Regex = Regex::new(r"(?s)^\s*@(callback|template|typeparam|typeParam)\s+([a-zA-Z_$]\S*)(?:\s+(.+))?").unwrap();
1717
/// @tag {type} name maybe_value
@@ -71,6 +71,7 @@ impl From<String> for JsDoc {
7171
let mut tag_is_codeblock = false;
7272
let mut current_tag: Option<String> = None;
7373
let mut current_tag_name = "";
74+
let mut description_override: Option<String> = None;
7475
for line in value.lines() {
7576
let caps = JS_DOC_TAG_RE.captures(line);
7677
if is_tag || caps.is_some() {
@@ -82,7 +83,15 @@ impl From<String> for JsDoc {
8283
tag_is_codeblock = false;
8384
let current_tag = std::mem::take(&mut current_tag);
8485
if let Some(current_tag) = current_tag {
85-
tags.push(current_tag.into());
86+
if current_tag_name == "description" {
87+
if let Some(caps) = JS_DOC_TAG_WITH_VALUE_RE.captures(&current_tag) {
88+
if let Some(m) = caps.get(2) {
89+
description_override = Some(m.as_str().to_string());
90+
}
91+
}
92+
} else {
93+
tags.push(current_tag.into());
94+
}
8695
}
8796
}
8897
if let Some(caps) = caps {
@@ -118,9 +127,22 @@ impl From<String> for JsDoc {
118127
}
119128
}
120129
if let Some(current_tag) = current_tag {
121-
tags.push(current_tag.into());
130+
if current_tag_name == "description" {
131+
if let Some(rest) = current_tag.strip_prefix("@description") {
132+
let desc = rest.trim_start();
133+
if !desc.is_empty() {
134+
description_override = Some(desc.to_string());
135+
}
136+
}
137+
} else {
138+
tags.push(current_tag.into());
139+
}
122140
}
123-
let doc = doc_lines.map(|doc_lines| doc_lines.into_boxed_str());
141+
let doc = if let Some(desc) = description_override {
142+
Some(desc.into_boxed_str())
143+
} else {
144+
doc_lines.map(|doc_lines| doc_lines.into_boxed_str())
145+
};
124146
Self {
125147
doc,
126148
tags: tags.into_boxed_slice(),
@@ -270,6 +292,11 @@ pub enum JsDocTag {
270292
See {
271293
doc: Box<str>,
272294
},
295+
/// `@summary comment`
296+
Summary {
297+
#[serde(default)]
298+
doc: Box<str>,
299+
},
273300
/// `@since version`
274301
Since {
275302
doc: Box<str>,
@@ -363,6 +390,7 @@ impl From<String> for JsDocTag {
363390
},
364391
"see" => Self::See { doc },
365392
"since" => Self::Since { doc },
393+
"summary" => Self::Summary { doc },
366394
"priority" => {
367395
let Ok(priority) = doc.parse() else {
368396
return Self::Unsupported {
@@ -371,6 +399,7 @@ impl From<String> for JsDocTag {
371399
};
372400
Self::Priority { priority }
373401
}
402+
"description" => unreachable!("@description is handled earlier"),
374403
_ => unreachable!("kind unexpected: {}", kind),
375404
}
376405
} else if let Some(caps) = JS_DOC_TAG_PARAM_RE.captures(&value) {
@@ -804,6 +833,69 @@ const a = "a";
804833

805834
})
806835
);
836+
assert_eq!(
837+
serde_json::to_value(JsDoc::from(
838+
"@summary A brief summary".to_string()
839+
))
840+
.unwrap(),
841+
json!({
842+
"tags": [{
843+
"kind": "summary",
844+
"doc": "A brief summary",
845+
}]
846+
})
847+
);
848+
}
849+
850+
#[test]
851+
fn test_js_doc_description_tag() {
852+
// @description overrides the doc field
853+
assert_eq!(
854+
serde_json::to_value(JsDoc::from(
855+
"Normal doc text\n@description Override description".to_string()
856+
))
857+
.unwrap(),
858+
json!({
859+
"doc": "Override description",
860+
})
861+
);
862+
// @description without preceding doc text
863+
assert_eq!(
864+
serde_json::to_value(JsDoc::from(
865+
"@description The description".to_string()
866+
))
867+
.unwrap(),
868+
json!({
869+
"doc": "The description",
870+
})
871+
);
872+
// @description with other tags
873+
assert_eq!(
874+
serde_json::to_value(JsDoc::from(
875+
"Normal doc\n@description Override\n@param {string} a a param"
876+
.to_string()
877+
))
878+
.unwrap(),
879+
json!({
880+
"doc": "Override",
881+
"tags": [{
882+
"kind": "param",
883+
"name": "a",
884+
"type": "string",
885+
"doc": "a param",
886+
}]
887+
})
888+
);
889+
// multi-line @description
890+
assert_eq!(
891+
serde_json::to_value(JsDoc::from(
892+
"@description Line 1\nLine 2".to_string()
893+
))
894+
.unwrap(),
895+
json!({
896+
"doc": "Line 1\nLine 2",
897+
})
898+
);
807899
}
808900

809901
#[test]
@@ -1208,6 +1300,16 @@ multi-line
12081300
"type": "Map<string, string>",
12091301
})
12101302
);
1303+
assert_eq!(
1304+
serde_json::to_value(JsDocTag::Summary {
1305+
doc: "A brief summary".into(),
1306+
})
1307+
.unwrap(),
1308+
json!({
1309+
"kind": "summary",
1310+
"doc": "A brief summary",
1311+
})
1312+
);
12111313
assert_eq!(
12121314
serde_json::to_value(JsDocTag::Unsupported {
12131315
value: "unsupported".into()

src/printer.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,10 @@ impl DocPrinter<'_> {
406406
writeln!(w, "{}@{}", Indent(indent), colors::magenta("since"))?;
407407
self.format_jsdoc_tag_doc(w, doc, indent)
408408
}
409+
JsDocTag::Summary { doc } => {
410+
writeln!(w, "{}@{}", Indent(indent), colors::magenta("summary"))?;
411+
self.format_jsdoc_tag_doc(w, doc, indent)
412+
}
409413
JsDocTag::Priority { priority } => {
410414
writeln!(
411415
w,

tests/snapshots/html_test__html_doc_files_multiple-21.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ expression: files.get(file_name).unwrap()
121121
<div>
122122
<div class="text-2xl leading-none break-all">
123123
<span class="text-Enum">enum</span>&nbsp;<span class="font-bold">Enum2</span>
124-
</div></div></div><div><div class="space-y-7" id=""><section class="section" id="members"><div>
124+
</div></div></div><div><div class="space-y-7" id=""><div class="markdown"><p>the description</p>
125+
</div><section class="section" id="members"><div>
125126
<h2 class="anchorable mb-1"><a href="#members" class="anchor" aria-label="Anchor" tabIndex="-1"><svg
126127
width="16"
127128
height="16"

tests/snapshots/html_test__html_doc_files_multiple-73.snap

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/snapshots/html_test__html_doc_files_multiple.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ Enums</h2></div><div class="namespaceSection"><div id="namespace_enum" class="na
214214
</div><div id="namespace_enum2" class="namespaceItem" ><div class="docNodeKindIcon"><div class="text-Enum bg-Enum/15 dark:text-EnumDark dark:bg-EnumDark/15" title="Enum">E</div></div>
215215
<div class="namespaceItemContent">
216216
<a href=".&#x2F;.&#x2F;.&#x2F;~&#x2F;Enum2.html" title="Enum2">
217-
<span>Enum2</span></a></div>
217+
<span>Enum2</span></a><div class="namespaceItemContentDoc"><div class="markdown"><p>the summary</p>
218+
</div></div></div>
218219
</div></div>
219220
</section>
220221
<section class="section" id="default_functions"><div>

tests/snapshots/html_test__symbol_group.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,7 @@ expression: files
12321232
"kind": "other",
12331233
"value": {
12341234
"id": "",
1235-
"docs": null,
1235+
"docs": "<div class=\"markdown\"><p>the description</p>\n</div>",
12361236
"sections": [
12371237
{
12381238
"header": {

tests/snapshots/html_test__symbol_search.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ expression: search_index
231231
],
232232
"name": "Enum2",
233233
"file": ".",
234-
"doc": "",
234+
"doc": "the description",
235235
"url": "././~/Enum2.html",
236236
"deprecated": false
237237
},

tests/testdata/multiple/a.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ export enum Enum {
203203
Bar = "bar",
204204
}
205205

206+
/**
207+
* description to be overwritten
208+
* @summary the summary
209+
* @description the description
210+
*/
206211
export enum Enum2 {
207212
Foo,
208213
Bar,

0 commit comments

Comments
 (0)