Skip to content

Commit 01764e5

Browse files
divybotlittledivy
andauthored
feat: support JSDoc @event, @fires, @emits, and @Listens tags (#800)
Adds three new JsDocTag variants for documenting events on classes that fire or listen for them, e.g. subclasses of EventTarget: - `@event <name>` — declares an event a class can emit - `@fires <name>` / `@emits <name>` — documents which events a method/function fires - `@listens <name>` — documents which events a symbol listens for Names follow the JSDoc convention (e.g. `Hurl#snowball`) and an optional trailing description is captured into `doc`. Refs denoland/deno#26697 Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
1 parent 89a6055 commit 01764e5

3 files changed

Lines changed: 153 additions & 1 deletion

File tree

src/diff/js_doc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,14 @@ fn tags_same_kind(a: &JsDocTag, b: &JsDocTag) -> bool {
107107
(Default { value: v1, .. }, Default { value: v2, .. }) => v1 == v2,
108108
(Deprecated { .. }, Deprecated { .. }) => true,
109109
(Enum { ts_type: t1, .. }, Enum { ts_type: t2, .. }) => t1 == t2,
110+
(Event { name: n1, .. }, Event { name: n2, .. }) => n1 == n2,
110111
(Example { .. }, Example { .. }) => true,
111112
(Experimental, Experimental) => true,
112113
(Extends { ts_type: t1, .. }, Extends { ts_type: t2, .. }) => t1 == t2,
114+
(Fires { name: n1, .. }, Fires { name: n2, .. }) => n1 == n2,
113115
(Ignore, Ignore) => true,
114116
(Internal, Internal) => true,
117+
(Listens { name: n1, .. }, Listens { name: n2, .. }) => n1 == n2,
115118
(Module { .. }, Module { .. }) => true,
116119
(Param { name: n1, .. }, Param { name: n2, .. }) => n1 == n2,
117120
(Public, Public) => true,

src/js_doc.rs

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ lazy_static! {
2121
/// @tag value
2222
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();
2323
/// @tag name maybe_value
24-
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+(?:-\s+)?(.+))?").unwrap();
24+
static ref JS_DOC_TAG_NAMED_WITH_MAYBE_VALUE_RE: Regex = Regex::new(r"(?s)^\s*@(callback|template|typeparam|typeParam|event|fires|emits|listens)\s+([a-zA-Z_$]\S*)(?:\s+(?:-\s+)?(.+))?").unwrap();
2525
/// @tag {type} name maybe_value
2626
static ref JS_DOC_TAG_NAMED_TYPED_RE: Regex = Regex::new(r"(?s)^\s*@(prop(?:erty)?|typedef)\s+\{([^}]+)\}\s+([a-zA-Z_$]\S*)(?:\s+(?:-\s+)?(.+))?").unwrap();
2727
/// @tag {type} name maybe_value
@@ -254,6 +254,12 @@ pub enum JsDocTag {
254254
#[serde(skip_serializing_if = "Option::is_none", default)]
255255
doc: Option<Box<str>>,
256256
},
257+
/// `@event name comment`
258+
Event {
259+
name: Box<str>,
260+
#[serde(skip_serializing_if = "Option::is_none", default)]
261+
doc: Option<Box<str>>,
262+
},
257263
/// `@example comment`
258264
Example {
259265
#[serde(default)]
@@ -268,10 +274,22 @@ pub enum JsDocTag {
268274
#[serde(skip_serializing_if = "Option::is_none", default)]
269275
doc: Option<Box<str>>,
270276
},
277+
/// `@fires name comment` or `@emits name comment`
278+
Fires {
279+
name: Box<str>,
280+
#[serde(skip_serializing_if = "Option::is_none", default)]
281+
doc: Option<Box<str>>,
282+
},
271283
/// `@ignore`
272284
Ignore,
273285
/// `@internal`
274286
Internal,
287+
/// `@listens name comment`
288+
Listens {
289+
name: Box<str>,
290+
#[serde(skip_serializing_if = "Option::is_none", default)]
291+
doc: Option<Box<str>>,
292+
},
275293
/// `@module`
276294
/// `@module name`
277295
Module {
@@ -405,6 +423,9 @@ impl JsDocTag {
405423
match kind {
406424
"callback" => Self::Callback { name, doc },
407425
"template" | "typeparam" | "typeParam" => Self::Template { name, doc },
426+
"event" => Self::Event { name, doc },
427+
"fires" | "emits" => Self::Fires { name, doc },
428+
"listens" => Self::Listens { name, doc },
408429
_ => unreachable!("kind unexpected: {}", kind),
409430
}
410431
} else if let Some(caps) =
@@ -766,6 +787,70 @@ class Foo {}
766787
]
767788
})
768789
);
790+
assert_eq!(
791+
serde_json::to_value(parse_jsdoc(
792+
"@event Hurl#snowball Fired when a snowball is thrown"
793+
))
794+
.unwrap(),
795+
serde_json::json!({
796+
"tags": [
797+
{
798+
"kind": "event",
799+
"name": "Hurl#snowball",
800+
"doc": "Fired when a snowball is thrown",
801+
}
802+
]
803+
})
804+
);
805+
assert_eq!(
806+
serde_json::to_value(parse_jsdoc("@event open")).unwrap(),
807+
serde_json::json!({
808+
"tags": [
809+
{
810+
"kind": "event",
811+
"name": "open",
812+
}
813+
]
814+
})
815+
);
816+
assert_eq!(
817+
serde_json::to_value(parse_jsdoc("@fires Hurl#snowball")).unwrap(),
818+
serde_json::json!({
819+
"tags": [
820+
{
821+
"kind": "fires",
822+
"name": "Hurl#snowball",
823+
}
824+
]
825+
})
826+
);
827+
assert_eq!(
828+
serde_json::to_value(parse_jsdoc(
829+
"@emits open - when the connection opens"
830+
))
831+
.unwrap(),
832+
serde_json::json!({
833+
"tags": [
834+
{
835+
"kind": "fires",
836+
"name": "open",
837+
"doc": "when the connection opens",
838+
}
839+
]
840+
})
841+
);
842+
assert_eq!(
843+
serde_json::to_value(parse_jsdoc("@listens module:hurler~snowball"))
844+
.unwrap(),
845+
serde_json::json!({
846+
"tags": [
847+
{
848+
"kind": "listens",
849+
"name": "module:hurler~snowball",
850+
}
851+
]
852+
})
853+
);
769854
}
770855

