@@ -8690,29 +8690,6 @@ namespace ts {
8690
8690
return Ternary.False;
8691
8691
}
8692
8692
8693
- // Check if a property with the given name is known anywhere in the given type. In an object type, a property
8694
- // is considered known if the object type is empty and the check is for assignability, if the object type has
8695
- // index signatures, or if the property is actually declared in the object type. In a union or intersection
8696
- // type, a property is considered known if it is known in any constituent type.
8697
- function isKnownProperty(type: Type, name: string, isComparingJsxAttributes: boolean): boolean {
8698
- if (type.flags & TypeFlags.Object) {
8699
- const resolved = resolveStructuredTypeMembers(<ObjectType>type);
8700
- if (resolved.stringIndexInfo || resolved.numberIndexInfo && isNumericLiteralName(name) ||
8701
- getPropertyOfType(type, name) || isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
8702
- // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
8703
- return true;
8704
- }
8705
- }
8706
- else if (type.flags & TypeFlags.UnionOrIntersection) {
8707
- for (const t of (<UnionOrIntersectionType>type).types) {
8708
- if (isKnownProperty(t, name, isComparingJsxAttributes)) {
8709
- return true;
8710
- }
8711
- }
8712
- }
8713
- return false;
8714
- }
8715
-
8716
8693
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
8717
8694
if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
8718
8695
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
@@ -13276,6 +13253,8 @@ namespace ts {
13276
13253
let spread: Type = emptyObjectType;
13277
13254
let attributesArray: Symbol[] = [];
13278
13255
let hasSpreadAnyType = false;
13256
+ let explicitlySpecifyChildrenAttribute = false;
13257
+ const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();
13279
13258
13280
13259
for (const attributeDecl of attributes.properties) {
13281
13260
const member = attributeDecl.symbol;
@@ -13294,6 +13273,9 @@ namespace ts {
13294
13273
attributeSymbol.target = member;
13295
13274
attributesTable.set(attributeSymbol.name, attributeSymbol);
13296
13275
attributesArray.push(attributeSymbol);
13276
+ if (attributeDecl.name.text === jsxChildrenPropertyName) {
13277
+ explicitlySpecifyChildrenAttribute = true;
13278
+ }
13297
13279
}
13298
13280
else {
13299
13281
Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute);
@@ -13352,11 +13334,11 @@ namespace ts {
13352
13334
}
13353
13335
}
13354
13336
13355
- // Error if there is a attribute named "children" and children element.
13356
- // This is because children element will overwrite the value from attributes
13357
- const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();
13358
13337
if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
13359
- if (attributesTable.has(jsxChildrenPropertyName)) {
13338
+ // Error if there is a attribute named "children" explicitly specified and children element.
13339
+ // This is because children element will overwrite the value from attributes.
13340
+ // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread.
13341
+ if (explicitlySpecifyChildrenAttribute) {
13360
13342
error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, jsxChildrenPropertyName);
13361
13343
}
13362
13344
@@ -13378,8 +13360,7 @@ namespace ts {
13378
13360
*/
13379
13361
function createJsxAttributesType(symbol: Symbol, attributesTable: Map<Symbol>) {
13380
13362
const result = createAnonymousType(symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
13381
- const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral;
13382
- result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag;
13363
+ result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral;
13383
13364
result.objectFlags |= ObjectFlags.ObjectLiteral;
13384
13365
return result;
13385
13366
}
@@ -13898,6 +13879,34 @@ namespace ts {
13898
13879
checkJsxAttributesAssignableToTagNameAttributes(node);
13899
13880
}
13900
13881
13882
+ /**
13883
+ * Check if a property with the given name is known anywhere in the given type. In an object type, a property
13884
+ * is considered known if the object type is empty and the check is for assignability, if the object type has
13885
+ * index signatures, or if the property is actually declared in the object type. In a union or intersection
13886
+ * type, a property is considered known if it is known in any constituent type.
13887
+ * @param targetType a type to search a given name in
13888
+ * @param name a property name to search
13889
+ * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType
13890
+ */
13891
+ function isKnownProperty(targetType: Type, name: string, isComparingJsxAttributes: boolean): boolean {
13892
+ if (targetType.flags & TypeFlags.Object) {
13893
+ const resolved = resolveStructuredTypeMembers(<ObjectType>targetType);
13894
+ if (resolved.stringIndexInfo || resolved.numberIndexInfo && isNumericLiteralName(name) ||
13895
+ getPropertyOfType(targetType, name) || isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
13896
+ // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
13897
+ return true;
13898
+ }
13899
+ }
13900
+ else if (targetType.flags & TypeFlags.UnionOrIntersection) {
13901
+ for (const t of (<UnionOrIntersectionType>targetType).types) {
13902
+ if (isKnownProperty(t, name, isComparingJsxAttributes)) {
13903
+ return true;
13904
+ }
13905
+ }
13906
+ }
13907
+ return false;
13908
+ }
13909
+
13901
13910
/**
13902
13911
* Check whether the given attributes of JSX opening-like element is assignable to the tagName attributes.
13903
13912
* Get the attributes type of the opening-like element through resolving the tagName, "target attributes"
@@ -13930,7 +13939,19 @@ namespace ts {
13930
13939
error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, getJsxElementPropertiesName());
13931
13940
}
13932
13941
else {
13933
- checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.attributes.properties.length > 0 ? openingLikeElement.attributes : openingLikeElement);
13942
+ // Check if sourceAttributesType assignable to targetAttributesType though this check will allow excess properties
13943
+ const isSourceAttributeTypeAssignableToTarget = checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.attributes.properties.length > 0 ? openingLikeElement.attributes : openingLikeElement);
13944
+ // After we check for assignability, we will do another pass to check that all explicitly specified attributes have correct name corresponding in targetAttributeType.
13945
+ // This will allow excess properties in spread type as it is very common pattern to spread outter attributes into React component in its render method.
13946
+ if (isSourceAttributeTypeAssignableToTarget && !isTypeAny(sourceAttributesType) && !isTypeAny(targetAttributesType)) {
13947
+ for (const attribute of openingLikeElement.attributes.properties) {
13948
+ if (isJsxAttribute(attribute) && !isKnownProperty(targetAttributesType, attribute.name.text, /*isComparingJsxAttributes*/ true)) {
13949
+ error(attribute, Diagnostics.Property_0_does_not_exist_on_type_1, attribute.name.text, typeToString(targetAttributesType));
13950
+ // We break here so that errors won't be cascading
13951
+ break;
13952
+ }
13953
+ }
13954
+ }
13934
13955
}
13935
13956
}
13936
13957
0 commit comments