Skip to content

Commit 31c4ad1

Browse files
authored
Merge pull request microsoft#14216 from Microsoft/master-fix13526
[Master] Fix13526 allow JSX attributes to be union type
2 parents 359823b + cabaeae commit 31c4ad1

9 files changed

+341
-42
lines changed

src/compiler/checker.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12754,11 +12754,6 @@ namespace ts {
1275412754
// Props is of type 'any' or unknown
1275512755
return attributesType;
1275612756
}
12757-
else if (attributesType.flags & TypeFlags.Union) {
12758-
// Props cannot be a union type
12759-
error(openingLikeElement.tagName, Diagnostics.JSX_element_attributes_type_0_may_not_be_a_union_type, typeToString(attributesType));
12760-
return anyType;
12761-
}
1276212757
else {
1276312758
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements
1276412759
let apparentAttributesType = attributesType;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [file.tsx]
2+
3+
import React = require('react');
4+
5+
interface Address {
6+
street: string;
7+
country: string;
8+
}
9+
10+
interface CanadianAddress extends Address {
11+
postalCode: string;
12+
}
13+
14+
interface AmericanAddress extends Address {
15+
zipCode: string;
16+
}
17+
18+
type Properties = CanadianAddress | AmericanAddress;
19+
20+
export class AddressComp extends React.Component<Properties, void> {
21+
public render() {
22+
return null;
23+
}
24+
}
25+
26+
let a = <AddressComp postalCode='T1B 0L3' street="vancouver" country="CA" />
27+
28+
//// [file.jsx]
29+
"use strict";
30+
var __extends = (this && this.__extends) || (function () {
31+
var extendStatics = Object.setPrototypeOf ||
32+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
33+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
34+
return function (d, b) {
35+
extendStatics(d, b);
36+
function __() { this.constructor = d; }
37+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
38+
};
39+
})();
40+
exports.__esModule = true;
41+
var React = require("react");
42+
var AddressComp = (function (_super) {
43+
__extends(AddressComp, _super);
44+
function AddressComp() {
45+
return _super !== null && _super.apply(this, arguments) || this;
46+
}
47+
AddressComp.prototype.render = function () {
48+
return null;
49+
};
50+
return AddressComp;
51+
}(React.Component));
52+
exports.AddressComp = AddressComp;
53+
var a = <AddressComp postalCode='T1B 0L3' street="vancouver" country="CA"/>;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
3+
import React = require('react');
4+
>React : Symbol(React, Decl(file.tsx, 0, 0))
5+
6+
interface Address {
7+
>Address : Symbol(Address, Decl(file.tsx, 1, 32))
8+
9+
street: string;
10+
>street : Symbol(Address.street, Decl(file.tsx, 3, 19))
11+
12+
country: string;
13+
>country : Symbol(Address.country, Decl(file.tsx, 4, 17))
14+
}
15+
16+
interface CanadianAddress extends Address {
17+
>CanadianAddress : Symbol(CanadianAddress, Decl(file.tsx, 6, 1))
18+
>Address : Symbol(Address, Decl(file.tsx, 1, 32))
19+
20+
postalCode: string;
21+
>postalCode : Symbol(CanadianAddress.postalCode, Decl(file.tsx, 8, 43))
22+
}
23+
24+
interface AmericanAddress extends Address {
25+
>AmericanAddress : Symbol(AmericanAddress, Decl(file.tsx, 10, 1))
26+
>Address : Symbol(Address, Decl(file.tsx, 1, 32))
27+
28+
zipCode: string;
29+
>zipCode : Symbol(AmericanAddress.zipCode, Decl(file.tsx, 12, 43))
30+
}
31+
32+
type Properties = CanadianAddress | AmericanAddress;
33+
>Properties : Symbol(Properties, Decl(file.tsx, 14, 1))
34+
>CanadianAddress : Symbol(CanadianAddress, Decl(file.tsx, 6, 1))
35+
>AmericanAddress : Symbol(AmericanAddress, Decl(file.tsx, 10, 1))
36+
37+
export class AddressComp extends React.Component<Properties, void> {
38+
>AddressComp : Symbol(AddressComp, Decl(file.tsx, 16, 52))
39+
>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55))
40+
>React : Symbol(React, Decl(file.tsx, 0, 0))
41+
>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55))
42+
>Properties : Symbol(Properties, Decl(file.tsx, 14, 1))
43+
44+
public render() {
45+
>render : Symbol(AddressComp.render, Decl(file.tsx, 18, 68))
46+
47+
return null;
48+
}
49+
}
50+
51+
let a = <AddressComp postalCode='T1B 0L3' street="vancouver" country="CA" />
52+
>a : Symbol(a, Decl(file.tsx, 24, 3))
53+
>AddressComp : Symbol(AddressComp, Decl(file.tsx, 16, 52))
54+
>postalCode : Symbol(postalCode, Decl(file.tsx, 24, 20))
55+
>street : Symbol(street, Decl(file.tsx, 24, 41))
56+
>country : Symbol(country, Decl(file.tsx, 24, 60))
57+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
3+
import React = require('react');
4+
>React : typeof React
5+
6+
interface Address {
7+
>Address : Address
8+
9+
street: string;
10+
>street : string
11+
12+
country: string;
13+
>country : string
14+
}
15+
16+
interface CanadianAddress extends Address {
17+
>CanadianAddress : CanadianAddress
18+
>Address : Address
19+
20+
postalCode: string;
21+
>postalCode : string
22+
}
23+
24+
interface AmericanAddress extends Address {
25+
>AmericanAddress : AmericanAddress
26+
>Address : Address
27+
28+
zipCode: string;
29+
>zipCode : string
30+
}
31+
32+
type Properties = CanadianAddress | AmericanAddress;
33+
>Properties : CanadianAddress | AmericanAddress
34+
>CanadianAddress : CanadianAddress
35+
>AmericanAddress : AmericanAddress
36+
37+
export class AddressComp extends React.Component<Properties, void> {
38+
>AddressComp : AddressComp
39+
>React.Component : React.Component<CanadianAddress | AmericanAddress, void>
40+
>React : typeof React
41+
>Component : typeof React.Component
42+
>Properties : CanadianAddress | AmericanAddress
43+
44+
public render() {
45+
>render : () => any
46+
47+
return null;
48+
>null : null
49+
}
50+
}
51+
52+
let a = <AddressComp postalCode='T1B 0L3' street="vancouver" country="CA" />
53+
>a : JSX.Element
54+
><AddressComp postalCode='T1B 0L3' street="vancouver" country="CA" /> : JSX.Element
55+
>AddressComp : typeof AddressComp
56+
>postalCode : string
57+
>street : string
58+
>country : string
59+

