Skip to content

Commit 878d57d

Browse files
committed
Relax composition detection
Previously, a module was only considered to be composed if it was used in a member expression containing "propTypes". E.g. var Foo = require('Foo'); var A = React.createClass({ propTypes: { ...Foo.propTypes } }); but not var Foo = require('Foo'); var A = React.createClass({ propTypes: { ...Foo } }); The latter is now allowed as well, because react-docgen doesn't actually care about the characteristics of the module (see #9). This also fixes a bug where `propTypes: Foo.propTypes` wasn't treated correctly.
1 parent e18dc1d commit 878d57d

File tree

5 files changed

+190
-11
lines changed

5 files changed

+190
-11
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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, it, expect, beforeEach, afterEach*/
12+
13+
"use strict";
14+
15+
jest.autoMockOff();
16+
jest.mock('../../Documentation');
17+
18+
describe('propTypeCompositionHandler', () => {
19+
var utils;
20+
var getPropTypeMock;
21+
var documentation;
22+
var propTypeCompositionHandler;
23+
24+
beforeEach(() => {
25+
utils = require('../../../tests/utils');
26+
getPropTypeMock = jest.genMockFunction().mockImplementation(() => ({}));
27+
jest.setMock('../../utils/getPropType', getPropTypeMock);
28+
jest.mock('../../utils/getPropType');
29+
30+
documentation = new (require('../../Documentation'))();
31+
propTypeCompositionHandler = require('../propTypeCompositionHandler');
32+
});
33+
34+
function parse(definition) {
35+
var programPath = utils.parseWithTemplate(definition, utils.REACT_TEMPLATE);
36+
return programPath.get(
37+
'body',
38+
programPath.node.body.length - 1,
39+
'expression'
40+
);
41+
}
42+
43+
it('understands assignment from module', () => {
44+
var definition = parse([
45+
'var Foo = require("Foo.react");',
46+
'({',
47+
' propTypes: Foo.propTypes',
48+
'})',
49+
].join('\n'));
50+
51+
propTypeCompositionHandler(documentation, definition);
52+
expect(documentation.composes).toEqual(['Foo.react']);
53+
54+
documentation.composes.length = 0;
55+
definition = parse([
56+
'var SharedProps = require("SharedProps");',
57+
'({',
58+
' propTypes: SharedProps',
59+
'})',
60+
].join('\n'));
61+
62+
propTypeCompositionHandler(documentation, definition);
63+
expect(documentation.composes).toEqual(['SharedProps']);
64+
});
65+
66+
it('understands the spread operator', () => {
67+
var definition = parse([
68+
'var Foo = require("Foo.react");',
69+
'var SharedProps = require("SharedProps");',
70+
'({',
71+
' propTypes: {',
72+
' ...Foo.propTypes,',
73+
' ...SharedProps',
74+
' }',
75+
'})',
76+
].join('\n'));
77+
78+
propTypeCompositionHandler(documentation, definition);
79+
expect(documentation.composes).toEqual(['Foo.react', 'SharedProps']);
80+
});
81+
82+
it('does not error if propTypes cannot be found', () => {
83+
var definition = parse([
84+
'({',
85+
' fooBar: 42',
86+
'})',
87+
].join('\n'));
88+
89+
expect(function() {
90+
propTypeCompositionHandler(documentation, definition);
91+
}).not.toThrow();
92+
});
93+
});

src/handlers/__tests__/propTypeHandler-test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*
99
*/
1010

11+
/*global jest, describe, it, expect, beforeEach, afterEach*/
12+
1113
"use strict";
1214

1315
jest.autoMockOff();
@@ -145,6 +147,18 @@ describe('propTypeHandler', () => {
145147
});
146148
});
147149

