Skip to content

Commit 74e9f9f

Browse files
committed
Add arity error, refine messages and spans
1 parent 70ffc24 commit 74e9f9f

15 files changed

+780
-160
lines changed

src/compiler/checker.ts

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11314,41 +11314,94 @@ namespace ts {
1131411314

1131511315
function *generateJsxChildren(node: JsxElement): ElaborationIterator {
1131611316
if (!length(node.children)) return;
11317-
for(let i=0; i < node.children.length; i++) {
11317+
let memberOffset = 0;
11318+
for (let i = 0; i < node.children.length; i++) {
1131811319
const child = node.children[i];
11319-
const nameType = getLiteralType(i);
11320-
switch (child.kind) {
11321-
case SyntaxKind.JsxText:
11322-
if (child.containsOnlyWhiteSpaces) {
11323-
break; // Whitespace only jsx text isn't real jsx text
11324-
}
11325-
// child is of type string
11326-
yield { errorNode: child, innerExpression: undefined, nameType };
11327-
break;
11328-
case SyntaxKind.JsxExpression:
11329-
// child is of the type of the expression
11330-
yield { errorNode: child, innerExpression: child.expression, nameType };
11331-
break;
11332-
case SyntaxKind.JsxElement:
11333-
case SyntaxKind.JsxSelfClosingElement:
11334-
case SyntaxKind.JsxFragment:
11335-
// child is of type JSX.Element
11336-
yield { errorNode: child, innerExpression: undefined, nameType };
11337-
break;
11338-
default:
11339-
return Debug.assertNever(child, "Found invalid jsx child");
11320+
const nameType = getLiteralType(i - memberOffset);
11321+
const elem = getElaborationElementForJsxChild(child, nameType);
11322+
if (elem) {
11323+
yield elem;
11324+
}
11325+
else {
11326+
memberOffset++;
1134011327
}
1134111328
}
1134211329
}
1134311330

11331+
function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType) {
11332+
switch (child.kind) {
11333+
case SyntaxKind.JsxExpression:
11334+
// child is of the type of the expression
11335+
return { errorNode: child, innerExpression: child.expression, nameType };
11336+
case SyntaxKind.JsxText:
11337+
if (child.containsOnlyWhiteSpaces) {
11338+
break; // Whitespace only jsx text isn't real jsx text
11339+
}
11340+
// child is a string
11341+
return { errorNode: child, innerExpression: undefined, nameType };
11342+
case SyntaxKind.JsxElement:
11343+
case SyntaxKind.JsxSelfClosingElement:
11344+
case SyntaxKind.JsxFragment:
11345+
// child is of type JSX.Element
11346+
return { errorNode: child, innerExpression: child, nameType };
11347+
default:
11348+
return Debug.assertNever(child, "Found invalid jsx child");
11349+
}
11350+
}
11351+
1134411352
function elaborateJsxComponents(node: JsxAttributes, source: Type, target: Type, relation: Map<RelationComparisonResult>) {
1134511353
let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation);
1134611354
if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) {
11355+
const containingElement = node.parent.parent;
1134711356
const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
1134811357
const childrenNameType = getLiteralType(childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName));
1134911358
const childrenTargetType = getIndexedAccessType(target, childrenNameType);
11350-
if (isArrayLikeType(childrenTargetType)) {
11351-
result = elaborateElementwise(generateJsxChildren(node.parent.parent), getIndexedAccessType(source, childrenNameType), childrenTargetType, relation) || result;
11359+
const validChildren = filter(containingElement.children, i => !isJsxText(i) || !i.containsOnlyWhiteSpaces);
11360+
if (!length(validChildren)) {
11361+
return result;
11362+
}
11363+
const moreThanOneRealChildren = length(validChildren) > 1;
11364+
const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType);
11365+
const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t));
11366+
if (moreThanOneRealChildren) {
11367+
if (arrayLikeTargetParts !== neverType) {
11368+
const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal));
11369+
result = elaborateElementwise(generateJsxChildren(containingElement), realSource, arrayLikeTargetParts, relation) || result;
11370+
}
11371+
else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) {
11372+
// arity mismatch
11373+
result = true;
11374+
error(
11375+
containingElement.openingElement.tagName,
11376+
Diagnostics.Target_JSX_element_expects_0_prop_of_type_1_but_multiple_children_were_provided,
11377+
childPropName ? unescapeLeadingUnderscores(childPropName) : "children",
11378+
typeToString(childrenTargetType)
11379+
);
11380+
}
11381+
}
11382+
else {
11383+
if (nonArrayLikeTargetParts !== neverType) {
11384+
const child = validChildren[0];
11385+
const elem = getElaborationElementForJsxChild(child, childrenNameType);
11386+
if (elem) {
11387+
result = elaborateElementwise(
11388+
(function*() { yield elem; })(),
11389+
source,
11390+
target,
11391+
relation
11392+
) || result;
11393+
}
11394+
}
11395+
else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) {
11396+
// arity mismatch
11397+
result = true;
11398+
error(
11399+
containingElement.openingElement.tagName,
11400+
Diagnostics.Target_JSX_element_expects_0_prop_of_type_1_but_only_a_single_child_was_provided,
11401+
childPropName ? unescapeLeadingUnderscores(childPropName) : "children",
11402+
typeToString(childrenTargetType)
11403+
);
11404+
}
1135211405
}
1135311406
}
1135411407
return result;
@@ -13499,6 +13552,10 @@ namespace ts {
1349913552
return isTupleType(type) || !!getPropertyOfType(type, "0" as __String);
1350013553
}
1350113554

