From a1273d5e4bd247e7c289110e9e56b06d55329708 Mon Sep 17 00:00:00 2001 From: Dan Reeves Date: Mon, 16 Sep 2024 15:34:24 +0100 Subject: [PATCH] [New] `no-unstable-nested-components`: add `propNamePattern` to support custom render prop naming conventions --- CHANGELOG.md | 2 ++ docs/rules/no-unstable-nested-components.md | 11 ++++++++ lib/rules/no-unstable-nested-components.js | 28 ++++++++++++------- .../rules/no-unstable-nested-components.js | 12 ++++++++ 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35bb82625a..e9da7d044f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * add type generation ([#3830][] @voxpelli) * [`no-unescaped-entities`]: add suggestions ([#3831][] @StyleShit) * [`forbid-component-props`]: add `allowedForPatterns`/`disallowedForPatterns` options ([#3805][] @Efimenko) +* [`no-unstable-nested-components`]: add `propNamePattern` to support custom render prop naming conventions ([#3826][] @danreeves) [#3831]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3831 [#3830]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3830 +[#3826]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3826 [#3805]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3805 ## [7.36.1] - 2024.09.12 diff --git a/docs/rules/no-unstable-nested-components.md b/docs/rules/no-unstable-nested-components.md index 8117636805..45fa214ef4 100644 --- a/docs/rules/no-unstable-nested-components.md +++ b/docs/rules/no-unstable-nested-components.md @@ -125,6 +125,7 @@ function Component() { { "allowAsProps": true | false, "customValidators": [] /* optional array of validators used for propTypes validation */ + "propNamePattern": string } ] ... @@ -148,6 +149,16 @@ function Component() { } ``` +You can allow other render prop naming conventions by setting the `propNamePattern` option. By default this option is `"render*"`. + +For example, if `propNamePattern` is set to `"*Renderer"` the following pattern is **not** considered warnings: + +```jsx +} +/> +``` + ## When Not To Use It If you are not interested in preventing bugs related to re-creation of the nested components or do not care about optimization of virtual DOM. diff --git a/lib/rules/no-unstable-nested-components.js b/lib/rules/no-unstable-nested-components.js index e17f65e487..c330795ff0 100644 --- a/lib/rules/no-unstable-nested-components.js +++ b/lib/rules/no-unstable-nested-components.js @@ -5,6 +5,7 @@ 'use strict'; +const minimatch = require('minimatch'); const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); const astUtil = require('../util/ast'); @@ -32,12 +33,13 @@ function generateErrorMessageWithParentName(parentName) { } /** - * Check whether given text starts with `render`. Comparison is case-sensitive. + * Check whether given text matches the pattern passed in. * @param {string} text Text to validate + * @param {string} pattern Pattern to match against * @returns {boolean} */ -function startsWithRender(text) { - return typeof text === 'string' && text.startsWith('render'); +function propMatchesRenderPropPattern(text, pattern) { + return typeof text === 'string' && minimatch(text, pattern); } /** @@ -165,15 +167,16 @@ function isReturnStatementOfHook(node, context) { * ``` * @param {ASTNode} node The AST node * @param {Context} context eslint context + * @param {string} propNamePattern a pattern to match render props against * @returns {boolean} True if component is declared inside a render prop, false if not */ -function isComponentInRenderProp(node, context) { +function isComponentInRenderProp(node, context, propNamePattern) { if ( node && node.parent && node.parent.type === 'Property' && node.parent.key - && startsWithRender(node.parent.key.name) + && propMatchesRenderPropPattern(node.parent.key.name, propNamePattern) ) { return true; } @@ -202,7 +205,7 @@ function isComponentInRenderProp(node, context) { const propName = jsxExpressionContainer.parent.name.name; // Starts with render, e.g.
} /> - if (startsWithRender(propName)) { + if (propMatchesRenderPropPattern(propName, propNamePattern)) { return true; } @@ -222,16 +225,17 @@ function isComponentInRenderProp(node, context) { *
}] } /> * ``` * @param {ASTNode} node The AST node + * @param {string} propNamePattern The pattern to match render props against * @returns {boolean} True if component is declared inside a render property, false if not */ -function isDirectValueOfRenderProperty(node) { +function isDirectValueOfRenderProperty(node, propNamePattern) { return ( node && node.parent && node.parent.type === 'Property' && node.parent.key && node.parent.key.type === 'Identifier' - && startsWithRender(node.parent.key.name) + && propMatchesRenderPropPattern(node.parent.key.name, propNamePattern) ); } @@ -277,6 +281,9 @@ module.exports = { allowAsProps: { type: 'boolean', }, + propNamePattern: { + type: 'string', + }, }, additionalProperties: false, }], @@ -284,6 +291,7 @@ module.exports = { create: Components.detect((context, components, utils) => { const allowAsProps = context.options.some((option) => option && option.allowAsProps); + const propNamePattern = (context.options[0] || {}).propNamePattern || 'render*'; /** * Check whether given node is declared inside class component's render block @@ -418,7 +426,7 @@ module.exports = { if ( // Support allowAsProps option - (isDeclaredInsideProps && (allowAsProps || isComponentInRenderProp(node, context))) + (isDeclaredInsideProps && (allowAsProps || isComponentInRenderProp(node, context, propNamePattern))) // Prevent reporting components created inside Array.map calls || isMapCall(node) @@ -428,7 +436,7 @@ module.exports = { || isReturnStatementOfHook(node, context) // Do not mark objects containing render methods - || isDirectValueOfRenderProperty(node) + || isDirectValueOfRenderProperty(node, propNamePattern) // Prevent reporting nested class components twice || isInsideRenderMethod(node) diff --git a/tests/lib/rules/no-unstable-nested-components.js b/tests/lib/rules/no-unstable-nested-components.js index 3e20ad648a..8a9c0aad47 100644 --- a/tests/lib/rules/no-unstable-nested-components.js +++ b/tests/lib/rules/no-unstable-nested-components.js @@ -580,6 +580,18 @@ ruleTester.run('no-unstable-nested-components', rule, { allowAsProps: true, }], }, + { + code: ` + function ParentComponent() { + return
} + /> + } + `, + options: [{ + propNamePattern: '*Renderer', + }], + }, /* TODO These minor cases are currently falsely marked due to component detection { code: `