diff --git a/packages/@winglang/wingc/src/docs.rs b/packages/@winglang/wingc/src/docs.rs index 960b0c34187..946d35e2e90 100644 --- a/packages/@winglang/wingc/src/docs.rs +++ b/packages/@winglang/wingc/src/docs.rs @@ -6,7 +6,7 @@ use regex::Regex; use crate::{ ast::{AccessModifier, Phase}, closure_transform::CLOSURE_CLASS_PREFIX, - jsify::codemaker::CodeMaker, + jsify::{codemaker::CodeMaker, escape_javascript_string}, type_check::{ jsii_importer::is_construct_base, symbol_env::SymbolEnvKind, Class, ClassLike, Enum, FunctionSignature, Interface, Namespace, Struct, SymbolKind, Type, TypeRef, VariableInfo, VariableKind, CLASS_INFLIGHT_INIT_NAME, @@ -47,6 +47,50 @@ impl Docs { markdown.to_string().trim().to_string() } + pub fn to_escaped_string(&self) -> String { + let mut contents = String::new(); + if let Some(summary) = &self.summary { + contents.push_str(summary); + } + if let Some(remarks) = &self.remarks { + contents.push_str("\n\n"); + contents.push_str(remarks); + } + if let Some(example) = &self.example { + contents.push_str("\n@example "); + contents.push_str(example); + } + if let Some(returns) = &self.returns { + contents.push_str("\n@returns "); + contents.push_str(returns); + } + if let Some(deprecated) = &self.deprecated { + contents.push_str("\n@deprecated "); + contents.push_str(deprecated); + } + if let Some(see) = &self.see { + contents.push_str("\n@see "); + contents.push_str(see); + } + if let Some(default) = &self.default { + contents.push_str("\n@default "); + contents.push_str(default); + } + if let Some(stability) = &self.stability { + contents.push_str("\n@stability "); + contents.push_str(stability); + } + if let Some(_) = &self.subclassable { + contents.push_str("\n@subclassable"); + } + for (k, v) in self.custom.iter() { + contents.push_str(&format!("\n@{} ", k)); + contents.push_str(v); + } + + escape_javascript_string(&contents) + } + pub(crate) fn with_summary(summary: &str) -> Docs { Docs { summary: Some(summary.to_string()), diff --git a/packages/@winglang/wingc/src/jsify.rs b/packages/@winglang/wingc/src/jsify.rs index dc2959baf19..53a924bbf2a 100644 --- a/packages/@winglang/wingc/src/jsify.rs +++ b/packages/@winglang/wingc/src/jsify.rs @@ -2704,7 +2704,7 @@ fn lookup_span(span: &WingSpan, files: &Files) -> String { result } -fn escape_javascript_string(s: &str) -> String { +pub fn escape_javascript_string(s: &str) -> String { let mut result = String::new(); // escape all escapable characters -- see the section "Escape sequences" in diff --git a/packages/@winglang/wingc/src/json_schema_generator.rs b/packages/@winglang/wingc/src/json_schema_generator.rs index 8267644b355..1efcf86cf89 100644 --- a/packages/@winglang/wingc/src/json_schema_generator.rs +++ b/packages/@winglang/wingc/src/json_schema_generator.rs @@ -45,7 +45,7 @@ impl JsonSchemaGenerator { Some(docs) => format!( "{{type:\"{}\",description:\"{}\"}}", jsified_type, - docs.summary.unwrap_or_default() + docs.to_escaped_string() ), None => format!("{{type:\"{}\"}}", jsified_type), } @@ -59,7 +59,7 @@ impl JsonSchemaGenerator { code.append("},"); code.append(self.get_struct_schema_required_fields(&s.env)); let docs = docs.unwrap_or(s.docs.clone()); - code.append(format!(",description:\"{}\"", docs.summary.unwrap_or_default())); + code.append(format!(",description:\"{}\"", docs.to_escaped_string())); code.append("}"); code.to_string() } @@ -76,7 +76,7 @@ impl JsonSchemaGenerator { code.append(format!(",items:{}", self.get_struct_schema_field(t, None))); if let Some(docs) = docs { - code.append(format!(",description:\"{}\"", docs.summary.unwrap_or_default())); + code.append(format!(",description:\"{}\"", docs.to_escaped_string())); } code.append("}"); @@ -93,7 +93,7 @@ impl JsonSchemaGenerator { )); if let Some(docs) = docs { - code.append(format!("description:\"{}\",", docs.summary.unwrap_or_default())); + code.append(format!("description:\"{}\",", docs.to_escaped_string())); } code.append("}"); @@ -103,7 +103,7 @@ impl JsonSchemaGenerator { Type::Json(_) => match docs { Some(docs) => format!( "{{type:[\"object\",\"string\",\"boolean\",\"number\",\"array\"],description:\"{}\"}}", - docs.summary.unwrap_or_default() + docs.to_escaped_string() ), None => "{type:[\"object\",\"string\",\"boolean\",\"number\",\"array\"]}".to_string(), }, @@ -118,14 +118,11 @@ impl JsonSchemaGenerator { format!( "{{type:\"string\",enum:[{}],description:\"{}\"}}", choices, - docs.summary.unwrap_or_default() + docs.to_escaped_string() ) } _ => match docs { - Some(docs) => format!( - "{{type:\"null\",description:\"{}\" }}", - docs.summary.unwrap_or_default() - ), + Some(docs) => format!("{{type:\"null\",description:\"{}\" }}", docs.to_escaped_string()), None => "{type:\"null\"}".to_string(), }, } @@ -147,10 +144,7 @@ impl JsonSchemaGenerator { code.append(self.get_struct_schema_required_fields(&struct_.env)); - code.append(format!( - ",description:\"{}\"", - struct_.docs.summary.as_ref().unwrap_or(&String::new()) - )); + code.append(format!(",description:\"{}\"", struct_.docs.to_escaped_string())); // close schema code.append("}"); diff --git a/tests/valid/struct_from_json.test.w b/tests/valid/struct_from_json.test.w index ce6a77eebfe..e87fff49f16 100644 --- a/tests/valid/struct_from_json.test.w +++ b/tests/valid/struct_from_json.test.w @@ -264,12 +264,14 @@ bring "./subdir/structs_2.w" as otherExternalStructs; enum Color { red, green, blue } /// MyStruct docs +/// @foo bar struct MyStruct { /// m1 docs m1: externalStructs.MyStruct; /// m2 docs m2: otherExternalStructs.MyStruct; /// color docs + /// @example Color.red color: Color; } @@ -299,7 +301,7 @@ let expectedSchema = { "color": { "type": "string", "enum": ["red", "green", "blue"], - "description": "color docs", + "description": "color docs\n@example Color.red", }, "m1": { "type": "object", @@ -315,7 +317,7 @@ let expectedSchema = { }, }, "required": ["color", "m1", "m2"], - "description": "MyStruct docs", + "description": "MyStruct docs\n@foo bar", }; expect.equal(schema.asStr(), Json.stringify(expectedSchema));