mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 12:51:57 +00:00
feat(transformer): react jsx transform (#2961)
This commit is contained in:
parent
01e64bf64d
commit
bd9fc6d169
8 changed files with 770 additions and 75 deletions
|
|
@ -148,6 +148,10 @@ impl<'a> AstBuilder<'a> {
|
||||||
BooleanLiteral { span, value }
|
BooleanLiteral { span, value }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string_literal(&self, span: Span, name: &str) -> StringLiteral<'a> {
|
||||||
|
StringLiteral::new(span, self.new_atom(name))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bigint_literal(&self, span: Span, raw: Atom<'a>, base: BigintBase) -> BigIntLiteral<'a> {
|
pub fn bigint_literal(&self, span: Span, raw: Atom<'a>, base: BigintBase) -> BigIntLiteral<'a> {
|
||||||
BigIntLiteral { span, raw, base }
|
BigIntLiteral { span, raw, base }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ impl<'a> Transformer<'a> {
|
||||||
impl<'a> VisitMut<'a> for Transformer<'a> {
|
impl<'a> VisitMut<'a> for Transformer<'a> {
|
||||||
fn visit_program(&mut self, program: &mut Program<'a>) {
|
fn visit_program(&mut self, program: &mut Program<'a>) {
|
||||||
walk_mut::walk_program_mut(self, program);
|
walk_mut::walk_program_mut(self, program);
|
||||||
|
self.x1_react.transform_program_on_exit(program);
|
||||||
self.x0_typescript.transform_program_on_exit(program);
|
self.x0_typescript.transform_program_on_exit(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,714 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use oxc_allocator::Vec;
|
||||||
|
use oxc_ast::{ast::*, AstBuilder};
|
||||||
|
use oxc_span::{CompactStr, SPAN};
|
||||||
|
use oxc_syntax::{
|
||||||
|
identifier::{is_irregular_whitespace, is_line_terminator},
|
||||||
|
xml_entities::XML_ENTITIES,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::context::Ctx;
|
use crate::context::Ctx;
|
||||||
|
|
||||||
pub use super::options::ReactOptions;
|
pub use super::options::{ReactJsxRuntime, ReactOptions};
|
||||||
|
|
||||||
/// [plugin-transform-react-jsx](https://babeljs.io/docs/babel-plugin-transform-react-jsx)
|
/// [plugin-transform-react-jsx](https://babeljs.io/docs/babel-plugin-transform-react-jsx)
|
||||||
///
|
///
|
||||||
/// This plugin generates production-ready JS code.
|
/// This plugin generates production-ready JS code.
|
||||||
///
|
///
|
||||||
/// If you are developing a React app in a development environment,
|
|
||||||
/// please use @babel/plugin-transform-react-jsx-development for a better debugging experience.
|
|
||||||
///
|
|
||||||
/// This plugin is included in `preset-react`.
|
/// This plugin is included in `preset-react`.
|
||||||
///
|
///
|
||||||
/// References:
|
/// References:
|
||||||
///
|
///
|
||||||
/// * <https://babeljs.io/docs/babel-plugin-transform-react-jsx>
|
/// * <https://babeljs.io/docs/babel-plugin-transform-react-jsx>
|
||||||
/// * <https://github.com/babel/babel/tree/main/packages/babel-helper-builder-react-jsx>
|
/// * <https://github.com/babel/babel/tree/main/packages/babel-helper-builder-react-jsx>
|
||||||
#[allow(unused)]
|
|
||||||
pub struct ReactJsx<'a> {
|
pub struct ReactJsx<'a> {
|
||||||
options: Rc<ReactOptions>,
|
options: Rc<ReactOptions>,
|
||||||
|
|
||||||
ctx: Ctx<'a>,
|
ctx: Ctx<'a>,
|
||||||
|
|
||||||
|
// States
|
||||||
|
imports: std::vec::Vec<Statement<'a>>,
|
||||||
|
require_jsx_runtime: bool,
|
||||||
|
jsx_runtime_importer: CompactStr,
|
||||||
|
default_runtime: ReactJsxRuntime,
|
||||||
|
|
||||||
|
import_jsx: bool,
|
||||||
|
import_jsxs: bool,
|
||||||
|
import_fragment: bool,
|
||||||
|
import_create_element: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transforms
|
||||||
impl<'a> ReactJsx<'a> {
|
impl<'a> ReactJsx<'a> {
|
||||||
pub fn new(options: &Rc<ReactOptions>, ctx: &Ctx<'a>) -> Self {
|
pub fn new(options: &Rc<ReactOptions>, ctx: &Ctx<'a>) -> Self {
|
||||||
Self { options: Rc::clone(options), ctx: Rc::clone(ctx) }
|
let default_runtime = options.runtime;
|
||||||
|
let jsx_runtime_importer =
|
||||||
|
if options.import_source == "react" || default_runtime.is_classic() {
|
||||||
|
CompactStr::from("react/jsx-runtime")
|
||||||
|
} else {
|
||||||
|
CompactStr::from(format!("{}/jsx-runtime", options.import_source))
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
options: Rc::clone(options),
|
||||||
|
ctx: Rc::clone(ctx),
|
||||||
|
imports: vec![],
|
||||||
|
require_jsx_runtime: false,
|
||||||
|
jsx_runtime_importer,
|
||||||
|
import_jsx: false,
|
||||||
|
import_jsxs: false,
|
||||||
|
import_fragment: false,
|
||||||
|
import_create_element: false,
|
||||||
|
default_runtime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_program_on_exit(&mut self, program: &mut Program<'a>) {
|
||||||
|
self.add_runtime_imports(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_jsx_element(&mut self, e: &JSXElement<'a>) -> Expression<'a> {
|
||||||
|
self.transform_jsx(&JSXElementOrFragment::Element(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_jsx_fragment(&mut self, e: &JSXFragment<'a>) -> Expression<'a> {
|
||||||
|
self.transform_jsx(&JSXElementOrFragment::Fragment(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_script(&self) -> bool {
|
||||||
|
self.ctx.semantic.source_type().is_script()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ast(&self) -> &AstBuilder<'a> {
|
||||||
|
&self.ctx.ast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add imports
|
||||||
|
impl<'a> ReactJsx<'a> {
|
||||||
|
pub fn add_runtime_imports(&mut self, program: &mut Program<'a>) {
|
||||||
|
if self.options.runtime.is_classic() {
|
||||||
|
if self.options.import_source != "react" {
|
||||||
|
// self.ctx.error(ImportSourceCannotBeSet);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.options.pragma != "React.createElement"
|
||||||
|
|| self.options.pragma_frag != "React.Fragment"
|
||||||
|
{
|
||||||
|
// self.ctx.error(PragmaAndPragmaFragCannotBeSet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let imports = self.ctx.ast.new_vec_from_iter(self.imports.drain(..));
|
||||||
|
let index = program
|
||||||
|
.body
|
||||||
|
.iter()
|
||||||
|
.rposition(|stmt| matches!(stmt, Statement::ModuleDeclaration(m) if m.is_import()))
|
||||||
|
.map_or(0, |i| i + 1);
|
||||||
|
program.body.splice(index..index, imports);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_import<'b>(
|
||||||
|
&mut self,
|
||||||
|
e: &JSXElementOrFragment<'a, 'b>,
|
||||||
|
has_key_after_props_spread: bool,
|
||||||
|
need_jsxs: bool,
|
||||||
|
) {
|
||||||
|
if self.options.runtime.is_classic() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match e {
|
||||||
|
JSXElementOrFragment::Element(_) if has_key_after_props_spread => {
|
||||||
|
self.add_import_create_element();
|
||||||
|
}
|
||||||
|
JSXElementOrFragment::Element(_) if need_jsxs => self.add_import_jsxs(),
|
||||||
|
JSXElementOrFragment::Element(_) => self.add_import_jsx(),
|
||||||
|
JSXElementOrFragment::Fragment(_) => {
|
||||||
|
self.add_import_fragment();
|
||||||
|
if need_jsxs {
|
||||||
|
self.add_import_jsxs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_require_jsx_runtime(&mut self) {
|
||||||
|
if !self.require_jsx_runtime {
|
||||||
|
self.require_jsx_runtime = true;
|
||||||
|
let source = self.ast().string_literal(SPAN, self.jsx_runtime_importer.as_str());
|
||||||
|
self.add_require_statement("_reactJsxRuntime", source, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_jsx_runtime_source(&self) -> StringLiteral<'a> {
|
||||||
|
self.ast().string_literal(SPAN, self.jsx_runtime_importer.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_import_jsx(&mut self) {
|
||||||
|
if self.is_script() {
|
||||||
|
self.add_require_jsx_runtime();
|
||||||
|
} else if !self.import_jsx {
|
||||||
|
self.import_jsx = true;
|
||||||
|
self.add_import_statement("jsx", "_jsx", self.get_jsx_runtime_source());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_import_jsxs(&mut self) {
|
||||||
|
if self.is_script() {
|
||||||
|
self.add_require_jsx_runtime();
|
||||||
|
} else if !self.import_jsxs {
|
||||||
|
self.import_jsxs = true;
|
||||||
|
self.add_import_statement("jsxs", "_jsxs", self.get_jsx_runtime_source());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_import_fragment(&mut self) {
|
||||||
|
if self.is_script() {
|
||||||
|
self.add_require_jsx_runtime();
|
||||||
|
} else if !self.import_fragment {
|
||||||
|
self.import_fragment = true;
|
||||||
|
self.add_import_statement("Fragment", "_Fragment", self.get_jsx_runtime_source());
|
||||||
|
self.add_import_jsx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_import_create_element(&mut self) {
|
||||||
|
if !self.import_create_element {
|
||||||
|
self.import_create_element = true;
|
||||||
|
let source = self.ast().string_literal(SPAN, self.options.import_source.as_ref());
|
||||||
|
if self.is_script() {
|
||||||
|
self.add_require_statement("_react", source, true);
|
||||||
|
} else {
|
||||||
|
self.add_import_statement("createElement", "_createElement", source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_import_statement(&mut self, imported: &str, local: &str, source: StringLiteral<'a>) {
|
||||||
|
let mut specifiers = self.ast().new_vec_with_capacity(1);
|
||||||
|
let imported =
|
||||||
|
ModuleExportName::Identifier(IdentifierName::new(SPAN, self.ast().new_atom(imported)));
|
||||||
|
specifiers.push(ImportDeclarationSpecifier::ImportSpecifier(ImportSpecifier {
|
||||||
|
span: SPAN,
|
||||||
|
imported,
|
||||||
|
local: BindingIdentifier::new(SPAN, self.ast().new_atom(local)),
|
||||||
|
import_kind: ImportOrExportKind::Value,
|
||||||
|
}));
|
||||||
|
let value = ImportOrExportKind::Value;
|
||||||
|
let import_stmt =
|
||||||
|
self.ast().import_declaration(SPAN, Some(specifiers), source, None, value);
|
||||||
|
let decl = self.ast().module_declaration(ModuleDeclaration::ImportDeclaration(import_stmt));
|
||||||
|
self.imports.push(decl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `var variable_name = require(source);`
|
||||||
|
fn add_require_statement(
|
||||||
|
&mut self,
|
||||||
|
variable_name: &str,
|
||||||
|
source: StringLiteral<'a>,
|
||||||
|
front: bool,
|
||||||
|
) {
|
||||||
|
let var_kind = VariableDeclarationKind::Var;
|
||||||
|
let callee = {
|
||||||
|
let ident = IdentifierReference::new(SPAN, "require".into());
|
||||||
|
self.ctx.ast.identifier_reference_expression(ident)
|
||||||
|
};
|
||||||
|
let args = {
|
||||||
|
let arg = Argument::Expression(self.ast().literal_string_expression(source));
|
||||||
|
self.ctx.ast.new_vec_single(arg)
|
||||||
|
};
|
||||||
|
let id = {
|
||||||
|
let ident = BindingIdentifier::new(SPAN, self.ast().new_atom(variable_name));
|
||||||
|
self.ast().binding_pattern(self.ast().binding_pattern_identifier(ident), None, false)
|
||||||
|
};
|
||||||
|
let decl = {
|
||||||
|
let init = self.ast().call_expression(SPAN, callee, args, false, None);
|
||||||
|
let decl = self.ast().variable_declarator(SPAN, var_kind, id, Some(init), false);
|
||||||
|
self.ast().new_vec_single(decl)
|
||||||
|
};
|
||||||
|
let variable_declaration =
|
||||||
|
self.ast().variable_declaration(SPAN, var_kind, decl, Modifiers::empty());
|
||||||
|
let stmt = Statement::Declaration(Declaration::VariableDeclaration(variable_declaration));
|
||||||
|
if front {
|
||||||
|
self.imports.insert(0, stmt);
|
||||||
|
} else {
|
||||||
|
self.imports.push(stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JSXElementOrFragment<'a, 'b> {
|
||||||
|
Element(&'b JSXElement<'a>),
|
||||||
|
Fragment(&'b JSXFragment<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> JSXElementOrFragment<'a, 'b> {
|
||||||
|
fn attributes(&self) -> Option<&'b Vec<'a, JSXAttributeItem<'a>>> {
|
||||||
|
match self {
|
||||||
|
Self::Element(e) if !e.opening_element.attributes.is_empty() => {
|
||||||
|
Some(&e.opening_element.attributes)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> &'b Vec<'a, JSXChild<'a>> {
|
||||||
|
match self {
|
||||||
|
Self::Element(e) => &e.children,
|
||||||
|
Self::Fragment(e) => &e.children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The react jsx/jsxs transform falls back to `createElement` when an explicit `key` argument comes after a spread
|
||||||
|
/// <https://github.com/microsoft/TypeScript/blob/6134091642f57c32f50e7b5604635e4d37dd19e8/src/compiler/transformers/jsx.ts#L264-L278>
|
||||||
|
fn has_key_after_props_spread(&self) -> bool {
|
||||||
|
let Self::Element(e) = self else { return false };
|
||||||
|
let mut spread = false;
|
||||||
|
for attr in &e.opening_element.attributes {
|
||||||
|
if matches!(attr, JSXAttributeItem::SpreadAttribute(_)) {
|
||||||
|
spread = true;
|
||||||
|
} else if spread && matches!(attr, JSXAttributeItem::Attribute(a) if a.is_key()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform jsx
|
||||||
|
impl<'a> ReactJsx<'a> {
|
||||||
|
fn transform_jsx<'b>(&mut self, e: &JSXElementOrFragment<'a, 'b>) -> Expression<'a> {
|
||||||
|
let is_classic = self.default_runtime.is_classic();
|
||||||
|
let is_automatic = self.default_runtime.is_automatic();
|
||||||
|
let has_key_after_props_spread = e.has_key_after_props_spread();
|
||||||
|
|
||||||
|
let mut arguments = self.ast().new_vec();
|
||||||
|
arguments.push(Argument::Expression(match e {
|
||||||
|
JSXElementOrFragment::Element(e) => {
|
||||||
|
self.transform_element_name(&e.opening_element.name)
|
||||||
|
}
|
||||||
|
JSXElementOrFragment::Fragment(_) => self.get_fragment(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// The key prop in `<div key={true} />`
|
||||||
|
let mut key_prop = None;
|
||||||
|
|
||||||
|
let attributes = e.attributes();
|
||||||
|
let attributes_len = attributes.map_or(0, |attrs| attrs.len());
|
||||||
|
|
||||||
|
// Add `null` to second argument in classic mode
|
||||||
|
if is_classic && attributes_len == 0 {
|
||||||
|
let null_expr = self.ast().literal_null_expression(NullLiteral::new(SPAN));
|
||||||
|
arguments.push(Argument::Expression(null_expr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The object properties for the second argument of `React.createElement`
|
||||||
|
let mut properties = self.ast().new_vec();
|
||||||
|
|
||||||
|
if let Some(attributes) = attributes {
|
||||||
|
for attribute in attributes {
|
||||||
|
match attribute {
|
||||||
|
// optimize `{...prop}` to `prop` in static mode
|
||||||
|
JSXAttributeItem::SpreadAttribute(spread)
|
||||||
|
if is_classic && attributes_len == 1 =>
|
||||||
|
{
|
||||||
|
// deopt if spreading an object with `__proto__` key
|
||||||
|
if !matches!(&spread.argument, Expression::ObjectExpression(o) if o.has_proto())
|
||||||
|
{
|
||||||
|
arguments.push(Argument::Expression(self.ast().copy(&spread.argument)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSXAttributeItem::Attribute(attr) if attr.is_key() => {
|
||||||
|
// if attr.value.is_none() {
|
||||||
|
// self.ctx.error(ValuelessKey(attr.name.span()));
|
||||||
|
// }
|
||||||
|
// In automatic mode, extract the key before spread prop,
|
||||||
|
// and add it to the third argument later.
|
||||||
|
if is_automatic && !has_key_after_props_spread {
|
||||||
|
key_prop = attr.value.as_ref();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add attribute to prop object
|
||||||
|
self.transform_jsx_attribute_item(&mut properties, attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut need_jsxs = false;
|
||||||
|
|
||||||
|
let children = e.children();
|
||||||
|
|
||||||
|
// Append children to object properties in automatic mode
|
||||||
|
if is_automatic {
|
||||||
|
let allocator = self.ast().allocator;
|
||||||
|
let mut children = Vec::from_iter_in(
|
||||||
|
children.iter().filter_map(|child| self.transform_jsx_child(child)),
|
||||||
|
allocator,
|
||||||
|
);
|
||||||
|
let children_len = children.len();
|
||||||
|
if children_len != 0 {
|
||||||
|
let value = if children_len == 1 {
|
||||||
|
children.pop().unwrap()
|
||||||
|
} else {
|
||||||
|
let elements = Vec::from_iter_in(
|
||||||
|
children.into_iter().map(ArrayExpressionElement::Expression),
|
||||||
|
allocator,
|
||||||
|
);
|
||||||
|
need_jsxs = true;
|
||||||
|
self.ast().array_expression(SPAN, elements, None)
|
||||||
|
};
|
||||||
|
let object_property = {
|
||||||
|
let kind = PropertyKind::Init;
|
||||||
|
let ident = IdentifierName::new(SPAN, "children".into());
|
||||||
|
let key = self.ast().property_key_identifier(ident);
|
||||||
|
self.ast().object_property(SPAN, kind, key, value, None, false, false, false)
|
||||||
|
};
|
||||||
|
properties.push(ObjectPropertyKind::ObjectProperty(object_property));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.add_import(e, has_key_after_props_spread, need_jsxs);
|
||||||
|
|
||||||
|
if !properties.is_empty() || is_automatic {
|
||||||
|
let object_expression = self.ast().object_expression(SPAN, properties, None);
|
||||||
|
arguments.push(Argument::Expression(object_expression));
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_automatic && key_prop.is_some() {
|
||||||
|
arguments.push(Argument::Expression(self.transform_jsx_attribute_value(key_prop)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_classic && !children.is_empty() {
|
||||||
|
arguments.extend(
|
||||||
|
children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|child| self.transform_jsx_child(child))
|
||||||
|
.map(Argument::Expression),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let callee = self.get_create_element(has_key_after_props_spread, need_jsxs);
|
||||||
|
self.ast().call_expression(SPAN, callee, arguments, false, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_element_name(&self, name: &JSXElementName<'a>) -> Expression<'a> {
|
||||||
|
match name {
|
||||||
|
JSXElementName::Identifier(ident) => {
|
||||||
|
if ident.name == "this" {
|
||||||
|
self.ast().this_expression(SPAN)
|
||||||
|
} else if ident.name.chars().next().is_some_and(|c| c.is_ascii_lowercase()) {
|
||||||
|
let string = StringLiteral::new(SPAN, ident.name.clone());
|
||||||
|
self.ast().literal_string_expression(string)
|
||||||
|
} else {
|
||||||
|
let ident = IdentifierReference::new(SPAN, ident.name.clone());
|
||||||
|
self.ctx.ast.identifier_reference_expression(ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSXElementName::MemberExpression(member_expr) => {
|
||||||
|
self.transform_jsx_member_expression(member_expr)
|
||||||
|
}
|
||||||
|
JSXElementName::NamespacedName(name) => {
|
||||||
|
// if self.options.throw_if_namespace {
|
||||||
|
// self.ctx.error(NamespaceDoesNotSupport(name.span));
|
||||||
|
// }
|
||||||
|
let name = self.ast().new_atom(&name.to_string());
|
||||||
|
let string_literal = StringLiteral::new(SPAN, name);
|
||||||
|
self.ast().literal_string_expression(string_literal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fragment(&self) -> Expression<'a> {
|
||||||
|
match self.options.runtime {
|
||||||
|
ReactJsxRuntime::Classic => {
|
||||||
|
if self.options.pragma_frag == "React.Fragment" {
|
||||||
|
let object = self.get_react_references();
|
||||||
|
let property = IdentifierName::new(SPAN, "Fragment".into());
|
||||||
|
self.ast().static_member_expression(SPAN, object, property, false)
|
||||||
|
} else {
|
||||||
|
self.get_call_expression_callee(self.options.pragma_frag.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReactJsxRuntime::Automatic => {
|
||||||
|
if self.is_script() {
|
||||||
|
self.get_static_member_expression("_reactJsxRuntime", "Fragment")
|
||||||
|
} else {
|
||||||
|
let ident = IdentifierReference::new(SPAN, "_Fragment".into());
|
||||||
|
self.ast().identifier_reference_expression(ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_create_element(&self, has_key_after_props_spread: bool, jsxs: bool) -> Expression<'a> {
|
||||||
|
match self.options.runtime {
|
||||||
|
ReactJsxRuntime::Classic => {
|
||||||
|
if self.options.pragma == "React.createElement" {
|
||||||
|
let object = self.get_react_references();
|
||||||
|
let property = IdentifierName::new(SPAN, "createElement".into());
|
||||||
|
self.ast().static_member_expression(SPAN, object, property, false)
|
||||||
|
} else {
|
||||||
|
self.get_call_expression_callee(self.options.pragma.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReactJsxRuntime::Automatic => {
|
||||||
|
let name = if self.is_script() {
|
||||||
|
if has_key_after_props_spread {
|
||||||
|
"createElement"
|
||||||
|
} else if jsxs {
|
||||||
|
"jsxs"
|
||||||
|
} else {
|
||||||
|
"jsx"
|
||||||
|
}
|
||||||
|
} else if has_key_after_props_spread {
|
||||||
|
"_createElement"
|
||||||
|
} else if jsxs {
|
||||||
|
"_jsxs"
|
||||||
|
} else {
|
||||||
|
"_jsx"
|
||||||
|
};
|
||||||
|
if self.is_script() {
|
||||||
|
let object_ident_name =
|
||||||
|
if has_key_after_props_spread { "_react" } else { "_reactJsxRuntime" };
|
||||||
|
self.get_static_member_expression(object_ident_name, name)
|
||||||
|
} else {
|
||||||
|
let ident = IdentifierReference::new(SPAN, name.into());
|
||||||
|
self.ast().identifier_reference_expression(ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_react_references(&self) -> Expression<'a> {
|
||||||
|
let ident = IdentifierReference::new(SPAN, "React".into());
|
||||||
|
self.ast().identifier_reference_expression(ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_static_member_expression(
|
||||||
|
&self,
|
||||||
|
object_ident_name: &str,
|
||||||
|
property_name: &str,
|
||||||
|
) -> Expression<'a> {
|
||||||
|
let property = IdentifierName::new(SPAN, self.ast().new_atom(property_name));
|
||||||
|
let ident = IdentifierReference::new(SPAN, self.ast().new_atom(object_ident_name));
|
||||||
|
let object = self.ast().identifier_reference_expression(ident);
|
||||||
|
self.ast().static_member_expression(SPAN, object, property, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the callee from `pragma` and `pragmaFrag`
|
||||||
|
fn get_call_expression_callee(&self, literal_callee: &str) -> Expression<'a> {
|
||||||
|
let mut callee = literal_callee.split('.');
|
||||||
|
let member = callee.next().unwrap();
|
||||||
|
let property = callee.next();
|
||||||
|
property.map_or_else(
|
||||||
|
|| {
|
||||||
|
let ident = IdentifierReference::new(SPAN, self.ast().new_atom(member));
|
||||||
|
self.ast().identifier_reference_expression(ident)
|
||||||
|
},
|
||||||
|
|property_name| self.get_static_member_expression(member, property_name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_jsx_member_expression(&self, expr: &JSXMemberExpression<'a>) -> Expression<'a> {
|
||||||
|
let object = match &expr.object {
|
||||||
|
JSXMemberExpressionObject::Identifier(ident) => {
|
||||||
|
let ident = IdentifierReference::new(SPAN, ident.name.clone());
|
||||||
|
self.ast().identifier_reference_expression(ident)
|
||||||
|
}
|
||||||
|
JSXMemberExpressionObject::MemberExpression(expr) => {
|
||||||
|
self.transform_jsx_member_expression(expr)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let property = IdentifierName::new(SPAN, expr.property.name.clone());
|
||||||
|
self.ast().static_member_expression(SPAN, object, property, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_jsx_attribute_item(
|
||||||
|
&mut self,
|
||||||
|
properties: &mut Vec<'a, ObjectPropertyKind<'a>>,
|
||||||
|
attribute: &JSXAttributeItem<'a>,
|
||||||
|
) {
|
||||||
|
match attribute {
|
||||||
|
JSXAttributeItem::Attribute(attr) => {
|
||||||
|
let kind = PropertyKind::Init;
|
||||||
|
let key = self.get_attribute_name(&attr.name);
|
||||||
|
let value = self.transform_jsx_attribute_value(attr.value.as_ref());
|
||||||
|
let object_property =
|
||||||
|
self.ast().object_property(SPAN, kind, key, value, None, false, false, false);
|
||||||
|
let object_property = ObjectPropertyKind::ObjectProperty(object_property);
|
||||||
|
properties.push(object_property);
|
||||||
|
}
|
||||||
|
JSXAttributeItem::SpreadAttribute(attr) => match &attr.argument {
|
||||||
|
Expression::ObjectExpression(expr) if !expr.has_proto() => {
|
||||||
|
properties.extend(self.ast().copy(&expr.properties));
|
||||||
|
}
|
||||||
|
expr => {
|
||||||
|
let argument = self.ast().copy(expr);
|
||||||
|
let spread_property = self.ast().spread_element(SPAN, argument);
|
||||||
|
let object_property = ObjectPropertyKind::SpreadProperty(spread_property);
|
||||||
|
properties.push(object_property);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_jsx_attribute_value(
|
||||||
|
&mut self,
|
||||||
|
value: Option<&JSXAttributeValue<'a>>,
|
||||||
|
) -> Expression<'a> {
|
||||||
|
match value {
|
||||||
|
Some(JSXAttributeValue::StringLiteral(s)) => {
|
||||||
|
let jsx_text = Self::decode_entities(s.value.as_str());
|
||||||
|
let literal = StringLiteral::new(s.span, self.ast().new_atom(&jsx_text));
|
||||||
|
self.ast().literal_string_expression(literal)
|
||||||
|
}
|
||||||
|
Some(JSXAttributeValue::Element(e)) => {
|
||||||
|
self.transform_jsx(&JSXElementOrFragment::Element(e))
|
||||||
|
}
|
||||||
|
Some(JSXAttributeValue::Fragment(e)) => {
|
||||||
|
self.transform_jsx(&JSXElementOrFragment::Fragment(e))
|
||||||
|
}
|
||||||
|
Some(JSXAttributeValue::ExpressionContainer(c)) => match &c.expression {
|
||||||
|
JSXExpression::Expression(e) => self.ast().copy(e),
|
||||||
|
JSXExpression::EmptyExpression(_e) => {
|
||||||
|
self.ast().literal_boolean_expression(BooleanLiteral::new(SPAN, true))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => self.ast().literal_boolean_expression(BooleanLiteral::new(SPAN, true)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_jsx_child(&mut self, child: &JSXChild<'a>) -> Option<Expression<'a>> {
|
||||||
|
match child {
|
||||||
|
JSXChild::Text(text) => self.transform_jsx_text(text.value.as_str()),
|
||||||
|
JSXChild::ExpressionContainer(e) => match &e.expression {
|
||||||
|
JSXExpression::Expression(e) => Some(self.ast().copy(e)),
|
||||||
|
JSXExpression::EmptyExpression(_) => None,
|
||||||
|
},
|
||||||
|
JSXChild::Element(e) => Some(self.transform_jsx(&JSXElementOrFragment::Element(e))),
|
||||||
|
JSXChild::Fragment(e) => Some(self.transform_jsx(&JSXElementOrFragment::Fragment(e))),
|
||||||
|
JSXChild::Spread(_e) => {
|
||||||
|
// self.ctx.error(SpreadChildrenAreNotSupported(e.span));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_attribute_name(&self, name: &JSXAttributeName<'a>) -> PropertyKey<'a> {
|
||||||
|
match name {
|
||||||
|
JSXAttributeName::Identifier(ident) => {
|
||||||
|
let name = ident.name.clone();
|
||||||
|
if ident.name.contains('-') {
|
||||||
|
let expr = self.ast().literal_string_expression(StringLiteral::new(SPAN, name));
|
||||||
|
self.ast().property_key_expression(expr)
|
||||||
|
} else {
|
||||||
|
self.ast().property_key_identifier(IdentifierName::new(SPAN, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSXAttributeName::NamespacedName(name) => {
|
||||||
|
let name = self.ast().new_atom(&name.to_string());
|
||||||
|
let expr = self.ast().literal_string_expression(StringLiteral::new(SPAN, name));
|
||||||
|
self.ast().property_key_expression(expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_jsx_text(&self, text: &str) -> Option<Expression<'a>> {
|
||||||
|
Self::fixup_whitespace_and_decode_entities(text).map(|s| {
|
||||||
|
let s = StringLiteral::new(SPAN, self.ast().new_atom(&s));
|
||||||
|
self.ast().literal_string_expression(s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JSX trims whitespace at the end and beginning of lines, except that the
|
||||||
|
/// start/end of a tag is considered a start/end of a line only if that line is
|
||||||
|
/// on the same line as the closing tag. See examples in
|
||||||
|
/// tests/cases/conformance/jsx/tsxReactEmitWhitespace.tsx
|
||||||
|
/// See also <https://www.w3.org/TR/html4/struct/text.html#h-9.1> and <https://www.w3.org/TR/CSS2/text.html#white-space-model>
|
||||||
|
///
|
||||||
|
/// An equivalent algorithm would be:
|
||||||
|
/// - If there is only one line, return it.
|
||||||
|
/// - If there is only whitespace (but multiple lines), return `undefined`.
|
||||||
|
/// - Split the text into lines.
|
||||||
|
/// - 'trimRight' the first line, 'trimLeft' the last line, 'trim' middle lines.
|
||||||
|
/// - Decode entities on each line (individually).
|
||||||
|
/// - Remove empty lines and join the rest with " ".
|
||||||
|
///
|
||||||
|
/// <https://github.com/microsoft/TypeScript/blob/f0374ce2a9c465e27a15b7fa4a347e2bd9079450/src/compiler/transformers/jsx.ts#L557-L608>
|
||||||
|
fn fixup_whitespace_and_decode_entities(text: &str) -> Option<String> {
|
||||||
|
let mut acc: Option<String> = None;
|
||||||
|
let mut first_non_whitespace: Option<usize> = Some(0);
|
||||||
|
let mut last_non_whitespace: Option<usize> = None;
|
||||||
|
let mut i: usize = 0;
|
||||||
|
for c in text.chars() {
|
||||||
|
if is_line_terminator(c) {
|
||||||
|
if let (Some(first), Some(last)) = (first_non_whitespace, last_non_whitespace) {
|
||||||
|
acc = Some(Self::add_line_of_jsx_text(acc, &text[first..=last]));
|
||||||
|
}
|
||||||
|
first_non_whitespace = None;
|
||||||
|
} else if c != ' ' && !is_irregular_whitespace(c) {
|
||||||
|
last_non_whitespace = Some(i);
|
||||||
|
if first_non_whitespace.is_none() {
|
||||||
|
first_non_whitespace.replace(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += c.len_utf8();
|
||||||
|
}
|
||||||
|
if let Some(first) = first_non_whitespace {
|
||||||
|
Some(Self::add_line_of_jsx_text(acc, &text[first..]))
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_line_of_jsx_text(acc: Option<String>, trimmed_line: &str) -> String {
|
||||||
|
let decoded = Self::decode_entities(trimmed_line);
|
||||||
|
if let Some(acc) = acc {
|
||||||
|
format!("{acc} {decoded}")
|
||||||
|
} else {
|
||||||
|
decoded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace entities like " ", "{", and "�" with the characters they encode.
|
||||||
|
/// * See <https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references>
|
||||||
|
/// Code adapted from <https://github.com/microsoft/TypeScript/blob/514f7e639a2a8466c075c766ee9857a30ed4e196/src/compiler/transformers/jsx.ts#L617C1-L635>
|
||||||
|
fn decode_entities(s: &str) -> String {
|
||||||
|
let mut buffer = vec![];
|
||||||
|
let mut chars = s.bytes().enumerate();
|
||||||
|
let mut prev = 0;
|
||||||
|
while let Some((i, c)) = chars.next() {
|
||||||
|
if c == b'&' {
|
||||||
|
let start = i;
|
||||||
|
let mut end = None;
|
||||||
|
for (j, c) in chars.by_ref() {
|
||||||
|
if c == b';' {
|
||||||
|
end.replace(j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(end) = end {
|
||||||
|
let word = &s[start + 1..end];
|
||||||
|
buffer.extend_from_slice(s[prev..start].as_bytes());
|
||||||
|
prev = end + 1;
|
||||||
|
if let Some(c) = XML_ENTITIES.get(word) {
|
||||||
|
buffer.extend_from_slice(c.to_string().as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.extend_from_slice(s[prev..].as_bytes());
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
// SAFETY: The buffer is constructed from valid utf chars.
|
||||||
|
unsafe {
|
||||||
|
String::from_utf8_unchecked(buffer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,18 +53,28 @@ impl<'a> React<'a> {
|
||||||
|
|
||||||
// Transforms
|
// Transforms
|
||||||
impl<'a> React<'a> {
|
impl<'a> React<'a> {
|
||||||
pub fn transform_expression(&self, expr: &mut Expression<'a>) {
|
pub fn transform_program_on_exit(&mut self, program: &mut Program<'a>) {
|
||||||
|
if self.options.jsx_plugin {
|
||||||
|
self.jsx.transform_program_on_exit(program);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_expression(&mut self, expr: &mut Expression<'a>) {
|
||||||
match expr {
|
match expr {
|
||||||
Expression::AssignmentExpression(e) => {
|
Expression::AssignmentExpression(e) => {
|
||||||
if self.options.display_name_plugin {
|
if self.options.display_name_plugin {
|
||||||
self.display_name.transform_assignment_expression(e);
|
self.display_name.transform_assignment_expression(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::JSXElement(_e) => {
|
Expression::JSXElement(e) => {
|
||||||
// *expr = unimplemented!();
|
if self.options.jsx_plugin {
|
||||||
|
*expr = self.jsx.transform_jsx_element(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expression::JSXFragment(_e) => {
|
Expression::JSXFragment(e) => {
|
||||||
// *expr = unimplemented!();
|
if self.options.jsx_plugin {
|
||||||
|
*expr = self.jsx.transform_jsx_fragment(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ use serde::Deserialize;
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ReactOptions {
|
pub struct ReactOptions {
|
||||||
|
#[serde(skip)]
|
||||||
|
pub jsx_plugin: bool,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub display_name_plugin: bool,
|
pub display_name_plugin: bool,
|
||||||
|
|
||||||
|
|
@ -65,6 +68,7 @@ pub struct ReactOptions {
|
||||||
impl Default for ReactOptions {
|
impl Default for ReactOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
jsx_plugin: false,
|
||||||
display_name_plugin: false,
|
display_name_plugin: false,
|
||||||
jsx_self_plugin: false,
|
jsx_self_plugin: false,
|
||||||
jsx_source_plugin: false,
|
jsx_source_plugin: false,
|
||||||
|
|
@ -100,10 +104,20 @@ fn default_for_pragma_frag() -> Cow<'static, str> {
|
||||||
///
|
///
|
||||||
/// Auto imports the functions that JSX transpiles to.
|
/// Auto imports the functions that JSX transpiles to.
|
||||||
/// classic does not automatic import anything.
|
/// classic does not automatic import anything.
|
||||||
#[derive(Debug, Default, Clone, Deserialize)]
|
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Deserialize)]
|
||||||
pub enum ReactJsxRuntime {
|
pub enum ReactJsxRuntime {
|
||||||
Classic,
|
Classic,
|
||||||
/// The default runtime is switched to automatic in Babel 8.
|
/// The default runtime is switched to automatic in Babel 8.
|
||||||
#[default]
|
#[default]
|
||||||
Automatic,
|
Automatic,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ReactJsxRuntime {
|
||||||
|
pub fn is_classic(self) -> bool {
|
||||||
|
self == Self::Classic
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_automatic(self) -> bool {
|
||||||
|
self == Self::Automatic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ pub struct BabelOptions {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub plugins: Vec<Value>, // Can be a string or an array
|
pub plugins: Vec<Value>, // Can be a string or an array
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub presets: Vec<Value>, // Can be a string or an array
|
||||||
|
#[serde(default)]
|
||||||
pub allow_return_outside_function: bool,
|
pub allow_return_outside_function: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub allow_await_outside_function: bool,
|
pub allow_await_outside_function: bool,
|
||||||
|
|
@ -88,12 +90,21 @@ impl BabelOptions {
|
||||||
/// * `Some<Some<Value>>` if the plugin exists with a config
|
/// * `Some<Some<Value>>` if the plugin exists with a config
|
||||||
/// * `None` if the plugin does not exist
|
/// * `None` if the plugin does not exist
|
||||||
pub fn get_plugin(&self, name: &str) -> Option<Option<Value>> {
|
pub fn get_plugin(&self, name: &str) -> Option<Option<Value>> {
|
||||||
self.plugins.iter().find_map(|v| match v {
|
self.plugins.iter().find_map(|v| Self::get_value(v, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_preset(&self, name: &str) -> Option<Option<Value>> {
|
||||||
|
self.presets.iter().find_map(|v| Self::get_value(v, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::option_option)]
|
||||||
|
fn get_value(value: &Value, name: &str) -> Option<Option<Value>> {
|
||||||
|
match value {
|
||||||
Value::String(s) if s == name => Some(None),
|
Value::String(s) if s == name => Some(None),
|
||||||
Value::Array(a) if a.first().and_then(Value::as_str).is_some_and(|s| s == name) => {
|
Value::Array(a) if a.first().and_then(Value::as_str).is_some_and(|s| s == name) => {
|
||||||
Some(a.get(1).cloned())
|
Some(a.get(1).cloned())
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Passed: 83/392
|
Passed: 131/392
|
||||||
|
|
||||||
# All Passed:
|
# All Passed:
|
||||||
* babel-plugin-transform-react-jsx-source
|
* babel-plugin-transform-react-jsx-source
|
||||||
|
|
@ -125,44 +125,34 @@ Passed: 83/392
|
||||||
* regression/15768/input.ts
|
* regression/15768/input.ts
|
||||||
* variable-declaration/non-null-in-optional-chain/input.ts
|
* variable-declaration/non-null-in-optional-chain/input.ts
|
||||||
|
|
||||||
# babel-preset-react (2/13)
|
# babel-preset-react (4/13)
|
||||||
* preset-options/development/input.js
|
* preset-options/development/input.js
|
||||||
* preset-options/development-runtime-automatic/input.js
|
* preset-options/development-runtime-automatic/input.js
|
||||||
* preset-options/development-runtime-automatic-windows/input.js
|
* preset-options/development-runtime-automatic-windows/input.js
|
||||||
* preset-options/development-windows/input.js
|
* preset-options/development-windows/input.js
|
||||||
* preset-options/empty-options/input.js
|
|
||||||
* preset-options/runtime-automatic/input.js
|
|
||||||
* preset-options/runtime-classic/input.js
|
* preset-options/runtime-classic/input.js
|
||||||
* preset-options/runtime-classic-pragma-no-frag/input.js
|
* preset-options/runtime-classic-pragma-no-frag/input.js
|
||||||
* regression/11294/input.mjs
|
* regression/11294/input.mjs
|
||||||
* regression/another-preset-with-custom-jsx-keep-source-self/input.mjs
|
* regression/another-preset-with-custom-jsx-keep-source-self/input.mjs
|
||||||
* regression/runtime-classic-allow-multiple-source-self/input.mjs
|
* regression/runtime-classic-allow-multiple-source-self/input.mjs
|
||||||
|
|
||||||
# babel-plugin-transform-react-jsx (1/156)
|
# babel-plugin-transform-react-jsx (47/156)
|
||||||
* autoImport/after-polyfills/input.mjs
|
|
||||||
* autoImport/after-polyfills-2/input.mjs
|
|
||||||
* autoImport/after-polyfills-compiled-to-cjs/input.mjs
|
* autoImport/after-polyfills-compiled-to-cjs/input.mjs
|
||||||
* autoImport/after-polyfills-script-not-supported/input.js
|
|
||||||
* autoImport/auto-import-react-source-type-module/input.js
|
* autoImport/auto-import-react-source-type-module/input.js
|
||||||
* autoImport/auto-import-react-source-type-script/input.js
|
|
||||||
* autoImport/complicated-scope-module/input.js
|
* autoImport/complicated-scope-module/input.js
|
||||||
* autoImport/complicated-scope-script/input.js
|
|
||||||
* autoImport/import-source/input.js
|
* autoImport/import-source/input.js
|
||||||
* autoImport/import-source-pragma/input.js
|
* autoImport/import-source-pragma/input.js
|
||||||
* autoImport/react-defined/input.js
|
* autoImport/react-defined/input.js
|
||||||
* pure/false-default-pragma-automatic-runtime/input.js
|
|
||||||
* pure/false-default-pragma-classic-runtime/input.js
|
* pure/false-default-pragma-classic-runtime/input.js
|
||||||
* pure/false-pragma-comment-automatic-runtime/input.js
|
* pure/false-pragma-comment-automatic-runtime/input.js
|
||||||
* pure/false-pragma-comment-classic-runtime/input.js
|
* pure/false-pragma-comment-classic-runtime/input.js
|
||||||
* pure/false-pragma-option-automatic-runtime/input.js
|
* pure/false-pragma-option-automatic-runtime/input.js
|
||||||
* pure/false-pragma-option-classic-runtime/input.js
|
* pure/false-pragma-option-classic-runtime/input.js
|
||||||
* pure/true-default-pragma-automatic-runtime/input.js
|
|
||||||
* pure/true-default-pragma-classic-runtime/input.js
|
* pure/true-default-pragma-classic-runtime/input.js
|
||||||
* pure/true-pragma-comment-automatic-runtime/input.js
|
* pure/true-pragma-comment-automatic-runtime/input.js
|
||||||
* pure/true-pragma-comment-classic-runtime/input.js
|
* pure/true-pragma-comment-classic-runtime/input.js
|
||||||
* pure/true-pragma-option-automatic-runtime/input.js
|
* pure/true-pragma-option-automatic-runtime/input.js
|
||||||
* pure/true-pragma-option-classic-runtime/input.js
|
* pure/true-pragma-option-classic-runtime/input.js
|
||||||
* pure/unset-default-pragma-automatic-runtime/input.js
|
|
||||||
* pure/unset-default-pragma-classic-runtime/input.js
|
* pure/unset-default-pragma-classic-runtime/input.js
|
||||||
* pure/unset-pragma-comment-automatic-runtime/input.js
|
* pure/unset-pragma-comment-automatic-runtime/input.js
|
||||||
* pure/unset-pragma-comment-classic-runtime/input.js
|
* pure/unset-pragma-comment-classic-runtime/input.js
|
||||||
|
|
@ -223,62 +213,26 @@ Passed: 83/392
|
||||||
* react/wraps-props-in-react-spread-for-first-spread-attributes/input.js
|
* react/wraps-props-in-react-spread-for-first-spread-attributes/input.js
|
||||||
* react/wraps-props-in-react-spread-for-last-spread-attributes/input.js
|
* react/wraps-props-in-react-spread-for-last-spread-attributes/input.js
|
||||||
* react/wraps-props-in-react-spread-for-middle-spread-attributes/input.js
|
* react/wraps-props-in-react-spread-for-middle-spread-attributes/input.js
|
||||||
* react-automatic/adds-appropriate-newlines-when-using-spread-attribute/input.js
|
|
||||||
* react-automatic/arrow-functions/input.js
|
* react-automatic/arrow-functions/input.js
|
||||||
* react-automatic/assignment/input.js
|
|
||||||
* react-automatic/concatenates-adjacent-string-literals/input.js
|
* react-automatic/concatenates-adjacent-string-literals/input.js
|
||||||
* react-automatic/does-not-add-source-self-automatic/input.mjs
|
* react-automatic/does-not-add-source-self-automatic/input.mjs
|
||||||
* react-automatic/dont-coerce-expression-containers/input.js
|
|
||||||
* react-automatic/duplicate-props/input.js
|
|
||||||
* react-automatic/flattens-spread/input.js
|
|
||||||
* react-automatic/handle-fragments/input.js
|
* react-automatic/handle-fragments/input.js
|
||||||
* react-automatic/handle-fragments-with-key/input.js
|
|
||||||
* react-automatic/handle-fragments-with-no-children/input.js
|
* react-automatic/handle-fragments-with-no-children/input.js
|
||||||
* react-automatic/handle-nonstatic-children/input.js
|
|
||||||
* react-automatic/handle-spread-with-proto/input.js
|
|
||||||
* react-automatic/handle-static-children/input.js
|
* react-automatic/handle-static-children/input.js
|
||||||
* react-automatic/jsx-with-retainlines-option/input.js
|
|
||||||
* react-automatic/jsx-without-retainlines-option/input.js
|
|
||||||
* react-automatic/key-undefined-works/input.js
|
|
||||||
* react-automatic/optimisation.react.constant-elements/input.js
|
* react-automatic/optimisation.react.constant-elements/input.js
|
||||||
* react-automatic/pragma-works-with-no-space-at-the-end/input.js
|
* react-automatic/pragma-works-with-no-space-at-the-end/input.js
|
||||||
* react-automatic/should-add-quotes-es3/input.js
|
* react-automatic/should-add-quotes-es3/input.js
|
||||||
* react-automatic/should-allow-constructor-as-prop/input.js
|
|
||||||
* react-automatic/should-allow-deeper-js-namespacing/input.js
|
|
||||||
* react-automatic/should-allow-elements-as-attributes/input.js
|
|
||||||
* react-automatic/should-allow-js-namespacing/input.js
|
|
||||||
* react-automatic/should-allow-nested-fragments/input.js
|
* react-automatic/should-allow-nested-fragments/input.js
|
||||||
* react-automatic/should-avoid-wrapping-in-extra-parens-if-not-needed/input.js
|
|
||||||
* react-automatic/should-convert-simple-tags/input.js
|
|
||||||
* react-automatic/should-convert-simple-text/input.js
|
|
||||||
* react-automatic/should-disallow-spread-children/input.js
|
* react-automatic/should-disallow-spread-children/input.js
|
||||||
* react-automatic/should-disallow-valueless-key/input.js
|
* react-automatic/should-disallow-valueless-key/input.js
|
||||||
* react-automatic/should-disallow-xml-namespacing/input.js
|
* react-automatic/should-disallow-xml-namespacing/input.js
|
||||||
* react-automatic/should-escape-xhtml-jsxattribute/input.js
|
|
||||||
* react-automatic/should-escape-xhtml-jsxtext/input.js
|
* react-automatic/should-escape-xhtml-jsxtext/input.js
|
||||||
* react-automatic/should-handle-attributed-elements/input.js
|
* react-automatic/should-handle-attributed-elements/input.js
|
||||||
* react-automatic/should-handle-has-own-property-correctly/input.js
|
|
||||||
* react-automatic/should-have-correct-comma-in-nested-children/input.js
|
* react-automatic/should-have-correct-comma-in-nested-children/input.js
|
||||||
* react-automatic/should-insert-commas-after-expressions-before-whitespace/input.js
|
|
||||||
* react-automatic/should-not-add-quotes-to-identifier-names/input.js
|
|
||||||
* react-automatic/should-not-mangle-expressioncontainer-attribute-values/input.js
|
|
||||||
* react-automatic/should-not-strip-nbsp-even-coupled-with-other-whitespace/input.js
|
|
||||||
* react-automatic/should-not-strip-tags-with-a-single-child-of-nbsp/input.js
|
|
||||||
* react-automatic/should-properly-handle-comments-between-props/input.js
|
|
||||||
* react-automatic/should-properly-handle-keys/input.js
|
* react-automatic/should-properly-handle-keys/input.js
|
||||||
* react-automatic/should-properly-handle-null-prop-spread/input.js
|
|
||||||
* react-automatic/should-quote-jsx-attributes/input.js
|
|
||||||
* react-automatic/should-support-xml-namespaces-if-flag/input.js
|
|
||||||
* react-automatic/should-throw-error-namespaces-if-not-flag/input.js
|
* react-automatic/should-throw-error-namespaces-if-not-flag/input.js
|
||||||
* react-automatic/should-throw-when-filter-is-specified/input.js
|
* react-automatic/should-throw-when-filter-is-specified/input.js
|
||||||
* react-automatic/should-transform-known-hyphenated-tags/input.js
|
|
||||||
* react-automatic/should-use-createElement-when-key-comes-after-spread/input.js
|
|
||||||
* react-automatic/should-use-jsx-when-key-comes-before-spread/input.js
|
|
||||||
* react-automatic/should-warn-when-pragma-or-pragmaFrag-is-set/input.js
|
* react-automatic/should-warn-when-pragma-or-pragmaFrag-is-set/input.js
|
||||||
* react-automatic/this-tag-name/input.js
|
|
||||||
* react-automatic/weird-symbols/input.js
|
|
||||||
* react-automatic/wraps-props-in-react-spread-for-last-spread-attributes/input.js
|
|
||||||
* react-automatic/wraps-props-in-react-spread-for-middle-spread-attributes/input.js
|
|
||||||
* regression/issue-12478-automatic/input.js
|
* regression/issue-12478-automatic/input.js
|
||||||
* regression/issue-12478-classic/input.js
|
* regression/issue-12478-classic/input.js
|
||||||
* regression/pragma-frag-set-default-classic-runtime/input.js
|
* regression/pragma-frag-set-default-classic-runtime/input.js
|
||||||
|
|
@ -287,10 +241,8 @@ Passed: 83/392
|
||||||
* removed-options/invalid-use-spread-false/input.js
|
* removed-options/invalid-use-spread-false/input.js
|
||||||
* removed-options/invalid-use-spread-true/input.js
|
* removed-options/invalid-use-spread-true/input.js
|
||||||
* runtime/classic/input.js
|
* runtime/classic/input.js
|
||||||
* runtime/defaults-to-automatic/input.js
|
|
||||||
* runtime/invalid-runtime/input.js
|
* runtime/invalid-runtime/input.js
|
||||||
* runtime/pragma-runtime-classsic/input.js
|
* runtime/pragma-runtime-classsic/input.js
|
||||||
* runtime/runtime-automatic/input.js
|
|
||||||
* sourcemaps/JSXText/input.js
|
* sourcemaps/JSXText/input.js
|
||||||
* spread-transform/transform-to-babel-extend/input.js
|
* spread-transform/transform-to-babel-extend/input.js
|
||||||
* spread-transform/transform-to-object-assign/input.js
|
* spread-transform/transform-to-object-assign/input.js
|
||||||
|
|
|
||||||
|
|
@ -87,13 +87,33 @@ pub trait TestCase {
|
||||||
}
|
}
|
||||||
let options = self.options();
|
let options = self.options();
|
||||||
|
|
||||||
let mut react = options
|
let react = options.get_preset("react").map_or_else(
|
||||||
.get_plugin("transform-react-jsx")
|
|| {
|
||||||
.map(get_options::<ReactOptions>)
|
let mut react_options = options
|
||||||
.unwrap_or_default();
|
.get_plugin("transform-react-jsx")
|
||||||
react.display_name_plugin = options.get_plugin("transform-react-display-name").is_some();
|
.map(|options| {
|
||||||
react.jsx_self_plugin = options.get_plugin("transform-react-jsx-self").is_some();
|
let mut options = get_options::<ReactOptions>(options);
|
||||||
react.jsx_source_plugin = options.get_plugin("transform-react-jsx-source").is_some();
|
options.jsx_plugin = true;
|
||||||
|
options
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
react_options.display_name_plugin =
|
||||||
|
options.get_plugin("transform-react-display-name").is_some();
|
||||||
|
react_options.jsx_self_plugin =
|
||||||
|
options.get_plugin("transform-react-jsx-self").is_some();
|
||||||
|
react_options.jsx_source_plugin =
|
||||||
|
options.get_plugin("transform-react-jsx-source").is_some();
|
||||||
|
react_options
|
||||||
|
},
|
||||||
|
|options| {
|
||||||
|
let mut react_options = get_options::<ReactOptions>(options);
|
||||||
|
react_options.jsx_plugin = true;
|
||||||
|
react_options.jsx_self_plugin = true;
|
||||||
|
react_options.jsx_source_plugin = true;
|
||||||
|
react_options.display_name_plugin = true;
|
||||||
|
react_options
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
TransformOptions {
|
TransformOptions {
|
||||||
assumptions: serde_json::from_value(options.assumptions.clone()).unwrap_or_default(),
|
assumptions: serde_json::from_value(options.assumptions.clone()).unwrap_or_default(),
|
||||||
|
|
@ -263,10 +283,10 @@ impl TestCase for ConformanceTestCase {
|
||||||
let passed =
|
let passed =
|
||||||
transformed_code == output || (!output.is_empty() && actual_errors.contains(&output));
|
transformed_code == output || (!output.is_empty() && actual_errors.contains(&output));
|
||||||
if filtered {
|
if filtered {
|
||||||
println!("Input:\n");
|
|
||||||
println!("{input}\n");
|
|
||||||
println!("Options:");
|
println!("Options:");
|
||||||
println!("{transform_options:#?}\n");
|
println!("{transform_options:#?}\n");
|
||||||
|
println!("Input:\n");
|
||||||
|
println!("{input}\n");
|
||||||
if babel_options.throws.is_some() {
|
if babel_options.throws.is_some() {
|
||||||
println!("Expected Errors:\n");
|
println!("Expected Errors:\n");
|
||||||
println!("{output}\n");
|
println!("{output}\n");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue