feat(ast)!: add ThisExpression variants to JSXElementName and JSXMemberExpressionObject (#5466)

Close #5352.

Add to AST:

* `JSXElementName::ThisExpression` (`<this>`)
* `JSXMemberExpressionObject::ThisExpression` (`<this.foo>`, `<this.foo.bar>`)
This commit is contained in:
overlookmotel 2024-09-05 02:06:20 +00:00
parent 82c0a167c4
commit cba93f52d0
23 changed files with 117 additions and 128 deletions

View file

@ -173,6 +173,8 @@ pub enum JSXElementName<'a> {
NamespacedName(Box<'a, JSXNamespacedName<'a>>) = 2,
/// `<Apple.Orange />`
MemberExpression(Box<'a, JSXMemberExpression<'a>>) = 3,
/// `<this />`
ThisExpression(Box<'a, ThisExpression>) = 4,
}
/// JSX Namespaced Name
@ -233,6 +235,7 @@ pub struct JSXMemberExpression<'a> {
pub enum JSXMemberExpressionObject<'a> {
IdentifierReference(Box<'a, IdentifierReference<'a>>) = 0,
MemberExpression(Box<'a, JSXMemberExpression<'a>>) = 1,
ThisExpression(Box<'a, ThisExpression>) = 2,
}
/// JSX Expression Container

View file

@ -46,6 +46,7 @@ impl<'a> fmt::Display for JSXMemberExpressionObject<'a> {
match self {
Self::IdentifierReference(id) => id.fmt(f),
Self::MemberExpression(expr) => expr.fmt(f),
Self::ThisExpression(_) => "this".fmt(f),
}
}
}
@ -57,6 +58,7 @@ impl<'a> fmt::Display for JSXElementName<'a> {
Self::IdentifierReference(ident) => ident.fmt(f),
Self::NamespacedName(namespaced) => namespaced.fmt(f),
Self::MemberExpression(member_expr) => member_expr.fmt(f),
Self::ThisExpression(_) => "this".fmt(f),
}
}
}

View file

@ -12903,6 +12903,26 @@ impl<'a> AstBuilder<'a> {
JSXElementName::MemberExpression(inner.into_in(self.allocator))
}
/// Build a [`JSXElementName::ThisExpression`]
///
/// This node contains a [`ThisExpression`] that will be stored in the memory arena.
///
/// ## Parameters
/// - span: The [`Span`] covering this node
#[inline]
pub fn jsx_element_name_this_expression(self, span: Span) -> JSXElementName<'a> {
JSXElementName::ThisExpression(self.alloc(self.this_expression(span)))
}
/// Convert a [`ThisExpression`] into a [`JSXElementName::ThisExpression`]
#[inline]
pub fn jsx_element_name_from_this_expression<T>(self, inner: T) -> JSXElementName<'a>
where
T: IntoIn<'a, Box<'a, ThisExpression>>,
{
JSXElementName::ThisExpression(inner.into_in(self.allocator))
}
/// Builds a [`JSXNamespacedName`]
///
/// If you want the built node to be allocated in the memory arena, use [`AstBuilder::alloc_jsx_namespaced_name`] instead.
@ -13040,6 +13060,32 @@ impl<'a> AstBuilder<'a> {
JSXMemberExpressionObject::MemberExpression(inner.into_in(self.allocator))
}
/// Build a [`JSXMemberExpressionObject::ThisExpression`]
///
/// This node contains a [`ThisExpression`] that will be stored in the memory arena.
///
/// ## Parameters
/// - span: The [`Span`] covering this node
#[inline]
pub fn jsx_member_expression_object_this_expression(
self,
span: Span,
) -> JSXMemberExpressionObject<'a> {
JSXMemberExpressionObject::ThisExpression(self.alloc(self.this_expression(span)))
}
/// Convert a [`ThisExpression`] into a [`JSXMemberExpressionObject::ThisExpression`]
#[inline]
pub fn jsx_member_expression_object_from_this_expression<T>(
self,
inner: T,
) -> JSXMemberExpressionObject<'a>
where
T: IntoIn<'a, Box<'a, ThisExpression>>,
{
JSXMemberExpressionObject::ThisExpression(inner.into_in(self.allocator))
}
/// Builds a [`JSXExpressionContainer`]
///
/// If you want the built node to be allocated in the memory arena, use [`AstBuilder::alloc_jsx_expression_container`] instead.

