Skip to content

Commit 65f0339

Browse files
committed
Add helper methods to get members from objects or classes
With `getMemberValuePath`, handlers don't have to care whether the component definition comes from an ObjectExpression (via `React.createClass`) or a class definition. This helper function also "normalizes" member names between ObjectExpressions and class definitions. E.g. "defaultProps" will both look for "defaultProps" and "getDefaultProps".
1 parent 33c8e7c commit 65f0339

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
11+
/*global jest, describe, beforeEach, it, expect*/
12+
13+
jest.autoMockOff();
14+
15+
describe('getClassMemberValuePath', () => {
16+
var getClassMemberValuePath;
17+
var statement;
18+
19+
beforeEach(() => {
20+
getClassMemberValuePath = require('../getClassMemberValuePath');
21+
({statement} = require('../../../tests/utils'));
22+
});
23+
24+
describe('MethodDefinitions', () => {
25+
it('finds "normal" method definitions', () => {
26+
var def = statement(`
27+
class Foo {
28+
render() {}
29+
}
30+
`);
31+
32+
expect(getClassMemberValuePath(def, 'render'))
33+
.toBe(def.get('body', 'body', 0, 'value'));
34+
});
35+
36+
it('finds computed method definitions with literal keys', () => {
37+
var def = statement(`
38+
class Foo {
39+
['render']() {}
40+
}
41+
`);
42+
43+
expect(getClassMemberValuePath(def, 'render'))
44+
.toBe(def.get('body', 'body', 0, 'value'));
45+
});
46+
47+
it('ignores computed method definitions with expression', () => {
48+
var def = statement(`
49+
class Foo {
50+
[render]() {}
51+
}
52+
`);
53+
54+
expect(getClassMemberValuePath(def, 'render')).not.toBeDefined();
55+
});
56+
});
57+
58+
xdescribe('ClassProperty', () => {
59+
it('finds "normal" class properties', () => {
60+
var def = statement(`
61+
class Foo {
62+
foo = 42;
63+
}
64+
`);
65+
66+
expect(getClassMemberValuePath(def, 'foo'))
67+
.toBe(def.get('body', 'body', 0, 'value'));
68+
});
69+
});
70+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
11+
/*global jest, describe, xdescribe, beforeEach, it, expect*/
12+
13+
jest
14+
.dontMock('../getMemberValuePath.js');
15+
16+
describe('getMemberValuePath', () => {
17+
var getMemberValuePath;
18+
var expression, statement;
19+
20+
beforeEach(() => {
21+
({expression, statement} = require('../../../tests/utils'));
22+
getMemberValuePath = require('../getMemberValuePath');
23+
});
24+
25+
it('handles ObjectExpresisons', () => {
26+
var getPropertyValuePath = require('../getPropertyValuePath');
27+
var path = expression('{}');
28+
29+
getMemberValuePath(path, 'foo');
30+
expect(getPropertyValuePath).toBeCalledWith(path, 'foo');
31+
});
32+
33+
it('handles ClassDeclarations', () => {
34+
var getClassMemberValuePath = require('../getClassMemberValuePath');
35+
var path = statement('class Foo {}');
36+
37+
getMemberValuePath(path, 'foo');
38+
expect(getClassMemberValuePath).toBeCalledWith(path, 'foo');
39+
});
40+
41+
it('handles ClassExpressions', () => {
42+
var getClassMemberValuePath = require('../getClassMemberValuePath');
43+
var path = expression('class {}');
44+
45+
getMemberValuePath(path, 'foo');
46+
expect(getClassMemberValuePath).toBeCalledWith(path, 'foo');
47+
});
48+
49+
it('tries synonyms', () => {
50+
var getPropertyValuePath = require('../getPropertyValuePath');
51+
var getClassMemberValuePath = require('../getClassMemberValuePath');
52+
var path = expression('{}');
53+
54+
getMemberValuePath(path, 'defaultProps');
55+
expect(getPropertyValuePath).toBeCalledWith(path, 'defaultProps');
56+
expect(getPropertyValuePath).toBeCalledWith(path, 'getDefaultProps');
57+
58+
path = statement('class Foo {}');
59+
60+
getMemberValuePath(path, 'defaultProps');
61+
expect(getClassMemberValuePath).toBeCalledWith(path, 'defaultProps');
62+
expect(getClassMemberValuePath).toBeCalledWith(path, 'getDefaultProps');
63+
});
64+
65+
it('returns the result of getPropertyValuePath and getClassMemberValuePath', () => {
66+
var getPropertyValuePath = require('../getPropertyValuePath');
67+
var getClassMemberValuePath = require('../getClassMemberValuePath');
68+
getPropertyValuePath.mockReturnValue(42);
69+
getClassMemberValuePath.mockReturnValue(21);
70+
var path = expression('{}');
71+
72+
expect(getMemberValuePath(path, 'defaultProps')).toBe(42);
73+
74+
path = statement('class Foo {}');
75+
76+
expect(getMemberValuePath(path, 'defaultProps')).toBe(21);
77+
});
78+
});

src/utils/getClassMemberValuePath.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*
11+
*/
12+
13+
import getNameOrValue from './getNameOrValue';
14+
import recast from 'recast';
15+
16+
var {types: {namedTypes: types}} = recast;
17+
18+
export default function getClassMemberValuePath(
19+
classDefinition: NodePath,
20+
memberName: string
21+
) {
22+
// Fortunately it seems like that all members of a class body, be it
23+
// ClassProperty or MethodDefinition, have the same structure: They have a
24+
// "key" and a "value"
25+
return classDefinition.get('body', 'body')
26+
.filter(memberPath => (
27+
(!memberPath.node.computed || types.Literal.check(memberPath.node.key)) &&
28+
getNameOrValue(memberPath.get('key')) === memberName
29+
))
30+
.map(memberPath => memberPath.get('value'))[0];
31+
}

src/utils/getMemberValuePath.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*
11+
*/
12+
13+
import getClassMemberValuePath from './getClassMemberValuePath';
14+
import getPropertyValuePath from './getPropertyValuePath';
15+
import recast from 'recast';
16+
17+
var {types: {namedTypes: types}} = recast;
18+
19+
const SYNONYMS = {
20+
getDefaultProps: 'defaultProps',
21+
defaultProps: 'getDefaultProps',
22+
};
23+
24+
const LOOKUP_METHOD = {
25+
[types.ObjectExpression.name]: getPropertyValuePath,
26+
[types.ClassDeclaration.name]: getClassMemberValuePath,
27+
[types.ClassExpression.name]: getClassMemberValuePath,
28+
};
29+
30+
function isSupportedDefinitionType({node}) {
31+
return types.ObjectExpression.check(node) ||
32+
types.ClassDeclaration.check(node) ||
33+
types.ClassExpression.check(node);
34+
}
35+
36+
/**
37+
* This is a helper method for handlers to make it easier to work either with
38+
* an ObjectExpression from `React.createClass` class or with a class
39+
* definition.
40+
*
41+
* Given a path and a name, this function will either return the path of the
42+
* property value if the path is an ObjectExpression, or the value of the
43+
* ClassProperty/MethodDefinition if it is a class definition (declaration or
44+
* expression).
45+
*
46+
* It also normalizes the names so that e.g. `defaultProps` and
47+
* `getDefaultProps` can be used interchangeably.
48+
*/
49+
export default function getMemberValuePath(
50+
componentDefinition: NodePath,
51+
memberName: string
52+
): ?NodePath {
53+
if (!isSupportedDefinitionType(componentDefinition)) {
54+
throw new TypeError(
55+
'Got unsupported definition type. Definition must either be an ' +
56+
'ObjectExpression, ClassDeclaration or ClassExpression. Got "' +
57+
componentDefinition.node.type + '" instead.'
58+
);
59+
}
60+
61+
var lookupMethod = LOOKUP_METHOD[componentDefinition.node.type];
62+
var result = lookupMethod(componentDefinition, memberName);
63+
if (!result && SYNONYMS[memberName]) {
64+
return lookupMethod(componentDefinition, SYNONYMS[memberName]);
65+
}
66+
return result;
67+
}

0 commit comments

Comments
 (0)