From ae652404cdcf9821e7a397d7c16995ef2111baed Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 22 Jan 2018 13:34:12 -0800 Subject: [PATCH] Fix JSX attribute checking when spreading unions Previously, the code didn't account for the fact that spreading a union creates a union. In fact, before Decemeber, spreading a union in JSX didn't create a union. Now the check for properties of the spread type uses `getPropertiesOfType`, which works with unions, instead of accessing the `properties` property directly. --- src/compiler/checker.ts | 2 +- .../tsxSpreadAttributesResolution17.js | 47 +++++++++++++++++++ .../tsxSpreadAttributesResolution17.symbols | 41 ++++++++++++++++ .../tsxSpreadAttributesResolution17.types | 45 ++++++++++++++++++ .../jsx/tsxSpreadAttributesResolution17.tsx | 25 ++++++++++ 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/tsxSpreadAttributesResolution17.js create mode 100644 tests/baselines/reference/tsxSpreadAttributesResolution17.symbols create mode 100644 tests/baselines/reference/tsxSpreadAttributesResolution17.types create mode 100644 tests/cases/conformance/jsx/tsxSpreadAttributesResolution17.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f418dbabcb8..f4d8c99c471 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15310,7 +15310,7 @@ namespace ts { // If the targetAttributesType is an emptyObjectType, indicating that there is no property named 'props' on this instance type. // but there exists a sourceAttributesType, we need to explicitly give an error as normal assignability check allow excess properties and will pass. - if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || (sourceAttributesType).properties.length > 0)) { + if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || getPropertiesOfType(sourceAttributesType).length > 0)) { error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(getJsxElementPropertiesName())); } else { diff --git a/tests/baselines/reference/tsxSpreadAttributesResolution17.js b/tests/baselines/reference/tsxSpreadAttributesResolution17.js new file mode 100644 index 00000000000..81b1f073514 --- /dev/null +++ b/tests/baselines/reference/tsxSpreadAttributesResolution17.js @@ -0,0 +1,47 @@ +//// [file.tsx] +declare global { + namespace JSX { + interface Element {} + interface ElementAttributesProperty { props: {} } + } +} +declare var React: any; + +export class Empty extends React.Component<{}, {}> { + render() { + return
Hello
; + } +} + +declare const obj: { a: number | undefined } | undefined; + +// OK +let unionedSpread = ; + + +//// [file.jsx] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var Empty = /** @class */ (function (_super) { + __extends(Empty, _super); + function Empty() { + return _super !== null && _super.apply(this, arguments) || this; + } + Empty.prototype.render = function () { + return
Hello
; + }; + return Empty; +}(React.Component)); +exports.Empty = Empty; +// OK +var unionedSpread = ; diff --git a/tests/baselines/reference/tsxSpreadAttributesResolution17.symbols b/tests/baselines/reference/tsxSpreadAttributesResolution17.symbols new file mode 100644 index 00000000000..6f346ccdcb1 --- /dev/null +++ b/tests/baselines/reference/tsxSpreadAttributesResolution17.symbols @@ -0,0 +1,41 @@ +=== tests/cases/conformance/jsx/file.tsx === +declare global { +>global : Symbol(global, Decl(file.tsx, 0, 0)) + + namespace JSX { +>JSX : Symbol(JSX, Decl(file.tsx, 0, 16)) + + interface Element {} +>Element : Symbol(Element, Decl(file.tsx, 1, 19)) + + interface ElementAttributesProperty { props: {} } +>ElementAttributesProperty : Symbol(ElementAttributesProperty, Decl(file.tsx, 2, 28)) +>props : Symbol(ElementAttributesProperty.props, Decl(file.tsx, 3, 45)) + } +} +declare var React: any; +>React : Symbol(React, Decl(file.tsx, 6, 11)) + +export class Empty extends React.Component<{}, {}> { +>Empty : Symbol(Empty, Decl(file.tsx, 6, 23)) +>React : Symbol(React, Decl(file.tsx, 6, 11)) + + render() { +>render : Symbol(Empty.render, Decl(file.tsx, 8, 52)) + + return
Hello
; +>div : Symbol(unknown) +>div : Symbol(unknown) + } +} + +declare const obj: { a: number | undefined } | undefined; +>obj : Symbol(obj, Decl(file.tsx, 14, 13)) +>a : Symbol(a, Decl(file.tsx, 14, 20)) + +// OK +let unionedSpread = ; +>unionedSpread : Symbol(unionedSpread, Decl(file.tsx, 17, 3)) +>Empty : Symbol(Empty, Decl(file.tsx, 6, 23)) +>obj : Symbol(obj, Decl(file.tsx, 14, 13)) + diff --git a/tests/baselines/reference/tsxSpreadAttributesResolution17.types b/tests/baselines/reference/tsxSpreadAttributesResolution17.types new file mode 100644 index 00000000000..9477e7366ee --- /dev/null +++ b/tests/baselines/reference/tsxSpreadAttributesResolution17.types @@ -0,0 +1,45 @@ +=== tests/cases/conformance/jsx/file.tsx === +declare global { +>global : any + + namespace JSX { +>JSX : any + + interface Element {} +>Element : Element + + interface ElementAttributesProperty { props: {} } +>ElementAttributesProperty : ElementAttributesProperty +>props : {} + } +} +declare var React: any; +>React : any + +export class Empty extends React.Component<{}, {}> { +>Empty : Empty +>React.Component : any +>React : any +>Component : any + + render() { +>render : () => JSX.Element + + return
Hello
; +>
Hello
: JSX.Element +>div : any +>div : any + } +} + +declare const obj: { a: number | undefined } | undefined; +>obj : { a: number | undefined; } | undefined +>a : number | undefined + +// OK +let unionedSpread = ; +>unionedSpread : JSX.Element +> : JSX.Element +>Empty : typeof Empty +>obj : { a: number | undefined; } | undefined + diff --git a/tests/cases/conformance/jsx/tsxSpreadAttributesResolution17.tsx b/tests/cases/conformance/jsx/tsxSpreadAttributesResolution17.tsx new file mode 100644 index 00000000000..da5638d3e2a --- /dev/null +++ b/tests/cases/conformance/jsx/tsxSpreadAttributesResolution17.tsx @@ -0,0 +1,25 @@ +// @strictNullChecks: true +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @skipLibCheck: true +// @libFiles: lib.d.ts + +declare global { + namespace JSX { + interface Element {} + interface ElementAttributesProperty { props: {} } + } +} +declare var React: any; + +export class Empty extends React.Component<{}, {}> { + render() { + return
Hello
; + } +} + +declare const obj: { a: number | undefined } | undefined; + +// OK +let unionedSpread = ;