View file

@ -3507,6 +3507,7 @@ impl<'old_alloc, 'new_alloc> CloneIn<'new_alloc> for JSXElementName<'old_alloc>
}
Self::NamespacedName(it) => JSXElementName::NamespacedName(it.clone_in(allocator)),
Self::MemberExpression(it) => JSXElementName::MemberExpression(it.clone_in(allocator)),
Self::ThisExpression(it) => JSXElementName::ThisExpression(it.clone_in(allocator)),
}
}
}
@ -3543,6 +3544,9 @@ impl<'old_alloc, 'new_alloc> CloneIn<'new_alloc> for JSXMemberExpressionObject<'
Self::MemberExpression(it) => {
JSXMemberExpressionObject::MemberExpression(it.clone_in(allocator))
}
Self::ThisExpression(it) => {
JSXMemberExpressionObject::ThisExpression(it.clone_in(allocator))
}
}
}
}

View file

@ -3555,6 +3555,9 @@ impl<'a> ContentEq for JSXElementName<'a> {
Self::MemberExpression(it) => {
matches!(other, Self::MemberExpression(other) if it.content_eq(other))
}
Self::ThisExpression(it) => {
matches!(other, Self::ThisExpression(other) if it.content_eq(other))
}
}
}
}
@ -3580,6 +3583,9 @@ impl<'a> ContentEq for JSXMemberExpressionObject<'a> {
Self::MemberExpression(it) => {
matches!(other, Self::MemberExpression(other) if it.content_eq(other))
}
Self::ThisExpression(it) => {
matches!(other, Self::ThisExpression(other) if it.content_eq(other))
}
}
}
}

View file

@ -2042,6 +2042,7 @@ impl<'a> GetSpan for JSXElementName<'a> {
Self::IdentifierReference(it) => it.span(),
Self::NamespacedName(it) => it.span(),
Self::MemberExpression(it) => it.span(),
Self::ThisExpression(it) => it.span(),
}
}
}
@ -2065,6 +2066,7 @@ impl<'a> GetSpan for JSXMemberExpressionObject<'a> {
match self {
Self::IdentifierReference(it) => it.span(),
Self::MemberExpression(it) => it.span(),
Self::ThisExpression(it) => it.span(),
}
}
}

View file

@ -2042,6 +2042,7 @@ impl<'a> GetSpanMut for JSXElementName<'a> {
Self::IdentifierReference(it) => it.span_mut(),
Self::NamespacedName(it) => it.span_mut(),
Self::MemberExpression(it) => it.span_mut(),
Self::ThisExpression(it) => it.span_mut(),
}
}
}
@ -2065,6 +2066,7 @@ impl<'a> GetSpanMut for JSXMemberExpressionObject<'a> {
match self {
Self::IdentifierReference(it) => it.span_mut(),
Self::MemberExpression(it) => it.span_mut(),
Self::ThisExpression(it) => it.span_mut(),
}
}
}

View file

@ -3300,6 +3300,7 @@ pub mod walk {
JSXElementName::IdentifierReference(it) => visitor.visit_identifier_reference(it),
JSXElementName::NamespacedName(it) => visitor.visit_jsx_namespaced_name(it),
JSXElementName::MemberExpression(it) => visitor.visit_jsx_member_expression(it),
JSXElementName::ThisExpression(it) => visitor.visit_this_expression(it),
}
visitor.leave_node(kind);
}
@ -3346,6 +3347,7 @@ pub mod walk {
JSXMemberExpressionObject::MemberExpression(it) => {
visitor.visit_jsx_member_expression(it)
}
JSXMemberExpressionObject::ThisExpression(it) => visitor.visit_this_expression(it),
}
visitor.leave_node(kind);
}

View file

