Skip to content

Improve error message for invalid return type of JSX component #32702

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23356,18 +23356,18 @@ namespace ts {
return anyType;
}

function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: Node) {
function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: JsxOpeningLikeElement) {
if (refKind === JsxReferenceKind.Function) {
const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
if (sfcReturnConstraint) {
checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_return_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain);
}
}
else if (refKind === JsxReferenceKind.Component) {
const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
if (classConstraint) {
// Issue an error if this return type isn't assignable to JSX.ElementClass or JSX.Element, failing that
checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
// Issue an error if this return type isn't assignable to JSX.ElementClass, failing that
checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_instance_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain);
}
}
else { // Mixed
Expand All @@ -23377,7 +23377,12 @@ namespace ts {
return;
}
const combined = getUnionType([sfcReturnConstraint, classConstraint]);
checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_element_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain);
}

function generateInitialErrorChain(): DiagnosticMessageChain {
const componentName = getTextOfNode(openingLikeElement.tagName);
return chainDiagnosticMessages(/* details */ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName);
}
}

Expand Down Expand Up @@ -23468,8 +23473,9 @@ namespace ts {
}

if (isNodeOpeningLikeElement) {
const sig = getResolvedSignature(node as JsxOpeningLikeElement);
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(node as JsxOpeningLikeElement), getReturnTypeOfSignature(sig), node);
const jsxOpeningLikeNode = node as JsxOpeningLikeElement;
const sig = getResolvedSignature(jsxOpeningLikeNode);
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode);
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2947,6 +2947,22 @@
"category": "Error",
"code": 2785
},
"'{0}' cannot be used as a JSX component.": {
"category": "Error",
"code": 2786
},
"Its return type '{0}' is not a valid JSX element.": {
"category": "Error",
"code": 2787
},
"Its instance type '{0}' is not a valid JSX element.": {
"category": "Error",
"code": 2788
},
"Its element type '{0}' is not a valid JSX element.": {
"category": "Error",
"code": 2789
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
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'.
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.
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'.
tests/cases/conformance/jsx/inline/index.tsx(21,22): error TS2786: 'MySFC' cannot be used as a JSX component.
Its return type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' is not a valid JSX element.
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'.
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'.
tests/cases/conformance/jsx/inline/index.tsx(21,40): error TS2605: JSX element type 'MyClass' is not a constructor function for JSX elements.
Property '__domBrand' is missing in type 'MyClass' but required in type 'ElementClass'.
tests/cases/conformance/jsx/inline/index.tsx(21,41): error TS2786: 'MyClass' cannot be used as a JSX component.
Its instance type 'MyClass' is not a valid JSX element.
Property '__domBrand' is missing in type 'MyClass' but required in type 'ElementClass'.
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'.
tests/cases/conformance/jsx/inline/index.tsx(21,63): error TS2605: JSX element type 'MyClass' is not a constructor function for JSX elements.
tests/cases/conformance/jsx/inline/index.tsx(21,64): error TS2786: 'MyClass' cannot be used as a JSX component.
Its instance type 'MyClass' is not a valid JSX element.
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'.
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'.

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

// Should fail, no dom elements
const _brokenTree = <MySFC x={1} y={2}><MyClass x={3} y={4} /><MyClass x={5} y={6} /></MySFC>
~~~~~~~~~~~~~~~~~~~
!!! error TS2605: JSX element type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' is not a constructor function for JSX elements.
!!! 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'.
~~~~~
!!! error TS2786: 'MySFC' cannot be used as a JSX component.
!!! error TS2786: Its return type 'import("tests/cases/conformance/jsx/inline/renderer2").predom.JSX.Element' is not a valid JSX element.
!!! 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'.
!!! related TS2728 tests/cases/conformance/jsx/inline/renderer.d.ts:7:13: '__domBrand' is declared here.
~~~~~~~~~~~~~~~~~~~~~~~
!!! 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'.
~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2605: JSX element type 'MyClass' is not a constructor function for JSX elements.
!!! error TS2605: Property '__domBrand' is missing in type 'MyClass' but required in type 'ElementClass'.
~~~~~~~
!!! error TS2786: 'MyClass' cannot be used as a JSX component.
!!! error TS2786: Its instance type 'MyClass' is not a valid JSX element.
!!! error TS2786: Property '__domBrand' is missing in type 'MyClass' but required in type 'ElementClass'.
!!! related TS2728 tests/cases/conformance/jsx/inline/renderer.d.ts:7:13: '__domBrand' is declared here.
~~~~~~~~~~~~~~~~~~~~~~~
!!! 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'.
~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2605: JSX element type 'MyClass' is not a constructor function for JSX elements.
~~~~~~~
!!! error TS2786: 'MyClass' cannot be used as a JSX component.
!!! error TS2786: Its instance type 'MyClass' is not a valid JSX element.

// Should fail, nondom isn't allowed as children of dom
const _brokenTree2 = <DOMSFC x={1} y={2}>{tree}{tree}</DOMSFC>
Expand Down
103 changes: 103 additions & 0 deletions tests/baselines/reference/jsxComponentTypeErrors.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
tests/cases/compiler/jsxComponentTypeErrors.tsx(16,11): error TS2786: 'this' cannot be used as a JSX component.
Its return type '{ type: "foo" | undefined; }' is not a valid JSX element.
Types of property 'type' are incompatible.
Type '"foo" | undefined' is not assignable to type '"element"'.
Type 'undefined' is not assignable to type '"element"'.
tests/cases/compiler/jsxComponentTypeErrors.tsx(25,16): error TS2786: 'FunctionComponent' cannot be used as a JSX component.
Its return type '{ type: "abc" | undefined; }' is not a valid JSX element.
Types of property 'type' are incompatible.
Type '"abc" | undefined' is not assignable to type '"element"'.
Type 'undefined' is not assignable to type '"element"'.
tests/cases/compiler/jsxComponentTypeErrors.tsx(26,16): error TS2786: 'FunctionComponent' cannot be used as a JSX component.
Its return type '{ type: "abc" | undefined; }' is not a valid JSX element.
tests/cases/compiler/jsxComponentTypeErrors.tsx(27,16): error TS2786: 'ClassComponent' cannot be used as a JSX component.
Its instance type 'ClassComponent' is not a valid JSX element.
Types of property 'type' are incompatible.
Type 'string' is not assignable to type '"element-class"'.
tests/cases/compiler/jsxComponentTypeErrors.tsx(28,16): error TS2786: 'MixedComponent' cannot be used as a JSX component.
Its element type 'ClassComponent | { type: string | undefined; }' is not a valid JSX element.
Type 'ClassComponent' is not assignable to type 'Element | ElementClass | null'.
Type 'ClassComponent' is not assignable to type 'ElementClass'.
tests/cases/compiler/jsxComponentTypeErrors.tsx(37,16): error TS2786: 'obj.MemberFunctionComponent' cannot be used as a JSX component.
Its return type '{}' is not a valid JSX element.
Property 'type' is missing in type '{}' but required in type 'Element'.
tests/cases/compiler/jsxComponentTypeErrors.tsx(38,16): error TS2786: 'obj. MemberClassComponent' cannot be used as a JSX component.
Its instance type 'MemberClassComponent' is not a valid JSX element.
Property 'type' is missing in type 'MemberClassComponent' but required in type 'ElementClass'.


==== tests/cases/compiler/jsxComponentTypeErrors.tsx (7 errors) ====
namespace JSX {
export interface Element {
type: 'element';
}
export interface ElementClass {
type: 'element-class';
}
}

function FunctionComponent<T extends string>({type}: {type?: T}) {
return {
type
}
}
FunctionComponent.useThis = function() {
return <this type="foo" />;
~~~~
!!! error TS2786: 'this' cannot be used as a JSX component.
!!! error TS2786: Its return type '{ type: "foo" | undefined; }' is not a valid JSX element.
!!! error TS2786: Types of property 'type' are incompatible.
!!! error TS2786: Type '"foo" | undefined' is not assignable to type '"element"'.
!!! error TS2786: Type 'undefined' is not assignable to type '"element"'.
}

class ClassComponent {
type = 'string';
}

const MixedComponent = Math.random() ? FunctionComponent : ClassComponent;

const elem1 = <FunctionComponent type="abc" />;
~~~~~~~~~~~~~~~~~
!!! error TS2786: 'FunctionComponent' cannot be used as a JSX component.
!!! error TS2786: Its return type '{ type: "abc" | undefined; }' is not a valid JSX element.
!!! error TS2786: Types of property 'type' are incompatible.
!!! error TS2786: Type '"abc" | undefined' is not assignable to type '"element"'.
!!! error TS2786: Type 'undefined' is not assignable to type '"element"'.
const elem2 = <FunctionComponent<"abc"> />;
~~~~~~~~~~~~~~~~~
!!! error TS2786: 'FunctionComponent' cannot be used as a JSX component.
!!! error TS2786: Its return type '{ type: "abc" | undefined; }' is not a valid JSX element.
const elem3 = <ClassComponent />;
~~~~~~~~~~~~~~
!!! error TS2786: 'ClassComponent' cannot be used as a JSX component.
!!! error TS2786: Its instance type 'ClassComponent' is not a valid JSX element.
!!! error TS2786: Types of property 'type' are incompatible.
!!! error TS2786: Type 'string' is not assignable to type '"element-class"'.
const elem4 = <MixedComponent />;
~~~~~~~~~~~~~~
!!! error TS2786: 'MixedComponent' cannot be used as a JSX component.
!!! error TS2786: Its element type 'ClassComponent | { type: string | undefined; }' is not a valid JSX element.
!!! error TS2786: Type 'ClassComponent' is not assignable to type 'Element | ElementClass | null'.
!!! error TS2786: Type 'ClassComponent' is not assignable to type 'ElementClass'.

const obj = {
MemberFunctionComponent() {
return {};
},
MemberClassComponent: class {},
};

const elem5 = <obj.MemberFunctionComponent />;
~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2786: 'obj.MemberFunctionComponent' cannot be used as a JSX component.
!!! error TS2786: Its return type '{}' is not a valid JSX element.
!!! error TS2786: Property 'type' is missing in type '{}' but required in type 'Element'.
!!! related TS2728 tests/cases/compiler/jsxComponentTypeErrors.tsx:3:5: 'type' is declared here.
const elem6 = <obj. MemberClassComponent />;
~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2786: 'obj. MemberClassComponent' cannot be used as a JSX component.
!!! error TS2786: Its instance type 'MemberClassComponent' is not a valid JSX element.
!!! error TS2786: Property 'type' is missing in type 'MemberClassComponent' but required in type 'ElementClass'.
!!! related TS2728 tests/cases/compiler/jsxComponentTypeErrors.tsx:6:5: 'type' is declared here.

75 changes: 75 additions & 0 deletions tests/baselines/reference/jsxComponentTypeErrors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//// [jsxComponentTypeErrors.tsx]
namespace JSX {
export interface Element {
type: 'element';
}
export interface ElementClass {
type: 'element-class';
}
}

function FunctionComponent<T extends string>({type}: {type?: T}) {
return {
type
}
}
FunctionComponent.useThis = function() {
return <this type="foo" />;
}

class ClassComponent {
type = 'string';
}

const MixedComponent = Math.random() ? FunctionComponent : ClassComponent;

const elem1 = <FunctionComponent type="abc" />;
const elem2 = <FunctionComponent<"abc"> />;
const elem3 = <ClassComponent />;
const elem4 = <MixedComponent />;

const obj = {
MemberFunctionComponent() {
return {};
},
MemberClassComponent: class {},
};

const elem5 = <obj.MemberFunctionComponent />;
const elem6 = <obj. MemberClassComponent />;


//// [jsxComponentTypeErrors.jsx]
"use strict";
function FunctionComponent(_a) {
var type = _a.type;
return {
type: type
};
}
FunctionComponent.useThis = function () {
return <this type="foo"/>;
};
var ClassComponent = /** @class */ (function () {
function ClassComponent() {
this.type = 'string';
}
return ClassComponent;
}());
var MixedComponent = Math.random() ? FunctionComponent : ClassComponent;
var elem1 = <FunctionComponent type="abc"/>;
var elem2 = <FunctionComponent />;
var elem3 = <ClassComponent />;
var elem4 = <MixedComponent />;
var obj = {
MemberFunctionComponent: function () {
return {};
},
MemberClassComponent: /** @class */ (function () {
function MemberClassComponent() {
}
return MemberClassComponent;
}())
};
var elem5 = <obj.MemberFunctionComponent />;
var elem6 = <obj.MemberClassComponent />;
Loading