[compiler] Fix FBT whitespace handling again (again (again))

ghstack-source-id: 00a86e41cf
Pull Request resolved: https://github.com/facebook/react/pull/30451
This commit is contained in:
Mofei Zhang
2024-07-29 13:18:45 -04:00
parent e0acd77aab
commit e215aa1160
9 changed files with 254 additions and 167 deletions
@@ -2113,10 +2113,10 @@ function lowerExpression(
}
props.push({kind: 'JsxAttribute', name: propName, place: value});
}
if (
tag.kind === 'BuiltinTag' &&
(tag.name === 'fbt' || tag.name === 'fbs')
) {
const isFbt =
tag.kind === 'BuiltinTag' && (tag.name === 'fbt' || tag.name === 'fbs');
if (isFbt) {
const tagName = tag.name;
const openingIdentifier = opening.get('name');
const tagIdentifier = openingIdentifier.isJSXIdentifier()
@@ -2168,35 +2168,17 @@ function lowerExpression(
}
}
let children: Array<Place>;
if (
tag.kind === 'BuiltinTag' &&
(tag.name === 'fbt' || tag.name === 'fbs')
) {
children = expr
.get('children')
.map(child => {
if (child.isJSXText()) {
/*
* FBT whitespace normalization differs from standard JSX:
* https://github.com/facebook/fbt/blob/0b4e0d13c30bffd0daa2a75715d606e3587b4e40/packages/babel-plugin-fbt/src/FbtUtil.js#L76-L87
*/
const text = child.node.value.replace(/[^\S\u00A0]+/g, ' ');
return lowerValueToTemporary(builder, {
kind: 'JSXText',
value: text,
loc: child.node.loc ?? GeneratedSource,
});
}
return lowerJsxElement(builder, child);
})
.filter(notNull);
} else {
children = expr
.get('children')
.map(child => lowerJsxElement(builder, child))
.filter(notNull);
}
/**
* Increment fbt counter before traversing into children, as whitespace
* in jsx text is handled differently for fbt subtrees.
*/
isFbt && builder.fbtDepth++;
const children: Array<Place> = expr
.get('children')
.map(child => lowerJsxElement(builder, child))
.filter(notNull);
isFbt && builder.fbtDepth--;
return {
kind: 'JsxExpression',
tag,
@@ -3158,7 +3140,19 @@ function lowerJsxElement(
return lowerExpressionToTemporary(builder, expression);
}
} else if (exprPath.isJSXText()) {
const text = trimJsxText(exprPath.node.value);
let text: string | null;
if (builder.fbtDepth > 0) {
/*
* FBT whitespace normalization differs from standard JSX.
* https://github.com/facebook/fbt/blob/0b4e0d13c30bffd0daa2a75715d606e3587b4e40/packages/babel-plugin-fbt/src/FbtUtil.js#L76-L87
* Since the fbt transform runs after, let's just preserve all
* whitespace in FBT subtrees as is.
*/
text = exprPath.node.value;
} else {
text = trimJsxText(exprPath.node.value);
}
if (text === null) {
return null;
}
@@ -110,6 +110,11 @@ export default class HIRBuilder {
#exceptionHandlerStack: Array<BlockId> = [];
parentFunction: NodePath<t.Function>;
errors: CompilerError = new CompilerError();
/**
* Traversal context: counts the number of `fbt` tag parents
* of the current babel node.
*/
fbtDepth: number = 0;
get nextIdentifierId(): IdentifierId {
return this.#env.nextIdentifierId;
@@ -1,100 +0,0 @@
## Input
```javascript
import fbt from 'fbt';
/**
* Currently fails with the following:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div><span>Jason !</span></div>
* Forget:
* (kind: ok) <div><span>Jason!</span></div>
*/
function Foo(props) {
return (
// prettier-ignore
<div>
<fbt desc={"Dialog to show to user"}>
<span>
<fbt:param name="user name">
{props.name}
</fbt:param>
!
</span>
</fbt>
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{name: 'Jason'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import fbt from "fbt";
/**
* Currently fails with the following:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div><span>Jason !</span></div>
* Forget:
* (kind: ok) <div><span>Jason!</span></div>
*/
function Foo(props) {
const $ = _c(2);
let t0;
if ($[0] !== props.name) {
t0 = (
<div>
{fbt._(
"{=m0}",
[
fbt._implicitParam(
"=m0",
<span>
{fbt._(
"{user name}!",
[
fbt._param(
"user name",
props.name,
),
],
{ hk: "mBBZ9" },
)}
</span>,
),
],
{ hk: "3RVfuk" },
)}
</div>
);
$[0] = props.name;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ name: "Jason" }],
};
```
@@ -1,32 +0,0 @@
import fbt from 'fbt';
/**
* Currently fails with the following:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div><span>Jason !</span></div>
* Forget:
* (kind: ok) <div><span>Jason!</span></div>
*/
function Foo(props) {
return (
// prettier-ignore
<div>
<fbt desc={"Dialog to show to user"}>
<span>
<fbt:param name="user name">
{props.name}
</fbt:param>
!
</span>
</fbt>
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{name: 'Jason'}],
};
@@ -0,0 +1,89 @@
## Input
```javascript
import fbt from 'fbt';
/**
* Note that fbt whitespace rules apply to the entire fbt subtree,
* not just direct children of fbt elements.
* (e.g. here, the JSXText children of the span element also use
* fbt whitespace rules)
*/
function Foo(props) {
return (
<fbt desc={'Dialog to show to user'}>
<span>
<fbt:param name="user name really long description for prettier">
{props.name}
</fbt:param>
!
</span>
</fbt>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{name: 'Jason'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import fbt from "fbt";
/**
* Note that fbt whitespace rules apply to the entire fbt subtree,
* not just direct children of fbt elements.
* (e.g. here, the JSXText children of the span element also use
* fbt whitespace rules)
*/
function Foo(props) {
const $ = _c(2);
let t0;
if ($[0] !== props.name) {
t0 = fbt._(
"{=m0}",
[
fbt._implicitParam(
"=m0",
<span>
{fbt._(
"{user name really long description for prettier} !",
[
fbt._param(
"user name really long description for prettier",
props.name,
),
],
{ hk: "rdgIJ" },
)}
</span>,
),
],
{ hk: "32Ufy5" },
);
$[0] = props.name;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ name: "Jason" }],
};
```
### Eval output
(kind: ok) <span>Jason !</span>
@@ -0,0 +1,26 @@
import fbt from 'fbt';
/**
* Note that fbt whitespace rules apply to the entire fbt subtree,
* not just direct children of fbt elements.
* (e.g. here, the JSXText children of the span element also use
* fbt whitespace rules)
*/
function Foo(props) {
return (
<fbt desc={'Dialog to show to user'}>
<span>
<fbt:param name="user name really long description for prettier">
{props.name}
</fbt:param>
!
</span>
</fbt>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{name: 'Jason'}],
};
@@ -0,0 +1,81 @@
## Input
```javascript
import fbt from 'fbt';
function Foo({name1, name2}) {
return (
<fbt desc="Text that is displayed when two people accepts the user's pull request.">
<fbt:param name="user1">
<span>
<b>{name1}</b>
</span>
</fbt:param>
and
<fbt:param name="user2">
<span>
<b>{name2}</b>
</span>
</fbt:param>
accepted your PR!
</fbt>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{name1: 'Mike', name2: 'Jan'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import fbt from "fbt";
function Foo(t0) {
const $ = _c(3);
const { name1, name2 } = t0;
let t1;
if ($[0] !== name1 || $[1] !== name2) {
t1 = fbt._(
"{user1} and {user2} accepted your PR!",
[
fbt._param(
"user1",
<span>
<b>{name1}</b>
</span>,
),
fbt._param(
"user2",
<span>
<b>{name2}</b>
</span>,
),
],
{ hk: "2PxMie" },
);
$[0] = name1;
$[1] = name2;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ name1: "Mike", name2: "Jan" }],
};
```
### Eval output
(kind: ok) <span><b>Mike</b></span> and <span><b>Jan</b></span> accepted your PR!
@@ -0,0 +1,25 @@
import fbt from 'fbt';
function Foo({name1, name2}) {
return (
<fbt desc="Text that is displayed when two people accepts the user's pull request.">
<fbt:param name="user1">
<span>
<b>{name1}</b>
</span>
</fbt:param>
and
<fbt:param name="user2">
<span>
<b>{name2}</b>
</span>
</fbt:param>
accepted your PR!
</fbt>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{name1: 'Mike', name2: 'Jan'}],
};
@@ -484,7 +484,6 @@ const skipFilter = new Set([
'rules-of-hooks/rules-of-hooks-69521d94fa03',
// bugs
'fbt/bug-fbt-preserve-whitespace-param',
'bug-invalid-hoisting-functionexpr',
'original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block',
'original-reactive-scopes-fork/bug-hoisted-declaration-with-scope',