Skip to content

Commit 8c4761a

Browse files
motiz88Ville Saukkonen
authored andcommitted
React DOM: Restrict content props on void HTML elements
1 parent f85289d commit 8c4761a

File tree

3 files changed

+78
-18
lines changed

3 files changed

+78
-18
lines changed

lib/react-dom.js

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -274,24 +274,24 @@ declare type $JSXIntrinsics = {
274274
a: {instance: HTMLAnchorElement, props: ReactDOM$HTMLElementProps},
275275
abbr: ReactDOM$HTMLElementJSXIntrinsic,
276276
address: ReactDOM$HTMLElementJSXIntrinsic,
277-
area: ReactDOM$HTMLElementJSXIntrinsic,
277+
area: {instance: HTMLElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
278278
article: ReactDOM$HTMLElementJSXIntrinsic,
279279
aside: ReactDOM$HTMLElementJSXIntrinsic,
280280
audio: {instance: HTMLAudioElement, props: ReactDOM$HTMLElementProps},
281281
b: ReactDOM$HTMLElementJSXIntrinsic,
282-
base: ReactDOM$HTMLElementJSXIntrinsic,
282+
base: {instance: HTMLElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
283283
bdi: ReactDOM$HTMLElementJSXIntrinsic,
284284
bdo: ReactDOM$HTMLElementJSXIntrinsic,
285285
big: ReactDOM$HTMLElementJSXIntrinsic,
286286
blockquote: ReactDOM$HTMLElementJSXIntrinsic,
287287
body: ReactDOM$HTMLElementJSXIntrinsic,
288-
br: {instance: HTMLBRElement, props: ReactDOM$HTMLElementProps},
288+
br: {instance: HTMLBRElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
289289
button: {instance: HTMLButtonElement, props: ReactDOM$HTMLElementProps},
290290
canvas: {instance: HTMLCanvasElement, props: ReactDOM$HTMLElementProps},
291291
caption: {instance: HTMLTableCaptionElement, props: ReactDOM$HTMLElementProps},
292292
cite: ReactDOM$HTMLElementJSXIntrinsic,
293293
code: ReactDOM$HTMLElementJSXIntrinsic,
294-
col: ReactDOM$HTMLElementJSXIntrinsic,
294+
col: {instance: HTMLElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
295295
colgroup: ReactDOM$HTMLElementJSXIntrinsic,
296296
data: ReactDOM$HTMLElementJSXIntrinsic,
297297
datalist: ReactDOM$HTMLElementJSXIntrinsic,
@@ -304,7 +304,7 @@ declare type $JSXIntrinsics = {
304304
dl: {instance: HTMLDListElement, props: ReactDOM$HTMLElementProps},
305305
dt: ReactDOM$HTMLElementJSXIntrinsic,
306306
em: ReactDOM$HTMLElementJSXIntrinsic,
307-
embed: ReactDOM$HTMLElementJSXIntrinsic,
307+
embed: {instance: HTMLElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
308308
fieldset: {instance: HTMLFieldSetElement, props: ReactDOM$HTMLElementProps},
309309
figcaption: ReactDOM$HTMLElementJSXIntrinsic,
310310
figure: ReactDOM$HTMLElementJSXIntrinsic,
@@ -319,24 +319,24 @@ declare type $JSXIntrinsics = {
319319
head: ReactDOM$HTMLElementJSXIntrinsic,
320320
header: ReactDOM$HTMLElementJSXIntrinsic,
321321
hgroup: ReactDOM$HTMLElementJSXIntrinsic,
322-
hr: {instance: HTMLHRElement, props: ReactDOM$HTMLElementProps},
322+
hr: {instance: HTMLHRElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
323323
html: ReactDOM$HTMLElementJSXIntrinsic,
324324
i: ReactDOM$HTMLElementJSXIntrinsic,
325325
iframe: {instance: HTMLIFrameElement, props: ReactDOM$HTMLElementProps},
326-
img: {instance: HTMLImageElement, props: ReactDOM$HTMLElementProps},
326+
img: {instance: HTMLImageElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
327327
ins: ReactDOM$HTMLElementJSXIntrinsic,
328328
kbd: ReactDOM$HTMLElementJSXIntrinsic,
329-
keygen: ReactDOM$HTMLElementJSXIntrinsic,
329+
keygen: {instance: HTMLElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
330330
label: {instance: HTMLLabelElement, props: ReactDOM$HTMLElementProps},
331331
legend: {instance: HTMLLegendElement, props: ReactDOM$HTMLElementProps},
332332
li: {instance: HTMLLIElement, props: ReactDOM$HTMLElementProps},
333-
link: {instance: HTMLLinkElement, props: ReactDOM$HTMLElementProps},
333+
link: {instance: HTMLLinkElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
334334
main: ReactDOM$HTMLElementJSXIntrinsic,
335335
map: ReactDOM$HTMLElementJSXIntrinsic,
336336
mark: ReactDOM$HTMLElementJSXIntrinsic,
337337
menu: ReactDOM$HTMLElementJSXIntrinsic,
338-
menuitem: ReactDOM$HTMLElementJSXIntrinsic,
339-
meta: {instance: HTMLMetaElement, props: ReactDOM$HTMLElementProps},
338+
menuitem: {instance: HTMLElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
339+
meta: {instance: HTMLMetaElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
340340
meter: ReactDOM$HTMLElementJSXIntrinsic,
341341
nav: ReactDOM$HTMLElementJSXIntrinsic,
342342
noscript: ReactDOM$HTMLElementJSXIntrinsic,
@@ -346,7 +346,7 @@ declare type $JSXIntrinsics = {
346346
option: {instance: HTMLOptionElement, props: ReactDOM$HTMLElementProps},
347347
output: ReactDOM$HTMLElementJSXIntrinsic,
348348
p: {instance: HTMLParagraphElement, props: ReactDOM$HTMLElementProps},
349-
param: ReactDOM$HTMLElementJSXIntrinsic,
349+
param: {instance: HTMLElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
350350
picture: ReactDOM$HTMLElementJSXIntrinsic,
351351
pre: {instance: HTMLPreElement, props: ReactDOM$HTMLElementProps},
352352
progress: ReactDOM$HTMLElementJSXIntrinsic,
@@ -359,7 +359,7 @@ declare type $JSXIntrinsics = {
359359
script: {instance: HTMLScriptElement, props: ReactDOM$HTMLElementProps},
360360
section: ReactDOM$HTMLElementJSXIntrinsic,
361361
small: ReactDOM$HTMLElementJSXIntrinsic,
362-
source: {instance: HTMLSourceElement, props: ReactDOM$HTMLElementProps},
362+
source: {instance: HTMLSourceElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
363363
span: {instance: HTMLSpanElement, props: ReactDOM$HTMLElementProps},
364364
strong: ReactDOM$HTMLElementJSXIntrinsic,
365365
style: {instance: HTMLStyleElement, props: ReactDOM$HTMLElementProps},
@@ -375,12 +375,12 @@ declare type $JSXIntrinsics = {
375375
time: ReactDOM$HTMLElementJSXIntrinsic,
376376
title: ReactDOM$HTMLElementJSXIntrinsic,
377377
tr: {instance: HTMLTableRowElement, props: ReactDOM$HTMLElementProps},
378-
track: ReactDOM$HTMLElementJSXIntrinsic,
378+
track: {instance: HTMLElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
379379
u: ReactDOM$HTMLElementJSXIntrinsic,
380380
ul: {instance: HTMLUListElement, props: ReactDOM$HTMLElementProps},
381381
'var': ReactDOM$HTMLElementJSXIntrinsic,
382382
video: {instance: HTMLVideoElement, props: ReactDOM$HTMLElementProps},
383-
wbr: ReactDOM$HTMLElementJSXIntrinsic,
383+
wbr: {instance: HTMLElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
384384
// SVG
385385
svg: ReactDOM$SVGElementJSXIntrinsic,
386386
animate: ReactDOM$SVGElementJSXIntrinsic,
@@ -404,7 +404,7 @@ declare type $JSXIntrinsics = {
404404
tspan: ReactDOM$SVGElementJSXIntrinsic,
405405
use: ReactDOM$SVGElementJSXIntrinsic,
406406
// Elements React adds extra props for.
407-
input: {instance: HTMLInputElement, props: ReactDOM$HTMLElementProps},
407+
input: {instance: HTMLInputElement, props: ReactDOM$VoidElementProps<ReactDOM$HTMLElementProps>},
408408
textarea: {instance: HTMLTextAreaElement, props: ReactDOM$HTMLElementProps},
409409
select: {instance: HTMLSelectElement, props: ReactDOM$HTMLElementProps},
410410
// Catch-all for custom elements.
@@ -505,4 +505,19 @@ type ReactDOM$Style = Object; // TODO
505505

506506
// NOTE: In principle this can be `number | string`, but constraining values to
507507
// numbers might be a useful design choice.
508-
type ReactDOM$Number = number;
508+
type ReactDOM$Number = number;
509+
510+
// Changes children and dangerouslySetInnerHTML in a given props type to be
511+
// optional (if they're not already) and limits them to empty values.
512+
// This helps model the React dev mode warning emitted when setting either of
513+
// these props on an empty HTML element.
514+
type ReactDOM$VoidElementProps<Props> = $Diff<
515+
Props,
516+
{
517+
children: any,
518+
dangerouslySetInnerHTML: any
519+
}
520+
> & {
521+
children?: ?void, // Empty elements cannot have children
522+
dangerouslySetInnerHTML?: ?void // innerHTML cannot be set on empty elements
523+
};

tests/react_dom/react_dom.exp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,5 +414,40 @@ References:
414414
^^^^^^^ [2]
415415

416416

417+
Error ----------------------------------------------------------------------------------------- void_element_tags.js:7:1
417418

418-
Found 20 errors
419+
Cannot create `menuitem` element because object literal [1] is incompatible with undefined [2] in property
420+
`dangerouslySetInnerHTML`.
421+
422+
void_element_tags.js:7:1
423+
7| <menuitem dangerouslySetInnerHTML={{ __html: "Content" }} />; // Error
424+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
425+
426+
References:
427+
void_element_tags.js:7:36
428+
7| <menuitem dangerouslySetInnerHTML={{ __html: "Content" }} />; // Error
429+
^^^^^^^^^^^^^^^^^^^^^ [1]
430+
<BUILTINS>/react-dom.js:499:30
431+
499| dangerouslySetInnerHTML?: ?void // innerHTML cannot be set on empty elements
432+
^^^^ [2]
433+
434+
435+
Error ----------------------------------------------------------------------------------------- void_element_tags.js:8:1
436+
437+
Cannot create `br` element because JSX text [1] is incompatible with undefined [2] in property `children`.
438+
439+
void_element_tags.js:8:1
440+
8| <br>Content</br>; // Error
441+
^^^^^^^^^^^^^^^^
442+
443+
References:
444+
void_element_tags.js:8:5
445+
8| <br>Content</br>; // Error
446+
^^^^^^^ [1]
447+
<BUILTINS>/react-dom.js:498:15
448+
498| children?: ?void, // Empty elements cannot have children
449+
^^^^ [2]
450+
451+
452+
453+
Found 22 errors
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @flow
2+
3+
import React from "react";
4+
5+
<menuitem />; // OK
6+
<br />; // OK
7+
<menuitem dangerouslySetInnerHTML={{ __html: "Content" }} />; // Error
8+
<br>Content</br>; // Error
9+
<menuitem dangerouslySetInnerHTML={null} />; // OK
10+
<br>{null}</br>; // OK

0 commit comments

Comments
 (0)