@ -3464,6 +3464,7 @@ pub mod walk_mut {
JSXElementName::IdentifierReference(it) => visitor.visit_identifier_reference(it),
JSXElementName::NamespacedName(it) => visitor.visit_jsx_namespaced_name(it),
JSXElementName::MemberExpression(it) => visitor.visit_jsx_member_expression(it),
JSXElementName::ThisExpression(it) => visitor.visit_this_expression(it),
}
visitor.leave_node(kind);
}
@ -3513,6 +3514,7 @@ pub mod walk_mut {
JSXMemberExpressionObject::MemberExpression(it) => {
visitor.visit_jsx_member_expression(it)
}
JSXMemberExpressionObject::ThisExpression(it) => visitor.visit_this_expression(it),
}
visitor.leave_node(kind);
}

View file

@ -2249,6 +2249,7 @@ impl<'a> Gen for JSXMemberExpressionObject<'a> {
match self {
Self::IdentifierReference(ident) => ident.gen(p, ctx),
Self::MemberExpression(member_expr) => member_expr.gen(p, ctx),
Self::ThisExpression(expr) => expr.gen(p, ctx),
}
}
}
@ -2268,6 +2269,7 @@ impl<'a> Gen for JSXElementName<'a> {
Self::IdentifierReference(identifier) => identifier.gen(p, ctx),
Self::NamespacedName(namespaced_name) => namespaced_name.gen(p, ctx),
Self::MemberExpression(member_expr) => member_expr.gen(p, ctx),
Self::ThisExpression(expr) => expr.gen(p, ctx),
}
}
}

View file

@ -35,16 +35,19 @@ declare_oxc_lint!(
fn get_resolvable_ident<'a>(node: &'a JSXElementName<'a>) -> Option<&'a IdentifierReference> {
match node {
JSXElementName::Identifier(_) | JSXElementName::NamespacedName(_) => None,
JSXElementName::Identifier(_)
| JSXElementName::NamespacedName(_)
| JSXElementName::ThisExpression(_) => None,
JSXElementName::IdentifierReference(ref ident) => Some(ident),
JSXElementName::MemberExpression(expr) => Some(get_member_ident(expr)),
JSXElementName::MemberExpression(expr) => get_member_ident(expr),
}
}
fn get_member_ident<'a>(mut expr: &'a JSXMemberExpression<'a>) -> &'a IdentifierReference {
fn get_member_ident<'a>(mut expr: &'a JSXMemberExpression<'a>) -> Option<&'a IdentifierReference> {
loop {
match &expr.object {
JSXMemberExpressionObject::IdentifierReference(ident) => return ident,
JSXMemberExpressionObject::IdentifierReference(ident) => return Some(ident),
JSXMemberExpressionObject::ThisExpression(_) => return None,
JSXMemberExpressionObject::MemberExpression(next_expr) => {
expr = next_expr;
}
@ -61,10 +64,6 @@ impl Rule for JsxNoUndef {
return;
}
let name = ident.name.as_str();
// TODO: Remove this check once we have `JSXMemberExpressionObject::ThisExpression`
if name == "this" {
return;
}
if ctx.globals().is_enabled(name) {
return;
}

View file

@ -172,7 +172,9 @@ fn is_jsx_fragment(elem: &JSXOpeningElement) -> bool {
false
}
}
JSXElementName::NamespacedName(_) | JSXElementName::Identifier(_) => false,
JSXElementName::NamespacedName(_)
| JSXElementName::Identifier(_)
| JSXElementName::ThisExpression(_) => false,
}
}

View file

@ -791,6 +791,7 @@ impl<'a> ListenerMap for JSXElementName<'a> {
Self::Identifier(_) | Self::NamespacedName(_) => {}
Self::IdentifierReference(ident) => ident.report_effects_when_called(options),
Self::MemberExpression(member) => member.report_effects_when_called(options),
Self::ThisExpression(expr) => expr.report_effects_when_called(options),
}
}
}
@ -806,6 +807,7 @@ impl<'a> ListenerMap for JSXMemberExpressionObject<'a> {
match self {
Self::IdentifierReference(ident) => ident.report_effects_when_called(options),
Self::MemberExpression(member) => member.report_effects_when_called(options),
Self::ThisExpression(expr) => expr.report_effects_when_called(options),
}
}
}

