diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b59395fe87c..dbbee348cd9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14767,7 +14767,7 @@ namespace ts { return propsType; } - function getJsxPropsTypeFromClassType(hostClassType: Type, isJs: boolean, context: Node) { + function getJsxPropsTypeFromClassType(hostClassType: Type, isJs: boolean, context: JsxOpeningLikeElement, reportErrors: boolean) { if (isTypeAny(hostClassType)) { return hostClassType; } @@ -14786,6 +14786,9 @@ namespace ts { if (!attributesType) { // There is no property named 'props' on this instance type + if (reportErrors && !!length(context.attributes.properties)) { + error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(propsName)); + } return emptyObjectType; } else if (isTypeAny(attributesType)) { @@ -14816,10 +14819,10 @@ namespace ts { } } - function getJsxPropsTypeFromConstructSignature(sig: Signature, isJs: boolean, context: Node) { + function getJsxPropsTypeFromConstructSignature(sig: Signature, isJs: boolean, context: JsxOpeningLikeElement) { const hostClassType = getReturnTypeOfSignature(sig); if (hostClassType) { - return getJsxPropsTypeFromClassType(hostClassType, isJs, context); + return getJsxPropsTypeFromClassType(hostClassType, isJs, context, /*reportErrors*/ false); } return getJsxPropsTypeFromCallSignature(sig, context); } @@ -15857,7 +15860,7 @@ namespace ts { checkTypeRelatedTo(elemInstanceType, elementClassType, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); } - return getJsxPropsTypeFromClassType(elemInstanceType, isInJavaScriptFile(openingLikeElement), openingLikeElement); + return getJsxPropsTypeFromClassType(elemInstanceType, isInJavaScriptFile(openingLikeElement), openingLikeElement, /*reportErrors*/ true); } /** @@ -16062,28 +16065,21 @@ namespace ts { // attr1 and attr2 are treated as JSXAttributes attached in the JsxOpeningLikeElement as "attributes". const sourceAttributesType = createJsxAttributesTypeFromAttributesProperty(openingLikeElement, checkMode); - // If the targetAttributesType is an emptyObjectType, indicating that there is no property named 'props' on this instance type. - // but there exists a sourceAttributesType, we need to explicitly give an error as normal assignability check allow excess properties and will pass. - if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || getPropertiesOfType(sourceAttributesType).length > 0)) { - error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(getJsxElementPropertiesName(getJsxNamespaceAt(openingLikeElement)))); - } - else { - // Check if sourceAttributesType assignable to targetAttributesType though this check will allow excess properties - const isSourceAttributeTypeAssignableToTarget = checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.attributes.properties.length > 0 ? openingLikeElement.attributes : openingLikeElement); - // After we check for assignability, we will do another pass to check that all explicitly specified attributes have correct name corresponding in targetAttributeType. - // This will allow excess properties in spread type as it is very common pattern to spread outer attributes into React component in its render method. - if (isSourceAttributeTypeAssignableToTarget && !isTypeAny(sourceAttributesType) && !isTypeAny(targetAttributesType)) { - for (const attribute of openingLikeElement.attributes.properties) { - if (!isJsxAttribute(attribute)) { - continue; - } - const attrName = attribute.name; - const isNotIgnoredJsxProperty = (isUnhyphenatedJsxName(idText(attrName)) || !!(getPropertyOfType(targetAttributesType, attrName.escapedText))); - if (isNotIgnoredJsxProperty && !isKnownProperty(targetAttributesType, attrName.escapedText, /*isComparingJsxAttributes*/ true)) { - error(attribute, Diagnostics.Property_0_does_not_exist_on_type_1, idText(attrName), typeToString(targetAttributesType)); - // We break here so that errors won't be cascading - break; - } + // Check if sourceAttributesType assignable to targetAttributesType though this check will allow excess properties + const isSourceAttributeTypeAssignableToTarget = checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.attributes.properties.length > 0 ? openingLikeElement.attributes : openingLikeElement); + // After we check for assignability, we will do another pass to check that all explicitly specified attributes have correct name corresponding in targetAttributeType. + // This will allow excess properties in spread type as it is very common pattern to spread outer attributes into React component in its render method. + if (isSourceAttributeTypeAssignableToTarget && !isTypeAny(sourceAttributesType) && !isTypeAny(targetAttributesType)) { + for (const attribute of openingLikeElement.attributes.properties) { + if (!isJsxAttribute(attribute)) { + continue; + } + const attrName = attribute.name; + const isNotIgnoredJsxProperty = (isUnhyphenatedJsxName(idText(attrName)) || !!(getPropertyOfType(targetAttributesType, attrName.escapedText))); + if (isNotIgnoredJsxProperty && !isKnownProperty(targetAttributesType, attrName.escapedText, /*isComparingJsxAttributes*/ true)) { + error(attribute, Diagnostics.Property_0_does_not_exist_on_type_1, idText(attrName), typeToString(targetAttributesType)); + // We break here so that errors won't be cascading + break; } } } diff --git a/tests/baselines/reference/tsxElementResolution12.errors.txt b/tests/baselines/reference/tsxElementResolution12.errors.txt index ac9758acfb6..63b39aea1f9 100644 --- a/tests/baselines/reference/tsxElementResolution12.errors.txt +++ b/tests/baselines/reference/tsxElementResolution12.errors.txt @@ -1,11 +1,13 @@ tests/cases/conformance/jsx/file.tsx(23,1): error TS2607: JSX element class does not support attributes because it does not have a 'pr' property. +tests/cases/conformance/jsx/file.tsx(23,7): error TS2339: Property 'x' does not exist on type '{}'. tests/cases/conformance/jsx/file.tsx(25,1): error TS2607: JSX element class does not support attributes because it does not have a 'pr' property. +tests/cases/conformance/jsx/file.tsx(26,1): error TS2607: JSX element class does not support attributes because it does not have a 'pr' property. tests/cases/conformance/jsx/file.tsx(33,7): error TS2322: Type '{ x: string; }' is not assignable to type '{ x: number; }'. Types of property 'x' are incompatible. Type 'string' is not assignable to type 'number'. -==== tests/cases/conformance/jsx/file.tsx (3 errors) ==== +==== tests/cases/conformance/jsx/file.tsx (5 errors) ==== declare module JSX { interface Element { } interface ElementAttributesProperty { pr: any; } @@ -31,11 +33,15 @@ tests/cases/conformance/jsx/file.tsx(33,7): error TS2322: Type '{ x: string; }' ; // Error ~~~~~~~~~~~~~~~ !!! error TS2607: JSX element class does not support attributes because it does not have a 'pr' property. + ~~~~~~ +!!! error TS2339: Property 'x' does not exist on type '{}'. var attributes: any; ; // Error ~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2607: JSX element class does not support attributes because it does not have a 'pr' property. ; // OK + ~~~~~~~~~~~~~~~~ +!!! error TS2607: JSX element class does not support attributes because it does not have a 'pr' property. interface Obj4type { new(n: string): { x: number; pr: { x: number; } }; diff --git a/tests/baselines/reference/tsxNoTypeAnnotatedSFC.errors.txt b/tests/baselines/reference/tsxNoTypeAnnotatedSFC.errors.txt new file mode 100644 index 00000000000..6415843f8a0 --- /dev/null +++ b/tests/baselines/reference/tsxNoTypeAnnotatedSFC.errors.txt @@ -0,0 +1,14 @@ +tests/cases/compiler/tsxNoTypeAnnotatedSFC.tsx(2,24): error TS2307: Cannot find module 'react'. + + +==== tests/cases/compiler/tsxNoTypeAnnotatedSFC.tsx (1 errors) ==== + // not _actually_ making react available in this test to regression test #22948 + import * as React from 'react'; + ~~~~~~~ +!!! error TS2307: Cannot find module 'react'. + + const Test123 = () =>
; + + function testComponent(props) { + return ; + } \ No newline at end of file diff --git a/tests/baselines/reference/tsxNoTypeAnnotatedSFC.js b/tests/baselines/reference/tsxNoTypeAnnotatedSFC.js new file mode 100644 index 00000000000..6834f9e7880 --- /dev/null +++ b/tests/baselines/reference/tsxNoTypeAnnotatedSFC.js @@ -0,0 +1,19 @@ +//// [tsxNoTypeAnnotatedSFC.tsx] +// not _actually_ making react available in this test to regression test #22948 +import * as React from 'react'; + +const Test123 = () =>
; + +function testComponent(props) { + return ; +} + +//// [tsxNoTypeAnnotatedSFC.jsx] +"use strict"; +exports.__esModule = true; +// not _actually_ making react available in this test to regression test #22948 +var React = require("react"); +var Test123 = function () { return
; }; +function testComponent(props) { + return ; +} diff --git a/tests/baselines/reference/tsxNoTypeAnnotatedSFC.symbols b/tests/baselines/reference/tsxNoTypeAnnotatedSFC.symbols new file mode 100644 index 00000000000..5b626acf5ea --- /dev/null +++ b/tests/baselines/reference/tsxNoTypeAnnotatedSFC.symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/tsxNoTypeAnnotatedSFC.tsx === +// not _actually_ making react available in this test to regression test #22948 +import * as React from 'react'; +>React : Symbol(React, Decl(tsxNoTypeAnnotatedSFC.tsx, 1, 6)) + +const Test123 = () =>
; +>Test123 : Symbol(Test123, Decl(tsxNoTypeAnnotatedSFC.tsx, 3, 5)) + +function testComponent(props) { +>testComponent : Symbol(testComponent, Decl(tsxNoTypeAnnotatedSFC.tsx, 3, 29)) +>props : Symbol(props, Decl(tsxNoTypeAnnotatedSFC.tsx, 5, 23)) + + return ; +>Test123 : Symbol(Test123, Decl(tsxNoTypeAnnotatedSFC.tsx, 3, 5)) +>props : Symbol(props, Decl(tsxNoTypeAnnotatedSFC.tsx, 5, 23)) +} diff --git a/tests/baselines/reference/tsxNoTypeAnnotatedSFC.types b/tests/baselines/reference/tsxNoTypeAnnotatedSFC.types new file mode 100644 index 00000000000..120029734d3 --- /dev/null +++ b/tests/baselines/reference/tsxNoTypeAnnotatedSFC.types @@ -0,0 +1,20 @@ +=== tests/cases/compiler/tsxNoTypeAnnotatedSFC.tsx === +// not _actually_ making react available in this test to regression test #22948 +import * as React from 'react'; +>React : any + +const Test123 = () =>
; +>Test123 : () => any +>() =>
: () => any +>
: any +>div : any + +function testComponent(props) { +>testComponent : (props: any) => any +>props : any + + return ; +> : any +>Test123 : () => any +>props : any +} diff --git a/tests/baselines/reference/tsxSpreadAttributesResolution17.errors.txt b/tests/baselines/reference/tsxSpreadAttributesResolution17.errors.txt new file mode 100644 index 00000000000..b57d6e3586b --- /dev/null +++ b/tests/baselines/reference/tsxSpreadAttributesResolution17.errors.txt @@ -0,0 +1,25 @@ +tests/cases/conformance/jsx/file.tsx(18,21): error TS2607: JSX element class does not support attributes because it does not have a 'props' property. + + +==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== + declare global { + namespace JSX { + interface Element {} + interface ElementAttributesProperty { props: {} } + } + } + declare var React: any; + + export class Empty extends React.Component<{}, {}> { + render() { + return
Hello
; + } + } + + declare const obj: { a: number | undefined } | undefined; + + // OK + let unionedSpread = ; + ~~~~~~~~~~~~~~~~~~ +!!! error TS2607: JSX element class does not support attributes because it does not have a 'props' property. + \ No newline at end of file diff --git a/tests/cases/compiler/tsxNoTypeAnnotatedSFC.tsx b/tests/cases/compiler/tsxNoTypeAnnotatedSFC.tsx new file mode 100644 index 00000000000..ccaebb18f52 --- /dev/null +++ b/tests/cases/compiler/tsxNoTypeAnnotatedSFC.tsx @@ -0,0 +1,9 @@ +// @jsx: preserve +// not _actually_ making react available in this test to regression test #22948 +import * as React from 'react'; + +const Test123 = () =>
; + +function testComponent(props) { + return ; +} \ No newline at end of file