From 53106cb5ed6b789475320109930df202f22843bb Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 4 Jan 2016 23:00:22 -0800 Subject: [PATCH 1/2] Change logic in identifying SFCs Our logic for detecting SFC vs Element Class had a few issues: * Object Type flag is not actually useful * Parameter arity isn't actually relevant * The check for Element Class should take priority Fixes #6349 and #6353 --- src/compiler/checker.ts | 30 ++++++---- .../reference/tsxElementResolution9.js | 2 +- .../reference/tsxElementResolution9.symbols | 5 +- .../reference/tsxElementResolution9.types | 3 +- .../tsxStatelessFunctionComponents3.js | 37 ++++++++++++ .../tsxStatelessFunctionComponents3.symbols | 48 +++++++++++++++ .../tsxStatelessFunctionComponents3.types | 59 +++++++++++++++++++ .../conformance/jsx/tsxElementResolution9.tsx | 2 +- .../jsx/tsxStatelessFunctionComponents3.tsx | 23 ++++++++ 9 files changed, 191 insertions(+), 18 deletions(-) create mode 100644 tests/baselines/reference/tsxStatelessFunctionComponents3.js create mode 100644 tests/baselines/reference/tsxStatelessFunctionComponents3.symbols create mode 100644 tests/baselines/reference/tsxStatelessFunctionComponents3.types create mode 100644 tests/cases/conformance/jsx/tsxStatelessFunctionComponents3.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fb4af5d729..a5b96c4a9a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8248,27 +8248,31 @@ namespace ts { // Get the element instance type (the result of newing or invoking this tag) const elemInstanceType = getJsxElementInstanceType(node); - // Is this is a stateless function component? See if its single signature is - // assignable to the JSX Element Type - const callSignature = getSingleCallSignature(getTypeOfSymbol(sym)); - const callReturnType = callSignature && getReturnTypeOfSignature(callSignature); - let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0])); - if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType) && (paramType.flags & TypeFlags.ObjectType)) { - // Intersect in JSX.IntrinsicAttributes if it exists - const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes); - if (intrinsicAttributes !== unknownType) { - paramType = intersectTypes(intrinsicAttributes, paramType); + const elemClassType = getJsxGlobalElementClassType(); + + 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 + const elemType = getTypeOfSymbol(sym); + const callSignatures = elemType && getSignaturesOfType(elemType, SignatureKind.Call); + const callSignature = callSignatures && callSignatures.length > 0 && callSignatures[0]; + const callReturnType = callSignature && getReturnTypeOfSignature(callSignature); + let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0])); + if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) { + // Intersect in JSX.IntrinsicAttributes if it exists + const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes); + if (intrinsicAttributes !== unknownType) { + paramType = intersectTypes(intrinsicAttributes, paramType); + } + return paramType; } - return paramType; } // Issue an error if this return type isn't assignable to JSX.ElementClass - const elemClassType = getJsxGlobalElementClassType(); if (elemClassType) { checkTypeRelatedTo(elemInstanceType, elemClassType, assignableRelation, node, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); } - if (isTypeAny(elemInstanceType)) { return links.resolvedJsxType = elemInstanceType; } diff --git a/tests/baselines/reference/tsxElementResolution9.js b/tests/baselines/reference/tsxElementResolution9.js index bc65bb0bb0..5ad47730e4 100644 --- a/tests/baselines/reference/tsxElementResolution9.js +++ b/tests/baselines/reference/tsxElementResolution9.js @@ -1,6 +1,6 @@ //// [file.tsx] declare module JSX { - interface Element { } + interface Element { something; } interface IntrinsicElements { } } diff --git a/tests/baselines/reference/tsxElementResolution9.symbols b/tests/baselines/reference/tsxElementResolution9.symbols index e38c64d084..0aec19a809 100644 --- a/tests/baselines/reference/tsxElementResolution9.symbols +++ b/tests/baselines/reference/tsxElementResolution9.symbols @@ -2,11 +2,12 @@ declare module JSX { >JSX : Symbol(JSX, Decl(file.tsx, 0, 0)) - interface Element { } + interface Element { something; } >Element : Symbol(Element, Decl(file.tsx, 0, 20)) +>something : Symbol(something, Decl(file.tsx, 1, 20)) interface IntrinsicElements { } ->IntrinsicElements : Symbol(IntrinsicElements, Decl(file.tsx, 1, 22)) +>IntrinsicElements : Symbol(IntrinsicElements, Decl(file.tsx, 1, 33)) } interface Obj1 { diff --git a/tests/baselines/reference/tsxElementResolution9.types b/tests/baselines/reference/tsxElementResolution9.types index dd84e6f07b..b138aa0509 100644 --- a/tests/baselines/reference/tsxElementResolution9.types +++ b/tests/baselines/reference/tsxElementResolution9.types @@ -2,8 +2,9 @@ declare module JSX { >JSX : any - interface Element { } + interface Element { something; } >Element : Element +>something : any interface IntrinsicElements { } >IntrinsicElements : IntrinsicElements diff --git a/tests/baselines/reference/tsxStatelessFunctionComponents3.js b/tests/baselines/reference/tsxStatelessFunctionComponents3.js new file mode 100644 index 0000000000..d58586dd1e --- /dev/null +++ b/tests/baselines/reference/tsxStatelessFunctionComponents3.js @@ -0,0 +1,37 @@ +//// [file.tsx] + +import React = require('react'); + +const Foo = (props: any) =>
; +// Should be OK +const foo = ; + + +// Should be OK +var MainMenu: React.StatelessComponent<{}> = (props) => (
+