13555+
function isArrayOrTupleLikeType(type: Type): boolean {
13556+
return isArrayLikeType(type) || isTupleLikeType(type);
13557+
}
13558+
1350213559
function getTupleElementType(type: Type, index: number) {
1350313560
const propType = getTypeOfPropertyOfType(type, "" + index as __String);
1350413561
if (propType) {

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,6 +2541,14 @@
25412541
"category": "Error",
25422542
"code": 2744
25432543
},
2544+
"Target JSX element expects {0} prop of type {1}, but only a single child was provided.": {
2545+
"category": "Error",
2546+
"code": 2745
2547+
},
2548+
"Target JSX element expects {0} prop of type {1}, but multiple children were provided.": {
2549+
"category": "Error",
2550+
"code": 2746
2551+
},
25442552

25452553
"Import declaration '{0}' is using private name '{1}'.": {
25462554
"category": "Error",

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ namespace ts {
888888
}
889889

890890
const isMissing = nodeIsMissing(errorNode);
891-
const pos = isMissing
891+
const pos = isMissing || isJsxText(node)
892892
? errorNode.pos
893893
: skipTrivia(sourceFile.text, errorNode.pos);
894894

tests/baselines/reference/checkJsxChildrenProperty14.errors.txt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
tests/cases/conformance/jsx/file.tsx(42,11): error TS2322: Type '{ children: Element[]; a: number; b: string; }' is not assignable to type 'SingleChildProp'.
2-
Types of property 'children' are incompatible.
3-
Type 'Element[]' is missing the following properties from type 'Element': type, props
1+
tests/cases/conformance/jsx/file.tsx(42,11): error TS2746: Target JSX element expects children prop of type Element, but multiple children were provided.
42

53

64
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
@@ -47,6 +45,4 @@ tests/cases/conformance/jsx/file.tsx(42,11): error TS2322: Type '{ children: Ele
4745
// Error
4846
let k5 = <SingleChildComp a={10} b="hi"><></><Button /><AnotherButton /></SingleChildComp>;
4947
~~~~~~~~~~~~~~~
50-
!!! error TS2322: Type '{ children: Element[]; a: number; b: string; }' is not assignable to type 'SingleChildProp'.
51-
!!! error TS2322: Types of property 'children' are incompatible.
52-
!!! error TS2322: Type 'Element[]' is missing the following properties from type 'Element': type, props
48+
!!! error TS2746: Target JSX element expects children prop of type Element, but multiple children were provided.

tests/baselines/reference/checkJsxChildrenProperty2.errors.txt

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,9 @@
11
tests/cases/conformance/jsx/file.tsx(14,10): error TS2741: Property 'children' is missing in type '{ a: number; b: string; }' but required in type 'Prop'.
22
tests/cases/conformance/jsx/file.tsx(17,11): error TS2710: 'children' are specified twice. The attribute named 'children' will be overwritten.
3-
tests/cases/conformance/jsx/file.tsx(31,6): error TS2322: Type '{ children: (Element | ((name: string) => Element))[]; a: number; b: string; }' is not assignable to type 'Prop'.
4-
Types of property 'children' are incompatible.
5-
Type '(Element | ((name: string) => Element))[]' is not assignable to type 'string | Element'.
6-
Type '(Element | ((name: string) => Element))[]' is not assignable to type 'string'.
7-
tests/cases/conformance/jsx/file.tsx(37,6): error TS2322: Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'.
8-
Types of property 'children' are incompatible.
9-
Type '(number | Element)[]' is not assignable to type 'string | Element'.
10-
Type '(number | Element)[]' is not assignable to type 'string'.
11-
tests/cases/conformance/jsx/file.tsx(43,6): error TS2322: Type '{ children: (string | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'.
12-
Types of property 'children' are incompatible.
13-
Type '(string | Element)[]' is not assignable to type 'string | Element'.
14-
Type '(string | Element)[]' is not assignable to type 'string'.
15-
tests/cases/conformance/jsx/file.tsx(49,6): error TS2322: Type '{ children: Element[]; a: number; b: string; }' is not assignable to type 'Prop'.
16-
Types of property 'children' are incompatible.
17-
Type 'Element[]' is not assignable to type 'string | Element'.
18-
Type 'Element[]' is not assignable to type 'string'.
3+
tests/cases/conformance/jsx/file.tsx(31,6): error TS2746: Target JSX element expects children prop of type string | Element, but multiple children were provided.
4+
tests/cases/conformance/jsx/file.tsx(37,6): error TS2746: Target JSX element expects children prop of type string | Element, but multiple children were provided.
5+
tests/cases/conformance/jsx/file.tsx(43,6): error TS2746: Target JSX element expects children prop of type string | Element, but multiple children were provided.
6+
tests/cases/conformance/jsx/file.tsx(49,6): error TS2746: Target JSX element expects children prop of type string | Element, but multiple children were provided.
197

208

219
==== tests/cases/conformance/jsx/file.tsx (6 errors) ====
@@ -56,43 +44,31 @@ tests/cases/conformance/jsx/file.tsx(49,6): error TS2322: Type '{ children: Elem
5644
let k2 =
5745
<Comp a={10} b="hi">
5846
~~~~
59-
!!! error TS2322: Type '{ children: (Element | ((name: string) => Element))[]; a: number; b: string; }' is not assignable to type 'Prop'.
60-
!!! error TS2322: Types of property 'children' are incompatible.
61-
!!! error TS2322: Type '(Element | ((name: string) => Element))[]' is not assignable to type 'string | Element'.
62-
!!! error TS2322: Type '(Element | ((name: string) => Element))[]' is not assignable to type 'string'.
47+
!!! error TS2746: Target JSX element expects children prop of type string | Element, but multiple children were provided.
6348
<div> My Div </div>
6449
{(name: string) => <div> My name {name} </div>}
6550
</Comp>;
6651

6752
let k3 =
6853
<Comp a={10} b="hi">
6954
~~~~
70-
!!! error TS2322: Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'.
71-
!!! error TS2322: Types of property 'children' are incompatible.
72-
!!! error TS2322: Type '(number | Element)[]' is not assignable to type 'string | Element'.
73-
!!! error TS2322: Type '(number | Element)[]' is not assignable to type 'string'.
55+
!!! error TS2746: Target JSX element expects children prop of type string | Element, but multiple children were provided.
7456
<div> My Div </div>
7557
{1000000}
7658
</Comp>;
7759

7860
let k4 =
7961
<Comp a={10} b="hi" >
8062
~~~~
81-
!!! error TS2322: Type '{ children: (string | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'.
82-
!!! error TS2322: Types of property 'children' are incompatible.
83-
!!! error TS2322: Type '(string | Element)[]' is not assignable to type 'string | Element'.
84-
!!! error TS2322: Type '(string | Element)[]' is not assignable to type 'string'.
63+
!!! error TS2746: Target JSX element expects children prop of type string | Element, but multiple children were provided.
8564
<div> My Div </div>
8665
hi hi hi!
8766
</Comp>;
8867

8968
let k5 =
9069
<Comp a={10} b="hi" >
9170
~~~~
92-
!!! error TS2322: Type '{ children: Element[]; a: number; b: string; }' is not assignable to type 'Prop'.
93-
!!! error TS2322: Types of property 'children' are incompatible.
94-
!!! error TS2322: Type 'Element[]' is not assignable to type 'string | Element'.
95-
!!! error TS2322: Type 'Element[]' is not assignable to type 'string'.
71+
!!! error TS2746: Target JSX element expects children prop of type string | Element, but multiple children were provided.
9672
<div> My Div </div>
9773
<div> My Div </div>
9874
</Comp>;

tests/baselines/reference/checkJsxChildrenProperty4.errors.txt

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
tests/cases/conformance/jsx/file.tsx(24,28): error TS2551: Property 'NAme' does not exist on type 'IUser'. Did you mean 'Name'?
2-
tests/cases/conformance/jsx/file.tsx(32,10): error TS2322: Type '{ children: (((user: IUser) => Element) | ((user: IUser) => Element))[]; }' is not assignable to type 'IFetchUserProps'.
3-
Types of property 'children' are incompatible.
4-
Type '(((user: IUser) => Element) | ((user: IUser) => Element))[]' is not assignable to type '(user: IUser) => Element'.
5-
Type '(((user: IUser) => Element) | ((user: IUser) => Element))[]' provides no match for the signature '(user: IUser): Element'.
2+
tests/cases/conformance/jsx/file.tsx(36,15): error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | any[] | ReactElement<any>'.
3+
Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement<any>': type, props
4+
tests/cases/conformance/jsx/file.tsx(39,15): error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | any[] | ReactElement<any>'.
5+
Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement<any>': type, props
66

77

8-
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
8+
==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
99
import React = require('react');
1010

1111
interface IUser {
@@ -41,20 +41,27 @@ tests/cases/conformance/jsx/file.tsx(32,10): error TS2322: Type '{ children: (((
4141
function UserName1() {
4242
return (
4343
<FetchUser>
44-
~~~~~~~~~
45-
!!! error TS2322: Type '{ children: (((user: IUser) => Element) | ((user: IUser) => Element))[]; }' is not assignable to type 'IFetchUserProps'.
46-
!!! error TS2322: Types of property 'children' are incompatible.
47-
!!! error TS2322: Type '(((user: IUser) => Element) | ((user: IUser) => Element))[]' is not assignable to type '(user: IUser) => Element'.
48-
!!! error TS2322: Type '(((user: IUser) => Element) | ((user: IUser) => Element))[]' provides no match for the signature '(user: IUser): Element'.
4944

5045

5146

5247
{ user => (
48+
~~~~~~~~~
5349
<h1>{ user.Name }</h1>
50+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5451
) }
52+
~~~~~~~~~~~~~
53+
!!! error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | any[] | ReactElement<any>'.
54+
!!! error TS2322: Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement<any>': type, props
55+
!!! related TS6212 tests/cases/conformance/jsx/file.tsx:36:15: Did you mean to call this expression?
5556
{ user => (
57+
~~~~~~~~~
5658
<h1>{ user.Name }</h1>
59+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5760
) }
61+
~~~~~~~~~~~~~
62+
!!! error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | any[] | ReactElement<any>'.
63+
!!! error TS2322: Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement<any>': type, props
64+
!!! related TS6212 tests/cases/conformance/jsx/file.tsx:39:15: Did you mean to call this expression?
5865
</FetchUser>
5966
);
6067
}

0 commit comments

Comments
 (0)