Skip to content

Commit

Permalink
fix: Fix wrong detection of forwardRef in combination with memo (#592)
Browse files Browse the repository at this point in the history
Release-As: 6.0.0-alpha.3

Co-authored-by: Daniel Tschinder <231804+danez@users.noreply.github.com>
  • Loading branch information
mfazekas and danez committed Jun 13, 2022
1 parent c78e9bd commit ea9cbeb
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 11 deletions.
16 changes: 16 additions & 0 deletions src/__tests__/__snapshots__/main-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,22 @@ Object {
}
`;
exports[`main fixtures processes component "component_42.js" without errors 1`] = `
Object {
"description": "",
"methods": Array [],
"props": Object {
"foo": Object {
"defaultValue": Object {
"computed": false,
"value": "'bar'",
},
"required": false,
},
},
}
`;
exports[`main fixtures processes component "flow-export-type.js" without errors 1`] = `
Object {
"description": "This is a Flow class component",
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/fixtures/component_42.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React, {memo, forwardRef} from 'react';

export default memo(forwardRef(({ foo = 'bar' }, ref) => <div ref={ref}>{foo}</div>));
16 changes: 16 additions & 0 deletions src/handlers/__tests__/componentDocblockHandler-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,22 @@ describe('componentDocblockHandler', () => {
);
});

describe('inline implementation with memo', () => {
test(`
React.memo(React.forwardRef((props, ref) => {}));
import React from "react";`, src =>
beforeLastStatement(src).get('expression'));

testImports(
`
export default React.memo(React.forwardRef((props, ref) => {}));
import React from 'react';`,
src => beforeLastStatement(src).get('declaration'),
'RefComponent',
useDefault,
);
});

describe('out of line implementation', () => {
test(`let Component = (props, ref) => {};
React.forwardRef(Component);
Expand Down
7 changes: 4 additions & 3 deletions src/handlers/defaultPropsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ function getStatelessPropsPath(
componentDefinition: NodePath,
importer: Importer,
): NodePath {
const value = resolveToValue(componentDefinition, importer);
let value = resolveToValue(componentDefinition, importer);

if (isReactForwardRefCall(value, importer)) {
const inner = resolveToValue(value.get('arguments', 0), importer);
return inner.get('params', 0);
value = resolveToValue(value.get('arguments', 0), importer);
}

return value.get('params', 0);
}

Expand Down
8 changes: 8 additions & 0 deletions src/utils/__tests__/isReactForwardRefCall-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ describe('isReactForwardRefCall', () => {
expect(isReactForwardRefCall(def, noopImporter)).toBe(true);
});

it('does not accept forwardRef if not outer call', () => {
const def = expressionLast(`
import { forwardRef, memo } from "react";
memo(forwardRef({}));
`);
expect(isReactForwardRefCall(def, noopImporter)).toBe(false);
});

it('accepts forwardRef called on imported aliased value', () => {
const def = expressionLast(`
import { forwardRef as foo } from "react";
Expand Down
13 changes: 7 additions & 6 deletions src/utils/isReactBuiltinCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,8 @@ export default function isReactBuiltinCall(

// Check if this is a destructuring assignment
// const { x } = require('react')
if (isDestructuringAssignment(value, name)) {
const module = resolveToModule(value, importer);
return Boolean(module && isReactModuleName(module));
} else if (
if (
isDestructuringAssignment(value, name) ||
// `require('react').createElement`
(t.MemberExpression.check(value.node) &&
t.Identifier.check(value.get('property').node) &&
Expand All @@ -43,11 +41,14 @@ export default function isReactBuiltinCall(
(t.ImportDeclaration.check(value.node) &&
value.node.specifiers &&
value.node.specifiers.some(
// @ts-ignore
specifier => specifier.imported && specifier.imported.name === name,
specifier =>
// @ts-ignore
specifier.imported?.name === name &&
specifier.local?.name === path.node.callee.name,
))
) {
const module = resolveToModule(value, importer);

return Boolean(module && isReactModuleName(module));
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/utils/isReactCreateClassCall.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { namedTypes as t } from 'ast-types';
import match from './match';
import resolveToModule from './resolveToModule';
import isReactBuiltinCall from './isReactBuiltinCall';
import type { Importer } from '../parse';
Expand All @@ -20,7 +19,7 @@ function isReactCreateClassCallModular(
path = path.get('expression');
}

if (!match(path.node, { type: 'CallExpression' })) {
if (!t.CallExpression.check(path.node)) {
return false;
}
const module = resolveToModule(path, importer);
Expand Down

0 comments on commit ea9cbeb

Please sign in to comment.