Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import invariant from 'invariant';
import {isValidIdentifier} from '@babel/types';
import {Environment} from '../HIR';
import {
BasicBlock,
Expand Down Expand Up @@ -215,16 +216,38 @@ type OutlinedJsxAttribute = {
place: Place;
};

/**
* Returns true when the original JSX attribute name contains non-identifier
* characters (e.g. `aria-label`) but is still a valid JSX attribute name.
* These props should keep their original name in the outlined JSX call site
* and use a quoted key in the destructuring pattern.
*/
function isHyphenatedJsxProp(originalName: string): boolean {
return (
!isValidIdentifier(originalName) && /^[a-zA-Z_][\w.-]*$/.test(originalName)
);
}

function collectProps(
env: Environment,
instructions: Array<JsxInstruction>,
): Array<OutlinedJsxAttribute> | null {
let id = 1;

function generateName(oldName: string): string {
let newName = oldName;
// Sanitize names that aren't valid JS identifiers (e.g. "aria-label" -> "ariaLabel")
let baseName = oldName;
if (!isValidIdentifier(baseName)) {
baseName = baseName.replace(/[^a-zA-Z0-9$_]+(.)?/g, (_, char) =>
char != null ? char.toUpperCase() : '',
);
if (!isValidIdentifier(baseName)) {
baseName = `_${baseName}`;
}
}
let newName = baseName;
while (seen.has(newName)) {
newName = `${oldName}${id++}`;
newName = `${baseName}${id++}`;
}
seen.add(newName);
env.programContext.addNewReference(newName);
Expand Down Expand Up @@ -280,7 +303,13 @@ function emitOutlinedJsx(
): Array<Instruction> {
const props: Array<JsxAttribute> = outlinedProps.map(p => ({
kind: 'JsxAttribute',
name: p.newName,
/*
* Use the original name when it's a valid JSX attribute name (e.g. `aria-label`
* is a valid JSX attr even though it's not a valid JS identifier).
* Fall back to newName for internal temp names (e.g. `#t16`) and for
* deduplicated props where originalName would conflict.
*/
name: isHyphenatedJsxProp(p.originalName) ? p.originalName : p.newName,
place: p.place,
}));

Expand Down Expand Up @@ -494,12 +523,18 @@ function emitDestructureProps(
): Instruction {
const properties: Array<ObjectProperty> = [];
for (const [_, prop] of oldToNewProps) {
/*
* When the original prop name is a valid identifier (e.g. `disabled`),
* use newName as the key (handles deduplication like `k` → `k1`).
* When the original prop name is NOT a valid identifier (e.g. `aria-label`),
* use originalName as the string key so we get `'aria-label': ariaLabel`
* instead of `ariaLabel: ariaLabel`.
*/
properties.push({
kind: 'ObjectProperty',
key: {
kind: 'string',
name: prop.newName,
},
key: isHyphenatedJsxProp(prop.originalName)
? {kind: 'string', name: prop.originalName}
: {kind: 'identifier', name: prop.newName},
type: 'property',
place: prop.place,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,9 @@ function codegenInstructionNullable(
});
CompilerError.invariant(value?.type === 'FunctionExpression', {
reason: 'Expected a function as a function declaration value',
description: `Got ${value == null ? String(value) : value.type} at ${printInstruction(instr)}`,
description: `Got ${
value == null ? String(value) : value.type
} at ${printInstruction(instr)}`,
loc: instr.value.loc,
});
return createFunctionDeclaration(
Expand Down Expand Up @@ -1963,34 +1965,35 @@ function codegenInstructionValue(
instruction,
})),
).body;
const expressions = body.map(stmt => {
const expressions = body.flatMap(stmt => {
if (stmt.type === 'ExpressionStatement') {
return stmt.expression;
return [stmt.expression];
} else if (t.isVariableDeclaration(stmt)) {
return stmt.declarations.map(declarator => {
if (declarator.init != null) {
return t.assignmentExpression(
'=',
declarator.id as t.LVal,
declarator.init,
);
} else {
return t.assignmentExpression(
'=',
declarator.id as t.LVal,
t.identifier('undefined'),
);
}
});
} else {
if (t.isVariableDeclaration(stmt)) {
const declarator = stmt.declarations[0];
cx.recordError(
new CompilerErrorDetail({
reason: `(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block, tried to declare '${
(declarator.id as t.Identifier).name
}'`,
category: ErrorCategory.Todo,
loc: declarator.loc ?? null,
suggestions: null,
}),
);
return t.stringLiteral(`TODO handle ${declarator.id}`);
} else {
cx.recordError(
new CompilerErrorDetail({
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
category: ErrorCategory.Todo,
loc: stmt.loc ?? null,
suggestions: null,
}),
);
return t.stringLiteral(`TODO handle ${stmt.type}`);
}
cx.recordError(
new CompilerErrorDetail({
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
category: ErrorCategory.Todo,
loc: stmt.loc ?? null,
suggestions: null,
}),
);
return [t.stringLiteral(`TODO handle ${stmt.type}`)];
}
});
if (expressions.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function Component(t0) {
}
function _temp(t0) {
const $ = _c(5);
const { i: i, x: x } = t0;
const { i, x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function Component(t0) {
}
function _temp(t0) {
const $ = _c(8);
const { i: i, k: k, x: x } = t0;
const { i, k, x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function Component(t0) {
}
function _temp(t0) {
const $ = _c(11);
const { k: k, k1: k1, k12: k12, x: x } = t0;
const { k, k1, k12, x } = t0;
let t1;
if ($[0] !== k) {
t1 = <Foo k={k} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function Component(t0) {
}
function _temp(t0) {
const $ = _c(8);
const { k: k, k1: k1, x: x } = t0;
const { k, k1, x } = t0;
let t1;
if ($[0] !== k) {
t1 = <Foo k={k} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function Component(t0) {
}
function _temp(t0) {
const $ = _c(8);
const { i: i, i1: i1, x: x } = t0;
const { i, i1, x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function Component(t0) {
}
function _temp(t0) {
const $ = _c(5);
const { i: i, x: x } = t0;
const { i, x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function Component(t0) {
}
function _temp(t0) {
const $ = _c(11);
const { i: i, j: j, k: k, x: x } = t0;
const { i, j, k, x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function Component(t0) {
}
function _temp(t0) {
const $ = _c(5);
const { i: i, x: x } = t0;
const { i, x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

## Input

```javascript
// @enableJsxOutlining
function Component() {
const [isSubmitting] = useState(false);

return ssoProviders.map(provider => {
return (
<div key={provider.providerId}>
<Switch
disabled={isSubmitting}
aria-label={`Toggle ${provider.displayName}`}
/>
</div>
);
});
}

```

## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
function Component() {
const $ = _c(2);
const [isSubmitting] = useState(false);
let t0;
if ($[0] !== isSubmitting) {
t0 = ssoProviders.map((provider) => {
const T0 = _temp;
return (
<T0
disabled={isSubmitting}
aria-label={`Toggle ${provider.displayName}`}
key={provider.providerId}
/>
);
});
$[0] = isSubmitting;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
function _temp(t0) {
const $ = _c(3);
const { disabled, "aria-label": ariaLabel } = t0;
let t1;
if ($[0] !== ariaLabel || $[1] !== disabled) {
t1 = (
<div>
<Switch disabled={disabled} aria-label={ariaLabel} />
</div>
);
$[0] = ariaLabel;
$[1] = disabled;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}

```

### Eval output
(kind: exception) Fixture not implemented
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @enableJsxOutlining
function Component() {
const [isSubmitting] = useState(false);

return ssoProviders.map(provider => {
return (
<div key={provider.providerId}>
<Switch
disabled={isSubmitting}
aria-label={`Toggle ${provider.displayName}`}
/>
</div>
);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function Component(t0) {
}
function _temp(t0) {
const $ = _c(9);
const { i: i, t: t, k: k, x: x } = t0;
const { i, t, k, x } = t0;
let t1;
if ($[0] !== i || $[1] !== t) {
t1 = <Baz i={i}>{t}</Baz>;
Expand Down
Loading