fix(css): preserve logical border-radius properties (#26006)

## Summary
- CSS logical border-radius properties (`border-start-start-radius`,
`border-start-end-radius`, `border-end-end-radius`,
`border-end-start-radius`) were being silently dropped when processed by
the CSS bundler
- The bug was in `src/css/properties/border_radius.zig` where
`VendorPrefix{}` (all fields false) was used instead of `VendorPrefix{
.none = true }` when computing prefixes for logical properties
- This caused the properties to be dropped by a later `isEmpty()` check
since an empty prefix struct was returned

## Test plan
- [x] Added regression test `test/regression/issue/25785.test.ts`
- [x] Verified test fails with system Bun (`USE_SYSTEM_BUN=1 bun test`)
- [x] Verified test passes with fixed bun-debug (`bun bd test`)

Fixes #25785

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
robobun
2026-01-14 13:34:31 -08:00
committed by GitHub
parent 6a27a25e5b
commit c57d0f73b4
2 changed files with 107 additions and 1 deletions

View File

@@ -220,7 +220,7 @@ pub const BorderRadiusHandler = struct {
if (logical_supported) {
bun.handleOom(d.append(ctx.allocator, v));
} else {
const prefix = ctx.targets.prefixes(css.VendorPrefix{}, css.prefixes.Feature.border_radius);
const prefix = ctx.targets.prefixes(css.VendorPrefix{ .none = true }, css.prefixes.Feature.border_radius);
switch (v) {
.@"border-start-start-radius",
.@"border-start-end-radius",

View File

@@ -0,0 +1,106 @@
import { expect, test } from "bun:test";
import { tempDir } from "harness";
// Regression test for https://github.com/oven-sh/bun/issues/25785
// CSS logical border-radius properties were being silently dropped
test("CSS bundler should preserve logical border-radius properties", async () => {
using dir = tempDir("issue-25785", {
"test.css": `
.test1 {
border-start-start-radius: 0.75rem;
}
.test2 {
border-end-start-radius: 0.75rem;
}
.test3 {
border-start-end-radius: 0.75rem;
}
.test4 {
border-end-end-radius: 0.75rem;
}
.test5 {
border-top-left-radius: 0.75rem;
}
`,
});
const result = await Bun.build({
entrypoints: [`${dir}/test.css`],
outdir: `${dir}/dist`,
experimentalCss: true,
minify: false,
});
expect(result.success).toBe(true);
expect(result.outputs.length).toBe(1);
const output = await result.outputs[0].text();
// Logical properties are compiled to physical properties with LTR/RTL rules
// .test1 with border-start-start-radius compiles to border-top-left-radius (LTR) and border-top-right-radius (RTL)
expect(output).toContain(".test1");
expect(output).toContain("border-top-left-radius");
expect(output).toContain("border-top-right-radius");
// .test2 with border-end-start-radius compiles to border-bottom-left-radius (LTR) and border-bottom-right-radius (RTL)
expect(output).toContain(".test2");
expect(output).toContain("border-bottom-left-radius");
expect(output).toContain("border-bottom-right-radius");
// .test3 with border-start-end-radius
expect(output).toContain(".test3");
// .test4 with border-end-end-radius
expect(output).toContain(".test4");
// Physical property should also be preserved
expect(output).toContain(".test5");
});
test("CSS bundler should handle logical border-radius with targets that compile logical properties", async () => {
using dir = tempDir("issue-25785-compiled", {
"test.css": `
.test1 {
border-start-start-radius: 0.75rem;
}
.test2 {
border-end-start-radius: 0.75rem;
}
.test3 {
border-start-end-radius: 0.75rem;
}
.test4 {
border-end-end-radius: 0.75rem;
}
`,
});
const result = await Bun.build({
entrypoints: [`${dir}/test.css`],
outdir: `${dir}/dist`,
experimentalCss: true,
minify: false,
// Target older browsers that don't support logical properties
target: "browser",
});
expect(result.success).toBe(true);
expect(result.outputs.length).toBe(1);
const output = await result.outputs[0].text();
// When logical properties are compiled down, they should produce physical properties
// with :lang() selectors to handle LTR/RTL
// At minimum, the output should NOT be empty (the bug caused empty output)
expect(output.trim().length).toBeGreaterThan(0);
// Should have some border-radius output (compiled to physical)
expect(output).toMatch(/border-.*-radius/);
// All classes should be present in the output
expect(output).toContain(".test1");
expect(output).toContain(".test2");
expect(output).toContain(".test3");
expect(output).toContain(".test4");
});