[babel-plugin] processStylexRules corrupts @position-try rules with RTL ancestor selectors
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
- Create a file that uses
stylex.positionTry(...)and references it viapositionTryFallbacks:
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,
},
});Build the CSS with
processStylexRulesusing the default config (enableLTRRTLComments: false), e.g. via@stylexjs/unpluginwithuseCSSLayers: true.Observe the emitted
@position-tryrule: the RTL ancestor selector (html[dir='rtl']) is injected into the declaration block, producing invalid CSS.
Environment:
@stylexjs/babel-plugin0.19.0 (latest)- OS: Linux
Test case
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.
Source: facebook/stylex