From 3a1fd1918f65c0a751a872d66e2d5bb01c64830c Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 3 Oct 2022 17:34:52 +0200 Subject: Add support for creating a component, using layout --- tests/test_utils/mod.rs | 1 + tests/test_utils/to_document.rs | 601 ++++++++++++++++++++++++++++++++++++++++ tests/test_utils/to_swc.rs | 53 +++- tests/xxx_document.rs | 229 +++++++++++++++ tests/xxx_swc.rs | 1 + 5 files changed, 883 insertions(+), 2 deletions(-) create mode 100644 tests/test_utils/to_document.rs create mode 100644 tests/xxx_document.rs (limited to 'tests') diff --git a/tests/test_utils/mod.rs b/tests/test_utils/mod.rs index 87c1e1e..aa1d509 100644 --- a/tests/test_utils/mod.rs +++ b/tests/test_utils/mod.rs @@ -1,4 +1,5 @@ pub mod hast; pub mod swc; +pub mod to_document; pub mod to_hast; pub mod to_swc; diff --git a/tests/test_utils/to_document.rs b/tests/test_utils/to_document.rs new file mode 100644 index 0000000..b44d029 --- /dev/null +++ b/tests/test_utils/to_document.rs @@ -0,0 +1,601 @@ +extern crate swc_common; +extern crate swc_ecma_ast; +use crate::test_utils::to_swc::Program; + +/// JSX runtimes. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum JsxRuntime { + /// Automatic runtime. + /// + /// With the automatic runtime, some module is expected to exist somewhere. + /// That modules is expected to expose a certain API. + /// The compiler adds an import of that module and compiles JSX away to + /// function calls that use that API. + #[default] + Automatic, + /// Classic runtime. + /// + /// With the classic runtime, you define two values yourself in each file, + /// which are expected to work a certain way. + /// The compiler compiles JSX away to function calls using those two values. + Classic, +} + +/// Configuration. +#[derive(Debug, PartialEq, Eq)] +pub struct Options { + /// Pragma for JSX (used in classic runtime). + /// + /// Default: `React.createElement`. + pub pragma: Option, + /// Pragma for JSX fragments (used in classic runtime). + /// + /// Default: `React.Fragment`. + pub pragma_frag: Option, + /// Where to import the identifier of `pragma` from (used in classic runtime). + /// + /// Default: `react`. + pub pragma_import_source: Option, + /// Place to import automatic JSX runtimes from (used in automatic runtime). + /// + /// Default: `react`. + pub jsx_import_source: Option, + /// JSX runtime to use. + /// + /// Default: `automatic`. + pub jsx_runtime: Option, +} + +impl Default for Options { + /// Use the automatic JSX runtime with React. + fn default() -> Self { + Self { + pragma: None, + pragma_frag: None, + pragma_import_source: None, + jsx_import_source: None, + jsx_runtime: Some(JsxRuntime::default()), + } + } +} + +#[allow(dead_code)] +pub fn to_document(mut program: Program, options: &Options) -> Result { + // New body children. + let mut replacements = vec![]; + + // Inject JSX configuration comment. + if let Some(runtime) = &options.jsx_runtime { + let mut pragmas = vec![]; + let react = &"react".into(); + let create_element = &"React.createElement".into(); + let fragment = &"React.Fragment".into(); + + if *runtime == JsxRuntime::Automatic { + pragmas.push("@jsxRuntime automatic".into()); + pragmas.push(format!( + "@jsxImportSource {}", + if let Some(jsx_import_source) = &options.jsx_import_source { + jsx_import_source + } else { + react + } + )); + } else { + pragmas.push("@jsxRuntime classic".into()); + pragmas.push(format!( + "@jsx {}", + if let Some(pragma) = &options.pragma { + pragma + } else { + create_element + } + )); + pragmas.push(format!( + "@jsxFrag {}", + if let Some(pragma_frag) = &options.pragma_frag { + pragma_frag + } else { + fragment + } + )); + } + + if !pragmas.is_empty() { + program.comments.insert( + 0, + swc_common::comments::Comment { + kind: swc_common::comments::CommentKind::Block, + text: pragmas.join(" ").into(), + span: swc_common::DUMMY_SP, + }, + ); + } + } + + // Inject an import in the classic runtime for the pragma (and presumably, + // fragment). + if options.jsx_runtime == Some(JsxRuntime::Classic) { + let pragma = if let Some(pragma) = &options.pragma { + pragma + } else { + "React" + }; + let sym = pragma.split('.').next().expect("first item always exists"); + + replacements.push(swc_ecma_ast::ModuleItem::ModuleDecl( + swc_ecma_ast::ModuleDecl::Import(swc_ecma_ast::ImportDecl { + specifiers: vec![swc_ecma_ast::ImportSpecifier::Named( + swc_ecma_ast::ImportNamedSpecifier { + local: swc_ecma_ast::Ident { + sym: sym.into(), + optional: false, + span: swc_common::DUMMY_SP, + }, + imported: None, + span: swc_common::DUMMY_SP, + is_type_only: false, + }, + )], + src: Box::new(swc_ecma_ast::Str { + value: (if let Some(source) = &options.pragma_import_source { + source.clone() + } else { + "react".into() + }) + .into(), + span: swc_common::DUMMY_SP, + raw: None, + }), + type_only: false, + asserts: None, + span: swc_common::DUMMY_SP, + }), + )); + } + + // Find the `export default`, the JSX expression, and leave the rest as it + // is. + let mut input = program.module.body.split_off(0); + input.reverse(); + // To do: place position in this. + let mut layout = false; + let content = true; + + while let Some(module_item) = input.pop() { + match module_item { + // ```js + // export default props => <>{props.children} + // ``` + // + // Treat it as an inline layout declaration. + // + // In estree, the below two are the same node (`ExportDefault`). + swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportDefaultDecl( + decl, + )) => { + // To do: use positional info. + if layout { + return Err("Cannot specify multiple layouts".into()); + } + + // To do: set positional info. + layout = true; + replacements.push(create_layout_decl(match decl.decl { + swc_ecma_ast::DefaultDecl::Class(cls) => swc_ecma_ast::Expr::Class(cls), + swc_ecma_ast::DefaultDecl::Fn(func) => swc_ecma_ast::Expr::Fn(func), + swc_ecma_ast::DefaultDecl::TsInterfaceDecl(_) => { + // To do: improved error? Not sure what a real example of this is? + unreachable!("Cannot use TypeScript interface declarations as default export in MDX") + }, + })); + } + swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportDefaultExpr( + expr, + )) => { + // To do: use positional info. + if layout { + return Err("Cannot specify multiple layouts".into()); + } + + // To do: set positional info. + layout = true; + replacements.push(create_layout_decl(*expr.expr)); + } + // ```js + // export {a, b as c} from 'd' + // export {a, b as c} + // ``` + swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportNamed(mut named_export)) => { + // SWC is currently crashing when generating code, w/o source + // map, if an actual location is set on this node. + named_export.span = swc_common::DUMMY_SP; + + let mut index = 0; + let mut id = None; + + while index < named_export.specifiers.len() { + let mut take = false; + // Note: the `swc_ecma_ast::ExportSpecifier::Default` + // branch of this looks interesting, but as far as I + // understand it *is not* valid ES. + // `export a from 'b'` is a syntax error, even in SWC. + if let swc_ecma_ast::ExportSpecifier::Named(named) = &named_export.specifiers[index] { + if let Some(swc_ecma_ast::ModuleExportName::Ident(ident)) = &named.exported { + if ident.sym.as_ref() == "default" { + // For some reason the AST supports strings + // instead of identifiers. + // Looks like some TC39 proposal. Ignore for now + // and only do things if this is an ID. + if let swc_ecma_ast::ModuleExportName::Ident(ident) = &named.orig { + // To do: use positional info. + if layout { + return Err("Cannot specify multiple layouts".into()); + } + // To do: set positional info. + layout = true; + take = true; + id = Some(ident.clone()); + } + } + } + } + + if take { + named_export.specifiers.remove(index); + } else { + index += 1; + } + } + + if let Some(id) = id { + let source = named_export.src.clone(); + + // If there was just a default export, we can drop the original node. + if !named_export.specifiers.is_empty() { + // Pass through. + replacements.push(swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportNamed(named_export))); + } + + // It’s an `export {x} from 'y'`, so generate an import. + if let Some(source) = source { + replacements.push(swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::Import(swc_ecma_ast::ImportDecl { + specifiers: vec![swc_ecma_ast::ImportSpecifier::Named( + swc_ecma_ast::ImportNamedSpecifier { + local: swc_ecma_ast::Ident { + sym: "MDXLayout".into(), + optional: false, + span: swc_common::DUMMY_SP, + }, + imported: Some(swc_ecma_ast::ModuleExportName::Ident( id)), + span: swc_common::DUMMY_SP, + is_type_only: false, + }, + )], + src: source, + type_only: false, + asserts: None, + span: swc_common::DUMMY_SP, + }))) + } + // It’s an `export {x}`, so generate a variable declaration. + else { + replacements.push(create_layout_decl(swc_ecma_ast::Expr::Ident(id))); + } + } else { + // Pass through. + replacements.push(swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportNamed(named_export))); + } + } + swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::Import(_)) + | swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportDecl(_)) + | swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportAll(_)) + // To do: handle TS things? + | swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::TsImportEquals(_)) + | swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::TsExportAssignment( + _, + )) + | swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::TsNamespaceExport( + _, + )) => { + // Pass through. + replacements.push(module_item); + } + swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Expr(expr_stmt)) => { + match *expr_stmt.expr { + swc_ecma_ast::Expr::JSXElement(elem) => { + replacements.append(&mut create_mdx_content( + Some(swc_ecma_ast::Expr::JSXElement(elem)), + layout, + )); + } + swc_ecma_ast::Expr::JSXFragment(mut frag) => { + // Unwrap if possible. + if frag.children.len() == 1 { + let item = frag.children.pop().unwrap(); + + if let swc_ecma_ast::JSXElementChild::JSXElement(elem) = item { + replacements.append(&mut create_mdx_content( + Some(swc_ecma_ast::Expr::JSXElement(elem)), + layout, + )); + continue; + } + + frag.children.push(item) + } + + replacements.append(&mut create_mdx_content( + Some(swc_ecma_ast::Expr::JSXFragment(frag)), + layout, + )); + } + _ => { + // Pass through. + replacements.push(swc_ecma_ast::ModuleItem::Stmt( + swc_ecma_ast::Stmt::Expr(expr_stmt), + )); + } + } + } + swc_ecma_ast::ModuleItem::Stmt(stmt) => { + replacements.push(swc_ecma_ast::ModuleItem::Stmt(stmt)); + } + } + } + + // Generate an empty component. + if !content { + replacements.append(&mut create_mdx_content(None, layout)); + } + + // ```jsx + // export default MDXContent + // ``` + replacements.push(swc_ecma_ast::ModuleItem::ModuleDecl( + swc_ecma_ast::ModuleDecl::ExportDefaultExpr(swc_ecma_ast::ExportDefaultExpr { + expr: Box::new(swc_ecma_ast::Expr::Ident(swc_ecma_ast::Ident { + sym: "MDXContent".into(), + optional: false, + span: swc_common::DUMMY_SP, + })), + span: swc_common::DUMMY_SP, + }), + )); + + program.module.body = replacements; + + Ok(program) +} + +/// Create a content component. +fn create_mdx_content( + expr: Option, + has_internal_layout: bool, +) -> Vec { + // ```jsx + // xxx + // ``` + let mut result = swc_ecma_ast::Expr::JSXElement(Box::new(swc_ecma_ast::JSXElement { + opening: swc_ecma_ast::JSXOpeningElement { + name: swc_ecma_ast::JSXElementName::Ident(swc_ecma_ast::Ident { + sym: "MDXLayout".into(), + optional: false, + span: swc_common::DUMMY_SP, + }), + attrs: vec![swc_ecma_ast::JSXAttrOrSpread::SpreadElement( + swc_ecma_ast::SpreadElement { + dot3_token: swc_common::DUMMY_SP, + expr: Box::new(swc_ecma_ast::Expr::Ident(swc_ecma_ast::Ident { + sym: "props".into(), + optional: false, + span: swc_common::DUMMY_SP, + })), + }, + )], + self_closing: false, + type_args: None, + span: swc_common::DUMMY_SP, + }, + closing: Some(swc_ecma_ast::JSXClosingElement { + name: swc_ecma_ast::JSXElementName::Ident(swc_ecma_ast::Ident { + sym: "MDXLayout".into(), + optional: false, + span: swc_common::DUMMY_SP, + }), + span: swc_common::DUMMY_SP, + }), + // ```jsx + // <_createMdxContent {...props} /> + // ``` + children: vec![swc_ecma_ast::JSXElementChild::JSXElement(Box::new( + swc_ecma_ast::JSXElement { + opening: swc_ecma_ast::JSXOpeningElement { + name: swc_ecma_ast::JSXElementName::Ident(swc_ecma_ast::Ident { + sym: "_createMdxContent".into(), + optional: false, + span: swc_common::DUMMY_SP, + }), + attrs: vec![swc_ecma_ast::JSXAttrOrSpread::SpreadElement( + swc_ecma_ast::SpreadElement { + dot3_token: swc_common::DUMMY_SP, + expr: Box::new(swc_ecma_ast::Expr::Ident(swc_ecma_ast::Ident { + sym: "props".into(), + optional: false, + span: swc_common::DUMMY_SP, + })), + }, + )], + self_closing: true, + type_args: None, + span: swc_common::DUMMY_SP, + }, + closing: None, + children: vec![], + span: swc_common::DUMMY_SP, + }, + ))], + span: swc_common::DUMMY_SP, + })); + + if !has_internal_layout { + // ```jsx + // MDXLayout ? xxx : _createMdxContent(props) + // ``` + result = swc_ecma_ast::Expr::Cond(swc_ecma_ast::CondExpr { + test: Box::new(swc_ecma_ast::Expr::Ident(swc_ecma_ast::Ident { + sym: "MDXLayout".into(), + optional: false, + span: swc_common::DUMMY_SP, + })), + cons: Box::new(result), + alt: Box::new(swc_ecma_ast::Expr::Call(swc_ecma_ast::CallExpr { + callee: swc_ecma_ast::Callee::Expr(Box::new(swc_ecma_ast::Expr::Ident( + swc_ecma_ast::Ident { + sym: "_createMdxContent".into(), + optional: false, + span: swc_common::DUMMY_SP, + }, + ))), + args: vec![swc_ecma_ast::ExprOrSpread { + spread: None, + expr: Box::new(swc_ecma_ast::Expr::Ident(swc_ecma_ast::Ident { + sym: "props".into(), + optional: false, + span: swc_common::DUMMY_SP, + })), + }], + type_args: None, + span: swc_common::DUMMY_SP, + })), + span: swc_common::DUMMY_SP, + }); + } + + // ```jsx + // function _createMdxContent(props) { + // return xxx + // } + // ``` + let create_mdx_content = swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Decl( + swc_ecma_ast::Decl::Fn(swc_ecma_ast::FnDecl { + ident: swc_ecma_ast::Ident { + sym: "_createMdxContent".into(), + optional: false, + span: swc_common::DUMMY_SP, + }, + declare: false, + function: Box::new(swc_ecma_ast::Function { + params: vec![swc_ecma_ast::Param { + pat: swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent { + id: swc_ecma_ast::Ident { + sym: "props".into(), + optional: false, + span: swc_common::DUMMY_SP, + }, + type_ann: None, + }), + decorators: vec![], + span: swc_common::DUMMY_SP, + }], + decorators: vec![], + body: Some(swc_ecma_ast::BlockStmt { + stmts: vec![swc_ecma_ast::Stmt::Return(swc_ecma_ast::ReturnStmt { + arg: Some(Box::new(expr.unwrap_or({ + swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Null(swc_ecma_ast::Null { + span: swc_common::DUMMY_SP, + })) + }))), + span: swc_common::DUMMY_SP, + })], + span: swc_common::DUMMY_SP, + }), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + span: swc_common::DUMMY_SP, + }), + }), + )); + + // ```jsx + // function MDXContent(props = {}) { + // return xxx + // } + // ``` + let mdx_content = swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Decl( + swc_ecma_ast::Decl::Fn(swc_ecma_ast::FnDecl { + ident: swc_ecma_ast::Ident { + sym: "MDXContent".into(), + optional: false, + span: swc_common::DUMMY_SP, + }, + declare: false, + function: Box::new(swc_ecma_ast::Function { + params: vec![swc_ecma_ast::Param { + pat: swc_ecma_ast::Pat::Assign(swc_ecma_ast::AssignPat { + left: Box::new(swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent { + id: swc_ecma_ast::Ident { + sym: "props".into(), + optional: false, + span: swc_common::DUMMY_SP, + }, + type_ann: None, + })), + right: Box::new(swc_ecma_ast::Expr::Object(swc_ecma_ast::ObjectLit { + props: vec![], + span: swc_common::DUMMY_SP, + })), + span: swc_common::DUMMY_SP, + type_ann: None, + }), + decorators: vec![], + span: swc_common::DUMMY_SP, + }], + decorators: vec![], + body: Some(swc_ecma_ast::BlockStmt { + stmts: vec![swc_ecma_ast::Stmt::Return(swc_ecma_ast::ReturnStmt { + arg: Some(Box::new(result)), + span: swc_common::DUMMY_SP, + })], + span: swc_common::DUMMY_SP, + }), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + span: swc_common::DUMMY_SP, + }), + }), + )); + + vec![create_mdx_content, mdx_content] +} + +/// Create a layout, inside the document. +fn create_layout_decl(expr: swc_ecma_ast::Expr) -> swc_ecma_ast::ModuleItem { + // ```jsx + // const MDXLayout = xxx + // ``` + swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Decl(swc_ecma_ast::Decl::Var(Box::new( + swc_ecma_ast::VarDecl { + kind: swc_ecma_ast::VarDeclKind::Const, + declare: false, + decls: vec![swc_ecma_ast::VarDeclarator { + name: swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent { + id: swc_ecma_ast::Ident { + sym: "MDXLayout".into(), + optional: false, + span: swc_common::DUMMY_SP, + }, + type_ann: None, + }), + init: Some(Box::new(expr)), + span: swc_common::DUMMY_SP, + definite: false, + }], + span: swc_common::DUMMY_SP, + }, + )))) +} diff --git a/tests/test_utils/to_swc.rs b/tests/test_utils/to_swc.rs index 1d25f47..59d60ee 100644 --- a/tests/test_utils/to_swc.rs +++ b/tests/test_utils/to_swc.rs @@ -345,11 +345,44 @@ fn transform_root( node: &hast::Node, _root: &hast::Root, ) -> Result, String> { - let children = all(context, node)?; + let mut children = all(context, node)?; + let mut queue = vec![]; + let mut nodes = vec![]; + let mut seen = false; + + children.reverse(); + + // Remove initial/final whitespace. + while let Some(child) = children.pop() { + let mut stash = false; + + if let swc_ecma_ast::JSXElementChild::JSXExprContainer(container) = &child { + if let swc_ecma_ast::JSXExpr::Expr(expr) = &container.expr { + if let swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(str)) = (*expr).as_ref() { + if inter_element_whitespace(str.value.as_ref()) { + stash = true; + } + } + } + } + + if stash { + if seen { + queue.push(child); + } + } else { + if !queue.is_empty() { + nodes.append(&mut queue); + } + nodes.push(child); + seen = true; + } + } + // To do: remove whitespace? // To do: return a single child if there is one? Ok(Some(swc_ecma_ast::JSXElementChild::JSXFragment( - create_fragment(children, node), + create_fragment(nodes, node), ))) } @@ -493,6 +526,22 @@ fn position_to_span(position: Option<&Position>) -> swc_common::Span { }) } +fn inter_element_whitespace(value: &str) -> bool { + let bytes = value.as_bytes(); + let mut index = 0; + + while index < bytes.len() { + match bytes[index] { + // To do: form feed. + b'\t' | b'\r' | b'\n' | b' ' => {} + _ => return false, + } + index += 1; + } + + true +} + /// Different kinds of JSX names. enum JsxName<'a> { // `a.b.c` diff --git a/tests/xxx_document.rs b/tests/xxx_document.rs new file mode 100644 index 0000000..2d89f16 --- /dev/null +++ b/tests/xxx_document.rs @@ -0,0 +1,229 @@ +extern crate micromark; +extern crate swc_common; +extern crate swc_ecma_ast; +extern crate swc_ecma_codegen; +mod test_utils; +use micromark::{micromark_to_mdast, Constructs, Options}; +use pretty_assertions::assert_eq; +use swc_common::{sync::Lrc, FilePathMapping, SourceMap}; +use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; +use test_utils::{ + swc::{parse_esm, parse_expression}, + to_document::{to_document, Options as DocumentOptions}, + to_hast::to_hast, + to_swc::{to_swc, Program}, +}; + +// To do: share with `xxx_swc`. +fn serialize(program: &Program) -> String { + let mut buf = vec![]; + let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + // let comm = &program.comments as &dyn swc_common::comments::Comments; + { + let mut emitter = Emitter { + cfg: swc_ecma_codegen::Config { + ..Default::default() + }, + cm: cm.clone(), + // To do: figure out how to pass them. + comments: None, + wr: JsWriter::new(cm, "\n", &mut buf, None), + }; + + emitter.emit_module(&program.module).unwrap(); + } + + String::from_utf8_lossy(&buf).to_string() +} + +fn from_markdown(value: &str) -> Result { + let mdast = micromark_to_mdast( + value, + &Options { + constructs: Constructs::mdx(), + mdx_esm_parse: Some(Box::new(parse_esm)), + mdx_expression_parse: Some(Box::new(parse_expression)), + ..Options::default() + }, + )?; + let hast = to_hast(&mdast); + let program = to_document(to_swc(&hast)?, &DocumentOptions::default())?; + let value = serialize(&program); + Ok(value) +} + +#[test] +fn document() -> Result<(), String> { + assert_eq!( + from_markdown("# hi\n\nAlpha *bravo* **charlie**.")?, + "function _createMdxContent(props) { + return <>

