Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 18e3f48

Browse filesBrowse files
authored
Support LibraryManagedAttributes<TComponent, TAttributes> JSX namespace type (#24422)
* WIP * Allow type alias for managed type * Add a large test * Accept updatedbaselines * Fix typo in test, add one more example
1 parent 313a0b8 commit 18e3f48
Copy full SHA for 18e3f48

File tree

Expand file treeCollapse file tree

6 files changed

+1635
-4
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+1635
-4
lines changed

‎src/compiler/checker.ts

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+31-4Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15730,8 +15730,9 @@ namespace ts {
1573015730
return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromClassType(t, isJs, context, /*reportErrors*/ false) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None);
1573115731
}
1573215732

15733-
function getJsxPropsTypeFromCallSignature(sig: Signature, context: Node) {
15733+
function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) {
1573415734
let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, emptyObjectType);
15735+
propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType);
1573515736
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
1573615737
if (intrinsicAttribs !== errorType) {
1573715738
propsType = intersectTypes(intrinsicAttribs, propsType);
@@ -15744,9 +15745,26 @@ namespace ts {
1574415745
return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation);
1574515746
}
1574615747

15748+
function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) {
15749+
const managedSym = getJsxLibraryManagedAttributes(ns);
15750+
if (managedSym) {
15751+
const declaredManagedType = getDeclaredTypeOfSymbol(managedSym);
15752+
if (length((declaredManagedType as GenericType).typeParameters) >= 2) {
15753+
const args = fillMissingTypeArguments([checkExpressionCached(context.tagName), attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJavaScriptFile(context));
15754+
return createTypeReference((declaredManagedType as GenericType), args);
15755+
}
15756+
else if (length(declaredManagedType.aliasTypeArguments) >= 2) {
15757+
const args = fillMissingTypeArguments([checkExpressionCached(context.tagName), attributesType], declaredManagedType.aliasTypeArguments!, 2, isInJavaScriptFile(context));
15758+
return getTypeAliasInstantiation(declaredManagedType.aliasSymbol!, args);
15759+
}
15760+
}
15761+
return attributesType;
15762+
}
15763+
1574715764
function getJsxPropsTypeFromClassType(sig: Signature, isJs: boolean, context: JsxOpeningLikeElement, reportErrors: boolean) {
15748-
const forcedLookupLocation = getJsxElementPropertiesName(getJsxNamespaceAt(context));
15749-
const attributesType = forcedLookupLocation === undefined
15765+
const ns = getJsxNamespaceAt(context);
15766+
const forcedLookupLocation = getJsxElementPropertiesName(ns);
15767+
let attributesType = forcedLookupLocation === undefined
1575015768
// If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type
1575115769
? getTypeOfFirstParameterOfSignatureWithFallback(sig, emptyObjectType)
1575215770
: forcedLookupLocation === ""
@@ -15762,7 +15780,10 @@ namespace ts {
1576215780
}
1576315781
return emptyObjectType;
1576415782
}
15765-
else if (isTypeAny(attributesType)) {
15783+
15784+
attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType);
15785+
15786+
if (isTypeAny(attributesType)) {
1576615787
// Props is of type 'any' or unknown
1576715788
return attributesType;
1576815789
}
@@ -16593,6 +16614,11 @@ namespace ts {
1659316614
return undefined;
1659416615
}
1659516616

16617+
function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) {
16618+
// JSX.LibraryManagedAttributes [symbol]
16619+
return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type);
16620+
}
16621+
1659616622
/// e.g. "props" for React.d.ts,
1659716623
/// or 'undefined' if ElementAttributesProperty doesn't exist (which means all
1659816624
/// non-intrinsic elements' attributes type is 'any'),
@@ -28927,6 +28953,7 @@ namespace ts {
2892728953
export const Element = "Element" as __String;
2892828954
export const IntrinsicAttributes = "IntrinsicAttributes" as __String;
2892928955
export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String;
28956+
export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String;
2893028957
// tslint:enable variable-name
2893128958
}
2893228959
}
+199Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(55,12): error TS2322: Type '{ foo: number; }' is not assignable to type 'Defaultize<InferredPropTypes<{ foo: PropTypeChecker<number, false>; bar: PropTypeChecker<ReactNode, false>; baz: PropTypeChecker<string, true>; }>, { foo: number; }>'.
2+
Type '{ foo: number; }' is not assignable to type '{ bar: string | number | ReactComponent<{}, {}> | null | undefined; baz: string; }'.
3+
Property 'bar' is missing in type '{ foo: number; }'.
4+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(57,41): error TS2339: Property 'bat' does not exist on type 'Defaultize<InferredPropTypes<{ foo: PropTypeChecker<number, false>; bar: PropTypeChecker<ReactNode, false>; baz: PropTypeChecker<string, true>; }>, { foo: number; }>'.
5+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(59,42): error TS2326: Types of property 'baz' are incompatible.
6+
Type 'null' is not assignable to type 'string'.
7+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(69,26): error TS2326: Types of property 'foo' are incompatible.
8+
Type 'string' is not assignable to type 'number | null | undefined'.
9+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(71,35): error TS2326: Types of property 'bar' are incompatible.
10+
Type 'null' is not assignable to type 'ReactNode'.
11+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(80,38): error TS2339: Property 'bar' does not exist on type 'Defaultize<{}, { foo: number; }>'.
12+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(81,29): error TS2326: Types of property 'foo' are incompatible.
13+
Type 'string' is not assignable to type 'number | undefined'.
14+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(98,12): error TS2322: Type '{ foo: string; }' is not assignable to type 'Defaultize<FooProps & InferredPropTypes<{ foo: PropTypeChecker<string, false>; bar: PropTypeChecker<ReactNode, false>; baz: PropTypeChecker<number, true>; }>, { foo: string; }>'.
15+
Type '{ foo: string; }' is not assignable to type '{ bar: string | number | ReactComponent<{}, {}> | null | undefined; baz: number; }'.
16+
Property 'bar' is missing in type '{ foo: string; }'.
17+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(100,56): error TS2339: Property 'bat' does not exist on type 'Defaultize<FooProps & InferredPropTypes<{ foo: PropTypeChecker<string, false>; bar: PropTypeChecker<ReactNode, false>; baz: PropTypeChecker<number, true>; }>, { foo: string; }>'.
18+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(102,57): error TS2326: Types of property 'baz' are incompatible.
19+
Type 'null' is not assignable to type 'number'.
20+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(111,46): error TS2326: Types of property 'foo' are incompatible.
21+
Type 'number' is not assignable to type 'string'.
22+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(112,46): error TS2326: Types of property 'foo' are incompatible.
23+
Type 'null' is not assignable to type 'string'.
24+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(113,57): error TS2326: Types of property 'bar' are incompatible.
25+
Type 'null' is not assignable to type 'ReactNode'.
26+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(122,58): error TS2339: Property 'bar' does not exist on type 'Defaultize<FooProps, { foo: string; }>'.
27+
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(123,49): error TS2326: Types of property 'foo' are incompatible.
28+
Type 'number' is not assignable to type 'string | undefined'.
29+
30+
31+
==== tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx (15 errors) ====
32+
type Defaultize<TProps, TDefaults> =
33+
& {[K in Extract<keyof TProps, keyof TDefaults>]?: TProps[K]}
34+
& {[K in Exclude<keyof TProps, keyof TDefaults>]: TProps[K]}
35+
& Partial<TDefaults>;
36+
37+
type InferredPropTypes<P> = {[K in keyof P]: P[K] extends PropTypeChecker<infer T, infer U> ? PropTypeChecker<T, U>[typeof checkedType] : {}};
38+
39+
declare const checkedType: unique symbol;
40+
interface PropTypeChecker<U, TRequired = false> {
41+
(props: any, propName: string, componentName: string, location: any, propFullName: string): boolean;
42+
isRequired: PropTypeChecker<U, true>;
43+
[checkedType]: TRequired extends true ? U : U | null | undefined;
44+
}
45+
46+
declare namespace PropTypes {
47+
export const number: PropTypeChecker<number>;
48+
export const string: PropTypeChecker<string>;
49+
export const node: PropTypeChecker<ReactNode>;
50+
}
51+
52+
type ReactNode = string | number | ReactComponent<{}, {}>;
53+
54+
declare class ReactComponent<P={}, S={}> {
55+
constructor(props: P);
56+
props: P & Readonly<{children: ReactNode[]}>;
57+
setState(s: Partial<S>): S;
58+
render(): ReactNode;
59+
}
60+
61+
declare namespace JSX {
62+
interface Element extends ReactComponent {}
63+
interface IntrinsicElements {}
64+
type LibraryManagedAttributes<TComponent, TProps> =
65+
TComponent extends { defaultProps: infer D; propTypes: infer P; }
66+
? Defaultize<TProps & InferredPropTypes<P>, D>
67+
: TComponent extends { defaultProps: infer D }
68+
? Defaultize<TProps, D>
69+
: TComponent extends { propTypes: infer P }
70+
? TProps & InferredPropTypes<P>
71+
: TProps;
72+
}
73+
74+
class Component extends ReactComponent {
75+
static propTypes = {
76+
foo: PropTypes.number,
77+
bar: PropTypes.node,
78+
baz: PropTypes.string.isRequired,
79+
};
80+
static defaultProps = {
81+
foo: 42,
82+
}
83+
}
84+
85+
const a = <Component foo={12} bar="yes" baz="yeah" />;
86+
const b = <Component foo={12} />; // Error, missing required prop bar
87+
~~~~~~~~~
88+
!!! error TS2322: Type '{ foo: number; }' is not assignable to type 'Defaultize<InferredPropTypes<{ foo: PropTypeChecker<number, false>; bar: PropTypeChecker<ReactNode, false>; baz: PropTypeChecker<string, true>; }>, { foo: number; }>'.
89+
!!! error TS2322: Type '{ foo: number; }' is not assignable to type '{ bar: string | number | ReactComponent<{}, {}> | null | undefined; baz: string; }'.
90+
!!! error TS2322: Property 'bar' is missing in type '{ foo: number; }'.
91+
const c = <Component bar="yes" baz="yeah" />;
92+
const d = <Component bar="yes" baz="yo" bat="ohno" />; // Error, baz not a valid prop
93+
~~~~~~~~~~
94+
!!! error TS2339: Property 'bat' does not exist on type 'Defaultize<InferredPropTypes<{ foo: PropTypeChecker<number, false>; bar: PropTypeChecker<ReactNode, false>; baz: PropTypeChecker<string, true>; }>, { foo: number; }>'.
95+
const e = <Component foo={12} bar={null} baz="cool" />; // bar is nullable/undefinable since it's not marked `isRequired`
96+
const f = <Component foo={12} bar="yeah" baz={null} />; // Error, baz is _not_ nullable/undefinable since it's marked `isRequired`
97+
~~~~~~~~~~
98+
!!! error TS2326: Types of property 'baz' are incompatible.
99+
!!! error TS2326: Type 'null' is not assignable to type 'string'.
100+
101+
class JustPropTypes extends ReactComponent {
102+
static propTypes = {
103+
foo: PropTypes.number,
104+
bar: PropTypes.node.isRequired,
105+
};
106+
}
107+
108+
const g = <JustPropTypes foo={12} bar="ok" />;
109+
const h = <JustPropTypes foo="no" />; // error, wrong type
110+
~~~~~~~~
111+
!!! error TS2326: Types of property 'foo' are incompatible.
112+
!!! error TS2326: Type 'string' is not assignable to type 'number | null | undefined'.
113+
const i = <JustPropTypes foo={null} bar="ok" />;
114+
const j = <JustPropTypes foo={12} bar={null} />; // error, bar is required
115+
~~~~~~~~~~
116+
!!! error TS2326: Types of property 'bar' are incompatible.
117+
!!! error TS2326: Type 'null' is not assignable to type 'ReactNode'.
118+
119+
class JustDefaultProps extends ReactComponent {
120+
static defaultProps = {
121+
foo: 42,
122+
};
123+
}
124+
125+
const k = <JustDefaultProps foo={12} />;
126+
const l = <JustDefaultProps foo={12} bar="ok" />; // error, no prop named bar
127+
~~~~~~~~
128+
!!! error TS2339: Property 'bar' does not exist on type 'Defaultize<{}, { foo: number; }>'.
129+
const m = <JustDefaultProps foo="no" />; // error, wrong type
130+
~~~~~~~~
131+
!!! error TS2326: Types of property 'foo' are incompatible.
132+
!!! error TS2326: Type 'string' is not assignable to type 'number | undefined'.
133+
134+
interface FooProps {
135+
foo: string;
136+
}
137+
138+
class BothWithSpecifiedGeneric extends ReactComponent<FooProps> {
139+
static propTypes = {
140+
foo: PropTypes.string,
141+
bar: PropTypes.node,
142+
baz: PropTypes.number.isRequired,
143+
};
144+
static defaultProps = {
145+
foo: "yo",
146+
};
147+
}
148+
const n = <BothWithSpecifiedGeneric foo="fine" bar="yes" baz={12} />;
149+
const o = <BothWithSpecifiedGeneric foo="no" />; // Error, missing required prop bar
150+
~~~~~~~~~~~~~~~~~~~~~~~~
151+
!!! error TS2322: Type '{ foo: string; }' is not assignable to type 'Defaultize<FooProps & InferredPropTypes<{ foo: PropTypeChecker<string, false>; bar: PropTypeChecker<ReactNode, false>; baz: PropTypeChecker<number, true>; }>, { foo: string; }>'.
152+
!!! error TS2322: Type '{ foo: string; }' is not assignable to type '{ bar: string | number | ReactComponent<{}, {}> | null | undefined; baz: number; }'.
153+
!!! error TS2322: Property 'bar' is missing in type '{ foo: string; }'.
154+
const p = <BothWithSpecifiedGeneric bar="yes" baz={12} />;
155+
const q = <BothWithSpecifiedGeneric bar="yes" baz={12} bat="ohno" />; // Error, baz not a valid prop
156+
~~~~~~~~~~
157+
!!! error TS2339: Property 'bat' does not exist on type 'Defaultize<FooProps & InferredPropTypes<{ foo: PropTypeChecker<string, false>; bar: PropTypeChecker<ReactNode, false>; baz: PropTypeChecker<number, true>; }>, { foo: string; }>'.
158+
const r = <BothWithSpecifiedGeneric foo="no" bar={null} baz={0} />; // bar is nullable/undefinable since it's not marked `isRequired`
159+
const s = <BothWithSpecifiedGeneric foo="eh" bar="yeah" baz={null} />; // Error, baz is _not_ nullable/undefinable since it's marked `isRequired`
160+
~~~~~~~~~~
161+
!!! error TS2326: Types of property 'baz' are incompatible.
162+
!!! error TS2326: Type 'null' is not assignable to type 'number'.
163+
164+
class JustPropTypesWithSpecifiedGeneric extends ReactComponent<FooProps> {
165+
static propTypes = {
166+
foo: PropTypes.string,
167+
bar: PropTypes.node.isRequired,
168+
};
169+
}
170+
const t = <JustPropTypesWithSpecifiedGeneric foo="nice" bar="ok" />;
171+
const u = <JustPropTypesWithSpecifiedGeneric foo={12} />; // error, wrong type
172+
~~~~~~~~
173+
!!! error TS2326: Types of property 'foo' are incompatible.
174+
!!! error TS2326: Type 'number' is not assignable to type 'string'.
175+
const v = <JustPropTypesWithSpecifiedGeneric foo={null} bar="ok" />; // generic overrides propTypes required-ness, null isn't valid
176+
~~~~~~~~~~
177+
!!! error TS2326: Types of property 'foo' are incompatible.
178+
!!! error TS2326: Type 'null' is not assignable to type 'string'.
179+
const w = <JustPropTypesWithSpecifiedGeneric foo="cool" bar={null} />; // error, bar is required
180+
~~~~~~~~~~
181+
!!! error TS2326: Types of property 'bar' are incompatible.
182+
!!! error TS2326: Type 'null' is not assignable to type 'ReactNode'.
183+
184+
class JustDefaultPropsWithSpecifiedGeneric extends ReactComponent<FooProps> {
185+
static defaultProps = {
186+
foo: "no",
187+
};
188+
}
189+
190+
const x = <JustDefaultPropsWithSpecifiedGeneric foo="eh" />;
191+
const y = <JustDefaultPropsWithSpecifiedGeneric foo="no" bar="ok" />; // error, no prop named bar
192+
~~~~~~~~
193+
!!! error TS2339: Property 'bar' does not exist on type 'Defaultize<FooProps, { foo: string; }>'.
194+
const z = <JustDefaultPropsWithSpecifiedGeneric foo={12} />; // error, wrong type
195+
~~~~~~~~
196+
!!! error TS2326: Types of property 'foo' are incompatible.
197+
!!! error TS2326: Type 'number' is not assignable to type 'string | undefined'.
198+
const aa = <JustDefaultPropsWithSpecifiedGeneric />;
199+

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.