Skip to content

Commit 1b520fc

Browse files
authored
Merge pull request #15595 from Microsoft/master-fix15463
[Master] fix 15463
2 parents 0b0a2d0 + 1e32b10 commit 1b520fc

File tree

53 files changed

+1228
-196
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1228
-196
lines changed

src/compiler/checker.ts

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8690,29 +8690,6 @@ namespace ts {
86908690
return Ternary.False;
86918691
}
86928692

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-
87168693
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
87178694
if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
87188695
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
@@ -13276,6 +13253,8 @@ namespace ts {
1327613253
let spread: Type = emptyObjectType;
1327713254
let attributesArray: Symbol[] = [];
1327813255
let hasSpreadAnyType = false;
13256+
let explicitlySpecifyChildrenAttribute = false;
13257+
const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();
1327913258

1328013259
for (const attributeDecl of attributes.properties) {
1328113260
const member = attributeDecl.symbol;
@@ -13294,6 +13273,9 @@ namespace ts {
1329413273
attributeSymbol.target = member;
1329513274
attributesTable.set(attributeSymbol.name, attributeSymbol);
1329613275
attributesArray.push(attributeSymbol);
13276+
if (attributeDecl.name.text === jsxChildrenPropertyName) {
13277+
explicitlySpecifyChildrenAttribute = true;
13278+
}
1329713279
}
1329813280
else {
1329913281
Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute);
@@ -13352,11 +13334,11 @@ namespace ts {
1335213334
}
1335313335
}
1335413336

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();
1335813337
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) {
1336013342
error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, jsxChildrenPropertyName);
1336113343
}
1336213344

