Skip to content

Commit 6b7bf5e

Browse files
authored
Do not rely on ElementAttributeProperty if it is not present (#22936)
* Do not rely on ElementAttributeProperty if it is not present * Fix bug * Fix nits and lint
1 parent 1ed30c6 commit 6b7bf5e

10 files changed

+285
-88
lines changed

src/compiler/checker.ts

Lines changed: 74 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -14766,80 +14766,69 @@ namespace ts {
1476614766
signatures = mapDefined(signatures, s => getJsxSignatureTypeArgumentInstantiation(s, context, isJs));
1476714767
}
1476814768

14769-
return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromConstructSignature(t, isJs, context) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None);
14769+
return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromClassType(t, isJs, context, /*reportErrors*/ false) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None);
1477014770
}
1477114771

1477214772
function getJsxPropsTypeFromCallSignature(sig: Signature, context: Node) {
14773-
let propsType = getTypeOfFirstParameterOfSignature(sig);
14773+
let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, emptyObjectType);
1477414774
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
1477514775
if (intrinsicAttribs !== unknownType) {
1477614776
propsType = intersectTypes(intrinsicAttribs, propsType);
1477714777
}
1477814778
return propsType;
1477914779
}
1478014780

14781-
function getJsxPropsTypeFromClassType(hostClassType: Type, isJs: boolean, context: JsxOpeningLikeElement, reportErrors: boolean) {
14782-
if (isTypeAny(hostClassType)) {
14783-
return hostClassType;
14784-
}
14781+
function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) {
14782+
const instanceType = getReturnTypeOfSignature(sig);
14783+
return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation);
14784+
}
1478514785

14786-
const propsName = getJsxElementPropertiesName(getJsxNamespaceAt(context));
14787-
if (propsName === undefined) {
14788-
// There is no type ElementAttributesProperty, return 'any'
14789-
return anyType;
14786+
function getJsxPropsTypeFromClassType(sig: Signature, isJs: boolean, context: JsxOpeningLikeElement, reportErrors: boolean) {
14787+
const forcedLookupLocation = getJsxElementPropertiesName(getJsxNamespaceAt(context));
14788+
const attributesType = forcedLookupLocation === undefined
14789+
// If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type
14790+
? getTypeOfFirstParameterOfSignatureWithFallback(sig, emptyObjectType)
14791+
: forcedLookupLocation === ""
14792+
// If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead
14793+
? getReturnTypeOfSignature(sig)
14794+
// Otherwise get the type of the property on the signature return type
14795+
: getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation);
14796+
14797+
if (!attributesType) {
14798+
// There is no property named 'props' on this instance type
14799+
if (reportErrors && !!forcedLookupLocation && !!length(context.attributes.properties)) {
14800+
error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation));
14801+
}
14802+
return emptyObjectType;
1479014803
}
14791-
else if (propsName === "") {
14792-
// If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead
14793-
return hostClassType;
14804+
else if (isTypeAny(attributesType)) {
14805+
// Props is of type 'any' or unknown
14806+
return attributesType;
1479414807
}
1479514808
else {
14796-
const attributesType = getTypeOfPropertyOfType(hostClassType, propsName);
14797-
14798-
if (!attributesType) {
14799-
// There is no property named 'props' on this instance type
14800-
if (reportErrors && !!length(context.attributes.properties)) {
14801-
error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(propsName));
14802-
}
14803-
return emptyObjectType;
14809+
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements
14810+
let apparentAttributesType = attributesType;
14811+
const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context);
14812+
if (intrinsicClassAttribs !== unknownType) {
14813+
const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol);
14814+
const hostClassType = getReturnTypeOfSignature(sig);
14815+
apparentAttributesType = intersectTypes(
14816+
typeParams
14817+
? createTypeReference(<GenericType>intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isJs))
14818+
: intrinsicClassAttribs,
14819+
apparentAttributesType
14820+
);
1480414821
}
14805-
else if (isTypeAny(attributesType)) {
14806-
// Props is of type 'any' or unknown
14807-
return attributesType;
14808-
}
14809-
else {
14810-
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements
14811-
let apparentAttributesType = attributesType;
14812-
const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context);
14813-
if (intrinsicClassAttribs !== unknownType) {
14814-
const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol);
14815-
apparentAttributesType = intersectTypes(
14816-
typeParams
14817-
? createTypeReference(<GenericType>intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isJs))
14818-
: intrinsicClassAttribs,
14819-
apparentAttributesType
14820-
);
14821-
}
14822-
14823-
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
14824-
if (intrinsicAttribs !== unknownType) {
14825-
apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType);
14826-
}
1482714822