View file

@ -171,6 +171,10 @@ impl<'a> ParserImpl<'a> {
let element_name = if is_reference {
let identifier = self.ast.identifier_reference(identifier.span, identifier.name);
JSXElementName::IdentifierReference(self.ast.alloc(identifier))
} else if name == "this" {
JSXElementName::ThisExpression(
self.ast.alloc(self.ast.this_expression(identifier.span)),
)
} else {
JSXElementName::Identifier(self.ast.alloc(identifier))
};
@ -185,9 +189,15 @@ impl<'a> ParserImpl<'a> {
span: Span,
object: JSXIdentifier<'a>,
) -> Result<Box<'a, JSXMemberExpression<'a>>> {
let mut object = if object.name == "this" {
let object = self.ast.this_expression(object.span);
JSXMemberExpressionObject::ThisExpression(self.ast.alloc(object))
} else {
let object = self.ast.identifier_reference(object.span, object.name);
JSXMemberExpressionObject::IdentifierReference(self.ast.alloc(object))
};
let mut span = span;
let object = self.ast.identifier_reference(object.span, object.name);
let mut object = JSXMemberExpressionObject::IdentifierReference(self.ast.alloc(object));
let mut property = None;
while self.eat(Kind::Dot) && !self.at(Kind::Eof) {
@ -434,6 +444,7 @@ impl<'a> ParserImpl<'a> {
(JSXElementName::MemberExpression(lhs), JSXElementName::MemberExpression(rhs)) => {
Self::jsx_member_expression_eq(lhs, rhs)
}
(JSXElementName::ThisExpression(_), JSXElementName::ThisExpression(_)) => true,
_ => false,
}
}
@ -454,6 +465,10 @@ impl<'a> ParserImpl<'a> {
JSXMemberExpressionObject::MemberExpression(lhs),
JSXMemberExpressionObject::MemberExpression(rhs),
) => Self::jsx_member_expression_eq(lhs, rhs),
(
JSXMemberExpressionObject::ThisExpression(_),
JSXMemberExpressionObject::ThisExpression(_),
) => true,
_ => false,
}
}

View file