tests/baselines/reference/tsxSpreadAttributesResolution6.errors.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
tests/cases/conformance/jsx/file.tsx(14,10): error TS2600: JSX element attributes type '({ editable: false; } & { children?: ReactNode; }) | ({ editable: true; onEdit: (newText: string) => void; } & { children?: ReactNode; })' may not be a union type.
1+
tests/cases/conformance/jsx/file.tsx(14,24): error TS2322: Type '{ editable: true; }' is not assignable to type '(IntrinsicAttributes & IntrinsicClassAttributes<TextComponent> & { editable: false; } & { children?: ReactNode; }) | (IntrinsicAttributes & IntrinsicClassAttributes<TextComponent> & { editable: true; onEdit: (newText: string) => void; } & { children?: ReactNode; })'.
2+
Type '{ editable: true; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<TextComponent> & { editable: true; onEdit: (newText: string) => void; } & { children?: ReactNode; }'.
3+
Type '{ editable: true; }' is not assignable to type '{ editable: true; onEdit: (newText: string) => void; }'.
4+
Property 'onEdit' is missing in type '{ editable: true; }'.
25

36

47
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
@@ -16,8 +19,11 @@ tests/cases/conformance/jsx/file.tsx(14,10): error TS2600: JSX element attribute
1619

1720
// Error
1821
let x = <TextComponent editable={true} />
19-
~~~~~~~~~~~~~
20-
!!! error TS2600: JSX element attributes type '({ editable: false; } & { children?: ReactNode; }) | ({ editable: true; onEdit: (newText: string) => void; } & { children?: ReactNode; })' may not be a union type.
22+
~~~~~~~~~~~~~~~
23+
!!! error TS2322: Type '{ editable: true; }' is not assignable to type '(IntrinsicAttributes & IntrinsicClassAttributes<TextComponent> & { editable: false; } & { children?: ReactNode; }) | (IntrinsicAttributes & IntrinsicClassAttributes<TextComponent> & { editable: true; onEdit: (newText: string) => void; } & { children?: ReactNode; })'.
24+
!!! error TS2322: Type '{ editable: true; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<TextComponent> & { editable: true; onEdit: (newText: string) => void; } & { children?: ReactNode; }'.
25+
!!! error TS2322: Type '{ editable: true; }' is not assignable to type '{ editable: true; onEdit: (newText: string) => void; }'.
26+
!!! error TS2322: Property 'onEdit' is missing in type '{ editable: true; }'.
2127