{\"hi\"}

{\"\\n\"}

{\"Alpha \"}{\"bravo\"}{\" \"}{\"charlie\"}{\".\"}

; +} +function MDXContent(props = {}) { + return MDXLayout ? <_createMdxContent {...props}/> : _createMdxContent(props); +} +export default MDXContent; +", + "should support a small program", + ); + + assert_eq!( + from_markdown("import a from 'b'\n\n# {a}")?, + "import a from 'b'; +function _createMdxContent(props) { + return

{a}

; +} +function MDXContent(props = {}) { + return MDXLayout ? <_createMdxContent {...props}/> : _createMdxContent(props); +} +export default MDXContent; +", + "should support an import", + ); + + assert_eq!( + from_markdown("export * from 'a'\n\n# b")?, + "export * from 'a'; +function _createMdxContent(props) { + return

{\"b\"}

; +} +function MDXContent(props = {}) { + return MDXLayout ? <_createMdxContent {...props}/> : _createMdxContent(props); +} +export default MDXContent; +", + "should support an export all", + ); + + assert_eq!( + from_markdown("export function a() {}")?, + "export function a() {} +function _createMdxContent(props) { + return <>; +} +function MDXContent(props = {}) { + return MDXLayout ? <_createMdxContent {...props}/> : _createMdxContent(props); +} +export default MDXContent; +", + "should support an export declaration", + ); + + assert_eq!( + from_markdown("export default a")?, + "const MDXLayout = a; +function _createMdxContent(props) { + return <>; +} +function MDXContent(props = {}) { + return <_createMdxContent {...props}/>; +} +export default MDXContent; +", + "should support an export default expression", + ); + + assert_eq!( + from_markdown("export default function () {}")?, + "const MDXLayout = function() {}; +function _createMdxContent(props) { + return <>; +} +function MDXContent(props = {}) { + return <_createMdxContent {...props}/>; +} +export default MDXContent; +", + "should support an export default declaration", + ); + + assert_eq!( + from_markdown("export {a, b as default}")?, + "export { a }; +const MDXLayout = b; +function _createMdxContent(props) { + return <>; +} +function MDXContent(props = {}) { + return <_createMdxContent {...props}/>; +} +export default MDXContent; +", + "should support a named export w/o source, w/ a default specifier", + ); + + assert_eq!( + from_markdown("export {a}")?, + "export { a }; +function _createMdxContent(props) { + return <>; +} +function MDXContent(props = {}) { + return MDXLayout ? <_createMdxContent {...props}/> : _createMdxContent(props); +} +export default MDXContent; +", + "should support a named export w/o source, w/o a default specifier", + ); + + assert_eq!( + from_markdown("export {}")?, + "export { }; +function _createMdxContent(props) { + return <>; +} +function MDXContent(props = {}) { + return MDXLayout ? <_createMdxContent {...props}/> : _createMdxContent(props); +} +export default MDXContent; +", + "should support a named export w/o source, w/o a specifiers", + ); + + // ........... + + assert_eq!( + from_markdown("export {a, b as default} from 'c'")?, + "export { a } from 'c'; +import { b as MDXLayout } from 'c'; +function _createMdxContent(props) { + return <>; +} +function MDXContent(props = {}) { + return <_createMdxContent {...props}/>; +} +export default MDXContent; +", + "should support a named export w/ source, w/ a default specifier", + ); + + assert_eq!( + from_markdown("export {a} from 'b'")?, + "export { a } from 'b'; +function _createMdxContent(props) { + return <>; +} +function MDXContent(props = {}) { + return MDXLayout ? <_createMdxContent {...props}/> : _createMdxContent(props); +} +export default MDXContent; +", + "should support a named export w/ source, w/o a default specifier", + ); + + assert_eq!( + from_markdown("export {} from 'a'")?, + "export { } from 'a'; +function _createMdxContent(props) { + return <>; +} +function MDXContent(props = {}) { + return MDXLayout ? <_createMdxContent {...props}/> : _createMdxContent(props); +} +export default MDXContent; +", + "should support a named export w/ source, w/o a specifiers", + ); + + Ok(()) +} diff --git a/tests/xxx_swc.rs b/tests/xxx_swc.rs index a882244..5504432 100644 --- a/tests/xxx_swc.rs +++ b/tests/xxx_swc.rs @@ -11,6 +11,7 @@ use test_utils::{ to_swc::{to_swc, Program}, }; +// To do: share with `xxx_document`. fn serialize(program: &Program) -> String { let mut buf = vec![]; let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); -- cgit