diff --git a/crates/oxc_formatter/src/print/jsx/element.rs b/crates/oxc_formatter/src/print/jsx/element.rs
index 1f7295b4e3524..dcb6f2a8a7789 100644
--- a/crates/oxc_formatter/src/print/jsx/element.rs
+++ b/crates/oxc_formatter/src/print/jsx/element.rs
@@ -229,15 +229,18 @@ 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 =
-/// {foo(() =>
the quick brown fox jumps over the lazy dog
)}
-///
;
+/// // As JSX child:
+/// let bar =
+/// {foo(() =>
the quick brown fox jumps over the lazy dog
)}
+///
;
+///
+/// // As JSX attribute:
+/// ({name}))} />;
/// ```
pub fn should_expand(mut parent: &AstNodes<'_>) -> bool {
if let AstNodes::ExpressionStatement(stmt) = parent {
@@ -245,7 +248,7 @@ pub fn should_expand(mut parent: &AstNodes<'_>) -> bool {
// 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,
@@ -253,9 +256,8 @@ pub fn should_expand(mut parent: &AstNodes<'_>) -> bool {
_ => 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(_)
)
}
diff --git a/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-callback.jsx b/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-callback.jsx
new file mode 100644
index 0000000000000..fda4a1fbd4e00
--- /dev/null
+++ b/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-callback.jsx
@@ -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 (
+ {name}
+ ))}
+ >
+ Foo
+
+ }
+ }
+];
+
+// Simpler attribute case
+ (
+ - {item.name}
+ ))}
+/>;
+
+// JSX as child
+
+ {items.map(item => (
+ - {item.name}
+ ))}
+
;
+
+// Multiple attributes with callback returning JSX
+ (
+ {col.label}
+ ))}
+ rows={rows.map(row => (
+ {row.data}
+ ))}
+/>;
+
+// Nested JSX in attribute
+ (
+ (
+ {sub.name}
+ ))}
+ />
+ ))}
+/>;
diff --git a/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-callback.jsx.snap b/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-callback.jsx.snap
new file mode 100644
index 0000000000000..36552d9320d26
--- /dev/null
+++ b/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-callback.jsx.snap
@@ -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 (
+ {name}
+ ))}
+ >
+ Foo
+
+ }
+ }
+];
+
+// Simpler attribute case
+ (
+ - {item.name}
+ ))}
+/>;
+
+// JSX as child
+
+ {items.map(item => (
+ - {item.name}
+ ))}
+
;
+
+// Multiple attributes with callback returning JSX
+ (
+ {col.label}
+ ))}
+ rows={rows.map(row => (
+ {row.data}
+ ))}
+/>;
+
+// Nested JSX in attribute
+ (
+ (
+ {sub.name}
+ ))}
+ />
+ ))}
+/>;
+
+==================== 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 (
+ (
+ {name}
+ ))}
+ >
+ Foo
+
+ );
+ },
+ },
+];
+
+// Simpler attribute case
+ (
+ - {item.name}
+ ))}
+/>;
+
+// JSX as child
+
+ {items.map((item) => (
+ - {item.name}
+ ))}
+
;
+
+// Multiple attributes with callback returning JSX
+ (
+ {col.label}
+ ))}
+ rows={rows.map((row) => (
+ {row.data}
+ ))}
+/>;
+
+// Nested JSX in attribute
+ (
+ (
+ {sub.name}
+ ))}
+ />
+ ))}
+/>;
+
+-------------------
+{ 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 (
+ (
+ {name}
+ ))}
+ >
+ Foo
+
+ );
+ },
+ },
+];
+
+// Simpler attribute case
+ (
+ - {item.name}
+ ))}
+/>;
+
+// JSX as child
+
+ {items.map((item) => (
+ - {item.name}
+ ))}
+
;
+
+// Multiple attributes with callback returning JSX
+ (
+ {col.label}
+ ))}
+ rows={rows.map((row) => (
+ {row.data}
+ ))}
+/>;
+
+// Nested JSX in attribute
+ (
+ (
+ {sub.name}
+ ))}
+ />
+ ))}
+/>;
+
+===================== End =====================