Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit f5d8cf7

Browse files
committed
feat: Added required props
1 parent e5f41d3 commit f5d8cf7

File tree

5 files changed

+107
-50
lines changed

5 files changed

+107
-50
lines changed

Diff for: index.ts

+61-39
Original file line numberDiff line numberDiff line change
@@ -102,47 +102,66 @@ function isNode(obj: any): boolean {
102102
return obj && typeof obj.type != 'undefined' && typeof obj.loc != 'undefined';
103103
}
104104

105-
export function getTypeFromPropType(node: any): string {
105+
function getReactPropTypeFromExpression(node: any): any {
106+
if (node.object.type == 'MemberExpression'
107+
&& node.object.object.name == 'React' && node.object.property.name == 'PropTypes') {
108+
return node.property;
109+
}
110+
return 'undefined';
111+
}
112+
113+
function isRequiredPropType(node: any): any {
114+
const isRequired: boolean = node.type == 'MemberExpression' && node.property.name == 'isRequired';
115+
return {
116+
isRequired,
117+
type: getReactPropTypeFromExpression(isRequired ? node.object : node)
118+
};
119+
}
120+
121+
interface IProperty {
122+
type: string;
123+
optional: boolean;
124+
}
125+
126+
export function getTypeFromPropType(node: any): IProperty {
127+
const result: any = {
128+
type: 'any',
129+
optional: true
130+
};
106131
if (isNode(node)) {
107-
const isMemberExpression = (node: any): boolean => {
108-
return node.type == 'MemberExpression';
109-
};
110-
const convertMemberExpression = (node: any): string => {
111-
if (isMemberExpression(node.object)) {
112-
return convertMemberExpression(node.object) + '.' + node.property.name;
113-
}
114-
return node.object.name + '.' + node.property.name;
115-
};
116-
if (isMemberExpression(node)) {
117-
const type: string = convertMemberExpression(node);
118-
switch (type) {
119-
case 'React.PropTypes.any':
120-
return 'any';
121-
case 'React.PropTypes.array':
122-
return 'any[]';
123-
case 'React.PropTypes.bool':
124-
return 'boolean';
125-
case 'React.PropTypes.func':
126-
return '(...args: any[]) => any';
127-
case 'React.PropTypes.number':
128-
return 'number';
129-
case 'React.PropTypes.object':
130-
return 'Object';
131-
case 'React.PropTypes.string':
132-
return 'string';
133-
case 'React.PropTypes.node':
134-
return 'React.ReactNode';
135-
case 'React.PropTypes.element':
136-
return 'React.ReactElement<any>';
137-
// - React.PropTypes.instanceOf - Would only be possible if the TS
138-
// class is known to the definition file. Probably then all code
139-
// is written in TS.
140-
// - React.PropTypes.oneOf - Currently this could not be expressed
141-
// in a typesave manner in TS.
142-
}
132+
const {isRequired, type}: any = isRequiredPropType(node);
133+
result.optional = !isRequired;
134+
switch (type.name) {
135+
case 'any':
136+
result.type = 'any';
137+
break;
138+
case 'array':
139+
result.type = 'any[]';
140+
break;
141+
case 'bool':
142+
result.type = 'boolean';
143+
break;
144+
case 'func':
145+
result.type = '(...args: any[]) => any';
146+
break;
147+
case 'number':
148+
result.type = 'number';
149+
break;
150+
case 'object':
151+
result.type = 'Object';
152+
break;
153+
case 'string':
154+
result.type = 'string';
155+
break;
156+
case 'node':
157+
result.type = 'React.ReactNode';
158+
break;
159+
case 'element':
160+
result.type = 'React.ReactElement<any>';
161+
break;
143162
}
144163
}
145-
return 'any';
164+
return result;
146165
}
147166

148167
export class Writer {
@@ -189,7 +208,10 @@ export class Writer {
189208
public props(name: string, props: any, fn?: () => void): void {
190209
this.interface(`${name}Props`, () => {
191210
this.prop('key', 'any', true);
192-
Object.keys(props).forEach((propName: any) => this.prop(propName, props[propName], true));
211+
Object.keys(props).forEach((propName: any) => {
212+
const prop: IProperty = props[propName];
213+
this.prop(propName, prop.type, prop.optional);
214+
});
193215
});
194216
if (fn) {
195217
fn();

Diff for: tests/parse-prop-types-test.ts

+27-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe('The PropType parser', () => {
1212
}
1313
};
1414
it('should return any on unknown PropTypes', () => {
15-
assert.equal(getTypeFromPropType({}), 'any');
15+
assert.deepEqual(getTypeFromPropType({}), {type: 'any', optional: true});
1616
});
1717
it('should return any[] for generic array prop types', () => {
1818
const ast: any = {
@@ -23,7 +23,7 @@ describe('The PropType parser', () => {
2323
name: 'array'
2424
}
2525
};
26-
assert.equal(getTypeFromPropType(ast), 'any[]');
26+
assert.deepEqual(getTypeFromPropType(ast), {type: 'any[]', optional: true});
2727
});
2828
it('should return boolean for bool prop types', () => {
2929
const ast: any = {
@@ -34,7 +34,7 @@ describe('The PropType parser', () => {
3434
name: 'bool'
3535
}
3636
};
37-
assert.equal(getTypeFromPropType(ast), 'boolean');
37+
assert.deepEqual(getTypeFromPropType(ast), {type: 'boolean', optional: true});
3838
});
3939
it('should return a generic function for func prop types', () => {
4040
const ast: any = {
@@ -45,7 +45,25 @@ describe('The PropType parser', () => {
4545
name: 'func'
4646
}
4747
};
48-
assert.equal(getTypeFromPropType(ast), '(...args: any[]) => any');
48+
assert.deepEqual(getTypeFromPropType(ast), {type: '(...args: any[]) => any', optional: true});
49+
});
50+
it('should return a generic required function for func.isRequired prop types', () => {
51+
const ast: any = {
52+
type: 'MemberExpression',
53+
loc: {},
54+
object: {
55+
object: reactPropTypesMemberExpression,
56+
property: {
57+
name: 'func'
58+
}
59+
},
60+
property: {
61+
name: 'isRequired'
62+
}
63+
};
64+
const result: any = getTypeFromPropType(ast);
65+
assert.equal(result.type, '(...args: any[]) => any');
66+
assert.equal(result.optional, false);
4967
});
5068
it('should return number for number prop types', () => {
5169
const ast: any = {
@@ -56,7 +74,7 @@ describe('The PropType parser', () => {
5674
name: 'number'
5775
}
5876
};
59-
assert.equal(getTypeFromPropType(ast), 'number');
77+
assert.deepEqual(getTypeFromPropType(ast), {type: 'number', optional: true});
6078
});
6179
it('should return Object for object prop types', () => {
6280
const ast: any = {
@@ -67,7 +85,7 @@ describe('The PropType parser', () => {
6785
name: 'object'
6886
}
6987
};
70-
assert.equal(getTypeFromPropType(ast), 'Object');
88+
assert.deepEqual(getTypeFromPropType(ast), {type: 'Object', optional: true});
7189
});
7290
it('should return string for string prop types', () => {
7391
const ast: any = {
@@ -78,7 +96,7 @@ describe('The PropType parser', () => {
7896
name: 'string'
7997
}
8098
};
81-
assert.equal(getTypeFromPropType(ast), 'string');
99+
assert.deepEqual(getTypeFromPropType(ast), {type: 'string', optional: true});
82100
});
83101
it('should return React.ReactNode for node prop types', () => {
84102
const ast: any = {
@@ -89,7 +107,7 @@ describe('The PropType parser', () => {
89107
name: 'node'
90108
}
91109
};
92-
assert.equal(getTypeFromPropType(ast), 'React.ReactNode');
110+
assert.deepEqual(getTypeFromPropType(ast), {type: 'React.ReactNode', optional: true});
93111
});
94112
it('should return React.ReactElement<any> for element prop types', () => {
95113
const ast: any = {
@@ -100,6 +118,6 @@ describe('The PropType parser', () => {
100118
name: 'element'
101119
}
102120
};
103-
assert.equal(getTypeFromPropType(ast), 'React.ReactElement<any>');
121+
assert.deepEqual(getTypeFromPropType(ast), {type: 'React.ReactElement<any>', optional: true});
104122
});
105123
});

Diff for: tests/simple-component.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ declare module 'simple-component' {
1212
optionalString?: string;
1313
optionalNode?: React.ReactNode;
1414
optionalElement?: React.ReactElement<any>;
15+
requiredFunc: (...args: any[]) => any;
16+
requiredAny: any;
1517
}
1618

1719
export default class SimpleComponent extends React.Component<SimpleComponentProps, any> {

Diff for: tests/simple-component.jsx

+16-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,22 @@ export default class SimpleComponent extends React.Component {
1111
optionalObject: React.PropTypes.object,
1212
optionalString: React.PropTypes.string,
1313
optionalNode: React.PropTypes.node,
14-
optionalElement: React.PropTypes.element
14+
optionalElement: React.PropTypes.element,
15+
//optionalMessage: React.PropTypes.instanceOf(Message),
16+
//optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
17+
//optionalUnion: React.PropTypes.oneOfType([
18+
// React.PropTypes.string,
19+
// React.PropTypes.number,
20+
// React.PropTypes.instanceOf(Message)
21+
//]),
22+
//optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
23+
//optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
24+
//optionalObjectWithShape: React.PropTypes.shape({
25+
// color: React.PropTypes.string,
26+
// fontSize: React.PropTypes.number
27+
//}),
28+
requiredFunc: React.PropTypes.func.isRequired,
29+
requiredAny: React.PropTypes.any.isRequired,
1530
};
1631

1732
render() {

Diff for: tests/writer-test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('The Writer', () => {
2525
assert.equal(writer.toString(), 'name?: type;\n');
2626
});
2727
it('should write a property interface', () => {
28-
writer.props('Name', {prop: 'type'});
28+
writer.props('Name', {prop: {type: 'type', optional: true}});
2929
assert.equal(writer.toString(), 'interface NameProps {\n\tkey?: any;\n\tprop?: type;\n}\n');
3030
});
3131
it('should write a class with props declaration', () => {

0 commit comments

Comments
 (0)