From b379e7fc791fc8646d9d255adce5127a6ab83099 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:40:26 -0700 Subject: [PATCH] Pass contextFlags when getting contextual type of JSX elements/attributes (#49707) --- src/compiler/checker.ts | 20 +++--- .../contextuallyTypedJsxAttribute.js | 32 +++++++++ .../contextuallyTypedJsxAttribute.symbols | 68 +++++++++++++++++++ .../contextuallyTypedJsxAttribute.types | 66 ++++++++++++++++++ .../compiler/contextuallyTypedJsxAttribute.ts | 26 +++++++ 5 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/contextuallyTypedJsxAttribute.js create mode 100644 tests/baselines/reference/contextuallyTypedJsxAttribute.symbols create mode 100644 tests/baselines/reference/contextuallyTypedJsxAttribute.types create mode 100644 tests/cases/compiler/contextuallyTypedJsxAttribute.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9d88667111ce5..8e24fee20606d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 !== "")) { @@ -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); } } @@ -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); diff --git a/tests/baselines/reference/contextuallyTypedJsxAttribute.js b/tests/baselines/reference/contextuallyTypedJsxAttribute.js new file mode 100644 index 0000000000000..18b4dec211ccc --- /dev/null +++ b/tests/baselines/reference/contextuallyTypedJsxAttribute.js @@ -0,0 +1,32 @@ +//// [index.tsx] +interface Elements { + foo: { callback?: (value: number) => void }; + bar: { callback?: (value: string) => void }; +} + +type Props = { as?: C } & Elements[C]; +declare function Test(props: Props): null; + + {}} +/>; + +Test({ + as: "bar", + callback: (value) => {}, +}); + + + as="bar" + callback={(value) => {}} +/>; + + +//// [index.jsx] +; +Test({ + as: "bar", + callback: function (value) { } +}); +; diff --git a/tests/baselines/reference/contextuallyTypedJsxAttribute.symbols b/tests/baselines/reference/contextuallyTypedJsxAttribute.symbols new file mode 100644 index 0000000000000..fb315569a7eda --- /dev/null +++ b/tests/baselines/reference/contextuallyTypedJsxAttribute.symbols @@ -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 = { 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(props: Props): 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 : 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 : 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)) + +/>; + diff --git a/tests/baselines/reference/contextuallyTypedJsxAttribute.types b/tests/baselines/reference/contextuallyTypedJsxAttribute.types new file mode 100644 index 0000000000000..6e33aa57d8253 --- /dev/null +++ b/tests/baselines/reference/contextuallyTypedJsxAttribute.types @@ -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 = { as?: C } & Elements[C]; +>Props : Props +>as : C + +declare function Test(props: Props): null; +>Test : (props: Props) => null +>props : Props +>null : null + + {}}/> : error +>Test : (props: Props) => 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 : (props: Props) => 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 + +}); + + +> as="bar" callback={(value) => {}}/> : error +>Test : (props: Props) => null + + as="bar" +>as : "bar" + + callback={(value) => {}} +>callback : (value: string) => void +>(value) => {} : (value: string) => void +>value : string + +/>; + diff --git a/tests/cases/compiler/contextuallyTypedJsxAttribute.ts b/tests/cases/compiler/contextuallyTypedJsxAttribute.ts new file mode 100644 index 0000000000000..1510e7ef44c09 --- /dev/null +++ b/tests/cases/compiler/contextuallyTypedJsxAttribute.ts @@ -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 = { as?: C } & Elements[C]; +declare function Test(props: Props): null; + + {}} +/>; + +Test({ + as: "bar", + callback: (value) => {}, +}); + + + as="bar" + callback={(value) => {}} +/>;