feat(transformer): implement more of react transform attributes (#1081)

This commit is contained in:
Boshen 2023-10-28 22:47:39 +08:00 committed by GitHub
parent 96332c85c6
commit 0856111bea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 104 additions and 63 deletions

View file

@ -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,

View file

@ -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()

View file

@ -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>>,

View file

@ -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