Skip to content

Fix false-positive react component classes #354

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/__tests__/fixtures/component_15.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {Props as BarProps} from 'Bar.react';
import React from 'react';

const Bar = require('Bar.react');

Expand Down
2 changes: 2 additions & 0 deletions src/__tests__/fixtures/component_9.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Testing render method as public class field.
*/
import view from "./view.jsx";
import {Component as SomeOtherComponent} from 'react';

/**
* Should be recognized as component.
*/
Expand Down
10 changes: 6 additions & 4 deletions src/handlers/__tests__/componentMethodsHandler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ describe('componentMethodsHandler', () => {

it('extracts the documentation for a ClassDeclaration', () => {
const src = `
class Test {
import React from 'react';
class Test extends React.Component {
/**
* The foo method
*/
Expand Down Expand Up @@ -124,12 +125,13 @@ describe('componentMethodsHandler', () => {
}
`;

test(parse(src).get('body', 0));
test(parse(src).get('body', 1));
});

it('should handle and ignore computed methods', () => {
const src = `
class Test {
import React from 'react';
class Test extends React.Component {
/**
* The foo method
*/
Expand All @@ -152,7 +154,7 @@ describe('componentMethodsHandler', () => {
}
`;

componentMethodsHandler(documentation, parse(src).get('body', 0));
componentMethodsHandler(documentation, parse(src).get('body', 1));
expect(documentation.methods).toMatchSnapshot();
});

Expand Down
2 changes: 1 addition & 1 deletion src/resolver/__tests__/findAllComponentDefinitions-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('findAllComponentDefinitions', () => {

const result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(4);
expect(result.length).toBe(2);
});

it('finds React.createClass, independent of the var name', () => {
Expand Down
41 changes: 1 addition & 40 deletions src/utils/__tests__/isReactComponentClass-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,10 @@
*
*/

import { expression, statement, parse } from '../../../tests/utils';
import { parse } from '../../../tests/utils';
import isReactComponentClass from '../isReactComponentClass';

describe('isReactComponentClass', () => {
describe('render method', () => {
it('accepts class declarations with a render method', () => {
const def = statement('class Foo { render() {}}');
expect(isReactComponentClass(def)).toBe(true);
});

it('accepts class expression with a render method', () => {
const def = expression('class { render() {}}');
expect(isReactComponentClass(def)).toBe(true);
});

it('ignores static render methods', () => {
const def = statement('class Foo { static render() {}}');
expect(isReactComponentClass(def)).toBe(false);
});

it('ignores dynamic render methods', () => {
const def = statement('class Foo { static [render]() {}}');
expect(isReactComponentClass(def)).toBe(false);
});

it('ignores getter or setter render methods', () => {
let def = statement('class Foo { get render() {}}');
expect(isReactComponentClass(def)).toBe(false);

def = statement('class Foo { set render(value) {}}');
expect(isReactComponentClass(def)).toBe(false);
});
});

describe('JSDoc @extends React.Component', () => {
it('accepts class declarations declaring `@extends React.Component` in JSDoc', () => {
const def = parse(`
Expand Down Expand Up @@ -92,14 +62,5 @@ describe('isReactComponentClass', () => {

expect(isReactComponentClass(def)).toBe(false);
});

it('does not consider super class if render method is present', () => {
const def = parse(`
var {Component} = require('FakeReact');
class Foo extends Component { render() {} }
`).get('body', 1);

expect(isReactComponentClass(def)).toBe(true);
});
});
});
24 changes: 1 addition & 23 deletions src/utils/isReactComponentClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,19 @@

import types from 'ast-types';
import isReactModuleName from './isReactModuleName';
import match from './match';
import resolveToModule from './resolveToModule';
import resolveToValue from './resolveToValue';

const { namedTypes: t } = types;

function isRenderMethod(node) {
const isProperty = node.type === 'ClassProperty';
return (
(t.MethodDefinition.check(node) || isProperty) &&
!node.computed &&
!node.static &&
(node.kind === '' || node.kind === 'method' || isProperty) &&
node.key.name === 'render'
);
}

/**
* Returns `true` of the path represents a class definition which either extends
* `React.Component` or implements a `render()` method.
* Returns `true` of the path represents a class definition which extends `React.Component`
*/
export default function isReactComponentClass(path: NodePath): boolean {
const node = path.node;
if (!t.ClassDeclaration.check(node) && !t.ClassExpression.check(node)) {
return false;
}

// render method
if (node.body.body.some(isRenderMethod)) {
return true;
}

// check for @extends React.Component in docblock
if (path.parentPath && path.parentPath.value) {
const classDeclaration = Array.isArray(path.parentPath.value)
Expand All @@ -65,9 +46,6 @@ export default function isReactComponentClass(path: NodePath): boolean {
return false;
}
const superClass = resolveToValue(path.get('superClass'));
if (!match(superClass.node, { property: { name: 'Component' } })) {
return false;
}
const module = resolveToModule(superClass);
return !!module && isReactModuleName(module);
}