Skip to content

Commit fb820cc

Browse files
committed
add support for components wrapped in forwardRef
1 parent 0206acb commit fb820cc

File tree

3 files changed

+125
-7
lines changed

3 files changed

+125
-7
lines changed

.vscode/launch.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Debug Jest Tests",
6+
"type": "node",
7+
"request": "launch",
8+
"runtimeArgs": [
9+
"--inspect-brk",
10+
"${workspaceRoot}/node_modules/.bin/jest",
11+
"--runInBand"
12+
],
13+
"console": "integratedTerminal",
14+
"internalConsoleOptions": "neverOpen"
15+
}
16+
]
17+
}

index.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,29 @@ function getReturnStatement(node) {
3636
return;
3737
}
3838

39+
if (node.type === 'ArrowFunctionExpression') return node.body;
3940
return node.type === 'VariableDeclaration'
4041
? node.declarations?.[0]?.init?.body?.body?.find(
4142
(statement) => statement.type === 'ReturnStatement',
42-
) ?? node.declarations?.[0]?.init?.body
43+
) ??
44+
node.declarations?.[0]?.init?.arguments?.[0]?.body ??
45+
node.declarations?.[0]?.init?.body
4346
: node.body?.body?.find(
4447
(statement) => statement.type === 'ReturnStatement',
4548
);
4649
}
4750

51+
function isForwardRef(node) {
52+
if (!Boolean(node)) {
53+
return;
54+
}
55+
56+
return node.type === 'VariableDeclaration'
57+
? node.declarations?.[0]?.init?.callee?.property?.name === 'forwardRef'
58+
: node.callee?.name === 'forwardRef' ||
59+
node.callee?.property?.name === 'forwardRef';
60+
}
61+
4862
function isTreeDone(node, excludeComponentNames) {
4963
return (
5064
node.type === 'JSXElement' &&
@@ -95,11 +109,20 @@ const rules = {
95109
return {
96110
Program(node) {
97111
const componentNodes = node.body
98-
.map((child) => child?.declaration ?? child)
112+
.map((child) => {
113+
const declaration = child?.declaration ?? child;
114+
if (isForwardRef(declaration)) {
115+
// do something
116+
return declaration?.arguments?.[0] ?? declaration;
117+
}
118+
return declaration;
119+
})
99120
.filter(
100121
(child) =>
101122
child.type === 'VariableDeclaration' ||
102-
child.type === 'FunctionDeclaration',
123+
child.type === 'FunctionDeclaration' ||
124+
child.type === 'FunctionExpression' ||
125+
child.type === 'ArrowFunctionExpression',
103126
)
104127
.filter((child) => {
105128
let flag = false;

test.js

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,7 @@ const fragmentsWontUpdate = `const Component = () => {
9191
const reactForwardRef = /* tsx */ `
9292
export const InternalLink = React.forwardRef<HTMLAnchorElement, InternalLinkProps>(
9393
({ variant, ...props }, ref) => (
94-
<Link
95-
data-component="InternalLink"
94+
<Link data-component="InternalLink"
9695
ref={ref}
9796
className={classNames(css.link, { [css.inverse]: variant === 'inverse' })}
9897
{...props}
@@ -118,8 +117,7 @@ export const InternalLink = React.forwardRef<HTMLAnchorElement, InternalLinkProp
118117
const forwardRef = /* tsx */ `
119118
export const InternalLink = forwardRef<HTMLAnchorElement, InternalLinkProps>(
120119
({ variant, ...props }, ref) => (
121-
<Link
122-
data-component="InternalLink"
120+
<Link data-component="InternalLink"
123121
ref={ref}
124122
className={classNames(css.link, { [css.inverse]: variant === 'inverse' })}
125123
{...props}
@@ -142,6 +140,66 @@ export const InternalLink = forwardRef<HTMLAnchorElement, InternalLinkProps>(
142140
),
143141
);`;
144142

143+
const defaultReactForwardRef = /* tsx */ `
144+
export default React.forwardRef<HTMLAnchorElement, InternalLinkProps>(
145+
function InternalLink({ variant, ...props }, ref) {
146+
return (
147+
<Link data-component="InternalLink"
148+
ref={ref}
149+
className={classNames(css.link, { [css.inverse]: variant === 'inverse' })}
150+
{...props}
151+
>
152+
{props.children}
153+
</Link>
154+
);
155+
}
156+
);`;
157+
158+
const defaultReactForwardRefError = /* tsx */ `
159+
export default React.forwardRef<HTMLAnchorElement, InternalLinkProps>(
160+
function InternalLink({ variant, ...props }, ref) {
161+
return (
162+
<Link
163+
ref={ref}
164+
className={classNames(css.link, { [css.inverse]: variant === 'inverse' })}
165+
{...props}
166+
>
167+
{props.children}
168+
</Link>
169+
);
170+
}
171+
);`;
172+
173+
const defaultForwardRef = /* tsx */ `
174+
export default forwardRef<HTMLAnchorElement, InternalLinkProps>(
175+
function InternalLink({ variant, ...props }, ref) {
176+
return (
177+
<Link data-component="InternalLink"
178+
ref={ref}
179+
className={classNames(css.link, { [css.inverse]: variant === 'inverse' })}
180+
{...props}
181+
>
182+
{props.children}
183+
</Link>
184+
);
185+
}
186+
);`;
187+
188+
const defaultForwardRefError = /* tsx */ `
189+
export default forwardRef<HTMLAnchorElement, InternalLinkProps>(
190+
function InternalLink({ variant, ...props }, ref) {
191+
return (
192+
<Link
193+
ref={ref}
194+
className={classNames(css.link, { [css.inverse]: variant === 'inverse' })}
195+
{...props}
196+
>
197+
{props.children}
198+
</Link>
199+
);
200+
}
201+
);`;
202+
145203
const tests = {
146204
'data-component': {
147205
// Require the actual rule definition
@@ -193,6 +251,12 @@ const tests = {
193251
{
194252
code: forwardRef,
195253
},
254+
{
255+
code: defaultReactForwardRef,
256+
},
257+
{
258+
code: defaultForwardRef,
259+
},
196260
],
197261
invalid: [
198262
{
@@ -239,6 +303,20 @@ const tests = {
239303
'InternalLink is missing the data-component attribute for the top-level element.',
240304
],
241305
},
306+
{
307+
code: defaultReactForwardRefError,
308+
output: defaultReactForwardRef,
309+
errors: [
310+
'InternalLink is missing the data-component attribute for the top-level element.',
311+
],
312+
},
313+
{
314+
code: defaultForwardRefError,
315+
output: defaultForwardRef,
316+
errors: [
317+
'InternalLink is missing the data-component attribute for the top-level element.',
318+
],
319+
},
242320
],
243321
},
244322
},

0 commit comments

Comments
 (0)