Move JSX props support check and make syntactic (#22970)

* Move JSX props support check and make syntactic

* Make parameter required
This commit is contained in:
Wesley Wigham 2018-03-29 12:36:42 -07:00 committed by GitHub
parent e4a73f3981
commit 51d44b6097
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 132 additions and 27 deletions

View File

@ -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(<ResolvedType>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;
}
}
}

View File

@ -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; }'
<Obj3 x={10} />; // 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;
<Obj3 {...attributes} />; // Error
~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2607: JSX element class does not support attributes because it does not have a 'pr' property.
<Obj3 {...{}} />; // 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; } };

View File

@ -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 = () => <div/>;
function testComponent(props) {
return <Test123 {...props}/>;
}

View File

@ -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 = () => <div/>;
function testComponent(props) {
return <Test123 {...props}/>;
}
//// [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 <div />; };
function testComponent(props) {
return <Test123 {...props}/>;
}

View File

@ -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 = () => <div/>;
>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 {...props}/>;
>Test123 : Symbol(Test123, Decl(tsxNoTypeAnnotatedSFC.tsx, 3, 5))
>props : Symbol(props, Decl(tsxNoTypeAnnotatedSFC.tsx, 5, 23))
}

View File

@ -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 = () => <div/>;
>Test123 : () => any
>() => <div/> : () => any
><div/> : any
>div : any
function testComponent(props) {
>testComponent : (props: any) => any
>props : any
return <Test123 {...props}/>;
><Test123 {...props}/> : any
>Test123 : () => any
>props : any
}

View File

@ -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 <div>Hello</div>;
}
}
declare const obj: { a: number | undefined } | undefined;
// OK
let unionedSpread = <Empty {...obj} />;
~~~~~~~~~~~~~~~~~~
!!! error TS2607: JSX element class does not support attributes because it does not have a 'props' property.

View File

@ -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 = () => <div/>;
function testComponent(props) {
return <Test123 {...props}/>;
}