mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(transformer): implement more of react transform attributes (#1081)
This commit is contained in:
parent
96332c85c6
commit
0856111bea
4 changed files with 104 additions and 63 deletions
|
|
@ -376,6 +376,13 @@ pub struct ObjectExpression<'a> {
|
|||
pub trailing_comma: Option<Span>,
|
||||
}
|
||||
|
||||
impl<'a> ObjectExpression<'a> {
|
||||
pub fn has_proto(&self) -> bool {
|
||||
use crate::syntax_directed_operations::PropName;
|
||||
self.properties.iter().any(|p| p.prop_name().is_some_and(|name| name.0 == "__proto__"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize), serde(untagged))]
|
||||
pub enum ObjectPropertyKind<'a> {
|
||||
|
|
@ -1827,6 +1834,10 @@ pub enum ModuleDeclaration<'a> {
|
|||
}
|
||||
|
||||
impl<'a> ModuleDeclaration<'a> {
|
||||
pub fn is_import(&self) -> bool {
|
||||
matches!(self, Self::ImportDeclaration(_))
|
||||
}
|
||||
|
||||
pub fn is_export(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ fn main() {
|
|||
let transform_options = TransformOptions {
|
||||
target: TransformTarget::ES2015,
|
||||
react_jsx: Some(ReactJsxOptions {
|
||||
runtime: ReactJsxRuntime::Automatic,
|
||||
runtime: ReactJsxRuntime::Classic,
|
||||
..ReactJsxOptions::default()
|
||||
}),
|
||||
..TransformOptions::default()
|
||||
|
|
|
|||
|
|
@ -92,8 +92,12 @@ impl<'a> ReactJsx<'a> {
|
|||
if self.options.runtime.is_classic() {
|
||||
return;
|
||||
}
|
||||
self.imports.extend(stmts.drain(..));
|
||||
*stmts = self.ast.move_statement_vec(&mut self.imports);
|
||||
let imports = self.ast.move_statement_vec(&mut self.imports);
|
||||
let index = stmts
|
||||
.iter()
|
||||
.position(|stmt| matches!(stmt, Statement::ModuleDeclaration(m) if m.is_import()))
|
||||
.map_or(0, |i| i + 1);
|
||||
stmts.splice(index..index, imports);
|
||||
}
|
||||
|
||||
fn add_import<'b>(
|
||||
|
|
@ -169,7 +173,6 @@ impl<'a> ReactJsx<'a> {
|
|||
let is_classic = self.options.runtime.is_classic();
|
||||
let is_automatic = self.options.runtime.is_automatic();
|
||||
let has_key_after_props_spread = e.has_key_after_props_spread();
|
||||
let children = e.children();
|
||||
|
||||
// TODO: compute the correct capacity for both runtimes
|
||||
let mut arguments = self.ast.new_vec_with_capacity(1);
|
||||
|
|
@ -181,51 +184,60 @@ impl<'a> ReactJsx<'a> {
|
|||
JSXElementOrFragment::Fragment(_) => self.get_fragment(),
|
||||
}));
|
||||
|
||||
let mut key = None;
|
||||
// TODO: compute the correct capacity for both runtimes
|
||||
let mut properties = self.ast.new_vec_with_capacity(0);
|
||||
if let Some(attributes) = e.attributes() {
|
||||
for attribute in attributes {
|
||||
let kind = PropertyKind::Init;
|
||||
match attribute {
|
||||
JSXAttributeItem::Attribute(attr) => {
|
||||
if is_automatic && attr.is_key() && !has_key_after_props_spread {
|
||||
key = attr.value.as_ref();
|
||||
continue;
|
||||
}
|
||||
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) => {
|
||||
for object_property in &expr.properties {
|
||||
properties.push(self.ast.copy(object_property));
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if is_classic {
|
||||
// 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_with_capacity(0);
|
||||
|
||||
if let Some(attributes) = attributes {
|
||||
// TODO: compute the correct capacity for both runtimes
|
||||
|
||||
for attribute in attributes {
|
||||
// optimize `{...prop}` to `prop` in static mode
|
||||
if is_classic && attributes_len == 1 {
|
||||
if let JSXAttributeItem::SpreadAttribute(spread) = attribute {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
if let JSXAttributeItem::Attribute(attr) = attribute {
|
||||
if attr.is_key() {
|
||||
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 && !children.is_empty() {
|
||||
let key =
|
||||
self.ast.property_key_identifier(IdentifierName::new(SPAN, "children".into()));
|
||||
let ident = IdentifierName::new(SPAN, "children".into());
|
||||
let key = self.ast.property_key_identifier(ident);
|
||||
let value = if children.len() == 1 {
|
||||
self.transform_jsx_child(&children[0])
|
||||
} else {
|
||||
|
|
@ -239,16 +251,9 @@ impl<'a> ReactJsx<'a> {
|
|||
Some(self.ast.array_expression(SPAN, elements, None))
|
||||
};
|
||||
if let Some(value) = value {
|
||||
let object_property = self.ast.object_property(
|
||||
SPAN,
|
||||
PropertyKind::Init,
|
||||
key,
|
||||
value,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
let kind = PropertyKind::Init;
|
||||
let object_property =
|
||||
self.ast.object_property(SPAN, kind, key, value, None, false, false, false);
|
||||
properties.push(ObjectPropertyKind::ObjectProperty(object_property));
|
||||
}
|
||||
}
|
||||
|
|
@ -258,8 +263,8 @@ impl<'a> ReactJsx<'a> {
|
|||
arguments.push(Argument::Expression(object_expression));
|
||||
}
|
||||
|
||||
if is_automatic && key.is_some() {
|
||||
arguments.push(Argument::Expression(self.transform_jsx_attribute_value(key)));
|
||||
if is_automatic && key_prop.is_some() {
|
||||
arguments.push(Argument::Expression(self.transform_jsx_attribute_value(key_prop)));
|
||||
}
|
||||
|
||||
if is_classic && !children.is_empty() {
|
||||
|
|
@ -273,7 +278,6 @@ impl<'a> ReactJsx<'a> {
|
|||
|
||||
let callee = self.get_create_element(has_key_after_props_spread, need_jsxs);
|
||||
self.add_import(e, has_key_after_props_spread, need_jsxs);
|
||||
|
||||
self.ast.call_expression(SPAN, callee, arguments, false, None)
|
||||
}
|
||||
|
||||
|
|
@ -365,6 +369,37 @@ impl<'a> ReactJsx<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
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() => {
|
||||
for object_property in &expr.properties {
|
||||
properties.push(self.ast.copy(object_property));
|
||||
}
|
||||
}
|
||||
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>>,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Passed: 219/1083
|
||||
Passed: 224/1083
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-numeric-separator
|
||||
|
|
@ -804,7 +804,7 @@ Passed: 219/1083
|
|||
* regression/11061/input.mjs
|
||||
* variable-declaration/non-null-in-optional-chain/input.ts
|
||||
|
||||
# babel-plugin-transform-react-jsx (70/172)
|
||||
# babel-plugin-transform-react-jsx (75/172)
|
||||
* autoImport/after-polyfills/input.mjs
|
||||
* autoImport/after-polyfills-2/input.mjs
|
||||
* autoImport/after-polyfills-compiled-to-cjs/input.mjs
|
||||
|
|
@ -815,7 +815,6 @@ Passed: 219/1083
|
|||
* autoImport/complicated-scope-script/input.js
|
||||
* autoImport/import-source/input.js
|
||||
* autoImport/import-source-pragma/input.js
|
||||
* autoImport/react-defined/input.js
|
||||
* pure/false-default-pragma-automatic-runtime/input.js
|
||||
* pure/false-pragma-comment-automatic-runtime/input.js
|
||||
* pure/false-pragma-comment-classic-runtime/input.js
|
||||
|
|
@ -837,8 +836,6 @@ Passed: 219/1083
|
|||
* react/assignment-babel-7/input.js
|
||||
* react/avoids-spread-babel-7/input.js
|
||||
* react/does-not-add-source-self-babel-7/input.mjs
|
||||
* react/flattens-spread/input.js
|
||||
* react/handle-spread-with-proto/input.js
|
||||
* react/handle-spread-with-proto-babel-7/input.js
|
||||
* react/honor-custom-jsx-comment/input.js
|
||||
* react/honor-custom-jsx-comment-if-jsx-pragma-option-set/input.js
|
||||
|
|
@ -870,9 +867,7 @@ Passed: 219/1083
|
|||
* react-automatic/.should-properly-handle-comments-adjacent-to-children/input.js
|
||||
* react-automatic/arrow-functions/input.js
|
||||
* react-automatic/does-not-add-source-self-automatic/input.mjs
|
||||
* react-automatic/handle-fragments-with-key/input.js
|
||||
* react-automatic/handle-nonstatic-children/input.js
|
||||
* react-automatic/handle-spread-with-proto/input.js
|
||||
* react-automatic/optimisation.react.constant-elements/input.js
|
||||
* react-automatic/pragma-works-with-no-space-at-the-end/input.js
|
||||
* react-automatic/should-add-quotes-es3/input.js
|
||||
|
|
|
|||
Loading…
Reference in a new issue