Skip to content

Commit

Permalink
[react] Add JSX namespace to React namespace (#64464)
Browse files Browse the repository at this point in the history
* Move React JSX namespace out of the global

* Apply `scoped-jsx@8500bdedae` codemod

* Fix import placement breaking DT header parser

* Adjust issues introduced by codemod

Almost all all stylistic lint rules. Minority is real issues (e.g. not actually using React, reference types misplaced etc)

* Backwards compatible scoped JSX

* Use scoped JSX everywhere

* Fix augmentations for deprecated namespace

* [react-blessed] Actually augment React's JSX namespace

* Remove global JSX namespace

The goal of just adding the scoped namespace
was to ensure the ecosystem  can prepare.
However, you'd still have to change augmentations to target the scoped namespace
i.e. making this a breaking change.
So if we break anyway, we might break everything. The additional breakage is
codemoddable after all.

* Reintroduce global namespace

This makes the change fully backwards compatible.
Global JSX augmentations still work.
Scoped JSX augmentations will work.
Everybody can start moving to the scoped JSX if they ensure they're on the
latest types/react release for their supported SemVer major.

* Revert changes using new scoped JSX namespace

That way we can see in the branch diff of this PR
that the change is backwards compatible (excluding libraries relying on
buggy behavior)).
We'll remove usage of the scoped namespace once we landed this change and
after a grace period of one week (for integration testing)

* Add dedicated test for scoped vs global namespace

* [react-blessed] Actually augment React's JSX namespace

* Backport to TS 5.0

* Remove duplicate interface declarations

* Reduce diff noise

---------

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
  • Loading branch information
