Skip to content

Commit 5a829e9

Browse files
skovhuscursoragent
andauthored
fix: restore null guard for optional pseudo-element style props (#344)
For simple identity props inside pseudo-element interpolations, check whether the prop is optional via ctx.isJsxPropOptional(). Only set condition: 'always' for required props (preserving the merge into a single style function). Optional props omit the condition, so the emit-time isRequired check adds null guards: tagColor != null && styles.tagAfterBackgroundColor(tagColor) Previously, condition: 'always' was applied to all pseudo-element styleFn entries regardless of optionality, which forced unconditional calls even for optional props — passing undefined into style functions. Extends the selector-dynamicPseudoElement test case with an optional simple identity prop (Tag with $tagColor?) to verify the null guard. Addresses PR #343 review comment. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent e64780e commit 5a829e9

File tree

3 files changed

+64
-6
lines changed

3 files changed

+64
-6
lines changed

src/internal/lower-rules/rule-interpolated-declaration.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2417,12 +2417,21 @@ function tryHandleDynamicPseudoElementStyleFunction(args: InterpolatedDeclaratio
24172417
styleFnDecls.set(fnKey, j.arrowFunctionExpression([param], body));
24182418
}
24192419

2420-
styleFnFromProps.push({
2421-
fnKey,
2422-
jsxProp,
2423-
condition: "always" as const,
2424-
...(isSimpleIdentity ? {} : { callArg: cloneAstNode(valueExpr) as ExpressionKind }),
2425-
});
2420+
if (isSimpleIdentity) {
2421+
const isOptional = ctx.isJsxPropOptional(jsxProp);
2422+
styleFnFromProps.push({
2423+
fnKey,
2424+
jsxProp,
2425+
...(isOptional ? {} : { condition: "always" as const }),
2426+
});
2427+
} else {
2428+
styleFnFromProps.push({
2429+
fnKey,
2430+
jsxProp: "__props" as const,
2431+
condition: "always" as const,
2432+
callArg: cloneAstNode(valueExpr) as ExpressionKind,
2433+
});
2434+
}
24262435
}
24272436

24282437
for (const propName of propsUsed) {

test-cases/selector-dynamicPseudoElement.input.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@ const Tooltip = styled.div<{ $tipColor?: string }>`
3535
}
3636
`;
3737

38+
// Optional simple identity prop in pseudo-element: should emit null guard
39+
const Tag = styled.span<{ $tagColor?: string }>`
40+
position: relative;
41+
padding: 4px 8px;
42+
background-color: #e0e0e0;
43+
44+
&::after {
45+
content: "";
46+
display: block;
47+
height: 2px;
48+
background-color: ${(props) => props.$tagColor};
49+
}
50+
`;
51+
3852
// Dynamic pseudo-element style inside :hover context
3953
const Button = styled.button<{ $glowColor: string }>`
4054
padding: 8px 16px;
@@ -61,6 +75,8 @@ export const App = () => (
6175
<Badge $badgeColor="blue">Info</Badge>
6276
<Tooltip $tipColor="navy">With color</Tooltip>
6377
<Tooltip>Default</Tooltip>
78+
<Tag $tagColor="tomato">With color</Tag>
79+
<Tag>No color</Tag>
6480
<Button $glowColor="rgba(0,128,255,0.3)">Hover me</Button>
6581
</div>
6682
);

test-cases/selector-dynamicPseudoElement.output.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ function Tooltip(props: TooltipProps) {
4242
);
4343
}
4444

45+
type TagProps = React.PropsWithChildren<{
46+
tagColor?: string;
47+
}>;
48+
49+
// Optional simple identity prop in pseudo-element: should emit null guard
50+
function Tag(props: TagProps) {
51+
const { children, tagColor } = props;
52+
53+
return (
54+
<span sx={[styles.tag, tagColor != null && styles.tagAfterBackgroundColor(tagColor)]}>
55+
{children}
56+
</span>
57+
);
58+
}
59+
4560
type ButtonProps = React.PropsWithChildren<{
4661
glowColor: string;
4762
}>;
@@ -68,6 +83,8 @@ export const App = () => (
6883
<Badge badgeColor="blue">Info</Badge>
6984
<Tooltip tipColor="navy">With color</Tooltip>
7085
<Tooltip>Default</Tooltip>
86+
<Tag tagColor="tomato">With color</Tag>
87+
<Tag>No color</Tag>
7188
<Button glowColor="rgba(0,128,255,0.3)">Hover me</Button>
7289
</div>
7390
);
@@ -100,6 +117,22 @@ const styles = stylex.create({
100117
backgroundColor: props.backgroundColor,
101118
},
102119
}),
120+
tag: {
121+
position: "relative",
122+
paddingBlock: 4,
123+
paddingInline: 8,
124+
backgroundColor: "#e0e0e0",
125+
"::after": {
126+
content: '""',
127+
display: "block",
128+
height: 2,
129+
},
130+
},
131+
tagAfterBackgroundColor: (tagColor: string) => ({
132+
"::after": {
133+
backgroundColor: tagColor,
134+
},
135+
}),
103136
button: (props: { glowColor: string }) => ({
104137
paddingBlock: 8,
105138
paddingInline: 16,

0 commit comments

Comments
 (0)