diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b8a610a50..ecb9789788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`jsx-no-literals`]: properly error on children with noAttributeStrings: true ([#3317][] @TildaDares) -* [`jsx-key`]: catch key errors inside conditional statements ([#3320][] @TildaDates) +* [`jsx-key`]: catch key errors inside conditional statements ([#3320][] @TildaDares) +* [`display-name`]: Accept forwardRef and Memo nesting in newer React versions ([#3321][] @TildaDares) ### Changed * [Refactor] [`jsx-indent-props`]: improved readability of the checkNodesIndent function ([#3315][] @caroline223) * [Tests] [`jsx-indent`], [`jsx-one-expression-per-line`]: add passing test cases ([#3314][] @ROSSROSALES) +[#3321]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3321 [#3320]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3320 [#3317]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3317 [#3315]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3315 diff --git a/lib/rules/display-name.js b/lib/rules/display-name.js index a99aabceee..c07f9255b4 100644 --- a/lib/rules/display-name.js +++ b/lib/rules/display-name.js @@ -11,6 +11,7 @@ const Components = require('../util/Components'); const astUtil = require('../util/ast'); const componentUtil = require('../util/componentUtil'); const docsUrl = require('../util/docsUrl'); +const testReactVersion = require('../util/version').testReactVersion; const propsUtil = require('../util/props'); const report = require('../util/report'); @@ -58,11 +59,29 @@ module.exports = { }); } + /** + * Checks if React.forwardRef is nested inside React.memo + * @param {ASTNode} node The AST node being checked. + * @returns {Boolean} True if React.forwardRef is nested inside React.memo, false if not. + */ + function isNestedMemo(node) { + const argumentIsCallExpression = node.arguments && node.arguments[0] && node.arguments[0].type === 'CallExpression'; + + return node.type === 'CallExpression' && argumentIsCallExpression && utils.isPragmaComponentWrapper(node); + } + /** * Reports missing display name for a given component * @param {Object} component The component to process */ function reportMissingDisplayName(component) { + if ( + testReactVersion(context, '^0.14.10 || ^15.7.0 || >= 16.12.0') + && isNestedMemo(component.node) + ) { + return; + } + report(context, messages.noDisplayName, 'noDisplayName', { node: component.node, }); diff --git a/tests/lib/rules/display-name.js b/tests/lib/rules/display-name.js index 85e076d4d1..445add83a5 100644 --- a/tests/lib/rules/display-name.js +++ b/tests/lib/rules/display-name.js @@ -601,6 +601,94 @@ ruleTester.run('display-name', rule, { ) `, }, + { + // Nested React.forwardRef should be accepted in React versions in the following range: + // ^0.14.10 || ^15.7.0 || >= 16.12.0 + code: ` + import React from 'react' + + const MemoizedForwardRefComponentLike = React.memo( + React.forwardRef(function({ world }, ref) { + return
Hello {world}
+ }) + ) + `, + settings: { + react: { + version: '16.14.0', + }, + }, + }, + { + // Nested React.forwardRef should be accepted in React versions in the following range: + // ^0.14.10 || ^15.7.0 || >= 16.12.0 + code: ` + import React from 'react' + + const MemoizedForwardRefComponentLike = React.memo( + React.forwardRef(({ world }, ref) => { + return
Hello {world}
+ }) + ) + `, + settings: { + react: { + version: '15.7.0', + }, + }, + }, + { + // Nested React.forwardRef should be accepted in React versions in the following range: + // ^0.14.10 || ^15.7.0 || >= 16.12.0 + code: ` + import React from 'react' + + const MemoizedForwardRefComponentLike = React.memo( + React.forwardRef(function ComponentLike({ world }, ref) { + return
Hello {world}
+ }) + ) + `, + settings: { + react: { + version: '16.12.1', + }, + }, + }, + { + // Nested React.forwardRef should be accepted in React versions in the following range: + // ^0.14.10 || ^15.7.0 || >= 16.12.0 + code: ` + export const ComponentWithForwardRef = React.memo( + React.forwardRef(function Component({ world }) { + return
Hello {world}
+ }) + ) + `, + settings: { + react: { + version: '0.14.11', + }, + }, + }, + { + // Nested React.forwardRef should be accepted in React versions in the following range: + // ^0.14.10 || ^15.7.0 || >= 16.12.0 + code: ` + import React from 'react' + + const MemoizedForwardRefComponentLike = React.memo( + React.forwardRef(function({ world }, ref) { + return
Hello {world}
+ }) + ) + `, + settings: { + react: { + version: '15.7.1', + }, + }, + }, ]), invalid: parsers.all([ @@ -823,7 +911,9 @@ ruleTester.run('display-name', rule, { errors: [{ messageId: 'noDisplayName' }], }, { - // Only trigger an error for the outer React.memo + // Only trigger an error for the outer React.memo, + // if the React version is not in the following range: + // ^0.14.10 || ^15.7.0 || >= 16.12.0 code: ` import React from 'react' @@ -837,19 +927,31 @@ ruleTester.run('display-name', rule, { { messageId: 'noDisplayName', }], + settings: { + react: { + version: '15.6.0', + }, + }, }, { - // Only trigger an error for the outer React.memo + // Only trigger an error for the outer React.memo, + // if the React version is not in the following range: + // ^0.14.10 || ^15.7.0 || >= ^16.12.0 code: ` import React from 'react' const MemoizedForwardRefComponentLike = React.memo( React.forwardRef(function({ world }, ref) { return
Hello {world}
- }) + }) ) `, errors: [{ messageId: 'noDisplayName' }], + settings: { + react: { + version: '0.14.2', + }, + }, }, { // React does not handle the result of forwardRef being passed into memo @@ -865,6 +967,11 @@ ruleTester.run('display-name', rule, { ) `, errors: [{ messageId: 'noDisplayName' }], + settings: { + react: { + version: '15.0.1', + }, + }, }, { code: `