Skip to content
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

Support LibraryManagedAttributes<TComponent, TAttributes> JSX namespace type #24422

Merged
merged 6 commits into from
Jun 30, 2018
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
35 changes: 31 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15870,8 +15870,9 @@ namespace ts {
return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromClassType(t, isJs, context, /*reportErrors*/ false) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None);
}

function getJsxPropsTypeFromCallSignature(sig: Signature, context: Node) {
function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) {
let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, emptyObjectType);
propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType);
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
if (intrinsicAttribs !== errorType) {
propsType = intersectTypes(intrinsicAttribs, propsType);
Expand All @@ -15884,9 +15885,26 @@ namespace ts {
return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation);
}

function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) {
const managedSym = getJsxLibraryManagedAttributes(ns);
if (managedSym) {
const declaredManagedType = getDeclaredTypeOfSymbol(managedSym);
if (length((declaredManagedType as GenericType).typeParameters) >= 2) {
const args = fillMissingTypeArguments([checkExpressionCached(context.tagName), attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJavaScriptFile(context));
return createTypeReference((declaredManagedType as GenericType), args);
}
else if (length(declaredManagedType.aliasTypeArguments) >= 2) {
const args = fillMissingTypeArguments([checkExpressionCached(context.tagName), attributesType], declaredManagedType.aliasTypeArguments!, 2, isInJavaScriptFile(context));
return getTypeAliasInstantiation(declaredManagedType.aliasSymbol!, args);
}
}
return attributesType;
}

function getJsxPropsTypeFromClassType(sig: Signature, isJs: boolean, context: JsxOpeningLikeElement, reportErrors: boolean) {
const forcedLookupLocation = getJsxElementPropertiesName(getJsxNamespaceAt(context));
const attributesType = forcedLookupLocation === undefined
const ns = getJsxNamespaceAt(context);
const forcedLookupLocation = getJsxElementPropertiesName(ns);
let attributesType = forcedLookupLocation === undefined
// If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type
? getTypeOfFirstParameterOfSignatureWithFallback(sig, emptyObjectType)
: forcedLookupLocation === ""
Expand All @@ -15902,7 +15920,10 @@ namespace ts {
}
return emptyObjectType;
}
else if (isTypeAny(attributesType)) {

attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType);

if (isTypeAny(attributesType)) {
// Props is of type 'any' or unknown
return attributesType;
}
Expand Down Expand Up @@ -16733,6 +16754,11 @@ namespace ts {
return undefined;
}

function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) {
// JSX.LibraryManagedAttributes [symbol]
return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type);
}

/// e.g. "props" for React.d.ts,
/// or 'undefined' if ElementAttributesProperty doesn't exist (which means all
/// non-intrinsic elements' attributes type is 'any'),
Expand Down Expand Up @@ -29067,6 +29093,7 @@ namespace ts {
export const Element = "Element" as __String;
export const IntrinsicAttributes = "IntrinsicAttributes" as __String;
export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String;
export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String;
// tslint:enable variable-name
}
}
199 changes: 199 additions & 0 deletions tests/baselines/reference/tsxLibraryManagedAttributes.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
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; }>'.
Type '{ foo: number; }' is not assignable to type '{ bar: string | number | ReactComponent<{}, {}> | null | undefined; baz: string; }'.
Property 'bar' is missing in type '{ foo: number; }'.
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; }>'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(59,42): error TS2326: Types of property 'baz' are incompatible.
Type 'null' is not assignable to type 'string'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(69,26): error TS2326: Types of property 'foo' are incompatible.
Type 'string' is not assignable to type 'number | null | undefined'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(71,35): error TS2326: Types of property 'bar' are incompatible.
Type 'null' is not assignable to type 'ReactNode'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(80,38): error TS2339: Property 'bar' does not exist on type 'Defaultize<{}, { foo: number; }>'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(81,29): error TS2326: Types of property 'foo' are incompatible.
Type 'string' is not assignable to type 'number | undefined'.
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; }>'.
Type '{ foo: string; }' is not assignable to type '{ bar: string | number | ReactComponent<{}, {}> | null | undefined; baz: number; }'.
Property 'bar' is missing in type '{ foo: string; }'.
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; }>'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(102,57): error TS2326: Types of property 'baz' are incompatible.
Type 'null' is not assignable to type 'number'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(111,46): error TS2326: Types of property 'foo' are incompatible.
Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(112,46): error TS2326: Types of property 'foo' are incompatible.
Type 'null' is not assignable to type 'string'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(113,57): error TS2326: Types of property 'bar' are incompatible.
Type 'null' is not assignable to type 'ReactNode'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(122,58): error TS2339: Property 'bar' does not exist on type 'Defaultize<FooProps, { foo: string; }>'.
tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx(123,49): error TS2326: Types of property 'foo' are incompatible.
Type 'number' is not assignable to type 'string | undefined'.


==== tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx (15 errors) ====
type Defaultize<TProps, TDefaults> =
& {[K in Extract<keyof TProps, keyof TDefaults>]?: TProps[K]}
& {[K in Exclude<keyof TProps, keyof TDefaults>]: TProps[K]}
& Partial<TDefaults>;

type InferredPropTypes<P> = {[K in keyof P]: P[K] extends PropTypeChecker<infer T, infer U> ? PropTypeChecker<T, U>[typeof checkedType] : {}};

declare const checkedType: unique symbol;
interface PropTypeChecker<U, TRequired = false> {
(props: any, propName: string, componentName: string, location: any, propFullName: string): boolean;
isRequired: PropTypeChecker<U, true>;
[checkedType]: TRequired extends true ? U : U | null | undefined;
}

declare namespace PropTypes {
export const number: PropTypeChecker<number>;
export const string: PropTypeChecker<string>;
export const node: PropTypeChecker<ReactNode>;
}

type ReactNode = string | number | ReactComponent<{}, {}>;

declare class ReactComponent<P={}, S={}> {
constructor(props: P);
props: P & Readonly<{children: ReactNode[]}>;
setState(s: Partial<S>): S;
render(): ReactNode;
}

declare namespace JSX {
interface Element extends ReactComponent {}
interface IntrinsicElements {}
type LibraryManagedAttributes<TComponent, TProps> =
TComponent extends { defaultProps: infer D; propTypes: infer P; }
? Defaultize<TProps & InferredPropTypes<P>, D>
: TComponent extends { defaultProps: infer D }
? Defaultize<TProps, D>
: TComponent extends { propTypes: infer P }
? TProps & InferredPropTypes<P>
: TProps;
}

class Component extends ReactComponent {
static propTypes = {
foo: PropTypes.number,
bar: PropTypes.node,
baz: PropTypes.string.isRequired,
};
static defaultProps = {
foo: 42,
}
}

const a = <Component foo={12} bar="yes" baz="yeah" />;
const b = <Component foo={12} />; // Error, missing required prop bar
~~~~~~~~~
!!! 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; }>'.
!!! error TS2322: Type '{ foo: number; }' is not assignable to type '{ bar: string | number | ReactComponent<{}, {}> | null | undefined; baz: string; }'.
!!! error TS2322: Property 'bar' is missing in type '{ foo: number; }'.
const c = <Component bar="yes" baz="yeah" />;
const d = <Component bar="yes" baz="yo" bat="ohno" />; // Error, baz not a valid prop
~~~~~~~~~~
!!! 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; }>'.
const e = <Component foo={12} bar={null} baz="cool" />; // bar is nullable/undefinable since it's not marked `isRequired`
const f = <Component foo={12} bar="yeah" baz={null} />; // Error, baz is _not_ nullable/undefinable since it's marked `isRequired`
~~~~~~~~~~
!!! error TS2326: Types of property 'baz' are incompatible.
!!! error TS2326: Type 'null' is not assignable to type 'string'.

class JustPropTypes extends ReactComponent {
static propTypes = {
foo: PropTypes.number,
bar: PropTypes.node.isRequired,
};
}

const g = <JustPropTypes foo={12} bar="ok" />;
const h = <JustPropTypes foo="no" />; // error, wrong type
~~~~~~~~
!!! error TS2326: Types of property 'foo' are incompatible.
!!! error TS2326: Type 'string' is not assignable to type 'number | null | undefined'.
const i = <JustPropTypes foo={null} bar="ok" />;
const j = <JustPropTypes foo={12} bar={null} />; // error, bar is required
~~~~~~~~~~
!!! error TS2326: Types of property 'bar' are incompatible.
!!! error TS2326: Type 'null' is not assignable to type 'ReactNode'.

