Skip to content

Commit 54ba9f6

Browse files
committed
Detect "isRequired" in shapes (closes #21)
1 parent 8169e63 commit 54ba9f6

File tree

6 files changed

+100
-16
lines changed

6 files changed

+100
-16
lines changed

src/handlers/propTypeHandler.js

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212

1313
import type Documentation from '../Documentation';
1414

15-
import getMembers from '../utils/getMembers';
1615
import getPropType from '../utils/getPropType';
1716
import getPropertyName from '../utils/getPropertyName';
1817
import getMemberValuePath from '../utils/getMemberValuePath';
1918
import isReactModuleName from '../utils/isReactModuleName';
19+
import isRequiredPropType from '../utils/isRequiredPropType';
2020
import printValue from '../utils/printValue';
2121
import recast from 'recast';
2222
import resolveToModule from '../utils/resolveToModule';
@@ -33,16 +33,6 @@ function isPropTypesExpression(path) {
3333
return false;
3434
}
3535

36-
/**
37-
* Returns true of the prop is required, according to its type defintion
38-
*/
39-
function isRequired(path) {
40-
return getMembers(path).some(
41-
member => !member.computed && member.path.node.name === 'isRequired' ||
42-
member.computed && member.path.node.value === 'isRequired'
43-
);
44-
}
45-
4636
function amendPropTypes(documentation, path) {
4737
path.get('properties').each(function(propertyPath) {
4838
switch (propertyPath.node.type) {
@@ -58,7 +48,7 @@ function amendPropTypes(documentation, path) {
5848
if (type) {
5949
propDescriptor.type = type;
6050
propDescriptor.required =
61-
type.name !== 'custom' && isRequired(valuePath);
51+
type.name !== 'custom' && isRequiredPropType(valuePath);
6252
}
6353
break;
6454
case types.SpreadProperty.name:

src/utils/__tests__/getPropType-test.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,11 @@ describe('getPropType', () => {
100100
value: {
101101
foo: {
102102
name: 'string',
103+
required: false,
103104
},
104105
bar: {
105106
name: 'bool',
107+
required: false,
106108
},
107109
},
108110
});
@@ -114,6 +116,7 @@ describe('getPropType', () => {
114116
foo: {
115117
name: 'custom',
116118
raw: 'xyz',
119+
required: false,
117120
},
118121
},
119122
});
@@ -128,7 +131,10 @@ describe('getPropType', () => {
128131
expect(getPropType(propTypeExpression)).toEqual({
129132
name: 'shape',
130133
value: {
131-
bar: {name: 'string'},
134+
bar: {
135+
name: 'string',
136+
required: false,
137+
},
132138
},
133139
});
134140
});
@@ -162,10 +168,32 @@ describe('getPropType', () => {
162168
foo: {
163169
name: 'string',
164170
description: 'test1',
171+
required: false,
165172
},
166173
bar: {
167174
name: 'bool',
168175
description: 'test2',
176+
required: false,
177+
},
178+
},
179+
});
180+
});
181+
182+
it('detects required notations of nested types in shapes', () => {
183+
expect(getPropType(expression(`shape({
184+
foo: string.isRequired,
185+
bar: bool
186+
})`)))
187+
.toEqual({
188+
name: 'shape',
189+
value: {
190+
foo: {
191+
name: 'string',
192+
required: true,
193+
},
194+
bar: {
195+
name: 'bool',
196+
required: false,
169197
},
170198
},
171199
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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('isRequiredPropType', () => {
16+
var expression;
17+
var isRequiredPropType;
18+
19+
beforeEach(() => {
20+
isRequiredPropType = require('../isRequiredPropType');
21+
({expression} = require('../../../tests/utils'));
22+
});
23+
24+
25+
it('considers isRequired', () => {
26+
expect(isRequiredPropType(expression('foo.bar.isRequired'))).toEqual(true);
27+
expect(isRequiredPropType(expression('foo.isRequired.bar'))).toEqual(true);
28+
});
29+
30+
it('considers ["isRequired"]', () => {
31+
expect(isRequiredPropType(expression('foo.bar["isRequired"]')))
32+
.toEqual(true);
33+
expect(isRequiredPropType(expression('foo["isRequired"].bar')))
34+
.toEqual(true);
35+
});
36+
37+
it('ignores variables', () => {
38+
expect(isRequiredPropType(expression('foo.bar[isRequired]')))
39+
.toEqual(false);
40+
});
41+
42+
});
43+

src/utils/getMembers.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ var {types: {namedTypes: types}} = recast;
3838
* {path: NodePath<42>, arguments: null, computed: false}
3939
* ]
4040
*/
41-
export default function getMembers(path: NodePath): Array<MemberDescriptor> {
41+
export default function getMembers(
42+
path: NodePath,
43+
includeRoot: bool = false
44+
): Array<MemberDescriptor> {
4245
var result = [];
4346
var argumentsPath = null;
4447
loop: while(true) { // eslint-disable-line no-constant-condition
@@ -60,5 +63,12 @@ export default function getMembers(path: NodePath): Array<MemberDescriptor> {
6063
break loop;
6164
}
6265
}
66+
if (includeRoot && result.length > 0) {
67+
result.push({
68+
path,
69+
computed: false,
70+
argumentsPath,
71+
});
72+
}
6373
return result.reverse();
6474
}

src/utils/getPropType.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import {getDocblock} from '../utils/docblock';
1717
import getMembers from './getMembers';
1818
import getPropertyName from './getPropertyName';
19+
import isRequiredPropType from '../utils/isRequiredPropType';
1920
import printValue from './printValue';
2021
import recast from 'recast';
2122
import resolveToValue from './resolveToValue';
@@ -75,11 +76,12 @@ function getPropTypeShape(argumentPath) {
7576
if (types.ObjectExpression.check(argumentPath.node)) {
7677
type.value = {};
7778
argumentPath.get('properties').each(function(propertyPath) {
78-
var descriptor = getPropType(propertyPath.get('value'), true);
79+
var descriptor = getPropType(propertyPath.get('value'));
7980
var docs = getDocblock(propertyPath);
8081
if (docs) {
8182
descriptor.description = docs;
8283
}
84+
descriptor.required = isRequiredPropType(propertyPath.get('value'));
8385
type.value[getPropertyName(propertyPath)] = descriptor;
8486
});
8587
}
@@ -124,7 +126,7 @@ var propTypes = {
124126
*/
125127
export default function getPropType(path: NodePath): PropTypeDescriptor {
126128
var descriptor;
127-
getMembers(path).some(member => {
129+
getMembers(path, true).some(member => {
128130
var node = member.path.node;
129131
var name;
130132
if (types.Literal.check(node)) {

src/utils/isRequiredPropType.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import getMembers from '../utils/getMembers';
2+
3+
/**
4+
* Returns true of the prop is required, according to its type defintion
5+
*/
6+
export default function isRequiredPropType(path) {
7+
return getMembers(path).some(
8+
member => !member.computed && member.path.node.name === 'isRequired' ||
9+
member.computed && member.path.node.value === 'isRequired'
10+
);
11+
}

0 commit comments

Comments
 (0)