Skip to content

Commit eaf76a3

Browse files
committed
Handle class properties with function values as methods (fixes #182)
1 parent dd44ef5 commit eaf76a3

File tree

4 files changed

+243
-38
lines changed

4 files changed

+243
-38
lines changed

src/__tests__/__snapshots__/main-test.js.snap

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,59 @@ Object {
191191
],
192192
}
193193
`;
194+
195+
exports[`main fixtures processes component "component_10.js" without errors 1`] = `
196+
Object {
197+
"description": "React component that display current time at current location.",
198+
"methods": Array [
199+
Object {
200+
"description": "Update clock state with new time",
201+
"docblock": "Update clock state with new time",
202+
"modifiers": Array [],
203+
"name": "updateClock",
204+
"params": Array [],
205+
"returns": null,
206+
},
207+
Object {
208+
"description": "Parse current Date object",
209+
"docblock": "Parse current Date object
210+
211+
@returns {Object} currentTime
212+
@returns {int} currentTime.hour
213+
@returns {int} currentTime.minutes
214+
@returns {string} currentTime.ampm \"am\" or \"pm\"
215+
@returns {string} currentTime.dayOfWeek
216+
@returns {string} currentTime.month
217+
@returns {int} currentTime.date",
218+
"modifiers": Array [],
219+
"name": "getTime",
220+
"params": Array [],
221+
"returns": Object {
222+
"description": "currentTime",
223+
"type": Object {
224+
"name": "Object",
225+
},
226+
},
227+
},
228+
Object {
229+
"description": "Update current clock for every 1 second",
230+
"docblock": "Update current clock for every 1 second",
231+
"modifiers": Array [],
232+
"name": "setTimer",
233+
"params": Array [],
234+
"returns": null,
235+
},
236+
],
237+
"props": Object {
238+
"title": Object {
239+
"description": "A text display current\'s user identity,
240+
\"Nobody\" if no one is detected in the background,
241+
\"Hi, ..name\" if an user is detected",
242+
"required": false,
243+
"type": Object {
244+
"name": "string",
245+
},
246+
},
247+
},
248+
}
249+
`;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* Test for documentation of "arrow function methods."
3+
*/
4+
5+
import React, { Component } from 'react';
6+
import styled from 'styled-components';
7+
8+
/**
9+
* React component that display current time at current location.
10+
*/
11+
class Clock extends Component {
12+
13+
constructor(props){
14+
super(props);
15+
const currentTime = new Date();
16+
this.state = this.getTime();
17+
}
18+
19+
componentDidMount() {
20+
this.setTimer();
21+
}
22+
23+
componentWillUnmount(){
24+
// Avoiding timeout still runs when component is unmounted
25+
if (this.timeOut) {
26+
clearTimeout(this.timeOut);
27+
}
28+
}
29+
30+
/**
31+
* Update clock state with new time
32+
*
33+
*/
34+
updateClock = () => {
35+
const currentTime = this.getTime();
36+
this.setState(currentTime);
37+
this.setTimer();
38+
}
39+
40+
/**
41+
* Parse current Date object
42+
*
43+
* @returns {Object} currentTime
44+
* @returns {int} currentTime.hour
45+
* @returns {int} currentTime.minutes
46+
* @returns {string} currentTime.ampm "am" or "pm"
47+
* @returns {string} currentTime.dayOfWeek
48+
* @returns {string} currentTime.month
49+
* @returns {int} currentTime.date
50+
*/
51+
getTime = () => {
52+
const dateObject = new Date();
53+
const dateString = dateObject.toDateString().split(" ");
54+
const currentTime = {
55+
hours: dateObject.getHours(),
56+
minutes: dateObject.getMinutes(),
57+
seconds: dateObject.getSeconds(),
58+
ampm: dateObject.getHours() >= 12 ? 'pm' : 'am',
59+
dayOfWeek: dateString[0],
60+
month: dateString[1],
61+
date: dateString[2]
62+
};
63+
64+
return currentTime;
65+
}
66+
67+
/**
68+
* Update current clock for every 1 second
69+
*
70+
*/
71+
setTimer = () => {
72+
this.timeOut = setTimeout(()=> {
73+
this.updateClock()
74+
}, 1000);
75+
}
76+
77+
render(){
78+
const {
79+
hours,
80+
minutes,
81+
seconds,
82+
ampm,
83+
dayOfWeek,
84+
month,
85+
date
86+
} = this.state;
87+
88+
const ClockContainer = styled.div`
89+
color: #fff;
90+
font-size: xx-large;
91+
float: right;
92+
top: 1em;
93+
position: relative;
94+
`;
95+
96+
return(
97+
<ClockContainer>
98+
{ this.props.title } <br />
99+
{ dayOfWeek }, { month } { date } <br/>
100+
{
101+
hours == 0 ? 12 :
102+
(hours >12) ? hours - 12 : hours
103+
}: {
104+
minutes > 9 ? minutes: `0${minutes}`
105+
}: {
106+
seconds > 9 ? seconds: `0${seconds}`
107+
} {ampm} <br/>
108+
109+
</ClockContainer>
110+
);
111+
}
112+
}
113+
114+
Clock.propTypes = {
115+
/** A text display current's user identity,
116+
* "Nobody" if no one is detected in the background,
117+
* "Hi, ..name" if an user is detected
118+
*/
119+
title: React.PropTypes.string
120+
}
121+
122+
export default Clock;

src/handlers/__tests__/componentMethodsHandler-test.js

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,39 @@ describe('componentMethodsHandler', () => {
2626

2727
function test(definition) {
2828
componentMethodsHandler(documentation, definition);
29-
expect(documentation.methods).toEqual([{
30-
name: 'foo',
31-
docblock: 'The foo method',
32-
modifiers: [],
33-
returns: {
34-
type: {name: 'number'},
29+
expect(documentation.methods).toEqual([
30+
{
31+
name: 'foo',
32+
docblock: 'The foo method',
33+
modifiers: [],
34+
returns: {
35+
type: {name: 'number'},
36+
},
37+
params: [{
38+
name: 'bar',
39+
type: {name: 'number'},
40+
}],
41+
},
42+
{
43+
name: 'baz',
44+
docblock: '"arrow function method"',
45+
modifiers: [],
46+
returns: {
47+
type: {name: 'string'},
48+
},
49+
params: [{
50+
name: 'foo',
51+
type: {name: 'string'},
52+
}],
3553
},
36-
params: [{
54+
{
3755
name: 'bar',
38-
type: {name: 'number'},
39-
}],
40-
}, {
41-
name: 'bar',
42-
docblock: 'Static function',
43-
modifiers: ['static'],
44-
returns: null,
45-
params: [],
46-
}]);
56+
docblock: 'Static function',
57+
modifiers: ['static'],
58+
returns: null,
59+
params: [],
60+
},
61+
]);
4762
}
4863

4964
it('extracts the documentation for an ObjectExpression', () => {
@@ -55,6 +70,10 @@ describe('componentMethodsHandler', () => {
5570
foo(bar: number): number {
5671
return bar;
5772
},
73+
/**
74+
* "arrow function method"
75+
*/
76+
baz: (foo: string): string => {},
5877
statics: {
5978
/**
6079
* Static function
@@ -84,6 +103,11 @@ describe('componentMethodsHandler', () => {
84103
return bar;
85104
}
86105
106+
/**
107+
* "arrow function method"
108+
*/
109+
baz = (foo: string): string => {};
110+
87111
/**
88112
* Static function
89113
*/

src/handlers/componentMethodsHandler.js

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,28 @@ import type Documentation from '../Documentation';
2020

2121
const {types: {namedTypes: types}} = recast;
2222

23-
function getMethodsDoc(methodPaths) {
24-
const methods = [];
25-
26-
methodPaths.forEach((methodPath) => {
27-
if (isReactComponentMethod(methodPath)) {
28-
return;
29-
}
30-
31-
methods.push(getMethodDocumentation(methodPath));
32-
});
33-
34-
return methods;
35-
}
23+
/**
24+
* The following values/constructs are considered methods:
25+
*
26+
* - Method declarations in classes (except "constructor" and React lifecycle
27+
* methods
28+
* - Public class fields in classes whose value are a functions
29+
* - Object properties whose values are functions
30+
*/
31+
function isMethod(path) {
32+
const isProbablyMethod =
33+
(types.MethodDefinition.check(path.node) &&
34+
path.node.kind !== 'constructor'
35+
) ||
36+
(types.ClassProperty.check(path.node) &&
37+
types.Function.check(path.get('value').node)
38+
) ||
39+
(types.Property.check(path.node) &&
40+
types.Function.check(path.get('value').node)
41+
)
42+
;
3643

37-
function isFunctionExpression(path) {
38-
return types.FunctionExpression.check(path.get('value').node)
44+
return isProbablyMethod && !isReactComponentMethod(path);
3945
}
4046

4147
/**
@@ -49,24 +55,21 @@ export default function componentMethodsHandler(
4955
// Extract all methods from the class or object.
5056
let methodPaths = [];
5157
if (isReactComponentClass(path)) {
52-
methodPaths = path
53-
.get('body', 'body')
54-
.filter(p => types.MethodDefinition.check(p.node) && p.node.kind !== 'constructor');
58+
methodPaths = path.get('body', 'body').filter(isMethod);
5559
} else if (types.ObjectExpression.check(path.node)) {
56-
methodPaths = path.get('properties').filter(isFunctionExpression);
60+
methodPaths = path.get('properties').filter(isMethod);
5761

5862
// Add the statics object properties.
5963
const statics = getMemberValuePath(path, 'statics');
6064
if (statics) {
6165
statics.get('properties').each(p => {
62-
if (isFunctionExpression(p)) {
66+
if (isMethod(p)) {
6367
p.node.static = true;
6468
methodPaths.push(p);
6569
}
6670
});
6771
}
6872
}
6973

70-
const methods = getMethodsDoc(methodPaths);
71-
documentation.set('methods', methods);
74+
documentation.set('methods', methodPaths.map(getMethodDocumentation));
7275
}

0 commit comments

Comments
 (0)