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
22 changes: 12 additions & 10 deletions crates/oxc_formatter/src/print/jsx/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,33 +229,35 @@ impl<'a> Format<'a> for AnyJsxTagWithChildren<'a, '_> {

/// This is a very special situation where we're returning a JsxElement
/// from an arrow function that's passed as an argument to a function,
/// which is itself inside a JSX expression child.
/// which is itself inside a JSX expression container.
///
/// If you're wondering why this is the only other case, it's because
/// Prettier defines it to be that way.
/// This matches Prettier's `shouldBreakJsxElement` behavior.
///
/// ```jsx
/// let bar = <div>
/// {foo(() => <div> the quick brown fox jumps over the lazy dog </div>)}
/// </div>;
/// // As JSX child:
/// let bar = <div>
/// {foo(() => <div> the quick brown fox jumps over the lazy dog </div>)}
/// </div>;
///
/// // As JSX attribute:
/// <Tooltip title={[].map(name => (<Foo>{name}</Foo>))} />;
/// ```
pub fn should_expand(mut parent: &AstNodes<'_>) -> bool {
if let AstNodes::ExpressionStatement(stmt) = parent {
// If the parent is a JSXExpressionContainer, we need to check its parent
// to determine if it should expand.
parent = stmt.grand_parent();
}
let maybe_jsx_expression_child = match parent {
let maybe_jsx_expression_container = match parent {
AstNodes::ArrowFunctionExpression(arrow) if arrow.expression => match arrow.parent() {
AstNodes::CallExpression(call) => call.parent(),
_ => return false,
},
_ => return false,
};
matches!(
maybe_jsx_expression_child.without_chain_expression(),
AstNodes::JSXExpressionContainer(container)
if matches!(container.parent(), AstNodes::JSXElement(_) | AstNodes::JSXFragment(_))
maybe_jsx_expression_container.without_chain_expression(),
AstNodes::JSXExpressionContainer(_)
)
}

Expand Down
53 changes: 53 additions & 0 deletions crates/oxc_formatter/tests/fixtures/js/jsx/arrow-callback.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Arrow functions returning JSX inside JSX expression containers
// The multiline structure should be preserved when the arrow body is JSX

// https://github.com/oxc-project/oxc/issues/18738
// Arrow returning JSX in attribute callback
[
{
baz: () => {
return <Tooltip
title={[].map(name => (
<Foo>{name}</Foo>
))}
>
Foo
</Tooltip>
}
}
];

// Simpler attribute case
<Component
items={data.map(item => (
<Item key={item.id}>{item.name}</Item>
))}
/>;

// JSX as child
<div>
{items.map(item => (
<Item key={item.id}>{item.name}</Item>
))}
</div>;

// Multiple attributes with callback returning JSX
<DataGrid
columns={columns.map(col => (
<Column key={col.id}>{col.label}</Column>
))}
rows={rows.map(row => (
<Row key={row.id}>{row.data}</Row>
))}
/>;

// Nested JSX in attribute
<Outer
render={items.map(item => (
<Inner
content={subitems.map(sub => (
<Leaf>{sub.name}</Leaf>
))}
/>
))}
/>;
178 changes: 178 additions & 0 deletions crates/oxc_formatter/tests/fixtures/js/jsx/arrow-callback.jsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
// Arrow functions returning JSX inside JSX expression containers
// The multiline structure should be preserved when the arrow body is JSX

// https://github.com/oxc-project/oxc/issues/18738
// Arrow returning JSX in attribute callback
[
{
baz: () => {
return <Tooltip
title={[].map(name => (
<Foo>{name}</Foo>
))}
>
Foo
</Tooltip>
}
}
];

// Simpler attribute case
<Component
items={data.map(item => (
<Item key={item.id}>{item.name}</Item>
))}
/>;

// JSX as child
<div>
{items.map(item => (
<Item key={item.id}>{item.name}</Item>
))}
</div>;

// Multiple attributes with callback returning JSX
<DataGrid
columns={columns.map(col => (
<Column key={col.id}>{col.label}</Column>
))}
rows={rows.map(row => (
<Row key={row.id}>{row.data}</Row>
))}
/>;

// Nested JSX in attribute
<Outer
render={items.map(item => (
<Inner
content={subitems.map(sub => (
<Leaf>{sub.name}</Leaf>
))}
/>
))}
/>;

==================== Output ====================
------------------
{ printWidth: 80 }
------------------
// Arrow functions returning JSX inside JSX expression containers
// The multiline structure should be preserved when the arrow body is JSX

// https://github.com/oxc-project/oxc/issues/18738
// Arrow returning JSX in attribute callback
[
{
baz: () => {
return (
<Tooltip
title={[].map((name) => (
<Foo>{name}</Foo>
))}
>
Foo
</Tooltip>
);
},
},
];

// Simpler attribute case
<Component
items={data.map((item) => (
<Item key={item.id}>{item.name}</Item>
))}
/>;

// JSX as child
<div>
{items.map((item) => (
<Item key={item.id}>{item.name}</Item>
))}
</div>;

// Multiple attributes with callback returning JSX
<DataGrid
columns={columns.map((col) => (
<Column key={col.id}>{col.label}</Column>
))}
rows={rows.map((row) => (
<Row key={row.id}>{row.data}</Row>
))}
/>;

// Nested JSX in attribute
<Outer
render={items.map((item) => (
<Inner
content={subitems.map((sub) => (
<Leaf>{sub.name}</Leaf>
))}
/>
))}
/>;

-------------------
{ printWidth: 100 }
-------------------
// Arrow functions returning JSX inside JSX expression containers
// The multiline structure should be preserved when the arrow body is JSX

// https://github.com/oxc-project/oxc/issues/18738
// Arrow returning JSX in attribute callback
[
{
baz: () => {
return (
<Tooltip
title={[].map((name) => (
<Foo>{name}</Foo>
))}
>
Foo
</Tooltip>
);
},
},
];

// Simpler attribute case
<Component
items={data.map((item) => (
<Item key={item.id}>{item.name}</Item>
))}
/>;

// JSX as child
<div>
{items.map((item) => (
<Item key={item.id}>{item.name}</Item>
))}
</div>;

// Multiple attributes with callback returning JSX
<DataGrid
columns={columns.map((col) => (
<Column key={col.id}>{col.label}</Column>
))}
rows={rows.map((row) => (
<Row key={row.id}>{row.data}</Row>
))}
/>;

// Nested JSX in attribute
<Outer
render={items.map((item) => (
<Inner
content={subitems.map((sub) => (
<Leaf>{sub.name}</Leaf>
))}
/>
))}
/>;

===================== End =====================
Loading