diff --git a/apps/oxfmt/test/api/sort_tailwindcss.test.ts b/apps/oxfmt/test/api/sort_tailwindcss.test.ts index 2e213e79523e2..496bc812975d6 100644 --- a/apps/oxfmt/test/api/sort_tailwindcss.test.ts +++ b/apps/oxfmt/test/api/sort_tailwindcss.test.ts @@ -634,6 +634,20 @@ shadow-lg\` : "font-normal"}\`} />;`; expect(result.errors).toStrictEqual([]); }); + // https://github.com/oxc-project/oxc/issues/20397 + it("should preserve trailing space in ternary inside binary concat", async () => { + const input = `const A =
;`; + + const result = await format("test.tsx", input, { + experimentalTailwindcss: {}, + }); + + expect(result.code).toContain('"m-1 h-fit w-full "'); + expect(result.code).toContain('"block "'); + expect(result.code).toContain('"hidden "'); + expect(result.errors).toStrictEqual([]); + }); + // Tests for template literals in binary expressions it("should sort template literal on right side of binary expression", async () => { const input = "const A = ;"; diff --git a/crates/oxc_formatter/src/utils/tailwindcss.rs b/crates/oxc_formatter/src/utils/tailwindcss.rs index 7b132a5d9c025..4b8313c2fcb70 100644 --- a/crates/oxc_formatter/src/utils/tailwindcss.rs +++ b/crates/oxc_formatter/src/utils/tailwindcss.rs @@ -188,24 +188,33 @@ where // 2. Check binary concat context (walk parent chain) for ancestor in ancestors { - let AstNodes::BinaryExpression(binary) = ancestor else { - break; - }; - - if binary.operator() != BinaryOperator::Addition { - break; - } - - let left = binary.left().span(); - let right = binary.right().span(); - - // Left operand needs trailing space for separation from `+ right` - if left.contains_inclusive(span) { - collapse.end = false; - } - // Right operand needs leading space for separation from `left +` - if right.contains_inclusive(span) { - collapse.start = false; + match ancestor { + AstNodes::BinaryExpression(binary) if binary.operator() == BinaryOperator::Addition => { + let left = binary.left().span(); + let right = binary.right().span(); + + // Left operand needs trailing space for separation from `+ right` + if left.contains_inclusive(span) { + collapse.end = false; + } + // Right operand needs leading space for separation from `left +` + if right.contains_inclusive(span) { + collapse.start = false; + } + + // Both flags are one-way latches; no need to continue once both are set. + if !collapse.start && !collapse.end { + break; + } + } + // Transparent nodes: skip through to find outer BinaryExpression(+) + AstNodes::ConditionalExpression(_) + | AstNodes::ParenthesizedExpression(_) + | AstNodes::TSAsExpression(_) + | AstNodes::TSSatisfiesExpression(_) + | AstNodes::TSNonNullExpression(_) + | AstNodes::TSTypeAssertion(_) => {} + _ => break, } }