Skip to content

Commit eda9133

Browse files
authored
Merge pull request #15674 from Microsoft/release-fix15463
[Release] fix15463
2 parents 9dafe8b + b4fce08 commit eda9133

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
@@ -8667,29 +8667,6 @@ namespace ts {
86678667
return Ternary.False;
86688668
}
86698669

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-
86938670
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
86948671
if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
86958672
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
@@ -13226,6 +13203,8 @@ namespace ts {
1322613203
let spread: Type = emptyObjectType;
1322713204
let attributesArray: Symbol[] = [];
1322813205
let hasSpreadAnyType = false;
13206+
let explicitlySpecifyChildrenAttribute = false;
13207+
const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();
1322913208

1323013209
for (const attributeDecl of attributes.properties) {
1323113210
const member = attributeDecl.symbol;
@@ -13244,6 +13223,9 @@ namespace ts {
1324413223
attributeSymbol.target = member;
1324513224
attributesTable.set(attributeSymbol.name, attributeSymbol);
1324613225
attributesArray.push(attributeSymbol);
13226+
if (attributeDecl.name.text === jsxChildrenPropertyName) {
13227+
explicitlySpecifyChildrenAttribute = true;
13228+
}
1324713229
}
1324813230
else {
1324913231
Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute);
@@ -13302,11 +13284,11 @@ namespace ts {
1330213284
}
1330313285
}
1330413286

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();
1330813287
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) {
1331013292
error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, jsxChildrenPropertyName);
1331113293
}
1331213294

@@ -13328,8 +13310,7 @@ namespace ts {
1332813310
*/
1332913311
function createJsxAttributesType(symbol: Symbol, attributesTable: Map<Symbol>) {
1333013312
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;
1333313314
result.objectFlags |= ObjectFlags.ObjectLiteral;
1333413315
return result;
1333513316
}
@@ -13848,6 +13829,34 @@ namespace ts {
1384813829
checkJsxAttributesAssignableToTagNameAttributes(node);
1384913830
}
1385013831

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+
1385113860
/**
1385213861
* Check whether the given attributes of JSX opening-like element is assignable to the tagName attributes.
1385313862
* Get the attributes type of the opening-like element through resolving the tagName, "target attributes"
@@ -13880,7 +13889,19 @@ namespace ts {
1388013889
error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, getJsxElementPropertiesName());
1388113890
}
1388213891
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+
}
1388413905
}
1388513906
}
1388613907

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)