Skip to content

Commit 747ab05

Browse files
author
Kanchalai Tanglertsampan
committed
Refactor getJsxAttributeSymbolsFromJsxOpeningLikeElement to createJsxAttributesTypeFromAttributesProperty
1 parent 6ce31d7 commit 747ab05

File tree

4 files changed

+57
-47
lines changed

4 files changed

+57
-47
lines changed

src/compiler/checker.ts

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11866,11 +11866,11 @@ namespace ts {
1186611866
}
1186711867

1186811868
/**
11869-
* Get attributes symbol of the given Jsx opening-like element. The result is from resolving "attributes" property of the opening-like element.
11869+
* Get attributes type of the given Jsx opening-like element. The result is from resolving "attributes" property of the opening-like element.
1187011870
* @param openingLikeElement a Jsx opening-like element
11871-
* @return a symbol table resulted from resolving "attributes" property or undefined if any of the attribute resolved to any or there is no attributes.
11871+
* @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property.
1187211872
*/
11873-
function getJsxAttributeSymbolsFromJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement): Symbol[] | undefined {
11873+
function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, filter?:(symbol: Symbol)=>boolean) {
1187411874
const attributes = openingLikeElement.attributes;
1187511875
let attributesTable = createMap<Symbol>();
1187611876
let spread: Type = emptyObjectType;
@@ -11903,10 +11903,10 @@ namespace ts {
1190311903
const exprType = checkExpression(attributeDecl.expression);
1190411904
if (!(exprType.flags & (TypeFlags.Object | TypeFlags.Any))) {
1190511905
error(attributeDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types);
11906-
return undefined;
11906+
return anyType;
1190711907
}
1190811908
if (isTypeAny(exprType)) {
11909-
return undefined;
11909+
return anyType;
1191011910
}
1191111911
spread = getSpreadType(spread, exprType);
1191211912
}
@@ -11921,38 +11921,38 @@ namespace ts {
1192111921
attributesArray = getPropertiesOfType(spread);
1192211922
}
1192311923

11924-
return attributesArray;
11925-
}
11924+
attributesTable = createMap<Symbol>();
11925+
if (attributesArray) {
11926+
forEach(attributesArray, (attr) => {
11927+
if (!filter || (filter && filter(attr))) {
11928+
attributesTable[attr.name] = attr;
11929+
}
11930+
});
11931+
}
11932+
return createJsxAttributesType(attributes.symbol, attributesTable);
1192611933

