Skip to content

Commit

Permalink
#13890 Implementing JSX.ElementType support
Browse files Browse the repository at this point in the history
  • Loading branch information
Antanas Arvasevicius committed Feb 7, 2017
1 parent a7728f8 commit 1aee377
Show file tree
Hide file tree
Showing 13 changed files with 438 additions and 4 deletions.
150 changes: 146 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ namespace ts {
IntrinsicElements: "IntrinsicElements",
ElementClass: "ElementClass",
ElementAttributesPropertyNameContainer: "ElementAttributesProperty",
ElementTypePropertyNameContainer: "ElementTypeProperty",
Element: "Element",
IntrinsicAttributes: "IntrinsicAttributes",
IntrinsicClassAttributes: "IntrinsicClassAttributes"
Expand Down Expand Up @@ -11901,7 +11902,7 @@ namespace ts {

function checkJsxSelfClosingElement(node: JsxSelfClosingElement) {
checkJsxOpeningLikeElement(node);
return jsxElementType || anyType;
return getJsxElementType(node);
}

function checkJsxElement(node: JsxElement) {
Expand Down Expand Up @@ -11931,7 +11932,7 @@ namespace ts {
}
}

return jsxElementType || anyType;
return getJsxElementType(node);
}

/**
Expand Down Expand Up @@ -12073,7 +12074,7 @@ namespace ts {
* element is not a class element, or the class element type cannot be determined, returns 'undefined'.
* For example, in the element <MyClass>, the element instance type is `MyClass` (not `typeof MyClass`).
*/
function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type) {
function getJsxElementInstanceType(node: JsxOpeningLikeElement | JsxElement | JsxSelfClosingElement, valueType: Type) {
Debug.assert(!(valueType.flags & TypeFlags.Union));
if (isTypeAny(valueType)) {
// Short-circuit if the class tag is using an element type 'any'
Expand All @@ -12086,8 +12087,11 @@ namespace ts {
// No construct signatures, try call signatures
signatures = getSignaturesOfType(valueType, SignatureKind.Call);
if (signatures.length === 0) {

const tagName = (node.kind === SyntaxKind.JsxElement) ? node.openingElement.tagName : node.tagName;

// We found no signatures at all, which is an error
error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName));
error(tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(tagName));
return unknownType;
}
}
Expand Down Expand Up @@ -12132,6 +12136,41 @@ namespace ts {
}
}

/// Returns a property name which type will be the JSX Element type
/// or 'undefined' if ElementTypeProperty doesn't exist (then element type will be type of JSX.Element or any)
/// or '' if it has 0 properties (element type will be class instance type)
function getJsxElementTypePropertyName() {
// JSX
const jsxNamespace = getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/undefined);
// JSX.ElementTypeProperty [symbol]
const attribsPropTypeSym = jsxNamespace && getSymbol(jsxNamespace.exports, JsxNames.ElementTypePropertyNameContainer, SymbolFlags.Type);
// JSX.ElementTypeProperty [type]
const attribPropType = attribsPropTypeSym && getDeclaredTypeOfSymbol(attribsPropTypeSym);
// The properties of JSX.ElementTypeProperty
const attribProperties = attribPropType && getPropertiesOfType(attribPropType);

if (attribProperties) {
// ElementTypeProperty has zero properties, so the element type will be the class instance type
if (attribProperties.length === 0) {
return "";
}
// ElementTypeProperty has one property, so the element type type will be the type of the corresponding
// property of the class instance type
else if (attribProperties.length === 1) {
return attribProperties[0].name;
}
// More than one property on ElementTypeProperty is an error
else {
error(attribsPropTypeSym.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, JsxNames.ElementTypePropertyNameContainer);
return undefined;
}
}
else {
// No interface exists, so the element type will be a type of JSX.Element or any
return undefined;
}
}