Main Menu

+
); + +var App: React.StatelessComponent<{ children }> = ({children}) => ( +
+ +
+); + +//// [file.jsx] +define(["require", "exports", 'react'], function (require, exports, React) { + "use strict"; + var Foo = function (props) { return
; }; + // Should be OK + var foo = ; + // Should be OK + var MainMenu = function (props) { return (
+

Main Menu

+
); }; + var App = function (_a) { + var children = _a.children; + return (
+ +
); + }; +}); diff --git a/tests/baselines/reference/tsxStatelessFunctionComponents3.symbols b/tests/baselines/reference/tsxStatelessFunctionComponents3.symbols new file mode 100644 index 0000000000..4ce502c6a6 --- /dev/null +++ b/tests/baselines/reference/tsxStatelessFunctionComponents3.symbols @@ -0,0 +1,48 @@ +=== tests/cases/conformance/jsx/file.tsx === + +import React = require('react'); +>React : Symbol(React, Decl(file.tsx, 0, 0)) + +const Foo = (props: any) =>
; +>Foo : Symbol(Foo, Decl(file.tsx, 3, 5)) +>props : Symbol(props, Decl(file.tsx, 3, 13)) +>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 927, 45)) + +// Should be OK +const foo = ; +>foo : Symbol(foo, Decl(file.tsx, 5, 5)) +>Foo : Symbol(Foo, Decl(file.tsx, 3, 5)) + + +// Should be OK +var MainMenu: React.StatelessComponent<{}> = (props) => (
+>MainMenu : Symbol(MainMenu, Decl(file.tsx, 9, 3)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 139, 5)) +>props : Symbol(props, Decl(file.tsx, 9, 46)) +>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 927, 45)) + +

Main Menu

