mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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:
@@ -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;
|
||||
|
||||
-100
@@ -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" }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
-32
@@ -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'}],
|
||||
};
|
||||
+89
@@ -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>
|
||||
+26
@@ -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'}],
|
||||
};
|
||||
+81
@@ -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!
|
||||
+25
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user