Skip to content

Commit

Permalink
Detect "isRequired" in shapes (closes #21)
Browse files Browse the repository at this point in the history
  • Loading branch information
fkling committed Sep 28, 2015
1 parent 8169e63 commit 54ba9f6
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 16 deletions.
14 changes: 2 additions & 12 deletions src/handlers/propTypeHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

import type Documentation from '../Documentation';

import getMembers from '../utils/getMembers';
import getPropType from '../utils/getPropType';
import getPropertyName from '../utils/getPropertyName';
import getMemberValuePath from '../utils/getMemberValuePath';
import isReactModuleName from '../utils/isReactModuleName';
import isRequiredPropType from '../utils/isRequiredPropType';
import printValue from '../utils/printValue';
import recast from 'recast';
import resolveToModule from '../utils/resolveToModule';
Expand All @@ -33,16 +33,6 @@ function isPropTypesExpression(path) {
return false;
}

/**
* Returns true of the prop is required, according to its type defintion
*/
function isRequired(path) {
return getMembers(path).some(
member => !member.computed && member.path.node.name === 'isRequired' ||
member.computed && member.path.node.value === 'isRequired'
);
}

function amendPropTypes(documentation, path) {
path.get('properties').each(function(propertyPath) {
switch (propertyPath.node.type) {
Expand All @@ -58,7 +48,7 @@ function amendPropTypes(documentation, path) {
if (type) {
propDescriptor.type = type;
propDescriptor.required =
type.name !== 'custom' && isRequired(valuePath);
type.name !== 'custom' && isRequiredPropType(valuePath);
}
break;
case types.SpreadProperty.name:
Expand Down
30 changes: 29 additions & 1 deletion src/utils/__tests__/getPropType-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,11 @@ describe('getPropType', () => {
value: {
foo: {
name: 'string',
required: false,
},
bar: {
name: 'bool',
required: false,
},
},
});
Expand All @@ -114,6 +116,7 @@ describe('getPropType', () => {
foo: {
name: 'custom',
raw: 'xyz',
required: false,
},
},
});
Expand All @@ -128,7 +131,10 @@ describe('getPropType', () => {
expect(getPropType(propTypeExpression)).toEqual({
name: 'shape',
value: {
bar: {name: 'string'},
bar: {
name: 'string',
required: false,
},
},
});
});
Expand Down Expand Up @@ -162,10 +168,32 @@ describe('getPropType', () => {
foo: {
name: 'string',
description: 'test1',
required: false,
},
bar: {
name: 'bool',
description: 'test2',
required: false,
},
},
});
});

it('detects required notations of nested types in shapes', () => {
expect(getPropType(expression(`shape({
foo: string.isRequired,
bar: bool
})`)))
.toEqual({
name: 'shape',
value: {
foo: {
name: 'string',
required: true,
},
bar: {
name: 'bool',
required: false,
},
},
});
Expand Down
43 changes: 43 additions & 0 deletions src/utils/__tests__/isRequiredPropType-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

/*global jest, describe, beforeEach, it, expect*/

jest.autoMockOff();

describe('isRequiredPropType', () => {
var expression;
var isRequiredPropType;

beforeEach(() => {
isRequiredPropType = require('../isRequiredPropType');
({expression} = require('../../../tests/utils'));
});


it('considers isRequired', () => {
expect(isRequiredPropType(expression('foo.bar.isRequired'))).toEqual(true);
expect(isRequiredPropType(expression('foo.isRequired.bar'))).toEqual(true);
});

it('considers ["isRequired"]', () => {
expect(isRequiredPropType(expression('foo.bar["isRequired"]')))
.toEqual(true);
expect(isRequiredPropType(expression('foo["isRequired"].bar')))
.toEqual(true);
});

it('ignores variables', () => {
expect(isRequiredPropType(expression('foo.bar[isRequired]')))
.toEqual(false);
});

});

12 changes: 11 additions & 1 deletion src/utils/getMembers.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ var {types: {namedTypes: types}} = recast;
* {path: NodePath<42>, arguments: null, computed: false}
* ]
*/
export default function getMembers(path: NodePath): Array<MemberDescriptor> {
export default function getMembers(
path: NodePath,
includeRoot: bool = false
): Array<MemberDescriptor> {
var result = [];
var argumentsPath = null;
loop: while(true) { // eslint-disable-line no-constant-condition
Expand All @@ -60,5 +63,12 @@ export default function getMembers(path: NodePath): Array<MemberDescriptor> {
break loop;
}
}
if (includeRoot && result.length > 0) {
result.push({
path,
computed: false,
argumentsPath,
});
}
return result.reverse();
}
6 changes: 4 additions & 2 deletions src/utils/getPropType.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import {getDocblock} from '../utils/docblock';
import getMembers from './getMembers';
import getPropertyName from './getPropertyName';
import isRequiredPropType from '../utils/isRequiredPropType';
import printValue from './printValue';
import recast from 'recast';
import resolveToValue from './resolveToValue';
Expand Down Expand Up @@ -75,11 +76,12 @@ function getPropTypeShape(argumentPath) {
if (types.ObjectExpression.check(argumentPath.node)) {
type.value = {};
argumentPath.get('properties').each(function(propertyPath) {
var descriptor = getPropType(propertyPath.get('value'), true);
var descriptor = getPropType(propertyPath.get('value'));
var docs = getDocblock(propertyPath);
if (docs) {
descriptor.description = docs;
}
descriptor.required = isRequiredPropType(propertyPath.get('value'));
type.value[getPropertyName(propertyPath)] = descriptor;
});
}
Expand Down Expand Up @@ -124,7 +126,7 @@ var propTypes = {
*/
export default function getPropType(path: NodePath): PropTypeDescriptor {
var descriptor;
getMembers(path).some(member => {
getMembers(path, true).some(member => {
var node = member.path.node;
var name;
if (types.Literal.check(node)) {
Expand Down
11 changes: 11 additions & 0 deletions src/utils/isRequiredPropType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import getMembers from '../utils/getMembers';

/**
* Returns true of the prop is required, according to its type defintion
*/
export default function isRequiredPropType(path) {
return getMembers(path).some(
member => !member.computed && member.path.node.name === 'isRequired' ||
member.computed && member.path.node.value === 'isRequired'
);
}

0 comments on commit 54ba9f6

Please sign in to comment.