Skip to content

Commit b924f97

Browse files
Extract documentation from Component methods
1 parent dca8ec9 commit b924f97

14 files changed

+827
-3
lines changed

.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
},
1212
"globals": {
1313
"ASTNode": true,
14+
"FlowTypeDescriptor": true,
1415
"Handler": true,
1516
"NodePath": true,
16-
"PropTypeDescriptor": true,
1717
"PropDescriptor": true,
18+
"PropTypeDescriptor": true,
1819
"Resolver": true
1920
}
2021
}

flow/recast.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ declare class NodePath {
3535
each(f: (p: NodePath) => any): any;
3636
map<T>(f: (p: NodePath) => T): Array<T>;
3737
filter(f: (p: NodePath) => bool): Array<NodePath>;
38+
push(node: ASTNode): void;
3839
}
3940

4041
type Recast = {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"async": "^1.4.2",
2828
"babel-runtime": "~5.8.25",
2929
"babylon": "~5.8.3",
30+
"doctrine": "^1.2.0",
3031
"node-dir": "^0.1.10",
3132
"nomnom": "^1.8.1",
3233
"recast": "^0.10.41"

src/__tests__/main-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('main', () => {
2626
expect(docs).toEqual({
2727
displayName: 'ABC',
2828
description: 'Example component description',
29+
methods: [],
2930
props: {
3031
foo: {
3132
type: {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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+
jest.mock('../../Documentation');
15+
16+
describe('componentMethodsHandler', () => {
17+
let documentation;
18+
let componentMethodsHandler;
19+
let parse;
20+
21+
beforeEach(() => {
22+
({parse} = require('../../../tests/utils'));
23+
documentation = new (require('../../Documentation'));
24+
componentMethodsHandler = require('../componentMethodsHandler');
25+
});
26+
27+
function test(definition) {
28+
componentMethodsHandler(documentation, definition);
29+
expect(documentation.methods).toEqual([{
30+
name: 'foo',
31+
description: 'The foo method',
32+
visibility: 'protected',
33+
modifiers: [],
34+
return: {
35+
description: 'The number',
36+
type: {name: 'number'},
37+
},
38+
params: [{
39+
name: 'bar',
40+
description: 'The bar param',
41+
type: {name: 'number'},
42+
}],
43+
}, {
44+
name: 'bar',
45+
description: 'Static function',
46+
visibility: 'public',
47+
modifiers: ['static'],
48+
return: null,
49+
params: [],
50+
}]);
51+
}
52+
53+
it('extracts the documentation for an ObjectExpression', () => {
54+
const src = `
55+
({
56+
/**
57+
* The foo method
58+
* @protected
59+
* @param bar The bar param
60+
* @returns The number
61+
*/
62+
foo(bar: number): number {
63+
return bar;
64+
},
65+
statics: {
66+
/**
67+
* Static function
68+
*/
69+
bar() {}
70+
},
71+
state: {
72+
foo: 'foo',
73+
},
74+
componentDidMount() {},
75+
render() {
76+
return null;
77+
},
78+
})
79+
`;
80+
81+
test(parse(src).get('body', 0, 'expression'));
82+
});
83+
84+
it('extracts the documentation for a ClassDeclaration', () => {
85+
const src = `
86+
class Test {
87+
/**
88+
* The foo method
89+
* @protected
90+
* @param bar The bar param
91+
* @returns The number
92+
*/
93+
foo(bar: number): number {
94+
return bar;
95+
}
96+
97+
/**
98+
* Static function
99+
*/
100+
static bar() {}
101+
102+
state = {
103+
foo: 'foo',
104+
};
105+
106+
componentDidMount() {}
107+
108+
render() {
109+
return null;
110+
}
111+
}
112+
`;
113+
114+
test(parse(src).get('body', 0));
115+
});
116+
117+
it('should not find methods for stateless components', () => {
118+
const src = `
119+
(props) => {}
120+
`;
121+
122+
const definition = parse(src).get('body', 0, 'expression');
123+
componentMethodsHandler(documentation, definition);
124+
expect(documentation.methods).toEqual([]);
125+
});
126+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
import recast from 'recast';
13+
14+
import getMemberValuePath from '../utils/getMemberValuePath';
15+
import getMethodDocumentation from '../utils/getMethodDocumentation';
16+
import isReactComponentClass from '../utils/isReactComponentClass';
17+
import isReactComponentMethod from '../utils/isReactComponentMethod';
18+
import isReactCreateClassCall from '../utils/isReactCreateClassCall';
19+
20+
import type Documentation from '../Documentation';
21+
22+
const {types: {namedTypes: types}} = recast;
23+
24+
function getMethodsDoc(methodPaths) {
25+
const methods = [];
26+
27+
methodPaths.forEach((methodPath) => {
28+
if (isReactComponentMethod(methodPath)) {
29+
return;
30+
}
31+
32+
methods.push(getMethodDocumentation(methodPath));
33+
});
34+
35+
return methods;
36+
}
37+
38+
/**
39+
* Extract all flow types and information from jsdoc for the methods of a
40+
* react component. Doesn't return any react specific lifecycle methods.
41+
*/
42+
export default function componentMethodsHandler(
43+
documentation: Documentation,
44+
path: NodePath
45+
) {
46+
// Extract all methods from the class or object.
47+
let methodPaths;
48+
if (isReactComponentClass(path)) {
49+
methodPaths = path
50+
.get('body', 'body')
51+
.filter(p => types.MethodDefinition.check(p.node) && p.node.kind !== 'constructor');
52+
} else if (types.ObjectExpression.check(path.node)) {
53+
const properties = path.get('properties');
54+
55+
// Add the statics object properties.
56+
const statics = getMemberValuePath(path, 'statics');
57+
if (statics) {
58+
statics.get('properties').each(p => {
59+
p.node.static = true;
60+
properties.push(p.node);
61+
});
62+
}
63+
64+
methodPaths = properties.filter(p => types.FunctionExpression.check(p.get('value').node));
65+
}
66+
67+
if (!methodPaths) {
68+
documentation.set('methods', []);
69+
return;
70+
}
71+
72+
const methods = getMethodsDoc(methodPaths);
73+
documentation.set('methods', methods);
74+
}

src/handlers/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313

1414
export {default as componentDocblockHandler} from './componentDocblockHandler';
15+
export {default as componentMethodsHandler} from './componentMethodsHandler';
1516
export {default as defaultPropsHandler} from './defaultPropsHandler';
1617
export {default as propTypeHandler} from './propTypeHandler';
1718
export {default as propTypeCompositionHandler} from './propTypeCompositionHandler';

src/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var defaultHandlers = [
2525
handlers.defaultPropsHandler,
2626
handlers.componentDocblockHandler,
2727
handlers.displayNameHandler,
28+
handlers.componentMethodsHandler,
2829
];
2930

3031
/**

0 commit comments

Comments
 (0)