From b47194bfa1dfe26de5d2fbda44809bdea871a4a8 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 23 Apr 2019 13:51:39 -0700 Subject: [PATCH] Fix contextual types for a single jsx child (#31040) --- src/compiler/checker.ts | 13 ++-- ...drenIndividualErrorElaborations.errors.txt | 5 +- ...xChildrenIndividualErrorElaborations.types | 6 +- ...ldConfusableWithMultipleChildrenNoError.js | 61 +++++++++++++++++++ ...fusableWithMultipleChildrenNoError.symbols | 52 ++++++++++++++++ ...onfusableWithMultipleChildrenNoError.types | 57 +++++++++++++++++ ...dConfusableWithMultipleChildrenNoError.tsx | 26 ++++++++ 7 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.js create mode 100644 tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.symbols create mode 100644 tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.types create mode 100644 tests/cases/compiler/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2652318f959..9127a470325 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11773,6 +11773,10 @@ namespace ts { } } + function getSemanticJsxChildren(children: NodeArray) { + return filter(children, i => !isJsxText(i) || !i.containsOnlyTriviaWhiteSpaces); + } + function elaborateJsxComponents(node: JsxAttributes, source: Type, target: Type, relation: Map) { let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation); let invalidTextDiagnostic: DiagnosticMessage | undefined; @@ -11782,7 +11786,7 @@ namespace ts { const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); const childrenNameType = getLiteralType(childrenPropName); const childrenTargetType = getIndexedAccessType(target, childrenNameType); - const validChildren = filter(containingElement.children, i => !isJsxText(i) || !i.containsOnlyTriviaWhiteSpaces); + const validChildren = getSemanticJsxChildren(containingElement.children); if (!length(validChildren)) { return result; } @@ -18193,16 +18197,17 @@ namespace ts { if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) { return undefined; } - const childIndex = node.children.indexOf(child); + const realChildren = getSemanticJsxChildren(node.children); + const childIndex = realChildren.indexOf(child); const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName); - return childFieldType && mapType(childFieldType, t => { + return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => { if (isArrayLikeType(t)) { return getIndexedAccessType(t, getLiteralType(childIndex)); } else { return t; } - }, /*noReductions*/ true); + }, /*noReductions*/ true)); } function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined { diff --git a/tests/baselines/reference/jsxChildrenIndividualErrorElaborations.errors.txt b/tests/baselines/reference/jsxChildrenIndividualErrorElaborations.errors.txt index b8f4ed8b2a8..f7750b86b7a 100644 --- a/tests/baselines/reference/jsxChildrenIndividualErrorElaborations.errors.txt +++ b/tests/baselines/reference/jsxChildrenIndividualErrorElaborations.errors.txt @@ -2,6 +2,7 @@ tests/cases/compiler/index.tsx(14,9): error TS2322: Type 'number' is not assigna tests/cases/compiler/index.tsx(18,15): error TS2747: 'Blah' components don't accept text as child elements. Text in JSX has the type 'string', but the expected type of 'children' is '(x: number) => string'. tests/cases/compiler/index.tsx(23,10): error TS2746: This JSX tag's 'children' prop expects a single child of type '(x: number) => string', but multiple children were provided. tests/cases/compiler/index.tsx(37,10): error TS2745: This JSX tag's 'children' prop expects type '((x: number) => string)[]' which requires multiple children, but only a single child was provided. +tests/cases/compiler/index.tsx(38,4): error TS7006: Parameter 'x' implicitly has an 'any' type. tests/cases/compiler/index.tsx(42,10): error TS2745: This JSX tag's 'children' prop expects type '((x: number) => string)[]' which requires multiple children, but only a single child was provided. tests/cases/compiler/index.tsx(48,9): error TS2322: Type 'number' is not assignable to type 'string'. tests/cases/compiler/index.tsx(49,9): error TS2322: Type 'number' is not assignable to type 'string'. @@ -13,7 +14,7 @@ tests/cases/compiler/index.tsx(73,9): error TS2322: Type 'number' is not assigna tests/cases/compiler/index.tsx(74,9): error TS2322: Type 'number' is not assignable to type 'string'. -==== tests/cases/compiler/index.tsx (11 errors) ==== +==== tests/cases/compiler/index.tsx (12 errors) ==== /// import * as React from "react"; @@ -64,6 +65,8 @@ tests/cases/compiler/index.tsx(74,9): error TS2322: Type 'number' is not assigna ~~~~~ !!! error TS2745: This JSX tag's 'children' prop expects type '((x: number) => string)[]' which requires multiple children, but only a single child was provided. {x => x} + ~ +!!! error TS7006: Parameter 'x' implicitly has an 'any' type. // Blah2 components don't accept text as child elements diff --git a/tests/baselines/reference/jsxChildrenIndividualErrorElaborations.types b/tests/baselines/reference/jsxChildrenIndividualErrorElaborations.types index d466a71ecc3..267e3a7027e 100644 --- a/tests/baselines/reference/jsxChildrenIndividualErrorElaborations.types +++ b/tests/baselines/reference/jsxChildrenIndividualErrorElaborations.types @@ -85,9 +85,9 @@ var a = >Blah2 : (props: PropsArr) => JSX.Element {x => x} ->x => x : (x: number) => number ->x : number ->x : number +>x => x : (x: any) => any +>x : any +>x : any >Blah2 : (props: PropsArr) => JSX.Element diff --git a/tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.js b/tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.js new file mode 100644 index 00000000000..7ccea1b36ae --- /dev/null +++ b/tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.js @@ -0,0 +1,61 @@ +//// [jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx] +/// + +import * as React from 'react' + +type Tab = [string, React.ReactNode] // [tabName, tabContent] + +interface Props { + children: Tab[] +} + +function TabLayout(props: Props) { + return
+} + +export class App extends React.Component<{}> { + render() { + return + {[ + ['Users',
], + ['Products',
] + ]} + + } +} + +//// [jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.js] +"use strict"; +/// +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var React = require("react"); +function TabLayout(props) { + return React.createElement("div", null); +} +var App = /** @class */ (function (_super) { + __extends(App, _super); + function App() { + return _super !== null && _super.apply(this, arguments) || this; + } + App.prototype.render = function () { + return React.createElement(TabLayout, null, [ + ['Users', React.createElement("div", null)], + ['Products', React.createElement("div", null)] + ]); + }; + return App; +}(React.Component)); +exports.App = App; diff --git a/tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.symbols b/tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.symbols new file mode 100644 index 00000000000..93737d2a6f8 --- /dev/null +++ b/tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.symbols @@ -0,0 +1,52 @@ +=== tests/cases/compiler/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx === +/// + +import * as React from 'react' +>React : Symbol(React, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 6)) + +type Tab = [string, React.ReactNode] // [tabName, tabContent] +>Tab : Symbol(Tab, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 30)) +>React : Symbol(React, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 6)) +>ReactNode : Symbol(React.ReactNode, Decl(react16.d.ts, 216, 49)) + +interface Props { +>Props : Symbol(Props, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 4, 36)) + + children: Tab[] +>children : Symbol(Props.children, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 6, 17)) +>Tab : Symbol(Tab, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 30)) +} + +function TabLayout(props: Props) { +>TabLayout : Symbol(TabLayout, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 8, 1)) +>props : Symbol(props, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 10, 19)) +>Props : Symbol(Props, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 4, 36)) + + return
+>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114)) +} + +export class App extends React.Component<{}> { +>App : Symbol(App, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 12, 1)) +>React.Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94)) +>React : Symbol(React, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 6)) +>Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94)) + + render() { +>render : Symbol(App.render, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 14, 46)) + + return +>TabLayout : Symbol(TabLayout, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 8, 1)) + + {[ + ['Users',
], +>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114)) + + ['Products',
] +>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114)) + + ]} + +>TabLayout : Symbol(TabLayout, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 8, 1)) + } +} diff --git a/tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.types b/tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.types new file mode 100644 index 00000000000..dd302f40679 --- /dev/null +++ b/tests/baselines/reference/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.types @@ -0,0 +1,57 @@ +=== tests/cases/compiler/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx === +/// + +import * as React from 'react' +>React : typeof React + +type Tab = [string, React.ReactNode] // [tabName, tabContent] +>Tab : [string, React.ReactNode] +>React : any + +interface Props { + children: Tab[] +>children : [string, React.ReactNode][] +} + +function TabLayout(props: Props) { +>TabLayout : (props: Props) => JSX.Element +>props : Props + + return
+>
: JSX.Element +>div : any +} + +export class App extends React.Component<{}> { +>App : App +>React.Component : React.Component<{}, {}, any> +>React : typeof React +>Component : typeof React.Component + + render() { +>render : () => JSX.Element + + return +> {[ ['Users',
], ['Products',
] ]} : JSX.Element +>TabLayout : (props: Props) => JSX.Element + + {[ +>[ ['Users',
], ['Products',
] ] : [string, JSX.Element][] + + ['Users',
], +>['Users',
] : [string, JSX.Element] +>'Users' : "Users" +>
: JSX.Element +>div : any + + ['Products',
] +>['Products',
] : [string, JSX.Element] +>'Products' : "Products" +>
: JSX.Element +>div : any + + ]} + +>TabLayout : (props: Props) => JSX.Element + } +} diff --git a/tests/cases/compiler/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx b/tests/cases/compiler/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx new file mode 100644 index 00000000000..ae53669cad5 --- /dev/null +++ b/tests/cases/compiler/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx @@ -0,0 +1,26 @@ +// @skipLibCheck: true +// @jsx: react +/// + +import * as React from 'react' + +type Tab = [string, React.ReactNode] // [tabName, tabContent] + +interface Props { + children: Tab[] +} + +function TabLayout(props: Props) { + return
+} + +export class App extends React.Component<{}> { + render() { + return + {[ + ['Users',
], + ['Products',
] + ]} + + } +} \ No newline at end of file