class JustDefaultProps extends ReactComponent {
static defaultProps = {
foo: 42,
};
}

const k = <JustDefaultProps foo={12} />;
const l = <JustDefaultProps foo={12} bar="ok" />; // error, no prop named bar
~~~~~~~~
!!! error TS2339: Property 'bar' does not exist on type 'Defaultize<{}, { foo: number; }>'.
const m = <JustDefaultProps foo="no" />; // error, wrong type
~~~~~~~~
!!! error TS2326: Types of property 'foo' are incompatible.
!!! error TS2326: Type 'string' is not assignable to type 'number | undefined'.

interface FooProps {
foo: string;
}

class BothWithSpecifiedGeneric extends ReactComponent<FooProps> {
static propTypes = {
foo: PropTypes.string,
bar: PropTypes.node,
baz: PropTypes.number.isRequired,
};
static defaultProps = {
foo: "yo",
};
}
const n = <BothWithSpecifiedGeneric foo="fine" bar="yes" baz={12} />;
const o = <BothWithSpecifiedGeneric foo="no" />; // Error, missing required prop bar
~~~~~~~~~~~~~~~~~~~~~~~~
!!! 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; }>'.
!!! error TS2322: Type '{ foo: string; }' is not assignable to type '{ bar: string | number | ReactComponent<{}, {}> | null | undefined; baz: number; }'.
!!! error TS2322: Property 'bar' is missing in type '{ foo: string; }'.
const p = <BothWithSpecifiedGeneric bar="yes" baz={12} />;
const q = <BothWithSpecifiedGeneric bar="yes" baz={12} bat="ohno" />; // Error, baz not a valid prop
~~~~~~~~~~
!!! 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; }>'.
const r = <BothWithSpecifiedGeneric foo="no" bar={null} baz={0} />; // bar is nullable/undefinable since it's not marked `isRequired`
const s = <BothWithSpecifiedGeneric foo="eh" bar="yeah" baz={null} />; // Error, baz is _not_ nullable/undefinable since it's marked `isRequired`
~~~~~~~~~~
!!! error TS2326: Types of property 'baz' are incompatible.
!!! error TS2326: Type 'null' is not assignable to type 'number'.

class JustPropTypesWithSpecifiedGeneric extends ReactComponent<FooProps> {
static propTypes = {
foo: PropTypes.string,
bar: PropTypes.node.isRequired,
};
}
const t = <JustPropTypesWithSpecifiedGeneric foo="nice" bar="ok" />;
const u = <JustPropTypesWithSpecifiedGeneric foo={12} />; // error, wrong type
~~~~~~~~
!!! error TS2326: Types of property 'foo' are incompatible.
!!! error TS2326: Type 'number' is not assignable to type 'string'.
const v = <JustPropTypesWithSpecifiedGeneric foo={null} bar="ok" />; // generic overrides propTypes required-ness, null isn't valid
~~~~~~~~~~
!!! error TS2326: Types of property 'foo' are incompatible.
!!! error TS2326: Type 'null' is not assignable to type 'string'.
const w = <JustPropTypesWithSpecifiedGeneric foo="cool" bar={null} />; // error, bar is required
~~~~~~~~~~
!!! error TS2326: Types of property 'bar' are incompatible.
!!! error TS2326: Type 'null' is not assignable to type 'ReactNode'.

class JustDefaultPropsWithSpecifiedGeneric extends ReactComponent<FooProps> {
static defaultProps = {
foo: "no",
};
}

const x = <JustDefaultPropsWithSpecifiedGeneric foo="eh" />;
const y = <JustDefaultPropsWithSpecifiedGeneric foo="no" bar="ok" />; // error, no prop named bar
~~~~~~~~
!!! error TS2339: Property 'bar' does not exist on type 'Defaultize<FooProps, { foo: string; }>'.
const z = <JustDefaultPropsWithSpecifiedGeneric foo={12} />; // error, wrong type
~~~~~~~~
!!! error TS2326: Types of property 'foo' are incompatible.
!!! error TS2326: Type 'number' is not assignable to type 'string | undefined'.
const aa = <JustDefaultPropsWithSpecifiedGeneric />;

Loading