Sebastian Silbermann and Andarist committed May 6, 2023
1 parent da51f4f commit f1b2559
Show file tree
Hide file tree
Showing 16 changed files with 366 additions and 38 deletions.
53 changes: 42 additions & 11 deletions types/react-blessed/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,23 +206,17 @@ export type DetailedBlessedProps<E extends Element> = BlessedAttributes<E> & Rea
export interface BlessedIntrinsicElements {
element: DetailedBlessedProps<Element>;
box: DetailedBlessedProps<BoxElement>;
text: DetailedBlessedProps<TextElement>;
line: DetailedBlessedProps<LineElement>;
scrollablebox: DetailedBlessedProps<ScrollableBoxElement>;
scrollabletext: DetailedBlessedProps<ScrollableTextElement>;
bigtext: DetailedBlessedProps<BigTextElement>;
list: DetailedBlessedProps<ListElement>;
filemanager: DetailedBlessedProps<FileManagerElement>;
listtable: DetailedBlessedProps<ListTableElement>;
listbar: DetailedBlessedProps<ListbarElement>;
input: DetailedBlessedProps<InputElement>;
textarea: DetailedBlessedProps<TextareaElement>;
textbox: DetailedBlessedProps<TextboxElement>;
button: DetailedBlessedProps<ButtonElement>;
checkbox: DetailedBlessedProps<CheckboxElement>;
radioset: DetailedBlessedProps<RadioSetElement>;
radiobutton: DetailedBlessedProps<RadioButtonElement>;
table: DetailedBlessedProps<TableElement>;
prompt: DetailedBlessedProps<PromptElement>;
question: DetailedBlessedProps<QuestionElement>;
message: DetailedBlessedProps<MessageElement>;
Expand All @@ -248,6 +242,48 @@ export type BlessedIntrinsicElementsPrefixed = {
// augment react JSX when old JSX transform is used
declare module "react" {
namespace JSX {
interface ButtonHTMLAttributes<T>
extends HTMLAttributes<T>,
Omit<
DetailedBlessedProps<ButtonElement>,
'draggable' | 'onBlur' | 'onClick' | 'onFocus' | 'onResize' | 'ref' | 'style'
> {}

interface TableHTMLAttributes<T>
extends HTMLAttributes<T>,
Omit<
DetailedBlessedProps<TableElement>,
'border' | 'draggable' | 'onBlur' | 'onClick' | 'onFocus' | 'onResize' | 'ref' | 'style'
> {}

interface TextareaHTMLAttributes<T>
extends HTMLAttributes<T>,
Omit<
DetailedBlessedProps<TextElement>,
'draggable' | 'fill' | 'focusable' | 'onBlur' | 'onClick' | 'onFocus' | 'onResize' | 'ref' | 'style'
> {}

interface InputHTMLAttributes<T>
extends HTMLAttributes<T>,
Omit<
DetailedBlessedProps<InputElement>,
'draggable' | 'onBlur' | 'onClick' | 'onFocus' | 'onResize' | 'ref' | 'style'
> {}

interface SVGLineElementAttributes<T>
extends SVGProps<T>,
Omit<
DetailedBlessedProps<LineElement>,
'focusable' | 'onBlur' | 'onClick' | 'onFocus' | 'onResize' | 'orientation' | 'ref' | 'style'
> {}

interface SVGTextElementAttributes<T>
extends SVGProps<T>,
Omit<
DetailedBlessedProps<TextElement>,
'fill' | 'focusable' | 'onBlur' | 'onClick' | 'onFocus' | 'onResize' | 'ref' | 'style'
> {}

// set IntrinsicElements to 'react-blessed' elements both with and without
// 'blessed-' prefix
interface IntrinsicElements extends BlessedIntrinsicElementsPrefixed, BlessedIntrinsicElements {}
Expand All @@ -257,16 +293,11 @@ declare module "react" {
// augment react/jsx-runtime JSX when new JSX transform is used
declare module "react/jsx-runtime" {
namespace JSX {
// copy React JSX, otherwise class refs won't type as expected
type IntrinsicAttributes = React.Attributes;
interface IntrinsicClassAttributes<T> extends React.ClassAttributes<T> {}
interface IntrinsicElements extends BlessedIntrinsicElementsPrefixed, BlessedIntrinsicElements {}
}
}
declare module "react/jsx-dev-runtime" {
namespace JSX {
type IntrinsicAttributes = React.Attributes;
interface IntrinsicClassAttributes<T> extends React.ClassAttributes<T> {}
interface IntrinsicElements extends BlessedIntrinsicElementsPrefixed, BlessedIntrinsicElements {}
}
}
3 changes: 2 additions & 1 deletion types/react-blessed/react-blessed-tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ const Box = () => <box style={{ fg: "blue" }} />;
const BlessedBox = () => <blessed-box style={{ fg: "blue" }} />;
const FF: React.FC<ReactBlessed.BlessedIntrinsicElements["box"]> = props => <box {...props} />;

// @ts-expect-error
// Undesired. Should error but we can only augment intrinsic elements.
// Should not typecheck once `@types/react` moves DOM intrinsics to `react-dom`.
const Div = () => <div />;

/**
Expand Down
38 changes: 36 additions & 2 deletions types/react/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1398,6 +1398,9 @@ declare namespace React {
interface SVGProps<T> extends SVGAttributes<T>, ClassAttributes<T> {
}

interface SVGLineElementAttributes<T> extends SVGProps<T> {}
interface SVGTextElementAttributes<T> extends SVGProps<T> {}

interface DOMAttributes<T> {
children?: ReactNode | undefined;
dangerouslySetInnerHTML?: {
Expand Down Expand Up @@ -3115,6 +3118,19 @@ declare namespace React {
*/
componentStack: string;
}

namespace JSX {
interface Element extends GlobalJSXElement {}
interface ElementClass extends GlobalJSXElementClass {}
interface ElementAttributesProperty extends GlobalJSXElementAttributesProperty {}
interface ElementChildrenAttribute extends GlobalJSXElementChildrenAttribute {}

type LibraryManagedAttributes<C, P> = GlobalJSXLibraryManagedAttributes<C, P>;

interface IntrinsicAttributes extends GlobalJSXIntrinsicAttributes {}
interface IntrinsicClassAttributes<T> extends GlobalJSXIntrinsicClassAttributes<T> {}
interface IntrinsicElements extends GlobalJSXIntrinsicElements {}
}
}

// naked 'any' type in a conditional type will short circuit and union both the then/else branches
Expand Down Expand Up @@ -3162,6 +3178,9 @@ type ReactManagedAttributes<C, P> = C extends { propTypes: infer T; defaultProps
: P;

declare global {
/**
* @deprecated Use `React.JSX` instead of the global `JSX` namespace.
*/
namespace JSX {
interface Element extends React.ReactElement<any, any> { }
interface ElementClass extends React.Component<any> {
Expand Down Expand Up @@ -3342,7 +3361,7 @@ declare global {
foreignObject: React.SVGProps<SVGForeignObjectElement>;
g: React.SVGProps<SVGGElement>;
image: React.SVGProps<SVGImageElement>;
line: React.SVGProps<SVGLineElement>;
line: React.SVGLineElementAttributes<SVGLineElement>;
linearGradient: React.SVGProps<SVGLinearGradientElement>;
marker: React.SVGProps<SVGMarkerElement>;
mask: React.SVGProps<SVGMaskElement>;
Expand All @@ -3357,11 +3376,26 @@ declare global {
stop: React.SVGProps<SVGStopElement>;
switch: React.SVGProps<SVGSwitchElement>;
symbol: React.SVGProps<SVGSymbolElement>;
text: React.SVGProps<SVGTextElement>;
text: React.SVGTextElementAttributes<SVGTextElement>;
textPath: React.SVGProps<SVGTextPathElement>;
tspan: React.SVGProps<SVGTSpanElement>;
use: React.SVGProps<SVGUseElement>;
view: React.SVGProps<SVGViewElement>;
}
}
}

// React.JSX needs to point to global.JSX to keep global module augmentations intact.
// But we can't access global.JSX so we need to create these aliases instead.
// Once the global JSX namespace will be removed we replace React.JSX with the contents of global.JSX
interface GlobalJSXElement extends JSX.Element {}
interface GlobalJSXElementClass extends JSX.ElementClass {}
interface GlobalJSXElementAttributesProperty extends JSX.ElementAttributesProperty {}
interface GlobalJSXElementChildrenAttribute extends JSX.ElementChildrenAttribute {}

type GlobalJSXLibraryManagedAttributes<C, P> = JSX.LibraryManagedAttributes<C, P>;

interface GlobalJSXIntrinsicAttributes extends JSX.IntrinsicAttributes {}
interface GlobalJSXIntrinsicClassAttributes<T> extends JSX.IntrinsicClassAttributes<T> {}

interface GlobalJSXIntrinsicElements extends JSX.IntrinsicElements {}
14 changes: 12 additions & 2 deletions types/react/jsx-dev-runtime.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
// Expose `JSX` namespace in `global` namespace
import './';
import * as React from './';

export namespace JSX {
interface Element extends React.JSX.Element {}
interface ElementClass extends React.JSX.ElementClass {}
interface ElementAttributesProperty extends React.JSX.ElementAttributesProperty {}
interface ElementChildrenAttribute extends React.JSX.ElementChildrenAttribute {}
type LibraryManagedAttributes<C, P> = React.JSX.LibraryManagedAttributes<C, P>;
interface IntrinsicAttributes extends React.JSX.IntrinsicAttributes {}
interface IntrinsicClassAttributes<T> extends React.JSX.IntrinsicClassAttributes<T> {}
interface IntrinsicElements extends React.JSX.IntrinsicElements {}
}
14 changes: 12 additions & 2 deletions types/react/jsx-runtime.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
// Expose `JSX` namespace in `global` namespace
import './';
import * as React from './';

export namespace JSX {
interface Element extends React.JSX.Element {}
interface ElementClass extends React.JSX.ElementClass {}
interface ElementAttributesProperty extends React.JSX.ElementAttributesProperty {}
interface ElementChildrenAttribute extends React.JSX.ElementChildrenAttribute {}
type LibraryManagedAttributes<C, P> = React.JSX.LibraryManagedAttributes<C, P>;
interface IntrinsicAttributes extends React.JSX.IntrinsicAttributes {}
interface IntrinsicClassAttributes<T> extends React.JSX.IntrinsicClassAttributes<T> {}
interface IntrinsicElements extends React.JSX.IntrinsicElements {}
}
18 changes: 16 additions & 2 deletions types/react/test/tsx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,15 +502,29 @@ const HasHref2: React.ElementType<{ href?: string | undefined }> = 'div';
const CustomElement: React.ElementType = 'my-undeclared-element';

// custom elements now need to be declared as intrinsic elements
declare global {
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'my-declared-element': {};
}
}
}

const CustomElement2: React.ElementType = 'my-declared-element';
// Augmentations of the global namespace flow into the scoped JSX namespace
// This is deprecated and will be removed in next next major of `@types/react`
declare global {
namespace JSX {
interface IntrinsicElements {
'my-declared-element-deprecated': {};
}
}
}

const CustomElement2: React.ElementType = 'my-declared-element-deprecated';
<my-declared-element-deprecated />;

const CustomElement3: React.ElementType = 'my-declared-element';
<my-declared-element />;

interface TestPropTypesProps {
foo: string;
Expand Down
38 changes: 36 additions & 2 deletions types/react/ts5.0/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,9 @@ declare namespace React {
interface SVGProps<T> extends SVGAttributes<T>, ClassAttributes<T> {
}

interface SVGLineElementAttributes<T> extends SVGProps<T> {}
interface SVGTextElementAttributes<T> extends SVGProps<T> {}

interface DOMAttributes<T> {
children?: ReactNode | undefined;
dangerouslySetInnerHTML?: {
Expand Down Expand Up @@ -3083,6 +3086,19 @@ declare namespace React {
*/
componentStack: string;
}

namespace JSX {
interface Element extends GlobalJSXElement {}
interface ElementClass extends GlobalJSXElementClass {}
interface ElementAttributesProperty extends GlobalJSXElementAttributesProperty {}
interface ElementChildrenAttribute extends GlobalJSXElementChildrenAttribute {}

type LibraryManagedAttributes<C, P> = GlobalJSXLibraryManagedAttributes<C, P>;

interface IntrinsicAttributes extends GlobalJSXIntrinsicAttributes {}
interface IntrinsicClassAttributes<T> extends GlobalJSXIntrinsicClassAttributes<T> {}
interface IntrinsicElements extends GlobalJSXIntrinsicElements {}
}
}

// naked 'any' type in a conditional type will short circuit and union both the then/else branches
Expand Down Expand Up @@ -3130,6 +3146,9 @@ type ReactManagedAttributes<C, P> = C extends { propTypes: infer T; defaultProps
: P;

declare global {
/**
* @deprecated Use `React.JSX` instead of the global `JSX` namespace.
*/
namespace JSX {
interface Element extends React.ReactElement<any, any> { }
interface ElementClass extends React.Component<any> {
Expand Down Expand Up @@ -3310,7 +3329,7 @@ declare global {
foreignObject: React.SVGProps<SVGForeignObjectElement>;
g: React.SVGProps<SVGGElement>;
image: React.SVGProps<SVGImageElement>;
line: React.SVGProps<SVGLineElement>;
line: React.SVGLineElementAttributes<SVGLineElement>;
linearGradient: React.SVGProps<SVGLinearGradientElement>;
marker: React.SVGProps<SVGMarkerElement>;
mask: React.SVGProps<SVGMaskElement>;
Expand All @@ -3325,11 +3344,26 @@ declare global {
stop: React.SVGProps<SVGStopElement>;
switch: React.SVGProps<SVGSwitchElement>;
symbol: React.SVGProps<SVGSymbolElement>;
text: React.SVGProps<SVGTextElement>;
text: React.SVGTextElementAttributes<SVGTextElement>;
textPath: React.SVGProps<SVGTextPathElement>;
tspan: React.SVGProps<SVGTSpanElement>;
use: React.SVGProps<SVGUseElement>;
view: React.SVGProps<SVGViewElement>;
}
}
}

// React.JSX needs to point to global.JSX to keep global module augmentations intact.
// But we can't access global.JSX so we need to create these aliases instead.
// Once the global JSX namespace will be removed we replace React.JSX with the contents of global.JSX
interface GlobalJSXElement extends JSX.Element {}
interface GlobalJSXElementClass extends JSX.ElementClass {}
interface GlobalJSXElementAttributesProperty extends JSX.ElementAttributesProperty {}
interface GlobalJSXElementChildrenAttribute extends JSX.ElementChildrenAttribute {}

type GlobalJSXLibraryManagedAttributes<C, P> = JSX.LibraryManagedAttributes<C, P>;

interface GlobalJSXIntrinsicAttributes extends JSX.IntrinsicAttributes {}
interface GlobalJSXIntrinsicClassAttributes<T> extends JSX.IntrinsicClassAttributes<T> {}

interface GlobalJSXIntrinsicElements extends JSX.IntrinsicElements {}
14 changes: 12 additions & 2 deletions types/react/ts5.0/jsx-dev-runtime.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
// Expose `JSX` namespace in `global` namespace
import './';
import * as React from './';

export namespace JSX {
interface Element extends React.JSX.Element {}
interface ElementClass extends React.JSX.ElementClass {}
interface ElementAttributesProperty extends React.JSX.ElementAttributesProperty {}
interface ElementChildrenAttribute extends React.JSX.ElementChildrenAttribute {}
type LibraryManagedAttributes<C, P> = React.JSX.LibraryManagedAttributes<C, P>;
interface IntrinsicAttributes extends React.JSX.IntrinsicAttributes {}
interface IntrinsicClassAttributes<T> extends React.JSX.IntrinsicClassAttributes<T> {}
interface IntrinsicElements extends React.JSX.IntrinsicElements {}
}
14 changes: 12 additions & 2 deletions types/react/ts5.0/jsx-runtime.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
// Expose `JSX` namespace in `global` namespace
import './';
import * as React from './';

export namespace JSX {
interface Element extends React.JSX.Element {}
interface ElementClass extends React.JSX.ElementClass {}
interface ElementAttributesProperty extends React.JSX.ElementAttributesProperty {}
interface ElementChildrenAttribute extends React.JSX.ElementChildrenAttribute {}
type LibraryManagedAttributes<C, P> = React.JSX.LibraryManagedAttributes<C, P>;
interface IntrinsicAttributes extends React.JSX.IntrinsicAttributes {}
interface IntrinsicClassAttributes<T> extends React.JSX.IntrinsicClassAttributes<T> {}
interface IntrinsicElements extends React.JSX.IntrinsicElements {}
}
18 changes: 16 additions & 2 deletions types/react/ts5.0/test/tsx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,15 +502,29 @@ const HasHref2: React.ElementType<{ href?: string | undefined }> = 'div';
const CustomElement: React.ElementType = 'my-undeclared-element';

// custom elements now need to be declared as intrinsic elements
declare global {
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'my-declared-element': {};
}
}
}

const CustomElement2: React.ElementType = 'my-declared-element';
// Augmentations of the global namespace flow into the scoped JSX namespace
// This is deprecated and will be removed in next next major of `@types/react`
declare global {
namespace JSX {
interface IntrinsicElements {
'my-declared-element-deprecated': {};
}
}
}

const CustomElement2: React.ElementType = 'my-declared-element-deprecated';
<my-declared-element-deprecated />;

const CustomElement3: React.ElementType = 'my-declared-element';
<my-declared-element />;

interface TestPropTypesProps {
foo: string;
Expand Down

0 comments on commit f1b2559

Please sign in to comment.