Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] no-unknown-property add properties onToggle, fill, as, pointer events, media events and touch events #3385

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange

## Unreleased

### Fixed
* [`no-unknown-property`]: add properties `onToggle`, `fill`, `as`, and pointer events ([#3385][] @sjarva)
* [`no-unknown-property`]: add `defaultChecked` property ([#3385][] @sjarva)
* [`no-unknown-property`]: add touch and media event related properties ([#3385][] @sjarva)
* [`no-unknown-property`]: `children` is always an acceptable prop; iframes have `scrolling`; ; video has `playsInline` ([#3385][] @ljharb)

[#3385]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3385

## [7.31.4] - 2022.09.04

### Fixed
Expand Down
81 changes: 75 additions & 6 deletions lib/rules/no-unknown-property.js
Expand Up @@ -30,9 +30,57 @@ const ATTRIBUTE_TAGS_MAP = {
checked: ['input'],
// image is required for SVG support, all other tags are HTML.
crossOrigin: ['script', 'img', 'video', 'audio', 'link', 'image'],
fill: ['svg'],
fill: [ // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill
// Fill color
'altGlyph',
'circle',
'ellipse',
'g',
'path',
'polygon',
'polyline',
'rect',
'svg',
'text',
'textPath',
'tref',
'tspan',
// Animation final state
'animate',
'animateColor',
'animateMotion',
'animateTransform',
'set',
],
property: ['meta'],
viewBox: ['svg'],
as: ['link'],
// Media events allowed only on audio and video tags, see https://github.com/facebook/react/blob/256aefbea1449869620fb26f6ec695536ab453f5/CHANGELOG.md#notable-enhancements
onAbort: ['audio', 'video'],
onCanPlay: ['audio', 'video'],
onCanPlayThrough: ['audio', 'video'],
onDurationChange: ['audio', 'video'],
onEmptied: ['audio', 'video'],
onEncrypted: ['audio', 'video'],
onEnded: ['audio', 'video'],
onError: ['audio', 'video'],
onLoadedData: ['audio', 'video'],
onLoadedMetadata: ['audio', 'video'],
onLoadStart: ['audio', 'video'],
onPause: ['audio', 'video'],
onPlay: ['audio', 'video'],
onPlaying: ['audio', 'video'],
onProgress: ['audio', 'video'],
onRateChange: ['audio', 'video'],
onSeeked: ['audio', 'video'],
onSeeking: ['audio', 'video'],
onStalled: ['audio', 'video'],
onSuspend: ['audio', 'video'],
onTimeUpdate: ['audio', 'video'],
onVolumeChange: ['audio', 'video'],
onWaiting: ['audio', 'video'],
scrolling: ['iframe'],
playsInline: ['video'],
};

const SVGDOM_ATTRIBUTE_NAMES = {
Expand Down Expand Up @@ -127,7 +175,7 @@ const DOM_PROPERTY_NAMES_ONE_WORD = [
// Element specific attributes
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes (includes global attributes too)
// To be considered if these should be added also to ATTRIBUTE_TAGS_MAP
'accept', 'action', 'allow', 'alt', 'async', 'buffered', 'capture', 'challenge', 'cite', 'code', 'cols',
'accept', 'action', 'allow', 'alt', 'as', 'async', 'buffered', 'capture', 'challenge', 'cite', 'code', 'cols',
'content', 'coords', 'csp', 'data', 'decoding', 'default', 'defer', 'disabled', 'form',
'headers', 'height', 'high', 'href', 'icon', 'importance', 'integrity', 'kind', 'label',
'language', 'loading', 'list', 'loop', 'low', 'max', 'media', 'method', 'min', 'multiple', 'muted',
Expand All @@ -149,7 +197,7 @@ const DOM_PROPERTY_NAMES_ONE_WORD = [
// OpenGraph meta tag attributes
'property',
// React specific attributes
'ref', 'key',
'ref', 'key', 'children',
];

const DOM_PROPERTY_NAMES_TWO_WORDS = [
Expand All @@ -168,7 +216,7 @@ const DOM_PROPERTY_NAMES_TWO_WORDS = [
'onCompositionUpdate', 'onCut', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave',
'onError', 'onFocus', 'onInput', 'onKeyDown', 'onKeyPress', 'onKeyUp', 'onLoad', 'onWheel', 'onDragOver',
'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver',
'onMouseUp', 'onPaste', 'onScroll', 'onSelect', 'onSubmit', 'onTransitionEnd', 'radioGroup', 'readOnly', 'referrerPolicy',
'onMouseUp', 'onPaste', 'onScroll', 'onSelect', 'onSubmit', 'onToggle', 'onTransitionEnd', 'radioGroup', 'readOnly', 'referrerPolicy',
'rowSpan', 'srcDoc', 'srcLang', 'srcSet', 'useMap',
// SVG attributes
// See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
Expand Down Expand Up @@ -204,7 +252,11 @@ const DOM_PROPERTY_NAMES_TWO_WORDS = [
'autoCorrect', // https://stackoverflow.com/questions/47985384/html-autocorrect-for-text-input-is-not-working
'autoSave', // https://stackoverflow.com/questions/25456396/what-is-autosave-attribute-supposed-to-do-how-do-i-use-it
// React specific attributes https://reactjs.org/docs/dom-elements.html#differences-in-attributes
'className', 'dangerouslySetInnerHTML', 'defaultValue', 'htmlFor', 'onChange', 'suppressContentEditableWarning', 'suppressHydrationWarning',
'className', 'dangerouslySetInnerHTML', 'defaultValue', 'defaultChecked', 'htmlFor', 'onChange',
'onInvalid', 'onReset', 'onTouchCancel', 'onTouchEnd', 'onTouchMove', 'onTouchStart', 'suppressContentEditableWarning', 'suppressHydrationWarning',
'onAbort', 'onCanPlay', 'onCanPlayThrough', 'onDurationChange', 'onEmptied', 'onEncrypted', 'onEnded',
'onLoadedData', 'onLoadedMetadata', 'onLoadStart', 'onPause', 'onPlay', 'onPlaying', 'onProgress', 'onRateChange',
'onSeeked', 'onSeeking', 'onStalled', 'onSuspend', 'onTimeUpdate', 'onVolumeChange', 'onWaiting',
];

const DOM_PROPERTIES_IGNORE_CASE = ['charset'];
Expand All @@ -226,11 +278,28 @@ const ARIA_PROPERTIES = [
'aria-posinset', 'aria-rowcount', 'aria-rowindex', 'aria-rowindextext', 'aria-rowspan', 'aria-setsize',
];

const REACT_ON_PROPS = [
'onGotPointerCapture',
'onLostPointerCapture',
'onPointerCancel',
'onPointerDown',
'onPointerEnter',
'onPointerLeave',
'onPointerMove',
'onPointerOut',
'onPointerOver',
'onPointerUp',
];

function getDOMPropertyNames(context) {
const ALL_DOM_PROPERTY_NAMES = DOM_PROPERTY_NAMES_TWO_WORDS.concat(DOM_PROPERTY_NAMES_ONE_WORD);
// this was removed in React v16.1+, see https://github.com/facebook/react/pull/10823
if (!testReactVersion(context, '>= 16.1.0')) {
return ['allowTransparency'].concat(ALL_DOM_PROPERTY_NAMES);
return ALL_DOM_PROPERTY_NAMES.concat('allowTransparency');
}
// these were added in React v16.4.0, see https://reactjs.org/blog/2018/05/23/react-v-16-4.html and https://github.com/facebook/react/pull/12507
if (testReactVersion(context, '>= 16.4.0')) {
return ALL_DOM_PROPERTY_NAMES.concat(REACT_ON_PROPS);
}
return ALL_DOM_PROPERTY_NAMES;
}
Expand Down
90 changes: 90 additions & 0 deletions tests/lib/rules/no-unknown-property.js
Expand Up @@ -48,11 +48,25 @@ ruleTester.run('no-unknown-property', rule, {
{ code: '<img src="cat_keyboard.jpeg" alt="A cat sleeping on a keyboard" />' },
{ code: '<input type="password" required />' },
{ code: '<input ref={this.input} type="radio" />' },
{ code: '<div children="anything" />' },
{ code: '<iframe scrolling="?" />' },
{ code: '<input key="bar" type="radio" />' },
{ code: '<button disabled>You cannot click me</button>;' },
{ code: '<svg key="lock" viewBox="box" fill={10} d="d" stroke={1} strokeWidth={2} strokeLinecap={3} strokeLinejoin={4} transform="something" clipRule="else" x1={5} x2="6" y1="7" y2="8"></svg>' },
{ code: '<g fill="#7B82A0" fillRule="evenodd"></g>' },
{ code: '<meta property="og:type" content="website" />' },
{ code: '<input type="checkbox" checked={checked} disabled={disabled} id={id} onChange={onChange} />' },
{ code: '<video playsInline />' },
{
code: '<div allowTransparency="true" />',
settings: {
react: { version: '16.0.99' },
},
},
// React related attributes
{ code: '<div onPointerDown={this.onDown} onPointerUp={this.onUp} />' },
{ code: '<input type="checkbox" defaultChecked={this.state.checkbox} />' },
{ code: '<div onTouchStart={this.startAnimation} onTouchEnd={this.stopAnimation} onTouchCancel={this.cancel} onTouchMove={this.move} />' },
// Case ignored attributes, for `charset` discussion see https://github.com/jsx-eslint/eslint-plugin-react/pull/1863
{ code: '<meta charset="utf-8" />;' },
{ code: '<meta charSet="utf-8" />;' },
Expand Down Expand Up @@ -82,8 +96,26 @@ ruleTester.run('no-unknown-property', rule, {
{ code: '<script crossOrigin />' },
{ code: '<audio crossOrigin />' },
{ code: '<svg><image crossOrigin /></svg>' },
{ code: '<details onToggle={this.onToggle}>Some details</details>' },
{ code: '<path fill="pink" d="M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z"></path>' },
{ code: '<link as="audio">Audio content</link>' },
{ code: '<audio onAbort={this.abort} onDurationChange={this.durationChange} onEmptied={this.emptied} onEnded={this.end} onError={this.error}></audio>' },
]),
invalid: parsers.all([
{
code: '<div allowTransparency="true" />',
settings: {
react: { version: '16.1.0' },
},
errors: [
{
messageId: 'unknownProp',
data: {
name: 'allowTransparency',
},
},
],
},
{
code: '<div hasOwnProperty="should not be allowed property"></div>;',
errors: [
Expand Down Expand Up @@ -298,5 +330,63 @@ ruleTester.run('no-unknown-property', rule, {
},
],
},
{
code: '<div as="audio" />',
errors: [
{
messageId: 'invalidPropOnTag',
data: {
name: 'as',
tagName: 'div',
allowedTags: 'link',
},
},
],
},
{
code: '<div onAbort={this.abort} onDurationChange={this.durationChange} onEmptied={this.emptied} onEnded={this.end} onError={this.error} />',
errors: [
{
messageId: 'invalidPropOnTag',
data: {
name: 'onAbort',
tagName: 'div',
allowedTags: 'audio, video',
},
},
{
messageId: 'invalidPropOnTag',
data: {
name: 'onDurationChange',
tagName: 'div',
allowedTags: 'audio, video',
},
},
{
messageId: 'invalidPropOnTag',
data: {
name: 'onEmptied',
tagName: 'div',
allowedTags: 'audio, video',
},
},
{
messageId: 'invalidPropOnTag',
data: {
name: 'onEnded',
tagName: 'div',
allowedTags: 'audio, video',
},
},
{
messageId: 'invalidPropOnTag',
data: {
name: 'onError',
tagName: 'div',
allowedTags: 'audio, video',
},
},
],
},
]),
});