@@ -13378,8 +13360,7 @@ namespace ts {
1337813360
*/
1337913361
function createJsxAttributesType(symbol: Symbol, attributesTable: Map<Symbol>) {
1338013362
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;
1338313364
result.objectFlags |= ObjectFlags.ObjectLiteral;
1338413365
return result;
1338513366
}
@@ -13898,6 +13879,34 @@ namespace ts {
1389813879
checkJsxAttributesAssignableToTagNameAttributes(node);
1389913880
}
1390013881

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+
1390113910
/**
1390213911
* Check whether the given attributes of JSX opening-like element is assignable to the tagName attributes.
1390313912
* Get the attributes type of the opening-like element through resolving the tagName, "target attributes"
@@ -13930,7 +13939,19 @@ namespace ts {
1393013939
error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, getJsxElementPropertiesName());
1393113940
}
1393213941
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+
}
1393413955
}
1393513956
}
1393613957

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//// [file.tsx]
2+
import React = require('react');
3+
4+
interface ButtonProp {
5+
a: number,
6+
b: string,
7+
children: Button;
8+
}
9+
10+
class Button extends React.Component<ButtonProp, any> {
11+
render() {
12+
let condition: boolean;
13+
if (condition) {
14+
return <InnerButton {...this.props} />
15+
}
16+
else {
17+
return (<InnerButton {...this.props} >
18+
<div>Hello World</div>
19+
</InnerButton>);
20+
}
21+
}
22+
}
23+
24+
interface InnerButtonProp {
25+
a: number
26+
}
27+
28+
class InnerButton extends React.Component<InnerButtonProp, any> {
29+
render() {
30+
return (<button>Hello</button>);
31+
}
32+
}
33+
34+
35+
//// [file.jsx]
36+
"use strict";
37+
var __extends = (this && this.__extends) || (function () {
38+
var extendStatics = Object.setPrototypeOf ||
39+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
40+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
41+
return function (d, b) {
42+
extendStatics(d, b);
43+
function __() { this.constructor = d; }
44+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
45+
};
46+
})();
47+
exports.__esModule = true;
48+
var React = require("react");
49+
var Button = (function (_super) {
50+
__extends(Button, _super);
51+
function Button() {
52+
return _super !== null && _super.apply(this, arguments) || this;
53+
}
54+
Button.prototype.render = function () {
55+
var condition;
56+
if (condition) {
57+
return <InnerButton {...this.props}/>;
58+
}
59+
else {
60+
return (<InnerButton {...this.props}>
61+
<div>Hello World</div>
62+
</InnerButton>);
63+
}
64+
};
65+
return Button;
66+
}(React.Component));
67+
var InnerButton = (function (_super) {
68+
__extends(InnerButton, _super);
69+
function InnerButton() {
70+
return _super !== null && _super.apply(this, arguments) || this;
71+
}
72+
InnerButton.prototype.render = function () {
73+
return (<button>Hello</button>);
74+
};
75+
return InnerButton;
76+
}(React.Component));
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
import React = require('react');
3+
>React : Symbol(React, Decl(file.tsx, 0, 0))
4+
5+
interface ButtonProp {
6+
>ButtonProp : Symbol(ButtonProp, Decl(file.tsx, 0, 32))
7+
8+
a: number,
9+
>a : Symbol(ButtonProp.a, Decl(file.tsx, 2, 22))
10+
11+
b: string,
12+
>b : Symbol(ButtonProp.b, Decl(file.tsx, 3, 14))
13+
14+
children: Button;
15+
>children : Symbol(ButtonProp.children, Decl(file.tsx, 4, 14))
16+
>Button : Symbol(Button, Decl(file.tsx, 6, 1))
17+
}
18+
19+
class Button extends React.Component<ButtonProp, any> {
20+
>Button : Symbol(Button, Decl(file.tsx, 6, 1))
21+
>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55))
22+
>React : Symbol(React, Decl(file.tsx, 0, 0))
23+
>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55))
24+
>ButtonProp : Symbol(ButtonProp, Decl(file.tsx, 0, 32))
25+
26+
render() {
27+
>render : Symbol(Button.render, Decl(file.tsx, 8, 55))
28+
29+
let condition: boolean;
30+
>condition : Symbol(condition, Decl(file.tsx, 10, 5))
31+
32+
if (condition) {
33+
>condition : Symbol(condition, Decl(file.tsx, 10, 5))
34+
35+
return <InnerButton {...this.props} />
36+
>InnerButton : Symbol(InnerButton, Decl(file.tsx, 24, 1))
37+
>this.props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37))
38+
>this : Symbol(Button, Decl(file.tsx, 6, 1))
39+
>props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37))
40+
}
41+
else {
42+
return (<InnerButton {...this.props} >
43+
>InnerButton : Symbol(InnerButton, Decl(file.tsx, 24, 1))
44+
>this.props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37))
45+
>this : Symbol(Button, Decl(file.tsx, 6, 1))
46+
>props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37))
47+
48+
<div>Hello World</div>
49+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
50+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
51+
52+
</InnerButton>);
53+
>InnerButton : Symbol(InnerButton, Decl(file.tsx, 24, 1))
54+
}
55+
}
56+
}
57+
58+
interface InnerButtonProp {
59+
>InnerButtonProp : Symbol(InnerButtonProp, Decl(file.tsx, 20, 1))
60+
61+
a: number
62+
>a : Symbol(InnerButtonProp.a, Decl(file.tsx, 22, 27))
63+
}
64+
65+
class InnerButton extends React.Component<InnerButtonProp, any> {
66+
>InnerButton : Symbol(InnerButton, Decl(file.tsx, 24, 1))
67+
>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55))
68+
>React : Symbol(React, Decl(file.tsx, 0, 0))
69+
>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55))
70+
>InnerButtonProp : Symbol(InnerButtonProp, Decl(file.tsx, 20, 1))
71+
72+
render() {
73+
>render : Symbol(InnerButton.render, Decl(file.tsx, 26, 65))
74+
75+
return (<button>Hello</button>);
76+
>button : Symbol(JSX.IntrinsicElements.button, Decl(react.d.ts, 2385, 43))
77+
>button : Symbol(JSX.IntrinsicElements.button, Decl(react.d.ts, 2385, 43))
78+
}
79+
}
80+
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
import React = require('react');
3+
>React : typeof React
4+
5+
interface ButtonProp {
6+
>ButtonProp : ButtonProp
7+
8+
a: number,
9+
>a : number
10+
11+
b: string,
12+
>b : string
13+
14+
children: Button;
15+
>children : Button
16+
>Button : Button
17+
}
18+
19+
class Button extends React.Component<ButtonProp, any> {
20+
>Button : Button
21+
>React.Component : React.Component<ButtonProp, any>
22+
>React : typeof React
23+
>Component : typeof React.Component
24+
>ButtonProp : ButtonProp
25+
26+
render() {
27+
>render : () => JSX.Element
28+
29+
let condition: boolean;
30+
>condition : boolean
31+
32+
if (condition) {
33+
>condition : boolean
34+
35+
return <InnerButton {...this.props} />
36+
><InnerButton {...this.props} /> : JSX.Element
37+
>InnerButton : typeof InnerButton
38+
>this.props : ButtonProp & { children?: React.ReactNode; }
39+
>this : this
40+
>props : ButtonProp & { children?: React.ReactNode; }
41+
}
42+
else {
43+
return (<InnerButton {...this.props} >
44+
>(<InnerButton {...this.props} > <div>Hello World</div> </InnerButton>) : JSX.Element
45+
><InnerButton {...this.props} > <div>Hello World</div> </InnerButton> : JSX.Element
46+
>InnerButton : typeof InnerButton
47+
>this.props : ButtonProp & { children?: React.ReactNode; }
48+
>this : this
49+
>props : ButtonProp & { children?: React.ReactNode; }
50+
51+
<div>Hello World</div>
52+
><div>Hello World</div> : JSX.Element
53+
>div : any
54+
>div : any
55+
56+
</InnerButton>);
57+
>InnerButton : typeof InnerButton
58+
}
59+
}
60+
}
61+
62+
interface InnerButtonProp {
63+
>InnerButtonProp : InnerButtonProp
64+
65+
a: number
66+
>a : number
67+
}
68+
69+
class InnerButton extends React.Component<InnerButtonProp, any> {
70+
>InnerButton : InnerButton
71+
>React.Component : React.Component<InnerButtonProp, any>
72+
>React : typeof React
73+
>Component : typeof React.Component
74+
>InnerButtonProp : InnerButtonProp
75+
76+
render() {
77+
>render : () => JSX.Element
78+
79+
return (<button>Hello</button>);
80+
>(<button>Hello</button>) : JSX.Element
81+
><button>Hello</button> : JSX.Element
82+
>button : any
83+
>button : any
84+
}
85+
}
86+

0 commit comments

Comments
 (0)