eslint-plugin-react
Advanced tools
Comparing version 7.31.6 to 7.31.7
@@ -33,2 +33,4 @@ /** | ||
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 | ||
@@ -59,5 +61,9 @@ // Fill color | ||
], | ||
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 | ||
@@ -71,4 +77,4 @@ onAbort: ['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'], | ||
@@ -89,8 +95,5 @@ onLoadedMetadata: ['audio', 'video'], | ||
onWaiting: ['audio', 'video'], | ||
scrolling: ['iframe'], | ||
playsInline: ['video'], | ||
// Video related attributes | ||
autoPictureInPicture: ['video'], | ||
controls: ['audio', 'video'], | ||
controlList: ['video'], | ||
controlsList: ['audio', 'video'], | ||
disablePictureInPicture: ['video'], | ||
@@ -100,4 +103,6 @@ disableRemotePlayback: ['audio', 'video'], | ||
muted: ['audio', 'video'], | ||
playsInline: ['video'], | ||
poster: ['video'], | ||
preload: ['audio', 'video'], | ||
scrolling: ['iframe'], | ||
}; | ||
@@ -200,6 +205,6 @@ | ||
'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 | ||
@@ -221,2 +226,4 @@ // See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute | ||
'ref', 'key', 'children', | ||
// Non-standard | ||
'results', 'security', | ||
// Video specific | ||
@@ -234,5 +241,5 @@ 'controls', | ||
// 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', | ||
@@ -279,3 +286,5 @@ 'onBlur', 'onChange', 'onClick', 'onContextMenu', 'onCopy', 'onCompositionEnd', 'onCompositionStart', | ||
// 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', | ||
@@ -285,8 +294,20 @@ 'onAbort', 'onCanPlay', 'onCanPlayThrough', 'onDurationChange', 'onEmptied', 'onEncrypted', 'onEnded', | ||
'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']; | ||
@@ -312,11 +333,22 @@ const ARIA_PROPERTIES = [ | ||
'onGotPointerCapture', | ||
'onGotPointerCaptureCapture', | ||
'onLostPointerCapture', | ||
'onLostPointerCapture', | ||
'onLostPointerCaptureCapture', | ||
'onPointerCancel', | ||
'onPointerCancelCapture', | ||
'onPointerDown', | ||
'onPointerDownCapture', | ||
'onPointerEnter', | ||
'onPointerEnterCapture', | ||
'onPointerLeave', | ||
'onPointerLeaveCapture', | ||
'onPointerMove', | ||
'onPointerMoveCapture', | ||
'onPointerOut', | ||
'onPointerOutCapture', | ||
'onPointerOver', | ||
'onPointerOverCapture', | ||
'onPointerUp', | ||
'onPointerUpCapture', | ||
]; | ||
@@ -370,4 +402,5 @@ | ||
* 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. | ||
* | ||
@@ -378,4 +411,3 @@ * @param {String} name - Attribute name to be tested | ||
function isValidDataAttribute(name) { | ||
const dataAttrConvention = /^data(-[a-z1-9]*)*$/; | ||
return !!dataAttrConvention.test(name); | ||
return /^data(-[^:]*)*$/.test(name) && !/^data-xml/i.test(name); | ||
} | ||
@@ -409,3 +441,3 @@ | ||
function isCaseIgnoredAttribute(name) { | ||
return DOM_PROPERTIES_IGNORE_CASE.some((element) => element === name.toLowerCase()); | ||
return DOM_PROPERTIES_IGNORE_CASE.some((element) => element.toLowerCase() === name.toLowerCase()); | ||
} | ||
@@ -519,56 +551,59 @@ | ||
// 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; | ||
} | ||
if (tagName === 'fbt') { return; } // fbt nodes are bonkers, let's not go there | ||
// Let's see if the attribute is a close version to some standard property name | ||
const standardName = getStandardName(name, context); | ||
if (!isValidHTMLTagInJSX(node)) { return; } | ||
const hasStandardNameButIsNotUsed = standardName && standardName !== name; | ||
const usesStandardName = standardName && standardName === name; | ||
// Let's dive deeper into tags that are HTML/DOM elements (`<button>`), and not React components (`<Button />`) | ||
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', { | ||
// 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, | ||
tagName, | ||
allowedTags: allowedTags.join(', '), | ||
}, | ||
fix(fixer) { | ||
return fixer.replaceText(node.name, standardName); | ||
}, | ||
}); | ||
return; | ||
} | ||
// Scenario 1B: There are allowed attributes on allowed tags, no need to report it | ||
return; | ||
} | ||
// Scenario 3: We have an attribute that is unknown, report it | ||
report(context, messages.unknownProp, 'unknownProp', { | ||
// Let's see if the attribute is a close version to some standard property name | ||
const standardName = getStandardName(name, context); | ||
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, | ||
}, | ||
}); | ||
}, | ||
@@ -575,0 +610,0 @@ }; |
{ | ||
"name": "eslint-plugin-react", | ||
"version": "7.31.6", | ||
"version": "7.31.7", | ||
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>", | ||
@@ -44,5 +44,5 @@ "description": "React specific linting rules for ESLint", | ||
"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", | ||
@@ -49,0 +49,0 @@ "@babel/plugin-syntax-function-bind": "^7.18.6", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
750015
20708