feat(transformer): implement key extraction for react automatic (#1077)

This commit is contained in:
Boshen 2023-10-28 16:34:54 +08:00 committed by GitHub
parent 394ed358f6
commit 5fb27fbe8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 42 deletions

View file

@ -157,6 +157,12 @@ pub struct JSXAttribute<'a> {
pub value: Option<JSXAttributeValue<'a>>,
}
impl<'a> JSXAttribute<'a> {
pub fn is_key(&self) -> bool {
matches!(&self.name, JSXAttributeName::Identifier(ident) if ident.name == "key")
}
}
/// JSX Spread Attribute
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]

View file

@ -53,14 +53,8 @@ impl<'a, 'b> JSXElementOrFragment<'a, 'b> {
for attr in &e.opening_element.attributes {
if matches!(attr, JSXAttributeItem::SpreadAttribute(_)) {
spread = true;
} else if spread {
if let JSXAttributeItem::Attribute(a) = attr {
if let JSXAttributeName::Identifier(ident) = &a.name {
if ident.name == "key" {
return true;
}
}
}
} else if spread && matches!(attr, JSXAttributeItem::Attribute(a) if a.is_key()) {
return true;
}
}
false
@ -165,6 +159,8 @@ impl<'a> ReactJsx<'a> {
}
fn transform_jsx<'b>(&mut self, e: &JSXElementOrFragment<'a, 'b>) -> Option<Expression<'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 callee = self.get_create_element(has_key_after_props_spread);
let children = e.children();
@ -179,6 +175,7 @@ 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() {
@ -186,34 +183,12 @@ impl<'a> ReactJsx<'a> {
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 = match &attr.value {
Some(value) => {
match value {
JSXAttributeValue::StringLiteral(s) => {
self.ast.literal_string_expression(s.clone())
}
JSXAttributeValue::Element(_)
| JSXAttributeValue::Fragment(_) => {
/* TODO */
continue;
}
JSXAttributeValue::ExpressionContainer(c) => {
match &c.expression {
JSXExpression::Expression(e) => self.ast.copy(e),
JSXExpression::EmptyExpression(_e) =>
/* TODO */
{
continue;
}
}
}
}
}
None => {
self.ast.literal_boolean_expression(BooleanLiteral::new(SPAN, true))
}
};
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);
@ -236,12 +211,12 @@ impl<'a> ReactJsx<'a> {
},
}
}
} else if self.options.runtime.is_classic() {
} else if is_classic {
let null_expr = self.ast.literal_null_expression(NullLiteral::new(SPAN));
arguments.push(Argument::Expression(null_expr));
}
if self.options.runtime.is_automatic() && !children.is_empty() {
if is_automatic && !children.is_empty() {
let key =
self.ast.property_key_identifier(IdentifierName::new(SPAN, "children".into()));
let value = if children.len() == 1 {
@ -268,12 +243,16 @@ impl<'a> ReactJsx<'a> {
properties.push(ObjectPropertyKind::ObjectProperty(object_property));
}
if !properties.is_empty() || self.options.runtime.is_automatic() {
if !properties.is_empty() || is_automatic {
let object_expression = self.ast.object_expression(SPAN, properties, None);
arguments.push(Argument::Expression(object_expression));
}
if self.options.runtime.is_classic() && !children.is_empty() {
if is_automatic && key.is_some() {
arguments.push(Argument::Expression(self.transform_jsx_attribute_value(key)?));
}
if is_classic && !children.is_empty() {
arguments.extend(
children
.iter()
@ -373,6 +352,32 @@ impl<'a> ReactJsx<'a> {
}
}
fn transform_jsx_attribute_value(
&self,
value: Option<&JSXAttributeValue<'a>>,
) -> Option<Expression<'a>> {
match value {
Some(JSXAttributeValue::StringLiteral(s)) => {
Some(self.ast.literal_string_expression(s.clone()))
}
Some(JSXAttributeValue::Element(_) | JSXAttributeValue::Fragment(_)) => {
/* TODO */
None
}
Some(JSXAttributeValue::ExpressionContainer(c)) => {
match &c.expression {
JSXExpression::Expression(e) => Some(self.ast.copy(e)),
JSXExpression::EmptyExpression(_e) =>
/* TODO */
{
None
}
}
}
None => Some(self.ast.literal_boolean_expression(BooleanLiteral::new(SPAN, true))),
}
}
fn transform_jsx_member_expression(&self, expr: &JSXMemberExpression<'a>) -> Expression<'a> {
let object = match &expr.object {
JSXMemberExpressionObject::Identifier(ident) => {

View file

@ -1,4 +1,4 @@
Passed: 214/1083
Passed: 215/1083
# All Passed:
* babel-plugin-transform-numeric-separator
@ -804,7 +804,7 @@ Passed: 214/1083
* regression/11061/input.mjs
* variable-declaration/non-null-in-optional-chain/input.ts
# babel-plugin-transform-react-jsx (65/172)
# babel-plugin-transform-react-jsx (66/172)
* autoImport/after-polyfills/input.mjs
* autoImport/after-polyfills-2/input.mjs
* autoImport/after-polyfills-compiled-to-cjs/input.mjs
@ -895,7 +895,6 @@ Passed: 214/1083
* react-automatic/should-properly-handle-keys/input.js
* react-automatic/should-support-xml-namespaces-if-flag/input.js
* react-automatic/should-throw-when-filter-is-specified/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/weird-symbols/input.js
* regression/issue-12478-automatic/input.js