11927-
/**
11928-
* Create anonymous type from given attributes symbol table.
11929-
* @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable
11930-
* @param attributesTable a symbol table of attributes property
11931-
*/
11932-
function createJsxAttributesType(symbol: Symbol, attributesTable: Map<Symbol>) {
11933-
const result = createAnonymousType(symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
11934-
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral;
11935-
result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag;
11936-
result.objectFlags |= ObjectFlags.ObjectLiteral;
11937-
return result;
11934+
/**
11935+
* Create anonymous type from given attributes symbol table.
11936+
* @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable
11937+
* @param attributesTable a symbol table of attributes property
11938+
*/
11939+
function createJsxAttributesType(symbol: Symbol, attributesTable: Map<Symbol>) {
11940+
const result = createAnonymousType(symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
11941+
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral;
11942+
result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag;
11943+
result.objectFlags |= ObjectFlags.ObjectLiteral;
11944+
return result;
11945+
}
1193811946
}
1193911947

1194011948
/**
11941-
* Check JSXAttributes. This function is used when we are trying to figure out call signature for JSX opening-like element.
11942-
* In "checkApplicableSignatureForJsxOpeningLikeElement", we get type of arguments by checking the JSX opening-like element attributes property with contextual type.
11949+
* 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
11950+
* In "checkApplicableSignatureForJsxOpeningLikeElement", we get type of arguments for potential stateless function by checking
11951+
* the JSX opening-like element attributes property with contextual type.
1194311952
* @param node a JSXAttributes to be resolved of its type
1194411953
*/
1194511954
function checkJsxAttributes(node: JsxAttributes) {
11946-
const symbolArray = getJsxAttributeSymbolsFromJsxOpeningLikeElement(node.parent as JsxOpeningLikeElement);
11947-
let argAttributesType = anyType as Type;
11948-
if (symbolArray) {
11949-
const symbolTable = createMap<Symbol>();
11950-
forEach(symbolArray, (attr) => {
11951-
symbolTable.set(attr.name, attr);
11952-
});
11953-
argAttributesType = createJsxAttributesType(node.symbol, symbolTable);
11954-
}
11955-
return argAttributesType;
11955+
return createJsxAttributesTypeFromAttributesProperty(node.parent as JsxOpeningLikeElement);
1195611956
}
1195711957

1195811958
/**
@@ -11962,33 +11962,28 @@ namespace ts {
1196211962
* @param openingLikeElement an opening-like JSX element to check its JSXAttributes
1196311963
*/
1196411964
function checkJsxAttributesAssignableToTagnameAttributes(openingLikeElement: JsxOpeningLikeElement) {
11965+
// The function involves following steps:
11966+
// 1. Figure out expected attributes type expected by resolving tag-name of the JSX opening-like element, tagetAttributesType.
11967+
// During these steps, we will try to resolve the tag-name as intrinsic name, stateless function, stateful component (in the order)
11968+
// 2. Solved Jsx attributes type given by users, sourceAttributesType, which is by resolving "attributes" property of the JSX opening-like element.
11969+
// 3. Check if the two are assignable to each other
11970+
1196511971
// targetAttributesType is a type of an attributes from resolving tag-name of an opening-like JSX element.
1196611972
const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ?
1196711973
getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) :
1196811974
getCustomJsxElementAttributesType(openingLikeElement, /*shouldIncludeAllStatelessAttributesType*/ false);
1196911975

11970-
const symbolArray = getJsxAttributeSymbolsFromJsxOpeningLikeElement(openingLikeElement);
1197111976
// sourceAttributesType is a type of an attributes properties.
1197211977
// i.e <div attr1={10} attr2="string" />
1197311978
// attr1 and attr2 are treated as JSXAttributes attached in the JsxOpeningLikeElement as "attributes". They resolved to be sourceAttributesType.
11974-
let sourceAttributesType = anyType as Type;
11975-
let isSourceAttributesTypeEmpty = true;
11976-
if (symbolArray) {
11977-
// Filter out any hyphenated names as those do not play any role in type-checking unless there are corresponding properties in the target type
11978-
const symbolTable = createMap<Symbol>();
11979-
forEach(symbolArray, (attr) => {
11980-
if (isUnhyphenatedJsxName(attr.name) || getPropertyOfType(targetAttributesType, attr.name)) {
11981-
symbolTable.set(attr.name, attr);
11982-
isSourceAttributesTypeEmpty = false;
11983-
}
11979+
const sourceAttributesType = createJsxAttributesTypeFromAttributesProperty(openingLikeElement,
11980+
(attribute: Symbol) => {
11981+
return isUnhyphenatedJsxName(attribute.name) || !!(getPropertyOfType(targetAttributesType, attribute.name));
1198411982
});
1198511983

11986-
sourceAttributesType = createJsxAttributesType(openingLikeElement.attributes.symbol, symbolTable);
11987-
}
11988-
1198911984
// If the targetAttributesType is an emptyObjectType, indicating that there is no property named 'props' on this instance type.
1199011985
// but there exists a sourceAttributesType, we need to explicitly give an error as normal assignability check allow excess properties and will pass.
11991-
if (targetAttributesType === emptyObjectType && !isTypeAny(sourceAttributesType) && !isSourceAttributesTypeEmpty) {
11986+
if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || (<ResolvedType>sourceAttributesType).properties.length > 0)) {
1199211987
error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, getJsxElementPropertiesName());
1199311988
}
1199411989
else {

tests/baselines/reference/tsxElementResolution12.errors.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
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
2-
tests/cases/conformance/jsx/file.tsx(30,7): error TS2322: Type '{ x: "10"; }' is not assignable to type '{ x: number; }'.
2+
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
3+
tests/cases/conformance/jsx/file.tsx(33,7): error TS2322: Type '{ x: "10"; }' is not assignable to type '{ x: number; }'.
34
Types of property 'x' are incompatible.
45
Type '"10"' is not assignable to type 'number'.
56

67

7-
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
8+
==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
89
declare module JSX {
910
interface Element { }
1011
interface ElementAttributesProperty { pr: any; }
@@ -30,6 +31,11 @@ tests/cases/conformance/jsx/file.tsx(30,7): error TS2322: Type '{ x: "10"; }' is
3031
<Obj3 x={10} />; // Error
3132
~~~~~~~~~~~~~~~
3233
!!! error TS2607: JSX element class does not support attributes because it does not have a 'pr' property
34+
var attributes: any;
35+
<Obj3 {...attributes} />; // Error
36+
~~~~~~~~~~~~~~~~~~~~~~~~
37+
!!! error TS2607: JSX element class does not support attributes because it does not have a 'pr' property
38+
<Obj3 {...{}} />; // OK
3339

3440
interface Obj4type {
3541
new(n: string): { x: number; pr: { x: number; } };

tests/baselines/reference/tsxElementResolution12.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ interface Obj3type {
2222
}
2323
var Obj3: Obj3type;
2424
<Obj3 x={10} />; // Error
25+
var attributes: any;
26+
<Obj3 {...attributes} />; // Error
27+
<Obj3 {...{}} />; // OK
2528

2629
interface Obj4type {
2730
new(n: string): { x: number; pr: { x: number; } };
@@ -38,6 +41,9 @@ var Obj2;
3841
<Obj2 x={10}/>; // OK
3942
var Obj3;
4043
<Obj3 x={10}/>; // Error
44+
var attributes;
45+
<Obj3 {...attributes}/>; // Error
46+
<Obj3 {...{}}/>; // OK
4147
var Obj4;
4248
<Obj4 x={10}/>; // OK
4349
<Obj4 x={'10'}/>; // Error

tests/cases/conformance/jsx/tsxElementResolution12.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ interface Obj3type {
2323
}
2424
var Obj3: Obj3type;
2525
<Obj3 x={10} />; // Error
26+
var attributes: any;
27+
<Obj3 {...attributes} />; // Error
28+
<Obj3 {...{}} />; // OK
2629

2730
interface Obj4type {
2831
new(n: string): { x: number; pr: { x: number; } };

0 commit comments

Comments
 (0)