+>h3 : Symbol(JSX.IntrinsicElements.h3, Decl(react.d.ts, 939, 48)) +>h3 : Symbol(JSX.IntrinsicElements.h3, Decl(react.d.ts, 939, 48)) + +
); +>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 927, 45)) + +var App: React.StatelessComponent<{ children }> = ({children}) => ( +>App : Symbol(App, Decl(file.tsx, 13, 3)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 139, 5)) +>children : Symbol(children, Decl(file.tsx, 13, 35)) +>children : Symbol(children, Decl(file.tsx, 13, 52)) + +
+>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 927, 45)) + + +>MainMenu : Symbol(MainMenu, Decl(file.tsx, 9, 3)) + +
+>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 927, 45)) + +); diff --git a/tests/baselines/reference/tsxStatelessFunctionComponents3.types b/tests/baselines/reference/tsxStatelessFunctionComponents3.types new file mode 100644 index 0000000000..6531ff737b --- /dev/null +++ b/tests/baselines/reference/tsxStatelessFunctionComponents3.types @@ -0,0 +1,59 @@ +=== tests/cases/conformance/jsx/file.tsx === + +import React = require('react'); +>React : typeof React + +const Foo = (props: any) =>
; +>Foo : (props: any) => JSX.Element +>(props: any) =>
: (props: any) => JSX.Element +>props : any +>
: JSX.Element +>div : any + +// Should be OK +const foo = ; +>foo : JSX.Element +> : JSX.Element +>Foo : (props: any) => JSX.Element + + +// Should be OK +var MainMenu: React.StatelessComponent<{}> = (props) => (
+>MainMenu : React.StatelessComponent<{}> +>React : any +>StatelessComponent : React.StatelessComponent

+>(props) => (

Main Menu

) : (props: {}) => JSX.Element +>props : {} +>(

Main Menu

) : JSX.Element +>

Main Menu

: JSX.Element +>div : any + +

Main Menu

+>

Main Menu

: JSX.Element +>h3 : any +>h3 : any + +
); +>div : any + +var App: React.StatelessComponent<{ children }> = ({children}) => ( +>App : React.StatelessComponent<{ children: any; }> +>React : any +>StatelessComponent : React.StatelessComponent

+>children : any +>({children}) => (

) : ({children}: { children: any; }) => JSX.Element +>children : any +>(
) : JSX.Element + +
+>
: JSX.Element +>div : any + + +> : JSX.Element +>MainMenu : React.StatelessComponent<{}> + +
+>div : any + +); diff --git a/tests/cases/conformance/jsx/tsxElementResolution9.tsx b/tests/cases/conformance/jsx/tsxElementResolution9.tsx index 7165f8277b..4854484a22 100644 --- a/tests/cases/conformance/jsx/tsxElementResolution9.tsx +++ b/tests/cases/conformance/jsx/tsxElementResolution9.tsx @@ -1,7 +1,7 @@ //@filename: file.tsx //@jsx: preserve declare module JSX { - interface Element { } + interface Element { something; } interface IntrinsicElements { } } diff --git a/tests/cases/conformance/jsx/tsxStatelessFunctionComponents3.tsx b/tests/cases/conformance/jsx/tsxStatelessFunctionComponents3.tsx new file mode 100644 index 0000000000..48ce5fb5ef --- /dev/null +++ b/tests/cases/conformance/jsx/tsxStatelessFunctionComponents3.tsx @@ -0,0 +1,23 @@ +// @filename: file.tsx +// @jsx: preserve +// @module: amd +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +const Foo = (props: any) =>
; +// Should be OK +const foo = ; + + +// Should be OK +var MainMenu: React.StatelessComponent<{}> = (props) => (
+

Main Menu

+
); + +var App: React.StatelessComponent<{ children }> = ({children}) => ( +
+ +
+); \ No newline at end of file From cf8daffea1083c0fcf41ace28fb69e1a97e8f976 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 6 Jan 2016 13:21:03 -0800 Subject: [PATCH 2/2] Properly cache JSX element types for SFC expressions --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a5b96c4a9a..9b56824ec1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8264,7 +8264,7 @@ namespace ts { if (intrinsicAttributes !== unknownType) { paramType = intersectTypes(intrinsicAttributes, paramType); } - return paramType; + return links.resolvedJsxType = paramType; } }