Skip to content

Commit eaf977b

Browse files
committed
[New] add requireDataLowercase option to no-unknown-property rule
Fixes #3643
1 parent 9da1bb0 commit eaf977b

File tree

3 files changed

+61
-2
lines changed

3 files changed

+61
-2
lines changed

docs/rules/no-unknown-property.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,13 @@ var AtomPanel = <atom-panel class="foo"></atom-panel>;
5151

5252
```js
5353
...
54-
"react/no-unknown-property": [<enabled>, { ignore: <ignore> }]
54+
"react/no-unknown-property": [<enabled>, { ignore: <ignore>, requireDataLowercase: <requireDataLowercase> }]
5555
...
5656
```
5757

5858
- `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
5959
- `ignore`: optional array of property and attribute names to ignore during validation.
60+
- `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`.
6061

6162
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.
6263

lib/rules/no-unknown-property.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const report = require('../util/report');
1616

1717
const DEFAULTS = {
1818
ignore: [],
19+
requireDataLowercase: false,
1920
};
2021

2122
const DOM_ATTRIBUTE_NAMES = {
@@ -429,6 +430,16 @@ function isValidDataAttribute(name) {
429430
return /^data(-[^:]*)*$/.test(name) && !/^data-xml/i.test(name);
430431
}
431432

433+
/**
434+
* Checks if an attribute name has at least one uppercase characters
435+
*
436+
* @param {String} name
437+
* @returns {boolean} Result
438+
*/
439+
function hasUpperCaseCharacter(name) {
440+
return name.toLowerCase() !== name;
441+
}
442+
432443
/**
433444
* Checks if an attribute name is a standard aria attribute by compering it to a list
434445
* of standard aria property names
@@ -493,6 +504,7 @@ const messages = {
493504
invalidPropOnTag: 'Invalid property \'{{name}}\' found on tag \'{{tagName}}\', but it is only allowed on: {{allowedTags}}',
494505
unknownPropWithStandardName: 'Unknown property \'{{name}}\' found, use \'{{standardName}}\' instead',
495506
unknownProp: 'Unknown property \'{{name}}\' found',
507+
dataLowercaseRequired: 'React does not recognize data-* props with uppercase characters on a DOM element. Found \'{{name}}\', use \'{{lowerCaseName}}\' instead',
496508
};
497509

498510
module.exports = {
@@ -516,6 +528,10 @@ module.exports = {
516528
type: 'string',
517529
},
518530
},
531+
requireDataLowercase: {
532+
type: 'boolean',
533+
default: false,
534+
},
519535
},
520536
additionalProperties: false,
521537
}],
@@ -526,6 +542,12 @@ module.exports = {
526542
return (context.options[0] && context.options[0].ignore) || DEFAULTS.ignore;
527543
}
528544

545+
function getRequireDataLowercase() {
546+
return (context.options[0] && typeof context.options[0].requireDataLowercase !== 'undefined')
547+
? !!context.options[0].requireDataLowercase
548+
: DEFAULTS.requireDataLowercase;
549+
}
550+
529551
return {
530552
JSXAttribute(node) {
531553
const ignoreNames = getIgnoreConfig();
@@ -540,7 +562,19 @@ module.exports = {
540562
return;
541563
}
542564

543-
if (isValidDataAttribute(name)) { return; }
565+
if (isValidDataAttribute(name)) {
566+
if (getRequireDataLowercase() && hasUpperCaseCharacter(name)) {
567+
report(context, messages.dataLowercaseRequired, 'dataLowercaseRequired', {
568+
node,
569+
data: {
570+
name: actualName,
571+
lowerCaseName: actualName.toLowerCase(),
572+
},
573+
});
574+
}
575+
576+
return;
577+
}
544578

545579
if (isValidAriaAttribute(name)) { return; }
546580

tests/lib/rules/no-unknown-property.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ ruleTester.run('no-unknown-property', rule, {
9999
{ code: '<div data-index-number="1234"></div>;' },
100100
{ code: '<div data-e2e-id="5678"></div>;' },
101101
{ code: '<div data-testID="bar" data-under_sCoRe="bar" />;' },
102+
{
103+
code: '<div data-testID="bar" data-under_sCoRe="bar" />;',
104+
options: [{ requireDataLowercase: false }],
105+
},
102106
// Ignoring should work
103107
{
104108
code: '<div class="bar"></div>;',
@@ -573,6 +577,26 @@ ruleTester.run('no-unknown-property', rule, {
573577
},
574578
],
575579
},
580+
{
581+
code: '<div data-testID="bar" data-under_sCoRe="bar" />;',
582+
errors: [
583+
{
584+
messageId: 'dataLowercaseRequired',
585+
data: {
586+
name: 'data-testID',
587+
lowerCaseName: 'data-testid',
588+
},
589+
},
590+
{
591+
messageId: 'dataLowercaseRequired',
592+
data: {
593+
name: 'data-under_sCoRe',
594+
lowerCaseName: 'data-under_score',
595+
},
596+
},
597+
],
598+
options: [{ requireDataLowercase: true }],
599+
},
576600
{
577601
code: '<div abbr="abbr" />',
578602
errors: [

0 commit comments

Comments
 (0)