diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index d7acedd663d..ff10bea4f97 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -1,4 +1,4 @@
-///
+///
///
/* @internal */
@@ -424,6 +424,8 @@ namespace ts {
IntrinsicClassAttributes: "IntrinsicClassAttributes"
};
+ const jsxChildrenPropertyName = "children";
+
const subtypeRelation = createMap();
const assignableRelation = createMap();
const comparableRelation = createMap();
@@ -8648,7 +8650,7 @@ namespace ts {
// is considered known if the object type is empty and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object type. In a union or intersection
// type, a property is considered known if it is known in any constituent type.
- function isKnownProperty(type: Type, name: string, isComparingJsxAttributes: boolean): boolean {
+ function isKnownProperty(type: Type, name: string, isComparingJsxAttributes: boolean, containsSynthesizedJsxChildren: boolean): boolean {
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(type);
if ((relation === assignableRelation || relation === comparableRelation) &&
@@ -8658,14 +8660,16 @@ namespace ts {
else if (resolved.stringIndexInfo || (resolved.numberIndexInfo && isNumericLiteralName(name))) {
return true;
}
- else if (getPropertyOfType(type, name) || (isComparingJsxAttributes && !isUnhyphenatedJsxName(name))) {
- // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
+ else if (getPropertyOfType(type, name) || containsSynthesizedJsxChildren || (isComparingJsxAttributes && !isUnhyphenatedJsxName(name))) {
+ // For JSXAttributes, consider that the attribute to be known if
+ // 1. the attribute has a hyphenated name
+ // 2. "children" attribute that is synthesized from children property of Jsx element
return true;
}
}
else if (type.flags & TypeFlags.UnionOrIntersection) {
for (const t of (type).types) {
- if (isKnownProperty(t, name, isComparingJsxAttributes)) {
+ if (isKnownProperty(t, name, isComparingJsxAttributes, containsSynthesizedJsxChildren)) {
return true;
}
}
@@ -8676,8 +8680,9 @@ namespace ts {
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
+ const containsSynthesizedJsxChildren = !!(source.flags & TypeFlags.ContainsSynthesizedJsxChildren);
for (const prop of getPropertiesOfObjectType(source)) {
- if (!isKnownProperty(target, prop.name, isComparingJsxAttributes)) {
+ if (!isKnownProperty(target, prop.name, isComparingJsxAttributes, containsSynthesizedJsxChildren)) {
if (reportErrors) {
// We know *exactly* where things went wrong when comparing the types.
// Use this property as the error node as this will be more helpful in
@@ -13173,21 +13178,6 @@ namespace ts {
checkExpression(node.closingElement.tagName);
}
- // Check children
- for (const child of node.children) {
- switch (child.kind) {
- case SyntaxKind.JsxExpression:
- checkJsxExpression(child);
- break;
- case SyntaxKind.JsxElement:
- checkJsxElement(child);
- break;
- case SyntaxKind.JsxSelfClosingElement:
- checkJsxSelfClosingElement(child);
- break;
- }
- }
-
return getJsxGlobalElementType() || anyType;
}
@@ -13280,6 +13270,30 @@ namespace ts {
}
});
}
+
+ // Handle children attribute
+ const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ?
+ openingLikeElement.parent as JsxElement : undefined;
+ let containsSynthesizedJsxChildren = false;
+ // Comment
+ if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) {
+ // Error if there is a attribute named "children" and children element.
+ // This is because children element will overwrite the value from attributes
+ if (attributesTable.has(jsxChildrenPropertyName)) {
+ error(attributes, Diagnostics.props_children_are_specified_twice_The_attribute_named_children_will_be_overwritten);
+ }
+
+ // If there are children in the body of JSX element, create dummy attribute "children" with anyType so that it will pass the attribute checking process
+ const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName);
+ const childrenTypes: Type[] = [];
+ for (const child of (parent as JsxElement).children) {
+ childrenTypes.push(child.kind === SyntaxKind.JsxText ? stringType : checkExpression(child));
+ }
+ childrenPropSymbol.type = getUnionType(childrenTypes, /*subtypeReduction*/ false);
+ attributesTable.set(jsxChildrenPropertyName, childrenPropSymbol);
+ containsSynthesizedJsxChildren = true;
+ }
+
return createJsxAttributesType(attributes.symbol, attributesTable);
/**
@@ -13290,7 +13304,8 @@ namespace ts {
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;
+ const containsSynthesizedJsxChildrenFlag = containsSynthesizedJsxChildren ? TypeFlags.ContainsSynthesizedJsxChildren : 0;
+ result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | containsSynthesizedJsxChildrenFlag;
result.objectFlags |= ObjectFlags.ObjectLiteral;
return result;
}
@@ -14533,7 +14548,7 @@ namespace ts {
// We can figure that out by resolving attributes property and check number of properties in the resolved type
// If the call has correct arity, we will then check if the argument type and parameter type is assignable
- const callIsIncomplete = node.attributes.end === node.end; // If we are missing the close "/>", the call is incomplete
+ const callIsIncomplete = node.attributes.end === node.end; // If we are missing the close "/>", the call is incoplete
if (callIsIncomplete) {
return true;
}
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index 075d4247460..72554a9f0ba 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -2099,6 +2099,10 @@
"category": "Error",
"code": 2707
},
+ "props.children are specified twice. The attribute named 'children' will be overwritten.": {
+ "category": "Error",
+ "code": 2708
+ },
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 354ef9d8b10..fd246fed821 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -1,4 +1,4 @@
-namespace ts {
+namespace ts {
/**
* Type of objects whose values are all of the same type.
* The `in` and `for-in` operators can *not* be safely used,
@@ -10,7 +10,7 @@ namespace ts {
/** ES6 Map interface. */
export interface Map {
- get(key: string): T;
+ get(key: string): T | undefined;
has(key: string): boolean;
set(key: string, value: T): this;
delete(key: string): boolean;
@@ -2975,6 +2975,8 @@ namespace ts {
NonPrimitive = 1 << 24, // intrinsic object type
/* @internal */
JsxAttributes = 1 << 25, // Jsx attributes type
+ /* @internal */
+ ContainsSynthesizedJsxChildren = 1 << 26, // Jsx attributes type contains synthesized children property from Jsx element's children
/* @internal */
Nullable = Undefined | Null,