@ -131,40 +131,6 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> {
self.insert_this_var_statement_at_the_top_of_statements(&mut body.statements);
}
/// Change <this></this> to <_this></_this>, and mark it as found
fn enter_jsx_element_name(&mut self, name: &mut JSXElementName<'a>, ctx: &mut TraverseCtx<'a>) {
if !self.is_inside_arrow_function() {
return;
}
if let JSXElementName::Identifier(ident) = name {
if ident.name == "this" {
let mut new_ident = self.get_this_name(ctx).create_read_reference(ctx);
new_ident.span = ident.span;
*name = self.ctx.ast.jsx_element_name_from_identifier_reference(new_ident);
}
}
}
/// Change <this.foo></this.foo> to <_this.foo></_this.foo>, and mark it as found
fn enter_jsx_member_expression_object(
&mut self,
node: &mut JSXMemberExpressionObject<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if !self.is_inside_arrow_function() {
return;
}
if let JSXMemberExpressionObject::IdentifierReference(ident) = node {
if ident.name == "this" {
let new_ident = self.get_this_name(ctx).create_read_reference(ctx);
ident.name = new_ident.name;
ident.reference_id = new_ident.reference_id;
}
}
}
fn enter_expression(&mut self, expr: &mut Expression<'a>, _ctx: &mut TraverseCtx<'a>) {
match expr {
Expression::ArrowFunctionExpression(_) => {

View file

@ -51,22 +51,6 @@ impl<'a> Traverse<'a> for ES2015<'a> {
}
}
fn enter_jsx_element_name(&mut self, elem: &mut JSXElementName<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.enter_jsx_element_name(elem, ctx);
}
}
fn enter_jsx_member_expression_object(
&mut self,
node: &mut JSXMemberExpressionObject<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.arrow_function.is_some() {
self.arrow_functions.enter_jsx_member_expression_object(node, ctx);
}
}
fn enter_declaration(&mut self, decl: &mut Declaration<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.enter_declaration(decl, ctx);

View file

@ -235,18 +235,6 @@ impl<'a> Traverse<'a> for Transformer<'a> {
self.x1_react.transform_jsx_opening_element(elem, ctx);
}
fn enter_jsx_element_name(&mut self, elem: &mut JSXElementName<'a>, ctx: &mut TraverseCtx<'a>) {
self.x3_es2015.enter_jsx_element_name(elem, ctx);
}
fn enter_jsx_member_expression_object(
&mut self,
node: &mut JSXMemberExpressionObject<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x3_es2015.enter_jsx_member_expression_object(node, ctx);
}
fn enter_method_definition(
&mut self,
def: &mut MethodDefinition<'a>,

View file

@ -700,11 +700,7 @@ impl<'a> ReactJsx<'a> {
fn transform_element_name(&self, name: &JSXElementName<'a>) -> Expression<'a> {
match name {
JSXElementName::Identifier(ident) => {
if ident.name == "this" {
self.ast().expression_this(ident.span)
} else {
self.ast().expression_string_literal(ident.span, &ident.name)
}
self.ast().expression_string_literal(ident.span, ident.name.clone())
}
JSXElementName::IdentifierReference(ident) => {
self.ast().expression_from_identifier_reference(ident.as_ref().clone())
@ -718,6 +714,7 @@ impl<'a> ReactJsx<'a> {
}
self.ast().expression_string_literal(namespaced.span, namespaced.to_string())
}
JSXElementName::ThisExpression(expr) => self.ast().expression_this(expr.span),
}
}
@ -775,15 +772,14 @@ impl<'a> ReactJsx<'a> {
fn transform_jsx_member_expression(&self, expr: &JSXMemberExpression<'a>) -> Expression<'a> {
let object = match &expr.object {
JSXMemberExpressionObject::IdentifierReference(ident) => {
if ident.name == "this" {
self.ast().expression_this(ident.span)
} else {
self.ast().expression_from_identifier_reference(ident.as_ref().clone())
}
self.ast().expression_from_identifier_reference(ident.as_ref().clone())
}
JSXMemberExpressionObject::MemberExpression(expr) => {
self.transform_jsx_member_expression(expr)
}
JSXMemberExpressionObject::ThisExpression(expr) => {
self.ast().expression_this(expr.span)
}
};
let property = IdentifierName::new(expr.property.span, expr.property.name.clone());
self.ast().member_expression_static(expr.span, object, property, false).into()

View file

@ -464,6 +464,7 @@ impl<'a> GatherNodeParts<'a> for JSXElementName<'a> {
JSXElementName::IdentifierReference(ident) => ident.gather(f),
JSXElementName::NamespacedName(ns) => ns.gather(f),
JSXElementName::MemberExpression(expr) => expr.gather(f),
JSXElementName::ThisExpression(expr) => expr.gather(f),
}
}
}
@ -487,6 +488,7 @@ impl<'a> GatherNodeParts<'a> for JSXMemberExpressionObject<'a> {
match self {
JSXMemberExpressionObject::IdentifierReference(ident) => ident.gather(f),
JSXMemberExpressionObject::MemberExpression(expr) => expr.gather(f),
JSXMemberExpressionObject::ThisExpression(expr) => expr.gather(f),
}
}
}

View file

@ -3268,6 +3268,9 @@ pub(crate) unsafe fn walk_jsx_element_name<'a, Tr: Traverse<'a>>(
JSXElementName::MemberExpression(node) => {
walk_jsx_member_expression(traverser, (&mut **node) as *mut _, ctx)
}
JSXElementName::ThisExpression(node) => {
walk_this_expression(traverser, (&mut **node) as *mut _, ctx)
}
}
traverser.exit_jsx_element_name(&mut *node, ctx);
}
@ -3335,6 +3338,9 @@ pub(crate) unsafe fn walk_jsx_member_expression_object<'a, Tr: Traverse<'a>>(
JSXMemberExpressionObject::MemberExpression(node) => {
walk_jsx_member_expression(traverser, (&mut **node) as *mut _, ctx)
}
JSXMemberExpressionObject::ThisExpression(node) => {
walk_this_expression(traverser, (&mut **node) as *mut _, ctx)
}
}
traverser.exit_jsx_member_expression_object(&mut *node, ctx);
}