150+
it('understands assignment from module', () => {
151+
var definition = parse([
152+
'var Foo = require("Foo.react");',
153+
'({',
154+
' propTypes: Foo.propTypes',
155+
'})',
156+
].join('\n'));
157+
158+
propTypeHandler(documentation, definition);
159+
expect(documentation.composes).toEqual(['Foo.react']);
160+
});
161+
148162
it('understands the spread operator', () => {
149163
var definition = parse([
150164
'var Foo = require("Foo.react");',

src/handlers/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
exports.componentDocblockHandler = require('./componentDocblockHandler');
1414
exports.defaultPropsHandler = require('./defaultPropsHandler');
1515
exports.propTypeHandler = require('./propTypeHandler');
16+
exports.propTypeCompositionHandler = require('./propTypeCompositionHandler');
1617
exports.propDocBlockHandler = require('./propDocBlockHandler');
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
/**
12+
* @flow
13+
*/
14+
"use strict";
15+
16+
var Documentation = require('../Documentation');
17+
18+
var getPropertyValuePath = require('../utils/getPropertyValuePath');
19+
var recast = require('recast');
20+
var resolveToModule = require('../utils/resolveToModule');
21+
var resolveToValue = require('../utils/resolveToValue');
22+
var types = recast.types.namedTypes;
23+
24+
/**
25+
* It resolves the path to its module name and adds it to the "composes" entry
26+
* in the documentation.
27+
*/
28+
function amendComposes(documentation, path) {
29+
var moduleName = resolveToModule(path);
30+
if (moduleName) {
31+
documentation.addComposes(moduleName);
32+
}
33+
}
34+
35+
function processObjectExpression(documentation, path) {
36+
path.get('properties').each(function(propertyPath) {
37+
switch (propertyPath.node.type) {
38+
case types.SpreadProperty.name:
39+
var resolvedValuePath = resolveToValue(propertyPath.get('argument'));
40+
amendComposes(documentation, resolvedValuePath);
41+
break;
42+
}
43+
});
44+
}
45+
46+
function propTypeCompositionHandler(
47+
documentation: Documentation,
48+
path: NodePath
49+
) {
50+
var propTypesPath = getPropertyValuePath(path, 'propTypes');
51+
if (!propTypesPath) {
52+
return;
53+
}
54+
propTypesPath = resolveToValue(propTypesPath);
55+
if (!propTypesPath) {
56+
return;
57+
}
58+
59+
switch (propTypesPath.node.type) {
60+
case types.ObjectExpression.name:
61+
processObjectExpression(documentation, propTypesPath);
62+
break;
63+
default:
64+
amendComposes(documentation, propTypesPath);
65+
break;
66+
}
67+
}
68+
69+
module.exports = propTypeCompositionHandler;

src/handlers/propTypeHandler.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var getPropertyName = require('../utils/getPropertyName');
2222
var getPropertyValuePath = require('../utils/getPropertyValuePath');
2323
var isReactModuleName = require('../utils/isReactModuleName');
2424
var printValue = require('../utils/printValue');
25+
var propTypeCompositionHandler = require('./propTypeCompositionHandler');
2526
var recast = require('recast');
2627
var resolveToModule = require('../utils/resolveToModule');
2728
var resolveToValue = require('../utils/resolveToValue');
@@ -67,7 +68,7 @@ function amendComposes(documentation, path) {
6768
}
6869
}
6970

70-
function amendPropTypes(documentation, path) {
71+
function amendPropTypes(documentation, path, componentPath) {
7172
path.get('properties').each(function(propertyPath) {
7273
switch (propertyPath.node.type) {
7374
case types.Property.name:
@@ -91,8 +92,9 @@ function amendPropTypes(documentation, path) {
9192
case types.ObjectExpression.name: // normal object literal
9293
amendPropTypes(documentation, resolvedValuePath);
9394
break;
94-
case types.MemberExpression.name:
95-
amendComposes(documentation, resolvedValuePath);
95+
default:
96+
// for backwards compatibility
97+
propTypeCompositionHandler(documentation, componentPath);
9698
break;
9799
}
98100
break;
@@ -106,17 +108,17 @@ function propTypeHandler(documentation: Documentation, path: NodePath) {
106108
return;
107109
}
108110
propTypesPath = resolveToValue(propTypesPath);
109-
if (!propTypesPath || !types.ObjectExpression.check(propTypesPath.node)) {
111+
if (!propTypesPath) {
110112
return;
111113
}
112-
113-
switch (propTypesPath.node.type) {
114-
case types.ObjectExpression.name:
115-
amendPropTypes(documentation, propTypesPath);
116-
break;
117-
case types.MemberExpression.name:
118-
amendComposes(documentation, propTypesPath);
114+
if (!types.ObjectExpression.check(propTypesPath.node)) {
115+
// maybe composition
116+
// for backwards compatibility
117+
propTypeCompositionHandler(documentation, path);
118+
return;
119119
}
120+
121+
amendPropTypes(documentation, propTypesPath, path);
120122
}
121123

122124
module.exports = propTypeHandler;

0 commit comments

Comments
 (0)