From acf7d62cb40bef49ed8574e04a97eb89ce923907 Mon Sep 17 00:00:00 2001 From: Kev-Y-Huang Date: Mon, 1 Feb 2021 18:55:46 -0500 Subject: [PATCH] [New] `jsx-pascal-case`: support `allowNamespace` option --- CHANGELOG.md | 2 ++ docs/rules/jsx-pascal-case.md | 12 ++++++- lib/rules/jsx-pascal-case.js | 55 ++++++++++++++++++------------ tests/lib/rules/jsx-pascal-case.js | 22 ++++++++++++ 4 files changed, 69 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe2ef055e0..820ef38767 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Added * [`jsx-no-target-blank`]: add fixer ([#2862][] @Nokel81) * [`jsx-pascal-case`]: support minimatch `ignore` option ([#2906][] @bcherny) +* [`jsx-pascal-case`]: support `allowNamespace` option ([#2917][] @kev-y-huang) ### Fixed * [`jsx-no-constructed-context-values`]: avoid a crash with `as X` TS code ([#2894][] @ljharb) @@ -29,6 +30,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [#2929]: https://github.com/yannickcr/eslint-plugin-react/pull/2929 [#2925]: https://github.com/yannickcr/eslint-plugin-react/pull/2925 [#2923]: https://github.com/yannickcr/eslint-plugin-react/pull/2923 +[#2917]: https://github.com/yannickcr/eslint-plugin-react/pull/2917 [#2910]: https://github.com/yannickcr/eslint-plugin-react/pull/2910 [#2908]: https://github.com/yannickcr/eslint-plugin-react/pull/2908 [#2906]: https://github.com/yannickcr/eslint-plugin-react/pull/2906 diff --git a/docs/rules/jsx-pascal-case.md b/docs/rules/jsx-pascal-case.md index 14fcda99a8..27de599a97 100644 --- a/docs/rules/jsx-pascal-case.md +++ b/docs/rules/jsx-pascal-case.md @@ -40,12 +40,13 @@ Examples of **correct** code for this rule: ```js ... -"react/jsx-pascal-case": [, { allowAllCaps: , ignore: }] +"react/jsx-pascal-case": [, { allowAllCaps: , allowNamespace: , ignore: }] ... ``` * `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0. * `allowAllCaps`: optional boolean set to `true` to allow components name in all caps (default to `false`). +* `allowNamespace`: optional boolean set to `true` to ignore namespaced components (default to `false`). * `ignore`: optional string-array of component names to ignore during validation (supports [minimatch](https://github.com/isaacs/minimatch)-style globs). ### `allowAllCaps` @@ -57,6 +58,15 @@ Examples of **correct** code for this rule, when `allowAllCaps` is `true`: ``` +### `allowNamespace` + +Examples of **correct** code for this rule, when `allowNamespace` is `true`: + +```jsx + + +``` + ## When Not To Use It If you are not using JSX. diff --git a/lib/rules/jsx-pascal-case.js b/lib/rules/jsx-pascal-case.js index c71a4c53f9..696525dfda 100644 --- a/lib/rules/jsx-pascal-case.js +++ b/lib/rules/jsx-pascal-case.js @@ -60,6 +60,12 @@ function testAllCaps(name) { return true; } +function ignoreCheck(ignore, name) { + return ignore.some( + (entry) => name === entry || minimatch(name, entry, {noglobstar: true}) + ); +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -84,6 +90,9 @@ module.exports = { allowAllCaps: { type: 'boolean' }, + allowNamespace: { + type: 'boolean' + }, ignore: { items: [ { @@ -102,6 +111,7 @@ module.exports = { create(context) { const configuration = context.options[0] || {}; const allowAllCaps = configuration.allowAllCaps || false; + const allowNamespace = configuration.allowNamespace || false; const ignore = configuration.ignore || []; return { @@ -109,32 +119,35 @@ module.exports = { const isCompatTag = jsxUtil.isDOMComponent(node); if (isCompatTag) return undefined; - let name = elementType(node); + const name = elementType(node); + let checkNames = [name]; + let index = 0; - // Get JSXIdentifier if the type is JSXNamespacedName or JSXMemberExpression if (name.lastIndexOf(':') > -1) { - name = name.substring(name.lastIndexOf(':') + 1); + checkNames = name.split(':'); } else if (name.lastIndexOf('.') > -1) { - name = name.substring(name.lastIndexOf('.') + 1); + checkNames = name.split('.'); } - if (name.length === 1) return undefined; - - const isPascalCase = testPascalCase(name); - const isAllowedAllCaps = allowAllCaps && testAllCaps(name); - const isIgnored = ignore.some( - (entry) => name === entry || minimatch(name, entry, {noglobstar: true}) - ); - - if (!isPascalCase && !isAllowedAllCaps && !isIgnored) { - context.report({ - node, - messageId: allowAllCaps ? 'usePascalOrSnakeCase' : 'usePascalCase', - data: { - name - } - }); - } + do { + const splitName = checkNames[index]; + if (splitName.length === 1) return undefined; + const isPascalCase = testPascalCase(splitName); + const isAllowedAllCaps = allowAllCaps && testAllCaps(splitName); + const isIgnored = ignoreCheck(ignore, splitName); + + if (!isPascalCase && !isAllowedAllCaps && !isIgnored) { + context.report({ + node, + messageId: allowAllCaps ? 'usePascalOrSnakeCase' : 'usePascalCase', + data: { + name: splitName + } + }); + break; + } + index++; + } while (index < checkNames.length && !allowNamespace); } }; } diff --git a/tests/lib/rules/jsx-pascal-case.js b/tests/lib/rules/jsx-pascal-case.js index a29169fe58..8e6d5c2e4e 100644 --- a/tests/lib/rules/jsx-pascal-case.js +++ b/tests/lib/rules/jsx-pascal-case.js @@ -90,6 +90,9 @@ ruleTester.run('jsx-pascal-case', rule, { code: '

Hello!

' }, { code: '' + }, { + code: '', + options: [{allowNamespace: true}] }], invalid: [{ @@ -144,5 +147,24 @@ ruleTester.run('jsx-pascal-case', rule, { messageId: 'usePascalCase', data: {name: 'Foo_DEPRECATED'} }] + }, { + code: '', + errors: [{ + messageId: 'usePascalCase', + data: {name: 'h1'} + }] + }, { + code: '<$Typography.P />', + errors: [{ + messageId: 'usePascalCase', + data: {name: '$Typography'} + }] + }, { + code: '', + options: [{allowNamespace: true}], + errors: [{ + messageId: 'usePascalCase', + data: {name: 'STYLED'} + }] }] });