diff --git a/Cargo.lock b/Cargo.lock index 4eeb320..7cadae1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.6.19" @@ -410,6 +419,7 @@ name = "browser_sdk_dart_codegen" version = "0.1.0" dependencies = [ "browser_sdk_schema", + "browser_sdk_utils", "convert_case", ] @@ -439,6 +449,7 @@ dependencies = [ "biome_rowan", "browser_sdk_schema", "browser_sdk_ts_codegen_macros", + "browser_sdk_utils", "convert_case", "indexmap", "indoc", @@ -455,6 +466,14 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "browser_sdk_utils" +version = "0.1.0" +dependencies = [ + "markdown", + "mdast_util_to_markdown", +] + [[package]] name = "bstr" version = "1.12.0" @@ -694,6 +713,23 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "markdown" +version = "1.0.0" +source = "git+https://github.com/cirnov/markdown-rs#db3af3ee0bb907e8329ba535b29121c0827b2289" +dependencies = [ + "unicode-id", +] + +[[package]] +name = "mdast_util_to_markdown" +version = "0.0.2" +source = "git+https://github.com/cirnov/markdown-rs#db3af3ee0bb907e8329ba535b29121c0827b2289" +dependencies = [ + "markdown", + "regex", +] + [[package]] name = "memchr" version = "2.7.4" @@ -855,11 +891,34 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "result" @@ -1142,6 +1201,12 @@ version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" +[[package]] +name = "unicode-id" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/Cargo.toml b/Cargo.toml index a184f15..afd1475 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,6 @@ serde_yaml_ng = { version = "0.10" } browser_sdk_schema = { path = "./crates/browser_sdk_schema" } browser_sdk_ts_codegen = { path = "./crates/browser_sdk_ts_codegen" } browser_sdk_ts_codegen_macros = { path = "./crates/browser_sdk_ts_codegen_macros" } +browser_sdk_utils = { path = "./crates/browser_sdk_utils" } +markdown = { git = "https://github.com/cirnov/markdown-rs" } +mdast_util_to_markdown = { git = "https://github.com/cirnov/markdown-rs" } diff --git a/crates/browser_sdk_dart_codegen/Cargo.toml b/crates/browser_sdk_dart_codegen/Cargo.toml index bd89e7c..0057172 100644 --- a/crates/browser_sdk_dart_codegen/Cargo.toml +++ b/crates/browser_sdk_dart_codegen/Cargo.toml @@ -5,4 +5,5 @@ edition = "2024" [dependencies] browser_sdk_schema.workspace = true +browser_sdk_utils.workspace = true convert_case = { workspace = true } diff --git a/crates/browser_sdk_dart_codegen/src/ast/enum.rs b/crates/browser_sdk_dart_codegen/src/ast/enum.rs index 005e5cc..cda194b 100644 --- a/crates/browser_sdk_dart_codegen/src/ast/enum.rs +++ b/crates/browser_sdk_dart_codegen/src/ast/enum.rs @@ -66,7 +66,7 @@ mod tests { fn enum_with_variant() { let empty = Enum { name: Identifier::try_from("Test1").unwrap(), - description: Some(Comment("Test1 Enum".into())), + description: Some(Comment::try_from("Test1 Enum").unwrap()), variants: vec![ EnumVariant { name: Identifier::try_from("VARIANT_A").unwrap(), @@ -76,9 +76,9 @@ mod tests { EnumVariant { name: Identifier::try_from("VARIANT_B").unwrap(), value: "value_b".into(), - description: Some(Comment( - "This is a variant\nwith a multi-line description".into(), - )), + description: Some(Comment::try_from( + "This is a variant\nwith a multi-line description", + ).unwrap()), }, ], union_parents: vec![], diff --git a/crates/browser_sdk_dart_codegen/src/ast/mod.rs b/crates/browser_sdk_dart_codegen/src/ast/mod.rs index 93e8760..63e5bac 100644 --- a/crates/browser_sdk_dart_codegen/src/ast/mod.rs +++ b/crates/browser_sdk_dart_codegen/src/ast/mod.rs @@ -6,6 +6,7 @@ mod intersection; mod object; mod union; +use browser_sdk_utils::{MdastNodeExt, ToMdastExt}; pub use r#enum::*; pub use ident::*; pub use intersection::*; @@ -22,7 +23,7 @@ impl fmt::Display for Indent { } #[derive(Debug, Clone)] -pub struct Comment(pub String); +pub struct Comment(String); impl Comment { pub fn lines(&self) -> impl Iterator { @@ -30,6 +31,28 @@ impl Comment { } } +impl TryFrom<&str> for Comment { + type Error = String; + + fn try_from(value: &str) -> Result { + let cleaned = value + .to_mdast() + .map_err(|e| format!("Failed to parse markdown: {}", e))? + .remove_jsx_elements() + .to_markdown_string() + .map_err(|e| format!("Failed to convert markdown to string: {}", e))?; + Ok(Comment(cleaned)) + } +} + +impl TryFrom for Comment { + type Error = String; + + fn try_from(value: String) -> Result { + Comment::try_from(value.as_str()) + } +} + #[derive(Debug, Clone)] pub struct TypeReference { pub path: String, diff --git a/crates/browser_sdk_dart_codegen/src/ast/object.rs b/crates/browser_sdk_dart_codegen/src/ast/object.rs index 400175c..9aa12c6 100644 --- a/crates/browser_sdk_dart_codegen/src/ast/object.rs +++ b/crates/browser_sdk_dart_codegen/src/ast/object.rs @@ -202,7 +202,7 @@ mod tests { fn empty_object() { let object = Object { name: Identifier::try_from("Test").unwrap(), - description: Some(Comment("Test Object".into())), + description: Some(Comment::try_from("Test Object").unwrap()), fields: vec![], is_one_of: false, union_parents: vec![UnionParent::Union { @@ -229,7 +229,7 @@ class Test { fn object_with_fields() { let object = Object { name: Identifier::try_from("Address").unwrap(), - description: Some(Comment("주소 정보".into())), + description: Some(Comment::try_from("주소 정보").unwrap()), fields: vec![ ObjectField { name: Identifier::try_from("country").unwrap(), @@ -252,7 +252,7 @@ class Test { is_list: false, is_required: true, }, - description: Some(Comment("**일반주소**".into())), + description: Some(Comment::try_from("**일반주소**").unwrap()), }, ObjectField { name: Identifier::try_from("addressLine2").unwrap(), @@ -262,7 +262,7 @@ class Test { is_list: false, is_required: true, }, - description: Some(Comment("**상세주소**".into())), + description: Some(Comment::try_from("**상세주소**").unwrap()), }, ObjectField { name: Identifier::try_from("city").unwrap(), @@ -272,7 +272,7 @@ class Test { is_list: false, is_required: false, }, - description: Some(Comment("**도시**".into())), + description: Some(Comment::try_from("**도시**").unwrap()), }, ObjectField { name: Identifier::try_from("province").unwrap(), @@ -282,7 +282,7 @@ class Test { is_list: false, is_required: false, }, - description: Some(Comment("**주, 도, 시**".into())), + description: Some(Comment::try_from("**주, 도, 시**").unwrap()), }, ], is_one_of: false, @@ -326,7 +326,7 @@ class Address { fn one_of_object() { let object = Object { name: Identifier::try_from("MonthOption").unwrap(), - description: Some(Comment("**할부 개월 수 설정**".into())), + description: Some(Comment::try_from("**할부 개월 수 설정**").unwrap()), fields: vec![ ObjectField { name: Identifier::try_from("fixedMonth").unwrap(), @@ -336,9 +336,9 @@ class Address { is_list: false, is_required: true, }, - description: Some(Comment( - "**구매자가 선택할 수 없도록 고정된 할부 개월수**".into(), - )), + description: Some(Comment::try_from( + "**구매자가 선택할 수 없도록 고정된 할부 개월수**", + ).unwrap()), }, ObjectField { name: Identifier::try_from("availableMonthList").unwrap(), @@ -348,9 +348,9 @@ class Address { is_list: true, is_required: true, }, - description: Some(Comment( - "**구매자가 선택할 수 있는 할부 개월수 리스트**".into(), - )), + description: Some(Comment::try_from( + "**구매자가 선택할 수 있는 할부 개월수 리스트**", + ).unwrap()), }, ], is_one_of: true, diff --git a/crates/browser_sdk_dart_codegen/src/lib.rs b/crates/browser_sdk_dart_codegen/src/lib.rs index 6a2822d..8051c71 100644 --- a/crates/browser_sdk_dart_codegen/src/lib.rs +++ b/crates/browser_sdk_dart_codegen/src/lib.rs @@ -114,7 +114,7 @@ impl ResourceProcessor { name: field_name, serialized_name: name.to_string(), value_type, - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), }; } ParameterType::ResourceRef(r) => { @@ -135,7 +135,7 @@ impl ResourceProcessor { name: field_name, serialized_name: name.to_string(), value_type, - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), } } @@ -228,21 +228,21 @@ impl ResourceProcessor { match ¶meter.r#type { ParameterType::Object { properties, hide_if_empty: _ } => Some(Entity::Object(Object { name: name.clone(), - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), fields: Self::build_field_list(properties.iter()), is_one_of: false, union_parents: vec![], })), ParameterType::EmptyObject => Some(Entity::Object(Object { name: name.clone(), - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), fields: vec![], is_one_of: false, union_parents: vec![], })), ParameterType::Enum { variants, .. } => Some(Entity::Enum(Enum { name: name.clone(), - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), variants: variants .iter() .map(|(value, variant)| EnumVariant { @@ -252,21 +252,21 @@ impl ResourceProcessor { Identifier::try_from(value.as_str()).unwrap() }, value: value.clone(), - description: variant.description.clone().map(Comment), + description: variant.description.clone().map(|d| Comment::try_from(d).unwrap()), }) .collect(), union_parents: vec![], })), ParameterType::OneOf { properties, hide_if_empty: _ } => Some(Entity::Object(Object { name: name.clone(), - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), fields: Self::build_field_list(properties.iter()), is_one_of: true, union_parents: vec![], })), ParameterType::Union { types, hide_if_empty: _ } => Some(Entity::Union(Union { name: name.clone(), - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), variants: types .iter() .map(|parameter| match ¶meter.r#type { @@ -279,7 +279,7 @@ impl ResourceProcessor { .to_case(Case::Camel) .try_into() .unwrap(), - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), type_name: type_reference, } } @@ -289,7 +289,7 @@ impl ResourceProcessor { })), ParameterType::Intersection { types, hide_if_empty: _ } => Some(Entity::Intersection(Intersection { name: name.clone(), - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), constituents: types .iter() .map(|parameter| match ¶meter.r#type { @@ -318,7 +318,7 @@ impl ResourceProcessor { hide_if_empty: _, } => Some(Entity::DiscriminatedUnion(DiscriminatedUnion { name: name.clone(), - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), discriminator: Identifier::try_from(discriminator.as_str()).unwrap(), variants: types .iter() @@ -329,7 +329,7 @@ impl ResourceProcessor { discriminator_value: value.clone(), name: value.to_case(Case::Camel).try_into().unwrap(), type_name: type_reference, - description: parameter.description.clone().map(Comment), + description: parameter.description.clone().map(|d| Comment::try_from(d).unwrap()), } } _ => unreachable!(), diff --git a/crates/browser_sdk_ts_codegen/Cargo.toml b/crates/browser_sdk_ts_codegen/Cargo.toml index 4c51084..c8d4cde 100644 --- a/crates/browser_sdk_ts_codegen/Cargo.toml +++ b/crates/browser_sdk_ts_codegen/Cargo.toml @@ -18,5 +18,6 @@ indexmap = { workspace = true } indoc = { workspace = true } browser_sdk_schema = { workspace = true } browser_sdk_ts_codegen_macros = { workspace = true } +browser_sdk_utils.workspace = true pretty_assertions = { workspace = true } pathdiff = "0.2.1" diff --git a/crates/browser_sdk_ts_codegen/src/comment.rs b/crates/browser_sdk_ts_codegen/src/comment.rs index 33b8b02..d8994e1 100644 --- a/crates/browser_sdk_ts_codegen/src/comment.rs +++ b/crates/browser_sdk_ts_codegen/src/comment.rs @@ -1,3 +1,5 @@ +use browser_sdk_utils::{MdastNodeExt, ToMdastExt}; + pub(crate) trait JsDocExt> { fn to_jsdoc(&self, deprecated: bool) -> String; } @@ -19,6 +21,11 @@ impl> JsDocExt for Option { pub fn generate_jsdoc_comment(comment: &str, deprecated: bool) -> String { let mut comment = comment + .to_mdast() + .unwrap() + .remove_jsx_elements() + .to_markdown_string() + .unwrap() .trim() .split('\n') .map(|line| format!("* {}", line)) diff --git a/crates/browser_sdk_utils/Cargo.toml b/crates/browser_sdk_utils/Cargo.toml new file mode 100644 index 0000000..abaf52e --- /dev/null +++ b/crates/browser_sdk_utils/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "browser_sdk_utils" +version = "0.1.0" +edition = "2024" + +[dependencies] +markdown.workspace = true +mdast_util_to_markdown.workspace = true diff --git a/crates/browser_sdk_utils/src/lib.rs b/crates/browser_sdk_utils/src/lib.rs new file mode 100644 index 0000000..d5be81e --- /dev/null +++ b/crates/browser_sdk_utils/src/lib.rs @@ -0,0 +1,45 @@ +use markdown::{mdast::Node, to_mdast, ParseOptions}; +use mdast_util_to_markdown::to_markdown; + +pub trait ToMdastExt { + fn to_mdast(&self) -> Result; +} + +impl ToMdastExt for str { + fn to_mdast(&self) -> Result { + to_mdast(self, &ParseOptions::mdx()) + } +} + +impl ToMdastExt for String { + fn to_mdast(&self) -> Result { + to_mdast(self, &ParseOptions::mdx()) + } +} + +pub trait MdastNodeExt { + fn to_markdown_string(&self) -> Result; + fn remove_jsx_elements(&mut self) -> &mut Self; +} + +impl MdastNodeExt for Node { + fn to_markdown_string(&self) -> Result { + to_markdown(self) + } + + fn remove_jsx_elements(&mut self) -> &mut Self { + if let Some(children) = self.children_mut() { + children.retain_mut(|child| { + match child { + Node::MdxJsxFlowElement(_) | Node::MdxJsxTextElement(_) => false, + _ => { + child.remove_jsx_elements(); + true + } + } + }); + } + + self + } +}