Skip to content

Commit b92483f

Browse files
RFC: Consult new JSX.ElementType for valid JSX element types (#51328)
Co-authored-by: Daniel Rosenwasser <[email protected]>
1 parent b798e6b commit b92483f

12 files changed

+1289
-2
lines changed

src/compiler/checker.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -30351,7 +30351,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3035130351
/**
3035230352
* Returns true iff React would emit this tag name as a string rather than an identifier or qualified name
3035330353
*/
30354-
function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean {
30354+
function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): tagName is Identifier {
3035530355
return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText);
3035630356
}
3035730357

@@ -30816,6 +30816,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3081630816
}
3081730817
}
3081830818

30819+
function getJsxElementTypeTypeAt(location: Node): Type | undefined {
30820+
const type = getJsxType(JsxNames.ElementType, location);
30821+
if (isErrorType(type)) return undefined;
30822+
return type;
30823+
}
30824+
3081930825
/**
3082030826
* Returns all the properties of the Jsx.IntrinsicElements interface
3082130827
*/
@@ -30884,7 +30890,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3088430890
const jsxOpeningLikeNode = node ;
3088530891
const sig = getResolvedSignature(jsxOpeningLikeNode);
3088630892
checkDeprecatedSignature(sig, node);
30887-
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode);
30893+
30894+
const elementTypeConstraint = getJsxElementTypeTypeAt(jsxOpeningLikeNode);
30895+
if (elementTypeConstraint !== undefined) {
30896+
const tagName = jsxOpeningLikeNode.tagName;
30897+
const tagType = isJsxIntrinsicIdentifier(tagName)
30898+
? getStringLiteralType(unescapeLeadingUnderscores(tagName.escapedText))
30899+
: checkExpression(tagName);
30900+
checkTypeRelatedTo(tagType, elementTypeConstraint, assignableRelation, tagName, Diagnostics.Its_type_0_is_not_a_valid_JSX_element_type, () => {
30901+
const componentName = getTextOfNode(tagName);
30902+
return chainDiagnosticMessages(/*details*/ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName);
30903+
});
30904+
}
30905+
else {
30906+
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode);
30907+
}
3088830908
}
3088930909
}
3089030910

