diff --git a/src/js_doc.rs b/src/js_doc.rs index 926e7020..5ce816b1 100644 --- a/src/js_doc.rs +++ b/src/js_doc.rs @@ -6,16 +6,16 @@ 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(); + 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.+))?" ) .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(); } @@ -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` @@ -182,6 +186,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, @@ -202,6 +213,10 @@ pub enum JsDocTag { See { doc: String, }, + /// `@since version` + Since { + doc: String, + }, Unsupported { value: String, }, @@ -213,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, @@ -280,6 +297,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) { @@ -300,10 +318,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 } } @@ -325,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" } ] }), @@ -629,6 +659,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( @@ -637,6 +676,7 @@ if (true) { const a = "a"; @category foo @see bar +@since 1.0.0 "# .to_string() )) @@ -654,6 +694,9 @@ const a = "a"; }, { "kind": "see", "doc": "bar" + }, { + "kind": "since", + "doc": "1.0.0" }] }) @@ -798,6 +841,49 @@ 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": "throws", + "type": null, + "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 0c2bd047..697f5435 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")) } @@ -395,6 +401,19 @@ 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("since"))?; + 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) + } } } 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..a20a0b6b 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 + + @since + 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" } ] },