Skip to content

Commit a26fd34

Browse files
committed
refactor(ast)!: remove JSXOpeningElement::self_closing field (#10275)
Remove `self_closing` field from `JSXOpeningElement`. Whether the tag is self-closing can be determined by whether the `JSXElement` has a `closing_element` or not. This is advantageous for 2 reasons: 1. Reduce size of `JSXOpeningElement` by 8 bytes. 2. Make it impossible for `self_closing` and `closing_element` fields to get out of sync. Only one source of truth.
1 parent ecddaa0 commit a26fd34

File tree

14 files changed

+121
-108
lines changed

14 files changed

+121
-108
lines changed

crates/oxc_ast/src/ast/jsx.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,13 @@ pub struct JSXElement<'a> {
3737
/// Node location in source code
3838
pub span: Span,
3939
/// Opening tag of the element.
40+
#[estree(via = JSXElementOpening)]
4041
pub opening_element: Box<'a, JSXOpeningElement<'a>>,
41-
/// Closing tag of the element. Will be [`None`] for self-closing tags.
42+
/// Closing tag of the element.
43+
/// [`None`] for self-closing tags.
4244
pub closing_element: Option<Box<'a, JSXClosingElement<'a>>>,
43-
/// Children of the element. This can be text, other elements, or expressions.
45+
/// Children of the element.
46+
/// This can be text, other elements, or expressions.
4447
pub children: Vec<'a, JSXChild<'a>>,
4548
}
4649

@@ -62,18 +65,13 @@ pub struct JSXElement<'a> {
6265
#[ast(visit)]
6366
#[derive(Debug)]
6467
#[generate_derive(CloneIn, Dummy, TakeIn, GetSpan, GetSpanMut, ContentEq, ESTree)]
65-
#[estree(field_order(span, attributes, name, self_closing, type_arguments))]
68+
#[estree(
69+
add_fields(selfClosing = JSXOpeningElementSelfClosing),
70+
field_order(span, attributes, name, selfClosing, type_arguments),
71+
)]
6672
pub struct JSXOpeningElement<'a> {
6773
/// Node location in source code
6874
pub span: Span,
69-
/// Is this tag self-closing?
70-
///
71-
/// ## Examples
72-
/// ```tsx
73-
/// <Foo /> // <- self_closing = true
74-
/// <Foo> // <- self_closing = false
75-
/// ```
76-
pub self_closing: bool,
7775
/// The possibly-namespaced tag name, e.g. `Foo` in `<Foo />`.
7876
pub name: JSXElementName<'a>,
7977
/// List of JSX attributes. In React-like applications, these become props.

