Skip to content

Commit c5cbd52

Browse files
authored
Merge pull request #41166 from weswigham/empty-jsx-child
Do not consider empty jsx expressions semantically important children
2 parents 76df141 + c29d3f8 commit c5cbd52

13 files changed

+378
-7
lines changed

src/compiler/checker.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -15825,10 +15825,6 @@ namespace ts {
1582515825
}
1582615826
}
1582715827

15828-
function getSemanticJsxChildren(children: NodeArray<JsxChild>) {
15829-
return filter(children, i => !isJsxText(i) || !i.containsOnlyTriviaWhiteSpaces);
15830-
}
15831-
1583215828
function elaborateJsxComponents(
1583315829
node: JsxAttributes,
1583415830
source: Type,
@@ -24985,6 +24981,9 @@ namespace ts {
2498524981
childrenTypes.push(stringType);
2498624982
}
2498724983
}
24984+
else if (child.kind === SyntaxKind.JsxExpression && !child.expression) {
24985+
continue; // empty jsx expressions don't *really* count as present children
24986+
}
2498824987
else {
2498924988
childrenTypes.push(checkExpressionForMutableLocation(child, checkMode));
2499024989
}

src/compiler/transformers/jsx.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ namespace ts {
191191
}
192192

193193
function convertJsxChildrenToChildrenPropObject(children: readonly JsxChild[]) {
194-
const nonWhitespaceChildren = filter(children, c => !isJsxText(c) || !c.containsOnlyTriviaWhiteSpaces);
194+
const nonWhitespaceChildren = getSemanticJsxChildren(children);
195195
if (length(nonWhitespaceChildren) === 1) {
196196
const result = transformJsxChildToExpression(nonWhitespaceChildren[0]);
197197
return result && factory.createObjectLiteralExpression([
@@ -244,7 +244,7 @@ namespace ts {
244244
objectProperties = singleOrUndefined(segments) || emitHelpers().createAssignHelper(segments);
245245
}
246246

247-
return visitJsxOpeningLikeElementOrFragmentJSX(tagName, objectProperties, keyAttr, length(filter(children, c => !isJsxText(c) || !c.containsOnlyTriviaWhiteSpaces)), isChild, location);
247+
return visitJsxOpeningLikeElementOrFragmentJSX(tagName, objectProperties, keyAttr, length(getSemanticJsxChildren(children || emptyArray)), isChild, location);
248248
}
249249

250250
function visitJsxOpeningLikeElementOrFragmentJSX(tagName: Expression, objectProperties: Expression, keyAttr: JsxAttribute | undefined, childrenLength: number, isChild: boolean, location: TextRange) {
@@ -336,7 +336,7 @@ namespace ts {
336336
getImplicitJsxFragmentReference(),
337337
childrenProps || factory.createObjectLiteralExpression([]),
338338
/*keyAttr*/ undefined,
339-
length(filter(children, c => !isJsxText(c) || !c.containsOnlyTriviaWhiteSpaces)),
339+
length(getSemanticJsxChildren(children)),
340340
isChild,
341341
location
342342
);

src/compiler/utilities.ts

+13
Original file line numberDiff line numberDiff line change
@@ -3621,6 +3621,19 @@ namespace ts {
36213621
return -1;
36223622
}
36233623

3624+
export function getSemanticJsxChildren(children: readonly JsxChild[]) {
3625+
return filter(children, i => {
3626+
switch (i.kind) {
3627+
case SyntaxKind.JsxExpression:
3628+
return !!i.expression;
3629+
case SyntaxKind.JsxText:
3630+
return !i.containsOnlyTriviaWhiteSpaces;
3631+
default:
3632+
return true;
3633+
}
3634+
});
3635+
}
3636+
36243637
export function createDiagnosticCollection(): DiagnosticCollection {
36253638
let nonFileDiagnostics = [] as Diagnostic[] as SortedArray<Diagnostic>; // See GH#19873
36263639
const filesWithDiagnostics = [] as string[] as SortedArray<string>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//// [jsxEmptyExpressionNotCountedAsChild.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
import * as React from 'react'
4+
5+
interface Props {
6+
children: React.ReactElement<any>
7+
}
8+
9+
function Wrapper(props: Props) {
10+
return <div>{props.children}</div>
11+
}
12+
13+
const element = (
14+
<Wrapper>
15+
{/* comment */}
16+
<div>Hello</div>
17+
</Wrapper>
18+
)
19+
20+
//// [jsxEmptyExpressionNotCountedAsChild.js]
21+
"use strict";
22+
exports.__esModule = true;
23+
/// <reference path="react16.d.ts" />
24+
var React = require("react");
25+
function Wrapper(props) {
26+
return React.createElement("div", null, props.children);
27+
}
28+
var element = (React.createElement(Wrapper, null,
29+
React.createElement("div", null, "Hello")));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/jsxEmptyExpressionNotCountedAsChild.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import * as React from 'react'
4+
>React : Symbol(React, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 6))
5+
6+
interface Props {
7+
>Props : Symbol(Props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 30))
8+
9+
children: React.ReactElement<any>
10+
>children : Symbol(Props.children, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 3, 17))
11+
>React : Symbol(React, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 6))
12+
>ReactElement : Symbol(React.ReactElement, Decl(react16.d.ts, 135, 9))
13+
}
14+
15+
function Wrapper(props: Props) {
16+
>Wrapper : Symbol(Wrapper, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 5, 1))
17+
>props : Symbol(props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 7, 17))
18+
>Props : Symbol(Props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 30))
19+
20+
return <div>{props.children}</div>
21+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
22+
>props.children : Symbol(Props.children, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 3, 17))
23+
>props : Symbol(props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 7, 17))
24+
>children : Symbol(Props.children, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 3, 17))
25+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
26+
}
27+
28+
const element = (
29+
>element : Symbol(element, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 11, 5))
30+
31+
<Wrapper>
32+
>Wrapper : Symbol(Wrapper, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 5, 1))
33+
34+
{/* comment */}
35+
<div>Hello</div>
36+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
37+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
38+
39+
</Wrapper>
40+
>Wrapper : Symbol(Wrapper, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 5, 1))
41+
42+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/jsxEmptyExpressionNotCountedAsChild.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import * as React from 'react'
4+
>React : typeof React
5+
6+
interface Props {
7+
children: React.ReactElement<any>
8+
>children : React.ReactElement<any>
9+
>React : any
10+
}
11+
12+
function Wrapper(props: Props) {
13+
>Wrapper : (props: Props) => JSX.Element
14+
>props : Props
15+
16+
return <div>{props.children}</div>
17+
><div>{props.children}</div> : JSX.Element
18+
>div : any
19+
>props.children : React.ReactElement<any>
20+
>props : Props
21+
>children : React.ReactElement<any>
22+
>div : any
23+
}
24+
25+
const element = (
26+
>element : JSX.Element
27+
>( <Wrapper> {/* comment */} <div>Hello</div> </Wrapper>) : JSX.Element
28+
29+
<Wrapper>
30+
><Wrapper> {/* comment */} <div>Hello</div> </Wrapper> : JSX.Element
31+
>Wrapper : (props: Props) => JSX.Element
32+
33+
{/* comment */}
34+
<div>Hello</div>
35+
><div>Hello</div> : JSX.Element
36+
>div : any
37+
>div : any
38+
39+
</Wrapper>
40+
>Wrapper : (props: Props) => JSX.Element
41+
42+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//// [jsxEmptyExpressionNotCountedAsChild.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
import * as React from 'react'
4+
5+
interface Props {
6+
children: React.ReactElement<any>
7+
}
8+
9+
function Wrapper(props: Props) {
10+
return <div>{props.children}</div>
11+
}
12+
13+
const element = (
14+
<Wrapper>
15+
{/* comment */}
16+
<div>Hello</div>
17+
</Wrapper>
18+
)
19+
20+
//// [jsxEmptyExpressionNotCountedAsChild.js]
21+
"use strict";
22+
exports.__esModule = true;
23+
var jsx_runtime_1 = require("react/jsx-runtime");
24+
/// <reference path="react16.d.ts" />
25+
var React = require("react");
26+
function Wrapper(props) {
27+
return jsx_runtime_1.jsx("div", { children: props.children }, void 0);
28+
}
29+
var element = (jsx_runtime_1.jsx(Wrapper, { children: jsx_runtime_1.jsx("div", { children: "Hello" }, void 0) }, void 0));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/jsxEmptyExpressionNotCountedAsChild.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import * as React from 'react'
4+
>React : Symbol(React, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 6))
5+
6+
interface Props {
7+
>Props : Symbol(Props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 30))
8+
9+
children: React.ReactElement<any>
10+
>children : Symbol(Props.children, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 3, 17))
11+
>React : Symbol(React, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 6))
12+
>ReactElement : Symbol(React.ReactElement, Decl(react16.d.ts, 135, 9))
13+
}
14+
15+
function Wrapper(props: Props) {
16+
>Wrapper : Symbol(Wrapper, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 5, 1))
17+
>props : Symbol(props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 7, 17))
18+
>Props : Symbol(Props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 30))
19+
20+
return <div>{props.children}</div>
21+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
22+
>props.children : Symbol(Props.children, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 3, 17))
23+
>props : Symbol(props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 7, 17))
24+
>children : Symbol(Props.children, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 3, 17))
25+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
26+
}
27+
28+
const element = (
29+
>element : Symbol(element, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 11, 5))
30+
31+
<Wrapper>
32+
>Wrapper : Symbol(Wrapper, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 5, 1))
33+
34+
{/* comment */}
35+
<div>Hello</div>
36+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
37+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
38+
39+
</Wrapper>
40+
>Wrapper : Symbol(Wrapper, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 5, 1))
41+
42+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/jsxEmptyExpressionNotCountedAsChild.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import * as React from 'react'
4+
>React : typeof React
5+
6+
interface Props {
7+
children: React.ReactElement<any>
8+
>children : React.ReactElement<any>
9+
>React : any
10+
}
11+
12+
function Wrapper(props: Props) {
13+
>Wrapper : (props: Props) => JSX.Element
14+
>props : Props
15+
16+
return <div>{props.children}</div>
17+
><div>{props.children}</div> : JSX.Element
18+
>div : any
19+
>props.children : React.ReactElement<any>
20+
>props : Props
21+
>children : React.ReactElement<any>
22+
>div : any
23+
}
24+
25+
const element = (
26+
>element : JSX.Element
27+
>( <Wrapper> {/* comment */} <div>Hello</div> </Wrapper>) : JSX.Element
28+
29+
<Wrapper>
30+
><Wrapper> {/* comment */} <div>Hello</div> </Wrapper> : JSX.Element
31+
>Wrapper : (props: Props) => JSX.Element
32+
33+
{/* comment */}
34+
<div>Hello</div>
35+
><div>Hello</div> : JSX.Element
36+
>div : any
37+
>div : any
38+
39+
</Wrapper>
40+
>Wrapper : (props: Props) => JSX.Element
41+
42+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//// [jsxEmptyExpressionNotCountedAsChild.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
import * as React from 'react'
4+
5+
interface Props {
6+
children: React.ReactElement<any>
7+
}
8+
9+
function Wrapper(props: Props) {
10+
return <div>{props.children}</div>
11+
}
12+
13+
const element = (
14+
<Wrapper>
15+
{/* comment */}
16+
<div>Hello</div>
17+
</Wrapper>
18+
)
19+
20+
//// [jsxEmptyExpressionNotCountedAsChild.js]
21+
"use strict";
22+
exports.__esModule = true;
23+
var jsx_dev_runtime_1 = require("react/jsx-dev-runtime");
24+
var _jsxFileName = "tests/cases/compiler/jsxEmptyExpressionNotCountedAsChild.tsx";
25+
/// <reference path="react16.d.ts" />
26+
var React = require("react");
27+
function Wrapper(props) {
28+
return jsx_dev_runtime_1.jsxDEV("div", { children: props.children }, void 0, false, { fileName: _jsxFileName, lineNumber: 9, columnNumber: 11 }, this);
29+
}
30+
var element = (jsx_dev_runtime_1.jsxDEV(Wrapper, { children: jsx_dev_runtime_1.jsxDEV("div", { children: "Hello" }, void 0, false, { fileName: _jsxFileName, lineNumber: 15, columnNumber: 6 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 12, columnNumber: 18 }, this));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/jsxEmptyExpressionNotCountedAsChild.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import * as React from 'react'
4+
>React : Symbol(React, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 6))
5+
6+
interface Props {
7+
>Props : Symbol(Props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 30))
8+
9+
children: React.ReactElement<any>
10+
>children : Symbol(Props.children, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 3, 17))
11+
>React : Symbol(React, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 6))
12+
>ReactElement : Symbol(React.ReactElement, Decl(react16.d.ts, 135, 9))
13+
}
14+
15+
function Wrapper(props: Props) {
16+
>Wrapper : Symbol(Wrapper, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 5, 1))
17+
>props : Symbol(props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 7, 17))
18+
>Props : Symbol(Props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 1, 30))
19+
20+
return <div>{props.children}</div>
21+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
22+
>props.children : Symbol(Props.children, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 3, 17))
23+
>props : Symbol(props, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 7, 17))
24+
>children : Symbol(Props.children, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 3, 17))
25+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
26+
}
27+
28+
const element = (
29+
>element : Symbol(element, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 11, 5))
30+
31+
<Wrapper>
32+
>Wrapper : Symbol(Wrapper, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 5, 1))
33+
34+
{/* comment */}
35+
<div>Hello</div>
36+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
37+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
38+
39+
</Wrapper>
40+
>Wrapper : Symbol(Wrapper, Decl(jsxEmptyExpressionNotCountedAsChild.tsx, 5, 1))
41+
42+
)

0 commit comments

Comments
 (0)