From 4a05fa2bd677ecdaf1fe836fefcd11e66adab16f Mon Sep 17 00:00:00 2001 From: Ben Scott Date: Mon, 2 Sep 2019 19:24:01 -0700 Subject: [PATCH] [fix] `display-name`: Fix false positives Wrapping a named function declaration with a React.memo or React.forwardRef will no longer throw an false positive error Fixes #2324. Fixes #2269. --- lib/rules/display-name.js | 29 ++++++++- tests/lib/rules/display-name.js | 104 ++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/lib/rules/display-name.js b/lib/rules/display-name.js index ecfeaa7b4f..3c5c847110 100644 --- a/lib/rules/display-name.js +++ b/lib/rules/display-name.js @@ -90,13 +90,13 @@ module.exports = { const namedClass = ( (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') && node.id && - node.id.name + !!node.id.name ); const namedFunctionDeclaration = ( (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.id && - node.id.name + !!node.id.name ); const namedFunctionExpression = ( @@ -202,6 +202,31 @@ module.exports = { markDisplayNameAsDeclared(node); }, + CallExpression(node) { + if (!utils.isPragmaComponentWrapper(node)) { + return; + } + + if (node.arguments.length > 0 && astUtil.isFunctionLikeExpression(node.arguments[0])) { + // Skip over React.forwardRef declarations that are embeded within + // a React.memo i.e. React.memo(React.forwardRef(/* ... */)) + // This means that we raise a single error for the call to React.memo + // instead of one for React.memo and one for React.forwardRef + const isWrappedInAnotherPragma = utils.getPragmaComponentWrapper(node); + + if ( + !isWrappedInAnotherPragma && + (ignoreTranspilerName || !hasTranspilerName(node.arguments[0])) + ) { + return; + } + + if (components.get(node)) { + markDisplayNameAsDeclared(node); + } + } + }, + 'Program:exit': function () { const list = components.list(); // Report missing display name for all components diff --git a/tests/lib/rules/display-name.js b/tests/lib/rules/display-name.js index 0064bc4bc0..9f01354be5 100644 --- a/tests/lib/rules/display-name.js +++ b/tests/lib/rules/display-name.js @@ -455,6 +455,22 @@ ruleTester.run('display-name', rule, { export default React.memo(Component) ` + }, { + code: ` + import React from 'react' + + const ComponentWithMemo = React.memo(function Component({ world }) { + return
Hello {world}
+ }) + ` + }, { + code: ` + import React from 'react' + + const ForwardRefComponentLike = React.forwardRef(function ComponentLike({ world }, ref) { + return
Hello {world}
+ }) + ` }, { code: ` function F() { @@ -684,6 +700,94 @@ ruleTester.run('display-name', rule, { errors: [{ message: 'Component definition is missing display name' }] + }, { + code: ` + import React from 'react' + + const ComponentWithMemo = React.memo(({ world }) => { + return
Hello {world}
+ }) + `, + errors: [{ + message: 'Component definition is missing display name' + }] + }, { + code: ` + import React from 'react' + + const ComponentWithMemo = React.memo(function() { + return
Hello {world}
+ }) + `, + errors: [{ + message: 'Component definition is missing display name' + }] + }, { + code: ` + import React from 'react' + + const ForwardRefComponentLike = React.forwardRef(({ world }, ref) => { + return
Hello {world}
+ }) + `, + errors: [{ + message: 'Component definition is missing display name' + }] + }, { + code: ` + import React from 'react' + + const ForwardRefComponentLike = React.forwardRef(function({ world }, ref) { + return
Hello {world}
+ }) + `, + errors: [{ + message: 'Component definition is missing display name' + }] + }, { + // Only trigger an error for the outer React.memo + code: ` + import React from 'react' + + const MemoizedForwardRefComponentLike = React.memo( + React.forwardRef(({ world }, ref) => { + return
Hello {world}
+ }) + ) + `, + errors: [{ + message: 'Component definition is missing display name' + }] + }, { + // Only trigger an error for the outer React.memo + code: ` + import React from 'react' + + const MemoizedForwardRefComponentLike = React.memo( + React.forwardRef(function({ world }, ref) { + return
Hello {world}
+ }) + ) + `, + errors: [{ + message: 'Component definition is missing display name' + }] + }, { + // React does not handle the result of forwardRef being passed into memo + // ComponentWithMemoAndForwardRef gets shown as Memo(Anonymous) + // See https://github.com/facebook/react/issues/16722 + code: ` + import React from 'react' + + const MemoizedForwardRefComponentLike = React.memo( + React.forwardRef(function ComponentLike({ world }, ref) { + return
Hello {world}
+ }) + ) + `, + errors: [{ + message: 'Component definition is missing display name' + }] }, { code: ` import React from "react";