crates/oxc_ast/src/generated/assert_layouts.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -809,13 +809,12 @@ const _: () = {
809809
assert!(offset_of!(JSXElement, closing_element) == 16);
810810
assert!(offset_of!(JSXElement, children) == 24);
811811

812-
assert!(size_of::<JSXOpeningElement>() == 72);
812+
assert!(size_of::<JSXOpeningElement>() == 64);
813813
assert!(align_of::<JSXOpeningElement>() == 8);
814814
assert!(offset_of!(JSXOpeningElement, span) == 0);
815-
assert!(offset_of!(JSXOpeningElement, self_closing) == 8);
816-
assert!(offset_of!(JSXOpeningElement, name) == 16);
817-
assert!(offset_of!(JSXOpeningElement, attributes) == 32);
818-
assert!(offset_of!(JSXOpeningElement, type_arguments) == 64);
815+
assert!(offset_of!(JSXOpeningElement, name) == 8);
816+
assert!(offset_of!(JSXOpeningElement, attributes) == 24);
817+
assert!(offset_of!(JSXOpeningElement, type_arguments) == 56);
819818

820819
assert!(size_of::<JSXClosingElement>() == 24);
821820
assert!(align_of::<JSXClosingElement>() == 8);
@@ -2201,13 +2200,12 @@ const _: () = {
22012200
assert!(offset_of!(JSXElement, closing_element) == 12);
22022201
assert!(offset_of!(JSXElement, children) == 16);
22032202

2204-
assert!(size_of::<JSXOpeningElement>() == 40);
2203+
assert!(size_of::<JSXOpeningElement>() == 36);
22052204
assert!(align_of::<JSXOpeningElement>() == 4);
22062205
assert!(offset_of!(JSXOpeningElement, span) == 0);
2207-
assert!(offset_of!(JSXOpeningElement, self_closing) == 8);
2208-
assert!(offset_of!(JSXOpeningElement, name) == 12);
2209-
assert!(offset_of!(JSXOpeningElement, attributes) == 20);
2210-
assert!(offset_of!(JSXOpeningElement, type_arguments) == 36);
2206+
assert!(offset_of!(JSXOpeningElement, name) == 8);
2207+
assert!(offset_of!(JSXOpeningElement, attributes) == 16);
2208+
assert!(offset_of!(JSXOpeningElement, type_arguments) == 32);
22112209

22122210
assert!(size_of::<JSXClosingElement>() == 16);
22132211
assert!(align_of::<JSXClosingElement>() == 4);

crates/oxc_ast/src/generated/ast_builder.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,8 +1057,8 @@ impl<'a> AstBuilder<'a> {
10571057
/// ## Parameters
10581058
/// * `span`: Node location in source code
10591059
/// * `opening_element`: Opening tag of the element.
1060-
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
1061-
/// * `children`: Children of the element. This can be text, other elements, or expressions.
1060+
/// * `closing_element`: Closing tag of the element.
1061+
/// * `children`: Children of the element.
10621062
#[inline]
10631063
pub fn expression_jsx_element<T1, T2>(
10641064
self,
@@ -8623,8 +8623,8 @@ impl<'a> AstBuilder<'a> {
86238623
/// ## Parameters
86248624
/// * `span`: Node location in source code
86258625
/// * `opening_element`: Opening tag of the element.
8626-
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
8627-
/// * `children`: Children of the element. This can be text, other elements, or expressions.
8626+
/// * `closing_element`: Closing tag of the element.
8627+
/// * `children`: Children of the element.
86288628
#[inline]
86298629
pub fn jsx_element<T1, T2>(
86308630
self,
@@ -8653,8 +8653,8 @@ impl<'a> AstBuilder<'a> {
86538653
/// ## Parameters
86548654
/// * `span`: Node location in source code
86558655
/// * `opening_element`: Opening tag of the element.
8656-
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
8657-
/// * `children`: Children of the element. This can be text, other elements, or expressions.
8656+
/// * `closing_element`: Closing tag of the element.
8657+
/// * `children`: Children of the element.
86588658
#[inline]
86598659
pub fn alloc_jsx_element<T1, T2>(
86608660
self,
@@ -8680,15 +8680,13 @@ impl<'a> AstBuilder<'a> {
86808680
///
86818681
/// ## Parameters
86828682
/// * `span`: Node location in source code
8683-
/// * `self_closing`: Is this tag self-closing?
86848683
/// * `name`: The possibly-namespaced tag name, e.g. `Foo` in `<Foo />`.
86858684
/// * `attributes`: List of JSX attributes. In React-like applications, these become props.
86868685
/// * `type_arguments`: Type parameters for generic JSX elements.
86878686
#[inline]
86888687
pub fn jsx_opening_element<T1>(
86898688
self,
86908689
span: Span,
8691-
self_closing: bool,
86928690
name: JSXElementName<'a>,
86938691
attributes: Vec<'a, JSXAttributeItem<'a>>,
86948692
type_arguments: T1,
@@ -8698,7 +8696,6 @@ impl<'a> AstBuilder<'a> {
86988696
{
86998697
JSXOpeningElement {
87008698
span,
8701-
self_closing,
87028699
name,
87038700
attributes,
87048701
type_arguments: type_arguments.into_in(self.allocator),
@@ -8712,15 +8709,13 @@ impl<'a> AstBuilder<'a> {
87128709
///
87138710
/// ## Parameters
87148711
/// * `span`: Node location in source code
8715-
/// * `self_closing`: Is this tag self-closing?
87168712
/// * `name`: The possibly-namespaced tag name, e.g. `Foo` in `<Foo />`.
87178713
/// * `attributes`: List of JSX attributes. In React-like applications, these become props.
87188714
/// * `type_arguments`: Type parameters for generic JSX elements.
87198715
#[inline]
87208716
pub fn alloc_jsx_opening_element<T1>(
87218717
self,
87228718
span: Span,
8723-
self_closing: bool,
87248719
name: JSXElementName<'a>,
87258720
attributes: Vec<'a, JSXAttributeItem<'a>>,
87268721
type_arguments: T1,
@@ -8729,7 +8724,7 @@ impl<'a> AstBuilder<'a> {
87298724
T1: IntoIn<'a, Option<Box<'a, TSTypeParameterInstantiation<'a>>>>,
87308725
{
87318726
Box::new_in(
8732-
self.jsx_opening_element(span, self_closing, name, attributes, type_arguments),
8727+
self.jsx_opening_element(span, name, attributes, type_arguments),
87338728
self.allocator,
87348729
)
87358730
}
@@ -9350,8 +9345,8 @@ impl<'a> AstBuilder<'a> {
93509345
/// ## Parameters
93519346
/// * `span`: Node location in source code
93529347
/// * `opening_element`: Opening tag of the element.
9353-
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
9354-
/// * `children`: Children of the element. This can be text, other elements, or expressions.
9348+
/// * `closing_element`: Closing tag of the element.
9349+
/// * `children`: Children of the element.
93559350
#[inline]
93569351
pub fn jsx_attribute_value_element<T1, T2>(
93579352
self,
@@ -9452,8 +9447,8 @@ impl<'a> AstBuilder<'a> {
94529447
/// ## Parameters
94539448
/// * `span`: Node location in source code
94549449
/// * `opening_element`: Opening tag of the element.
9455-
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
9456-
/// * `children`: Children of the element. This can be text, other elements, or expressions.
9450+
/// * `closing_element`: Closing tag of the element.
9451+
/// * `children`: Children of the element.
94579452
#[inline]
94589453
pub fn jsx_child_element<T1, T2>(
94599454
self,

crates/oxc_ast/src/generated/derive_clone_in.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4969,7 +4969,6 @@ impl<'new_alloc> CloneIn<'new_alloc> for JSXOpeningElement<'_> {
49694969
fn clone_in(&self, allocator: &'new_alloc Allocator) -> Self::Cloned {
49704970
JSXOpeningElement {
49714971
span: CloneIn::clone_in(&self.span, allocator),
4972-
self_closing: CloneIn::clone_in(&self.self_closing, allocator),
49734972
name: CloneIn::clone_in(&self.name, allocator),
49744973
attributes: CloneIn::clone_in(&self.attributes, allocator),
49754974
type_arguments: CloneIn::clone_in(&self.type_arguments, allocator),
@@ -4979,7 +4978,6 @@ impl<'new_alloc> CloneIn<'new_alloc> for JSXOpeningElement<'_> {
49794978
fn clone_in_with_semantic_ids(&self, allocator: &'new_alloc Allocator) -> Self::Cloned {
49804979
JSXOpeningElement {
49814980
span: CloneIn::clone_in_with_semantic_ids(&self.span, allocator),
4982-
self_closing: CloneIn::clone_in_with_semantic_ids(&self.self_closing, allocator),
49834981
name: CloneIn::clone_in_with_semantic_ids(&self.name, allocator),
49844982
attributes: CloneIn::clone_in_with_semantic_ids(&self.attributes, allocator),
49854983
type_arguments: CloneIn::clone_in_with_semantic_ids(&self.type_arguments, allocator),

crates/oxc_ast/src/generated/derive_content_eq.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,8 +1505,7 @@ impl ContentEq for JSXElement<'_> {
15051505

15061506
impl ContentEq for JSXOpeningElement<'_> {
15071507
fn content_eq(&self, other: &Self) -> bool {
1508-
ContentEq::content_eq(&self.self_closing, &other.self_closing)
1509-
&& ContentEq::content_eq(&self.name, &other.name)
1508+
ContentEq::content_eq(&self.name, &other.name)
15101509
&& ContentEq::content_eq(&self.attributes, &other.attributes)
15111510
&& ContentEq::content_eq(&self.type_arguments, &other.type_arguments)
15121511
}

crates/oxc_ast/src/generated/derive_dummy.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,7 +1650,7 @@ impl<'a> Dummy<'a> for RegExpPattern<'a> {
16501650
impl<'a> Dummy<'a> for JSXElement<'a> {
16511651
/// Create a dummy [`JSXElement`].
16521652
///
1653-
/// Has cost of making 2 allocations (80 bytes).
1653+
/// Has cost of making 2 allocations (72 bytes).
16541654
fn dummy(allocator: &'a Allocator) -> Self {
16551655
Self {
16561656
span: Dummy::dummy(allocator),
@@ -1668,7 +1668,6 @@ impl<'a> Dummy<'a> for JSXOpeningElement<'a> {
16681668
fn dummy(allocator: &'a Allocator) -> Self {
16691669
Self {
16701670
span: Dummy::dummy(allocator),
1671-
self_closing: Dummy::dummy(allocator),
16721671
name: Dummy::dummy(allocator),
16731672
attributes: Dummy::dummy(allocator),
16741673
type_arguments: Dummy::dummy(allocator),

crates/oxc_ast/src/generated/derive_estree.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,7 +1982,7 @@ impl ESTree for JSXElement<'_> {
19821982
state.serialize_field("type", &JsonSafeString("JSXElement"));
19831983
state.serialize_field("start", &self.span.start);
19841984
state.serialize_field("end", &self.span.end);
1985-
state.serialize_field("openingElement", &self.opening_element);
1985+
state.serialize_field("openingElement", &crate::serialize::JSXElementOpening(self));
19861986
state.serialize_field("closingElement", &self.closing_element);
19871987
state.serialize_field("children", &self.children);
19881988
state.end();
@@ -1997,7 +1997,7 @@ impl ESTree for JSXOpeningElement<'_> {
19971997
state.serialize_field("end", &self.span.end);
19981998
state.serialize_field("attributes", &self.attributes);
19991999
state.serialize_field("name", &self.name);
2000-
state.serialize_field("selfClosing", &self.self_closing);
2000+
state.serialize_field("selfClosing", &crate::serialize::JSXOpeningElementSelfClosing(self));
20012001
state.serialize_ts_field("typeArguments", &self.type_arguments);
20022002
state.end();
20032003
}

crates/oxc_ast/src/serialize.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,52 @@ impl ESTree for ExportAllDeclarationWithClause<'_, '_> {
783783
// JSX
784784
// --------------------
785785

786+
/// Serializer for `opening_element` field of `JSXElement`.
787+
///
788+
/// `selfClosing` field of `JSXOpeningElement` depends on whether `JSXElement` has a `closing_element`.
789+
#[ast_meta]
790+
#[estree(
791+
ts_type = "JSXOpeningElement",
792+
raw_deser = "
793+
const openingElement = DESER[Box<JSXOpeningElement>](POS_OFFSET.opening_element);
794+
if (THIS.closingElement === null) openingElement.selfClosing = true;
795+
openingElement
796+
"
797+
)]
798+
pub struct JSXElementOpening<'a, 'b>(pub &'b JSXElement<'a>);
799+
800+
impl ESTree for JSXElementOpening<'_, '_> {
801+
fn serialize<S: Serializer>(&self, serializer: S) {
802+
let element = self.0;
803+
let opening_element = element.opening_element.as_ref();
804+
805+
let mut state = serializer.serialize_struct();
806+
state.serialize_field("type", &JsonSafeString("JSXOpeningElement"));
807+
state.serialize_field("start", &opening_element.span.start);
808+
state.serialize_field("end", &opening_element.span.end);
809+
state.serialize_field("attributes", &opening_element.attributes);
810+
state.serialize_field("name", &opening_element.name);
811+
state.serialize_field("selfClosing", &element.closing_element.is_none());
812+
state.serialize_ts_field("typeArguments", &opening_element.type_arguments);
813+
state.end();
814+
}
815+
}
816+
817+
/// Converter for `selfClosing` field of `JSXOpeningElement`.
818+
///
819+
/// This converter is not used for serialization - `JSXElementOpening` above handles serialization.
820+
/// This type is only required to add `selfClosing: boolean` to TS type def,
821+
/// and provide default value of `false` for raw transfer deserializer.
822+
#[ast_meta]
823+
#[estree(ts_type = "boolean", raw_deser = "false")]
824+
pub struct JSXOpeningElementSelfClosing<'a, 'b>(#[expect(dead_code)] pub &'b JSXOpeningElement<'a>);
825+
826+
impl ESTree for JSXOpeningElementSelfClosing<'_, '_> {
827+
fn serialize<S: Serializer>(&self, _serializer: S) {
828+
unreachable!()
829+
}
830+
}
831+
786832
/// Serializer for `IdentifierReference` variant of `JSXElementName` and `JSXMemberExpressionObject`.
787833
///
788834
/// Convert to `JSXIdentifier`.

crates/oxc_codegen/src/gen.rs

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,12 +2508,15 @@ impl Gen for JSXAttributeItem<'_> {
25082508
}
25092509
}
25102510

2511-
impl Gen for JSXOpeningElement<'_> {
2511+
impl Gen for JSXElement<'_> {
25122512
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
2513-
p.add_source_mapping(self.span);
2513+
// Opening element.
2514+
// Cannot `impl Gen for JSXOpeningElement` because it needs to know value of `self.closing_element`
2515+
// to determine whether to print a trailing `/`.
2516+
p.add_source_mapping(self.opening_element.span);
25142517
p.print_ascii_byte(b'<');
2515-
self.name.print(p, ctx);
2516-
for attr in &self.attributes {
2518+
self.opening_element.name.print(p, ctx);
2519+
for attr in &self.opening_element.attributes {
25172520
match attr {
25182521
JSXAttributeItem::Attribute(_) => {
25192522
p.print_hard_space();
@@ -2524,31 +2527,23 @@ impl Gen for JSXOpeningElement<'_> {
25242527
}
25252528
attr.print(p, ctx);
25262529
}
2527-
if self.self_closing {
2530+
if self.closing_element.is_none() {
25282531
p.print_soft_space();
25292532
p.print_str("/");
25302533
}
25312534
p.print_ascii_byte(b'>');
2532-
}
2533-
}
25342535

2535-
impl Gen for JSXClosingElement<'_> {
2536-
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
2537-
p.add_source_mapping(self.span);
2538-
p.print_str("</");
2539-
self.name.print(p, ctx);
2540-
p.print_ascii_byte(b'>');
2541-
}
2542-
}
2543-
2544-
impl Gen for JSXElement<'_> {
2545-
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
2546-
self.opening_element.print(p, ctx);
2536+
// Children
25472537
for child in &self.children {
25482538
child.print(p, ctx);
25492539
}
2540+
2541+
// Closing element
25502542
if let Some(closing_element) = &self.closing_element {
2551-
closing_element.print(p, ctx);
2543+
p.add_source_mapping(closing_element.span);
2544+
p.print_str("</");
2545+
closing_element.name.print(p, ctx);
2546+
p.print_ascii_byte(b'>');
25522547
}
25532548
}
25542549
}

crates/oxc_linter/src/rules/react/self_closing_comp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl Rule for SelfClosingComp {
8484
return;
8585
};
8686

87-
if jsx_el.opening_element.self_closing {
87+
if jsx_el.closing_element.is_none() {
8888
return;
8989
}
9090

0 commit comments

Comments
 (0)