[babel-plugin] processStylexRules corrupts @position-try rules with RTL ancestor selectors
- Dominant language
- JavaScript
- Stars
- 10.3k
- Forks
- 481
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 13
Description
### Describe the issue
When `enableLTRRTLComments` is disabled (the default), `processStylexRules` corrupts `@position-try` at-rules by injecting RTL ancestor selectors (`html:not([dir='rtl'])` / `html[dir='rtl']`) into the rule body. The resulting CSS is invalid and breaks the build — for example, lightningcss raises a `SquareBracketBlock` parse error.
The root cause is that `addAncestorSelector` treats every non-`@keyframes` at-rule the same way it treats `@media`/`@supports`/`@container` — it assumes the part after the at-rule header contains CSS *selectors* and prepends the ancestor selector. But `@position-try` (like `@keyframes`) has a name followed by a *declaration block with no selector*, so the ancestor selector is injected directly into the declaration list:
```
Before: @position-try --xhul0o3 {height:100px;top:0;width:100px;}
After: @position-try --xhul0o3 {html[dir='rtl'] height:100px;top:0;width:100px;}
```
The `html[dir='rtl']` token is not a valid declaration, and the `[` triggers a `SquareBracketBlock` parse error.
The same issue affects `addSpecificityLevel`, which injects `:not(#\#)` into the `@position-try` block when `useLayers` is disabled:
```
After: @position-try --xhul0o3 :not(#\#){height:100px;top:0;width:100px;}
```
### Expected behavior
`@position-try` should be skipped by `addAncestorSelector` and `addSpecificityLevel` just like `@keyframes`, since its block contains declarations, not selectors. `processStylexRules` already documents this intent ("Don't put @property, @keyframe, @position-try in layers") but does not apply the same exemption to ancestor-selector/specificity injection.
### Steps to reproduce
1. Create a file that uses `stylex.positionTry(...)` and references it via `positionTryFallbacks`:
```js
import * as stylex from '@stylexjs/stylex';
const popup = stylex.positionTry({
positionArea: 'bottom span-right',
});
const styles = stylex.create({
popup: {
positionArea: 'bottom span-right',
positionTryFallbacks: popup,
},
});
```
2. Build the CSS with `processStylexRules` using the default config (`enableLTRRTLComments: false`), e.g. via `@stylexjs/unplugin` with `useCSSLayers: true`.
3. Observe the emitted `@position-try` rule: the RTL ancestor selector (`html[dir='rtl']`) is injected into the declaration block, producing invalid CSS.
Environment:
- `@stylexjs/babel-plugin` 0.19.0 (latest)
- OS: Linux
### Test case
```js
import stylexPlugin from '@stylexjs/babel-plugin';
const rules = [[
'--xhul0o3',
{
ltr: '@position-try --xhul0o3 {height:height;height:100px;top:top;top:0;width:width;width:100px;}',
rtl: '@position-try --xhul0o3 {height:100px;top:0;width:100px;}',
},
0,
]];
const css = stylexPlugin.processStylexRules(rules, { enableLTRRTLComments: false });
// Expected: the @position-try rule is emitted unchanged.
// Actual (before fix): "@position-try --xhul0o3 {html[dir='rtl'] height:100px;top:0;width:100px;}"
```
### Additional comments
A proposed fix is to exempt `@position-try` from both `addAncestorSelector` and `addSpecificityLevel`, exactly as `@keyframes` already is — see PR #1854.
Contributor guide
Assessment
This issue has not been assessed yet.