diff options
Diffstat (limited to 'tests/test_utils/mdx_plugin_recma_jsx_rewrite.rs')
-rw-r--r-- | tests/test_utils/mdx_plugin_recma_jsx_rewrite.rs | 1165 |
1 files changed, 0 insertions, 1165 deletions
diff --git a/tests/test_utils/mdx_plugin_recma_jsx_rewrite.rs b/tests/test_utils/mdx_plugin_recma_jsx_rewrite.rs deleted file mode 100644 index 6b058fd..0000000 --- a/tests/test_utils/mdx_plugin_recma_jsx_rewrite.rs +++ /dev/null @@ -1,1165 +0,0 @@ -//! Rewrite JSX tags to accept them from props and an optional provider. -//! -//! Port of <https://github.com/mdx-js/mdx/blob/main/packages/mdx/lib/plugin/recma-jsx-rewrite.js>, -//! by the same author. - -use crate::test_utils::{ - hast_util_to_swc::Program, - swc_utils::{ - create_binary_expression, create_ident, create_ident_expression, create_member_expression, - position_to_string, span_to_position, - }, -}; -use markdown::{id_cont, id_start, unist::Position, Location}; -use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; - -/// Configuration. -#[derive(Debug, Default, Clone)] -pub struct Options { - /// Place to import a provider from. - /// - /// See [MDX provider](https://mdxjs.com/docs/using-mdx/#mdx-provider) - /// on the MDX website for more info. - pub provider_import_source: Option<String>, - /// Whether to add extra information to error messages in generated code. - /// This is not yet supported. - pub development: bool, -} - -/// Rewrite JSX in an MDX file so that components can be passed in and provided. -#[allow(dead_code)] -pub fn mdx_plugin_recma_jsx_rewrite( - program: &mut Program, - options: &Options, - location: Option<&Location>, -) { - let mut state = State { - scopes: vec![], - location, - provider: options.provider_import_source.is_some(), - path: program.path.clone(), - development: options.development, - create_provider_import: false, - create_error_helper: false, - }; - state.enter(Some(Info::default())); - program.module.visit_mut_with(&mut state); - - // If a provider is used (and can be used), import it. - if let Some(source) = &options.provider_import_source { - if state.create_provider_import { - program - .module - .body - .insert(0, create_import_provider(source)) - } - } - - // If potentially missing components are used, add the helper used for - // errors. - if state.create_error_helper { - program - .module - .body - .push(create_error_helper(state.development, state.path)); - } -} - -/// Collection of different SWC functions. -#[derive(Debug)] -enum Func<'a> { - /// Function declaration. - Decl(&'a mut swc_ecma_ast::FnDecl), - /// Function expression. - Expr(&'a mut swc_ecma_ast::FnExpr), - /// Arrow function. - Arrow(&'a mut swc_ecma_ast::ArrowExpr), -} - -/// Info for a function scope. -#[derive(Debug, Default, Clone)] -struct Info { - /// Function name. - name: Option<String>, - /// Used objects (`a` in `<a.b />`). - objects: Vec<String>, - /// Used components (`<A />`). - components: Vec<String>, - /// Used literals (`<a />`). - tags: Vec<String>, - /// List of JSX identifiers of literal tags that are not valid JS - /// identifiers in the shape of `Vec<(invalid, valid)>`. - /// - /// Example: - /// - /// ``` - /// vec![("a-b".into(), "_component0".into())] - /// ``` - aliases: Vec<(String, String)>, - /// Non-literal references in the shape of `Vec<(name, is_component)>`. - /// - /// Example: - /// - /// ``` - /// vec![("a".into(), false), ("a.b".into(), true)] - /// ``` - references: Vec<(String, bool, Option<Position>)>, -} - -/// Scope (block or function/global). -#[derive(Debug, Clone)] -struct Scope { - /// If this is a function (or global) scope, we track info. - info: Option<Info>, - /// Things that are defined in this scope. - defined: Vec<String>, -} - -/// Context. -#[derive(Debug, Default, Clone)] -struct State<'a> { - location: Option<&'a Location>, - /// Path to file. - path: Option<String>, - /// List of current scopes. - scopes: Vec<Scope>, - /// Whether the user is in development mode. - development: bool, - /// Whether the user uses a provider. - provider: bool, - /// Whether a provider is referenced. - create_provider_import: bool, - /// Whether a missing component helper is referenced. - /// - /// When things are referenced that might not be defined, we reference a - /// helper function to throw when they are missing. - create_error_helper: bool, -} - -impl<'a> State<'a> { - /// Open a new scope. - fn enter(&mut self, info: Option<Info>) { - self.scopes.push(Scope { - info, - defined: vec![], - }); - } - - /// Close the current scope. - fn exit(&mut self) -> Scope { - self.scopes.pop().expect("expected scope") - } - - /// Close a function. - fn exit_func(&mut self, func: Func) { - let mut scope = self.exit(); - let mut defaults = vec![]; - let mut info = scope.info.take().unwrap(); - let mut index = 0; - - // Create defaults for tags. - // - // ```jsx - // {h1: 'h1'} - // ``` - while index < info.tags.len() { - let name = &info.tags[index]; - - defaults.push(swc_ecma_ast::PropOrSpread::Prop(Box::new( - swc_ecma_ast::Prop::KeyValue(swc_ecma_ast::KeyValueProp { - key: if is_identifier_name(name) { - swc_ecma_ast::PropName::Ident(create_ident(name)) - } else { - swc_ecma_ast::PropName::Str(swc_ecma_ast::Str { - value: name.clone().into(), - span: swc_common::DUMMY_SP, - raw: None, - }) - }, - value: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str( - swc_ecma_ast::Str { - value: name.clone().into(), - span: swc_common::DUMMY_SP, - raw: None, - }, - ))), - }), - ))); - - index += 1; - } - - let mut actual = info.components.split_off(0); - let mut index = 0; - - // In some cases, a component is used directly (`<X>`) but it’s also - // used as an object (`<X.Y>`). - while index < info.objects.len() { - if !actual.contains(&info.objects[index]) { - actual.push(info.objects[index].clone()); - } - index += 1; - } - - let mut statements = vec![]; - - if !defaults.is_empty() || !actual.is_empty() || !info.aliases.is_empty() { - let mut parameters = vec![]; - - // Use a provider, if configured. - // - // ```jsx - // _provideComponents() - // ``` - if self.provider { - self.create_provider_import = true; - parameters.push(swc_ecma_ast::Expr::Call(swc_ecma_ast::CallExpr { - callee: swc_ecma_ast::Callee::Expr(Box::new(create_ident_expression( - "_provideComponents", - ))), - args: vec![], - type_args: None, - span: swc_common::DUMMY_SP, - })); - } - - // Accept `components` as a prop if this is the `MDXContent` or - // `_createMdxContent` function. - // - // ```jsx - // props.components - // ``` - if is_props_receiving_fn(&info.name) { - parameters.push(swc_ecma_ast::Expr::Member(swc_ecma_ast::MemberExpr { - obj: Box::new(create_ident_expression("props")), - prop: swc_ecma_ast::MemberProp::Ident(create_ident("components")), - span: swc_common::DUMMY_SP, - })); - } - - // Inject an object at the start, when: - // - there are defaults, - // - there are two sources - // - // ```jsx - // (_provideComponents(), props.components) - // () - // ``` - // - // To: - // - // ```jsx - // ({}, _provideComponents(), props.components) - // ({h1: 'h1'}) - // ``` - if !defaults.is_empty() || parameters.len() > 1 { - parameters.insert( - 0, - swc_ecma_ast::Expr::Object(swc_ecma_ast::ObjectLit { - props: defaults, - span: swc_common::DUMMY_SP, - }), - ); - } - - // Merge things and prevent errors. - // - // ```jsx - // {}, _provideComponents(), props.components - // props.components - // _provideComponents() - // ``` - // - // To: - // - // ```jsx - // Object.assign({}, _provideComponents(), props.components) - // props.components || {} - // _provideComponents() - // ``` - let mut components_init = if parameters.len() > 1 { - let mut args = vec![]; - parameters.reverse(); - while let Some(param) = parameters.pop() { - args.push(swc_ecma_ast::ExprOrSpread { - spread: None, - expr: Box::new(param), - }); - } - swc_ecma_ast::Expr::Call(swc_ecma_ast::CallExpr { - callee: swc_ecma_ast::Callee::Expr(Box::new(swc_ecma_ast::Expr::Member( - swc_ecma_ast::MemberExpr { - obj: Box::new(create_ident_expression("Object")), - prop: swc_ecma_ast::MemberProp::Ident(create_ident("assign")), - span: swc_common::DUMMY_SP, - }, - ))), - args, - type_args: None, - span: swc_common::DUMMY_SP, - }) - } else { - // Always one. - let param = parameters.pop().unwrap(); - - if let swc_ecma_ast::Expr::Member(_) = param { - create_binary_expression( - vec![ - param, - swc_ecma_ast::Expr::Object(swc_ecma_ast::ObjectLit { - props: vec![], - span: swc_common::DUMMY_SP, - }), - ], - swc_ecma_ast::BinaryOp::LogicalOr, - ) - } else { - param - } - }; - - // Add components to scope. - // - // For `['MyComponent', 'MDXLayout']` this generates: - // - // ```js - // const {MyComponent, wrapper: MDXLayout} = _components - // ``` - // - // Note that MDXLayout is special as it’s taken from - // `_components.wrapper`. - let components_pattern = if actual.is_empty() { - None - } else { - let mut props = vec![]; - actual.reverse(); - while let Some(key) = actual.pop() { - // `wrapper: MDXLayout` - if key == "MDXLayout" { - props.push(swc_ecma_ast::ObjectPatProp::KeyValue( - swc_ecma_ast::KeyValuePatProp { - key: swc_ecma_ast::PropName::Ident(create_ident("wrapper")), - value: Box::new(swc_ecma_ast::Pat::Ident( - swc_ecma_ast::BindingIdent { - id: create_ident(&key), - type_ann: None, - }, - )), - }, - )) - } - // `MyComponent` - else { - props.push(swc_ecma_ast::ObjectPatProp::Assign( - swc_ecma_ast::AssignPatProp { - key: create_ident(&key), - value: None, - span: swc_common::DUMMY_SP, - }, - )) - } - } - - Some(swc_ecma_ast::ObjectPat { - props, - optional: false, - span: swc_common::DUMMY_SP, - type_ann: None, - }) - }; - - let mut declarators = vec![]; - - // If there are tags, they take them from `_components`, so we need - // to make it defined. - if !info.tags.is_empty() { - declarators.push(swc_ecma_ast::VarDeclarator { - span: swc_common::DUMMY_SP, - name: swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent { - id: create_ident("_components"), - type_ann: None, - }), - init: Some(Box::new(components_init)), - definite: false, - }); - components_init = create_ident_expression("_components"); - } - - // For JSX IDs that can’t be represented as JavaScript IDs (as in, - // those with dashes, such as `custom-element`), we generated a - // separate variable that is a valid JS ID (such as `_component0`), - // and here we take it from components: - // ```js - // const _component0 = _components['custom-element'] - // ``` - if !info.aliases.is_empty() { - info.aliases.reverse(); - - while let Some((id, name)) = info.aliases.pop() { - declarators.push(swc_ecma_ast::VarDeclarator { - span: swc_common::DUMMY_SP, - name: swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent { - id: create_ident(&name), - type_ann: None, - }), - init: Some(Box::new(swc_ecma_ast::Expr::Member( - swc_ecma_ast::MemberExpr { - obj: Box::new(create_ident_expression("_components")), - prop: swc_ecma_ast::MemberProp::Computed( - swc_ecma_ast::ComputedPropName { - expr: Box::new(swc_ecma_ast::Expr::Lit( - swc_ecma_ast::Lit::Str(swc_ecma_ast::Str { - value: id.into(), - span: swc_common::DUMMY_SP, - raw: None, - }), - )), - span: swc_common::DUMMY_SP, - }, - ), - span: swc_common::DUMMY_SP, - }, - ))), - definite: false, - }); - } - } - - if let Some(pat) = components_pattern { - declarators.push(swc_ecma_ast::VarDeclarator { - name: swc_ecma_ast::Pat::Object(pat), - init: Some(Box::new(components_init)), - span: swc_common::DUMMY_SP, - definite: false, - }); - } - - // Add the variable declaration. - statements.push(swc_ecma_ast::Stmt::Decl(swc_ecma_ast::Decl::Var(Box::new( - swc_ecma_ast::VarDecl { - kind: swc_ecma_ast::VarDeclKind::Const, - decls: declarators, - span: swc_common::DUMMY_SP, - declare: false, - }, - )))); - } - - // Add checks at runtime to verify that object/components are passed. - // - // ```js - // if (!a) _missingMdxReference("a", false); - // if (!a.b) _missingMdxReference("a.b", true); - // ``` - for (id, component, position) in info.references { - self.create_error_helper = true; - - let mut args = vec![ - swc_ecma_ast::ExprOrSpread { - spread: None, - expr: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str( - swc_ecma_ast::Str { - value: id.clone().into(), - span: swc_common::DUMMY_SP, - raw: None, - }, - ))), - }, - swc_ecma_ast::ExprOrSpread { - spread: None, - expr: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Bool( - swc_ecma_ast::Bool { - value: component, - span: swc_common::DUMMY_SP, - }, - ))), - }, - ]; - - // Add the source location if it exists and if `development` is on. - if let Some(position) = position.as_ref() { - if self.development { - args.push(swc_ecma_ast::ExprOrSpread { - spread: None, - expr: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str( - swc_ecma_ast::Str { - value: position_to_string(position).into(), - span: swc_common::DUMMY_SP, - raw: None, - }, - ))), - }) - } - } - - statements.push(swc_ecma_ast::Stmt::If(swc_ecma_ast::IfStmt { - test: Box::new(swc_ecma_ast::Expr::Unary(swc_ecma_ast::UnaryExpr { - op: swc_ecma_ast::UnaryOp::Bang, - arg: Box::new(create_member_expression(&id)), - span: swc_common::DUMMY_SP, - })), - cons: Box::new(swc_ecma_ast::Stmt::Expr(swc_ecma_ast::ExprStmt { - span: swc_common::DUMMY_SP, - expr: Box::new(swc_ecma_ast::Expr::Call(swc_ecma_ast::CallExpr { - callee: swc_ecma_ast::Callee::Expr(Box::new(create_ident_expression( - "_missingMdxReference", - ))), - args, - type_args: None, - span: swc_common::DUMMY_SP, - })), - })), - alt: None, - span: swc_common::DUMMY_SP, - })); - } - - // Add statements to functions. - if !statements.is_empty() { - let mut body: &mut swc_ecma_ast::BlockStmt = match func { - Func::Expr(expr) => { - if expr.function.body.is_none() { - expr.function.body = Some(swc_ecma_ast::BlockStmt { - stmts: vec![], - span: swc_common::DUMMY_SP, - }); - } - expr.function.body.as_mut().unwrap() - } - Func::Decl(decl) => { - if decl.function.body.is_none() { - decl.function.body = Some(swc_ecma_ast::BlockStmt { - stmts: vec![], - span: swc_common::DUMMY_SP, - }); - } - decl.function.body.as_mut().unwrap() - } - Func::Arrow(arr) => { - if let swc_ecma_ast::BlockStmtOrExpr::Expr(expr) = &mut arr.body { - arr.body = - swc_ecma_ast::BlockStmtOrExpr::BlockStmt(swc_ecma_ast::BlockStmt { - stmts: vec![swc_ecma_ast::Stmt::Return(swc_ecma_ast::ReturnStmt { - // To do: figure out non-clone. - arg: Some(expr.clone()), - span: swc_common::DUMMY_SP, - })], - span: swc_common::DUMMY_SP, - }); - } - arr.body.as_mut_block_stmt().unwrap() - } - }; - - statements.append(&mut body.stmts.split_off(0)); - body.stmts = statements; - } - } - - /// Get the current function scope. - fn current_fn_scope_mut(&mut self) -> &mut Scope { - let mut index = self.scopes.len(); - - while index > 0 { - index -= 1; - if self.scopes[index].info.is_some() { - return &mut self.scopes[index]; - } - } - - unreachable!("expected scope") - } - - /// Get the current scope. - fn current_scope_mut(&mut self) -> &mut Scope { - self.scopes.last_mut().expect("expected scope") - } - - /// Get the top-level scope’s info. - fn current_top_level_info(&self) -> Option<&Info> { - if let Some(scope) = self.scopes.get(1) { - scope.info.as_ref() - } else { - None - } - } - - /// Get the top-level scope’s info, mutably. - fn current_top_level_info_mut(&mut self) -> Option<&mut Info> { - if let Some(scope) = self.scopes.get_mut(1) { - scope.info.as_mut() - } else { - None - } - } - - /// Check if `id` is in scope. - fn in_scope(&self, id: &String) -> bool { - let mut index = self.scopes.len(); - - while index > 0 { - index -= 1; - if self.scopes[index].defined.contains(id) { - return true; - } - } - - false - } - - /// Add an identifier to a scope. - fn add_id(&mut self, id: String, block: bool) { - let scope = if block { - self.current_scope_mut() - } else { - self.current_fn_scope_mut() - }; - scope.defined.push(id); - } - - // Add a pattern to a scope. - fn add_pat(&mut self, pat: &swc_ecma_ast::Pat, block: bool) { - match pat { - // `x` - swc_ecma_ast::Pat::Ident(d) => self.add_id(d.id.sym.to_string(), block), - // `...x` - swc_ecma_ast::Pat::Array(d) => { - let mut index = 0; - while index < d.elems.len() { - if let Some(d) = &d.elems[index] { - self.add_pat(d, block); - } - index += 1; - } - } - // `...x` - swc_ecma_ast::Pat::Rest(d) => self.add_pat(&d.arg, block), - // `{x=y}` - swc_ecma_ast::Pat::Assign(d) => self.add_pat(&d.left, block), - swc_ecma_ast::Pat::Object(d) => { - let mut index = 0; - while index < d.props.len() { - match &d.props[index] { - // `{...x}` - swc_ecma_ast::ObjectPatProp::Rest(d) => { - self.add_pat(&d.arg, block); - } - // `{key: value}` - swc_ecma_ast::ObjectPatProp::KeyValue(d) => { - self.add_pat(&d.value, block); - } - // `{key}` or `{key = value}` - swc_ecma_ast::ObjectPatProp::Assign(d) => { - self.add_id(d.key.to_string(), block); - } - } - index += 1; - } - } - // Ignore `Invalid` / `Expr`. - _ => {} - } - } -} - -impl<'a> VisitMut for State<'a> { - noop_visit_mut_type!(); - - /// Rewrite JSX identifiers. - fn visit_mut_jsx_element(&mut self, node: &mut swc_ecma_ast::JSXElement) { - // If there is a top-level, non-global, scope which is a function. - if let Some(info) = self.current_top_level_info() { - // Rewrite only if we can rewrite. - if is_props_receiving_fn(&info.name) || self.provider { - let position = span_to_position(&node.span, self.location); - match &node.opening.name { - // `<x.y>`, `<Foo.Bar>`, `<x.y.z>`. - swc_ecma_ast::JSXElementName::JSXMemberExpr(d) => { - let mut ids = vec![]; - let mut mem = d; - loop { - ids.push(mem.prop.sym.to_string()); - match &mem.obj { - swc_ecma_ast::JSXObject::Ident(d) => { - ids.push(d.sym.to_string()); - break; - } - swc_ecma_ast::JSXObject::JSXMemberExpr(d) => { - mem = d; - } - } - } - ids.reverse(); - let primary_id = ids.first().unwrap().clone(); - let in_scope = self.in_scope(&primary_id); - - if !in_scope { - let info_mut = self.current_top_level_info_mut().unwrap(); - - let mut index = 1; - while index <= ids.len() { - let full_id = ids[0..index].join("."); - let component = index == ids.len(); - if let Some(reference) = - info_mut.references.iter_mut().find(|d| d.0 == full_id) - { - if component { - reference.1 = true; - } - } else { - info_mut - .references - .push((full_id, component, position.clone())) - } - index += 1; - } - - if !info_mut.objects.contains(&primary_id) { - info_mut.objects.push(primary_id); - } - } - } - // `<foo>`, `<Foo>`, `<$>`, `<_bar>`, `<a_b>`. - swc_ecma_ast::JSXElementName::Ident(d) => { - // If the name is a valid ES identifier, and it doesn’t - // start with a lowercase letter, it’s a component. - // For example, `$foo`, `_bar`, `Baz` are all component - // names. - // But `foo` and `b-ar` are tag names. - let id = d.sym.to_string(); - - if is_literal_name(&id) { - // To do: ignore explicit JSX? - - let mut invalid = None; - - let name = if is_identifier_name(&id) { - swc_ecma_ast::JSXElementName::JSXMemberExpr( - swc_ecma_ast::JSXMemberExpr { - obj: swc_ecma_ast::JSXObject::Ident(create_ident( - "_components", - )), - prop: create_ident(&id), - }, - ) - } else { - let name = if let Some(invalid_ref) = - info.aliases.iter().find(|d| d.0 == id) - { - invalid_ref.1.clone() - } else { - let name = format!("_component{}", info.aliases.len()); - invalid = Some((id.clone(), name.clone())); - name - }; - - swc_ecma_ast::JSXElementName::Ident(create_ident(&name)) - }; - - let info_mut = self.current_top_level_info_mut().unwrap(); - - if !info_mut.tags.contains(&id) { - info_mut.tags.push(id); - } - - if let Some(invalid) = invalid { - info_mut.aliases.push(invalid) - } - - if let Some(closing) = node.closing.as_mut() { - closing.name = name.clone(); - } - - node.opening.name = name; - } else { - let mut is_layout = false; - - // The MDXLayout is wrapped in a - if let Some(name) = &info.name { - if name == "MDXContent" && id == "MDXLayout" { - is_layout = true; - } - } - - if !self.in_scope(&id) { - let info_mut = self.current_top_level_info_mut().unwrap(); - - if !is_layout { - if let Some(reference) = - info_mut.references.iter_mut().find(|d| d.0 == id) - { - reference.1 = true; - } else { - info_mut.references.push((id.clone(), true, position)) - } - } - - if !info_mut.components.contains(&id) { - info_mut.components.push(id); - } - } - } - } - // `<xml:thing>`. - swc_ecma_ast::JSXElementName::JSXNamespacedName(_) => { - // Ignore. - } - } - } - } - - node.visit_mut_children_with(self); - } - - /// Add specifiers of import declarations. - fn visit_mut_import_decl(&mut self, node: &mut swc_ecma_ast::ImportDecl) { - let mut index = 0; - while index < node.specifiers.len() { - let ident = match &node.specifiers[index] { - swc_ecma_ast::ImportSpecifier::Default(x) => &x.local.sym, - swc_ecma_ast::ImportSpecifier::Namespace(x) => &x.local.sym, - swc_ecma_ast::ImportSpecifier::Named(x) => &x.local.sym, - }; - self.add_id(ident.to_string(), false); - index += 1; - } - - node.visit_mut_children_with(self); - } - - /// Add patterns of variable declarations. - fn visit_mut_var_decl(&mut self, node: &mut swc_ecma_ast::VarDecl) { - let block = node.kind != swc_ecma_ast::VarDeclKind::Var; - let mut index = 0; - while index < node.decls.len() { - self.add_pat(&node.decls[index].name, block); - index += 1; - } - node.visit_mut_children_with(self); - } - - /// Add identifier of class declaration. - fn visit_mut_class_decl(&mut self, node: &mut swc_ecma_ast::ClassDecl) { - self.add_id(node.ident.sym.to_string(), false); - node.visit_mut_children_with(self); - } - - /// On function declarations, add name, create scope, add parameters. - fn visit_mut_fn_decl(&mut self, node: &mut swc_ecma_ast::FnDecl) { - let id = node.ident.sym.to_string(); - self.add_id(id.clone(), false); - self.enter(Some(Info { - name: Some(id), - ..Default::default() - })); - let mut index = 0; - while index < node.function.params.len() { - self.add_pat(&node.function.params[index].pat, false); - index += 1; - } - node.visit_mut_children_with(self); - // Rewrite. - self.exit_func(Func::Decl(node)); - } - - /// On function expressions, add name, create scope, add parameters. - fn visit_mut_fn_expr(&mut self, node: &mut swc_ecma_ast::FnExpr) { - // Note: `periscopic` adds the ID to the newly generated scope, for - // fn expressions. - // That seems wrong? - let name = if let Some(ident) = &node.ident { - let id = ident.sym.to_string(); - self.add_id(id.clone(), false); - Some(id) - } else { - None - }; - - self.enter(Some(Info { - name, - ..Default::default() - })); - let mut index = 0; - while index < node.function.params.len() { - self.add_pat(&node.function.params[index].pat, false); - index += 1; - } - node.visit_mut_children_with(self); - self.exit_func(Func::Expr(node)); - } - - /// On arrow functions, create scope, add parameters. - fn visit_mut_arrow_expr(&mut self, node: &mut swc_ecma_ast::ArrowExpr) { - self.enter(Some(Info::default())); - let mut index = 0; - while index < node.params.len() { - self.add_pat(&node.params[index], false); - index += 1; - } - node.visit_mut_children_with(self); - self.exit_func(Func::Arrow(node)); - } - - // Blocks. - // Not sure why `periscopic` only does `For`/`ForIn`/`ForOf`/`Block`. - // I added `While`/`DoWhile` here just to be sure. - // But there are more. - /// On for statements, create scope. - fn visit_mut_for_stmt(&mut self, node: &mut swc_ecma_ast::ForStmt) { - self.enter(None); - node.visit_mut_children_with(self); - self.exit(); - } - /// On for/in statements, create scope. - fn visit_mut_for_in_stmt(&mut self, node: &mut swc_ecma_ast::ForInStmt) { - self.enter(None); - node.visit_mut_children_with(self); - self.exit(); - } - /// On for/of statements, create scope. - fn visit_mut_for_of_stmt(&mut self, node: &mut swc_ecma_ast::ForOfStmt) { - self.enter(None); - node.visit_mut_children_with(self); - self.exit(); - } - /// On while statements, create scope. - fn visit_mut_while_stmt(&mut self, node: &mut swc_ecma_ast::WhileStmt) { - self.enter(None); - node.visit_mut_children_with(self); - self.exit(); - } - /// On do/while statements, create scope. - fn visit_mut_do_while_stmt(&mut self, node: &mut swc_ecma_ast::DoWhileStmt) { - self.enter(None); - node.visit_mut_children_with(self); - self.exit(); - } - /// On block statements, create scope. - fn visit_mut_block_stmt(&mut self, node: &mut swc_ecma_ast::BlockStmt) { - self.enter(None); - node.visit_mut_children_with(self); - self.exit(); - } - - /// On catch clauses, create scope, add param. - fn visit_mut_catch_clause(&mut self, node: &mut swc_ecma_ast::CatchClause) { - self.enter(None); - if let Some(pat) = &node.param { - self.add_pat(pat, true); - } - node.visit_mut_children_with(self); - self.exit(); - } -} - -/// Generate an import provider. -/// -/// ```js -/// import { useMDXComponents as _provideComponents } from "x" -/// ``` -fn create_import_provider(source: &str) -> swc_ecma_ast::ModuleItem { - 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: create_ident("_provideComponents"), - imported: Some(swc_ecma_ast::ModuleExportName::Ident(create_ident( - "useMDXComponents", - ))), - span: swc_common::DUMMY_SP, - is_type_only: false, - }, - )], - src: Box::new(swc_ecma_ast::Str { - value: source.into(), - span: swc_common::DUMMY_SP, - raw: None, - }), - type_only: false, - asserts: None, - span: swc_common::DUMMY_SP, - }, - )) -} - -/// Generate an error helper. -/// -/// ```js -/// function _missingMdxReference(id, component) { -/// throw new Error("Expected " + (component ? "component" : "object") + " `" + id + "` to be defined: you likely forgot to import, pass, or provide it."); -/// } -/// ``` -fn create_error_helper(development: bool, path: Option<String>) -> swc_ecma_ast::ModuleItem { - let mut parameters = vec![ - swc_ecma_ast::Param { - pat: swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent { - id: create_ident("id"), - type_ann: None, - }), - decorators: vec![], - span: swc_common::DUMMY_SP, - }, - swc_ecma_ast::Param { - pat: swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent { - id: create_ident("component"), - type_ann: None, - }), - decorators: vec![], - span: swc_common::DUMMY_SP, - }, - ]; - - // Accept a source location (which might be undefiend). - if development { - parameters.push(swc_ecma_ast::Param { - pat: swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent { - id: create_ident("place"), - type_ann: None, - }), - decorators: vec![], - span: swc_common::DUMMY_SP, - }) - } - - let mut message = vec![ - swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(swc_ecma_ast::Str { - value: "Expected ".into(), - span: swc_common::DUMMY_SP, - raw: None, - })), - // `component ? "component" : "object"` - swc_ecma_ast::Expr::Paren(swc_ecma_ast::ParenExpr { - expr: Box::new(swc_ecma_ast::Expr::Cond(swc_ecma_ast::CondExpr { - test: Box::new(create_ident_expression("component")), - cons: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str( - swc_ecma_ast::Str { - value: "component".into(), - span: swc_common::DUMMY_SP, - raw: None, - }, - ))), - alt: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str( - swc_ecma_ast::Str { - value: "object".into(), - span: swc_common::DUMMY_SP, - raw: None, - }, - ))), - span: swc_common::DUMMY_SP, - })), - span: swc_common::DUMMY_SP, - }), - swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(swc_ecma_ast::Str { - value: " `".into(), - span: swc_common::DUMMY_SP, - raw: None, - })), - create_ident_expression("id"), - swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(swc_ecma_ast::Str { - value: "` to be defined: you likely forgot to import, pass, or provide it.".into(), - span: swc_common::DUMMY_SP, - raw: None, - })), - ]; - - // `place ? "\nIt’s referenced in your code at `" + place+ "`" : ""` - if development { - message.push(swc_ecma_ast::Expr::Paren(swc_ecma_ast::ParenExpr { - expr: Box::new(swc_ecma_ast::Expr::Cond(swc_ecma_ast::CondExpr { - test: Box::new(create_ident_expression("place")), - cons: Box::new(create_binary_expression( - vec![ - swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(swc_ecma_ast::Str { - value: "\nIt’s referenced in your code at `".into(), - span: swc_common::DUMMY_SP, - raw: None, - })), - create_ident_expression("place"), - swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(swc_ecma_ast::Str { - value: if let Some(path) = path { - format!("` in `{}`", path).into() - } else { - "`".into() - }, - span: swc_common::DUMMY_SP, - raw: None, - })), - ], - swc_ecma_ast::BinaryOp::Add, - )), - alt: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str( - swc_ecma_ast::Str { - value: "".into(), - span: swc_common::DUMMY_SP, - raw: None, - }, - ))), - span: swc_common::DUMMY_SP, - })), - span: swc_common::DUMMY_SP, - })) - } - - swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Decl(swc_ecma_ast::Decl::Fn( - swc_ecma_ast::FnDecl { - ident: create_ident("_missingMdxReference"), - declare: false, - function: Box::new(swc_ecma_ast::Function { - params: parameters, - decorators: vec![], - body: Some(swc_ecma_ast::BlockStmt { - stmts: vec![swc_ecma_ast::Stmt::Throw(swc_ecma_ast::ThrowStmt { - arg: Box::new(swc_ecma_ast::Expr::New(swc_ecma_ast::NewExpr { - callee: Box::new(create_ident_expression("Error")), - args: Some(vec![swc_ecma_ast::ExprOrSpread { - spread: None, - expr: Box::new(create_binary_expression( - message, - swc_ecma_ast::BinaryOp::Add, - )), - }]), - span: swc_common::DUMMY_SP, - type_args: None, - })), - 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, - }), - }, - ))) -} - -/// Check if this function is a props receiving component: it’s one of ours. -fn is_props_receiving_fn(name: &Option<String>) -> bool { - if let Some(name) = name { - name == "_createMdxContent" || name == "MDXContent" - } else { - false - } -} - -/// Check if a name is a literal tag name or an identifier to a component. -fn is_literal_name(name: &str) -> bool { - matches!(name.as_bytes().first(), Some(b'a'..=b'z')) || !is_identifier_name(name) -} - -// Check if a name is a valid identifier name. -fn is_identifier_name(name: &str) -> bool { - for (index, char) in name.chars().enumerate() { - if if index == 0 { - !id_start(char) - } else { - !id_cont(char, false) - } { - return false; - } - } - - true -} |