14828-
return apparentAttributesType;
14823+
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
14824+
if (intrinsicAttribs !== unknownType) {
14825+
apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType);
1482914826
}
14830-
}
14831-
}
1483214827

14833-
function getJsxPropsTypeFromConstructSignature(sig: Signature, isJs: boolean, context: JsxOpeningLikeElement) {
14834-
const hostClassType = getReturnTypeOfSignature(sig);
14835-
if (hostClassType) {
14836-
return getJsxPropsTypeFromClassType(hostClassType, isJs, context, /*reportErrors*/ false);
14828+
return apparentAttributesType;
1483714829
}
14838-
return getJsxPropsTypeFromCallSignature(sig, context);
1483914830
}
1484014831

14841-
14842-
1484314832
// If the given type is an object or union type with a single signature, and if that signature has at
1484414833
// least as many parameters as the given function, return the signature. Otherwise return undefined.
1484514834
function getContextualCallSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature {
@@ -15556,31 +15545,7 @@ namespace ts {
1555615545
return links.resolvedSymbol;
1555715546
}
1555815547

15559-
/**
15560-
* Given a JSX element that is a class element, finds the Element Instance Type. If the
15561-
* element is not a class element, or the class element type cannot be determined, returns 'undefined'.
15562-
* For example, in the element <MyClass>, the element instance type is `MyClass` (not `typeof MyClass`).
15563-
*/
15564-
function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type) {
15565-
Debug.assert(!(valueType.flags & TypeFlags.Union));
15566-
if (isTypeAny(valueType)) {
15567-
// Short-circuit if the class tag is using an element type 'any'
15568-
return anyType;
15569-
}
15570-
15571-
// Resolve the signatures, preferring constructor
15572-
let signatures = getSignaturesOfType(valueType, SignatureKind.Construct);
15573-
if (signatures.length === 0) {
15574-
// No construct signatures, try call signatures
15575-
signatures = getSignaturesOfType(valueType, SignatureKind.Call);
15576-
if (signatures.length === 0) {
15577-
// We found no signatures at all, which is an error
15578-
error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName));
15579-
return unknownType;
15580-
}
15581-
}
15582-
15583-
// Instantiate in context of source type
15548+
function instantiateJsxSignatures(node: JsxOpeningLikeElement, signatures: Signature[]) {
1558415549
const instantiatedSignatures = [];
1558515550
let candidateForTypeArgumentError: Signature;
1558615551
let hasTypeArgumentError: boolean = !!node.typeArguments;
@@ -15605,7 +15570,6 @@ namespace ts {
1560515570
instantiatedSignatures.push(signature);
1560615571
}
1560715572
}
15608-
1560915573
if (node.typeArguments && hasTypeArgumentError) {
1561015574
if (candidateForTypeArgumentError) {
1561115575
checkTypeArguments(candidateForTypeArgumentError, node.typeArguments, /*reportErrors*/ true);
@@ -15615,8 +15579,7 @@ namespace ts {
1561515579
diagnostics.add(getTypeArgumentArityError(node, signatures, node.typeArguments));
1561615580
}
1561715581
}
15618-
15619-
return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
15582+
return instantiatedSignatures;
1562015583
}
1562115584

1562215585
function getJsxSignatureTypeArgumentInstantiation(signature: Signature, node: JsxOpeningLikeElement, isJavascript: boolean, reportErrors?: boolean) {
@@ -15827,8 +15790,12 @@ namespace ts {
1582715790
}), UnionReduction.Subtype);
1582815791
}
1582915792

