diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bdb186ad3ed..3b007bccccf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11866,11 +11866,11 @@ namespace ts { } /** - * Get attributes symbol of the given Jsx opening-like element. The result is from resolving "attributes" property of the opening-like element. + * Get attributes type of the given Jsx opening-like element. The result is from resolving "attributes" property of the opening-like element. * @param openingLikeElement a Jsx opening-like element - * @return a symbol table resulted from resolving "attributes" property or undefined if any of the attribute resolved to any or there is no attributes. + * @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property. */ - function getJsxAttributeSymbolsFromJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement): Symbol[] | undefined { + function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, filter?:(symbol: Symbol)=>boolean) { const attributes = openingLikeElement.attributes; let attributesTable = createMap(); let spread: Type = emptyObjectType; @@ -11903,10 +11903,10 @@ namespace ts { const exprType = checkExpression(attributeDecl.expression); if (!(exprType.flags & (TypeFlags.Object | TypeFlags.Any))) { error(attributeDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types); - return undefined; + return anyType; } if (isTypeAny(exprType)) { - return undefined; + return anyType; } spread = getSpreadType(spread, exprType); } @@ -11921,38 +11921,38 @@ namespace ts { attributesArray = getPropertiesOfType(spread); } - return attributesArray; + attributesTable = createMap(); + if (attributesArray) { + forEach(attributesArray, (attr) => { + if (!filter || (filter && filter(attr))) { + attributesTable[attr.name] = attr; + } + }); + } + return createJsxAttributesType(attributes.symbol, attributesTable); + + /** + * Create anonymous type from given attributes symbol table. + * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable + * @param attributesTable a symbol table of attributes property + */ + function createJsxAttributesType(symbol: Symbol, attributesTable: Map) { + const result = createAnonymousType(symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); + const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral; + result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag; + result.objectFlags |= ObjectFlags.ObjectLiteral; + return result; + } } /** - * Create anonymous type from given attributes symbol table. - * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable - * @param attributesTable a symbol table of attributes property - */ - function createJsxAttributesType(symbol: Symbol, attributesTable: Map) { - const result = createAnonymousType(symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); - const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral; - result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag; - result.objectFlags |= ObjectFlags.ObjectLiteral; - return result; - } - - /** - * Check JSXAttributes. This function is used when we are trying to figure out call signature for JSX opening-like element. - * In "checkApplicableSignatureForJsxOpeningLikeElement", we get type of arguments by checking the JSX opening-like element attributes property with contextual type. + * Check JSXAttributes from "attributes" property. This function is used when we are trying to figure out call signature for JSX opening-like element during chooseOverload + * In "checkApplicableSignatureForJsxOpeningLikeElement", we get type of arguments for potential stateless function by checking + * the JSX opening-like element attributes property with contextual type. * @param node a JSXAttributes to be resolved of its type */ function checkJsxAttributes(node: JsxAttributes) { - const symbolArray = getJsxAttributeSymbolsFromJsxOpeningLikeElement(node.parent as JsxOpeningLikeElement); - let argAttributesType = anyType as Type; - if (symbolArray) { - const symbolTable = createMap(); - forEach(symbolArray, (attr) => { - symbolTable.set(attr.name, attr); - }); - argAttributesType = createJsxAttributesType(node.symbol, symbolTable); - } - return argAttributesType; + return createJsxAttributesTypeFromAttributesProperty(node.parent as JsxOpeningLikeElement); } /** @@ -11962,33 +11962,28 @@ namespace ts { * @param openingLikeElement an opening-like JSX element to check its JSXAttributes */ function checkJsxAttributesAssignableToTagnameAttributes(openingLikeElement: JsxOpeningLikeElement) { + // The function involves following steps: + // 1. Figure out expected attributes type expected by resolving tag-name of the JSX opening-like element, tagetAttributesType. + // During these steps, we will try to resolve the tag-name as intrinsic name, stateless function, stateful component (in the order) + // 2. Solved Jsx attributes type given by users, sourceAttributesType, which is by resolving "attributes" property of the JSX opening-like element. + // 3. Check if the two are assignable to each other + // targetAttributesType is a type of an attributes from resolving tag-name of an opening-like JSX element. const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ? getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) : getCustomJsxElementAttributesType(openingLikeElement, /*shouldIncludeAllStatelessAttributesType*/ false); - const symbolArray = getJsxAttributeSymbolsFromJsxOpeningLikeElement(openingLikeElement); // sourceAttributesType is a type of an attributes properties. // i.e
// attr1 and attr2 are treated as JSXAttributes attached in the JsxOpeningLikeElement as "attributes". They resolved to be sourceAttributesType. - let sourceAttributesType = anyType as Type; - let isSourceAttributesTypeEmpty = true; - if (symbolArray) { - // Filter out any hyphenated names as those do not play any role in type-checking unless there are corresponding properties in the target type - const symbolTable = createMap(); - forEach(symbolArray, (attr) => { - if (isUnhyphenatedJsxName(attr.name) || getPropertyOfType(targetAttributesType, attr.name)) { - symbolTable.set(attr.name, attr); - isSourceAttributesTypeEmpty = false; - } + const sourceAttributesType = createJsxAttributesTypeFromAttributesProperty(openingLikeElement, + (attribute: Symbol) => { + return isUnhyphenatedJsxName(attribute.name) || !!(getPropertyOfType(targetAttributesType, attribute.name)); }); - sourceAttributesType = createJsxAttributesType(openingLikeElement.attributes.symbol, symbolTable); - } - // 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) && !isSourceAttributesTypeEmpty) { + if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || (sourceAttributesType).properties.length > 0)) { error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, getJsxElementPropertiesName()); } else { diff --git a/tests/baselines/reference/tsxElementResolution12.errors.txt b/tests/baselines/reference/tsxElementResolution12.errors.txt index 5c4c9baf9cd..e55ec837332 100644 --- a/tests/baselines/reference/tsxElementResolution12.errors.txt +++ b/tests/baselines/reference/tsxElementResolution12.errors.txt @@ -1,10 +1,11 @@ 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(30,7): error TS2322: Type '{ x: "10"; }' is not assignable to type '{ x: number; }'. +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(33,7): error TS2322: Type '{ x: "10"; }' is not assignable to type '{ x: number; }'. Types of property 'x' are incompatible. Type '"10"' is not assignable to type 'number'. -==== tests/cases/conformance/jsx/file.tsx (2 errors) ==== +==== tests/cases/conformance/jsx/file.tsx (3 errors) ==== declare module JSX { interface Element { } interface ElementAttributesProperty { pr: any; } @@ -30,6 +31,11 @@ tests/cases/conformance/jsx/file.tsx(30,7): error TS2322: Type '{ x: "10"; }' is ; // Error ~~~~~~~~~~~~~~~ !!! error TS2607: JSX element class does not support attributes because it does not have a 'pr' property + var attributes: any; + ; // Error + ~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2607: JSX element class does not support attributes because it does not have a 'pr' property + ; // OK interface Obj4type { new(n: string): { x: number; pr: { x: number; } }; diff --git a/tests/baselines/reference/tsxElementResolution12.js b/tests/baselines/reference/tsxElementResolution12.js index b01266aa789..a4d308fefe6 100644 --- a/tests/baselines/reference/tsxElementResolution12.js +++ b/tests/baselines/reference/tsxElementResolution12.js @@ -22,6 +22,9 @@ interface Obj3type { } var Obj3: Obj3type; ; // Error +var attributes: any; +; // Error +; // OK interface Obj4type { new(n: string): { x: number; pr: { x: number; } }; @@ -38,6 +41,9 @@ var Obj2; ; // OK var Obj3; ; // Error +var attributes; +; // Error +; // OK var Obj4; ; // OK ; // Error diff --git a/tests/cases/conformance/jsx/tsxElementResolution12.tsx b/tests/cases/conformance/jsx/tsxElementResolution12.tsx index a4b47336ef8..daac7cfcbdf 100644 --- a/tests/cases/conformance/jsx/tsxElementResolution12.tsx +++ b/tests/cases/conformance/jsx/tsxElementResolution12.tsx @@ -23,6 +23,9 @@ interface Obj3type { } var Obj3: Obj3type; ; // Error +var attributes: any; +; // Error +; // OK interface Obj4type { new(n: string): { x: number; pr: { x: number; } };