Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jsx-eslint/eslint-plugin-react
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.31.6
Choose a base ref
...
head repository: jsx-eslint/eslint-plugin-react
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.31.7
Choose a head ref
  • 14 commits
  • 4 files changed
  • 6 contributors

Commits on Sep 4, 2022

  1. 1

    Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    d2c9bef View commit details

Commits on Sep 5, 2022

  1. [Fix] no-unknown-property: add download property support for a

    …and `area`
    
    Fixes #3393
    HJain13 authored and ljharb committed Sep 5, 2022

    Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    3b30527 View commit details
  2. Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    7ba7ec4 View commit details
  3. Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    4be5632 View commit details
  4. Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    c59ad4a View commit details
  5. Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    32119f9 View commit details
  6. [Fix] no-unknown-property: Add more one word properties found in De…

    …finitelyTyped's react/index.d.ts
    
    Manifest, summary, wmode, results, security
    sjarva authored and ljharb committed Sep 5, 2022

    Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    1c0c92b View commit details
  7. [Fix] no-unknown-property: add onError and onLoad for iframe/picture

    Fixes #3410.
    
    Co-authored-by: Emmanuel Vuigner <hi@maiis.me>
    Co-authored-by: Aarni Koskela <akx@iki.fi>
    2 people authored and ljharb committed Sep 5, 2022

    Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    033fcc5 View commit details
  8. [Fix] no-unknown-property: allow imageSrcSet and imageSizes att…

    …ributes on `<link>`
    
    Fixes #3401.
    terrymun authored and ljharb committed Sep 5, 2022

    Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    3306b92 View commit details
  9. Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    e8356ad View commit details
  10. Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    9de16a7 View commit details
  11. Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    64ad602 View commit details
  12. Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    850d801 View commit details
  13. Verified

    This commit was signed with the committer’s verified signature.
    ljharb Jordan Harband
    Copy the full SHA
    1c3af25 View commit details
Showing with 211 additions and 70 deletions.
  1. +29 −0 CHANGELOG.md
  2. +94 −59 lib/rules/no-unknown-property.js
  3. +3 −3 package.json
  4. +85 −8 tests/lib/rules/no-unknown-property.js
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -5,6 +5,35 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange

## Unreleased

## [7.31.7] - 2022.09.05

