diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts index 08e9ebbb73..580b612955 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts @@ -335,7 +335,10 @@ export function printInstructionValue(instrValue: ReactiveValue): string { )}(${instrValue.args.map((arg) => printPattern(arg)).join(", ")})`; break; } - case "JSXText": + case "JSXText": { + value = `JSXText ${JSON.stringify(instrValue.value)}`; + break; + } case "Primitive": { if (instrValue.value === undefined) { value = ""; diff --git a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts index a9cac6ae46..8e9eb97993 100644 --- a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -418,9 +418,9 @@ function codegenTerminal( case "for": { return t.forStatement( codegenForInit(cx, terminal.init), - codegenInstructionValue(cx, terminal.test), + codegenInstructionValueToExpression(cx, terminal.test), terminal.update !== null - ? codegenInstructionValue(cx, terminal.update) + ? codegenInstructionValueToExpression(cx, terminal.update) : null, codegenBlock(cx, terminal.loop) ); @@ -504,7 +504,7 @@ function codegenTerminal( createVariableDeclaration(iterableItem.value.loc, varDeclKind, [ t.variableDeclarator(lval, null), ]), - codegenInstructionValue(cx, iterableCollection.value), + codegenInstructionValueToExpression(cx, iterableCollection.value), codegenBlock(cx, terminal.loop) ); } else { @@ -514,13 +514,13 @@ function codegenTerminal( createVariableDeclaration(iterableItem.value.loc, varDeclKind, [ t.variableDeclarator(lval, null), ]), - codegenInstructionValue(cx, iterableCollection.value), + codegenInstructionValueToExpression(cx, iterableCollection.value), codegenBlock(cx, terminal.loop) ); } } case "if": { - const test = codegenPlace(cx, terminal.test); + const test = codegenPlaceToExpression(cx, terminal.test); const consequent = codegenBlock(cx, terminal.consequent); let alternate: t.Statement | null = null; if (terminal.alternate !== null) { @@ -532,7 +532,7 @@ function codegenTerminal( return t.ifStatement(test, consequent, alternate); } case "return": { - const value = codegenPlace(cx, terminal.value); + const value = codegenPlaceToExpression(cx, terminal.value); if (value.type === "Identifier" && value.name === "undefined") { // Use implicit undefined return t.returnStatement(); @@ -541,24 +541,26 @@ function codegenTerminal( } case "switch": { return t.switchStatement( - codegenPlace(cx, terminal.test), + codegenPlaceToExpression(cx, terminal.test), terminal.cases.map((case_) => { const test = - case_.test !== null ? codegenPlace(cx, case_.test) : null; + case_.test !== null + ? codegenPlaceToExpression(cx, case_.test) + : null; const block = codegenBlock(cx, case_.block!); return t.switchCase(test, [block]); }) ); } case "throw": { - return t.throwStatement(codegenPlace(cx, terminal.value)); + return t.throwStatement(codegenPlaceToExpression(cx, terminal.value)); } case "do-while": { - const test = codegenInstructionValue(cx, terminal.test); + const test = codegenInstructionValueToExpression(cx, terminal.test); return t.doWhileStatement(test, codegenBlock(cx, terminal.loop)); } case "while": { - const test = codegenInstructionValue(cx, terminal.test); + const test = codegenInstructionValueToExpression(cx, terminal.test); return t.whileStatement(test, codegenBlock(cx, terminal.loop)); } case "label": { @@ -603,10 +605,10 @@ function codegenInstructionNullable( ? InstructionKind.Reassign : kind; lvalue = instr.value.lvalue.place; - value = codegenPlace(cx, instr.value.value); + value = codegenPlaceToExpression(cx, instr.value.value); } else if (instr.value.kind === "StoreContext") { lvalue = instr.value.lvalue.place; - value = codegenPlace(cx, instr.value.value); + value = codegenPlaceToExpression(cx, instr.value.value); } else if ( instr.value.kind === "DeclareLocal" || instr.value.kind === "DeclareContext" @@ -643,7 +645,7 @@ function codegenInstructionNullable( } else if (hasReasign) { kind = InstructionKind.Reassign; } - value = codegenPlace(cx, instr.value.value); + value = codegenPlaceToExpression(cx, instr.value.value); } switch (kind) { case InstructionKind.Const: { @@ -749,7 +751,7 @@ function codegenForInit( }); return declaration; } else { - return codegenInstructionValue(cx, init); + return codegenInstructionValueToExpression(cx, init); } } @@ -804,7 +806,7 @@ const createJsxText = withLoc(t.jsxText); const createJsxClosingElement = withLoc(t.jsxClosingElement); const createStringLiteral = withLoc(t.stringLiteral); -type Temporaries = Map; +type Temporaries = Map; function codegenLabel(id: BlockId): string { return `bb${id}`; @@ -813,48 +815,69 @@ function codegenLabel(id: BlockId): string { function codegenInstruction( cx: Context, instr: ReactiveInstruction, - value: t.Expression + value: t.Expression | t.JSXText ): t.Statement { if (t.isStatement(value)) { return value; } if (instr.lvalue === null) { - return t.expressionStatement(value); + return t.expressionStatement(convertValueToExpression(value)); } if (instr.lvalue.identifier.name === null) { // temporary cx.temp.set(instr.lvalue.identifier.id, value); return t.emptyStatement(); } else { + const expressionValue = convertValueToExpression(value); if (cx.hasDeclared(instr.lvalue.identifier)) { return createExpressionStatement( instr.loc, t.assignmentExpression( "=", convertIdentifier(instr.lvalue.identifier), - value + expressionValue ) ); } else { return createVariableDeclaration(instr.loc, "const", [ - t.variableDeclarator(convertIdentifier(instr.lvalue.identifier), value), + t.variableDeclarator( + convertIdentifier(instr.lvalue.identifier), + expressionValue + ), ]); } } } -function codegenInstructionValue( +function convertValueToExpression( + value: t.JSXText | t.Expression +): t.Expression { + if (value.type === "JSXText") { + return createStringLiteral(value.loc, value.value); + } + return value; +} + +function codegenInstructionValueToExpression( cx: Context, instrValue: ReactiveValue ): t.Expression { - let value: t.Expression; + const value = codegenInstructionValue(cx, instrValue); + return convertValueToExpression(value); +} + +function codegenInstructionValue( + cx: Context, + instrValue: ReactiveValue +): t.Expression | t.JSXText { + let value: t.Expression | t.JSXText; switch (instrValue.kind) { case "ArrayExpression": { const elements = instrValue.elements.map((element) => { if (element.kind === "Identifier") { - return codegenPlace(cx, element); + return codegenPlaceToExpression(cx, element); } else if (element.kind === "Spread") { - return t.spreadElement(codegenPlace(cx, element.place)); + return t.spreadElement(codegenPlaceToExpression(cx, element.place)); } else { return null; } @@ -863,8 +886,8 @@ function codegenInstructionValue( break; } case "BinaryExpression": { - const left = codegenPlace(cx, instrValue.left); - const right = codegenPlace(cx, instrValue.right); + const left = codegenPlaceToExpression(cx, instrValue.left); + const right = codegenPlaceToExpression(cx, instrValue.right); value = createBinaryExpression( instrValue.loc, instrValue.operator, @@ -876,7 +899,7 @@ function codegenInstructionValue( case "UnaryExpression": { value = t.unaryExpression( instrValue.operator as "throw", // todo - codegenPlace(cx, instrValue.value) + codegenPlaceToExpression(cx, instrValue.value) ); break; } @@ -885,13 +908,16 @@ function codegenInstructionValue( break; } case "CallExpression": { - const callee = codegenPlace(cx, instrValue.callee); + const callee = codegenPlaceToExpression(cx, instrValue.callee); const args = instrValue.args.map((arg) => codegenArgument(cx, arg)); value = createCallExpression(instrValue.loc, callee, args); break; } case "OptionalExpression": { - const optionalValue = codegenInstructionValue(cx, instrValue.value); + const optionalValue = codegenInstructionValueToExpression( + cx, + instrValue.value + ); switch (optionalValue.type) { case "OptionalCallExpression": case "CallExpression": { @@ -938,7 +964,7 @@ function codegenInstructionValue( break; } case "MethodCall": { - const memberExpr = codegenPlace(cx, instrValue.property); + const memberExpr = codegenPlaceToExpression(cx, instrValue.property); CompilerError.invariant( t.isMemberExpression(memberExpr) || t.isOptionalMemberExpression(memberExpr), @@ -954,7 +980,7 @@ function codegenInstructionValue( CompilerError.invariant( t.isNodesEquivalent( memberExpr.object, - codegenPlace(cx, instrValue.receiver) + codegenPlaceToExpression(cx, instrValue.receiver) ), { reason: @@ -970,7 +996,7 @@ function codegenInstructionValue( break; } case "NewExpression": { - const callee = codegenPlace(cx, instrValue.callee); + const callee = codegenPlaceToExpression(cx, instrValue.callee); const args = instrValue.args.map((arg) => codegenArgument(cx, arg)); value = t.newExpression(callee, args); break; @@ -983,7 +1009,7 @@ function codegenInstructionValue( switch (property.type) { case "property": { - const value = codegenPlace(cx, property.place); + const value = codegenPlaceToExpression(cx, property.place); properties.push( t.objectProperty( key, @@ -1029,14 +1055,16 @@ function codegenInstructionValue( ); } } else { - properties.push(t.spreadElement(codegenPlace(cx, property.place))); + properties.push( + t.spreadElement(codegenPlaceToExpression(cx, property.place)) + ); } } value = t.objectExpression(properties); break; } case "JSXText": { - value = createStringLiteral(instrValue.loc, instrValue.value); + value = createJsxText(instrValue.loc, instrValue.value); break; } case "JsxExpression": { @@ -1046,7 +1074,7 @@ function codegenInstructionValue( } let tagValue = instrValue.tag.kind === "Identifier" - ? codegenPlace(cx, instrValue.tag) + ? codegenPlaceToExpression(cx, instrValue.tag) : t.stringLiteral(instrValue.tag.name); let tag: t.JSXIdentifier | t.JSXNamespacedName | t.JSXMemberExpression; if (tagValue.type === "Identifier") { @@ -1130,15 +1158,15 @@ function codegenInstructionValue( value = t.assignmentExpression( "=", t.memberExpression( - codegenPlace(cx, instrValue.object), + codegenPlaceToExpression(cx, instrValue.object), t.identifier(instrValue.property) ), - codegenPlace(cx, instrValue.value) + codegenPlaceToExpression(cx, instrValue.value) ); break; } case "PropertyLoad": { - const object = codegenPlace(cx, instrValue.object); + const object = codegenPlaceToExpression(cx, instrValue.object); // We currently only lower single chains of optional memberexpr. // (See BuildHIR.ts for more detail.) value = t.memberExpression( @@ -1152,7 +1180,7 @@ function codegenInstructionValue( value = t.unaryExpression( "delete", t.memberExpression( - codegenPlace(cx, instrValue.object), + codegenPlaceToExpression(cx, instrValue.object), t.identifier(instrValue.property) ) ); @@ -1162,17 +1190,17 @@ function codegenInstructionValue( value = t.assignmentExpression( "=", t.memberExpression( - codegenPlace(cx, instrValue.object), - codegenPlace(cx, instrValue.property), + codegenPlaceToExpression(cx, instrValue.object), + codegenPlaceToExpression(cx, instrValue.property), true ), - codegenPlace(cx, instrValue.value) + codegenPlaceToExpression(cx, instrValue.value) ); break; } case "ComputedLoad": { - const object = codegenPlace(cx, instrValue.object); - const property = codegenPlace(cx, instrValue.property); + const object = codegenPlaceToExpression(cx, instrValue.object); + const property = codegenPlaceToExpression(cx, instrValue.property); value = t.memberExpression(object, property, true); break; } @@ -1180,8 +1208,8 @@ function codegenInstructionValue( value = t.unaryExpression( "delete", t.memberExpression( - codegenPlace(cx, instrValue.object), - codegenPlace(cx, instrValue.property), + codegenPlaceToExpression(cx, instrValue.object), + codegenPlaceToExpression(cx, instrValue.property), true ) ); @@ -1189,7 +1217,7 @@ function codegenInstructionValue( } case "LoadLocal": case "LoadContext": { - value = codegenPlace(cx, instrValue.place); + value = codegenPlaceToExpression(cx, instrValue.place); break; } case "FunctionExpression": { @@ -1223,14 +1251,14 @@ function codegenInstructionValue( case "TaggedTemplateExpression": { value = createTaggedTemplateExpression( instrValue.loc, - codegenPlace(cx, instrValue.tag), + codegenPlaceToExpression(cx, instrValue.tag), t.templateLiteral([t.templateElement(instrValue.value)], []) ); break; } case "TypeCastExpression": { value = t.typeCastExpression( - codegenPlace(cx, instrValue.value), + codegenPlaceToExpression(cx, instrValue.value), instrValue.type ); break; @@ -1239,17 +1267,17 @@ function codegenInstructionValue( value = createLogicalExpression( instrValue.loc, instrValue.operator, - codegenInstructionValue(cx, instrValue.left), - codegenInstructionValue(cx, instrValue.right) + codegenInstructionValueToExpression(cx, instrValue.left), + codegenInstructionValueToExpression(cx, instrValue.right) ); break; } case "ConditionalExpression": { value = createConditionalExpression( instrValue.loc, - codegenInstructionValue(cx, instrValue.test), - codegenInstructionValue(cx, instrValue.consequent), - codegenInstructionValue(cx, instrValue.alternate) + codegenInstructionValueToExpression(cx, instrValue.test), + codegenInstructionValueToExpression(cx, instrValue.consequent), + codegenInstructionValueToExpression(cx, instrValue.alternate) ); break; } @@ -1288,11 +1316,11 @@ function codegenInstructionValue( } }); if (expressions.length === 0) { - value = codegenInstructionValue(cx, instrValue.value); + value = codegenInstructionValueToExpression(cx, instrValue.value); } else { value = createSequenceExpression(instrValue.loc, [ ...expressions, - codegenInstructionValue(cx, instrValue.value), + codegenInstructionValueToExpression(cx, instrValue.value), ]); } break; @@ -1301,7 +1329,7 @@ function codegenInstructionValue( value = createTemplateLiteral( instrValue.loc, instrValue.quasis.map((q) => t.templateElement(q)), - instrValue.subexprs.map((p) => codegenPlace(cx, p)) + instrValue.subexprs.map((p) => codegenPlaceToExpression(cx, p)) ); break; } @@ -1314,21 +1342,21 @@ function codegenInstructionValue( break; } case "Await": { - value = t.awaitExpression(codegenPlace(cx, instrValue.value)); + value = t.awaitExpression(codegenPlaceToExpression(cx, instrValue.value)); break; } case "NextIterableOf": { - value = codegenPlace(cx, instrValue.value); + value = codegenPlaceToExpression(cx, instrValue.value); break; } case "NextPropertyOf": { - value = codegenPlace(cx, instrValue.value); + value = codegenPlaceToExpression(cx, instrValue.value); break; } case "PostfixUpdate": { value = t.updateExpression( instrValue.operation, - codegenPlace(cx, instrValue.lvalue), + codegenPlaceToExpression(cx, instrValue.lvalue), false ); break; @@ -1336,7 +1364,7 @@ function codegenInstructionValue( case "PrefixUpdate": { value = t.updateExpression( instrValue.operation, - codegenPlace(cx, instrValue.lvalue), + codegenPlaceToExpression(cx, instrValue.lvalue), true ); break; @@ -1382,7 +1410,7 @@ function codegenJsxAttribute( createJsxIdentifier(attribute.place.loc, name) ); } - const innerValue = codegenPlace(cx, attribute.place); + const innerValue = codegenPlaceToExpression(cx, attribute.place); let value; switch (innerValue.type) { case "StringLiteral": { @@ -1401,7 +1429,9 @@ function codegenJsxAttribute( return createJsxAttribute(attribute.place.loc, propName, value); } case "JsxSpreadAttribute": { - return t.jsxSpreadAttribute(codegenPlace(cx, attribute.argument)); + return t.jsxSpreadAttribute( + codegenPlaceToExpression(cx, attribute.argument) + ); } default: { assertExhaustive( @@ -1423,7 +1453,7 @@ function codegenJsxElement( | t.JSXFragment { const value = codegenPlace(cx, place); switch (value.type) { - case "StringLiteral": { + case "JSXText": { return createJsxText(place.loc, value.value); } case "JSXElement": @@ -1448,6 +1478,7 @@ function codegenJsxFbtChildElement( const value = codegenPlace(cx, place); switch (value.type) { // fbt:param only allows JSX element or expression container as children + case "JSXText": case "JSXElement": { return value; } @@ -1565,13 +1596,18 @@ function codegenArgument( arg: Place | SpreadPattern ): t.Expression | t.SpreadElement { if (arg.kind === "Identifier") { - return codegenPlace(cx, arg); + return codegenPlaceToExpression(cx, arg); } else { - return t.spreadElement(codegenPlace(cx, arg.place)); + return t.spreadElement(codegenPlaceToExpression(cx, arg.place)); } } -function codegenPlace(cx: Context, place: Place): t.Expression { +function codegenPlaceToExpression(cx: Context, place: Place): t.Expression { + const value = codegenPlace(cx, place); + return convertValueToExpression(value); +} + +function codegenPlace(cx: Context, place: Place): t.Expression | t.JSXText { let tmp = cx.temp.get(place.identifier.id); if (tmp != null) { return tmp; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbt-preserve-jsxtext.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbt-preserve-jsxtext.expect.md new file mode 100644 index 0000000000..1bb46e5cbc --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbt-preserve-jsxtext.expect.md @@ -0,0 +1,54 @@ + +## Input + +```javascript +import fbt from "fbt"; + +function Foo(props) { + return ( + + {" "} + {props.value} + {", "} + + ); +} + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +import fbt from "fbt"; + +function Foo(props) { + const $ = useMemoCache(2); + const c_0 = $[0] !== props.value; + let t0; + if (c_0) { + t0 = fbt._( + { "0": "hello {value},", "1": "goodbye {value}," }, + [ + fbt._enum(props.value ? "0" : "1", { "0": "hello", "1": "goodbye" }), + fbt._param( + "value", + + props.value + ), + ], + { hk: "Ri5kJ" } + ); + $[0] = props.value; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbt-preserve-jsxtext.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbt-preserve-jsxtext.js new file mode 100644 index 0000000000..f28cb7a1c8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbt-preserve-jsxtext.js @@ -0,0 +1,14 @@ +import fbt from "fbt"; + +function Foo(props) { + return ( + + {" "} + {props.value} + {", "} + + ); +} diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-fragment.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-fragment.expect.md index 70f2ad78a5..d2ba9cbde0 100644 --- a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-fragment.expect.md +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-fragment.expect.md @@ -43,8 +43,7 @@ function Foo(props) { if (c_1) { t1 = ( <> - Hello {props.greeting} - {t0} + Hello {props.greeting} {t0} ); $[1] = props.greeting; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/preserve-jsxtext-stringliteral-distinction.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/preserve-jsxtext-stringliteral-distinction.expect.md new file mode 100644 index 0000000000..f8ed286d63 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/preserve-jsxtext-stringliteral-distinction.expect.md @@ -0,0 +1,38 @@ + +## Input + +```javascript +function Foo() { + return
{", "}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Foo() { + const $ = useMemoCache(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
{", "}
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/preserve-jsxtext-stringliteral-distinction.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/preserve-jsxtext-stringliteral-distinction.js new file mode 100644 index 0000000000..461ac9017f --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/preserve-jsxtext-stringliteral-distinction.js @@ -0,0 +1,8 @@ +function Foo() { + return
{", "}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/snap/src/compiler-worker.ts b/compiler/packages/snap/src/compiler-worker.ts index 0a54470795..ca52ae6f15 100644 --- a/compiler/packages/snap/src/compiler-worker.ts +++ b/compiler/packages/snap/src/compiler-worker.ts @@ -88,7 +88,7 @@ export async function compile( runReactForgetBabelPlugin ).code; } catch (e) { - e.message = e.message.replace(/\u001b[^m]*m/g, ''); + e.message = e.message.replace(/\u001b[^m]*m/g, ""); error = e; } diff --git a/compiler/packages/sprout/src/SproutTodoFilter.ts b/compiler/packages/sprout/src/SproutTodoFilter.ts index 142e2e9d85..89c7ba7333 100644 --- a/compiler/packages/sprout/src/SproutTodoFilter.ts +++ b/compiler/packages/sprout/src/SproutTodoFilter.ts @@ -459,6 +459,7 @@ const skipFilter = new Set([ "fbtparam-with-jsx-element-content", "fbtparam-text-must-use-expression-container", "fbtparam-with-jsx-fragment-value", + "fbt-preserve-jsxtext", ]); export default skipFilter;