View file

@ -2,7 +2,7 @@ commit: a709f989
semantic_typescript Summary:
AST Parsed : 6479/6479 (100.00%)
Positive Passed: 3256/6479 (50.25%)
Positive Passed: 3259/6479 (50.30%)
tasks/coverage/typescript/tests/cases/compiler/2dArrays.ts
semantic error: Symbol reference IDs mismatch:
after transform: SymbolId(0): [ReferenceId(1)]
@ -40687,11 +40687,6 @@ Symbol reference IDs mismatch:
after transform: SymbolId(3): [ReferenceId(0), ReferenceId(1)]
rebuilt : SymbolId(2): [ReferenceId(2)]
tasks/coverage/typescript/tests/cases/conformance/jsx/tsxDynamicTagName5.tsx
semantic error: Unresolved references mismatch:
after transform: ["this"]
rebuilt : []
tasks/coverage/typescript/tests/cases/conformance/jsx/tsxDynamicTagName6.tsx
semantic error: Bindings mismatch:
after transform: ScopeId(0): ["JSX", "_jsxFileName", "_reactJsxRuntime", "foo", "t"]
@ -40700,16 +40695,6 @@ Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1)]
rebuilt : ScopeId(0): []
tasks/coverage/typescript/tests/cases/conformance/jsx/tsxDynamicTagName8.tsx
semantic error: Unresolved references mismatch:
after transform: ["this"]
rebuilt : []
tasks/coverage/typescript/tests/cases/conformance/jsx/tsxDynamicTagName9.tsx
semantic error: Unresolved references mismatch:
after transform: ["this"]
rebuilt : []
tasks/coverage/typescript/tests/cases/conformance/jsx/tsxElementResolution.tsx
semantic error: Missing SymbolId: Dotted
Missing SymbolId: _Dotted

View file

@ -1,6 +1,6 @@
commit: 3bcfee23
Passed: 317/1021
Passed: 321/1021
# All Passed:
* babel-plugin-transform-optional-catch-binding
@ -4906,13 +4906,7 @@ TS(18010)
# babel-plugin-transform-react-jsx (119/144)
* react/arrow-functions/input.js
x Unresolved references mismatch:
| after transform: ["React", "this"]
| rebuilt : ["React"]
# babel-plugin-transform-react-jsx (123/144)
* react/dont-coerce-expression-containers/input.js
x Unresolved reference IDs mismatch for "Text":
| after transform: [ReferenceId(0), ReferenceId(1)]
@ -4980,24 +4974,12 @@ TS(18010)
`----
* react/this-tag-name/input.js
x Unresolved references mismatch:
| after transform: ["React", "this"]
| rebuilt : ["React"]
* react/weird-symbols/input.js
x Unresolved reference IDs mismatch for "Text":
| after transform: [ReferenceId(1), ReferenceId(2)]
| rebuilt : [ReferenceId(2)]
* react-automatic/arrow-functions/input.js
x Unresolved references mismatch:
| after transform: ["this"]
| rebuilt : []
* react-automatic/does-not-add-source-self-automatic/input.mjs
transform-react-jsx: unknown field `autoImport`, expected one of `runtime`, `development`, `throwIfNamespace`, `pure`, `importSource`, `pragma`, `pragmaFrag`, `useBuiltIns`, `useSpread`, `refresh`
@ -5056,12 +5038,6 @@ transform-react-jsx: unknown field `autoImport`, expected one of `runtime`, `dev
`----
* react-automatic/this-tag-name/input.js
x Unresolved references mismatch:
| after transform: ["this"]
| rebuilt : []
* react-automatic/weird-symbols/input.js
x Unresolved reference IDs mismatch for "Text":
| after transform: [ReferenceId(1), ReferenceId(2)]

View file

@ -10,9 +10,6 @@ Passed: 13/41
# babel-plugin-transform-arrow-functions (1/2)
* with-this-member-expression/input.jsx
x Unresolved references mismatch:
| after transform: ["this"]
| rebuilt : []