771856
#[test]
@@ -1566,6 +1651,40 @@ multi-line
15661651
"name": "T",
15671652
})
15681653
);
1654+
assert_eq!(
1655+
serde_json::to_value(JsDocTag::Event {
1656+
name: "snowball".into(),
1657+
doc: Some("fired when a snowball is thrown".into()),
1658+
})
1659+
.unwrap(),
1660+
json!({
1661+
"kind": "event",
1662+
"name": "snowball",
1663+
"doc": "fired when a snowball is thrown",
1664+
})
1665+
);
1666+
assert_eq!(
1667+
serde_json::to_value(JsDocTag::Fires {
1668+
name: "Hurl#snowball".into(),
1669+
doc: None,
1670+
})
1671+
.unwrap(),
1672+
json!({
1673+
"kind": "fires",
1674+
"name": "Hurl#snowball",
1675+
})
1676+
);
1677+
assert_eq!(
1678+
serde_json::to_value(JsDocTag::Listens {
1679+
name: "module:hurler~snowball".into(),
1680+
doc: None,
1681+
})
1682+
.unwrap(),
1683+
json!({
1684+
"kind": "listens",
1685+
"name": "module:hurler~snowball",
1686+
})
1687+
);
15691688
assert_eq!(
15701689
serde_json::to_value(JsDocTag::This {
15711690
ts_type: TsTypeDef {

src/printer.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,16 @@ impl DocPrinter<'_> {
273273
)?;
274274
self.format_jsdoc_tag_maybe_doc(w, doc, indent)
275275
}
276+
JsDocTag::Event { name, doc } => {
277+
writeln!(
278+
w,
279+
"{}@{} {}",
280+
Indent(indent),
281+
colors::magenta("event"),
282+
colors::bold(name)
283+
)?;
284+
self.format_jsdoc_tag_maybe_doc(w, doc, indent)
285+
}
276286
JsDocTag::Example { doc } => {
277287
writeln!(w, "{}@{}", Indent(indent), colors::magenta("example"))?;
278288
self.format_jsdoc_tag_doc(w, doc, indent)
@@ -290,12 +300,32 @@ impl DocPrinter<'_> {
290300
)?;
291301
self.format_jsdoc_tag_maybe_doc(w, doc, indent)
292302
}
303+
JsDocTag::Fires { name, doc } => {
304+
writeln!(
305+
w,
306+
"{}@{} {}",
307+
Indent(indent),
308+
colors::magenta("fires"),
309+
colors::bold(name)
310+
)?;
311+
self.format_jsdoc_tag_maybe_doc(w, doc, indent)
312+
}
293313
JsDocTag::Ignore => {
294314
writeln!(w, "{}@{}", Indent(indent), colors::magenta("ignore"))
295315
}
296316
JsDocTag::Internal => {
297317
writeln!(w, "{}@{}", Indent(indent), colors::magenta("internal"))
298318
}
319+
JsDocTag::Listens { name, doc } => {
320+
writeln!(
321+
w,
322+
"{}@{} {}",
323+
Indent(indent),
324+
colors::magenta("listens"),
325+
colors::bold(name)
326+
)?;
327+
self.format_jsdoc_tag_maybe_doc(w, doc, indent)
328+
}
299329
JsDocTag::Module { name } => {
300330
writeln!(w, "{}@{}", Indent(indent), colors::magenta("module"))?;
301331
self.format_jsdoc_tag_maybe_doc(w, name, indent)

0 commit comments

Comments
 (0)