Skip to content

Commit 1f56ab0

Browse files
uhyoweswigham
andauthored
Improve error message for invalid return type of JSX component (microsoft#32702)
* New diagnostic message for wrong JSX function component * Component and Mixed type * fix existing tests * add new test for JSX component return type error * fix tslint error * update diagnostic message to include component name * accept baseline * update tests * missing semicolon * accept baseline Co-authored-by: Wesley Wigham <[email protected]>
1 parent 4c440e5 commit 1f56ab0

12 files changed

+509
-47
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23356,18 +23356,18 @@ namespace ts {
2335623356
return anyType;
2335723357
}
2335823358

23359-
function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: Node) {
23359+
function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: JsxOpeningLikeElement) {
2336023360
if (refKind === JsxReferenceKind.Function) {
2336123361
const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
2336223362
if (sfcReturnConstraint) {
23363-
checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
23363+
checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_return_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain);
2336423364
}
2336523365
}
2336623366
else if (refKind === JsxReferenceKind.Component) {
2336723367
const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
2336823368
if (classConstraint) {
23369-
// Issue an error if this return type isn't assignable to JSX.ElementClass or JSX.Element, failing that
23370-
checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
23369+
// Issue an error if this return type isn't assignable to JSX.ElementClass, failing that
23370+
checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_instance_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain);
2337123371
}
2337223372
}
2337323373
else { // Mixed
@@ -23377,7 +23377,12 @@ namespace ts {
2337723377
return;
2337823378
}
2337923379
const combined = getUnionType([sfcReturnConstraint, classConstraint]);
23380-
checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
23380+
checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_element_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain);
23381+
}
23382+
23383+
function generateInitialErrorChain(): DiagnosticMessageChain {
23384+
const componentName = getTextOfNode(openingLikeElement.tagName);
23385+
return chainDiagnosticMessages(/* details */ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName);
2338123386
}
2338223387
}
2338323388

@@ -23468,8 +23473,9 @@ namespace ts {
2346823473
}
2346923474

2347023475
if (isNodeOpeningLikeElement) {
23471-
const sig = getResolvedSignature(node as JsxOpeningLikeElement);
23472-
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(node as JsxOpeningLikeElement), getReturnTypeOfSignature(sig), node);
23476+
const jsxOpeningLikeNode = node as JsxOpeningLikeElement;
23477+
const sig = getResolvedSignature(jsxOpeningLikeNode);
23478+
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode);
2347323479
}
2347423480
}
2347523481

src/compiler/diagnosticMessages.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2947,6 +2947,22 @@
29472947
"category": "Error",
29482948
"code": 2785
29492949
},
2950+
"'{0}' cannot be used as a JSX component.": {
2951+
"category": "Error",
2952+
"code": 2786
2953+
},
2954+
"Its return type '{0}' is not a valid JSX element.": {
2955+
"category": "Error",
2956+
"code": 2787
2957+
},
2958+
"Its instance type '{0}' is not a valid JSX element.": {
2959+
"category": "Error",
2960+
"code": 2788
2961+
},
2962+
"Its element type '{0}' is not a valid JSX element.": {
2963+
"category": "Error",
2964+
"code": 2789
2965+
},
29502966

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

