use crate::hast_util_to_swc::Program;
use crate::swc_utils::{
bytepos_to_point, create_call_expression, create_ident, create_ident_expression,
create_null_expression, create_object_expression, create_str, position_opt_to_string,
span_to_position,
};
use markdown::{
unist::{Point, Position},
Location,
};
use swc_core::common::SyntaxContext;
use swc_core::ecma::ast::{
AssignPat, BindingIdent, BlockStmt, Callee, CondExpr, Decl, DefaultDecl, ExportDefaultExpr,
ExportSpecifier, Expr, ExprOrSpread, FnDecl, Function, ImportDecl, ImportDefaultSpecifier,
ImportNamedSpecifier, ImportPhase, ImportSpecifier, JSXAttrOrSpread, JSXClosingElement,
JSXElement, JSXElementChild, JSXElementName, JSXOpeningElement, ModuleDecl, ModuleExportName,
ModuleItem, Param, Pat, ReturnStmt, SpreadElement, Stmt, VarDecl, VarDeclKind, VarDeclarator,
};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serializable", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serializable", serde(rename_all = "camelCase"))]
pub enum JsxRuntime {
#[default]
Automatic,
Classic,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Options {
pub pragma: Option<String>,
pub pragma_frag: Option<String>,
pub pragma_import_source: Option<String>,
pub jsx_import_source: Option<String>,
pub jsx_runtime: Option<JsxRuntime>,
}
impl Default for Options {
fn default() -> Self {
Self {
pragma: None,
pragma_frag: None,
pragma_import_source: None,
jsx_import_source: None,
jsx_runtime: Some(JsxRuntime::default()),
}
}
}
pub fn mdx_plugin_recma_document(
program: &mut Program,
options: &Options,
location: Option<&Location>,
) -> Result<(), markdown::message::Message> {
let mut replacements = vec![];
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_core::common::comments::Comment {
kind: swc_core::common::comments::CommentKind::Block,
text: pragmas.join(" ").into(),
span: swc_core::common::DUMMY_SP,
},
);
}
}
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(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
specifiers: vec![ImportSpecifier::Default(ImportDefaultSpecifier {
local: create_ident(sym).into(),
span: swc_core::common::DUMMY_SP,
})],
src: Box::new(create_str(
if let Some(source) = &options.pragma_import_source {
source
} else {
"react"
},
)),
type_only: false,
with: None,
phase: ImportPhase::default(),
span: swc_core::common::DUMMY_SP,
})));
}
let mut input = program.module.body.split_off(0);
input.reverse();
let mut layout = false;
let mut layout_position = None;
let mut content = false;
while let Some(module_item) = input.pop() {
match module_item {
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(decl)) => {
err_for_double_layout(
layout,
layout_position.as_ref(),
bytepos_to_point(decl.span.lo, location).as_ref(),
)?;
layout = true;
layout_position = span_to_position(decl.span, location);
match decl.decl {
DefaultDecl::Class(cls) => {
replacements.push(create_layout_decl(Expr::Class(cls)));
}
DefaultDecl::Fn(func) => {
replacements.push(create_layout_decl(Expr::Fn(func)));
}
DefaultDecl::TsInterfaceDecl(_) => {
return Err(
markdown::message::Message {
reason: "Cannot use TypeScript interface declarations as default export in MDX files. The default export is reserved for a layout, which must be a component".into(),
place: bytepos_to_point(decl.span.lo, location).map(|p| Box::new(markdown::message::Place::Point(p))),
source: Box::new("mdxjs-rs".into()),
rule_id: Box::new("ts-interface".into()),
}
);
}
}
}
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(expr)) => {
err_for_double_layout(
layout,
layout_position.as_ref(),
bytepos_to_point(expr.span.lo, location).as_ref(),
)?;
layout = true;
layout_position = span_to_position(expr.span, location);
replacements.push(create_layout_decl(*expr.expr));
}
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(mut named_export)) => {
let mut index = 0;
let mut id = None;
while index < named_export.specifiers.len() {
let mut take = false;
if let ExportSpecifier::Named(named) = &named_export.specifiers[index] {
if let Some(ModuleExportName::Ident(ident)) = &named.exported {
if &ident.sym == "default" {
if let ModuleExportName::Ident(ident) = &named.orig {
err_for_double_layout(
layout,
layout_position.as_ref(),
bytepos_to_point(ident.span.lo, location).as_ref(),
)?;
layout = true;
layout_position = span_to_position(ident.span, location);
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 !named_export.specifiers.is_empty() {
replacements.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
named_export,
)));
}
if let Some(source) = source {
replacements.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
local: create_ident("MDXLayout").into(),
imported: Some(ModuleExportName::Ident(id)),
span: swc_core::common::DUMMY_SP,
is_type_only: false,
})],
src: source,
type_only: false,
with: None,
phase: ImportPhase::default(),
span: swc_core::common::DUMMY_SP,
})));
}
else {
replacements.push(create_layout_decl(create_ident_expression(&id.sym)));
}
} else {
replacements.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
named_export,
)));
}
}
ModuleItem::ModuleDecl(ModuleDecl::Import(x)) => {
replacements.push(ModuleItem::ModuleDecl(ModuleDecl::Import(x)));
}
ModuleItem::ModuleDecl(
ModuleDecl::ExportDecl(_)
| ModuleDecl::ExportAll(_)
| ModuleDecl::TsImportEquals(_)
| ModuleDecl::TsExportAssignment(_)
| ModuleDecl::TsNamespaceExport(_),
) => {
replacements.push(module_item);
}
ModuleItem::Stmt(Stmt::Expr(expr_stmt)) => {
match *expr_stmt.expr {
Expr::JSXElement(elem) => {
content = true;
replacements.append(&mut create_mdx_content(
Some(Expr::JSXElement(elem)),
layout,
));
}
Expr::JSXFragment(mut frag) => {
if frag.children.len() == 1 {
let item = frag.children.pop().unwrap();
if let JSXElementChild::JSXElement(elem) = item {
content = true;
replacements.append(&mut create_mdx_content(
Some(Expr::JSXElement(elem)),
layout,
));
continue;
}
frag.children.push(item);
}
content = true;
replacements.append(&mut create_mdx_content(
Some(Expr::JSXFragment(frag)),
layout,
));
}
_ => {
replacements.push(ModuleItem::Stmt(Stmt::Expr(expr_stmt)));
}
}
}
ModuleItem::Stmt(stmt) => {
replacements.push(ModuleItem::Stmt(stmt));
}
}
}
if !content {
replacements.append(&mut create_mdx_content(None, layout));
}
replacements.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
ExportDefaultExpr {
expr: Box::new(create_ident_expression("MDXContent")),
span: swc_core::common::DUMMY_SP,
},
)));
program.module.body = replacements;
Ok(())
}
fn create_mdx_content(expr: Option<Expr>, has_internal_layout: bool) -> Vec<ModuleItem> {
let mut result = Expr::JSXElement(Box::new(JSXElement {
opening: JSXOpeningElement {
name: JSXElementName::Ident(create_ident("MDXLayout").into()),
attrs: vec![JSXAttrOrSpread::SpreadElement(SpreadElement {
dot3_token: swc_core::common::DUMMY_SP,
expr: Box::new(create_ident_expression("props")),
})],
self_closing: false,
type_args: None,
span: swc_core::common::DUMMY_SP,
},
closing: Some(JSXClosingElement {
name: JSXElementName::Ident(create_ident("MDXLayout").into()),
span: swc_core::common::DUMMY_SP,
}),
children: vec![JSXElementChild::JSXElement(Box::new(JSXElement {
opening: JSXOpeningElement {
name: JSXElementName::Ident(create_ident("_createMdxContent").into()),
attrs: vec![JSXAttrOrSpread::SpreadElement(SpreadElement {
dot3_token: swc_core::common::DUMMY_SP,
expr: Box::new(create_ident_expression("props")),
})],
self_closing: true,
type_args: None,
span: swc_core::common::DUMMY_SP,
},
closing: None,
children: vec![],
span: swc_core::common::DUMMY_SP,
}))],
span: swc_core::common::DUMMY_SP,
}));
if !has_internal_layout {
result = Expr::Cond(CondExpr {
test: Box::new(create_ident_expression("MDXLayout")),
cons: Box::new(result),
alt: Box::new(create_call_expression(
Callee::Expr(Box::new(create_ident_expression("_createMdxContent"))),
vec![ExprOrSpread {
spread: None,
expr: Box::new(create_ident_expression("props")),
}],
)),
span: swc_core::common::DUMMY_SP,
});
}
let create_mdx_content = ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
ident: create_ident("_createMdxContent").into(),
declare: false,
function: Box::new(Function {
params: vec![Param {
pat: Pat::Ident(BindingIdent {
id: create_ident("props").into(),
type_ann: None,
}),
decorators: vec![],
span: swc_core::common::DUMMY_SP,
}],
decorators: vec![],
body: Some(BlockStmt {
stmts: vec![Stmt::Return(ReturnStmt {
arg: Some(Box::new(expr.unwrap_or_else(create_null_expression))),
span: swc_core::common::DUMMY_SP,
})],
span: swc_core::common::DUMMY_SP,
ctxt: SyntaxContext::empty(),
}),
is_generator: false,
is_async: false,
type_params: None,
return_type: None,
span: swc_core::common::DUMMY_SP,
ctxt: SyntaxContext::empty(),
}),
})));
let mdx_content = ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
ident: create_ident("MDXContent").into(),
declare: false,
function: Box::new(Function {
params: vec![Param {
pat: Pat::Assign(AssignPat {
left: Box::new(Pat::Ident(BindingIdent {
id: create_ident("props").into(),
type_ann: None,
})),
right: Box::new(create_object_expression(vec![])),
span: swc_core::common::DUMMY_SP,
}),
decorators: vec![],
span: swc_core::common::DUMMY_SP,
}],
decorators: vec![],
body: Some(BlockStmt {
stmts: vec![Stmt::Return(ReturnStmt {
arg: Some(Box::new(result)),
span: swc_core::common::DUMMY_SP,
})],
span: swc_core::common::DUMMY_SP,
ctxt: SyntaxContext::empty(),
}),
is_generator: false,
is_async: false,
type_params: None,
return_type: None,
span: swc_core::common::DUMMY_SP,
ctxt: SyntaxContext::empty(),
}),
})));
vec![create_mdx_content, mdx_content]
}
fn create_layout_decl(expr: Expr) -> ModuleItem {
ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
kind: VarDeclKind::Const,
declare: false,
decls: vec![VarDeclarator {
name: Pat::Ident(BindingIdent {
id: create_ident("MDXLayout").into(),
type_ann: None,
}),
init: Some(Box::new(expr)),
span: swc_core::common::DUMMY_SP,
definite: false,
}],
span: swc_core::common::DUMMY_SP,
ctxt: SyntaxContext::empty(),
}))))
}
fn err_for_double_layout(
layout: bool,
previous: Option<&Position>,
at: Option<&Point>,
) -> Result<(), markdown::message::Message> {
if layout {
Err(markdown::message::Message {
reason: format!(
"Cannot specify multiple layouts (previous: {})",
position_opt_to_string(previous)
),
place: at.map(|p| Box::new(markdown::message::Place::Point(p.clone()))),
source: Box::new("mdxjs-rs".into()),
rule_id: Box::new("double-layout".into()),
})
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hast_util_to_swc::hast_util_to_swc;
use crate::mdast_util_to_hast::mdast_util_to_hast;
use crate::mdx_plugin_recma_document::{mdx_plugin_recma_document, Options as DocumentOptions};
use crate::swc::{parse_esm, parse_expression, serialize};
use crate::swc_utils::create_bool_expression;
use markdown::{to_mdast, ParseOptions};
use pretty_assertions::assert_eq;
use rustc_hash::FxHashSet;
use swc_core::ecma::ast::{
EmptyStmt, ExportDefaultDecl, ExprStmt, JSXClosingFragment, JSXFragment,
JSXOpeningFragment, JSXText, Module, TsInterfaceBody, TsInterfaceDecl, WhileStmt,
};
fn compile(value: &str) -> Result<String, markdown::message::Message> {
let location = Location::new(value.as_bytes());
let mdast = to_mdast(
value,
&ParseOptions {
mdx_esm_parse: Some(Box::new(parse_esm)),
mdx_expression_parse: Some(Box::new(parse_expression)),
..ParseOptions::mdx()
},
)?;
let hast = mdast_util_to_hast(&mdast);
let mut program =
hast_util_to_swc(&hast, None, Some(&location), &mut FxHashSet::default())?;
mdx_plugin_recma_document(&mut program, &DocumentOptions::default(), Some(&location))?;
Ok(serialize(&mut program.module, Some(&program.comments)))
}
#[test]
fn small() -> Result<(), markdown::message::Message> {
assert_eq!(
compile("# hi\n\nAlpha *bravo* **charlie**.")?,
"function _createMdxContent(props) {
return <><h1>{\"hi\"}</h1>{\"\\n\"}<p>{\"Alpha \"}<em>{\"bravo\"}</em>{\" \"}<strong>{\"charlie\"}</strong>{\".\"}</p></>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should support a small program",
);
Ok(())
}
#[test]
fn import() -> Result<(), markdown::message::Message> {
assert_eq!(
compile("import a from 'b'\n\n# {a}")?,
"import a from 'b';
function _createMdxContent(props) {
return <h1>{a}</h1>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should support an import",
);
Ok(())
}
#[test]
fn export() -> Result<(), markdown::message::Message> {
assert_eq!(
compile("export * from 'a'\n\n# b")?,
"export * from 'a';
function _createMdxContent(props) {
return <h1>{\"b\"}</h1>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should support an export all",
);
assert_eq!(
compile("export function a() {}")?,
"export function a() {}
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should support an export declaration",
);
assert_eq!(
compile("export class A {}")?,
"export class A {
}
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should support an export class",
);
Ok(())
}
#[test]
fn export_default() -> Result<(), markdown::message::Message> {
assert_eq!(
compile("export default a")?,
"const MDXLayout = a;
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout>;
}
export default MDXContent;
",
"should support an export default expression",
);
assert_eq!(
compile("export default function () {}")?,
"const MDXLayout = function() {};
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout>;
}
export default MDXContent;
",
"should support an export default declaration",
);
assert_eq!(
compile("export default class A {}")?,
"const MDXLayout = class A {
};
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout>;
}
export default MDXContent;
",
"should support an export default class",
);
Ok(())
}
#[test]
fn named_exports() -> Result<(), markdown::message::Message> {
assert_eq!(
compile("export {a, b as default}")?,
"export { a };
const MDXLayout = b;
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout>;
}
export default MDXContent;
",
"should support a named export w/o source, w/ a default specifier",
);
assert_eq!(
compile("export {a}")?,
"export { a };
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should support a named export w/o source, w/o a default specifier",
);
assert_eq!(
compile("export {}")?,
"export { };
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should support a named export w/o source, w/o a specifiers",
);
assert_eq!(
compile("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 <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout>;
}
export default MDXContent;
",
"should support a named export w/ source, w/ a default specifier",
);
assert_eq!(
compile("export {a} from 'b'")?,
"export { a } from 'b';
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should support a named export w/ source, w/o a default specifier",
);
assert_eq!(
compile("export {} from 'a'")?,
"export { } from 'a';
function _createMdxContent(props) {
return <></>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should support a named export w/ source, w/o a specifiers",
);
Ok(())
}
#[test]
fn multiple_layouts() {
assert_eq!(
compile("export default a = 1\n\nexport default b = 2")
.err()
.unwrap()
.to_string(),
"3:1: Cannot specify multiple layouts (previous: 1:1-1:21) (mdxjs-rs:double-layout)",
"should crash on multiple layouts"
);
}
#[test]
fn ts_default_interface_declaration() {
assert_eq!(
mdx_plugin_recma_document(
&mut Program {
path: None,
comments: vec![],
module: Module {
span: swc_core::common::DUMMY_SP,
shebang: None,
body: vec![ModuleItem::ModuleDecl(
ModuleDecl::ExportDefaultDecl(
ExportDefaultDecl {
span: swc_core::common::DUMMY_SP,
decl: DefaultDecl::TsInterfaceDecl(Box::new(
TsInterfaceDecl {
span: swc_core::common::DUMMY_SP,
id: create_ident("a").into(),
declare: true,
type_params: None,
extends: vec![],
body: TsInterfaceBody {
span: swc_core::common::DUMMY_SP,
body: vec![]
}
}
))
}
)
)]
}
},
&Options::default(),
None
)
.err()
.unwrap().to_string(),
"Cannot use TypeScript interface declarations as default export in MDX files. The default export is reserved for a layout, which must be a component (mdxjs-rs:ts-interface)",
"should crash on a TypeScript default interface declaration"
);
}
#[test]
fn statement_pass_through() -> Result<(), markdown::message::Message> {
let mut program = Program {
path: None,
comments: vec![],
module: Module {
span: swc_core::common::DUMMY_SP,
shebang: None,
body: vec![ModuleItem::Stmt(Stmt::While(WhileStmt {
span: swc_core::common::DUMMY_SP,
test: Box::new(create_bool_expression(true)),
body: Box::new(Stmt::Empty(EmptyStmt {
span: swc_core::common::DUMMY_SP,
})),
}))],
},
};
mdx_plugin_recma_document(&mut program, &Options::default(), None)?;
assert_eq!(
serialize(&mut program.module, None),
"while(true);
function _createMdxContent(props) {
return null;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should pass statements through"
);
Ok(())
}
#[test]
fn expression_pass_through() -> Result<(), markdown::message::Message> {
let mut program = Program {
path: None,
comments: vec![],
module: Module {
span: swc_core::common::DUMMY_SP,
shebang: None,
body: vec![ModuleItem::Stmt(Stmt::Expr(ExprStmt {
span: swc_core::common::DUMMY_SP,
expr: Box::new(create_bool_expression(true)),
}))],
},
};
mdx_plugin_recma_document(&mut program, &Options::default(), None)?;
assert_eq!(
serialize(&mut program.module, None),
"true;
function _createMdxContent(props) {
return null;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should pass expressions through"
);
Ok(())
}
#[test]
fn fragment_non_element_single_child() -> Result<(), markdown::message::Message> {
let mut program = Program {
path: None,
comments: vec![],
module: Module {
span: swc_core::common::DUMMY_SP,
shebang: None,
body: vec![ModuleItem::Stmt(Stmt::Expr(ExprStmt {
span: swc_core::common::DUMMY_SP,
expr: Box::new(Expr::JSXFragment(JSXFragment {
span: swc_core::common::DUMMY_SP,
opening: JSXOpeningFragment {
span: swc_core::common::DUMMY_SP,
},
closing: JSXClosingFragment {
span: swc_core::common::DUMMY_SP,
},
children: vec![JSXElementChild::JSXText(JSXText {
value: "a".into(),
span: swc_core::common::DUMMY_SP,
raw: "a".into(),
})],
})),
}))],
},
};
mdx_plugin_recma_document(&mut program, &Options::default(), None)?;
assert_eq!(
serialize(&mut program.module, None),
"function _createMdxContent(props) {
return <>a</>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should pass a fragment with a single child that isn’t an element through"
);
Ok(())
}
#[test]
fn element() -> Result<(), markdown::message::Message> {
let mut program = Program {
path: None,
comments: vec![],
module: Module {
span: swc_core::common::DUMMY_SP,
shebang: None,
body: vec![ModuleItem::Stmt(Stmt::Expr(ExprStmt {
span: swc_core::common::DUMMY_SP,
expr: Box::new(Expr::JSXElement(Box::new(JSXElement {
span: swc_core::common::DUMMY_SP,
opening: JSXOpeningElement {
name: JSXElementName::Ident(create_ident("a").into()),
attrs: vec![],
self_closing: false,
type_args: None,
span: swc_core::common::DUMMY_SP,
},
closing: Some(JSXClosingElement {
name: JSXElementName::Ident(create_ident("a").into()),
span: swc_core::common::DUMMY_SP,
}),
children: vec![JSXElementChild::JSXText(JSXText {
value: "b".into(),
span: swc_core::common::DUMMY_SP,
raw: "b".into(),
})],
}))),
}))],
},
};
mdx_plugin_recma_document(&mut program, &Options::default(), None)?;
assert_eq!(
serialize(&mut program.module, None),
"function _createMdxContent(props) {
return <a>b</a>;
}
function MDXContent(props = {}) {
return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
}
export default MDXContent;
",
"should pass an element through"
);
Ok(())
}
}