### Fixed
* [`no-unknown-property`]: avoid warning on `fbt` nodes entirely ([#3391][] @ljharb)
* [`no-unknown-property`]: add `download` property support for `a` and `area` ([#3394][] @HJain13)
* [`no-unknown-property`]: allow `webkitAllowFullScreen` and `mozAllowFullScreen` ([#3396][] @ljharb)
* [`no-unknown-property`]: `controlsList`, not `controlList` ([#3397][] @ljharb)
* [`no-unknown-property`]: add more capture event properties ([#3402][] @sjarva)
* [`no-unknown-property`]: Add more one word properties found in DefinitelyTyped's react/index.d.ts ([#3402][] @sjarva)
* [`no-unknown-property`]: Mark onLoad/onError as supported on iframes ([#3398][] @maiis, [#3406][] @akx)
* [`no-unknown-property`]: allow `imageSrcSet` and `imageSizes` attributes on `<link>` ([#3407][] @terrymun)
* [`no-unknown-property`]: add `border`; `focusable` on `<svg>` ([#3404][] [#3404][] @ljharb)
* [`no-unknown-property`]: React lowercases `data-` attrs ([#3395][] @ljharb)
* [`no-unknown-property`]: add `valign` on table components ([#3389][] @ljharb)

[7.31.7]: https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.31.6...v7.31.7
[#3407]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3407
[#3406]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3406
[#3405]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3405
[#3404]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3404
[#3402]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3402
[#3398]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3398
[#3397]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3397
[#3396]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3396
[#3395]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3395
[#3394]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3394
[#3391]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3391
[#3389]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3389

## [7.31.6] - 2022.09.04

### Fixed
153 changes: 94 additions & 59 deletions lib/rules/no-unknown-property.js
Original file line number Diff line number Diff line change
@@ -30,6 +30,8 @@ const ATTRIBUTE_TAGS_MAP = {
checked: ['input'],
// image is required for SVG support, all other tags are HTML.
crossOrigin: ['script', 'img', 'video', 'audio', 'link', 'image'],
// https://html.spec.whatwg.org/multipage/links.html#downloading-resources
download: ['a', 'area'],
fill: [ // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill
// Fill color
'altGlyph',
@@ -55,9 +57,13 @@ const ATTRIBUTE_TAGS_MAP = {
'animateTransform',
'set',
],
focusable: ['svg'],
imageSizes: ['link'],
imageSrcSet: ['link'],
property: ['meta'],
viewBox: ['svg'],
as: ['link'],
valign: ['tr', 'td', 'th', 'thead', 'tbody', 'tfoot', 'colgroup', 'col'], // deprecated, but known
// 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'],
@@ -66,8 +72,8 @@ const ATTRIBUTE_TAGS_MAP = {
onEmptied: ['audio', 'video'],
onEncrypted: ['audio', 'video'],
onEnded: ['audio', 'video'],
onError: ['audio', 'video', 'img', 'link', 'source', 'script'],
onLoad: ['script', 'img', 'link'],
onError: ['audio', 'video', 'img', 'link', 'source', 'script', 'picture', 'iframe'],
onLoad: ['script', 'img', 'link', 'picture', 'iframe'],
onLoadedData: ['audio', 'video'],
onLoadedMetadata: ['audio', 'video'],
onLoadStart: ['audio', 'video'],
@@ -83,18 +89,17 @@ const ATTRIBUTE_TAGS_MAP = {
onTimeUpdate: ['audio', 'video'],
onVolumeChange: ['audio', 'video'],
onWaiting: ['audio', 'video'],
scrolling: ['iframe'],
playsInline: ['video'],
// Video related attributes
autoPictureInPicture: ['video'],
controls: ['audio', 'video'],
controlList: ['video'],
controlsList: ['audio', 'video'],
disablePictureInPicture: ['video'],
disableRemotePlayback: ['audio', 'video'],
loop: ['audio', 'video'],
muted: ['audio', 'video'],
playsInline: ['video'],
poster: ['video'],
preload: ['audio', 'video'],
scrolling: ['iframe'],
};

const SVGDOM_ATTRIBUTE_NAMES = {
@@ -192,10 +197,10 @@ const DOM_PROPERTY_NAMES_ONE_WORD = [
'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',
'language', 'loading', 'list', 'loop', 'low', 'manifest', 'max', 'media', 'method', 'min', 'multiple', 'muted',
'name', 'open', 'optimum', 'pattern', 'ping', 'placeholder', 'poster', 'preload', 'profile',
'rel', 'required', 'reversed', 'role', 'rows', 'sandbox', 'scope', 'selected', 'shape', 'size', 'sizes',
'span', 'src', 'start', 'step', 'target', 'type', 'value', 'width', 'wrap',
'rel', 'required', 'reversed', 'role', 'rows', 'sandbox', 'scope', 'seamless', 'selected', 'shape', 'size', 'sizes',
'span', 'src', 'start', 'step', 'summary', 'target', 'type', 'value', 'width', 'wmode', 'wrap',
// SVG attributes
// See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
'accumulate', 'additive', 'alphabetic', 'amplitude', 'ascent', 'azimuth', 'bbox', 'begin',
@@ -212,6 +217,8 @@ const DOM_PROPERTY_NAMES_ONE_WORD = [
'property',
// React specific attributes
'ref', 'key', 'children',
// Non-standard
'results', 'security',
// Video specific
'controls',
];
@@ -224,9 +231,9 @@ const DOM_PROPERTY_NAMES_TWO_WORDS = [
// 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
'acceptCharset', 'autoComplete', 'autoPlay', 'cellPadding', 'cellSpacing', 'classID', 'codeBase',
'acceptCharset', 'autoComplete', 'autoPlay', 'border', 'cellPadding', 'cellSpacing', 'classID', 'codeBase',
'colSpan', 'contextMenu', 'dateTime', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget',
'frameBorder', 'hrefLang', 'httpEquiv', 'isMap', 'keyParams', 'keyType', 'marginHeight', 'marginWidth',
'frameBorder', 'hrefLang', 'httpEquiv', 'imageSizes', 'imageSrcSet', 'isMap', 'keyParams', 'keyType', 'marginHeight', 'marginWidth',
'maxLength', 'mediaGroup', 'minLength', 'noValidate', 'onAnimationEnd', 'onAnimationIteration', 'onAnimationStart',
'onBlur', 'onChange', 'onClick', 'onContextMenu', 'onCopy', 'onCompositionEnd', 'onCompositionStart',
'onCompositionUpdate', 'onCut', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave',
@@ -268,17 +275,31 @@ 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', 'defaultChecked', 'htmlFor', 'onChange',
'className', 'dangerouslySetInnerHTML', 'defaultValue', 'defaultChecked', 'htmlFor',
// Events' capture events
'onBeforeInput', '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',
'onMouseMoveCapture',
// Video specific,
'autoPictureInPicture', 'controlList', 'disablePictureInPicture', 'disableRemotePlayback',
'onCopyCapture', 'onCutCapture', 'onPasteCapture', 'onCompositionEndCapture', 'onCompositionStartCapture', 'onCompositionUpdateCapture',
'onFocusCapture', 'onBlurCapture', 'onChangeCapture', 'onBeforeInputCapture', 'onInputCapture', 'onResetCapture', 'onSubmitCapture',
'onInvalidCapture', 'onLoadCapture', 'onErrorCapture', 'onKeyDownCapture', 'onKeyPressCapture', 'onKeyUpCapture',
'onAbortCapture', 'onCanPlayCapture', 'onCanPlayThroughCapture', 'onDurationChangeCapture', 'onEmptiedCapture', 'onEncryptedCapture',
'onEndedCapture', 'onLoadedDataCapture', 'onLoadedMetadataCapture', 'onLoadStartCapture', 'onPauseCapture', 'onPlayCapture',
'onPlayingCapture', 'onProgressCapture', 'onRateChangeCapture', 'onSeekedCapture', 'onSeekingCapture', 'onStalledCapture', 'onSuspendCapture',
'onTimeUpdateCapture', 'onVolumeChangeCapture', 'onWaitingCapture', 'onSelectCapture', 'onTouchCancelCapture', 'onTouchEndCapture',
'onTouchMoveCapture', 'onTouchStartCapture', 'onScrollCapture', 'onWheelCapture', 'onAnimationEndCapture', 'onAnimationIteration',
'onAnimationStartCapture', 'onTransitionEndCapture',
'onAuxClick', 'onAuxClickCapture', 'onClickCapture', 'onContextMenuCapture', 'onDoubleClickCapture',
'onDragCapture', 'onDragEndCapture', 'onDragEnterCapture', 'onDragExitCapture', 'onDragLeaveCapture',
'onDragOverCapture', 'onDragStartCapture', 'onDropCapture', 'onMouseDown', 'onMouseDownCapture',
'onMouseMoveCapture', 'onMouseOutCapture', 'onMouseOverCapture', 'onMouseUpCapture',
// Video specific
'autoPictureInPicture', 'controlsList', 'disablePictureInPicture', 'disableRemotePlayback',
];

const DOM_PROPERTIES_IGNORE_CASE = ['charset', 'allowfullscreen'];
const DOM_PROPERTIES_IGNORE_CASE = ['charset', 'allowFullScreen', 'webkitAllowFullScreen', 'mozAllowFullScreen'];

const ARIA_PROPERTIES = [
// See https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes
@@ -299,15 +320,26 @@ const ARIA_PROPERTIES = [

const REACT_ON_PROPS = [
'onGotPointerCapture',
'onGotPointerCaptureCapture',
'onLostPointerCapture',
'onLostPointerCapture',
'onLostPointerCaptureCapture',
'onPointerCancel',
'onPointerCancelCapture',
'onPointerDown',
'onPointerDownCapture',
'onPointerEnter',
'onPointerEnterCapture',
'onPointerLeave',
'onPointerLeaveCapture',
'onPointerMove',
'onPointerMoveCapture',
'onPointerOut',
'onPointerOutCapture',
'onPointerOver',
'onPointerOverCapture',
'onPointerUp',
'onPointerUpCapture',
];

function getDOMPropertyNames(context) {
@@ -356,15 +388,15 @@ function isValidHTMLTagInJSX(childNode) {

/**
* Checks if an attribute name is a valid `data-*` attribute:
* if the name starts with "data-" and has some lowcase (a to z) words that can contain numbers, separated but hyphens (-)
* (which is also called "kebab case" or "dash case"), then the attribute is valid data attribute.
* if the name starts with "data-" and has alphanumeric words (browsers require lowercase, but React and TS lowercase them),
* not start with any casing of "xml", and separated by hyphens (-) (which is also called "kebab case" or "dash case"),
* then the attribute is a valid data attribute.
*
* @param {String} name - Attribute name to be tested
* @returns {boolean} Result
*/
function isValidDataAttribute(name) {
const dataAttrConvention = /^data(-[a-z1-9]*)*$/;
return !!dataAttrConvention.test(name);
return /^data(-[^:]*)*$/.test(name) && !/^data-xml/i.test(name);
}

/**
@@ -393,7 +425,7 @@ function isValidAriaAttribute(name) {
*/

function isCaseIgnoredAttribute(name) {
return DOM_PROPERTIES_IGNORE_CASE.some((element) => element === name.toLowerCase());
return DOM_PROPERTIES_IGNORE_CASE.some((element) => element.toLowerCase() === name.toLowerCase());
}

/**
@@ -502,60 +534,63 @@ module.exports = {

const tagName = getTagName(node);

// Let's dive deeper into tags that are HTML/DOM elements (`<button>`), and not React components (`<Button />`)
if (isValidHTMLTagInJSX(node)) {
// Some attributes are allowed on some tags only
const allowedTags = has(ATTRIBUTE_TAGS_MAP, name) ? ATTRIBUTE_TAGS_MAP[name] : null;
if (tagName && allowedTags) {
// Scenario 1A: Allowed attribute found where not supposed to, report it
if (allowedTags.indexOf(tagName) === -1) {
report(context, messages.invalidPropOnTag, 'invalidPropOnTag', {
node,
data: {
name,
tagName,
allowedTags: allowedTags.join(', '),
},
});
}
// Scenario 1B: There are allowed attributes on allowed tags, no need to report it
return;
}

// Let's see if the attribute is a close version to some standard property name
const standardName = getStandardName(name, context);
if (tagName === 'fbt') { return; } // fbt nodes are bonkers, let's not go there

const hasStandardNameButIsNotUsed = standardName && standardName !== name;
const usesStandardName = standardName && standardName === name;
if (!isValidHTMLTagInJSX(node)) { return; }

if (usesStandardName) {
// Scenario 2A: The attribute name is the standard name, no need to report it
return;
}
// Let's dive deeper into tags that are HTML/DOM elements (`<button>`), and not React components (`<Button />`)

if (hasStandardNameButIsNotUsed) {
// Scenario 2B: The name of the attribute is close to a standard one, report it with the standard name
report(context, messages.unknownPropWithStandardName, 'unknownPropWithStandardName', {
// Some attributes are allowed on some tags only
const allowedTags = has(ATTRIBUTE_TAGS_MAP, name) ? ATTRIBUTE_TAGS_MAP[name] : null;
if (tagName && allowedTags) {
// Scenario 1A: Allowed attribute found where not supposed to, report it
if (allowedTags.indexOf(tagName) === -1) {
report(context, messages.invalidPropOnTag, 'invalidPropOnTag', {
node,
data: {
name,
standardName,
},
fix(fixer) {
return fixer.replaceText(node.name, standardName);
tagName,
allowedTags: allowedTags.join(', '),
},
});
return;
}
// Scenario 1B: There are allowed attributes on allowed tags, no need to report it
return;
}

// Let's see if the attribute is a close version to some standard property name
const standardName = getStandardName(name, context);

// Scenario 3: We have an attribute that is unknown, report it
report(context, messages.unknownProp, 'unknownProp', {
const hasStandardNameButIsNotUsed = standardName && standardName !== name;
const usesStandardName = standardName && standardName === name;

if (usesStandardName) {
// Scenario 2A: The attribute name is the standard name, no need to report it
return;
}

if (hasStandardNameButIsNotUsed) {
// Scenario 2B: The name of the attribute is close to a standard one, report it with the standard name
report(context, messages.unknownPropWithStandardName, 'unknownPropWithStandardName', {
node,
data: {
name,
standardName,
},
fix(fixer) {
return fixer.replaceText(node.name, standardName);
},
});
return;
}

// Scenario 3: We have an attribute that is unknown, report it
report(context, messages.unknownProp, 'unknownProp', {
node,
data: {
name,
},
});
},
};
},
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-react",
"version": "7.31.6",
"version": "7.31.7",
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>",
"description": "React specific linting rules for ESLint",
"main": "index.js",
@@ -41,9 +41,9 @@
"string.prototype.matchall": "^4.0.7"
},
"devDependencies": {
"@babel/core": "^7.18.13",
"@babel/core": "^7.19.0",
"@babel/eslint-parser": "^7.18.9",
"@babel/plugin-syntax-decorators": "^7.18.6",
"@babel/plugin-syntax-decorators": "^7.19.0",
"@babel/plugin-syntax-do-expressions": "^7.18.6",
"@babel/plugin-syntax-function-bind": "^7.18.6",
"@babel/preset-react": "^7.18.6",
93 changes: 85 additions & 8 deletions tests/lib/rules/no-unknown-property.js
Original file line number Diff line number Diff line change
@@ -44,12 +44,13 @@ ruleTester.run('no-unknown-property', rule, {
// Some HTML/DOM elements with common attributes should work
{ code: '<div className="bar"></div>;' },
{ code: '<div onMouseDown={this._onMouseDown}></div>;' },
{ code: '<a href="someLink">Read more</a>' },
{ code: '<a href="someLink" download="foo">Read more</a>' },
{ code: '<area download="foo" />' },
{ 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: '<iframe scrolling="?" onLoad={a} onError={b} />' },
{ 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>' },
@@ -59,9 +60,14 @@ ruleTester.run('no-unknown-property', rule, {
{ code: '<input type="checkbox" checked={checked} disabled={disabled} id={id} onChange={onChange} />' },
{ code: '<video playsInline />' },
{ code: '<img onError={foo} onLoad={bar} />' },
{ code: '<picture onError={foo} onLoad={bar} />' },
{ code: '<iframe onError={foo} onLoad={bar} />' },
{ code: '<script onLoad={bar} onError={foo} />' },
{ code: '<source onError={foo} />' },
{ code: '<link onLoad={bar} onError={foo} />' },
{ code: '<link rel="preload" as="image" href="someHref" imageSrcSet="someImageSrcSet" imageSizes="someImageSizes" />' },
{ code: '<div allowFullScreen webkitAllowFullScreen mozAllowFullScreen />' },
{ code: '<table border="1" />' },
{
code: '<div allowTransparency="true" />',
settings: {
@@ -71,7 +77,7 @@ ruleTester.run('no-unknown-property', rule, {
// 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} onMouseMoveCapture={this.capture} />' },
{ code: '<div onTouchStart={this.startAnimation} onTouchEnd={this.stopAnimation} onTouchCancel={this.cancel} onTouchMove={this.move} onMouseMoveCapture={this.capture} onTouchCancelCapture={this.log} />' },
// 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" />;' },
@@ -85,6 +91,7 @@ ruleTester.run('no-unknown-property', rule, {
{ code: '<div data-parent="parent"></div>;' },
{ code: '<div data-index-number="1234"></div>;' },
{ code: '<div data-e2e-id="5678"></div>;' },
{ code: '<div data-testID="bar" data-under_sCoRe="bar" />;' },
// Ignoring should work
{
code: '<div class="bar"></div>;',
@@ -105,13 +112,33 @@ ruleTester.run('no-unknown-property', rule, {
// Attributes on allowed elements should work
{ code: '<script crossOrigin />' },
{ code: '<audio crossOrigin />' },
{ code: '<svg><image crossOrigin /></svg>' },
{ code: '<svg focusable><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: '<line fill="pink" x1="0" y1="80" x2="100" y2="20"></line>' },
{ code: '<link as="audio">Audio content</link>' },
{ code: '<video controls={this.controls} loop={true} muted={false} src={this.videoSrc} playsInline={true}></video>' },
{ code: '<audio controls={this.controls} crossOrigin="anonymous" disableRemotePlayback loop muted preload="none" src="something" onAbort={this.abort} onDurationChange={this.durationChange} onEmptied={this.emptied} onEnded={this.end} onError={this.error}></audio>' },
{ code: '<video controlsList="nodownload" controls={this.controls} loop={true} muted={false} src={this.videoSrc} playsInline={true}></video>' },
{ code: '<audio controlsList="nodownload" controls={this.controls} crossOrigin="anonymous" disableRemotePlayback loop muted preload="none" src="something" onAbort={this.abort} onDurationChange={this.durationChange} onEmptied={this.emptied} onEnded={this.end} onError={this.error}></audio>' },
{
code: `
<table>
<colgroup valign="top">
<col valign="top" />
</colgroup>
<thead valign="top">
<tr valign="top">
<th valign="top">Header</th>
<td valign="top">Cell</td>
</tr>
</thead>
<tbody valign="top" />
<tfoot valign="top" />
</table>
`,
},

// fbt
{ code: '<fbt desc="foo" doNotExtract />;' },
]),
invalid: parsers.all([
{
@@ -395,7 +422,7 @@ ruleTester.run('no-unknown-property', rule, {
data: {
name: 'onError',
tagName: 'div',
allowedTags: 'audio, video, img, link, source, script',
allowedTags: 'audio, video, img, link, source, script, picture, iframe',
},
},
],
@@ -408,7 +435,7 @@ ruleTester.run('no-unknown-property', rule, {
data: {
name: 'onLoad',
tagName: 'div',
allowedTags: 'script, img, link',
allowedTags: 'script, img, link, picture, iframe',
},
},
],
@@ -463,5 +490,55 @@ ruleTester.run('no-unknown-property', rule, {
},
],
},
{
code: '<div download="foo" />',
errors: [
{
messageId: 'invalidPropOnTag',
data: {
name: 'download',
tagName: 'div',
allowedTags: 'a, area',
},
},
],
},
{
code: '<div imageSrcSet="someImageSrcSet" />',
errors: [
{
messageId: 'invalidPropOnTag',
data: {
name: 'imageSrcSet',
tagName: 'div',
allowedTags: 'link',
},
},
],
},
{
code: '<div imageSizes="someImageSizes" />',
errors: [
{
messageId: 'invalidPropOnTag',
data: {
name: 'imageSizes',
tagName: 'div',
allowedTags: 'link',
},
},
],
},
{
code: '<div data-xml-anything="invalid" />',
errors: [
{
messageId: 'unknownProp',
data: {
name: 'data-xml-anything',
},
},
],
},
]),
});