tests/baselines/reference/inlineJsxFactoryDeclarationsLocalTypes.errors.txt

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
tests/cases/conformance/jsx/inline/index.tsx(5,1): error TS2741: Property '__predomBrand' is missing in type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element' but required in type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element'.
2-
tests/cases/conformance/jsx/inline/index.tsx(21,21): error TS2605: JSX element type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' is not a constructor function for JSX elements.
3-
Property '__domBrand' is missing in type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' but required in type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element'.
2+
tests/cases/conformance/jsx/inline/index.tsx(21,22): error TS2786: 'MySFC' cannot be used as a JSX component.
3+
Its return type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' is not a valid JSX element.
4+
Property '__domBrand' is missing in type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' but required in type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element'.
45
tests/cases/conformance/jsx/inline/index.tsx(21,40): error TS2322: Type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element' is not assignable to type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element'.
5-
tests/cases/conformance/jsx/inline/index.tsx(21,40): error TS2605: JSX element type 'MyClass' is not a constructor function for JSX elements.
6-
Property '__domBrand' is missing in type 'MyClass' but required in type 'ElementClass'.
6+
tests/cases/conformance/jsx/inline/index.tsx(21,41): error TS2786: 'MyClass' cannot be used as a JSX component.
7+
Its instance type 'MyClass' is not a valid JSX element.
8+
Property '__domBrand' is missing in type 'MyClass' but required in type 'ElementClass'.
79
tests/cases/conformance/jsx/inline/index.tsx(21,63): error TS2322: Type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element' is not assignable to type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element'.
8-
tests/cases/conformance/jsx/inline/index.tsx(21,63): error TS2605: JSX element type 'MyClass' is not a constructor function for JSX elements.
10+
tests/cases/conformance/jsx/inline/index.tsx(21,64): error TS2786: 'MyClass' cannot be used as a JSX component.
11+
Its instance type 'MyClass' is not a valid JSX element.
912
tests/cases/conformance/jsx/inline/index.tsx(24,42): error TS2322: Type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' is not assignable to type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element'.
1013
tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' is not assignable to type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element'.
1114

@@ -95,20 +98,23 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
9598

9699
// Should fail, no dom elements
97100
const _brokenTree = <MySFC x={1} y={2}><MyClass x={3} y={4} /><MyClass x={5} y={6} /></MySFC>
98-
~~~~~~~~~~~~~~~~~~~
99-
!!! error TS2605: JSX element type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' is not a constructor function for JSX elements.
100-
!!! error TS2605: Property '__domBrand' is missing in type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' but required in type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element'.
101+
~~~~~
102+
!!! error TS2786: 'MySFC' cannot be used as a JSX component.
103+
!!! error TS2786: Its return type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' is not a valid JSX element.
104+
!!! error TS2786: Property '__domBrand' is missing in type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' but required in type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element'.
101105
!!! related TS2728 tests/cases/conformance/jsx/inline/renderer.d.ts:7:13: '__domBrand' is declared here.
102106
~~~~~~~~~~~~~~~~~~~~~~~
103107
!!! error TS2322: Type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element' is not assignable to type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element'.
104-
~~~~~~~~~~~~~~~~~~~~~~~
105-
!!! error TS2605: JSX element type 'MyClass' is not a constructor function for JSX elements.
106-
!!! error TS2605: Property '__domBrand' is missing in type 'MyClass' but required in type 'ElementClass'.
108+
~~~~~~~
109+
!!! error TS2786: 'MyClass' cannot be used as a JSX component.
110+
!!! error TS2786: Its instance type 'MyClass' is not a valid JSX element.
111+
!!! error TS2786: Property '__domBrand' is missing in type 'MyClass' but required in type 'ElementClass'.
107112
!!! related TS2728 tests/cases/conformance/jsx/inline/renderer.d.ts:7:13: '__domBrand' is declared here.
108113
~~~~~~~~~~~~~~~~~~~~~~~
109114
!!! error TS2322: Type 'import("tests/cases/conformance/jsx/inline/renderer").dom.JSX.Element' is not assignable to type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element'.
110-
~~~~~~~~~~~~~~~~~~~~~~~
111-
!!! error TS2605: JSX element type 'MyClass' is not a constructor function for JSX elements.
115+
~~~~~~~
116+
!!! error TS2786: 'MyClass' cannot be used as a JSX component.
117+
!!! error TS2786: Its instance type 'MyClass' is not a valid JSX element.
112118

