diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ff0e08075..f76e6f3728 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
### Added
* [`sort-prop-types`]: give errors on TS types ([#3615][] @akulsr0)
* [`no-invalid-html-attribute`]: add support for `apple-touch-startup-image` `rel` attributes in `link` tags ([#3638][] @thomashockaday)
+* [`no-unknown-property`]: add requireDataLowercase option ([#3645][] @HermanBilous)
### Fixed
* [`jsx-no-leaked-render`]: preserve RHS parens for multiline jsx elements while fixing ([#3623][] @akulsr0)
@@ -20,6 +21,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [Refactor] [`function-component-definition`]: exit early if no type params ([#3634][] @HenryBrown0)
* [Refactor] [`jsx-props-no-multi-spaces`]: extract type parameters to var ([#3634][] @HenryBrown0)
+[#3645]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3645
[#3638]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3638
[#3634]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3634
[#3633]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3633
diff --git a/docs/rules/no-unknown-property.md b/docs/rules/no-unknown-property.md
index ac47ccac9d..0d6c9cdc06 100644
--- a/docs/rules/no-unknown-property.md
+++ b/docs/rules/no-unknown-property.md
@@ -51,12 +51,13 @@ var AtomPanel = ;
```js
...
-"react/no-unknown-property": [, { ignore: }]
+"react/no-unknown-property": [, { ignore: , requireDataLowercase: }]
...
```
- `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
- `ignore`: optional array of property and attribute names to ignore during validation.
+- `requireDataLowercase`: optional (default: `false`), require data-\* attributes to contain only lowercase characters. React will issue a warning when data-\* attributes contain uppercase characters. In order to catch such attributes, set the `requireDataLowercase` option to `true`.
If you are using a library that passes something as a prop to JSX elements, it is recommended to add those props to the ignored properties.
diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js
index fc57336695..069596d73c 100644
--- a/lib/rules/no-unknown-property.js
+++ b/lib/rules/no-unknown-property.js
@@ -16,6 +16,7 @@ const report = require('../util/report');
const DEFAULTS = {
ignore: [],
+ requireDataLowercase: false,
};
const DOM_ATTRIBUTE_NAMES = {
@@ -429,6 +430,16 @@ function isValidDataAttribute(name) {
return /^data(-[^:]*)*$/.test(name) && !/^data-xml/i.test(name);
}
+/**
+ * Checks if an attribute name has at least one uppercase characters
+ *
+ * @param {String} name
+ * @returns {boolean} Result
+ */
+function hasUpperCaseCharacter(name) {
+ return name.toLowerCase() !== name;
+}
+
/**
* Checks if an attribute name is a standard aria attribute by compering it to a list
* of standard aria property names
@@ -493,6 +504,7 @@ const messages = {
invalidPropOnTag: 'Invalid property \'{{name}}\' found on tag \'{{tagName}}\', but it is only allowed on: {{allowedTags}}',
unknownPropWithStandardName: 'Unknown property \'{{name}}\' found, use \'{{standardName}}\' instead',
unknownProp: 'Unknown property \'{{name}}\' found',
+ dataLowercaseRequired: 'React does not recognize data-* props with uppercase characters on a DOM element. Found \'{{name}}\', use \'{{lowerCaseName}}\' instead',
};
module.exports = {
@@ -516,6 +528,10 @@ module.exports = {
type: 'string',
},
},
+ requireDataLowercase: {
+ type: 'boolean',
+ default: false,
+ },
},
additionalProperties: false,
}],
@@ -526,6 +542,12 @@ module.exports = {
return (context.options[0] && context.options[0].ignore) || DEFAULTS.ignore;
}
+ function getRequireDataLowercase() {
+ return (context.options[0] && typeof context.options[0].requireDataLowercase !== 'undefined')
+ ? !!context.options[0].requireDataLowercase
+ : DEFAULTS.requireDataLowercase;
+ }
+
return {
JSXAttribute(node) {
const ignoreNames = getIgnoreConfig();
@@ -540,7 +562,19 @@ module.exports = {
return;
}
- if (isValidDataAttribute(name)) { return; }
+ if (isValidDataAttribute(name)) {
+ if (getRequireDataLowercase() && hasUpperCaseCharacter(name)) {
+ report(context, messages.dataLowercaseRequired, 'dataLowercaseRequired', {
+ node,
+ data: {
+ name: actualName,
+ lowerCaseName: actualName.toLowerCase(),
+ },
+ });
+ }
+
+ return;
+ }
if (isValidAriaAttribute(name)) { return; }
diff --git a/tests/lib/rules/no-unknown-property.js b/tests/lib/rules/no-unknown-property.js
index 0fc510e3b5..d6713286c1 100644
--- a/tests/lib/rules/no-unknown-property.js
+++ b/tests/lib/rules/no-unknown-property.js
@@ -99,6 +99,10 @@ ruleTester.run('no-unknown-property', rule, {
{ code: ';' },
{ code: ';' },
{ code: ';' },
+ {
+ code: ';',
+ options: [{ requireDataLowercase: false }],
+ },
// Ignoring should work
{
code: ';',
@@ -573,6 +577,26 @@ ruleTester.run('no-unknown-property', rule, {
},
],
},
+ {
+ code: ';',
+ errors: [
+ {
+ messageId: 'dataLowercaseRequired',
+ data: {
+ name: 'data-testID',
+ lowerCaseName: 'data-testid',
+ },
+ },
+ {
+ messageId: 'dataLowercaseRequired',
+ data: {
+ name: 'data-under_sCoRe',
+ lowerCaseName: 'data-under_score',
+ },
+ },
+ ],
+ options: [{ requireDataLowercase: true }],
+ },
{
code: '',
errors: [