Skip to content

Commit d525864

Browse files
committed
[compiler] Fix enableJsxOutlining with hyphenated JSX attribute names and SequenceExpression variable declarations
Fixes two issues with enableJsxOutlining: 1. JSX attributes with hyphens (e.g. aria-label, data-testid) were used directly as JavaScript identifier names in the outlined component's props destructuring, producing invalid code like: `const { "aria-label": aria-label } = t0;` Fix: sanitize attribute names to valid JS identifiers by converting hyphens to camelCase (e.g. aria-label -> ariaLabel). 2. When a VariableDeclaration appeared inside a SequenceExpression value block during codegen, it would emit a Todo error or produce invalid JS. Fix: convert VariableDeclarations to assignment expressions within SequenceExpression contexts. Fixes #36217 Fixes #36218
1 parent 1b45e24 commit d525864

4 files changed

Lines changed: 123 additions & 28 deletions

File tree

compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import invariant from 'invariant';
9+
import {isValidIdentifier} from '@babel/types';
910
import {Environment} from '../HIR';
1011
import {
1112
BasicBlock,
@@ -222,9 +223,19 @@ function collectProps(
222223
let id = 1;
223224

224225
function generateName(oldName: string): string {
225-
let newName = oldName;
226+
// Sanitize names that aren't valid JS identifiers (e.g. "aria-label" -> "ariaLabel")
227+
let baseName = oldName;
228+
if (!isValidIdentifier(baseName)) {
229+
baseName = baseName.replace(/[^a-zA-Z0-9$_]+(.)?/g, (_, char) =>
230+
char != null ? char.toUpperCase() : '',
231+
);
232+
if (!isValidIdentifier(baseName)) {
233+
baseName = `_${baseName}`;
234+
}
235+
}
236+
let newName = baseName;
226237
while (seen.has(newName)) {
227-
newName = `${oldName}${id++}`;
238+
newName = `${baseName}${id++}`;
228239
}
229240
seen.add(newName);
230241
env.programContext.addNewReference(newName);

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,34 +1963,35 @@ function codegenInstructionValue(
19631963
instruction,
19641964
})),
19651965
).body;
1966-
const expressions = body.map(stmt => {
1966+
const expressions = body.flatMap(stmt => {
19671967
if (stmt.type === 'ExpressionStatement') {
1968-
return stmt.expression;
1968+
return [stmt.expression];
1969+
} else if (t.isVariableDeclaration(stmt)) {
1970+
return stmt.declarations.map(declarator => {
1971+
if (declarator.init != null) {
1972+
return t.assignmentExpression(
1973+
'=',
1974+
declarator.id as t.LVal,
1975+
declarator.init,
1976+
);
1977+
} else {
1978+
return t.assignmentExpression(
1979+
'=',
1980+
declarator.id as t.LVal,
1981+
t.identifier('undefined'),
1982+
);
1983+
}
1984+
});
19691985
} else {
1970-
if (t.isVariableDeclaration(stmt)) {
1971-
const declarator = stmt.declarations[0];
1972-
cx.recordError(
1973-
new CompilerErrorDetail({
1974-
reason: `(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block, tried to declare '${
1975-
(declarator.id as t.Identifier).name
1976-
}'`,
1977-
category: ErrorCategory.Todo,
1978-
loc: declarator.loc ?? null,
1979-
suggestions: null,
1980-
}),
1981-
);
1982-
return t.stringLiteral(`TODO handle ${declarator.id}`);
1983-
} else {
1984-
cx.recordError(
1985-
new CompilerErrorDetail({
1986-
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
1987-
category: ErrorCategory.Todo,
1988-
loc: stmt.loc ?? null,
1989-
suggestions: null,
1990-
}),
1991-
);
1992-
return t.stringLiteral(`TODO handle ${stmt.type}`);
1993-
}
1986+
cx.recordError(
1987+
new CompilerErrorDetail({
1988+
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
1989+
category: ErrorCategory.Todo,
1990+
loc: stmt.loc ?? null,
1991+
suggestions: null,
1992+
}),
1993+
);
1994+
return [t.stringLiteral(`TODO handle ${stmt.type}`)];
19941995
}
19951996
});
19961997
if (expressions.length === 0) {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @enableJsxOutlining
6+
function Component() {
7+
const [isSubmitting] = useState(false);
8+
9+
return ssoProviders.map((provider) => {
10+
return (
11+
<div key={provider.providerId}>
12+
<Switch disabled={isSubmitting} aria-label={`Toggle ${provider.displayName}`} />
13+
</div>
14+
);
15+
});
16+
}
17+
18+
```
19+
20+
## Code
21+
22+
```javascript
23+
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
24+
function Component() {
25+
const $ = _c(2);
26+
const [isSubmitting] = useState(false);
27+
let t0;
28+
if ($[0] !== isSubmitting) {
29+
t0 = ssoProviders.map((provider) => {
30+
const T0 = _temp;
31+
return (
32+
<T0
33+
disabled={isSubmitting}
34+
ariaLabel={`Toggle ${provider.displayName}`}
35+
key={provider.providerId}
36+
/>
37+
);
38+
});
39+
$[0] = isSubmitting;
40+
$[1] = t0;
41+
} else {
42+
t0 = $[1];
43+
}
44+
return t0;
45+
}
46+
function _temp(t0) {
47+
const $ = _c(3);
48+
const { disabled: disabled, ariaLabel: ariaLabel } = t0;
49+
let t1;
50+
if ($[0] !== ariaLabel || $[1] !== disabled) {
51+
t1 = (
52+
<div>
53+
<Switch disabled={disabled} aria-label={ariaLabel} />
54+
</div>
55+
);
56+
$[0] = ariaLabel;
57+
$[1] = disabled;
58+
$[2] = t1;
59+
} else {
60+
t1 = $[2];
61+
}
62+
return t1;
63+
}
64+
65+
```
66+
67+
### Eval output
68+
(kind: exception) Fixture not implemented
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @enableJsxOutlining
2+
function Component() {
3+
const [isSubmitting] = useState(false);
4+
5+
return ssoProviders.map(provider => {
6+
return (
7+
<div key={provider.providerId}>
8+
<Switch
9+
disabled={isSubmitting}
10+
aria-label={`Toggle ${provider.displayName}`}
11+
/>
12+
</div>
13+
);
14+
});
15+
}

0 commit comments

Comments
 (0)