From 70e6019eca0c1c24e8caa8349d505c578f2161a9 Mon Sep 17 00:00:00 2001 From: Herman Bilous Date: Fri, 20 Oct 2023 11:28:59 +0300 Subject: [PATCH] [New] `no-unknown-property`: add requireDataLowercase option Fixes #3643 --- CHANGELOG.md | 2 ++ docs/rules/no-unknown-property.md | 3 ++- lib/rules/no-unknown-property.js | 36 +++++++++++++++++++++++++- tests/lib/rules/no-unknown-property.js | 24 +++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) 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: [