Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/oxc_transformer/src/react/jsx/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
62 changes: 44 additions & 18 deletions crates/oxc_transformer/src/react/jsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
26 changes: 9 additions & 17 deletions crates/oxc_transformer/src/react/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<String>,

/// 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<String>,

/// `useBuiltIns` is deprecated in Babel 8.
///
Expand All @@ -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,
}
Expand Down Expand Up @@ -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());
}
}
}
Expand Down