Skip to content

Commit 48491b9

Browse files
committed
fix(display-name): expand shadowing test coverage and functionalities
1 parent 88092cd commit 48491b9

File tree

2 files changed

+279
-11
lines changed

2 files changed

+279
-11
lines changed

lib/rules/display-name.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,22 +200,56 @@ module.exports = {
200200

201201
function isIdentifierShadowed(node, identifierName) {
202202
let currentNode = node;
203+
203204
while (currentNode && currentNode.parent) {
204205
currentNode = currentNode.parent;
206+
205207
if (
206208
currentNode.type === 'FunctionDeclaration'
207-
|| currentNode.type === 'FunctionExpression'
208-
|| currentNode.type === 'ArrowFunctionExpression'
209+
|| currentNode.type === 'FunctionExpression'
210+
|| currentNode.type === 'ArrowFunctionExpression'
209211
) {
210-
break;
212+
if (currentNode.body && hasVariableDeclaration(currentNode.body, identifierName)) {
213+
return true;
214+
}
211215
}
212-
}
213216

214-
if (!currentNode || !currentNode.body) {
215-
return false;
217+
if (currentNode.type === 'BlockStatement') {
218+
if (hasVariableDeclaration(currentNode, identifierName)) {
219+
return true;
220+
}
221+
}
222+
223+
if (
224+
(currentNode.type === 'FunctionDeclaration'
225+
|| currentNode.type === 'FunctionExpression'
226+
|| currentNode.type === 'ArrowFunctionExpression')
227+
&& currentNode.params
228+
) {
229+
const isParamShadowed = currentNode.params.some((param) => {
230+
if (param.type === 'Identifier' && param.name === identifierName) {
231+
return true;
232+
}
233+
if (param.type === 'ObjectPattern') {
234+
return param.properties.some(
235+
(prop) => prop.type === 'Property' && prop.key && prop.key.name === identifierName
236+
);
237+
}
238+
if (param.type === 'ArrayPattern') {
239+
return param.elements.some(
240+
(el) => el && el.type === 'Identifier' && el.name === identifierName
241+
);
242+
}
243+
return false;
244+
});
245+
246+
if (isParamShadowed) {
247+
return true;
248+
}
249+
}
216250
}
217251

218-
return hasVariableDeclaration(currentNode.body, identifierName);
252+
return false;
219253
}
220254
/**
221255
* Checks whether the component wrapper (e.g. React.memo or forwardRef) is shadowed in the current scope.

tests/lib/rules/display-name.js

Lines changed: 238 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,76 @@ const parserOptions = {
2929
const ruleTester = new RuleTester({ parserOptions });
3030
ruleTester.run('display-name', rule, {
3131
valid: parsers.all([
32+
{
33+
code: `
34+
import React, { memo, forwardRef } from 'react'
35+
36+
const TestComponent = function () {
37+
{
38+
const memo = (cb) => cb()
39+
const forwardRef = (cb) => cb()
40+
const React = { memo, forwardRef }
41+
const BlockReactShadowedMemo = React.memo(() => {
42+
return <div>shadowed</div>
43+
})
44+
const BlockShadowedMemo = memo(() => {
45+
return <div>shadowed</div>
46+
})
47+
const BlockShadowedForwardRef = forwardRef((props, ref) => {
48+
return \`\${props} \${ref}\`
49+
})
50+
}
51+
return null
52+
}
53+
`,
54+
},
55+
{
56+
code: `
57+
import React, { memo } from 'react'
58+
59+
const TestComponent = function (memo) {
60+
const Comp = memo(() => {
61+
return <div>param shadowed</div>
62+
})
63+
return Comp
64+
}
65+
`,
66+
},
67+
{
68+
code: `
69+
import React, { memo } from 'react'
70+
71+
const TestComponent = function ({ memo }) {
72+
const Comp = memo(() => {
73+
return <div>destructured param shadowed</div>
74+
})
75+
return Comp
76+
}
77+
`,
78+
},
79+
{
80+
code: `
81+
import React, { memo, forwardRef } from 'react'
82+
83+
const TestComponent = function () {
84+
function innerFunction() {
85+
const memo = (cb) => cb()
86+
const React = { forwardRef }
87+
const Comp = memo(() => <div>nested shadowed</div>)
88+
const ForwardComp = React.forwardRef(() => <div>nested</div>)
89+
return [Comp, ForwardComp]
90+
}
91+
return innerFunction()
92+
}
93+
`,
94+
},
3295
{
3396
code: `
3497
import React, { forwardRef } from 'react'
3598
3699
const TestComponent = function () {
37100
const { forwardRef } = { forwardRef: () => null }
38-
39101
const OtherComp = forwardRef((props, ref) => \`\${props} \${ref}\`)
40-
41102
return OtherComp
42103
}
43104
`,
@@ -48,11 +109,9 @@ ruleTester.run('display-name', rule, {
48109
49110
const TestComponent = function () {
50111
const memo = (cb) => cb()
51-
52112
const Comp = memo(() => {
53113
return <div>shadowed</div>
54114
})
55-
56115
return Comp
57116
}
58117
`,
@@ -898,6 +957,181 @@ ruleTester.run('display-name', rule, {
898957
]),
899958

900959
invalid: parsers.all([
960+
{
961+
code: `
962+
import React, { memo, forwardRef } from 'react'
963+
964+
const TestComponent = function () {
965+
{
966+
const BlockReactMemo = React.memo(() => {
967+
return <div>not shadowed</div>
968+
})
969+
970+
const BlockMemo = memo(() => {
971+
return <div>not shadowed</div>
972+
})
973+
974+
const BlockForwardRef = forwardRef((props, ref) => {
975+
return \`\${props} \${ref}\`
976+
})
977+
}
978+
979+
return null
980+
}
981+
`,
982+
errors: [
983+
{
984+
messageId: 'noDisplayName',
985+
line: 6,
986+
},
987+
{
988+
messageId: 'noDisplayName',
989+
line: 10,
990+
},
991+
{
992+
messageId: 'noDisplayName',
993+
line: 14,
994+
},
995+
],
996+
},
997+
998+
{
999+
code: `
1000+
import React, { memo } from 'react'
1001+
1002+
const TestComponent = function () {
1003+
const Comp = memo(() => {
1004+
return <div>not param shadowed</div>
1005+
})
1006+
1007+
return Comp
1008+
}
1009+
`,
1010+
errors: [
1011+
{
1012+
messageId: 'noDisplayName',
1013+
line: 5,
1014+
},
1015+
],
1016+
},
1017+
1018+
{
1019+
code: `
1020+
import React, { memo } from 'react'
1021+
1022+
const TestComponent = function () {
1023+
const Comp = memo(() => {
1024+
return <div>not destructured param shadowed</div>
1025+
})
1026+
1027+
return Comp
1028+
}
1029+
`,
1030+
errors: [
1031+
{
1032+
messageId: 'noDisplayName',
1033+
line: 5,
1034+
},
1035+
],
1036+
},
1037+
1038+
{
1039+
code: `
1040+
import React, { memo, forwardRef } from 'react'
1041+
1042+
const TestComponent = function () {
1043+
function innerFunction() {
1044+
const Comp = memo(() => <div>nested not shadowed</div>)
1045+
const ForwardComp = React.forwardRef(() => <div>nested</div>)
1046+
1047+
return [Comp, ForwardComp]
1048+
}
1049+
1050+
return innerFunction()
1051+
}
1052+
`,
1053+
errors: [
1054+
{
1055+
messageId: 'noDisplayName',
1056+
line: 6,
1057+
},
1058+
{
1059+
messageId: 'noDisplayName',
1060+
line: 7,
1061+
},
1062+
],
1063+
},
1064+
{
1065+
code: `
1066+
import React, { forwardRef } from 'react'
1067+
1068+
const TestComponent = function () {
1069+
const OtherComp = forwardRef((props, ref) => \`\${props} \${ref}\`)
1070+
1071+
return OtherComp
1072+
}
1073+
`,
1074+
errors: [
1075+
{
1076+
messageId: 'noDisplayName',
1077+
line: 5,
1078+
},
1079+
],
1080+
},
1081+
{
1082+
code: `
1083+
import React, { memo } from 'react'
1084+
1085+
const TestComponent = function () {
1086+
const Comp = memo(() => {
1087+
return <div>not shadowed</div>
1088+
})
1089+
return Comp
1090+
}
1091+
`,
1092+
errors: [
1093+
{
1094+
messageId: 'noDisplayName',
1095+
line: 5,
1096+
},
1097+
],
1098+
},
1099+
{
1100+
code: `
1101+
import React, { memo, forwardRef } from 'react'
1102+
1103+
const MixedNotShadowed = function () {
1104+
const Comp = memo(() => {
1105+
return <div>not shadowed</div>
1106+
})
1107+
const ReactMemo = React.memo(() => null)
1108+
const ReactForward = React.forwardRef((props, ref) => {
1109+
return \`\${props} \${ref}\`
1110+
})
1111+
const OtherComp = forwardRef((props, ref) => \`\${props} \${ref}\`)
1112+
1113+
return [Comp, ReactMemo, ReactForward, OtherComp]
1114+
}
1115+
`,
1116+
errors: [
1117+
{
1118+
messageId: 'noDisplayName',
1119+
line: 5,
1120+
},
1121+
{
1122+
messageId: 'noDisplayName',
1123+
line: 8,
1124+
},
1125+
{
1126+
messageId: 'noDisplayName',
1127+
line: 9,
1128+
},
1129+
{
1130+
messageId: 'noDisplayName',
1131+
line: 12,
1132+
},
1133+
],
1134+
},
9011135
{
9021136
code: `
9031137
import React from 'react'

0 commit comments

Comments
 (0)