Skip to content

Commit

Permalink
Merge pull request #15160 from Microsoft/master-jsxChildren
Browse files Browse the repository at this point in the history
[Master] Type checking JSX children
  • Loading branch information
yuit authored Apr 21, 2017
2 parents d190530 + e7e13ec commit a1a2006
Show file tree
Hide file tree
Showing 77 changed files with 3,064 additions and 239 deletions.
229 changes: 153 additions & 76 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2119,6 +2119,10 @@
"category": "Error",
"code": 2709
},
"'{0}' are specified twice. The attribute named '{0}' will be overwritten.": {
"category": "Error",
"code": 2710
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2457,7 +2457,7 @@ namespace ts {
let indentation: number;
for (const line of lines) {
for (let i = 0; i < line.length && (indentation === undefined || i < indentation); i++) {
if (!isWhiteSpace(line.charCodeAt(i))) {
if (!isWhiteSpaceLike(line.charCodeAt(i))) {
if (indentation === undefined || i < indentation) {
indentation = i;
break;
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3828,13 +3828,15 @@ namespace ts {

function parseJsxText(): JsxText {
const node = <JsxText>createNode(SyntaxKind.JsxText, scanner.getStartPos());
node.containsOnlyWhiteSpaces = currentToken === SyntaxKind.JsxTextAllWhiteSpaces;
currentToken = scanner.scanJsxToken();
return finishNode(node);
}

function parseJsxChild(): JsxChild {
switch (token()) {
case SyntaxKind.JsxText:
case SyntaxKind.JsxTextAllWhiteSpaces:
return parseJsxText();
case SyntaxKind.OpenBraceToken:
return parseJsxExpression(/*inExpressionContext*/ false);
Expand Down Expand Up @@ -3864,7 +3866,10 @@ namespace ts {
else if (token() === SyntaxKind.ConflictMarkerTrivia) {
break;
}
result.push(parseJsxChild());
const child = parseJsxChild();
if (child) {
result.push(child);
}
}

result.end = scanner.getTokenPos();
Expand Down
29 changes: 24 additions & 5 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ namespace ts {
return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position);
}

export function isWhiteSpace(ch: number): boolean {
export function isWhiteSpaceLike(ch: number): boolean {
return isWhiteSpaceSingleLine(ch) || isLineBreak(ch);
}

Expand Down Expand Up @@ -512,7 +512,7 @@ namespace ts {
break;

default:
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpace(ch))) {
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) {
pos++;
continue;
}
Expand Down Expand Up @@ -694,7 +694,7 @@ namespace ts {
}
break scan;
default:
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpace(ch))) {
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) {
if (hasPendingCommentRange && isLineBreak(ch)) {
pendingHasTrailingNewLine = true;
}
Expand Down Expand Up @@ -1727,8 +1727,12 @@ namespace ts {
return token = SyntaxKind.OpenBraceToken;
}

// First non-whitespace character on this line.
let firstNonWhitespace = 0;
// These initial values are special because the first line is:
// firstNonWhitespace = 0 to indicate that we want leading whitspace,

while (pos < end) {
pos++;
char = text.charCodeAt(pos);
if (char === CharacterCodes.openBrace) {
break;
Expand All @@ -1740,8 +1744,23 @@ namespace ts {
}
break;
}

// FirstNonWhitespace is 0, then we only see whitespaces so far. If we see a linebreak, we want to ignore that whitespaces.
// i.e (- : whitespace)
// <div>----
// </div> becomes <div></div>
//
// <div>----</div> becomes <div>----</div>
if (isLineBreak(char) && firstNonWhitespace === 0) {
firstNonWhitespace = -1;
}
else if (!isWhiteSpaceLike(char)) {
firstNonWhitespace = pos;
}
pos++;
}
return token = SyntaxKind.JsxText;

return firstNonWhitespace === -1 ? SyntaxKind.JsxTextAllWhiteSpaces : SyntaxKind.JsxText;
}

// Scans a JSX identifier; these differ from normal identifiers in that
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace ts {

/** ES6 Map interface. */
export interface Map<T> {
get(key: string): T;
get(key: string): T | undefined;
has(key: string): boolean;
set(key: string, value: T): this;
delete(key: string): boolean;
Expand Down Expand Up @@ -65,6 +65,7 @@ namespace ts {
NumericLiteral,
StringLiteral,
JsxText,
JsxTextAllWhiteSpaces,
RegularExpressionLiteral,
NoSubstitutionTemplateLiteral,
// Pseudo-literals
Expand Down Expand Up @@ -1572,6 +1573,7 @@ namespace ts {

export interface JsxText extends Node {
kind: SyntaxKind.JsxText;
containsOnlyWhiteSpaces: boolean;
parent?: JsxElement;
}

Expand Down
2 changes: 1 addition & 1 deletion src/services/formatting/smartIndenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ namespace ts.formatting {
let current = position;
while (current > 0) {
const char = sourceFile.text.charCodeAt(current);
if (!isWhiteSpace(char)) {
if (!isWhiteSpaceLike(char)) {
break;
}
current--;
Expand Down
2 changes: 1 addition & 1 deletion src/services/textChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ namespace ts.textChanges {
if (force || !isTrivia(s)) {
this.lastNonTriviaPosition = this.writer.getTextPos();
let i = 0;
while (isWhiteSpace(s.charCodeAt(s.length - i - 1))) {
while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) {
i++;
}
// trim trailing whitespaces
Expand Down
2 changes: 1 addition & 1 deletion src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1380,7 +1380,7 @@ namespace ts {
}

export function getFirstNonSpaceCharacterPosition(text: string, position: number) {
while (isWhiteSpace(text.charCodeAt(position))) {
while (isWhiteSpaceLike(text.charCodeAt(position))) {
position += 1;
}
return position;
Expand Down
39 changes: 39 additions & 0 deletions tests/baselines/reference/checkJsxChildrenProperty1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//// [file.tsx]
import React = require('react');

interface Prop {
a: number,
b: string,
children: string | JSX.Element
}

function Comp(p: Prop) {
return <div>{p.b}</div>;
}

// OK
let k = <Comp a={10} b="hi" children ="lol" />;
let k1 =
<Comp a={10} b="hi">
hi hi hi!
</Comp>;
let k2 =
<Comp a={10} b="hi">
<div>hi hi hi!</div>
</Comp>;

//// [file.jsx]
"use strict";
exports.__esModule = true;
var React = require("react");
function Comp(p) {
return <div>{p.b}</div>;
}
// OK
var k = <Comp a={10} b="hi" children="lol"/>;
var k1 = <Comp a={10} b="hi">
hi hi hi!
</Comp>;
var k2 = <Comp a={10} b="hi">
<div>hi hi hi!</div>
</Comp>;
67 changes: 67 additions & 0 deletions tests/baselines/reference/checkJsxChildrenProperty1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
=== tests/cases/conformance/jsx/file.tsx ===
import React = require('react');
>React : Symbol(React, Decl(file.tsx, 0, 0))

interface Prop {
>Prop : Symbol(Prop, Decl(file.tsx, 0, 32))

a: number,
>a : Symbol(Prop.a, Decl(file.tsx, 2, 16))

b: string,
>b : Symbol(Prop.b, Decl(file.tsx, 3, 14))

children: string | JSX.Element
>children : Symbol(Prop.children, Decl(file.tsx, 4, 14))
>JSX : Symbol(JSX, Decl(react.d.ts, 2352, 1))
>Element : Symbol(JSX.Element, Decl(react.d.ts, 2355, 27))
}

function Comp(p: Prop) {
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
>p : Symbol(p, Decl(file.tsx, 8, 14))
>Prop : Symbol(Prop, Decl(file.tsx, 0, 32))

return <div>{p.b}</div>;
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
>p.b : Symbol(Prop.b, Decl(file.tsx, 3, 14))
>p : Symbol(p, Decl(file.tsx, 8, 14))
>b : Symbol(Prop.b, Decl(file.tsx, 3, 14))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
}

// OK
let k = <Comp a={10} b="hi" children ="lol" />;
>k : Symbol(k, Decl(file.tsx, 13, 3))
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
>a : Symbol(a, Decl(file.tsx, 13, 13))
>b : Symbol(b, Decl(file.tsx, 13, 20))
>children : Symbol(children, Decl(file.tsx, 13, 27))

let k1 =
>k1 : Symbol(k1, Decl(file.tsx, 14, 3))

<Comp a={10} b="hi">
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
>a : Symbol(a, Decl(file.tsx, 15, 9))
>b : Symbol(b, Decl(file.tsx, 15, 16))

hi hi hi!
</Comp>;
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))

let k2 =
>k2 : Symbol(k2, Decl(file.tsx, 18, 3))

<Comp a={10} b="hi">
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))
>a : Symbol(a, Decl(file.tsx, 19, 9))
>b : Symbol(b, Decl(file.tsx, 19, 16))

<div>hi hi hi!</div>
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))

</Comp>;
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))

75 changes: 75 additions & 0 deletions tests/baselines/reference/checkJsxChildrenProperty1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
=== tests/cases/conformance/jsx/file.tsx ===
import React = require('react');
>React : typeof React

interface Prop {
>Prop : Prop

a: number,
>a : number

b: string,
>b : string

children: string | JSX.Element
>children : string | JSX.Element
>JSX : any
>Element : JSX.Element
}

function Comp(p: Prop) {
>Comp : (p: Prop) => JSX.Element
>p : Prop
>Prop : Prop

return <div>{p.b}</div>;
><div>{p.b}</div> : JSX.Element
>div : any
>p.b : string
>p : Prop
>b : string
>div : any
}

// OK
let k = <Comp a={10} b="hi" children ="lol" />;
>k : JSX.Element
><Comp a={10} b="hi" children ="lol" /> : JSX.Element
>Comp : (p: Prop) => JSX.Element
>a : number
>10 : 10
>b : string
>children : string

let k1 =
>k1 : JSX.Element

<Comp a={10} b="hi">
><Comp a={10} b="hi"> hi hi hi! </Comp> : JSX.Element
>Comp : (p: Prop) => JSX.Element
>a : number
>10 : 10
>b : string

hi hi hi!
</Comp>;
>Comp : (p: Prop) => JSX.Element

let k2 =
>k2 : JSX.Element

<Comp a={10} b="hi">
><Comp a={10} b="hi"> <div>hi hi hi!</div> </Comp> : JSX.Element
>Comp : (p: Prop) => JSX.Element
>a : number
>10 : 10
>b : string

<div>hi hi hi!</div>
><div>hi hi hi!</div> : JSX.Element
>div : any
>div : any

</Comp>;
>Comp : (p: Prop) => JSX.Element

38 changes: 38 additions & 0 deletions tests/baselines/reference/checkJsxChildrenProperty10.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//// [file.tsx]
declare module JSX {
interface Element { }
interface ElementAttributesProperty { props: {} }
interface IntrinsicElements {
div: any;
h2: any;
h1: any;
}
}

class Button {
props: {}
render() {
return (<div>My Button</div>)
}
}

// OK
let k1 = <div> <h2> Hello </h2> <h1> world </h1></div>;
let k2 = <div> <h2> Hello </h2> {(user: any) => <h2>{user.name}</h2>}</div>;
let k3 = <div> {1} {"That is a number"} </div>;
let k4 = <Button> <h2> Hello </h2> </Button>;

//// [file.jsx]
var Button = (function () {
function Button() {
}
Button.prototype.render = function () {
return (<div>My Button</div>);
};
return Button;
}());
// OK
var k1 = <div> <h2> Hello </h2> <h1> world </h1></div>;
var k2 = <div> <h2> Hello </h2> {function (user) { return <h2>{user.name}</h2>; }}</div>;
var k3 = <div> {1} {"That is a number"} </div>;
var k4 = <Button> <h2> Hello </h2> </Button>;
Loading

0 comments on commit a1a2006

Please sign in to comment.