mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Fix JSX form of fbt
Fixes `<fbt>`. This required a bunk of yak shaving to work through several issues: * First, there was a bug in codegen for JsxNamedspacedName. I added handling for it for identifiers, but JsxNamespacedName gets converted to a Primitive. The output looked correct because Babel happily creates invalid Jsx identifiers! * Next, I needed to add locations to JSX nodes. It took me a while to pinpoint which specific node needed the location, so I ended up just adding locations to all the parts of a Jsx element. * That uncovered the fact that FBT was expecting the `<fbt:param>`'s `name` attribute value to be a StringLiteral, not a StringLiteral wrapped in a JsxExpressionContainer. So now we special-case JsxAttribute and emit raw StringLiteral (either is allowed per the spec) And with that, voila, `<fbt>` works.
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
InstructionKind,
|
||||
JsxAttribute,
|
||||
Pattern,
|
||||
Place,
|
||||
ReactiveBlock,
|
||||
@@ -566,6 +567,14 @@ const createLogicalExpression = withLoc(t.logicalExpression);
|
||||
const createSequenceExpression = withLoc(t.sequenceExpression);
|
||||
const createConditionalExpression = withLoc(t.conditionalExpression);
|
||||
const createTemplateLiteral = withLoc(t.templateLiteral);
|
||||
const createJsxNamespacedName = withLoc(t.jsxNamespacedName);
|
||||
const createJsxElement = withLoc(t.jsxElement);
|
||||
const createJsxAttribute = withLoc(t.jsxAttribute);
|
||||
const createJsxIdentifier = withLoc(t.jsxIdentifier);
|
||||
const createJsxExpressionContainer = withLoc(t.jsxExpressionContainer);
|
||||
const createJsxText = withLoc(t.jsxText);
|
||||
const createJsxClosingElement = withLoc(t.jsxClosingElement);
|
||||
const createStringLiteral = withLoc(t.stringLiteral);
|
||||
|
||||
type Temporaries = Map<IdentifierId, t.Expression>;
|
||||
|
||||
@@ -642,7 +651,7 @@ function codegenInstructionValue(
|
||||
break;
|
||||
}
|
||||
case "Primitive": {
|
||||
value = codegenValue(cx, instrValue.value);
|
||||
value = codegenValue(cx, instrValue.loc, instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "CallExpression": {
|
||||
@@ -712,58 +721,18 @@ function codegenInstructionValue(
|
||||
break;
|
||||
}
|
||||
case "JSXText": {
|
||||
value = t.stringLiteral(instrValue.value);
|
||||
value = createStringLiteral(instrValue.loc, instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "JsxExpression": {
|
||||
const attributes: Array<t.JSXAttribute | t.JSXSpreadAttribute> = [];
|
||||
for (const attribute of instrValue.props) {
|
||||
switch (attribute.kind) {
|
||||
case "JsxAttribute": {
|
||||
let propName: t.JSXIdentifier | t.JSXNamespacedName;
|
||||
if (attribute.name.indexOf(":") === -1) {
|
||||
propName = t.jsxIdentifier(attribute.name);
|
||||
} else {
|
||||
const [namespace, name] = attribute.name.split(":", 2);
|
||||
propName = t.jsxNamespacedName(
|
||||
t.jsxIdentifier(namespace),
|
||||
t.jsxIdentifier(name)
|
||||
);
|
||||
}
|
||||
attributes.push(
|
||||
t.jsxAttribute(
|
||||
propName,
|
||||
t.jsxExpressionContainer(codegenPlace(cx, attribute.place))
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "JsxSpreadAttribute": {
|
||||
attributes.push(
|
||||
t.jsxSpreadAttribute(codegenPlace(cx, attribute.argument))
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
attribute,
|
||||
`Unexpected attribute kind '${(attribute as any).kind}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
attributes.push(codegenJsxAttribute(cx, attribute));
|
||||
}
|
||||
let tagValue = codegenPlace(cx, instrValue.tag);
|
||||
let tag: t.JSXIdentifier | t.JSXNamespacedName | t.JSXMemberExpression;
|
||||
if (tagValue.type === "Identifier") {
|
||||
if (tagValue.name.indexOf(":") >= 0) {
|
||||
const [namespace, name] = tagValue.name.split(":", 2);
|
||||
tag = t.jsxNamespacedName(
|
||||
t.jsxIdentifier(namespace),
|
||||
t.jsxIdentifier(name)
|
||||
);
|
||||
} else {
|
||||
tag = t.jsxIdentifier(tagValue.name);
|
||||
}
|
||||
tag = createJsxIdentifier(instrValue.tag.loc, tagValue.name);
|
||||
} else if (tagValue.type === "MemberExpression") {
|
||||
tag = convertMemberExpressionToJsx(tagValue);
|
||||
} else {
|
||||
@@ -772,15 +741,27 @@ function codegenInstructionValue(
|
||||
"Expected JSX tag to be an identifier or string, got '%s'",
|
||||
tagValue.type
|
||||
);
|
||||
tag = t.jsxIdentifier(tagValue.value);
|
||||
if (tagValue.value.indexOf(":") >= 0) {
|
||||
const [namespace, name] = tagValue.value.split(":", 2);
|
||||
tag = createJsxNamespacedName(
|
||||
instrValue.tag.loc,
|
||||
createJsxIdentifier(instrValue.tag.loc, namespace),
|
||||
createJsxIdentifier(instrValue.tag.loc, name)
|
||||
);
|
||||
} else {
|
||||
tag = createJsxIdentifier(instrValue.loc, tagValue.value);
|
||||
}
|
||||
}
|
||||
const children =
|
||||
instrValue.children !== null
|
||||
? instrValue.children.map((child) => codegenJsxElement(cx, child))
|
||||
: [];
|
||||
value = t.jsxElement(
|
||||
value = createJsxElement(
|
||||
instrValue.loc,
|
||||
t.jsxOpeningElement(tag, attributes, instrValue.children === null),
|
||||
instrValue.children !== null ? t.jsxClosingElement(tag) : null,
|
||||
instrValue.children !== null
|
||||
? createJsxClosingElement(instrValue.tag.loc, tag)
|
||||
: null,
|
||||
children,
|
||||
instrValue.children === null
|
||||
);
|
||||
@@ -1007,6 +988,51 @@ function codegenInstructionValue(
|
||||
return value;
|
||||
}
|
||||
|
||||
function codegenJsxAttribute(
|
||||
cx: Context,
|
||||
attribute: JsxAttribute
|
||||
): t.JSXAttribute | t.JSXSpreadAttribute {
|
||||
switch (attribute.kind) {
|
||||
case "JsxAttribute": {
|
||||
let propName: t.JSXIdentifier | t.JSXNamespacedName;
|
||||
if (attribute.name.indexOf(":") === -1) {
|
||||
propName = createJsxIdentifier(attribute.place.loc, attribute.name);
|
||||
} else {
|
||||
const [namespace, name] = attribute.name.split(":", 2);
|
||||
propName = createJsxNamespacedName(
|
||||
attribute.place.loc,
|
||||
createJsxIdentifier(attribute.place.loc, namespace),
|
||||
createJsxIdentifier(attribute.place.loc, name)
|
||||
);
|
||||
}
|
||||
const innerValue = codegenPlace(cx, attribute.place);
|
||||
let value;
|
||||
switch (innerValue.type) {
|
||||
case "StringLiteral":
|
||||
case "JSXElement":
|
||||
case "JSXFragment": {
|
||||
value = innerValue;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
value = createJsxExpressionContainer(attribute.place.loc, innerValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return createJsxAttribute(attribute.place.loc, propName, value);
|
||||
}
|
||||
case "JsxSpreadAttribute": {
|
||||
return t.jsxSpreadAttribute(codegenPlace(cx, attribute.argument));
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
attribute,
|
||||
`Unexpected attribute kind '${(attribute as any).kind}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function codegenJsxElement(
|
||||
cx: Context,
|
||||
place: Place
|
||||
@@ -1019,14 +1045,14 @@ function codegenJsxElement(
|
||||
const value = codegenPlace(cx, place);
|
||||
switch (value.type) {
|
||||
case "StringLiteral": {
|
||||
return t.jsxText(value.value);
|
||||
return createJsxText(place.loc, value.value);
|
||||
}
|
||||
case "JSXElement":
|
||||
case "JSXFragment": {
|
||||
return value;
|
||||
}
|
||||
default: {
|
||||
return t.jsxExpressionContainer(value);
|
||||
return createJsxExpressionContainer(place.loc, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1093,6 +1119,7 @@ function codegenLValue(
|
||||
|
||||
function codegenValue(
|
||||
cx: Context,
|
||||
loc: SourceLocation,
|
||||
value: boolean | number | string | null | undefined
|
||||
): t.Expression {
|
||||
if (typeof value === "number") {
|
||||
@@ -1100,7 +1127,7 @@ function codegenValue(
|
||||
} else if (typeof value === "boolean") {
|
||||
return t.booleanLiteral(value);
|
||||
} else if (typeof value === "string") {
|
||||
return t.stringLiteral(value);
|
||||
return createStringLiteral(loc, value);
|
||||
} else if (value === null) {
|
||||
return t.nullLiteral();
|
||||
} else if (value === undefined) {
|
||||
@@ -1126,7 +1153,9 @@ function codegenPlace(cx: Context, place: Place): t.Expression {
|
||||
if (tmp != null) {
|
||||
return tmp;
|
||||
}
|
||||
return convertIdentifier(place.identifier);
|
||||
const identifier = convertIdentifier(place.identifier);
|
||||
identifier.loc = place.loc as any;
|
||||
return identifier;
|
||||
}
|
||||
|
||||
function convertIdentifier(identifier: Identifier): t.Identifier {
|
||||
|
||||
@@ -23,8 +23,8 @@ function component(a) {
|
||||
if (c_0) {
|
||||
x = { a };
|
||||
const y = {};
|
||||
y.x = x.a;
|
||||
|
||||
y.x = x.a;
|
||||
mutate(y);
|
||||
$[0] = a;
|
||||
$[1] = x;
|
||||
|
||||
-2
@@ -24,10 +24,8 @@ function component() {
|
||||
const z = [];
|
||||
const y = {};
|
||||
y.z = z;
|
||||
|
||||
x = {};
|
||||
x.y = y;
|
||||
|
||||
mutate(x.y.z);
|
||||
$[0] = x;
|
||||
} else {
|
||||
|
||||
+1
@@ -21,6 +21,7 @@ function Component(props) {
|
||||
let x;
|
||||
if (c_0) {
|
||||
x = [props.x];
|
||||
|
||||
x[0] = x[0] * 2;
|
||||
x["0"] = x["0"] + 3;
|
||||
$[0] = props.x;
|
||||
|
||||
@@ -24,7 +24,6 @@ function component(a, b) {
|
||||
const y = { a };
|
||||
x = { b };
|
||||
x.y = y;
|
||||
|
||||
mutate(x);
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
|
||||
@@ -30,11 +30,9 @@ function foo(a, b, c) {
|
||||
const $ = React.unstable_useMemoCache(5);
|
||||
|
||||
const [d, t46] = a;
|
||||
|
||||
const [t48] = t46;
|
||||
const { e: t50 } = t48;
|
||||
const { f: g } = t50;
|
||||
|
||||
const { l: t55, o } = b;
|
||||
const { m: t58 } = t55;
|
||||
const [t60] = t58;
|
||||
|
||||
@@ -44,7 +44,6 @@ function foo(a, b, c) {
|
||||
d = $[2];
|
||||
h = $[3];
|
||||
}
|
||||
|
||||
const [t1] = t0;
|
||||
const c_4 = $[4] !== t1;
|
||||
let t2;
|
||||
@@ -59,7 +58,6 @@ function foo(a, b, c) {
|
||||
g = $[6];
|
||||
}
|
||||
const { f } = t2;
|
||||
|
||||
const { l: t51, p } = b;
|
||||
const { m: t3 } = t51;
|
||||
const c_7 = $[7] !== t3;
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import fbt from "fbt";
|
||||
|
||||
function Component(props) {
|
||||
return (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello <fbt:param name="user name">{capitalize(props.name)}</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
fbt: unsupported babel node: Identifier
|
||||
---
|
||||
t0
|
||||
---
|
||||
```
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import fbt from "fbt";
|
||||
|
||||
function Component(props) {
|
||||
return (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello <fbt:param name="user name">{props.name}</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
fbt: unsupported babel node: MemberExpression
|
||||
---
|
||||
props.name
|
||||
---
|
||||
```
|
||||
|
||||
|
||||
-1
@@ -42,7 +42,6 @@ function Component(props) {
|
||||
b = [];
|
||||
const c = {};
|
||||
c.a = a;
|
||||
|
||||
b.push(props.b);
|
||||
$[2] = a;
|
||||
$[3] = props.b;
|
||||
|
||||
-1
@@ -61,7 +61,6 @@ function Component(props) {
|
||||
c = [];
|
||||
const d = {};
|
||||
d.b = b;
|
||||
|
||||
c.push(props.b);
|
||||
$[4] = b;
|
||||
$[5] = props.b;
|
||||
|
||||
-1
@@ -36,7 +36,6 @@ function Component(props) {
|
||||
b = [];
|
||||
const c = {};
|
||||
c.a = a;
|
||||
|
||||
b.push(props.c);
|
||||
$[0] = a;
|
||||
$[1] = props.c;
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import fbt from "fbt";
|
||||
|
||||
function Component(props) {
|
||||
return (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello <fbt:param name="user name">{capitalize(props.name)}</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import fbt from "fbt";
|
||||
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(4);
|
||||
const c_0 = $[0] !== props.name;
|
||||
let t1;
|
||||
if (c_0) {
|
||||
const c_2 = $[2] !== props.name;
|
||||
let t0;
|
||||
if (c_2) {
|
||||
t0 = capitalize(props.name);
|
||||
$[2] = props.name;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
}
|
||||
t1 = fbt._("Hello {user name}", [fbt._param("user name", t0)], {
|
||||
hk: "2zEDKF",
|
||||
});
|
||||
$[0] = props.name;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import fbt from "fbt";
|
||||
|
||||
function Component(props) {
|
||||
return (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello <fbt:param name="user name">{props.name}</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import fbt from "fbt";
|
||||
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(2);
|
||||
const c_0 = $[0] !== props.name;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = fbt._("Hello {user name}", [fbt._param("user name", props.name)], {
|
||||
hk: "2zEDKF",
|
||||
});
|
||||
$[0] = props.name;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
-1
@@ -48,7 +48,6 @@ function Component(props) {
|
||||
|
||||
const x = {};
|
||||
x.b = b;
|
||||
|
||||
const y = mutate(x, d);
|
||||
if (a) {
|
||||
}
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ function foo(a, b, c, d) {
|
||||
} else {
|
||||
x = { c };
|
||||
}
|
||||
|
||||
x.f = 1;
|
||||
$[0] = b;
|
||||
$[1] = c;
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ function foo(a, b, c, d) {
|
||||
} else {
|
||||
x = someObj();
|
||||
}
|
||||
|
||||
x.f = 1;
|
||||
$[0] = a;
|
||||
$[1] = x;
|
||||
|
||||
@@ -31,6 +31,7 @@ function foo(a, b, c, d) {
|
||||
} else {
|
||||
x = someObj();
|
||||
}
|
||||
|
||||
x.f = 1;
|
||||
$[0] = a;
|
||||
$[1] = x;
|
||||
|
||||
+1
@@ -48,6 +48,7 @@ function foo(a, b, c, d) {
|
||||
} else {
|
||||
x = someObj();
|
||||
}
|
||||
|
||||
x.f = 1;
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
|
||||
@@ -18,7 +18,6 @@ function foo(a, b, c) {
|
||||
function foo(a, b, c) {
|
||||
const y = b.c.d;
|
||||
y.z = c.d.e;
|
||||
|
||||
foo(a.b.c);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ function Component(props) {
|
||||
x = {};
|
||||
const y = [];
|
||||
x.y = y;
|
||||
|
||||
child = <Component data={y} />;
|
||||
x.y.push(props.p0);
|
||||
$[0] = props.p0;
|
||||
|
||||
+1
@@ -36,6 +36,7 @@ function TestConditionalSubpath2(props, other) {
|
||||
if (foo(other)) {
|
||||
x.a = props.a;
|
||||
}
|
||||
|
||||
x.b = props.a.b;
|
||||
$[0] = other;
|
||||
$[1] = props.a;
|
||||
|
||||
+1
@@ -34,6 +34,7 @@ function TestConditionalSuperpath2(props, other) {
|
||||
if (foo(other)) {
|
||||
x.b = props.a.b;
|
||||
}
|
||||
|
||||
x.a = props.a;
|
||||
$[0] = other;
|
||||
$[1] = props.a;
|
||||
|
||||
-1
@@ -29,7 +29,6 @@ function foo(a) {
|
||||
if (a) {
|
||||
const y = {};
|
||||
x.y = y;
|
||||
|
||||
mutate(y);
|
||||
} else {
|
||||
let t0;
|
||||
|
||||
@@ -22,7 +22,6 @@ function foo() {
|
||||
const x = [];
|
||||
y = {};
|
||||
y.x = x;
|
||||
|
||||
mutate(x);
|
||||
$[0] = y;
|
||||
} else {
|
||||
|
||||
@@ -25,6 +25,7 @@ function foo() {
|
||||
const a = {};
|
||||
y = a;
|
||||
const x = [];
|
||||
|
||||
y.x = x;
|
||||
|
||||
mutate(a);
|
||||
|
||||
@@ -22,7 +22,6 @@ function foo() {
|
||||
const x = [];
|
||||
y = {};
|
||||
y.x = x;
|
||||
|
||||
mutate(y);
|
||||
$[0] = y;
|
||||
} else {
|
||||
|
||||
@@ -25,6 +25,7 @@ function component() {
|
||||
const p = {};
|
||||
const q = {};
|
||||
const y = {};
|
||||
|
||||
x.y = y;
|
||||
p.y = x.y;
|
||||
q.y = p.y;
|
||||
|
||||
@@ -31,7 +31,6 @@ function Component(props) {
|
||||
} else {
|
||||
y = $[1];
|
||||
}
|
||||
|
||||
const z = (y: Foo);
|
||||
return z;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ function component() {
|
||||
} else {
|
||||
x = $[0];
|
||||
}
|
||||
|
||||
const z = x.t;
|
||||
return z;
|
||||
}
|
||||
|
||||
@@ -43,13 +43,14 @@ function component() {
|
||||
let x;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = {};
|
||||
|
||||
x.t = p;
|
||||
|
||||
x.t = o;
|
||||
$[2] = x;
|
||||
} else {
|
||||
x = $[2];
|
||||
}
|
||||
|
||||
const y = x.t;
|
||||
return y;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user