/**
* Given React element instance type and the class type, resolve the Jsx type
* Pass elemType to handle individual type in the union typed element type.
Expand Down Expand Up @@ -12252,6 +12291,88 @@ namespace ts {
}
}

/**
* Given React element instance type and the class type, resolve the Jsx element type
* Pass elemType to handle individual type in the union typed element type.
*/
function getResolvedJsxElementType(node: JsxElement | JsxSelfClosingElement, elemType?: Type, elemClassType?: Type): Type {
const defaultJsxElementType = jsxElementType || anyType;

const propsName = getJsxElementTypePropertyName();
if (propsName === undefined) {
// There is no type ElementTypeProperty just return JSX.Element or 'any'
return defaultJsxElementType;
}

const tagName = (node.kind === SyntaxKind.JsxElement) ? node.openingElement.tagName : node.tagName;

if (!elemType) {
elemType = checkExpression(tagName);
}

if (elemType.flags & TypeFlags.Union) {
const types = (<UnionOrIntersectionType>elemType).types;
return getUnionType(map(types, type => {
return getResolvedJsxElementType(node, type, elemClassType);
}), /*subtypeReduction*/ true);
}

// If the elemType is a string type, we have to return JSX.Element or 'any' to prevent an error downstream as we will try to find construct or call signature of the type
if (elemType.flags & TypeFlags.String) {
return defaultJsxElementType;
}
else if (elemType.flags & TypeFlags.StringLiteral) {
return defaultJsxElementType;
}

// Get the element instance type (the result of newing or invoking this tag)
const elemInstanceType = getJsxElementInstanceType(node, elemType);

if (!elemClassType || !isTypeAssignableTo(elemInstanceType, elemClassType)) {
// Is this is a stateless function component? See if its single signature's return type is
// assignable to the JSX Element Type
if (jsxElementType) {
const callSignatures = elemType && getSignaturesOfType(elemType, SignatureKind.Call);
const callSignature = callSignatures && callSignatures.length > 0 && callSignatures[0];
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);

if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) {
return callReturnType;
}
}
}

if (isTypeAny(elemInstanceType)) {
return defaultJsxElementType;
}


if (propsName === "") {
// If there is no e.g. attribute member in ElementTypeProperty, use the element class type instead
return elemInstanceType;
}
else {
const attributesType = getTypeOfPropertyOfType(elemInstanceType, propsName);

if (!attributesType) {
// There is no element property on this instance type return JSX.Element or 'any'
return defaultJsxElementType;
}
else if (isTypeAny(attributesType) || (attributesType === unknownType)) {
return defaultJsxElementType;
}
else if (attributesType.flags & TypeFlags.Union) {
// Props cannot be a union type
error(tagName, Diagnostics.JSX_element_attributes_type_0_may_not_be_a_union_type, typeToString(attributesType));
return defaultJsxElementType;
}
else {
return attributesType;
}
}
}


/**
* Given an opening/self-closing element, get the 'element attributes type', i.e. the type that tells
* us which attributes are valid on a given element.
Expand Down Expand Up @@ -12279,6 +12400,27 @@ namespace ts {
return links.resolvedJsxType;
}

/**
* Given an jsx element, get the 'element type'
*/
function getJsxElementType(node: JsxElement | JsxSelfClosingElement): Type {
const links = getNodeLinks(node);

if (!links.resolvedJsxElementType) {

const tagName = (node.kind === SyntaxKind.JsxElement) ? node.openingElement.tagName : node.tagName;

if (isJsxIntrinsicIdentifier(tagName)) {
return jsxElementType || anyType;
}
else {
const elemClassType = getJsxGlobalElementClassType();
return links.resolvedJsxElementType = getResolvedJsxElementType(node, undefined, elemClassType);
}
}
return links.resolvedJsxElementType;
}

