From 12c79d33a5b6cab73eecc4fbad510e3126f153b6 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Fri, 26 Apr 2024 17:46:10 +0200 Subject: [PATCH 1/6] feat: JSDoc @throws --- src/js_doc.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++---- src/printer.rs | 9 +++++++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/js_doc.rs b/src/js_doc.rs index 926e7020..93d06a71 100644 --- a/src/js_doc.rs +++ b/src/js_doc.rs @@ -15,7 +15,7 @@ lazy_static! { ) .unwrap(); static ref JS_DOC_TAG_RE: Regex = Regex::new(r"(?s)^\s*@(\S+)").unwrap(); - static ref JS_DOC_TAG_RETURN_RE: Regex = Regex::new(r"(?s)^\s*@returns?(?:\s+\{([^}]+)\})?(?:\s+(.+))?").unwrap(); + static ref JS_DOC_TAG_OPTIONAL_TYPE_AND_DOC_RE: Regex = Regex::new(r"(?s)^\s*@(returns?|throws|exception)(?:\s+\{([^}]+)\})?(?:\s+(.+))?").unwrap(); static ref JS_DOC_TAG_TYPED_RE: Regex = Regex::new(r"(?s)^\s*@(enum|extends|augments|this|type|default)\s+\{([^}]+)\}(?:\s+(.+))?").unwrap(); } @@ -182,6 +182,13 @@ pub enum JsDocTag { #[serde(skip_serializing_if = "Option::is_none", default)] doc: Option, }, + /// `@throws {type} comment` or `@exception {type} comment` + Throws { + #[serde(rename = "type")] + type_ref: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + doc: Option, + }, /// `@typedef {type} name comment` TypeDef { name: String, @@ -300,10 +307,17 @@ impl From for JsDocTag { default, doc, } - } else if let Some(caps) = JS_DOC_TAG_RETURN_RE.captures(&value) { - let type_ref = caps.get(1).map(|m| m.as_str().to_string()); - let doc = caps.get(2).map(|m| m.as_str().to_string()); - Self::Return { type_ref, doc } + } else if let Some(caps) = + JS_DOC_TAG_OPTIONAL_TYPE_AND_DOC_RE.captures(&value) + { + let kind = caps.get(1).unwrap().as_str(); + let type_ref = caps.get(2).map(|m| m.as_str().to_string()); + let doc = caps.get(3).map(|m| m.as_str().to_string()); + match kind { + "return" | "returns" => Self::Return { type_ref, doc }, + "throws" | "exception" => Self::Throws { type_ref, doc }, + _ => unreachable!("kind unexpected: {}", kind), + } } else { Self::Unsupported { value } } @@ -798,6 +812,48 @@ const a = "a"; ); } + #[test] + fn test_js_doc_tag_throws() { + assert_eq!( + serde_json::to_value(JsDoc::from( + "@throws {string} maybe doc\n\nnew paragraph".to_string() + )) + .unwrap(), + json!({ + "tags": [{ + "kind": "throws", + "type": "string", + "doc": "maybe doc\n\nnew paragraph", + }] + }) + ); + assert_eq!( + serde_json::to_value(JsDoc::from( + "@throws maybe doc\n\nnew paragraph".to_string() + )) + .unwrap(), + json!({ + "tags": [{ + "kind": "return", + "doc": "maybe doc\n\nnew paragraph", + }] + }) + ); + assert_eq!( + serde_json::to_value(JsDoc::from( + "@throws {string} maybe doc\n\nnew paragraph".to_string() + )) + .unwrap(), + json!({ + "tags": [{ + "kind": "throws", + "type": "string", + "doc": "maybe doc\n\nnew paragraph", + }] + }) + ); + } + #[test] fn test_js_doc_from_str() { assert_eq!( diff --git a/src/printer.rs b/src/printer.rs index ed91626d..6bef473c 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -395,6 +395,15 @@ impl<'a> DocPrinter<'a> { writeln!(w, "{}@{}", Indent(indent), colors::magenta("see"))?; self.format_jsdoc_tag_doc(w, doc, indent) } + JsDocTag::Throws { type_ref, doc } => { + write!(w, "{}@{}", Indent(indent), colors::magenta("return"))?; + if let Some(type_ref) = type_ref { + writeln!(w, " {{{}}}", colors::italic_cyan(type_ref))?; + } else { + writeln!(w)?; + } + self.format_jsdoc_tag_maybe_doc(w, doc, indent) + } } } From 18b1c14cac8c58e641bee88e97a02a90788f449a Mon Sep 17 00:00:00 2001 From: crowlkats Date: Fri, 26 Apr 2024 17:51:43 +0200 Subject: [PATCH 2/6] add since tag --- src/js_doc.rs | 23 +++++++++++++++++++++-- src/printer.rs | 4 ++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/js_doc.rs b/src/js_doc.rs index 93d06a71..92324412 100644 --- a/src/js_doc.rs +++ b/src/js_doc.rs @@ -6,7 +6,7 @@ use serde::Serialize; lazy_static! { static ref JS_DOC_TAG_MAYBE_DOC_RE: Regex = Regex::new(r"(?s)^\s*@(deprecated)(?:\s+(.+))?").unwrap(); - static ref JS_DOC_TAG_DOC_RE: Regex = Regex::new(r"(?s)^\s*@(category|see|example|tags)(?:\s+(.+))").unwrap(); + static ref JS_DOC_TAG_DOC_RE: Regex = Regex::new(r"(?s)^\s*@(category|see|example|tags|since)(?:\s+(.+))").unwrap(); static ref JS_DOC_TAG_NAMED_RE: Regex = Regex::new(r"(?s)^\s*@(callback|template|typeparam|typeParam)\s+([a-zA-Z_$]\S*)(?:\s+(.+))?").unwrap(); static ref JS_DOC_TAG_NAMED_TYPED_RE: Regex = Regex::new(r"(?s)^\s*@(prop(?:erty)?|typedef)\s+\{([^}]+)\}\s+([a-zA-Z_$]\S*)(?:\s+(.+))?").unwrap(); static ref JS_DOC_TAG_ONLY_RE: Regex = Regex::new(r"^\s*@(constructor|class|ignore|module|public|private|protected|readonly)").unwrap(); @@ -209,6 +209,10 @@ pub enum JsDocTag { See { doc: String, }, + /// `@since version` + Since { + doc: String, + }, Unsupported { value: String, }, @@ -287,6 +291,7 @@ impl From for JsDocTag { tags: doc.split(',').map(|i| i.trim().to_string()).collect(), }, "see" => Self::See { doc }, + "since" => Self::Since { doc }, _ => unreachable!("kind unexpected: {}", kind), } } else if let Some(caps) = JS_DOC_TAG_PARAM_RE.captures(&value) { @@ -643,6 +648,15 @@ if (true) { }] }) ); + assert_eq!( + serde_json::to_value(JsDoc::from("@since 1.0.0".to_string())).unwrap(), + json!({ + "tags": [{ + "kind": "since", + "doc": "1.0.0" + }] + }) + ); assert_eq!( serde_json::to_value(JsDoc::from( @@ -651,6 +665,7 @@ if (true) { const a = "a"; @category foo @see bar +@since 1.0.0 "# .to_string() )) @@ -668,6 +683,9 @@ const a = "a"; }, { "kind": "see", "doc": "bar" + }, { + "kind": "since", + "doc": "1.0.0" }] }) @@ -834,7 +852,8 @@ const a = "a"; .unwrap(), json!({ "tags": [{ - "kind": "return", + "kind": "throws", + "type": null, "doc": "maybe doc\n\nnew paragraph", }] }) diff --git a/src/printer.rs b/src/printer.rs index 6bef473c..ff15d25a 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -395,6 +395,10 @@ impl<'a> DocPrinter<'a> { writeln!(w, "{}@{}", Indent(indent), colors::magenta("see"))?; self.format_jsdoc_tag_doc(w, doc, indent) } + JsDocTag::Since { doc } => { + writeln!(w, "{}@{}", Indent(indent), colors::magenta("see"))?; + self.format_jsdoc_tag_doc(w, doc, indent) + } JsDocTag::Throws { type_ref, doc } => { write!(w, "{}@{}", Indent(indent), colors::magenta("return"))?; if let Some(type_ref) = type_ref { From 186fd081b1853d094861f2296633ad255fc197ad Mon Sep 17 00:00:00 2001 From: crowlkats Date: Fri, 10 May 2024 11:43:09 +0200 Subject: [PATCH 3/6] more tags and test --- src/js_doc.rs | 13 +++++++- src/parser.rs | 2 +- src/printer.rs | 6 ++++ src/util/swc.rs | 5 ++- tests/specs/internal_tag.txt | 12 +++---- tests/specs/jsdoc_tags.txt | 33 +++++++++++++++++-- ...ivate_type_ignored_class_not_namespace.txt | 3 +- .../private_type_re_export_referencing.txt | 3 +- 8 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/js_doc.rs b/src/js_doc.rs index 92324412..5ce816b1 100644 --- a/src/js_doc.rs +++ b/src/js_doc.rs @@ -9,7 +9,7 @@ lazy_static! { static ref JS_DOC_TAG_DOC_RE: Regex = Regex::new(r"(?s)^\s*@(category|see|example|tags|since)(?:\s+(.+))").unwrap(); static ref JS_DOC_TAG_NAMED_RE: Regex = Regex::new(r"(?s)^\s*@(callback|template|typeparam|typeParam)\s+([a-zA-Z_$]\S*)(?:\s+(.+))?").unwrap(); static ref JS_DOC_TAG_NAMED_TYPED_RE: Regex = Regex::new(r"(?s)^\s*@(prop(?:erty)?|typedef)\s+\{([^}]+)\}\s+([a-zA-Z_$]\S*)(?:\s+(.+))?").unwrap(); - static ref JS_DOC_TAG_ONLY_RE: Regex = Regex::new(r"^\s*@(constructor|class|ignore|module|public|private|protected|readonly)").unwrap(); + static ref JS_DOC_TAG_ONLY_RE: Regex = Regex::new(r"^\s*@(constructor|class|ignore|internal|module|public|private|protected|readonly|experimental)").unwrap(); static ref JS_DOC_TAG_PARAM_RE: Regex = Regex::new( r"(?s)^\s*@(?:param|arg(?:ument)?)(?:\s+\{(?P[^}]+)\})?\s+(?:(?:\[(?P[a-zA-Z_$]\S*?)(?:\s*=\s*(?P[^]]+))?\])|(?P[a-zA-Z_$]\S*))(?:\s+(?P.+))?" ) @@ -115,6 +115,8 @@ pub enum JsDocTag { #[serde(default)] doc: String, }, + /// `@experimental` + Experimental, /// `@extends {type} comment` Extends { #[serde(rename = "type")] @@ -124,6 +126,8 @@ pub enum JsDocTag { }, /// `@ignore` Ignore, + /// `@internal` + Internal, /// `@module` Module, /// `@param`, `@arg` or `argument`, in format of `@param {type} name comment` @@ -224,7 +228,9 @@ impl From for JsDocTag { let kind = caps.get(1).unwrap().as_str(); match kind { "constructor" | "class" => Self::Constructor, + "experimental" => Self::Experimental, "ignore" => Self::Ignore, + "internal" => Self::Internal, "module" => Self::Module, "public" => Self::Public, "private" => Self::Private, @@ -344,6 +350,11 @@ mod tests { serde_json::to_value(JsDoc::from("@class more".to_string())).unwrap(), json!({ "tags": [ { "kind": "constructor" } ] }), ); + assert_eq!( + serde_json::to_value(JsDoc::from("@experimental more".to_string())) + .unwrap(), + json!({ "tags": [ { "kind": "experimental" } ] }), + ); assert_eq!( serde_json::to_value(JsDoc::from("@ignore more".to_string())).unwrap(), json!({ "tags": [ { "kind": "ignore" } ] }), diff --git a/src/parser.rs b/src/parser.rs index b10956e6..b456c0bc 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1171,7 +1171,7 @@ impl<'a> DocParser<'a> { | SymbolNodeRef::TsGetterSignature(_) | SymbolNodeRef::TsSetterSignature(_) | SymbolNodeRef::TsMethodSignature(_) => { - debug_assert!(false, "should not reach here"); + //debug_assert!(false, "should not reach here"); None } } diff --git a/src/printer.rs b/src/printer.rs index ff15d25a..4a4781fb 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -255,6 +255,9 @@ impl<'a> DocPrinter<'a> { writeln!(w, "{}@{}", Indent(indent), colors::magenta("example"))?; self.format_jsdoc_tag_doc(w, doc, indent) } + JsDocTag::Experimental => { + writeln!(w, "{}@{}", Indent(indent), colors::magenta("experimental")) + } JsDocTag::Extends { type_ref, doc } => { writeln!( w, @@ -268,6 +271,9 @@ impl<'a> DocPrinter<'a> { JsDocTag::Ignore => { writeln!(w, "{}@{}", Indent(indent), colors::magenta("ignore")) } + JsDocTag::Internal => { + writeln!(w, "{}@{}", Indent(indent), colors::magenta("internal")) + } JsDocTag::Module => { writeln!(w, "{}@{}", Indent(indent), colors::magenta("module")) } diff --git a/src/util/swc.rs b/src/util/swc.rs index 179b93fa..149ce1a9 100644 --- a/src/util/swc.rs +++ b/src/util/swc.rs @@ -124,7 +124,10 @@ pub fn module_export_name_value( /// If the jsdoc has an `@internal` or `@ignore` tag. pub fn has_ignorable_js_doc_tag(js_doc: &JsDoc) -> bool { - js_doc.tags.iter().any(|t| matches!(t, JsDocTag::Ignore) || matches!(t, JsDocTag::Unsupported { value } if value == "@internal" || value.starts_with("@internal "))) + js_doc + .tags + .iter() + .any(|t| *t == JsDocTag::Ignore || *t == JsDocTag::Internal) } #[cfg(test)] diff --git a/tests/specs/internal_tag.txt b/tests/specs/internal_tag.txt index d5de1d7c..26709b8b 100644 --- a/tests/specs/internal_tag.txt +++ b/tests/specs/internal_tag.txt @@ -91,8 +91,7 @@ type Test = OtherPrivateType "jsDoc": { "tags": [ { - "kind": "unsupported", - "value": "@internal" + "kind": "internal" } ] }, @@ -190,8 +189,7 @@ type Test = OtherPrivateType "jsDoc": { "tags": [ { - "kind": "unsupported", - "value": "@internal" + "kind": "internal" } ] }, @@ -216,8 +214,7 @@ type Test = OtherPrivateType "jsDoc": { "tags": [ { - "kind": "unsupported", - "value": "@internal" + "kind": "internal" } ] }, @@ -284,8 +281,7 @@ type Test = OtherPrivateType "jsDoc": { "tags": [ { - "kind": "unsupported", - "value": "@internal" + "kind": "internal" } ] }, diff --git a/tests/specs/jsdoc_tags.txt b/tests/specs/jsdoc_tags.txt index 6cd885c1..c11282c5 100644 --- a/tests/specs/jsdoc_tags.txt +++ b/tests/specs/jsdoc_tags.txt @@ -6,11 +6,15 @@ * @param [c=1] additional doc * @param [d] more doc * @returns {string} returning doc + * @throws {number} throw doc + * @since 1.0.0 + * @experimental + * @internal */ export function a(b, c, d) {} # output.txt -Defined in file:///mod.ts:9:1 +Defined in file:///mod.ts:13:1 function a(b, c, d): void a is a function @@ -27,6 +31,14 @@ function a(b, c, d): void @return {string} returning doc + @return {number} + throw doc + + @see + 1.0.0 + + @experimental + @internal # output.json @@ -36,9 +48,9 @@ function a(b, c, d): void "name": "a", "location": { "filename": "file:///mod.ts", - "line": 9, + "line": 13, "col": 0, - "byteIndex": 149 + "byteIndex": 225 }, "declarationKind": "export", "jsDoc": { @@ -66,6 +78,21 @@ function a(b, c, d): void "kind": "return", "type": "string", "doc": "returning doc" + }, + { + "kind": "throws", + "type": "number", + "doc": "throw doc" + }, + { + "kind": "since", + "doc": "1.0.0" + }, + { + "kind": "experimental" + }, + { + "kind": "internal" } ] }, diff --git a/tests/specs/private_type_ignored_class_not_namespace.txt b/tests/specs/private_type_ignored_class_not_namespace.txt index a9ddfc38..69b12526 100644 --- a/tests/specs/private_type_ignored_class_not_namespace.txt +++ b/tests/specs/private_type_ignored_class_not_namespace.txt @@ -73,8 +73,7 @@ private namespace MyNamespace "jsDoc": { "tags": [ { - "kind": "unsupported", - "value": "@internal" + "kind": "internal" } ] }, diff --git a/tests/specs/private_type_re_export_referencing.txt b/tests/specs/private_type_re_export_referencing.txt index e4b4e87d..2c35a907 100644 --- a/tests/specs/private_type_re_export_referencing.txt +++ b/tests/specs/private_type_re_export_referencing.txt @@ -138,8 +138,7 @@ class Data "jsDoc": { "tags": [ { - "kind": "unsupported", - "value": "@internal" + "kind": "internal" } ] }, From 172d2ee3165a30454ed04210b4bc1af567cdbd1b Mon Sep 17 00:00:00 2001 From: crowlkats Date: Fri, 10 May 2024 11:49:13 +0200 Subject: [PATCH 4/6] fix --- src/printer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/printer.rs b/src/printer.rs index 4a4781fb..7f4d545f 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -402,7 +402,7 @@ impl<'a> DocPrinter<'a> { self.format_jsdoc_tag_doc(w, doc, indent) } JsDocTag::Since { doc } => { - writeln!(w, "{}@{}", Indent(indent), colors::magenta("see"))?; + writeln!(w, "{}@{}", Indent(indent), colors::magenta("since"))?; self.format_jsdoc_tag_doc(w, doc, indent) } JsDocTag::Throws { type_ref, doc } => { From f0a620ab8ff1ebf22a7d03e31a6c7a99949e9c8d Mon Sep 17 00:00:00 2001 From: crowlkats Date: Fri, 10 May 2024 12:50:51 +0200 Subject: [PATCH 5/6] update --- tests/specs/jsdoc_tags.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/specs/jsdoc_tags.txt b/tests/specs/jsdoc_tags.txt index c11282c5..a20a0b6b 100644 --- a/tests/specs/jsdoc_tags.txt +++ b/tests/specs/jsdoc_tags.txt @@ -34,7 +34,7 @@ function a(b, c, d): void @return {number} throw doc - @see + @since 1.0.0 @experimental From bd34f2f27ab91063b1d7c00c598fd604f22f115a Mon Sep 17 00:00:00 2001 From: crowlkats Date: Fri, 10 May 2024 17:29:59 +0200 Subject: [PATCH 6/6] uncomment --- src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index b456c0bc..b10956e6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1171,7 +1171,7 @@ impl<'a> DocParser<'a> { | SymbolNodeRef::TsGetterSignature(_) | SymbolNodeRef::TsSetterSignature(_) | SymbolNodeRef::TsMethodSignature(_) => { - //debug_assert!(false, "should not reach here"); + debug_assert!(false, "should not reach here"); None } }