facebook / facebook/stylex

[babel-plugin] processStylexRules corrupts @position-try rules with RTL ancestor selectors

Open
#1,856 0 comments 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.