@@ -8667,29 +8667,6 @@ namespace ts {
8667
8667
return Ternary.False;
8668
8668
}
8669
8669
8670
- // Check if a property with the given name is known anywhere in the given type. In an object type, a property
8671
- // is considered known if the object type is empty and the check is for assignability, if the object type has
8672
- // index signatures, or if the property is actually declared in the object type. In a union or intersection
8673
- // type, a property is considered known if it is known in any constituent type.
8674
- function isKnownProperty(type: Type, name: string, isComparingJsxAttributes: boolean): boolean {
8675
- if (type.flags & TypeFlags.Object) {
8676
- const resolved = resolveStructuredTypeMembers(<ObjectType>type);
8677
- if (resolved.stringIndexInfo || resolved.numberIndexInfo && isNumericLiteralName(name) ||
8678
- getPropertyOfType(type, name) || isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
8679
- // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
8680
- return true;
8681
- }
8682
- }
8683
- else if (type.flags & TypeFlags.UnionOrIntersection) {
8684
- for (const t of (<UnionOrIntersectionType>type).types) {
8685
- if (isKnownProperty(t, name, isComparingJsxAttributes)) {
8686
- return true;
8687
- }
8688
- }
8689
- }
8690
- return false;
8691
- }
8692
-
8693
8670
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
8694
8671
if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
8695
8672
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
@@ -13226,6 +13203,8 @@ namespace ts {
13226
13203
let spread: Type = emptyObjectType;
13227
13204
let attributesArray: Symbol[] = [];
13228
13205
let hasSpreadAnyType = false;
13206
+ let explicitlySpecifyChildrenAttribute = false;
13207
+ const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();
13229
13208
13230
13209
for (const attributeDecl of attributes.properties) {
13231
13210
const member = attributeDecl.symbol;
@@ -13244,6 +13223,9 @@ namespace ts {
13244
13223
attributeSymbol.target = member;
13245
13224
attributesTable.set(attributeSymbol.name, attributeSymbol);
13246
13225
attributesArray.push(attributeSymbol);
13226
+ if (attributeDecl.name.text === jsxChildrenPropertyName) {
13227
+ explicitlySpecifyChildrenAttribute = true;
13228
+ }
13247
13229
}
13248
13230
else {
13249
13231
Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute);
@@ -13302,11 +13284,11 @@ namespace ts {
13302
13284
}
13303
13285
}
13304
13286
13305
- // Error if there is a attribute named "children" and children element.
13306
- // This is because children element will overwrite the value from attributes
13307
- const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();
13308
13287
if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
13309
- if (attributesTable.has(jsxChildrenPropertyName)) {
13288
+ // Error if there is a attribute named "children" explicitly specified and children element.
13289
+ // This is because children element will overwrite the value from attributes.
13290
+ // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread.
13291
+ if (explicitlySpecifyChildrenAttribute) {
13310
13292
error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, jsxChildrenPropertyName);
13311
13293
}
13312
13294
@@ -13328,8 +13310,7 @@ namespace ts {
13328
13310
*/
13329
13311
function createJsxAttributesType(symbol: Symbol, attributesTable: Map<Symbol>) {
13330
13312
const result = createAnonymousType(symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
13331
- const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral;
13332
- result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag;
13313
+ result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral;
13333
13314
result.objectFlags |= ObjectFlags.ObjectLiteral;
13334
13315
return result;
13335
13316
}
@@ -13848,6 +13829,34 @@ namespace ts {
13848
13829
checkJsxAttributesAssignableToTagNameAttributes(node);
13849
13830
}
13850
13831
13832
+ /**
13833
+ * Check if a property with the given name is known anywhere in the given type. In an object type, a property
13834
+ * is considered known if the object type is empty and the check is for assignability, if the object type has
13835
+ * index signatures, or if the property is actually declared in the object type. In a union or intersection
13836
+ * type, a property is considered known if it is known in any constituent type.
13837
+ * @param targetType a type to search a given name in
13838
+ * @param name a property name to search
13839
+ * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType
13840
+ */
13841
+ function isKnownProperty(targetType: Type, name: string, isComparingJsxAttributes: boolean): boolean {
13842
+ if (targetType.flags & TypeFlags.Object) {
13843
+ const resolved = resolveStructuredTypeMembers(<ObjectType>targetType);
13844
+ if (resolved.stringIndexInfo || resolved.numberIndexInfo && isNumericLiteralName(name) ||
13845
+ getPropertyOfType(targetType, name) || isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
13846
+ // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
13847
+ return true;
13848
+ }
13849
+ }
13850
+ else if (targetType.flags & TypeFlags.UnionOrIntersection) {
13851
+ for (const t of (<UnionOrIntersectionType>targetType).types) {
13852
+ if (isKnownProperty(t, name, isComparingJsxAttributes)) {
13853
+ return true;
13854
+ }
13855
+ }
13856
+ }
13857
+ return false;
13858
+ }
13859
+
13851
13860
/**
13852
13861
* Check whether the given attributes of JSX opening-like element is assignable to the tagName attributes.
13853
13862
* Get the attributes type of the opening-like element through resolving the tagName, "target attributes"
@@ -13880,7 +13889,19 @@ namespace ts {
13880
13889
error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, getJsxElementPropertiesName());
13881
13890
}
13882
13891
else {
13883
- checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.attributes.properties.length > 0 ? openingLikeElement.attributes : openingLikeElement);
13892
+ // Check if sourceAttributesType assignable to targetAttributesType though this check will allow excess properties
13893
+ const isSourceAttributeTypeAssignableToTarget = checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.attributes.properties.length > 0 ? openingLikeElement.attributes : openingLikeElement);
13894
+ // After we check for assignability, we will do another pass to check that all explicitly specified attributes have correct name corresponding in targetAttributeType.
13895
+ // 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.
13896
+ if (isSourceAttributeTypeAssignableToTarget && !isTypeAny(sourceAttributesType) && !isTypeAny(targetAttributesType)) {
13897
+ for (const attribute of openingLikeElement.attributes.properties) {
13898
+ if (isJsxAttribute(attribute) && !isKnownProperty(targetAttributesType, attribute.name.text, /*isComparingJsxAttributes*/ true)) {
13899
+ error(attribute, Diagnostics.Property_0_does_not_exist_on_type_1, attribute.name.text, typeToString(targetAttributesType));
13900
+ // We break here so that errors won't be cascading
13901
+ break;
13902
+ }
13903
+ }
13904
+ }
13884
13905
}
13885
13906
}
13886
13907
0 commit comments