fbt:param does not allow jsxtext children

Per the title, `<fbt:param>0</fbt:param>` is invalid FBT, you must wrap the text 
in an expression container. But that's not all, `fbt:param` can only have a 
single child, which means we have to strip out the text elements that occur from 
the whitespace in the source.
This commit is contained in:
Joe Savona
2023-09-15 11:30:01 -07:00
parent e5e71dfd59
commit dc4e63e2d5
7 changed files with 117 additions and 8 deletions
@@ -34,6 +34,7 @@ import { eachPatternOperand } from "../HIR/visitors";
import { Err, Ok, Result } from "../Utils/Result";
import { assertExhaustive } from "../Utils/utils";
import { buildReactiveFunction } from "./BuildReactiveFunction";
import { SINGLE_CHILD_FBT_TAGS } from "./MemoizeFbtOperandsInSameScope";
export type CodegenFunction = {
type: "CodegenFunction";
@@ -990,10 +991,34 @@ function codegenInstructionValue(
tag = createJsxIdentifier(instrValue.loc, tagValue.value);
}
}
const children =
instrValue.children !== null
? instrValue.children.map((child) => codegenJsxElement(cx, child))
: [];
let children;
if (
tagValue.type === "StringLiteral" &&
SINGLE_CHILD_FBT_TAGS.has(tagValue.value)
) {
CompilerError.invariant(
instrValue.children != null &&
(instrValue.children.length === 3 ||
instrValue.children.length === 1),
{
loc: instrValue.loc,
reason:
"Expected fbt element to have 3 children (whitespace, content, whitespace) or 1 (content)",
suggestions: null,
description: null,
}
);
if (instrValue.children.length === 3) {
children = [codegenJsxFbtChildElement(cx, instrValue.children[1]!)];
} else {
children = [codegenJsxFbtChildElement(cx, instrValue.children[0]!)];
}
} else {
children =
instrValue.children !== null
? instrValue.children.map((child) => codegenJsxElement(cx, child))
: [];
}
value = createJsxElement(
instrValue.loc,
t.jsxOpeningElement(tag, attributes, instrValue.children === null),
@@ -1330,6 +1355,30 @@ function codegenJsxElement(
}
}
function codegenJsxFbtChildElement(
cx: Context,
place: Place
):
| t.JSXText
| t.JSXExpressionContainer
| t.JSXSpreadChild
| t.JSXElement
| t.JSXFragment {
const value = codegenPlace(cx, place);
switch (value.type) {
case "StringLiteral": {
return createJsxExpressionContainer(place.loc, value);
}
case "JSXElement":
case "JSXFragment": {
return value;
}
default: {
return createJsxExpressionContainer(place.loc, value);
}
}
}
function convertMemberExpressionToJsx(
expr: t.MemberExpression
): t.JSXMemberExpression {
@@ -44,7 +44,8 @@ export function memoizeFbtOperandsInSameScope(fn: ReactiveFunction): void {
}
}
const FBT_TAGS: Set<string> = new Set(["fbt", "fbt:param"]);
export const FBT_TAGS: Set<string> = new Set(["fbt", "fbt:param"]);
export const SINGLE_CHILD_FBT_TAGS: Set<string> = new Set(["fbt:param"]);
class Transform extends ReactiveFunctionVisitor<void> {
// Values that represent *potential* references of `fbt` as a JSX tag name
@@ -0,0 +1,47 @@
## Input
```javascript
import fbt from "fbt";
function Component(props) {
return (
<Foo
value={
<fbt desc="Description of the parameter">
<fbt:param name="value">{"0"}</fbt:param>%
</fbt>
}
/>
);
}
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import fbt from "fbt";
function Component(props) {
const $ = useMemoCache(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = fbt._("{value}%", [fbt._param("value", "0")], { hk: "10F5Cc" });
$[0] = t0;
} else {
t0 = $[0];
}
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Foo value={t0} />;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
```
@@ -0,0 +1,13 @@
import fbt from "fbt";
function Component(props) {
return (
<Foo
value={
<fbt desc="Description of the parameter">
<fbt:param name="value">{"0"}</fbt:param>%
</fbt>
}
/>
);
}
@@ -2,7 +2,6 @@
## Input
```javascript
// @debug
import fbt from "fbt";
function Component({ name, data, icon }) {
@@ -26,7 +25,7 @@ function Component({ name, data, icon }) {
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react"; // @debug
import { unstable_useMemoCache as useMemoCache } from "react";
import fbt from "fbt";
function Component(t39) {
@@ -1,4 +1,3 @@
// @debug
import fbt from "fbt";
function Component({ name, data, icon }) {
@@ -455,6 +455,7 @@ const skipFilter = new Set([
"infer-skip-components-without-hooks-or-jsx",
"class-component-with-render-helper",
"fbtparam-with-jsx-element-content",
"fbtparam-text-must-use-expression-container",
]);
export default skipFilter;