@@ -48823,6 +48843,7 @@ namespace JsxNames {
4882348843
export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support
4882448844
export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String;
4882548845
export const Element = "Element" as __String;
48846+
export const ElementType = "ElementType" as __String;
4882648847
export const IntrinsicAttributes = "IntrinsicAttributes" as __String;
4882748848
export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String;
4882848849
export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String;

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -7760,5 +7760,9 @@
77607760
"Non-abstract class '{0}' does not implement all abstract members of '{1}'": {
77617761
"category": "Error",
77627762
"code": 18052
7763+
},
7764+
"Its type '{0}' is not a valid JSX element type.": {
7765+
"category": "Error",
7766+
"code": 18053
77637767
}
77647768
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
tests/cases/compiler/jsxElementType.tsx(34,2): error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
2+
tests/cases/compiler/jsxElementType.tsx(36,16): error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
3+
Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
4+
tests/cases/compiler/jsxElementType.tsx(40,2): error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
5+
tests/cases/compiler/jsxElementType.tsx(42,15): error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
6+
Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
7+
tests/cases/compiler/jsxElementType.tsx(46,2): error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
8+
tests/cases/compiler/jsxElementType.tsx(48,15): error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
9+
Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
10+
tests/cases/compiler/jsxElementType.tsx(52,2): error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
11+
tests/cases/compiler/jsxElementType.tsx(54,14): error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
12+
Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
13+
tests/cases/compiler/jsxElementType.tsx(59,2): error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
14+
tests/cases/compiler/jsxElementType.tsx(61,16): error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
15+
Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
16+
tests/cases/compiler/jsxElementType.tsx(70,2): error TS2769: No overload matches this call.
17+
Overload 1 of 2, '(props: Readonly<{ title: string; }>): RenderStringClass', gave the following error.
18+
Property 'title' is missing in type '{}' but required in type 'Readonly<{ title: string; }>'.
19+
Overload 2 of 2, '(props: { title: string; }, context?: any): RenderStringClass', gave the following error.
20+
Property 'title' is missing in type '{}' but required in type 'Readonly<{ title: string; }>'.
21+
tests/cases/compiler/jsxElementType.tsx(72,20): error TS2769: No overload matches this call.
22+
Overload 1 of 2, '(props: Readonly<{ title: string; }>): RenderStringClass', gave the following error.
23+
Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<RenderStringClass> & Readonly<{ children?: ReactNode; }> & Readonly<{ title: string; }>'.
24+
Property 'excessProp' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<RenderStringClass> & Readonly<{ children?: ReactNode; }> & Readonly<{ title: string; }>'.
25+
Overload 2 of 2, '(props: { title: string; }, context?: any): RenderStringClass', gave the following error.
26+
Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<RenderStringClass> & Readonly<{ children?: ReactNode; }> & Readonly<{ title: string; }>'.
27+
Property 'excessProp' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<RenderStringClass> & Readonly<{ children?: ReactNode; }> & Readonly<{ title: string; }>'.
28+
tests/cases/compiler/jsxElementType.tsx(78,1): error TS2339: Property 'boop' does not exist on type 'JSX.IntrinsicElements'.
29+
tests/cases/compiler/jsxElementType.tsx(79,1): error TS2339: Property 'my-undeclared-custom-element' does not exist on type 'JSX.IntrinsicElements'.
30+
tests/cases/compiler/jsxElementType.tsx(91,2): error TS2786: 'ReactNativeFlatList' cannot be used as a JSX component.
31+
Its type '(props: {}, ref: ForwardedRef<typeof ReactNativeFlatList>) => null' is not a valid JSX element type.
32+
Type '(props: {}, ref: ForwardedRef<typeof ReactNativeFlatList>) => null' is not assignable to type '(props: any) => React18ReactNode'.
33+
Target signature provides too few arguments. Expected 2 or more, but got 1.
34+
tests/cases/compiler/jsxElementType.tsx(95,11): error TS2322: Type '{}' is not assignable to type 'LibraryManagedAttributes<T, {}>'.
35+
tests/cases/compiler/jsxElementType.tsx(98,2): error TS2304: Cannot find name 'Unresolved'.
36+
tests/cases/compiler/jsxElementType.tsx(99,2): error TS2304: Cannot find name 'Unresolved'.
37+
38+
39+
==== tests/cases/compiler/jsxElementType.tsx (18 errors) ====
40+
/// <reference path="/.lib/react16.d.ts" />
41+
import * as React from "react";
42+
43+
type React18ReactFragment = ReadonlyArray<React18ReactNode>;
44+
type React18ReactNode =
45+
| React.ReactElement<any>
46+
| string
47+
| number
48+
| React18ReactFragment
49+
| React.ReactPortal
50+
| boolean
51+
| null
52+
| undefined
53+
| Promise<React18ReactNode>;
54+
55+
// // React.JSXElementConstructor but it now can return React nodes from function components.
56+
type NewReactJSXElementConstructor<P> =
57+
| ((props: P) => React18ReactNode)
58+
| (new (props: P) => React.Component<P, any>);
59+
60+
declare global {
61+
namespace JSX {
62+
type ElementType = string | NewReactJSXElementConstructor<any>;
63+
interface IntrinsicElements {
64+
['my-custom-element']: React.DOMAttributes<unknown>;
65+
}
66+
}
67+
}
68+
69+
let Component: NewReactJSXElementConstructor<{ title: string }>;
70+
71+
const RenderElement = ({ title }: { title: string }) => <div>{title}</div>;
72+
Component = RenderElement;
73+
<RenderElement />;
74+
~~~~~~~~~~~~~
75+
!!! error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
76+
!!! related TS2728 tests/cases/compiler/jsxElementType.tsx:32:37: 'title' is declared here.
77+
<RenderElement title="react" />;
78+
<RenderElement excessProp />;
79+
~~~~~~~~~~
80+
!!! error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
81+
!!! error TS2322: Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
82+
83+
const RenderString = ({ title }: { title: string }) => title;
84+
Component = RenderString;
85+
<RenderString />;
86+
~~~~~~~~~~~~
87+
!!! error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
88+
!!! related TS2728 tests/cases/compiler/jsxElementType.tsx:38:36: 'title' is declared here.
89+
<RenderString title="react" />;
90+
<RenderString excessProp />;
91+
~~~~~~~~~~
92+
!!! error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
93+
!!! error TS2322: Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
94+
95+
const RenderNumber = ({ title }: { title: string }) => title.length;
96+
Component = RenderNumber;
97+
<RenderNumber />;
98+
~~~~~~~~~~~~
99+
!!! error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
100+
!!! related TS2728 tests/cases/compiler/jsxElementType.tsx:44:36: 'title' is declared here.
101+
<RenderNumber title="react" />;
102+
<RenderNumber excessProp />;
103+
~~~~~~~~~~
104+
!!! error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
105+
!!! error TS2322: Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
106+
107+
const RenderArray = ({ title }: { title: string }) => [title];
108+
Component = RenderArray;
109+
<RenderArray />;
110+
~~~~~~~~~~~
111+
!!! error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
112+
!!! related TS2728 tests/cases/compiler/jsxElementType.tsx:50:35: 'title' is declared here.
113+
<RenderArray title="react" />;
114+
<RenderArray excessProp />;
115+
~~~~~~~~~~
116+
!!! error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
117+
!!! error TS2322: Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
118+
119+
// React Server Component
120+
const RenderPromise = async ({ title }: { title: string }) => "react";
121+
Component = RenderPromise;
122+
<RenderPromise />;
123+
~~~~~~~~~~~~~
124+
!!! error TS2741: Property 'title' is missing in type '{}' but required in type '{ title: string; }'.
125+
!!! related TS2728 tests/cases/compiler/jsxElementType.tsx:57:43: 'title' is declared here.
126+
<RenderPromise title="react" />;
127+
<RenderPromise excessProp />;
128+
~~~~~~~~~~
129+
!!! error TS2322: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & { title: string; }'.
130+
!!! error TS2322: Property 'excessProp' does not exist on type 'IntrinsicAttributes & { title: string; }'.
131+
132+
// Class components still work
133+
class RenderStringClass extends React.Component<{ title: string }> {
134+
render() {
135+
return this.props.title;
136+
}
137+
}
138+
Component = RenderStringClass;
139+
<RenderStringClass />;
140+
~~~~~~~~~~~~~~~~~
141+
!!! error TS2769: No overload matches this call.
142+
!!! error TS2769: Overload 1 of 2, '(props: Readonly<{ title: string; }>): RenderStringClass', gave the following error.
143+
!!! error TS2769: Property 'title' is missing in type '{}' but required in type 'Readonly<{ title: string; }>'.
144+
!!! error TS2769: Overload 2 of 2, '(props: { title: string; }, context?: any): RenderStringClass', gave the following error.
145+
!!! error TS2769: Property 'title' is missing in type '{}' but required in type 'Readonly<{ title: string; }>'.
146+
!!! related TS2728 tests/cases/compiler/jsxElementType.tsx:64:51: 'title' is declared here.
147+
!!! related TS2728 tests/cases/compiler/jsxElementType.tsx:64:51: 'title' is declared here.
148+
<RenderStringClass title="react" />;
149+
<RenderStringClass excessProp />;
150+
~~~~~~~~~~
151+
!!! error TS2769: No overload matches this call.
152+
!!! error TS2769: Overload 1 of 2, '(props: Readonly<{ title: string; }>): RenderStringClass', gave the following error.
153+
!!! error TS2769: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<RenderStringClass> & Readonly<{ children?: ReactNode; }> & Readonly<{ title: string; }>'.
154+
!!! error TS2769: Property 'excessProp' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<RenderStringClass> & Readonly<{ children?: ReactNode; }> & Readonly<{ title: string; }>'.
155+
!!! error TS2769: Overload 2 of 2, '(props: { title: string; }, context?: any): RenderStringClass', gave the following error.
156+
!!! error TS2769: Type '{ excessProp: true; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<RenderStringClass> & Readonly<{ children?: ReactNode; }> & Readonly<{ title: string; }>'.
157+
!!! error TS2769: Property 'excessProp' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<RenderStringClass> & Readonly<{ children?: ReactNode; }> & Readonly<{ title: string; }>'.
158+
159+
// Host element types still work
160+
<div />;
161+
<my-custom-element />;
162+
// Undeclared host element types are still rejected
163+
<boop />;
164+
~~~~~~~~
165+
!!! error TS2339: Property 'boop' does not exist on type 'JSX.IntrinsicElements'.
166+
<my-undeclared-custom-element />;
167+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
168+
!!! error TS2339: Property 'my-undeclared-custom-element' does not exist on type 'JSX.IntrinsicElements'.
169+
170+
// Highlighting various ecosystem compat issues
171+
// react-native-gesture-handler
172+
// https://github.com/software-mansion/react-native-gesture-handler/blob/79017e5e7cc2e82e6467851f870920ff836ee04f/src/components/GestureComponents.tsx#L139-L146
173+
interface ReactNativeFlatListProps<Item> {}
174+
function ReactNativeFlatList(
175+
props: {},
176+
ref: React.ForwardedRef<typeof ReactNativeFlatList>
177+
) {
178+
return null;
179+
}
180+
<ReactNativeFlatList />;
181+
~~~~~~~~~~~~~~~~~~~
182+
!!! error TS2786: 'ReactNativeFlatList' cannot be used as a JSX component.
183+
!!! error TS2786: Its type '(props: {}, ref: ForwardedRef<typeof ReactNativeFlatList>) => null' is not a valid JSX element type.
184+
!!! error TS2786: Type '(props: {}, ref: ForwardedRef<typeof ReactNativeFlatList>) => null' is not assignable to type '(props: any) => React18ReactNode'.
185+
!!! error TS2786: Target signature provides too few arguments. Expected 2 or more, but got 1.
186+
187+
// testing higher-order component compat
188+
function f1<T extends (props: {}) => React.ReactElement<any>>(Component: T) {
189+
return <Component />;
190+
~~~~~~~~~
191+
!!! error TS2322: Type '{}' is not assignable to type 'LibraryManagedAttributes<T, {}>'.
192+
}
193+
194+
<Unresolved />;
195+
~~~~~~~~~~
196+
!!! error TS2304: Cannot find name 'Unresolved'.
197+
<Unresolved foo="abc" />;
198+
~~~~~~~~~~
199+
!!! error TS2304: Cannot find name 'Unresolved'.
200+

0 commit comments

Comments
 (0)