Skip to content

Commit e30a64f

Browse files
committed
JSX SFC WIP
1 parent 52b25a5 commit e30a64f

11 files changed

Lines changed: 19593 additions & 55 deletions

src/compiler/checker.ts

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ namespace ts {
141141
let globalTemplateStringsArrayType: ObjectType;
142142
let globalESSymbolType: ObjectType;
143143
let jsxElementType: ObjectType;
144-
/** Lazily loaded, use getJsxIntrinsicElementType() */
145-
let jsxIntrinsicElementsType: ObjectType;
144+
/** Things we lazy load from the JSX namespace */
145+
let jsxTypes: {[name: string]: ObjectType} = {};
146146
let globalIterableType: GenericType;
147147
let globalIteratorType: GenericType;
148148
let globalIterableIteratorType: GenericType;
@@ -7641,12 +7641,11 @@ namespace ts {
76417641
return type;
76427642
}
76437643

7644-
/// Returns the type JSX.IntrinsicElements. May return `unknownType` if that type is not present.
7645-
function getJsxIntrinsicElementsType() {
7646-
if (!jsxIntrinsicElementsType) {
7647-
jsxIntrinsicElementsType = getExportedTypeFromNamespace(JsxNames.JSX, JsxNames.IntrinsicElements) || unknownType;
7644+
function getJsxType(name: string) {
7645+
if (jsxTypes[name] === undefined) {
7646+
return jsxTypes[name] = getExportedTypeFromNamespace(JsxNames.JSX, name) || unknownType;
76487647
}
7649-
return jsxIntrinsicElementsType;
7648+
return jsxTypes[name];
76507649
}
76517650

76527651
/// Given a JSX opening element or self-closing element, return the symbol of the property that the tag name points to if
@@ -7669,7 +7668,7 @@ namespace ts {
76697668
return links.resolvedSymbol;
76707669

76717670
function lookupIntrinsicTag(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
7672-
let intrinsicElementsType = getJsxIntrinsicElementsType();
7671+
let intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements);
76737672
if (intrinsicElementsType !== unknownType) {
76747673
// Property case
76757674
let intrinsicProp = getPropertyOfType(intrinsicElementsType, (<Identifier>node.tagName).text);
@@ -7701,7 +7700,7 @@ namespace ts {
77017700

77027701
// Look up the value in the current scope
77037702
if (valueSymbol && valueSymbol !== unknownSymbol) {
7704-
links.jsxFlags |= JsxFlags.ClassElement;
7703+
links.jsxFlags |= JsxFlags.ValueElement;
77057704
if (valueSymbol.flags & SymbolFlags.Alias) {
77067705
markAliasSymbolAsReferenced(valueSymbol);
77077706
}
@@ -7730,7 +7729,7 @@ namespace ts {
77307729
function getJsxElementInstanceType(node: JsxOpeningLikeElement) {
77317730
// There is no such thing as an instance type for a non-class element. This
77327731
// line shouldn't be hit.
7733-
Debug.assert(!!(getNodeLinks(node).jsxFlags & JsxFlags.ClassElement), "Should not call getJsxElementInstanceType on non-class Element");
7732+
Debug.assert(!!(getNodeLinks(node).jsxFlags & JsxFlags.ValueElement), "Should not call getJsxElementInstanceType on non-class Element");
77347733

77357734
let classSymbol = getJsxElementTagSymbol(node);
77367735
if (classSymbol === unknownSymbol) {
@@ -7808,18 +7807,21 @@ namespace ts {
78087807
if (!links.resolvedJsxType) {
78097808
let sym = getJsxElementTagSymbol(node);
78107809

7811-
if (links.jsxFlags & JsxFlags.ClassElement) {
7810+
if (links.jsxFlags & JsxFlags.ValueElement) {
78127811
// Get the element instance type (the result of newing or invoking this tag)
78137812
let elemInstanceType = getJsxElementInstanceType(node);
78147813

78157814
// Is this is a stateless function component? See if its single signature is
7816-
// assignable to the JSX Element Type with either 0 arguments, or 1 argument
7817-
// that is an object type
7815+
// assignable to the JSX Element Type
78187816
let callSignature = getSingleCallSignature(getTypeOfSymbol(sym));
78197817
let callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
7820-
let paramType = callSignature && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0]));
7821-
if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType) && paramType.flags & TypeFlags.ObjectType) {
7822-
// TODO: Things like 'ref' and 'key' are always valid, how to account for that?
7818+
let paramType = callReturnType && callSignature && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0]));
7819+
if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType) && (paramType.flags & TypeFlags.ObjectType)) {
7820+
// Intersect in JSX.IntrinsicAttributes if it exists
7821+
let intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes);
7822+
if(intrinsicAttributes !== unknownType) {
7823+
paramType = intersectTypes(intrinsicAttributes, paramType);
7824+
}
78237825
return paramType;
78247826
}
78257827

@@ -7851,14 +7853,35 @@ namespace ts {
78517853
return links.resolvedJsxType = emptyObjectType;
78527854
}
78537855
else if (isTypeAny(attributesType) || (attributesType === unknownType)) {
7856+
// Props is of type 'any' or unknown
78547857
return links.resolvedJsxType = attributesType;
78557858
}
78567859
else if (!(attributesType.flags & TypeFlags.ObjectType)) {
7860+
// Props is not an object type
78577861
error(node.tagName, Diagnostics.JSX_element_attributes_type_0_must_be_an_object_type, typeToString(attributesType));
78587862
return links.resolvedJsxType = anyType;
78597863
}
78607864
else {
7861-
return links.resolvedJsxType = attributesType;
7865+
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements
7866+
let apparentAttributesType = attributesType;
7867+
let intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes);
7868+
if (intrinsicClassAttribs !== unknownType) {
7869+
let typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol);
7870+
if(typeParams) {
7871+
if(typeParams.length === 1) {
7872+
apparentAttributesType = intersectTypes(createTypeReference(<GenericType>intrinsicClassAttribs, [elemInstanceType]), apparentAttributesType);
7873+
}
7874+
} else {
7875+
apparentAttributesType = intersectTypes(attributesType, intrinsicClassAttribs);
7876+
}
7877+
}
7878+
7879+
let intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes);
7880+
if(intrinsicAttribs !== unknownType) {
7881+
apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType);
7882+
}
7883+
7884+
return links.resolvedJsxType = apparentAttributesType;
78627885
}
78637886
}
78647887
}
@@ -7898,7 +7921,7 @@ namespace ts {
78987921

78997922
/// Returns all the properties of the Jsx.IntrinsicElements interface
79007923
function getJsxIntrinsicTagNames(): Symbol[] {
7901-
let intrinsics = getJsxIntrinsicElementsType();
7924+
let intrinsics = getJsxType(JsxNames.IntrinsicElements);
79027925
return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray;
79037926
}
79047927

src/compiler/types.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,12 +434,16 @@ namespace ts {
434434

435435
export const enum JsxFlags {
436436
None = 0,
437+
/** An element from a named property of the JSX.IntrinsicElements interface */
437438
IntrinsicNamedElement = 1 << 0,
439+
/** An element inferred from the string index signature of the JSX.IntrinsicElements interface */
438440
IntrinsicIndexedElement = 1 << 1,
439-
ClassElement = 1 << 2,
440-
UnknownElement = 1 << 3,
441+
/** An element backed by a class, class-like, or function value */
442+
ValueElement = 1 << 2,
443+
/** Element resolution failed */
444+
UnknownElement = 1 << 4,
441445

442-
IntrinsicElement = IntrinsicNamedElement | IntrinsicIndexedElement
446+
IntrinsicElement = IntrinsicNamedElement | IntrinsicIndexedElement,
443447
}
444448

445449

src/harness/harness.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,7 @@ namespace Harness {
975975
useCaseSensitiveFileNames?: boolean;
976976
includeBuiltFile?: string;
977977
baselineFile?: string;
978+
libFiles?: string;
978979
}
979980

980981
// Additional options not already in ts.optionDeclarations
@@ -984,6 +985,7 @@ namespace Harness {
984985
{ name: "baselineFile", type: "string" },
985986
{ name: "includeBuiltFile", type: "string" },
986987
{ name: "fileName", type: "string" },
988+
{ name: "libFiles", type: "string" },
987989
{ name: "noErrorTruncation", type: "boolean" }
988990
];
989991

@@ -1115,6 +1117,15 @@ namespace Harness {
11151117
includeBuiltFiles.push({ unitName: builtFileName, content: normalizeLineEndings(IO.readFile(builtFileName), newLine) });
11161118
}
11171119

1120+
// Files from tests\lib that are requested by "@libFiles"
1121+
if (options.libFiles) {
1122+
ts.forEach(options.libFiles.split(','), filename => {
1123+
let libFileName = 'tests/lib/' + filename;
1124+
includeBuiltFiles.push({ unitName: libFileName, content: normalizeLineEndings(IO.readFile(libFileName), newLine) });
1125+
});
1126+
}
1127+
1128+
11181129
let useCaseSensitiveFileNames = options.useCaseSensitiveFileNames !== undefined ? options.useCaseSensitiveFileNames : Harness.IO.useCaseSensitiveFileNames();
11191130

11201131
let fileOutputs: GeneratedFile[] = [];
Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
1-
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(17,9): error TS2324: Property 'name' is missing in type '{ name: string; }'.
2-
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(17,16): error TS2339: Property 'naaame' does not exist on type '{ name: string; }'.
1+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(12,9): error TS2324: Property 'name' is missing in type 'IntrinsicAttributes & { name: string; }'.
2+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(12,16): error TS2339: Property 'naaame' does not exist on type 'IntrinsicAttributes & { name: string; }'.
3+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(19,15): error TS2322: Type 'number' is not assignable to type 'string'.
4+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(21,15): error TS2339: Property 'naaaaaaame' does not exist on type 'IntrinsicAttributes & { name?: string; }'.
35

46

5-
==== tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx (2 errors) ====
6-
declare module JSX {
7-
interface Element { el: any; }
8-
interface IntrinsicElements { div: any; }
9-
}
10-
7+
==== tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx (4 errors) ====
118

129
function Greet(x: {name: string}) {
1310
return <div>Hello, {x}</div>;
1411
}
1512
function Meet({name = 'world'}) {
16-
return <div>Hello, {x}</div>;
13+
return <div>Hello, {name}</div>;
1714
}
1815

1916
// OK
20-
let x = <Greet name='world' />;
17+
let a = <Greet name='world' />;
2118
// Error
22-
let y = <Greet naaame='world' />;
19+
let b = <Greet naaame='world' />;
2320
~~~~~~~~~~~~~~~~~~~~~~~~
24-
!!! error TS2324: Property 'name' is missing in type '{ name: string; }'.
21+
!!! error TS2324: Property 'name' is missing in type 'IntrinsicAttributes & { name: string; }'.
2522
~~~~~~
26-
!!! error TS2339: Property 'naaame' does not exist on type '{ name: string; }'.
23+
!!! error TS2339: Property 'naaame' does not exist on type 'IntrinsicAttributes & { name: string; }'.
24+
25+
// OK
26+
let c = <Meet />;
27+
// OK
28+
let d = <Meet name='me' />;
29+
// Error
30+
let e = <Meet name={42} />;
31+
~~~~~~~~~
32+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
33+
// Error
34+
let f = <Meet naaaaaaame='no' />;
35+
~~~~~~~~~~
36+
!!! error TS2339: Property 'naaaaaaame' does not exist on type 'IntrinsicAttributes & { name?: string; }'.
2737

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
//// [tsxStatelessFunctionComponents1.tsx]
2-
declare module JSX {
3-
interface Element { el: any; }
4-
interface IntrinsicElements { div: any; }
5-
}
6-
72

83
function Greet(x: {name: string}) {
94
return <div>Hello, {x}</div>;
105
}
116
function Meet({name = 'world'}) {
12-
return <div>Hello, {x}</div>;
7+
return <div>Hello, {name}</div>;
138
}
149

1510
// OK
16-
let x = <Greet name='world' />;
11+
let a = <Greet name='world' />;
12+
// Error
13+
let b = <Greet naaame='world' />;
14+
15+
// OK
16+
let c = <Meet />;
17+
// OK
18+
let d = <Meet name='me' />;
1719
// Error
18-
let y = <Greet naaame='world' />;
20+
let e = <Meet name={42} />;
21+
// Error
22+
let f = <Meet naaaaaaame='no' />;
1923

2024

2125
//// [tsxStatelessFunctionComponents1.jsx]
@@ -24,9 +28,17 @@ function Greet(x) {
2428
}
2529
function Meet(_a) {
2630
var _b = _a.name, name = _b === void 0 ? 'world' : _b;
27-
return <div>Hello, {x}</div>;
31+
return <div>Hello, {name}</div>;
2832
}
2933
// OK
30-
var x = <Greet name='world'/>;
34+
var a = <Greet name='world'/>;
35+
// Error
36+
var b = <Greet naaame='world'/>;
37+
// OK
38+
var c = <Meet />;
39+
// OK
40+
var d = <Meet name='me'/>;
41+
// Error
42+
var e = <Meet name={42}/>;
3143
// Error
32-
var y = <Greet naaame='world'/>;
44+
var f = <Meet naaaaaaame='no'/>;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(2,1): error TS1148: Cannot compile modules unless the '--module' flag is provided.
2+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(20,16): error TS2339: Property 'ref' does not exist on type 'IntrinsicAttributes & { name?: string; }'.
3+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(26,42): error TS2339: Property 'subtr' does not exist on type 'string'.
4+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(28,5): error TS2451: Cannot redeclare block-scoped variable 'f'.
5+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(28,33): error TS2339: Property 'notARealProperty' does not exist on type 'BigGreeter'.
6+
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(31,5): error TS2451: Cannot redeclare block-scoped variable 'f'.
7+
8+
9+
==== tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx (6 errors) ====
10+
11+
import React = require('react');
12+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13+
!!! error TS1148: Cannot compile modules unless the '--module' flag is provided.
14+
15+
function Greet(x: {name?: string}) {
16+
return <div>Hello, {x}</div>;
17+
}
18+
19+
class BigGreeter extends React.Component<{ name?: string }, {}> {
20+
render() {
21+
return <div></div>;
22+
}
23+
greeting: string;
24+
}
25+
26+
// OK
27+
let a = <Greet />;
28+
// OK
29+
let b = <Greet key="k" />;
30+
// Error
31+
let c = <Greet ref="myRef" />;
32+
~~~
33+
!!! error TS2339: Property 'ref' does not exist on type 'IntrinsicAttributes & { name?: string; }'.
34+
35+
36+
// OK
37+
let d = <BigGreeter ref={x => x.greeting.substr(10)} />;
38+
// Error ('subtr')
39+
let e = <BigGreeter ref={x => x.greeting.subtr(10)} />;
40+
~~~~~
41+
!!! error TS2339: Property 'subtr' does not exist on type 'string'.
42+
// Error
43+
let f = <BigGreeter ref={x => x.notARealProperty} />;
44+
~
45+
!!! error TS2451: Cannot redeclare block-scoped variable 'f'.
46+
~~~~~~~~~~~~~~~~
47+
!!! error TS2339: Property 'notARealProperty' does not exist on type 'BigGreeter'.
48+
49+
// OK
50+
let f = <BigGreeter key={100} />;
51+
~
52+
!!! error TS2451: Cannot redeclare block-scoped variable 'f'.

0 commit comments

Comments
 (0)