Skip to content

Commit b3601b6

Browse files
authored
fix: be more strict about detecting react class components (#397)
Previously a class just needed to have a `render` method. Now it in addition requires to have a superclass as well.
1 parent aa54200 commit b3601b6

File tree

4 files changed

+76
-20
lines changed

4 files changed

+76
-20
lines changed

src/handlers/__tests__/componentMethodsHandler-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ describe('componentMethodsHandler', () => {
9494

9595
it('extracts the documentation for a ClassDeclaration', () => {
9696
const src = `
97-
class Test {
97+
class Test extends React.Component {
9898
/**
9999
* The foo method
100100
*/
@@ -129,7 +129,7 @@ describe('componentMethodsHandler', () => {
129129

130130
it('should handle and ignore computed methods', () => {
131131
const src = `
132-
class Test {
132+
class Test extends React.Component {
133133
/**
134134
* The foo method
135135
*/

src/resolver/__tests__/findAllComponentDefinitions-test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ describe('findAllComponentDefinitions', () => {
9999
const source = `
100100
import React from 'React';
101101
class ComponentA extends React.Component {}
102-
class ComponentB { render() {} }
103-
var ComponentC = class extends React.Component {}
104-
var ComponentD = class { render() {} }
102+
class ComponentB extends Foo { render() {} }
103+
var ComponentC = class extends React.PureComponent {}
104+
var ComponentD = class extends Bar { render() {} }
105105
class NotAComponent {}
106106
`;
107107

src/utils/__tests__/isReactComponentClass-test.js

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import isReactComponentClass from '../isReactComponentClass';
1111

1212
describe('isReactComponentClass', () => {
1313
describe('render method', () => {
14-
it('accepts class declarations with a render method', () => {
14+
it('ignores class declarations with a render method without superclass', () => {
1515
const def = statement('class Foo { render() {}}');
16-
expect(isReactComponentClass(def)).toBe(true);
16+
expect(isReactComponentClass(def)).toBe(false);
1717
});
1818

19-
it('accepts class expression with a render method', () => {
19+
it('ignores class expression with a render method without superclass', () => {
2020
const def = expression('class { render() {}}');
21-
expect(isReactComponentClass(def)).toBe(true);
21+
expect(isReactComponentClass(def)).toBe(false);
2222
});
2323

2424
it('ignores static render methods', () => {
@@ -102,4 +102,52 @@ describe('isReactComponentClass', () => {
102102
expect(isReactComponentClass(def)).toBe(true);
103103
});
104104
});
105+
106+
describe('React.PureComponent inheritance', () => {
107+
it('accepts class declarations extending React.PureComponent', () => {
108+
const def = parse(`
109+
var React = require('react');
110+
class Foo extends React.PureComponent {}
111+
`).get('body', 1);
112+
113+
expect(isReactComponentClass(def)).toBe(true);
114+
});
115+
116+
it('accepts class expressions extending React.PureComponent', () => {
117+
const def = parse(`
118+
var React = require('react');
119+
var Foo = class extends React.PureComponent {}
120+
`).get('body', 1, 'declarations', 0, 'init');
121+
122+
expect(isReactComponentClass(def)).toBe(true);
123+
});
124+
125+
it('resolves the super class reference', () => {
126+
const def = parse(`
127+
var {PureComponent} = require('react');
128+
var C = PureComponent;
129+
class Foo extends C {}
130+
`).get('body', 2);
131+
132+
expect(isReactComponentClass(def)).toBe(true);
133+
});
134+
135+
it('does not accept references to other modules', () => {
136+
const def = parse(`
137+
var {PureComponent} = require('FakeReact');
138+
class Foo extends PureComponent {}
139+
`).get('body', 1);
140+
141+
expect(isReactComponentClass(def)).toBe(false);
142+
});
143+
144+
it('does not consider super class if render method is present', () => {
145+
const def = parse(`
146+
var {PureComponent} = require('FakeReact');
147+
class Foo extends PureComponent { render() {} }
148+
`).get('body', 1);
149+
150+
expect(isReactComponentClass(def)).toBe(true);
151+
});
152+
});
105153
});

src/utils/isReactComponentClass.js

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,31 @@ function isRenderMethod(node) {
2828

2929
/**
3030
* Returns `true` of the path represents a class definition which either extends
31-
* `React.Component` or implements a `render()` method.
31+
* `React.Component` or has a superclass and implements a `render()` method.
3232
*/
3333
export default function isReactComponentClass(path: NodePath): boolean {
3434
const node = path.node;
3535
if (!t.ClassDeclaration.check(node) && !t.ClassExpression.check(node)) {
3636
return false;
3737
}
3838

39+
// extends something
40+
if (!node.superClass) {
41+
return false;
42+
}
43+
44+
// React.Component or React.PureComponent
45+
const superClass = resolveToValue(path.get('superClass'));
46+
if (
47+
match(superClass.node, { property: { name: 'Component' } }) ||
48+
match(superClass.node, { property: { name: 'PureComponent' } })
49+
) {
50+
const module = resolveToModule(superClass);
51+
if (module && isReactModuleName(module)) {
52+
return true;
53+
}
54+
}
55+
3956
// render method
4057
if (node.body.body.some(isRenderMethod)) {
4158
return true;
@@ -60,14 +77,5 @@ export default function isReactComponentClass(path: NodePath): boolean {
6077
}
6178
}
6279

63-
// extends ReactComponent?
64-
if (!node.superClass) {
65-
return false;
66-
}
67-
const superClass = resolveToValue(path.get('superClass'));
68-
if (!match(superClass.node, { property: { name: 'Component' } })) {
69-
return false;
70-
}
71-
const module = resolveToModule(superClass);
72-
return !!module && isReactModuleName(module);
80+
return false;
7381
}

0 commit comments

Comments
 (0)