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
This commit is contained in:
Wesley Wigham
2018-03-29 16:19:55 -07:00
committed by GitHub
parent 1ed30c6d4d
commit 6b7bf5e22c
10 changed files with 286 additions and 89 deletions
+75 -87
View File
@@ -14766,11 +14766,11 @@ namespace ts {
signatures = mapDefined(signatures, s => getJsxSignatureTypeArgumentInstantiation(s, context, isJs));
}
return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromConstructSignature(t, isJs, context) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None);
return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromClassType(t, isJs, context, /*reportErrors*/ false) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None);
}
function getJsxPropsTypeFromCallSignature(sig: Signature, context: Node) {
let propsType = getTypeOfFirstParameterOfSignature(sig);
let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, emptyObjectType);
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
if (intrinsicAttribs !== unknownType) {
propsType = intersectTypes(intrinsicAttribs, propsType);
@@ -14778,68 +14778,57 @@ namespace ts {
return propsType;
}
function getJsxPropsTypeFromClassType(hostClassType: Type, isJs: boolean, context: JsxOpeningLikeElement, reportErrors: boolean) {
if (isTypeAny(hostClassType)) {
return hostClassType;
}
function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) {
const instanceType = getReturnTypeOfSignature(sig);
return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation);
}
const propsName = getJsxElementPropertiesName(getJsxNamespaceAt(context));
if (propsName === undefined) {
// There is no type ElementAttributesProperty, return 'any'
return anyType;
function getJsxPropsTypeFromClassType(sig: Signature, isJs: boolean, context: JsxOpeningLikeElement, reportErrors: boolean) {
const forcedLookupLocation = getJsxElementPropertiesName(getJsxNamespaceAt(context));
const attributesType = forcedLookupLocation === undefined
// If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type
? getTypeOfFirstParameterOfSignatureWithFallback(sig, emptyObjectType)
: forcedLookupLocation === ""
// If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead
? getReturnTypeOfSignature(sig)
// Otherwise get the type of the property on the signature return type
: getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation);
if (!attributesType) {
// There is no property named 'props' on this instance type
if (reportErrors && !!forcedLookupLocation && !!length(context.attributes.properties)) {
error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation));
}
return emptyObjectType;
}
else if (propsName === "") {
// If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead
return hostClassType;
else if (isTypeAny(attributesType)) {
// Props is of type 'any' or unknown
return attributesType;
}
else {
const attributesType = getTypeOfPropertyOfType(hostClassType, propsName);
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;
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements
let apparentAttributesType = attributesType;
const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context);
if (intrinsicClassAttribs !== unknownType) {
const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol);
const hostClassType = getReturnTypeOfSignature(sig);
apparentAttributesType = intersectTypes(
typeParams
? createTypeReference(<GenericType>intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isJs))
: intrinsicClassAttribs,
apparentAttributesType
);
}
else if (isTypeAny(attributesType)) {
// Props is of type 'any' or unknown
return attributesType;
}
else {
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements
let apparentAttributesType = attributesType;
const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context);
if (intrinsicClassAttribs !== unknownType) {
const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol);
apparentAttributesType = intersectTypes(
typeParams
? createTypeReference(<GenericType>intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isJs))
: intrinsicClassAttribs,
apparentAttributesType
);
}
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
if (intrinsicAttribs !== unknownType) {
apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType);
}
return apparentAttributesType;
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
if (intrinsicAttribs !== unknownType) {
apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType);
}
return apparentAttributesType;
}
}
function getJsxPropsTypeFromConstructSignature(sig: Signature, isJs: boolean, context: JsxOpeningLikeElement) {
const hostClassType = getReturnTypeOfSignature(sig);
if (hostClassType) {
return getJsxPropsTypeFromClassType(hostClassType, isJs, context, /*reportErrors*/ false);
}
return getJsxPropsTypeFromCallSignature(sig, context);
}
// If the given type is an object or union type with a single signature, and if that signature has at
// least as many parameters as the given function, return the signature. Otherwise return undefined.
function getContextualCallSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature {
@@ -15556,31 +15545,7 @@ namespace ts {
return links.resolvedSymbol;
}
/**
* Given a JSX element that is a class element, finds the Element Instance Type. If the
* element is not a class element, or the class element type cannot be determined, returns 'undefined'.
* For example, in the element <MyClass>, the element instance type is `MyClass` (not `typeof MyClass`).
*/
function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type) {
Debug.assert(!(valueType.flags & TypeFlags.Union));
if (isTypeAny(valueType)) {
// Short-circuit if the class tag is using an element type 'any'
return anyType;
}
// Resolve the signatures, preferring constructor
let signatures = getSignaturesOfType(valueType, SignatureKind.Construct);
if (signatures.length === 0) {
// No construct signatures, try call signatures
signatures = getSignaturesOfType(valueType, SignatureKind.Call);
if (signatures.length === 0) {
// We found no signatures at all, which is an error
error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName));
return unknownType;
}
}
// Instantiate in context of source type
function instantiateJsxSignatures(node: JsxOpeningLikeElement, signatures: Signature[]) {
const instantiatedSignatures = [];
let candidateForTypeArgumentError: Signature;
let hasTypeArgumentError: boolean = !!node.typeArguments;
@@ -15605,7 +15570,6 @@ namespace ts {
instantiatedSignatures.push(signature);
}
}
if (node.typeArguments && hasTypeArgumentError) {
if (candidateForTypeArgumentError) {
checkTypeArguments(candidateForTypeArgumentError, node.typeArguments, /*reportErrors*/ true);
@@ -15615,8 +15579,7 @@ namespace ts {
diagnostics.add(getTypeArgumentArityError(node, signatures, node.typeArguments));
}
}
return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
return instantiatedSignatures;
}
function getJsxSignatureTypeArgumentInstantiation(signature: Signature, node: JsxOpeningLikeElement, isJavascript: boolean, reportErrors?: boolean) {
@@ -15827,8 +15790,12 @@ namespace ts {
}), UnionReduction.Subtype);
}
// Shortcircuit any
if (isTypeAny(elementType)) {
return elementType;
}
// 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
if (elementType.flags & TypeFlags.String) {
else if (elementType.flags & TypeFlags.String) {
return anyType;
}
else if (elementType.flags & TypeFlags.StringLiteral) {
@@ -15854,7 +15821,22 @@ namespace ts {
}
// Get the element instance type (the result of newing or invoking this tag)
const elemInstanceType = getJsxElementInstanceType(openingLikeElement, elementType);
// Resolve the signatures, preferring constructor
let signatures = getSignaturesOfType(elementType, SignatureKind.Construct);
if (signatures.length === 0) {
// No construct signatures, try call signatures
signatures = getSignaturesOfType(elementType, SignatureKind.Call);
if (signatures.length === 0) {
// We found no signatures at all, which is an error
error(openingLikeElement.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(openingLikeElement.tagName));
return unknownType;
}
}
// Instantiate in context of source type
const instantiatedSignatures = instantiateJsxSignatures(openingLikeElement, signatures);
const elemInstanceType = getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
// If we should include all stateless attributes type, then get all attributes type from all stateless function signature.
// Otherwise get only attributes type from the signature picked by choose-overload logic.
@@ -15871,7 +15853,8 @@ 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, /*reportErrors*/ true);
const isJs = isInJavaScriptFile(openingLikeElement);
return getUnionType(map(instantiatedSignatures, sig => getJsxPropsTypeFromClassType(sig, isJs, openingLikeElement, /*reportErrors*/ true)));
}
/**
@@ -18469,8 +18452,13 @@ namespace ts {
pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType;
}
function getTypeOfFirstParameterOfSignature(signature: Signature) {
return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : neverType;
return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType);
}
function getTypeOfFirstParameterOfSignatureWithFallback(signature: Signature, fallbackType: Type) {
return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType;
}
function inferFromAnnotatedParameters(signature: Signature, context: Signature, mapper: TypeMapper) {
@@ -27565,7 +27553,7 @@ namespace ts {
export const JSX = "JSX" as __String;
export const IntrinsicElements = "IntrinsicElements" as __String;
export const ElementClass = "ElementClass" as __String;
export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String;
export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support
export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String;
export const Element = "Element" as __String;
export const IntrinsicAttributes = "IntrinsicAttributes" as __String;
@@ -1,8 +1,10 @@
tests/cases/conformance/jsx/file.tsx(13,1): error TS2605: JSX element type '{ x: number; }' is not a constructor function for JSX elements.
Property 'render' is missing in type '{ x: number; }'.
tests/cases/conformance/jsx/file.tsx(13,7): error TS2322: Type '{ x: number; }' is not assignable to type 'string'.
tests/cases/conformance/jsx/file.tsx(19,7): error TS2322: Type '{ x: number; render: number; }' is not assignable to type 'string'.
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
declare module JSX {
interface Element { }
interface ElementClass {
@@ -19,10 +21,14 @@ tests/cases/conformance/jsx/file.tsx(13,1): error TS2605: JSX element type '{ x:
~~~~~~~~~~~~~~~
!!! error TS2605: JSX element type '{ x: number; }' is not a constructor function for JSX elements.
!!! error TS2605: Property 'render' is missing in type '{ x: number; }'.
~~~~~~
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'string'.
interface Obj2type {
(n: string): { x: number; render: any; };
}
var Obj2: Obj2type;
<Obj2 x={32} render={100} />; // OK
~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '{ x: number; render: number; }' is not assignable to type 'string'.
@@ -1,7 +1,8 @@
tests/cases/conformance/jsx/file.tsx(3,12): error TS2608: The global type 'JSX.ElementAttributesProperty' may not have more than one property.
tests/cases/conformance/jsx/file.tsx(11,7): error TS2322: Type '{ x: number; }' is not assignable to type 'string'.
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
declare module JSX {
interface Element { }
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
}
var Obj1: Obj1type;
<Obj1 x={10} />; // Error
~~~~~~
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'string'.
@@ -0,0 +1,44 @@
tests/cases/conformance/jsx/file.tsx(11,1): error TS2322: Type '{}' is not assignable to type 'string | number'.
Type '{}' is not assignable to type 'number'.
tests/cases/conformance/jsx/file.tsx(18,1): error TS2322: Type '{}' is not assignable to type 'string | number'.
Type '{}' is not assignable to type 'number'.
tests/cases/conformance/jsx/file.tsx(25,7): error TS2322: Type '{ x: number; }' is not assignable to type 'string | number'.
Type '{ x: number; }' is not assignable to type 'number'.
==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
declare module JSX {
interface Element { something; }
interface IntrinsicElements { }
}
interface Obj1 {
new(n: string): { x: number };
new(n: number): { y: string };
}
var Obj1: Obj1;
<Obj1 />; // Error, return type is not an object type
~~~~~~~~
!!! error TS2322: Type '{}' is not assignable to type 'string | number'.
!!! error TS2322: Type '{}' is not assignable to type 'number'.
interface Obj2 {
(n: string): { x: number };
(n: number): { y: string };
}
var Obj2: Obj2;
<Obj2 />; // Error, return type is not an object type
~~~~~~~~
!!! error TS2322: Type '{}' is not assignable to type 'string | number'.
!!! error TS2322: Type '{}' is not assignable to type 'number'.
interface Obj3 {
(n: string): { x: number };
(n: number): { x: number; y: string };
}
var Obj3: Obj3;
<Obj3 x={42} />; // OK
~~~~~~
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'string | number'.
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'number'.
@@ -0,0 +1,22 @@
tests/cases/compiler/file.tsx(11,14): error TS2322: Type '{ prop: number; }' is not assignable to type 'Record<string, string>'.
Property 'prop' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
==== tests/cases/compiler/file.tsx (1 errors) ====
declare namespace JSX {
interface Element {
render(): Element | string | false;
}
}
function SFC<T>(props: Record<string, T>) {
return '';
}
<SFC<string> prop={1}></SFC>; // should error
~~~~~~~~
!!! error TS2322: Type '{ prop: number; }' is not assignable to type 'Record<string, string>'.
!!! error TS2322: Property 'prop' is incompatible with index signature.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
@@ -0,0 +1,19 @@
//// [file.tsx]
declare namespace JSX {
interface Element {
render(): Element | string | false;
}
}
function SFC<T>(props: Record<string, T>) {
return '';
}
<SFC<string> prop={1}></SFC>; // should error
//// [file.jsx]
function SFC(props) {
return '';
}
<SFC prop={1}></SFC>; // should error
@@ -0,0 +1,28 @@
=== tests/cases/compiler/file.tsx ===
declare namespace JSX {
>JSX : Symbol(JSX, Decl(file.tsx, 0, 0))
interface Element {
>Element : Symbol(Element, Decl(file.tsx, 0, 23))
render(): Element | string | false;
>render : Symbol(Element.render, Decl(file.tsx, 1, 23))
>Element : Symbol(Element, Decl(file.tsx, 0, 23))
}
}
function SFC<T>(props: Record<string, T>) {
>SFC : Symbol(SFC, Decl(file.tsx, 4, 1))
>T : Symbol(T, Decl(file.tsx, 6, 13))
>props : Symbol(props, Decl(file.tsx, 6, 16))
>Record : Symbol(Record, Decl(lib.d.ts, --, --))
>T : Symbol(T, Decl(file.tsx, 6, 13))
return '';
}
<SFC<string> prop={1}></SFC>; // should error
>SFC : Symbol(SFC, Decl(file.tsx, 4, 1))
>prop : Symbol(prop, Decl(file.tsx, 10, 12))
>SFC : Symbol(SFC, Decl(file.tsx, 4, 1))
@@ -0,0 +1,32 @@
=== tests/cases/compiler/file.tsx ===
declare namespace JSX {
>JSX : any
interface Element {
>Element : Element
render(): Element | string | false;
>render : () => string | false | Element
>Element : Element
>false : false
}
}
function SFC<T>(props: Record<string, T>) {
>SFC : <T>(props: Record<string, T>) => string
>T : T
>props : Record<string, T>
>Record : Record<K, T>
>T : T
return '';
>'' : ""
}
<SFC<string> prop={1}></SFC>; // should error
><SFC<string> prop={1}></SFC> : JSX.Element
>SFC : <T>(props: Record<string, T>) => string
>prop : number
>1 : 1
>SFC : <T>(props: Record<string, T>) => string
@@ -0,0 +1,42 @@
tests/cases/conformance/jsx/tsxTypeErrors.tsx(25,19): error TS2339: Property 'reqd' does not exist on type '{}'.
tests/cases/conformance/jsx/tsxTypeErrors.tsx(31,19): error TS2339: Property 'pt' does not exist on type '{}'.
==== tests/cases/conformance/jsx/tsxTypeErrors.tsx (2 errors) ====
// A built-in element (OK)
var a1 = <div id="foo" />;
// A built-in element with a mistyped property (error)
var a2 = <img srce="foo.jpg" />
// A built-in element with a badly-typed attribute value (error)
var thing = { oops: 100 };
var a3 = <div id={thing} />
// Mistyped html name (error)
var e1 = <imag src="bar.jpg" />
// A custom type
class MyClass {
props: {
pt?: { x: number; y: number; };
name?: string;
reqd: boolean;
}
}
// Let's use it
// TODO: Error on missing 'reqd'
var b1 = <MyClass reqd={true} />;
~~~~~~~~~~~
!!! error TS2339: Property 'reqd' does not exist on type '{}'.
// Mistyped attribute member
// sample.tsx(23,22): error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'.
// Types of property 'y' are incompatible.
// Type 'string' is not assignable to type 'number'.
var b2 = <MyClass pt={{x: 4, y: 'oops'}} />;
~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2339: Property 'pt' does not exist on type '{}'.
@@ -0,0 +1,13 @@
// @jsx: preserve
// @filename: file.tsx
declare namespace JSX {
interface Element {
render(): Element | string | false;
}
}
function SFC<T>(props: Record<string, T>) {
return '';
}
<SFC<string> prop={1}></SFC>; // should error