/**
* Given a JSX attribute, returns the symbol for the corresponds property
* of the element attributes type. Will return unknownSymbol for attributes
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2788,6 +2788,7 @@
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element
resolvedJsxElementType?: Type; // resolved element type of JSX openinglike element
hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt.
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
switchTypes?: Type[]; // Cached array of switch case expression types
Expand Down
29 changes: 29 additions & 0 deletions tests/baselines/reference/tsxElementResolution20.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// [file.tsx]
declare module JSX {
interface Element { }
interface ElementTypeProperty { returnType; }
}

interface CustomType { }

class Obj1 {
returnType: CustomType;
}
const obj = <Obj1 param="123"/>; // OK

const objSelfClosing = <Obj1/>; // OK

const Func = () => <Obj1/>;

const objFromFactory = <Func/>; // OK

//// [file.jsx]
var Obj1 = (function () {
function Obj1() {
}
return Obj1;
}());
var obj = <Obj1 param="123"/>; // OK
var objSelfClosing = <Obj1 />; // OK
var Func = function () { return <Obj1 />; };
var objFromFactory = <Func />; // OK
39 changes: 39 additions & 0 deletions tests/baselines/reference/tsxElementResolution20.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
=== tests/cases/conformance/jsx/file.tsx ===
declare module JSX {
>JSX : Symbol(JSX, Decl(file.tsx, 0, 0))

interface Element { }
>Element : Symbol(Element, Decl(file.tsx, 0, 20))

interface ElementTypeProperty { returnType; }
>ElementTypeProperty : Symbol(ElementTypeProperty, Decl(file.tsx, 1, 22))
>returnType : Symbol(ElementTypeProperty.returnType, Decl(file.tsx, 2, 32))
}

interface CustomType { }
>CustomType : Symbol(CustomType, Decl(file.tsx, 3, 1))

class Obj1 {
>Obj1 : Symbol(Obj1, Decl(file.tsx, 5, 24))

returnType: CustomType;
>returnType : Symbol(Obj1.returnType, Decl(file.tsx, 7, 12))
>CustomType : Symbol(CustomType, Decl(file.tsx, 3, 1))
}
const obj = <Obj1 param="123"/>; // OK
>obj : Symbol(obj, Decl(file.tsx, 10, 5))
>Obj1 : Symbol(Obj1, Decl(file.tsx, 5, 24))
>param : Symbol(unknown)

const objSelfClosing = <Obj1/>; // OK
>objSelfClosing : Symbol(objSelfClosing, Decl(file.tsx, 12, 5))
>Obj1 : Symbol(Obj1, Decl(file.tsx, 5, 24))

const Func = () => <Obj1/>;
>Func : Symbol(Func, Decl(file.tsx, 14, 5))
>Obj1 : Symbol(Obj1, Decl(file.tsx, 5, 24))

const objFromFactory = <Func/>; // OK
>objFromFactory : Symbol(objFromFactory, Decl(file.tsx, 16, 5))
>Func : Symbol(Func, Decl(file.tsx, 14, 5))

44 changes: 44 additions & 0 deletions tests/baselines/reference/tsxElementResolution20.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
=== tests/cases/conformance/jsx/file.tsx ===
declare module JSX {
>JSX : any

interface Element { }
>Element : Element

interface ElementTypeProperty { returnType; }
>ElementTypeProperty : ElementTypeProperty
>returnType : any
}

interface CustomType { }
>CustomType : CustomType

class Obj1 {
>Obj1 : Obj1

returnType: CustomType;
>returnType : CustomType
>CustomType : CustomType
}
const obj = <Obj1 param="123"/>; // OK
>obj : CustomType
><Obj1 param="123"/> : CustomType
>Obj1 : typeof Obj1
>param : any

const objSelfClosing = <Obj1/>; // OK
>objSelfClosing : CustomType
><Obj1/> : CustomType
>Obj1 : typeof Obj1

const Func = () => <Obj1/>;
>Func : () => CustomType
>() => <Obj1/> : () => CustomType
><Obj1/> : CustomType
>Obj1 : typeof Obj1

const objFromFactory = <Func/>; // OK
>objFromFactory : CustomType
><Func/> : CustomType
>Func : () => CustomType

28 changes: 28 additions & 0 deletions tests/baselines/reference/tsxElementResolution21.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//// [file.tsx]
declare module JSX {
interface Element { }
interface ElementTypeProperty { }
}

class Obj1 {
}

const obj = <Obj1 param="123"/>; // OK

const objSelfClosing = <Obj1/>; // OK

const Func = () => <Obj1/>;

const objFromFactory = <Func/>; // OK


//// [file.jsx]
var Obj1 = (function () {
function Obj1() {
}
return Obj1;
}());
var obj = <Obj1 param="123"/>; // OK
var objSelfClosing = <Obj1 />; // OK
var Func = function () { return <Obj1 />; };
var objFromFactory = <Func />; // OK
32 changes: 32 additions & 0 deletions tests/baselines/reference/tsxElementResolution21.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=== tests/cases/conformance/jsx/file.tsx ===
declare module JSX {
>JSX : Symbol(JSX, Decl(file.tsx, 0, 0))

interface Element { }
>Element : Symbol(Element, Decl(file.tsx, 0, 20))

interface ElementTypeProperty { }
>ElementTypeProperty : Symbol(ElementTypeProperty, Decl(file.tsx, 1, 22))
}

class Obj1 {
>Obj1 : Symbol(Obj1, Decl(file.tsx, 3, 1))
}

const obj = <Obj1 param="123"/>; // OK
>obj : Symbol(obj, Decl(file.tsx, 8, 5))
>Obj1 : Symbol(Obj1, Decl(file.tsx, 3, 1))
>param : Symbol(unknown)

const objSelfClosing = <Obj1/>; // OK
>objSelfClosing : Symbol(objSelfClosing, Decl(file.tsx, 10, 5))
>Obj1 : Symbol(Obj1, Decl(file.tsx, 3, 1))

const Func = () => <Obj1/>;
>Func : Symbol(Func, Decl(file.tsx, 12, 5))
>Obj1 : Symbol(Obj1, Decl(file.tsx, 3, 1))

const objFromFactory = <Func/>; // OK
>objFromFactory : Symbol(objFromFactory, Decl(file.tsx, 14, 5))
>Func : Symbol(Func, Decl(file.tsx, 12, 5))

Loading

0 comments on commit 1aee377

Please sign in to comment.