mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(transformer): implement key extraction for react automatic (#1077)
This commit is contained in:
parent
394ed358f6
commit
5fb27fbe8a
3 changed files with 52 additions and 42 deletions
|
|
@ -157,6 +157,12 @@ pub struct JSXAttribute<'a> {
|
||||||
pub value: Option<JSXAttributeValue<'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
|
/// JSX Spread Attribute
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
|
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
|
||||||
|
|
|
||||||
|
|
@ -53,14 +53,8 @@ impl<'a, 'b> JSXElementOrFragment<'a, 'b> {
|
||||||
for attr in &e.opening_element.attributes {
|
for attr in &e.opening_element.attributes {
|
||||||
if matches!(attr, JSXAttributeItem::SpreadAttribute(_)) {
|
if matches!(attr, JSXAttributeItem::SpreadAttribute(_)) {
|
||||||
spread = true;
|
spread = true;
|
||||||
} else if spread {
|
} else if spread && matches!(attr, JSXAttributeItem::Attribute(a) if a.is_key()) {
|
||||||
if let JSXAttributeItem::Attribute(a) = attr {
|
return true;
|
||||||
if let JSXAttributeName::Identifier(ident) = &a.name {
|
|
||||||
if ident.name == "key" {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
|
@ -165,6 +159,8 @@ impl<'a> ReactJsx<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_jsx<'b>(&mut self, e: &JSXElementOrFragment<'a, 'b>) -> Option<Expression<'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 has_key_after_props_spread = e.has_key_after_props_spread();
|
||||||
let callee = self.get_create_element(has_key_after_props_spread);
|
let callee = self.get_create_element(has_key_after_props_spread);
|
||||||
let children = e.children();
|
let children = e.children();
|
||||||
|
|
@ -179,6 +175,7 @@ impl<'a> ReactJsx<'a> {
|
||||||
JSXElementOrFragment::Fragment(_) => self.get_fragment(),
|
JSXElementOrFragment::Fragment(_) => self.get_fragment(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let mut key = None;
|
||||||
// TODO: compute the correct capacity for both runtimes
|
// TODO: compute the correct capacity for both runtimes
|
||||||
let mut properties = self.ast.new_vec_with_capacity(0);
|
let mut properties = self.ast.new_vec_with_capacity(0);
|
||||||
if let Some(attributes) = e.attributes() {
|
if let Some(attributes) = e.attributes() {
|
||||||
|
|
@ -186,34 +183,12 @@ impl<'a> ReactJsx<'a> {
|
||||||
let kind = PropertyKind::Init;
|
let kind = PropertyKind::Init;
|
||||||
match attribute {
|
match attribute {
|
||||||
JSXAttributeItem::Attribute(attr) => {
|
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 key = self.get_attribute_name(&attr.name);
|
||||||
let value = match &attr.value {
|
let value = self.transform_jsx_attribute_value(attr.value.as_ref())?;
|
||||||
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 object_property = self
|
let object_property = self
|
||||||
.ast
|
.ast
|
||||||
.object_property(SPAN, kind, key, value, None, false, false, false);
|
.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));
|
let null_expr = self.ast.literal_null_expression(NullLiteral::new(SPAN));
|
||||||
arguments.push(Argument::Expression(null_expr));
|
arguments.push(Argument::Expression(null_expr));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.options.runtime.is_automatic() && !children.is_empty() {
|
if is_automatic && !children.is_empty() {
|
||||||
let key =
|
let key =
|
||||||
self.ast.property_key_identifier(IdentifierName::new(SPAN, "children".into()));
|
self.ast.property_key_identifier(IdentifierName::new(SPAN, "children".into()));
|
||||||
let value = if children.len() == 1 {
|
let value = if children.len() == 1 {
|
||||||
|
|
@ -268,12 +243,16 @@ impl<'a> ReactJsx<'a> {
|
||||||
properties.push(ObjectPropertyKind::ObjectProperty(object_property));
|
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);
|
let object_expression = self.ast.object_expression(SPAN, properties, None);
|
||||||
arguments.push(Argument::Expression(object_expression));
|
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(
|
arguments.extend(
|
||||||
children
|
children
|
||||||
.iter()
|
.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> {
|
fn transform_jsx_member_expression(&self, expr: &JSXMemberExpression<'a>) -> Expression<'a> {
|
||||||
let object = match &expr.object {
|
let object = match &expr.object {
|
||||||
JSXMemberExpressionObject::Identifier(ident) => {
|
JSXMemberExpressionObject::Identifier(ident) => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Passed: 214/1083
|
Passed: 215/1083
|
||||||
|
|
||||||
# All Passed:
|
# All Passed:
|
||||||
* babel-plugin-transform-numeric-separator
|
* babel-plugin-transform-numeric-separator
|
||||||
|
|
@ -804,7 +804,7 @@ Passed: 214/1083
|
||||||
* regression/11061/input.mjs
|
* regression/11061/input.mjs
|
||||||
* variable-declaration/non-null-in-optional-chain/input.ts
|
* 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/input.mjs
|
||||||
* autoImport/after-polyfills-2/input.mjs
|
* autoImport/after-polyfills-2/input.mjs
|
||||||
* autoImport/after-polyfills-compiled-to-cjs/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-properly-handle-keys/input.js
|
||||||
* react-automatic/should-support-xml-namespaces-if-flag/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-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/should-warn-when-pragma-or-pragmaFrag-is-set/input.js
|
||||||
* react-automatic/weird-symbols/input.js
|
* react-automatic/weird-symbols/input.js
|
||||||
* regression/issue-12478-automatic/input.js
|
* regression/issue-12478-automatic/input.js
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue