Skip to content

Commit b3846bf

Browse files
author
Kanchalai Tanglertsampan
committed
Wip - type checking JSX children
1 parent 3029b8f commit b3846bf

File tree

3 files changed

+46
-25
lines changed

3 files changed

+46
-25
lines changed

src/compiler/checker.ts

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// <reference path="moduleNameResolver.ts"/>
1+
/// <reference path="moduleNameResolver.ts"/>
22
/// <reference path="binder.ts"/>
33

44
/* @internal */
@@ -424,6 +424,8 @@ namespace ts {
424424
IntrinsicClassAttributes: "IntrinsicClassAttributes"
425425
};
426426

427+
const jsxChildrenPropertyName = "children";
428+
427429
const subtypeRelation = createMap<RelationComparisonResult>();
428430
const assignableRelation = createMap<RelationComparisonResult>();
429431
const comparableRelation = createMap<RelationComparisonResult>();
@@ -8648,7 +8650,7 @@ namespace ts {
86488650
// is considered known if the object type is empty and the check is for assignability, if the object type has
86498651
// index signatures, or if the property is actually declared in the object type. In a union or intersection
86508652
// type, a property is considered known if it is known in any constituent type.
8651-
function isKnownProperty(type: Type, name: string, isComparingJsxAttributes: boolean): boolean {
8653+
function isKnownProperty(type: Type, name: string, isComparingJsxAttributes: boolean, containsSynthesizedJsxChildren: boolean): boolean {
86528654
if (type.flags & TypeFlags.Object) {
86538655
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
86548656
if ((relation === assignableRelation || relation === comparableRelation) &&
@@ -8658,14 +8660,16 @@ namespace ts {
86588660
else if (resolved.stringIndexInfo || (resolved.numberIndexInfo && isNumericLiteralName(name))) {
86598661
return true;
86608662
}
8661-
else if (getPropertyOfType(type, name) || (isComparingJsxAttributes && !isUnhyphenatedJsxName(name))) {
8662-
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
8663+
else if (getPropertyOfType(type, name) || containsSynthesizedJsxChildren || (isComparingJsxAttributes && !isUnhyphenatedJsxName(name))) {
8664+
// For JSXAttributes, consider that the attribute to be known if
8665+
// 1. the attribute has a hyphenated name
8666+
// 2. "children" attribute that is synthesized from children property of Jsx element
86638667
return true;
86648668
}
86658669
}
86668670
else if (type.flags & TypeFlags.UnionOrIntersection) {
86678671
for (const t of (<UnionOrIntersectionType>type).types) {
8668-
if (isKnownProperty(t, name, isComparingJsxAttributes)) {
8672+
if (isKnownProperty(t, name, isComparingJsxAttributes, containsSynthesizedJsxChildren)) {
86698673
return true;
86708674
}
86718675
}
@@ -8676,8 +8680,9 @@ namespace ts {
86768680
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
86778681
if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
86788682
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
8683+
const containsSynthesizedJsxChildren = !!(source.flags & TypeFlags.ContainsSynthesizedJsxChildren);
86798684
for (const prop of getPropertiesOfObjectType(source)) {
8680-
if (!isKnownProperty(target, prop.name, isComparingJsxAttributes)) {
8685+
if (!isKnownProperty(target, prop.name, isComparingJsxAttributes, containsSynthesizedJsxChildren)) {
86818686
if (reportErrors) {
86828687
// We know *exactly* where things went wrong when comparing the types.
86838688
// Use this property as the error node as this will be more helpful in
@@ -13173,21 +13178,6 @@ namespace ts {
1317313178
checkExpression(node.closingElement.tagName);
1317413179
}
1317513180

13176-
// Check children
13177-
for (const child of node.children) {
13178-
switch (child.kind) {
13179-
case SyntaxKind.JsxExpression:
13180-
checkJsxExpression(<JsxExpression>child);
13181-
break;
13182-
case SyntaxKind.JsxElement:
13183-
checkJsxElement(<JsxElement>child);
13184-
break;
13185-
case SyntaxKind.JsxSelfClosingElement:
13186-
checkJsxSelfClosingElement(<JsxSelfClosingElement>child);
13187-
break;
13188-
}
13189-
}
13190-
1319113181
return getJsxGlobalElementType() || anyType;
1319213182
}
1319313183

@@ -13280,6 +13270,30 @@ namespace ts {
1328013270
}
1328113271
});
1328213272
}
13273+
13274+
// Handle children attribute
13275+
const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ?
13276+
openingLikeElement.parent as JsxElement : undefined;
13277+
let containsSynthesizedJsxChildren = false;
13278+
// Comment
13279+
if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) {
13280+
// Error if there is a attribute named "children" and children element.
13281+
// This is because children element will overwrite the value from attributes
13282+
if (attributesTable.has(jsxChildrenPropertyName)) {
13283+
error(attributes, Diagnostics.props_children_are_specified_twice_The_attribute_named_children_will_be_overwritten);
13284+
}
13285+
13286+
// 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
13287+
const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName);
13288+
const childrenTypes: Type[] = [];
13289+
for (const child of (parent as JsxElement).children) {
13290+
childrenTypes.push(child.kind === SyntaxKind.JsxText ? stringType : checkExpression(child));
13291+
}
13292+
childrenPropSymbol.type = getUnionType(childrenTypes, /*subtypeReduction*/ false);
13293+
attributesTable.set(jsxChildrenPropertyName, childrenPropSymbol);
13294+
containsSynthesizedJsxChildren = true;
13295+
}
13296+
1328313297
return createJsxAttributesType(attributes.symbol, attributesTable);
1328413298

1328513299
/**
@@ -13290,7 +13304,8 @@ namespace ts {
1329013304
function createJsxAttributesType(symbol: Symbol, attributesTable: Map<Symbol>) {
1329113305
const result = createAnonymousType(symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
1329213306
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral;
13293-
result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag;
13307+
const containsSynthesizedJsxChildrenFlag = containsSynthesizedJsxChildren ? TypeFlags.ContainsSynthesizedJsxChildren : 0;
13308+
result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | containsSynthesizedJsxChildrenFlag;
1329413309
result.objectFlags |= ObjectFlags.ObjectLiteral;
1329513310
return result;
1329613311
}
@@ -14533,7 +14548,7 @@ namespace ts {
1453314548
// We can figure that out by resolving attributes property and check number of properties in the resolved type
1453414549
// If the call has correct arity, we will then check if the argument type and parameter type is assignable
1453514550

14536-
const callIsIncomplete = node.attributes.end === node.end; // If we are missing the close "/>", the call is incomplete
14551+
const callIsIncomplete = node.attributes.end === node.end; // If we are missing the close "/>", the call is incoplete
1453714552
if (callIsIncomplete) {
1453814553
return true;
1453914554
}

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,10 @@
20992099
"category": "Error",
21002100
"code": 2707
21012101
},
2102+
"props.children are specified twice. The attribute named 'children' will be overwritten.": {
2103+
"category": "Error",
2104+
"code": 2708
2105+
},
21022106

21032107
"Import declaration '{0}' is using private name '{1}'.": {
21042108
"category": "Error",

src/compiler/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace ts {
1+
namespace ts {
22
/**
33
* Type of objects whose values are all of the same type.
44
* The `in` and `for-in` operators can *not* be safely used,
@@ -10,7 +10,7 @@ namespace ts {
1010

1111
/** ES6 Map interface. */
1212
export interface Map<T> {
13-
get(key: string): T;
13+
get(key: string): T | undefined;
1414
has(key: string): boolean;
1515
set(key: string, value: T): this;
1616
delete(key: string): boolean;
@@ -2975,6 +2975,8 @@ namespace ts {
29752975
NonPrimitive = 1 << 24, // intrinsic object type
29762976
/* @internal */
29772977
JsxAttributes = 1 << 25, // Jsx attributes type
2978+
/* @internal */
2979+
ContainsSynthesizedJsxChildren = 1 << 26, // Jsx attributes type contains synthesized children property from Jsx element's children
29782980

29792981
/* @internal */
29802982
Nullable = Undefined | Null,

0 commit comments

Comments
 (0)