Skip to content

Commit 4a05fa2

Browse files
BPScottljharb
authored andcommitted
[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.
1 parent c6521ad commit 4a05fa2

File tree

2 files changed

+131
-2
lines changed

2 files changed

+131
-2
lines changed

lib/rules/display-name.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ module.exports = {
9090
const namedClass = (
9191
(node.type === 'ClassDeclaration' || node.type === 'ClassExpression') &&
9292
node.id &&
93-
node.id.name
93+
!!node.id.name
9494
);
9595

9696
const namedFunctionDeclaration = (
9797
(node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') &&
9898
node.id &&
99-
node.id.name
99+
!!node.id.name
100100
);
101101

102102
const namedFunctionExpression = (
@@ -202,6 +202,31 @@ module.exports = {
202202
markDisplayNameAsDeclared(node);
203203
},
204204

205+
CallExpression(node) {
206+
if (!utils.isPragmaComponentWrapper(node)) {
207+
return;
208+
}
209+
210+
if (node.arguments.length > 0 && astUtil.isFunctionLikeExpression(node.arguments[0])) {
211+
// Skip over React.forwardRef declarations that are embeded within
212+
// a React.memo i.e. React.memo(React.forwardRef(/* ... */))
213+
// This means that we raise a single error for the call to React.memo
214+
// instead of one for React.memo and one for React.forwardRef
215+
const isWrappedInAnotherPragma = utils.getPragmaComponentWrapper(node);
216+
217+
if (
218+
!isWrappedInAnotherPragma &&
219+
(ignoreTranspilerName || !hasTranspilerName(node.arguments[0]))
220+
) {
221+
return;
222+
}
223+
224+
if (components.get(node)) {
225+
markDisplayNameAsDeclared(node);
226+
}
227+
}
228+
},
229+
205230
'Program:exit': function () {
206231
const list = components.list();
207232
// Report missing display name for all components

tests/lib/rules/display-name.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,22 @@ ruleTester.run('display-name', rule, {
455455
456456
export default React.memo(Component)
457457
`
458+
}, {
459+
code: `
460+
import React from 'react'
461+
462+
const ComponentWithMemo = React.memo(function Component({ world }) {
463+
return <div>Hello {world}</div>
464+
})
465+
`
466+
}, {
467+
code: `
468+
import React from 'react'
469+
470+
const ForwardRefComponentLike = React.forwardRef(function ComponentLike({ world }, ref) {
471+
return <div ref={ref}>Hello {world}</div>
472+
})
473+
`
458474
}, {
459475
code: `
460476
function F() {
@@ -684,6 +700,94 @@ ruleTester.run('display-name', rule, {
684700
errors: [{
685701
message: 'Component definition is missing display name'
686702
}]
703+
}, {
704+
code: `
705+
import React from 'react'
706+
707+
const ComponentWithMemo = React.memo(({ world }) => {
708+
return <div>Hello {world}</div>
709+
})
710+
`,
711+
errors: [{
712+
message: 'Component definition is missing display name'
713+
}]
714+
}, {
715+
code: `
716+
import React from 'react'
717+
718+
const ComponentWithMemo = React.memo(function() {
719+
return <div>Hello {world}</div>
720+
})
721+
`,
722+
errors: [{
723+
message: 'Component definition is missing display name'
724+
}]
725+
}, {
726+
code: `
727+
import React from 'react'
728+
729+
const ForwardRefComponentLike = React.forwardRef(({ world }, ref) => {
730+
return <div ref={ref}>Hello {world}</div>
731+
})
732+
`,
733+
errors: [{
734+
message: 'Component definition is missing display name'
735+
}]
736+
}, {
737+
code: `
738+
import React from 'react'
739+
740+
const ForwardRefComponentLike = React.forwardRef(function({ world }, ref) {
741+
return <div ref={ref}>Hello {world}</div>
742+
})
743+
`,
744+
errors: [{
745+
message: 'Component definition is missing display name'
746+
}]
747+
}, {
748+
// Only trigger an error for the outer React.memo
749+
code: `
750+
import React from 'react'
751+
752+
const MemoizedForwardRefComponentLike = React.memo(
753+
React.forwardRef(({ world }, ref) => {
754+
return <div ref={ref}>Hello {world}</div>
755+
})
756+
)
757+
`,
758+
errors: [{
759+
message: 'Component definition is missing display name'
760+
}]
761+
}, {
762+
// Only trigger an error for the outer React.memo
763+
code: `
764+
import React from 'react'
765+
766+
const MemoizedForwardRefComponentLike = React.memo(
767+
React.forwardRef(function({ world }, ref) {
768+
return <div ref={ref}>Hello {world}</div>
769+
})
770+
)
771+
`,
772+
errors: [{
773+
message: 'Component definition is missing display name'
774+
}]
775+
}, {
776+
// React does not handle the result of forwardRef being passed into memo
777+
// ComponentWithMemoAndForwardRef gets shown as Memo(Anonymous)
778+
// See https://github.com/facebook/react/issues/16722
779+
code: `
780+
import React from 'react'
781+
782+
const MemoizedForwardRefComponentLike = React.memo(
783+
React.forwardRef(function ComponentLike({ world }, ref) {
784+
return <div ref={ref}>Hello {world}</div>
785+
})
786+
)
787+
`,
788+
errors: [{
789+
message: 'Component definition is missing display name'
790+
}]
687791
}, {
688792
code: `
689793
import React from "react";

0 commit comments

Comments
 (0)