Skip to content
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: 10 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27037,8 +27037,8 @@ namespace ts {
return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined;
}

function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild) {
const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName);
function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild, contextFlags?: ContextFlags) {
const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName, contextFlags);
// JSX expression is in children of JSX Element, we will look for an "children" attribute (we get the name from JSX.ElementAttributesProperty)
const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) {
Expand All @@ -27057,28 +27057,28 @@ namespace ts {
}, /*noReductions*/ true));
}

function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined {
function getContextualTypeForJsxExpression(node: JsxExpression, contextFlags?: ContextFlags): Type | undefined {
const exprParent = node.parent;
return isJsxAttributeLike(exprParent)
? getContextualType(node)
? getContextualType(node, contextFlags)
: isJsxElement(exprParent)
? getContextualTypeForChildJsxExpression(exprParent, node)
? getContextualTypeForChildJsxExpression(exprParent, node, contextFlags)
: undefined;
}

function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined {
function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute, contextFlags?: ContextFlags): Type | undefined {
// When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type
// which is a type of the parameter of the signature we are trying out.
// If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName
if (isJsxAttribute(attribute)) {
const attributesType = getApparentTypeOfContextualType(attribute.parent);
const attributesType = getApparentTypeOfContextualType(attribute.parent, contextFlags);
if (!attributesType || isTypeAny(attributesType)) {
return undefined;
}
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
}
else {
return getContextualType(attribute.parent);
return getContextualType(attribute.parent, contextFlags);
}
}

Expand Down Expand Up @@ -27272,10 +27272,10 @@ namespace ts {
case SyntaxKind.ExportAssignment:
return tryGetTypeFromEffectiveTypeNode(parent as ExportAssignment);
case SyntaxKind.JsxExpression:
return getContextualTypeForJsxExpression(parent as JsxExpression);
return getContextualTypeForJsxExpression(parent as JsxExpression, contextFlags);
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxSpreadAttribute:
return getContextualTypeForJsxAttribute(parent as JsxAttribute | JsxSpreadAttribute);
return getContextualTypeForJsxAttribute(parent as JsxAttribute | JsxSpreadAttribute, contextFlags);
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxSelfClosingElement:
return getContextualJsxElementAttributesType(parent as JsxOpeningLikeElement, contextFlags);
Expand Down
32 changes: 32 additions & 0 deletions tests/baselines/reference/contextuallyTypedJsxAttribute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//// [index.tsx]
interface Elements {
foo: { callback?: (value: number) => void };
bar: { callback?: (value: string) => void };
}

type Props<C extends keyof Elements> = { as?: C } & Elements[C];
declare function Test<C extends keyof Elements>(props: Props<C>): null;

<Test
as="bar"
callback={(value) => {}}
/>;

Test({
as: "bar",
callback: (value) => {},
});

<Test<'bar'>
as="bar"
callback={(value) => {}}
/>;


//// [index.jsx]
<Test as="bar" callback={function (value) { }}/>;
Test({
as: "bar",
callback: function (value) { }
});
<Test as="bar" callback={function (value) { }}/>;
68 changes: 68 additions & 0 deletions tests/baselines/reference/contextuallyTypedJsxAttribute.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
=== tests/cases/compiler/index.tsx ===
interface Elements {
>Elements : Symbol(Elements, Decl(index.tsx, 0, 0))

foo: { callback?: (value: number) => void };
>foo : Symbol(Elements.foo, Decl(index.tsx, 0, 20))
>callback : Symbol(callback, Decl(index.tsx, 1, 8))
>value : Symbol(value, Decl(index.tsx, 1, 21))

bar: { callback?: (value: string) => void };
>bar : Symbol(Elements.bar, Decl(index.tsx, 1, 46))
>callback : Symbol(callback, Decl(index.tsx, 2, 8))
>value : Symbol(value, Decl(index.tsx, 2, 21))
}

type Props<C extends keyof Elements> = { as?: C } & Elements[C];
>Props : Symbol(Props, Decl(index.tsx, 3, 1))
>C : Symbol(C, Decl(index.tsx, 5, 11))
>Elements : Symbol(Elements, Decl(index.tsx, 0, 0))
>as : Symbol(as, Decl(index.tsx, 5, 40))
>C : Symbol(C, Decl(index.tsx, 5, 11))
>Elements : Symbol(Elements, Decl(index.tsx, 0, 0))
>C : Symbol(C, Decl(index.tsx, 5, 11))

declare function Test<C extends keyof Elements>(props: Props<C>): null;
>Test : Symbol(Test, Decl(index.tsx, 5, 64))
>C : Symbol(C, Decl(index.tsx, 6, 22))
>Elements : Symbol(Elements, Decl(index.tsx, 0, 0))
>props : Symbol(props, Decl(index.tsx, 6, 48))
>Props : Symbol(Props, Decl(index.tsx, 3, 1))
>C : Symbol(C, Decl(index.tsx, 6, 22))

<Test
>Test : Symbol(Test, Decl(index.tsx, 5, 64))

as="bar"
>as : Symbol(as, Decl(index.tsx, 8, 5))

callback={(value) => {}}
>callback : Symbol(callback, Decl(index.tsx, 9, 10))
>value : Symbol(value, Decl(index.tsx, 10, 13))

/>;

Test({
>Test : Symbol(Test, Decl(index.tsx, 5, 64))

as: "bar",
>as : Symbol(as, Decl(index.tsx, 13, 6))

callback: (value) => {},
>callback : Symbol(callback, Decl(index.tsx, 14, 12))
>value : Symbol(value, Decl(index.tsx, 15, 13))

});

<Test<'bar'>
>Test : Symbol(Test, Decl(index.tsx, 5, 64))

as="bar"
>as : Symbol(as, Decl(index.tsx, 18, 12))

callback={(value) => {}}
>callback : Symbol(callback, Decl(index.tsx, 19, 10))
>value : Symbol(value, Decl(index.tsx, 20, 13))

/>;

66 changes: 66 additions & 0 deletions tests/baselines/reference/contextuallyTypedJsxAttribute.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
=== tests/cases/compiler/index.tsx ===
interface Elements {
foo: { callback?: (value: number) => void };
>foo : { callback?: (value: number) => void; }
>callback : (value: number) => void
>value : number

bar: { callback?: (value: string) => void };
>bar : { callback?: (value: string) => void; }
>callback : (value: string) => void
>value : string
}

type Props<C extends keyof Elements> = { as?: C } & Elements[C];
>Props : Props<C>
>as : C

declare function Test<C extends keyof Elements>(props: Props<C>): null;
>Test : <C extends keyof Elements>(props: Props<C>) => null
>props : Props<C>
>null : null

<Test
><Test as="bar" callback={(value) => {}}/> : error
>Test : <C extends keyof Elements>(props: Props<C>) => null

as="bar"
>as : "bar"

callback={(value) => {}}
>callback : (value: string) => void
>(value) => {} : (value: string) => void
>value : string

/>;

Test({
>Test({ as: "bar", callback: (value) => {},}) : null
>Test : <C extends keyof Elements>(props: Props<C>) => null
>{ as: "bar", callback: (value) => {},} : { as: "bar"; callback: (value: string) => void; }

as: "bar",
>as : "bar"
>"bar" : "bar"

callback: (value) => {},
>callback : (value: string) => void
>(value) => {} : (value: string) => void
>value : string

});

<Test<'bar'>
><Test<'bar'> as="bar" callback={(value) => {}}/> : error
>Test : <C extends keyof Elements>(props: Props<C>) => null

as="bar"
>as : "bar"

callback={(value) => {}}
>callback : (value: string) => void
>(value) => {} : (value: string) => void
>value : string

/>;

26 changes: 26 additions & 0 deletions tests/cases/compiler/contextuallyTypedJsxAttribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// @jsx: preserve
// @noImplicitAny: true

// @filename: index.tsx
interface Elements {
foo: { callback?: (value: number) => void };
bar: { callback?: (value: string) => void };
}

type Props<C extends keyof Elements> = { as?: C } & Elements[C];
declare function Test<C extends keyof Elements>(props: Props<C>): null;

<Test
as="bar"
callback={(value) => {}}
/>;

Test({
as: "bar",
callback: (value) => {},
});

<Test<'bar'>
as="bar"
callback={(value) => {}}
/>;