Skip to content

Commit a1a2006

Browse files
authored
Merge pull request #15160 from Microsoft/master-jsxChildren
[Master] Type checking JSX children
2 parents d190530 + e7e13ec commit a1a2006

File tree

77 files changed

+3064
-239
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+3064
-239
lines changed

Diff for: src/compiler/checker.ts

+153-76
Large diffs are not rendered by default.

Diff for: src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -2119,6 +2119,10 @@
21192119
"category": "Error",
21202120
"code": 2709
21212121
},
2122+
"'{0}' are specified twice. The attribute named '{0}' will be overwritten.": {
2123+
"category": "Error",
2124+
"code": 2710
2125+
},
21222126

21232127
"Import declaration '{0}' is using private name '{1}'.": {
21242128
"category": "Error",

Diff for: src/compiler/emitter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2457,7 +2457,7 @@ namespace ts {
24572457
let indentation: number;
24582458
for (const line of lines) {
24592459
for (let i = 0; i < line.length && (indentation === undefined || i < indentation); i++) {
2460-
if (!isWhiteSpace(line.charCodeAt(i))) {
2460+
if (!isWhiteSpaceLike(line.charCodeAt(i))) {
24612461
if (indentation === undefined || i < indentation) {
24622462
indentation = i;
24632463
break;

Diff for: src/compiler/parser.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -3828,13 +3828,15 @@ namespace ts {
38283828

38293829
function parseJsxText(): JsxText {
38303830
const node = <JsxText>createNode(SyntaxKind.JsxText, scanner.getStartPos());
3831+
node.containsOnlyWhiteSpaces = currentToken === SyntaxKind.JsxTextAllWhiteSpaces;
38313832
currentToken = scanner.scanJsxToken();
38323833
return finishNode(node);
38333834
}
38343835

38353836
function parseJsxChild(): JsxChild {
38363837
switch (token()) {
38373838
case SyntaxKind.JsxText:
3839+
case SyntaxKind.JsxTextAllWhiteSpaces:
38383840
return parseJsxText();
38393841
case SyntaxKind.OpenBraceToken:
38403842
return parseJsxExpression(/*inExpressionContext*/ false);
@@ -3864,7 +3866,10 @@ namespace ts {
38643866
else if (token() === SyntaxKind.ConflictMarkerTrivia) {
38653867
break;
38663868
}
3867-
result.push(parseJsxChild());
3869+
const child = parseJsxChild();
3870+
if (child) {
3871+
result.push(child);
3872+
}
38683873
}
38693874

38703875
result.end = scanner.getTokenPos();

Diff for: src/compiler/scanner.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ namespace ts {
367367
return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position);
368368
}
369369

370-
export function isWhiteSpace(ch: number): boolean {
370+
export function isWhiteSpaceLike(ch: number): boolean {
371371
return isWhiteSpaceSingleLine(ch) || isLineBreak(ch);
372372
}
373373

@@ -512,7 +512,7 @@ namespace ts {
512512
break;
513513

514514
default:
515-
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpace(ch))) {
515+
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) {
516516
pos++;
517517
continue;
518518
}
@@ -694,7 +694,7 @@ namespace ts {
694694
}
695695
break scan;
696696
default:
697-
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpace(ch))) {
697+
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) {
698698
if (hasPendingCommentRange && isLineBreak(ch)) {
699699
pendingHasTrailingNewLine = true;
700700
}
@@ -1727,8 +1727,12 @@ namespace ts {
17271727
return token = SyntaxKind.OpenBraceToken;
17281728
}
17291729

1730+
// First non-whitespace character on this line.
1731+
let firstNonWhitespace = 0;
1732+
// These initial values are special because the first line is:
1733+
// firstNonWhitespace = 0 to indicate that we want leading whitspace,
1734+
17301735
while (pos < end) {
1731-
pos++;
17321736
char = text.charCodeAt(pos);
17331737
if (char === CharacterCodes.openBrace) {
17341738
break;
@@ -1740,8 +1744,23 @@ namespace ts {
17401744
}
17411745
break;
17421746
}
1747+
1748+
// FirstNonWhitespace is 0, then we only see whitespaces so far. If we see a linebreak, we want to ignore that whitespaces.
1749+
// i.e (- : whitespace)
1750+
// <div>----
1751+
// </div> becomes <div></div>
1752+
//
1753+
// <div>----</div> becomes <div>----</div>
1754+
if (isLineBreak(char) && firstNonWhitespace === 0) {
1755+
firstNonWhitespace = -1;
1756+
}
1757+
else if (!isWhiteSpaceLike(char)) {
1758+
firstNonWhitespace = pos;
1759+
}
1760+
pos++;
17431761
}
1744-
return token = SyntaxKind.JsxText;
1762+
1763+
return firstNonWhitespace === -1 ? SyntaxKind.JsxTextAllWhiteSpaces : SyntaxKind.JsxText;
17451764
}
17461765

17471766
// Scans a JSX identifier; these differ from normal identifiers in that

Diff for: src/compiler/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace ts {
1010

1111
/** ES6 Map interface. */
1212
export interface Map<T> {
13-
get(key: string): T;
13+
get(key: string): T | undefined;
1414
has(key: string): boolean;
1515
set(key: string, value: T): this;
1616
delete(key: string): boolean;
@@ -65,6 +65,7 @@ namespace ts {
6565
NumericLiteral,
6666
StringLiteral,
6767
JsxText,
68+
JsxTextAllWhiteSpaces,
6869
RegularExpressionLiteral,
6970
NoSubstitutionTemplateLiteral,
7071
// Pseudo-literals
@@ -1572,6 +1573,7 @@ namespace ts {
15721573

15731574
export interface JsxText extends Node {
15741575
kind: SyntaxKind.JsxText;
1576+
containsOnlyWhiteSpaces: boolean;
15751577
parent?: JsxElement;
15761578
}
15771579

Diff for: src/services/formatting/smartIndenter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ namespace ts.formatting {
5454
let current = position;
5555
while (current > 0) {
5656
const char = sourceFile.text.charCodeAt(current);
57-
if (!isWhiteSpace(char)) {
57+
if (!isWhiteSpaceLike(char)) {
5858
break;
5959
}
6060
current--;

Diff for: src/services/textChanges.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ namespace ts.textChanges {
608608
if (force || !isTrivia(s)) {
609609
this.lastNonTriviaPosition = this.writer.getTextPos();
610610
let i = 0;
611-
while (isWhiteSpace(s.charCodeAt(s.length - i - 1))) {
611+
while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) {
612612
i++;
613613
}
614614
// trim trailing whitespaces

Diff for: src/services/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1380,7 +1380,7 @@ namespace ts {
13801380
}
13811381

13821382
export function getFirstNonSpaceCharacterPosition(text: string, position: number) {
1383-
while (isWhiteSpace(text.charCodeAt(position))) {
1383+
while (isWhiteSpaceLike(text.charCodeAt(position))) {
13841384
position += 1;
13851385
}
13861386
return position;
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//// [file.tsx]
2+
import React = require('react');
3+
4+
interface Prop {
5+
a: number,
6+
b: string,
7+
children: string | JSX.Element
8+
}
9+
10+
function Comp(p: Prop) {
11+
return <div>{p.b}</div>;
12+
}
13+
14+
// OK
15+
let k = <Comp a={10} b="hi" children ="lol" />;
16+
let k1 =
17+
<Comp a={10} b="hi">
18+
hi hi hi!
19+
</Comp>;
20+
let k2 =
21+
<Comp a={10} b="hi">
22+
<div>hi hi hi!</div>
23+
</Comp>;
24+
25+
//// [file.jsx]
26+
"use strict";
27+
exports.__esModule = true;
28+
var React = require("react");
29+
function Comp(p) {
30+
return <div>{p.b}</div>;
31+
}
32+
// OK
33+
var k = <Comp a={10} b="hi" children="lol"/>;
34+
var k1 = <Comp a={10} b="hi">
35+
hi hi hi!
36+
</Comp>;
37+
var k2 = <Comp a={10} b="hi">
38+
<div>hi hi hi!</div>
39+
</Comp>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
import React = require('react');
3+
>React : Symbol(React, Decl(file.tsx, 0, 0))
4+
5+
interface Prop {
6+
>Prop : Symbol(Prop, Decl(file.tsx, 0, 32))
7+
8+
a: number,
9+
>a : Symbol(Prop.a, Decl(file.tsx, 2, 16))
10+
11+
b: string,
12+
>b : Symbol(Prop.b, Decl(file.tsx, 3, 14))
13+
14+
children: string | JSX.Element
15+
>children : Symbol(Prop.children, Decl(file.tsx, 4, 14))
16+
>JSX : Symbol(JSX, Decl(react.d.ts, 2352, 1))
17+
>Element : Symbol(JSX.Element, Decl(react.d.ts, 2355, 27))
18+
}
19+
20+
function Comp(p: Prop) {
21+
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
22+
>p : Symbol(p, Decl(file.tsx, 8, 14))
23+
>Prop : Symbol(Prop, Decl(file.tsx, 0, 32))
24+
25+
return <div>{p.b}</div>;
26+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
27+
>p.b : Symbol(Prop.b, Decl(file.tsx, 3, 14))
28+
>p : Symbol(p, Decl(file.tsx, 8, 14))
29+
>b : Symbol(Prop.b, Decl(file.tsx, 3, 14))
30+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
31+
}
32+
33+
// OK
34+
let k = <Comp a={10} b="hi" children ="lol" />;
35+
>k : Symbol(k, Decl(file.tsx, 13, 3))
36+
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
37+
>a : Symbol(a, Decl(file.tsx, 13, 13))
38+
>b : Symbol(b, Decl(file.tsx, 13, 20))
39+
>children : Symbol(children, Decl(file.tsx, 13, 27))
40+
41+
let k1 =
42+
>k1 : Symbol(k1, Decl(file.tsx, 14, 3))
43+
44+
<Comp a={10} b="hi">
45+
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
46+
>a : Symbol(a, Decl(file.tsx, 15, 9))
47+
>b : Symbol(b, Decl(file.tsx, 15, 16))
48+
49+
hi hi hi!
50+
</Comp>;
51+
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
52+
53+
let k2 =
54+
>k2 : Symbol(k2, Decl(file.tsx, 18, 3))
55+
56+
<Comp a={10} b="hi">
57+
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
58+
>a : Symbol(a, Decl(file.tsx, 19, 9))
59+
>b : Symbol(b, Decl(file.tsx, 19, 16))
60+
61+
<div>hi hi hi!</div>
62+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
63+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
64+
65+
</Comp>;
66+
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
67+
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
import React = require('react');
3+
>React : typeof React
4+
5+
interface Prop {
6+
>Prop : Prop
7+
8+
a: number,
9+
>a : number
10+
11+
b: string,
12+
>b : string
13+
14+
children: string | JSX.Element
15+
>children : string | JSX.Element
16+
>JSX : any
17+
>Element : JSX.Element
18+
}
19+
20+
function Comp(p: Prop) {
21+
>Comp : (p: Prop) => JSX.Element
22+
>p : Prop
23+
>Prop : Prop
24+
25+
return <div>{p.b}</div>;
26+
><div>{p.b}</div> : JSX.Element
27+
>div : any
28+
>p.b : string
29+
>p : Prop
30+
>b : string
31+
>div : any
32+
}
33+
34+
// OK
35+
let k = <Comp a={10} b="hi" children ="lol" />;
36+
>k : JSX.Element
37+
><Comp a={10} b="hi" children ="lol" /> : JSX.Element
38+
>Comp : (p: Prop) => JSX.Element
39+
>a : number
40+
>10 : 10
41+
>b : string
42+
>children : string
43+
44+
let k1 =
45+
>k1 : JSX.Element
46+
47+
<Comp a={10} b="hi">
48+
><Comp a={10} b="hi"> hi hi hi! </Comp> : JSX.Element
49+
>Comp : (p: Prop) => JSX.Element
50+
>a : number
51+
>10 : 10
52+
>b : string
53+
54+
hi hi hi!
55+
</Comp>;
56+
>Comp : (p: Prop) => JSX.Element
57+
58+
let k2 =
59+
>k2 : JSX.Element
60+
61+
<Comp a={10} b="hi">
62+
><Comp a={10} b="hi"> <div>hi hi hi!</div> </Comp> : JSX.Element
63+
>Comp : (p: Prop) => JSX.Element
64+
>a : number
65+
>10 : 10
66+
>b : string
67+
68+
<div>hi hi hi!</div>
69+
><div>hi hi hi!</div> : JSX.Element
70+
>div : any
71+
>div : any
72+
73+
</Comp>;
74+
>Comp : (p: Prop) => JSX.Element
75+
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//// [file.tsx]
2+
declare module JSX {
3+
interface Element { }
4+
interface ElementAttributesProperty { props: {} }
5+
interface IntrinsicElements {
6+
div: any;
7+
h2: any;
8+
h1: any;
9+
}
10+
}
11+
12+
class Button {
13+
props: {}
14+
render() {
15+
return (<div>My Button</div>)
16+
}
17+
}
18+
19+
// OK
20+
let k1 = <div> <h2> Hello </h2> <h1> world </h1></div>;
21+
let k2 = <div> <h2> Hello </h2> {(user: any) => <h2>{user.name}</h2>}</div>;
22+
let k3 = <div> {1} {"That is a number"} </div>;
23+
let k4 = <Button> <h2> Hello </h2> </Button>;
24+
25+
//// [file.jsx]
26+
var Button = (function () {
27+
function Button() {
28+
}
29+
Button.prototype.render = function () {
30+
return (<div>My Button</div>);
31+
};
32+
return Button;
33+
}());
34+
// OK
35+
var k1 = <div> <h2> Hello </h2> <h1> world </h1></div>;
36+
var k2 = <div> <h2> Hello </h2> {function (user) { return <h2>{user.name}</h2>; }}</div>;
37+
var k3 = <div> {1} {"That is a number"} </div>;
38+
var k4 = <Button> <h2> Hello </h2> </Button>;

0 commit comments

Comments
 (0)