15793+
// Shortcircuit any
15794+
if (isTypeAny(elementType)) {
15795+
return elementType;
15796+
}
1583015797
// If the elemType is a string type, we have to return anyType to prevent an error downstream as we will try to find construct or call signature of the type
15831-
if (elementType.flags & TypeFlags.String) {
15798+
else if (elementType.flags & TypeFlags.String) {
1583215799
return anyType;
1583315800
}
1583415801
else if (elementType.flags & TypeFlags.StringLiteral) {
@@ -15854,7 +15821,22 @@ namespace ts {
1585415821
}
1585515822

1585615823
// Get the element instance type (the result of newing or invoking this tag)
15857-
const elemInstanceType = getJsxElementInstanceType(openingLikeElement, elementType);
15824+
15825+
// Resolve the signatures, preferring constructor
15826+
let signatures = getSignaturesOfType(elementType, SignatureKind.Construct);
15827+
if (signatures.length === 0) {
15828+
// No construct signatures, try call signatures
15829+
signatures = getSignaturesOfType(elementType, SignatureKind.Call);
15830+
if (signatures.length === 0) {
15831+
// We found no signatures at all, which is an error
15832+
error(openingLikeElement.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(openingLikeElement.tagName));
15833+
return unknownType;
15834+
}
15835+
}
15836+
15837+
// Instantiate in context of source type
15838+
const instantiatedSignatures = instantiateJsxSignatures(openingLikeElement, signatures);
15839+
const elemInstanceType = getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
1585815840

1585915841
// If we should include all stateless attributes type, then get all attributes type from all stateless function signature.
1586015842
// Otherwise get only attributes type from the signature picked by choose-overload logic.
@@ -15871,7 +15853,8 @@ namespace ts {
1587115853
checkTypeRelatedTo(elemInstanceType, elementClassType, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
1587215854
}
1587315855

15874-
return getJsxPropsTypeFromClassType(elemInstanceType, isInJavaScriptFile(openingLikeElement), openingLikeElement, /*reportErrors*/ true);
15856+
const isJs = isInJavaScriptFile(openingLikeElement);
15857+
return getUnionType(map(instantiatedSignatures, sig => getJsxPropsTypeFromClassType(sig, isJs, openingLikeElement, /*reportErrors*/ true)));
1587515858
}
1587615859

1587715860
/**
@@ -18469,8 +18452,13 @@ namespace ts {
1846918452
pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType;
1847018453
}
1847118454

18455+
1847218456
function getTypeOfFirstParameterOfSignature(signature: Signature) {
18473-
return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : neverType;
18457+
return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType);
18458+
}
18459+
18460+
function getTypeOfFirstParameterOfSignatureWithFallback(signature: Signature, fallbackType: Type) {
18461+
return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType;
1847418462
}
1847518463

1847618464
function inferFromAnnotatedParameters(signature: Signature, context: Signature, mapper: TypeMapper) {
@@ -27565,7 +27553,7 @@ namespace ts {
2756527553
export const JSX = "JSX" as __String;
2756627554
export const IntrinsicElements = "IntrinsicElements" as __String;
2756727555
export const ElementClass = "ElementClass" as __String;
27568-
export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String;
27556+
export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support
2756927557
export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String;
2757027558
export const Element = "Element" as __String;
2757127559
export const IntrinsicAttributes = "IntrinsicAttributes" as __String;

tests/baselines/reference/tsxElementResolution10.errors.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
tests/cases/conformance/jsx/file.tsx(13,1): error TS2605: JSX element type '{ x: number; }' is not a constructor function for JSX elements.
22
Property 'render' is missing in type '{ x: number; }'.
3+
tests/cases/conformance/jsx/file.tsx(13,7): error TS2322: Type '{ x: number; }' is not assignable to type 'string'.
4+
tests/cases/conformance/jsx/file.tsx(19,7): error TS2322: Type '{ x: number; render: number; }' is not assignable to type 'string'.
35

46

5-
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
7+
==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
68
declare module JSX {
79
interface Element { }
810
interface ElementClass {
@@ -19,10 +21,14 @@ tests/cases/conformance/jsx/file.tsx(13,1): error TS2605: JSX element type '{ x:
1921
~~~~~~~~~~~~~~~
2022
!!! error TS2605: JSX element type '{ x: number; }' is not a constructor function for JSX elements.
2123
!!! error TS2605: Property 'render' is missing in type '{ x: number; }'.
24+
~~~~~~
25+
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'string'.
2226

2327
interface Obj2type {
2428
(n: string): { x: number; render: any; };
2529
}
2630
var Obj2: Obj2type;
2731
<Obj2 x={32} render={100} />; // OK
32+
~~~~~~~~~~~~~~~~~~~
33+
!!! error TS2322: Type '{ x: number; render: number; }' is not assignable to type 'string'.
2834

tests/baselines/reference/tsxElementResolution15.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
tests/cases/conformance/jsx/file.tsx(3,12): error TS2608: The global type 'JSX.ElementAttributesProperty' may not have more than one property.
2+
tests/cases/conformance/jsx/file.tsx(11,7): error TS2322: Type '{ x: number; }' is not assignable to type 'string'.
23

34

4-
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
5+
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
56
declare module JSX {
67
interface Element { }
78
interface ElementAttributesProperty { pr1: any; pr2: any; }
@@ -15,4 +16,6 @@ tests/cases/conformance/jsx/file.tsx(3,12): error TS2608: The global type 'JSX.E
1516
}
1617
var Obj1: Obj1type;
1718
<Obj1 x={10} />; // Error
19+
~~~~~~
20+
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'string'.
1821

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
tests/cases/conformance/jsx/file.tsx(11,1): error TS2322: Type '{}' is not assignable to type 'string | number'.
2+
Type '{}' is not assignable to type 'number'.
3+
tests/cases/conformance/jsx/file.tsx(18,1): error TS2322: Type '{}' is not assignable to type 'string | number'.
4+
Type '{}' is not assignable to type 'number'.
5+
tests/cases/conformance/jsx/file.tsx(25,7): error TS2322: Type '{ x: number; }' is not assignable to type 'string | number'.
6+
Type '{ x: number; }' is not assignable to type 'number'.
7+
8+
9+
==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
10+
declare module JSX {
11+
interface Element { something; }
12+
interface IntrinsicElements { }
13+
}
14+
15+
interface Obj1 {
16+
new(n: string): { x: number };
17+
new(n: number): { y: string };
18+
}
19+
var Obj1: Obj1;
20+
<Obj1 />; // Error, return type is not an object type
21+
~~~~~~~~
22+
!!! error TS2322: Type '{}' is not assignable to type 'string | number'.
23+
!!! error TS2322: Type '{}' is not assignable to type 'number'.
24+
25+
interface Obj2 {
26+
(n: string): { x: number };
27+
(n: number): { y: string };
28+
}
29+
var Obj2: Obj2;
30+
<Obj2 />; // Error, return type is not an object type
31+
~~~~~~~~
32+
!!! error TS2322: Type '{}' is not assignable to type 'string | number'.
33+
!!! error TS2322: Type '{}' is not assignable to type 'number'.
34+
35+
interface Obj3 {
36+
(n: string): { x: number };
37+
(n: number): { x: number; y: string };
38+
}
39+
var Obj3: Obj3;
40+
<Obj3 x={42} />; // OK
41+
~~~~~~
42+
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'string | number'.
43+
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'number'.
44+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
tests/cases/compiler/file.tsx(11,14): error TS2322: Type '{ prop: number; }' is not assignable to type 'Record<string, string>'.
2+
Property 'prop' is incompatible with index signature.
3+
Type 'number' is not assignable to type 'string'.
4+
5+
6+
==== tests/cases/compiler/file.tsx (1 errors) ====
7+
declare namespace JSX {
8+
interface Element {
9+
render(): Element | string | false;
10+
}
11+
}
12+
13+
function SFC<T>(props: Record<string, T>) {
14+
return '';
15+
}
16+
17+
<SFC<string> prop={1}></SFC>; // should error
18+
~~~~~~~~
19+
!!! error TS2322: Type '{ prop: number; }' is not assignable to type 'Record<string, string>'.
20+
!!! error TS2322: Property 'prop' is incompatible with index signature.
21+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
22+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//// [file.tsx]
2+
declare namespace JSX {
3+
interface Element {
4+
render(): Element | string | false;
5+
}
6+
}
7+
8+
function SFC<T>(props: Record<string, T>) {
9+
return '';
10+
}
11+
12+
<SFC<string> prop={1}></SFC>; // should error
13+
14+
15+
//// [file.jsx]
16+
function SFC(props) {
17+
return '';
18+
}
19+
<SFC prop={1}></SFC>; // should error

0 commit comments

Comments
 (0)