diff options
Diffstat (limited to 'tests/test_utils/mdx_plugin_recma_document.rs')
-rw-r--r-- | tests/test_utils/mdx_plugin_recma_document.rs | 662 |
1 files changed, 0 insertions, 662 deletions
diff --git a/tests/test_utils/mdx_plugin_recma_document.rs b/tests/test_utils/mdx_plugin_recma_document.rs deleted file mode 100644 index 48648b9..0000000 --- a/tests/test_utils/mdx_plugin_recma_document.rs +++ /dev/null @@ -1,662 +0,0 @@ -//! Turn a JavaScript AST, coming from MD(X), into a component. -//! -//! Port of <https://github.com/mdx-js/mdx/blob/main/packages/mdx/lib/plugin/recma-document.js>, -//! by the same author. - -use crate::test_utils::{ - hast_util_to_swc::Program, - swc_utils::{bytepos_to_point, prefix_error_with_point, span_to_position}, -}; -use markdown::{ - unist::{Point, Position}, - Location, -}; - -/// JSX runtimes (default: `JsxRuntime: Automatic`). -#[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<String>, - /// Pragma for JSX fragments (used in classic runtime). - /// - /// Default: `React.Fragment`. - pub pragma_frag: Option<String>, - /// Where to import the identifier of `pragma` from (used in classic runtime). - /// - /// Default: `react`. - pub pragma_import_source: Option<String>, - /// Place to import automatic JSX runtimes from (used in automatic runtime). - /// - /// Default: `react`. - pub jsx_import_source: Option<String>, - /// JSX runtime to use. - /// - /// Default: `automatic`. - pub jsx_runtime: Option<JsxRuntime>, -} - -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 mdx_plugin_recma_document( - program: &mut Program, - options: &Options, - location: Option<&Location>, -) -> Result<(), String> { - // 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(); - let mut layout = false; - let mut layout_position = None; - 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, - )) => { - if layout { - return Err(create_double_layout_message( - bytepos_to_point(&decl.span.lo, location).as_ref(), - layout_position.as_ref(), - )); - } - - layout = true; - layout_position = span_to_position(&decl.span, location); - match decl.decl { - swc_ecma_ast::DefaultDecl::Class(cls) => { - replacements.push(create_layout_decl(swc_ecma_ast::Expr::Class(cls))) - } - swc_ecma_ast::DefaultDecl::Fn(func) => { - replacements.push(create_layout_decl(swc_ecma_ast::Expr::Fn(func))) - } - swc_ecma_ast::DefaultDecl::TsInterfaceDecl(_) => { - return Err( - prefix_error_with_point( - "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(), - bytepos_to_point(&decl.span.lo, location).as_ref() - ) - ); - } - } - } - swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportDefaultExpr( - expr, - )) => { - if layout { - return Err(create_double_layout_message( - bytepos_to_point(&expr.span.lo, location).as_ref(), - layout_position.as_ref(), - )); - } - - layout = true; - layout_position = span_to_position(&expr.span, location); - 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 { - if layout { - return Err(create_double_layout_message( - bytepos_to_point(&ident.span.lo, location).as_ref(), - layout_position.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 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(mut x)) => { - // SWC is currently crashing when generating code, w/o source - // map, if an actual location is set on this node. - x.span = swc_common::DUMMY_SP; - // Pass through. - replacements.push(swc_ecma_ast::ModuleItem::ModuleDecl( - swc_ecma_ast::ModuleDecl::Import(x), - )); - } - swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportDecl(_)) - | swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportAll(_)) - | 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(()) -} - -/// Create a content component. -fn create_mdx_content( - expr: Option<swc_ecma_ast::Expr>, - has_internal_layout: bool, -) -> Vec<swc_ecma_ast::ModuleItem> { - // ```jsx - // <MDXLayout {...props}>xxx</MDXLayout> - // ``` - 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 ? <MDXLayout>xxx</MDXLayout> : _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 <MDXLayout>xxx</MDXLayout> - // } - // ``` - 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, - }, - )))) -} - -/// Create an error message about multiple layouts. -fn create_double_layout_message(at: Option<&Point>, previous: Option<&Position>) -> String { - prefix_error_with_point( - format!( - "Cannot specify multiple layouts{}", - if let Some(previous) = previous { - format!(" (previous: {:?})", previous) - } else { - "".into() - } - ), - at, - ) -} |