Skip to content

Commit 13a8de9

Browse files
jquensedanez
authored andcommitted
fix: bail when function returns are recursive instead of stack overflowing
1 parent a2978e8 commit 13a8de9

File tree

2 files changed

+28
-11
lines changed

2 files changed

+28
-11
lines changed

src/utils/__tests__/isStatelessComponent-test.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*
77
*/
88

9-
import { statement, parse } from '../../../tests/utils';
9+
import { parse, statement } from '../../../tests/utils';
1010
import isStatelessComponent from '../isStatelessComponent';
1111

1212
describe('isStatelessComponent', () => {
@@ -233,6 +233,16 @@ describe('isStatelessComponent', () => {
233233
expect(isStatelessComponent(def)).toBe(true);
234234
});
235235

236+
it('handles recursive function calls', () => {
237+
const def = statement(`
238+
function Foo (props) {
239+
return props && Foo(props);
240+
}
241+
`);
242+
243+
expect(isStatelessComponent(def)).toBe(false);
244+
});
245+
236246
test(
237247
'handles simple resolves',
238248
`

src/utils/isStatelessComponent.js

+17-10
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,14 @@ function isJSXElementOrReactCall(path) {
3333
);
3434
}
3535

36-
function resolvesToJSXElementOrReactCall(path) {
36+
function resolvesToJSXElementOrReactCall(path, seen) {
37+
// avoid returns with recursive function calls
38+
if (seen.has(path)) {
39+
return false;
40+
}
41+
42+
seen.add(path);
43+
3744
// Is the path is already a JSX element or a call to one of the React.* functions
3845
if (isJSXElementOrReactCall(path)) {
3946
return true;
@@ -45,17 +52,17 @@ function resolvesToJSXElementOrReactCall(path) {
4552
// the two possible paths
4653
if (resolvedPath.node.type === 'ConditionalExpression') {
4754
return (
48-
resolvesToJSXElementOrReactCall(resolvedPath.get('consequent')) ||
49-
resolvesToJSXElementOrReactCall(resolvedPath.get('alternate'))
55+
resolvesToJSXElementOrReactCall(resolvedPath.get('consequent'), seen) ||
56+
resolvesToJSXElementOrReactCall(resolvedPath.get('alternate'), seen)
5057
);
5158
}
5259

5360
// If the path points to a logical expression (AND, OR, ...), then we need to look only at
5461
// the two possible paths
5562
if (resolvedPath.node.type === 'LogicalExpression') {
5663
return (
57-
resolvesToJSXElementOrReactCall(resolvedPath.get('left')) ||
58-
resolvesToJSXElementOrReactCall(resolvedPath.get('right'))
64+
resolvesToJSXElementOrReactCall(resolvedPath.get('left'), seen) ||
65+
resolvesToJSXElementOrReactCall(resolvedPath.get('right'), seen)
5966
);
6067
}
6168

@@ -69,7 +76,7 @@ function resolvesToJSXElementOrReactCall(path) {
6976
if (resolvedPath.node.type === 'CallExpression') {
7077
let calleeValue = resolveToValue(resolvedPath.get('callee'));
7178

72-
if (returnsJSXElementOrReactCall(calleeValue)) {
79+
if (returnsJSXElementOrReactCall(calleeValue, seen)) {
7380
return true;
7481
}
7582

@@ -110,7 +117,7 @@ function resolvesToJSXElementOrReactCall(path) {
110117

111118
if (
112119
!resolvedMemberExpression ||
113-
returnsJSXElementOrReactCall(resolvedMemberExpression)
120+
returnsJSXElementOrReactCall(resolvedMemberExpression, seen)
114121
) {
115122
return true;
116123
}
@@ -120,14 +127,14 @@ function resolvesToJSXElementOrReactCall(path) {
120127
return false;
121128
}
122129

123-
function returnsJSXElementOrReactCall(path) {
130+
function returnsJSXElementOrReactCall(path, seen = new WeakSet()) {
124131
let visited = false;
125132

126133
// early exit for ArrowFunctionExpressions
127134
if (
128135
path.node.type === 'ArrowFunctionExpression' &&
129136
path.get('body').node.type !== 'BlockStatement' &&
130-
resolvesToJSXElementOrReactCall(path.get('body'))
137+
resolvesToJSXElementOrReactCall(path.get('body'), seen)
131138
) {
132139
return true;
133140
}
@@ -143,7 +150,7 @@ function returnsJSXElementOrReactCall(path) {
143150
// Only check return statements which are part of the checked function scope
144151
if (returnPath.scope !== scope) return false;
145152

146-
if (resolvesToJSXElementOrReactCall(returnPath.get('argument'))) {
153+
if (resolvesToJSXElementOrReactCall(returnPath.get('argument'), seen)) {
147154
visited = true;
148155
return false;
149156
}

0 commit comments

Comments
 (0)