2228
const textProps: TextProps = {
2329
editable: false

tests/baselines/reference/tsxSpreadAttributesResolution7.errors.txt

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
3+
import React = require('react');
4+
>React : Symbol(React, Decl(file.tsx, 0, 0))
5+
6+
type TextProps = { editable: false }
7+
>TextProps : Symbol(TextProps, Decl(file.tsx, 1, 32))
8+
>editable : Symbol(editable, Decl(file.tsx, 3, 18))
9+
10+
| { editable: true, onEdit: (newText: string) => void };
11+
>editable : Symbol(editable, Decl(file.tsx, 4, 18))
12+
>onEdit : Symbol(onEdit, Decl(file.tsx, 4, 34))
13+
>newText : Symbol(newText, Decl(file.tsx, 4, 44))
14+
15+
class TextComponent extends React.Component<TextProps, {}> {
16+
>TextComponent : Symbol(TextComponent, Decl(file.tsx, 4, 71))
17+
>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55))
18+
>React : Symbol(React, Decl(file.tsx, 0, 0))
19+
>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55))
20+
>TextProps : Symbol(TextProps, Decl(file.tsx, 1, 32))
21+
22+
render() {
23+
>render : Symbol(TextComponent.render, Decl(file.tsx, 6, 60))
24+
25+
return <span>Some Text..</span>;
26+
>span : Symbol(JSX.IntrinsicElements.span, Decl(react.d.ts, 2458, 51))
27+
>span : Symbol(JSX.IntrinsicElements.span, Decl(react.d.ts, 2458, 51))
28+
}
29+
}
30+
31+
// OK
32+
const textPropsFalse: TextProps = {
33+
>textPropsFalse : Symbol(textPropsFalse, Decl(file.tsx, 13, 5))
34+
>TextProps : Symbol(TextProps, Decl(file.tsx, 1, 32))
35+
36+
editable: false
37+
>editable : Symbol(editable, Decl(file.tsx, 13, 35))
38+
39+
};
40+
41+
let y1 = <TextComponent {...textPropsFalse} />
42+
>y1 : Symbol(y1, Decl(file.tsx, 17, 3))
43+
>TextComponent : Symbol(TextComponent, Decl(file.tsx, 4, 71))
44+
>textPropsFalse : Symbol(textPropsFalse, Decl(file.tsx, 13, 5))
45+
46+
const textPropsTrue: TextProps = {
47+
>textPropsTrue : Symbol(textPropsTrue, Decl(file.tsx, 19, 5))
48+
>TextProps : Symbol(TextProps, Decl(file.tsx, 1, 32))
49+
50+
editable: true,
51+
>editable : Symbol(editable, Decl(file.tsx, 19, 34))
52+
53+
onEdit: () => {}
54+
>onEdit : Symbol(onEdit, Decl(file.tsx, 20, 19))
55+
56+
};
57+
58+
let y2 = <TextComponent {...textPropsTrue} />
59+
>y2 : Symbol(y2, Decl(file.tsx, 24, 3))
60+
>TextComponent : Symbol(TextComponent, Decl(file.tsx, 4, 71))
61+
>textPropsTrue : Symbol(textPropsTrue, Decl(file.tsx, 19, 5))
62+
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
3+
import React = require('react');
4+
>React : typeof React
5+
6+
type TextProps = { editable: false }
7+
>TextProps : { editable: false; } | { editable: true; onEdit: (newText: string) => void; }
8+
>editable : false
9+
>false : false
10+
11+
| { editable: true, onEdit: (newText: string) => void };
12+
>editable : true
13+
>true : true
14+
>onEdit : (newText: string) => void
15+
>newText : string
16+
17+
class TextComponent extends React.Component<TextProps, {}> {
18+
>TextComponent : TextComponent
19+
>React.Component : React.Component<{ editable: false; } | { editable: true; onEdit: (newText: string) => void; }, {}>
20+
>React : typeof React
21+
>Component : typeof React.Component
22+
>TextProps : { editable: false; } | { editable: true; onEdit: (newText: string) => void; }
23+
24+
render() {
25+
>render : () => JSX.Element
26+
27+
return <span>Some Text..</span>;
28+
><span>Some Text..</span> : JSX.Element
29+
>span : any
30+
>span : any
31+
}
32+
}
33+
34+
// OK
35+
const textPropsFalse: TextProps = {
36+
>textPropsFalse : { editable: false; } | { editable: true; onEdit: (newText: string) => void; }
37+
>TextProps : { editable: false; } | { editable: true; onEdit: (newText: string) => void; }
38+
>{ editable: false} : { editable: false; }
39+
40+
editable: false
41+
>editable : boolean
42+
>false : false
43+
44+
};
45+
46+
let y1 = <TextComponent {...textPropsFalse} />
47+
>y1 : JSX.Element
48+
><TextComponent {...textPropsFalse} /> : JSX.Element
49+
>TextComponent : typeof TextComponent
50+
>textPropsFalse : { editable: false; }
51+
52+
const textPropsTrue: TextProps = {
53+
>textPropsTrue : { editable: false; } | { editable: true; onEdit: (newText: string) => void; }
54+
>TextProps : { editable: false; } | { editable: true; onEdit: (newText: string) => void; }
55+
>{ editable: true, onEdit: () => {}} : { editable: true; onEdit: () => void; }
56+
57+
editable: true,
58+
>editable : boolean
59+
>true : true
60+
61+
onEdit: () => {}
62+
>onEdit : () => void
63+
>() => {} : () => void
64+
65+
};
66+
67+
let y2 = <TextComponent {...textPropsTrue} />
68+
>y2 : JSX.Element
69+
><TextComponent {...textPropsTrue} /> : JSX.Element
70+
>TextComponent : typeof TextComponent
71+
>textPropsTrue : { editable: true; onEdit: (newText: string) => void; }
72+

0 commit comments

Comments
 (0)