diff --git a/crates/oxc_transformer/src/react/jsx/diagnostics.rs b/crates/oxc_transformer/src/react/jsx/diagnostics.rs index 1b6a317bfc21f..2ec7dcd2cc08c 100644 --- a/crates/oxc_transformer/src/react/jsx/diagnostics.rs +++ b/crates/oxc_transformer/src/react/jsx/diagnostics.rs @@ -6,6 +6,11 @@ pub fn pragma_and_pragma_frag_cannot_be_set() -> OxcDiagnostic { .with_help("Remove `pragma` and `pragmaFrag` options.") } +pub fn invalid_pragma() -> OxcDiagnostic { + OxcDiagnostic::warn("pragma and pragmaFrag must be of the form `foo` or `foo.bar`.") + .with_help("Fix `pragma` and `pragmaFrag` options.") +} + pub fn import_source_cannot_be_set() -> OxcDiagnostic { OxcDiagnostic::warn("importSource cannot be set when runtime is classic.") .with_help("Remove `importSource` option.") diff --git a/crates/oxc_transformer/src/react/jsx/mod.rs b/crates/oxc_transformer/src/react/jsx/mod.rs index 36e59c2d72dba..48b08a42803b1 100644 --- a/crates/oxc_transformer/src/react/jsx/mod.rs +++ b/crates/oxc_transformer/src/react/jsx/mod.rs @@ -79,14 +79,42 @@ struct Pragma<'a> { } impl<'a> Pragma<'a> { - fn parse(pragma: &str, ast: &AstBuilder<'a>) -> Self { - let mut parts = pragma.split('.'); - let object = ast.new_atom(parts.next().unwrap()); - let property = parts.next().map(|property| { - assert!(parts.next().is_none(), "Invalid pragma"); - ast.new_atom(property) - }); - Self { object, property } + /// Parse `options.pragma` or `options.pragma_frag`. + /// + /// If provided option is invalid, raise an error and use default. + fn parse(pragma: Option<&String>, default_property_name: &'static str, ctx: &Ctx<'a>) -> Self { + if let Some(pragma) = pragma { + let mut parts = pragma.split('.'); + + let object_name = parts.next().unwrap(); + if object_name.is_empty() { + return Self::invalid(default_property_name, ctx); + } + + let property = match parts.next() { + Some(property_name) => { + if property_name.is_empty() || parts.next().is_some() { + return Self::invalid(default_property_name, ctx); + } + Some(ctx.ast.new_atom(property_name)) + } + None => None, + }; + + let object = ctx.ast.new_atom(object_name); + Self { object, property } + } else { + Self::default(default_property_name) + } + } + + fn invalid(default_property_name: &'static str, ctx: &Ctx<'a>) -> Self { + ctx.error(diagnostics::invalid_pragma()); + Self::default(default_property_name) + } + + fn default(default_property_name: &'static str) -> Self { + Self { object: Atom::from("React"), property: Some(Atom::from(default_property_name)) } } fn create_expression(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> { @@ -119,11 +147,16 @@ impl<'a> ReactJsx<'a> { // Parse pragmas let (pragma, pragma_frag) = match options.runtime { ReactJsxRuntime::Classic => { - let pragma = Pragma::parse(&options.pragma, &ctx.ast); - let pragma_frag = Pragma::parse(&options.pragma_frag, &ctx.ast); + let pragma = Pragma::parse(options.pragma.as_ref(), "createElement", ctx); + let pragma_frag = Pragma::parse(options.pragma_frag.as_ref(), "Fragment", ctx); (Some(pragma), Some(pragma_frag)) } - ReactJsxRuntime::Automatic => (None, None), + ReactJsxRuntime::Automatic => { + if options.pragma.is_some() || options.pragma_frag.is_some() { + ctx.error(diagnostics::pragma_and_pragma_frag_cannot_be_set()); + } + (None, None) + } }; Self { @@ -186,13 +219,6 @@ impl<'a> ReactJsx<'a> { return; } - if self.options.pragma != "React.createElement" - || self.options.pragma_frag != "React.Fragment" - { - self.ctx.error(diagnostics::pragma_and_pragma_frag_cannot_be_set()); - return; - } - let imports = self.ctx.module_imports.get_import_statements(); let mut index = program .body diff --git a/crates/oxc_transformer/src/react/options.rs b/crates/oxc_transformer/src/react/options.rs index 0c74b43e99324..66f2c46b4f359 100644 --- a/crates/oxc_transformer/src/react/options.rs +++ b/crates/oxc_transformer/src/react/options.rs @@ -14,14 +14,6 @@ fn default_for_import_source() -> Cow<'static, str> { Cow::Borrowed("react") } -fn default_for_pragma() -> Cow<'static, str> { - Cow::Borrowed("React.createElement") -} - -fn default_for_pragma_frag() -> Cow<'static, str> { - Cow::Borrowed("React.Fragment") -} - /// Decides which runtime to use. /// /// Auto imports the functions that JSX transpiles to. @@ -101,14 +93,14 @@ pub struct ReactOptions { /// Note that the @jsx React.DOM pragma has been deprecated as of React v0.12 /// /// Defaults to `React.createElement`. - #[serde(default = "default_for_pragma")] - pub pragma: Cow<'static, str>, + #[serde(default)] + pub pragma: Option, /// Replace the component used when compiling JSX fragments. It should be a valid JSX tag name. /// /// Defaults to `React.Fragment`. - #[serde(default = "default_for_pragma_frag")] - pub pragma_frag: Cow<'static, str>, + #[serde(default)] + pub pragma_frag: Option, /// `useBuiltIns` is deprecated in Babel 8. /// @@ -133,8 +125,8 @@ impl Default for ReactOptions { throw_if_namespace: default_as_true(), pure: default_as_true(), import_source: default_for_import_source(), - pragma: default_for_pragma(), - pragma_frag: default_for_pragma_frag(), + pragma: None, + pragma_frag: None, use_built_ins: None, use_spread: None, } @@ -187,20 +179,20 @@ impl ReactOptions { // read jsxImportSource if let Some(import_source) = comment.strip_prefix("jsxImportSource").map(str::trim) { - self.import_source = Cow::from(import_source.to_string()); + self.import_source = Cow::Owned(import_source.to_string()); continue; } // read jsxFrag if let Some(pragma_frag) = comment.strip_prefix("jsxFrag").map(str::trim) { - self.pragma_frag = Cow::from(pragma_frag.to_string()); + self.pragma_frag = Some(pragma_frag.to_string()); continue; } // Put this condition at the end to avoid breaking @jsxXX // read jsx if let Some(pragma) = comment.strip_prefix("jsx").map(str::trim) { - self.pragma = Cow::from(pragma.to_string()); + self.pragma = Some(pragma.to_string()); } } }