Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions crates/oxc_ast/src/ast/jsx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ pub struct JSXElement<'a> {
/// Node location in source code
pub span: Span,
/// Opening tag of the element.
#[estree(via = JSXElementOpening)]
pub opening_element: Box<'a, JSXOpeningElement<'a>>,
/// Closing tag of the element. Will be [`None`] for self-closing tags.
/// Closing tag of the element.
/// [`None`] for self-closing tags.
pub closing_element: Option<Box<'a, JSXClosingElement<'a>>>,
/// Children of the element. This can be text, other elements, or expressions.
/// Children of the element.
/// This can be text, other elements, or expressions.
pub children: Vec<'a, JSXChild<'a>>,
}

Expand All @@ -62,18 +65,13 @@ pub struct JSXElement<'a> {
#[ast(visit)]
#[derive(Debug)]
#[generate_derive(CloneIn, Dummy, TakeIn, GetSpan, GetSpanMut, ContentEq, ESTree)]
#[estree(field_order(span, attributes, name, self_closing, type_arguments))]
#[estree(
add_fields(selfClosing = JSXOpeningElementSelfClosing),
field_order(span, attributes, name, selfClosing, type_arguments),
)]
pub struct JSXOpeningElement<'a> {
/// Node location in source code
pub span: Span,
/// Is this tag self-closing?
///
/// ## Examples
/// ```tsx
/// <Foo /> // <- self_closing = true
/// <Foo> // <- self_closing = false
/// ```
pub self_closing: bool,
/// The possibly-namespaced tag name, e.g. `Foo` in `<Foo />`.
pub name: JSXElementName<'a>,
/// List of JSX attributes. In React-like applications, these become props.
Expand Down
18 changes: 8 additions & 10 deletions crates/oxc_ast/src/generated/assert_layouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -809,13 +809,12 @@ const _: () = {
assert!(offset_of!(JSXElement, closing_element) == 16);
assert!(offset_of!(JSXElement, children) == 24);

assert!(size_of::<JSXOpeningElement>() == 72);
assert!(size_of::<JSXOpeningElement>() == 64);
assert!(align_of::<JSXOpeningElement>() == 8);
assert!(offset_of!(JSXOpeningElement, span) == 0);
assert!(offset_of!(JSXOpeningElement, self_closing) == 8);
assert!(offset_of!(JSXOpeningElement, name) == 16);
assert!(offset_of!(JSXOpeningElement, attributes) == 32);
assert!(offset_of!(JSXOpeningElement, type_arguments) == 64);
assert!(offset_of!(JSXOpeningElement, name) == 8);
assert!(offset_of!(JSXOpeningElement, attributes) == 24);
assert!(offset_of!(JSXOpeningElement, type_arguments) == 56);

assert!(size_of::<JSXClosingElement>() == 24);
assert!(align_of::<JSXClosingElement>() == 8);
Expand Down Expand Up @@ -2201,13 +2200,12 @@ const _: () = {
assert!(offset_of!(JSXElement, closing_element) == 12);
assert!(offset_of!(JSXElement, children) == 16);

assert!(size_of::<JSXOpeningElement>() == 40);
assert!(size_of::<JSXOpeningElement>() == 36);
assert!(align_of::<JSXOpeningElement>() == 4);
assert!(offset_of!(JSXOpeningElement, span) == 0);
assert!(offset_of!(JSXOpeningElement, self_closing) == 8);
assert!(offset_of!(JSXOpeningElement, name) == 12);
assert!(offset_of!(JSXOpeningElement, attributes) == 20);
assert!(offset_of!(JSXOpeningElement, type_arguments) == 36);
assert!(offset_of!(JSXOpeningElement, name) == 8);
assert!(offset_of!(JSXOpeningElement, attributes) == 16);
assert!(offset_of!(JSXOpeningElement, type_arguments) == 32);

assert!(size_of::<JSXClosingElement>() == 16);
assert!(align_of::<JSXClosingElement>() == 4);
Expand Down
27 changes: 11 additions & 16 deletions crates/oxc_ast/src/generated/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,8 +1057,8 @@ impl<'a> AstBuilder<'a> {
/// ## Parameters
/// * `span`: Node location in source code
/// * `opening_element`: Opening tag of the element.
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
/// * `children`: Children of the element. This can be text, other elements, or expressions.
/// * `closing_element`: Closing tag of the element.
/// * `children`: Children of the element.
#[inline]
pub fn expression_jsx_element<T1, T2>(
self,
Expand Down Expand Up @@ -8623,8 +8623,8 @@ impl<'a> AstBuilder<'a> {
/// ## Parameters
/// * `span`: Node location in source code
/// * `opening_element`: Opening tag of the element.
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
/// * `children`: Children of the element. This can be text, other elements, or expressions.
/// * `closing_element`: Closing tag of the element.
/// * `children`: Children of the element.
#[inline]
pub fn jsx_element<T1, T2>(
self,
Expand Down Expand Up @@ -8653,8 +8653,8 @@ impl<'a> AstBuilder<'a> {
/// ## Parameters
/// * `span`: Node location in source code
/// * `opening_element`: Opening tag of the element.
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
/// * `children`: Children of the element. This can be text, other elements, or expressions.
/// * `closing_element`: Closing tag of the element.
/// * `children`: Children of the element.
#[inline]
pub fn alloc_jsx_element<T1, T2>(
self,
Expand All @@ -8680,15 +8680,13 @@ impl<'a> AstBuilder<'a> {
///
/// ## Parameters
/// * `span`: Node location in source code
/// * `self_closing`: Is this tag self-closing?
/// * `name`: The possibly-namespaced tag name, e.g. `Foo` in `<Foo />`.
/// * `attributes`: List of JSX attributes. In React-like applications, these become props.
/// * `type_arguments`: Type parameters for generic JSX elements.
#[inline]
pub fn jsx_opening_element<T1>(
self,
span: Span,
self_closing: bool,
name: JSXElementName<'a>,
attributes: Vec<'a, JSXAttributeItem<'a>>,
type_arguments: T1,
Expand All @@ -8698,7 +8696,6 @@ impl<'a> AstBuilder<'a> {
{
JSXOpeningElement {
span,
self_closing,
name,
attributes,
type_arguments: type_arguments.into_in(self.allocator),
Expand All @@ -8712,15 +8709,13 @@ impl<'a> AstBuilder<'a> {
///
/// ## Parameters
/// * `span`: Node location in source code
/// * `self_closing`: Is this tag self-closing?
/// * `name`: The possibly-namespaced tag name, e.g. `Foo` in `<Foo />`.
/// * `attributes`: List of JSX attributes. In React-like applications, these become props.
/// * `type_arguments`: Type parameters for generic JSX elements.
#[inline]
pub fn alloc_jsx_opening_element<T1>(
self,
span: Span,
self_closing: bool,
name: JSXElementName<'a>,
attributes: Vec<'a, JSXAttributeItem<'a>>,
type_arguments: T1,
Expand All @@ -8729,7 +8724,7 @@ impl<'a> AstBuilder<'a> {
T1: IntoIn<'a, Option<Box<'a, TSTypeParameterInstantiation<'a>>>>,
{
Box::new_in(
self.jsx_opening_element(span, self_closing, name, attributes, type_arguments),
self.jsx_opening_element(span, name, attributes, type_arguments),
self.allocator,
)
}
Expand Down Expand Up @@ -9350,8 +9345,8 @@ impl<'a> AstBuilder<'a> {
/// ## Parameters
/// * `span`: Node location in source code
/// * `opening_element`: Opening tag of the element.
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
/// * `children`: Children of the element. This can be text, other elements, or expressions.
/// * `closing_element`: Closing tag of the element.
/// * `children`: Children of the element.
#[inline]
pub fn jsx_attribute_value_element<T1, T2>(
self,
Expand Down Expand Up @@ -9452,8 +9447,8 @@ impl<'a> AstBuilder<'a> {
/// ## Parameters
/// * `span`: Node location in source code
/// * `opening_element`: Opening tag of the element.
/// * `closing_element`: Closing tag of the element. Will be [`None`] for self-closing tags.
/// * `children`: Children of the element. This can be text, other elements, or expressions.
/// * `closing_element`: Closing tag of the element.
/// * `children`: Children of the element.
#[inline]
pub fn jsx_child_element<T1, T2>(
self,
Expand Down
2 changes: 0 additions & 2 deletions crates/oxc_ast/src/generated/derive_clone_in.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4969,7 +4969,6 @@ impl<'new_alloc> CloneIn<'new_alloc> for JSXOpeningElement<'_> {
fn clone_in(&self, allocator: &'new_alloc Allocator) -> Self::Cloned {
JSXOpeningElement {
span: CloneIn::clone_in(&self.span, allocator),
self_closing: CloneIn::clone_in(&self.self_closing, allocator),
name: CloneIn::clone_in(&self.name, allocator),
attributes: CloneIn::clone_in(&self.attributes, allocator),
type_arguments: CloneIn::clone_in(&self.type_arguments, allocator),
Expand All @@ -4979,7 +4978,6 @@ impl<'new_alloc> CloneIn<'new_alloc> for JSXOpeningElement<'_> {
fn clone_in_with_semantic_ids(&self, allocator: &'new_alloc Allocator) -> Self::Cloned {
JSXOpeningElement {
span: CloneIn::clone_in_with_semantic_ids(&self.span, allocator),
self_closing: CloneIn::clone_in_with_semantic_ids(&self.self_closing, allocator),
name: CloneIn::clone_in_with_semantic_ids(&self.name, allocator),
attributes: CloneIn::clone_in_with_semantic_ids(&self.attributes, allocator),
type_arguments: CloneIn::clone_in_with_semantic_ids(&self.type_arguments, allocator),
Expand Down
3 changes: 1 addition & 2 deletions crates/oxc_ast/src/generated/derive_content_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1505,8 +1505,7 @@ impl ContentEq for JSXElement<'_> {

impl ContentEq for JSXOpeningElement<'_> {
fn content_eq(&self, other: &Self) -> bool {
ContentEq::content_eq(&self.self_closing, &other.self_closing)
&& ContentEq::content_eq(&self.name, &other.name)
ContentEq::content_eq(&self.name, &other.name)
&& ContentEq::content_eq(&self.attributes, &other.attributes)
&& ContentEq::content_eq(&self.type_arguments, &other.type_arguments)
}
Expand Down
3 changes: 1 addition & 2 deletions crates/oxc_ast/src/generated/derive_dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1650,7 +1650,7 @@ impl<'a> Dummy<'a> for RegExpPattern<'a> {
impl<'a> Dummy<'a> for JSXElement<'a> {
/// Create a dummy [`JSXElement`].
///
/// Has cost of making 2 allocations (80 bytes).
/// Has cost of making 2 allocations (72 bytes).
fn dummy(allocator: &'a Allocator) -> Self {
Self {
span: Dummy::dummy(allocator),
Expand All @@ -1668,7 +1668,6 @@ impl<'a> Dummy<'a> for JSXOpeningElement<'a> {
fn dummy(allocator: &'a Allocator) -> Self {
Self {
span: Dummy::dummy(allocator),
self_closing: Dummy::dummy(allocator),
name: Dummy::dummy(allocator),
attributes: Dummy::dummy(allocator),
type_arguments: Dummy::dummy(allocator),
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_ast/src/generated/derive_estree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1982,7 +1982,7 @@ impl ESTree for JSXElement<'_> {
state.serialize_field("type", &JsonSafeString("JSXElement"));
state.serialize_field("start", &self.span.start);
state.serialize_field("end", &self.span.end);
state.serialize_field("openingElement", &self.opening_element);
state.serialize_field("openingElement", &crate::serialize::JSXElementOpening(self));
state.serialize_field("closingElement", &self.closing_element);
state.serialize_field("children", &self.children);
state.end();
Expand All @@ -1997,7 +1997,7 @@ impl ESTree for JSXOpeningElement<'_> {
state.serialize_field("end", &self.span.end);
state.serialize_field("attributes", &self.attributes);
state.serialize_field("name", &self.name);
state.serialize_field("selfClosing", &self.self_closing);
state.serialize_field("selfClosing", &crate::serialize::JSXOpeningElementSelfClosing(self));
state.serialize_ts_field("typeArguments", &self.type_arguments);
state.end();
}
Expand Down
46 changes: 46 additions & 0 deletions crates/oxc_ast/src/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,52 @@ impl ESTree for ExportAllDeclarationWithClause<'_, '_> {
// JSX
// --------------------

/// Serializer for `opening_element` field of `JSXElement`.
///
/// `selfClosing` field of `JSXOpeningElement` depends on whether `JSXElement` has a `closing_element`.
#[ast_meta]
#[estree(
ts_type = "JSXOpeningElement",
raw_deser = "
const openingElement = DESER[Box<JSXOpeningElement>](POS_OFFSET.opening_element);
if (THIS.closingElement === null) openingElement.selfClosing = true;
openingElement
"
)]
pub struct JSXElementOpening<'a, 'b>(pub &'b JSXElement<'a>);

impl ESTree for JSXElementOpening<'_, '_> {
fn serialize<S: Serializer>(&self, serializer: S) {
let element = self.0;
let opening_element = element.opening_element.as_ref();

let mut state = serializer.serialize_struct();
state.serialize_field("type", &JsonSafeString("JSXOpeningElement"));
state.serialize_field("start", &opening_element.span.start);
state.serialize_field("end", &opening_element.span.end);
state.serialize_field("attributes", &opening_element.attributes);
state.serialize_field("name", &opening_element.name);
state.serialize_field("selfClosing", &element.closing_element.is_none());
state.serialize_ts_field("typeArguments", &opening_element.type_arguments);
state.end();
}
}

/// Converter for `selfClosing` field of `JSXOpeningElement`.
///
/// This converter is not used for serialization - `JSXElementOpening` above handles serialization.
/// This type is only required to add `selfClosing: boolean` to TS type def,
/// and provide default value of `false` for raw transfer deserializer.
#[ast_meta]
#[estree(ts_type = "boolean", raw_deser = "false")]
pub struct JSXOpeningElementSelfClosing<'a, 'b>(#[expect(dead_code)] pub &'b JSXOpeningElement<'a>);

impl ESTree for JSXOpeningElementSelfClosing<'_, '_> {
fn serialize<S: Serializer>(&self, _serializer: S) {
unreachable!()
}
}

/// Serializer for `IdentifierReference` variant of `JSXElementName` and `JSXMemberExpressionObject`.
///
/// Convert to `JSXIdentifier`.
Expand Down
35 changes: 15 additions & 20 deletions crates/oxc_codegen/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2508,12 +2508,15 @@ impl Gen for JSXAttributeItem<'_> {
}
}

impl Gen for JSXOpeningElement<'_> {
impl Gen for JSXElement<'_> {
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
p.add_source_mapping(self.span);
// Opening element.
// Cannot `impl Gen for JSXOpeningElement` because it needs to know value of `self.closing_element`
// to determine whether to print a trailing `/`.
p.add_source_mapping(self.opening_element.span);
p.print_ascii_byte(b'<');
self.name.print(p, ctx);
for attr in &self.attributes {
self.opening_element.name.print(p, ctx);
for attr in &self.opening_element.attributes {
match attr {
JSXAttributeItem::Attribute(_) => {
p.print_hard_space();
Expand All @@ -2524,31 +2527,23 @@ impl Gen for JSXOpeningElement<'_> {
}
attr.print(p, ctx);
}
if self.self_closing {
if self.closing_element.is_none() {
p.print_soft_space();
p.print_str("/");
}
p.print_ascii_byte(b'>');
}
}

impl Gen for JSXClosingElement<'_> {
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
p.add_source_mapping(self.span);
p.print_str("</");
self.name.print(p, ctx);
p.print_ascii_byte(b'>');
}
}

impl Gen for JSXElement<'_> {
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
self.opening_element.print(p, ctx);
// Children
for child in &self.children {
child.print(p, ctx);
}

// Closing element
if let Some(closing_element) = &self.closing_element {
closing_element.print(p, ctx);
p.add_source_mapping(closing_element.span);
p.print_str("</");
closing_element.name.print(p, ctx);
p.print_ascii_byte(b'>');
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/react/self_closing_comp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl Rule for SelfClosingComp {
return;
};

if jsx_el.opening_element.self_closing {
if jsx_el.closing_element.is_none() {
return;
}

Expand Down
Loading
Loading