113119
// Should fail, nondom isn't allowed as children of dom
114120
const _brokenTree2 = <DOMSFC x={1} y={2}>{tree}{tree}</DOMSFC>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
tests/cases/compiler/jsxComponentTypeErrors.tsx(16,11): error TS2786: 'this' cannot be used as a JSX component.
2+
Its return type '{ type: "foo" | undefined; }' is not a valid JSX element.
3+
Types of property 'type' are incompatible.
4+
Type '"foo" | undefined' is not assignable to type '"element"'.
5+
Type 'undefined' is not assignable to type '"element"'.
6+
tests/cases/compiler/jsxComponentTypeErrors.tsx(25,16): error TS2786: 'FunctionComponent' cannot be used as a JSX component.
7+
Its return type '{ type: "abc" | undefined; }' is not a valid JSX element.
8+
Types of property 'type' are incompatible.
9+
Type '"abc" | undefined' is not assignable to type '"element"'.
10+
Type 'undefined' is not assignable to type '"element"'.
11+
tests/cases/compiler/jsxComponentTypeErrors.tsx(26,16): error TS2786: 'FunctionComponent' cannot be used as a JSX component.
12+
Its return type '{ type: "abc" | undefined; }' is not a valid JSX element.
13+
tests/cases/compiler/jsxComponentTypeErrors.tsx(27,16): error TS2786: 'ClassComponent' cannot be used as a JSX component.
14+
Its instance type 'ClassComponent' is not a valid JSX element.
15+
Types of property 'type' are incompatible.
16+
Type 'string' is not assignable to type '"element-class"'.
17+
tests/cases/compiler/jsxComponentTypeErrors.tsx(28,16): error TS2786: 'MixedComponent' cannot be used as a JSX component.
18+
Its element type 'ClassComponent | { type: string | undefined; }' is not a valid JSX element.
19+
Type 'ClassComponent' is not assignable to type 'Element | ElementClass | null'.
20+
Type 'ClassComponent' is not assignable to type 'ElementClass'.
21+
tests/cases/compiler/jsxComponentTypeErrors.tsx(37,16): error TS2786: 'obj.MemberFunctionComponent' cannot be used as a JSX component.
22+
Its return type '{}' is not a valid JSX element.
23+
Property 'type' is missing in type '{}' but required in type 'Element'.
24+
tests/cases/compiler/jsxComponentTypeErrors.tsx(38,16): error TS2786: 'obj. MemberClassComponent' cannot be used as a JSX component.
25+
Its instance type 'MemberClassComponent' is not a valid JSX element.
26+
Property 'type' is missing in type 'MemberClassComponent' but required in type 'ElementClass'.
27+
28+
29+
==== tests/cases/compiler/jsxComponentTypeErrors.tsx (7 errors) ====
30+
namespace JSX {
31+
export interface Element {
32+
type: 'element';
33+
}
34+
export interface ElementClass {
35+
type: 'element-class';
36+
}
37+
}
38+
39+
function FunctionComponent<T extends string>({type}: {type?: T}) {
40+
return {
41+
type
42+
}
43+
}
44+
FunctionComponent.useThis = function() {
45+
return <this type="foo" />;
46+
~~~~
47+
!!! error TS2786: 'this' cannot be used as a JSX component.
48+
!!! error TS2786: Its return type '{ type: "foo" | undefined; }' is not a valid JSX element.
49+
!!! error TS2786: Types of property 'type' are incompatible.
50+
!!! error TS2786: Type '"foo" | undefined' is not assignable to type '"element"'.
51+
!!! error TS2786: Type 'undefined' is not assignable to type '"element"'.
52+
}
53+
54+
class ClassComponent {
55+
type = 'string';
56+
}
57+
58+
const MixedComponent = Math.random() ? FunctionComponent : ClassComponent;
59+
60+
const elem1 = <FunctionComponent type="abc" />;
61+
~~~~~~~~~~~~~~~~~
62+
!!! error TS2786: 'FunctionComponent' cannot be used as a JSX component.
63+
!!! error TS2786: Its return type '{ type: "abc" | undefined; }' is not a valid JSX element.
64+
!!! error TS2786: Types of property 'type' are incompatible.
65+
!!! error TS2786: Type '"abc" | undefined' is not assignable to type '"element"'.
66+
!!! error TS2786: Type 'undefined' is not assignable to type '"element"'.
67+
const elem2 = <FunctionComponent<"abc"> />;
68+
~~~~~~~~~~~~~~~~~
69+
!!! error TS2786: 'FunctionComponent' cannot be used as a JSX component.
70+
!!! error TS2786: Its return type '{ type: "abc" | undefined; }' is not a valid JSX element.
71+
const elem3 = <ClassComponent />;
72+
~~~~~~~~~~~~~~
73+
!!! error TS2786: 'ClassComponent' cannot be used as a JSX component.
74+
!!! error TS2786: Its instance type 'ClassComponent' is not a valid JSX element.
75+
!!! error TS2786: Types of property 'type' are incompatible.
76+
!!! error TS2786: Type 'string' is not assignable to type '"element-class"'.
77+
const elem4 = <MixedComponent />;
78+
~~~~~~~~~~~~~~
79+
!!! error TS2786: 'MixedComponent' cannot be used as a JSX component.
80+
!!! error TS2786: Its element type 'ClassComponent | { type: string | undefined; }' is not a valid JSX element.
81+
!!! error TS2786: Type 'ClassComponent' is not assignable to type 'Element | ElementClass | null'.
82+
!!! error TS2786: Type 'ClassComponent' is not assignable to type 'ElementClass'.
83+
84+
const obj = {
85+
MemberFunctionComponent() {
86+
return {};
87+
},
88+
MemberClassComponent: class {},
89+
};
90+
91+
const elem5 = <obj.MemberFunctionComponent />;
92+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
93+
!!! error TS2786: 'obj.MemberFunctionComponent' cannot be used as a JSX component.
94+
!!! error TS2786: Its return type '{}' is not a valid JSX element.
95+
!!! error TS2786: Property 'type' is missing in type '{}' but required in type 'Element'.
96+
!!! related TS2728 tests/cases/compiler/jsxComponentTypeErrors.tsx:3:5: 'type' is declared here.
97+
const elem6 = <obj. MemberClassComponent />;
98+
~~~~~~~~~~~~~~~~~~~~~~~~~
99+
!!! error TS2786: 'obj. MemberClassComponent' cannot be used as a JSX component.
100+
!!! error TS2786: Its instance type 'MemberClassComponent' is not a valid JSX element.
101+
!!! error TS2786: Property 'type' is missing in type 'MemberClassComponent' but required in type 'ElementClass'.
102+
!!! related TS2728 tests/cases/compiler/jsxComponentTypeErrors.tsx:6:5: 'type' is declared here.
103+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//// [jsxComponentTypeErrors.tsx]
2+
namespace JSX {
3+
export interface Element {
4+
type: 'element';
5+
}
6+
export interface ElementClass {
7+
type: 'element-class';
8+
}
9+
}
10+
11+
function FunctionComponent<T extends string>({type}: {type?: T}) {
12+
return {
13+
type
14+
}
15+
}
16+
FunctionComponent.useThis = function() {
17+
return <this type="foo" />;
18+
}
19+
20+
class ClassComponent {
21+
type = 'string';
22+
}
23+
24+
const MixedComponent = Math.random() ? FunctionComponent : ClassComponent;
25+
26+
const elem1 = <FunctionComponent type="abc" />;
27+
const elem2 = <FunctionComponent<"abc"> />;
28+
const elem3 = <ClassComponent />;
29+
const elem4 = <MixedComponent />;
30+
31+
const obj = {
32+
MemberFunctionComponent() {
33+
return {};
34+
},
35+
MemberClassComponent: class {},
36+
};
37+
38+
const elem5 = <obj.MemberFunctionComponent />;
39+
const elem6 = <obj. MemberClassComponent />;
40+
41+
42+
//// [jsxComponentTypeErrors.jsx]
43+
"use strict";
44+
function FunctionComponent(_a) {
45+
var type = _a.type;
46+
return {
47+
type: type
48+
};
49+
}
50+
FunctionComponent.useThis = function () {
51+
return <this type="foo"/>;
52+
};
53+
var ClassComponent = /** @class */ (function () {
54+
function ClassComponent() {
55+
this.type = 'string';
56+
}
57+
return ClassComponent;
58+
}());
59+
var MixedComponent = Math.random() ? FunctionComponent : ClassComponent;
60+
var elem1 = <FunctionComponent type="abc"/>;
61+
var elem2 = <FunctionComponent />;
62+
var elem3 = <ClassComponent />;
63+
var elem4 = <MixedComponent />;
64+
var obj = {
65+
MemberFunctionComponent: function () {
66+
return {};
67+
},
68+
MemberClassComponent: /** @class */ (function () {
69+
function MemberClassComponent() {
70+
}
71+
return MemberClassComponent;
72+
}())
73+
};
74+
var elem5 = <obj.MemberFunctionComponent />;
75+
var elem6 = <obj.MemberClassComponent />;

0 commit comments

Comments
 (0)