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

Commit 45c5dca

Browse files
vincentbelmohsen1
authored andcommitted
support more prop types (lyft#19)
1 parent aaf86ed commit 45c5dca

File tree

3 files changed

+199
-50
lines changed

3 files changed

+199
-50
lines changed

src/transforms/react-js-make-props-and-state-transform.ts

+135-50
Original file line numberDiff line numberDiff line change
@@ -251,24 +251,22 @@ function buildInterfaceFromPropTypeObjectLiteral(objectLiteral: ts.ObjectLiteral
251251
// get d() {}, // AccessorDeclaration
252252
// }
253253
.filter(ts.isPropertyAssignment)
254-
.filter(property => {
255-
return (
256-
// Ignore children, React types have it
257-
property.name.getText() !== 'children' &&
258-
ts.isPropertyAccessExpression(property.initializer)
259-
)
260-
})
254+
// Ignore children, React types have it
255+
.filter(property => property.name.getText() !== 'children')
261256
.map(propertyAssignment => {
262257
const name = propertyAssignment.name.getText();
263-
// We have guarantee this in the previous `filter`
264-
const initializer = propertyAssignment.initializer as ts.PropertyAccessExpression
265-
const typeValue = getTypeFromReactPropTypeExpression(initializer);
266-
const isOptional = isPropTypeOptional(initializer);
258+
const initializer = propertyAssignment.initializer;
259+
const isRequired = isPropTypeRequired(initializer);
260+
const typeExpression = isRequired
261+
// We have guaranteed the type in `isPropTypeRequired()`
262+
? (initializer as ts.PropertyAccessExpression).expression
263+
: initializer;
264+
const typeValue = getTypeFromReactPropTypeExpression(typeExpression);
267265

268266
return ts.createPropertySignature(
269267
[],
270268
name,
271-
isOptional ? ts.createToken(ts.SyntaxKind.QuestionToken) : undefined,
269+
isRequired ? undefined : ts.createToken(ts.SyntaxKind.QuestionToken),
272270
typeValue,
273271
undefined,
274272
);
@@ -282,51 +280,138 @@ function buildInterfaceFromPropTypeObjectLiteral(objectLiteral: ts.ObjectLiteral
282280
*
283281
* @param node React propTypes value
284282
*/
285-
function getTypeFromReactPropTypeExpression(node: ts.PropertyAccessExpression) {
286-
const text = node.getText().replace(/React\.PropTypes\./, '');
283+
function getTypeFromReactPropTypeExpression(node: ts.Expression): ts.TypeNode {
287284
let result = null;
288-
if (/string/.test(text)) {
289-
result = ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
290-
} else if (/any/.test(text)) {
291-
result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
292-
} else if (/array/.test(text)) {
293-
result = ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
294-
} else if (/bool/.test(text)) {
295-
result = ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
296-
} else if (/number/.test(text)) {
297-
result = ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
298-
} else if (/object/.test(text)) {
299-
result = ts.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
300-
} else if (/node/.test(text)) {
301-
result = ts.createTypeReferenceNode('React.ReactNode', []);
302-
} else if (/element/.test(text)) {
303-
result = ts.createTypeReferenceNode('JSX.Element', []);
304-
} else if (/func/.test(text)) {
305-
const arrayOfAny = ts.createParameter(
306-
[],
307-
[],
308-
ts.createToken(ts.SyntaxKind.DotDotDotToken),
309-
'args',
310-
undefined,
311-
ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)),
312-
undefined,
313-
);
314-
result = ts.createFunctionTypeNode(
315-
[],
316-
[arrayOfAny],
317-
ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
318-
);
319-
} else {
285+
if (ts.isPropertyAccessExpression(node)) {
286+
/**
287+
* PropTypes.array,
288+
* PropTypes.bool,
289+
* PropTypes.func,
290+
* PropTypes.number,
291+
* PropTypes.object,
292+
* PropTypes.string,
293+
* PropTypes.symbol, (ignore)
294+
* PropTypes.node,
295+
* PropTypes.element,
296+
* PropTypes.any,
297+
*/
298+
const text = node.getText().replace(/React\.PropTypes\./, '');
299+
300+
if (/string/.test(text)) {
301+
result = ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
302+
} else if (/any/.test(text)) {
303+
result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
304+
} else if (/array/.test(text)) {
305+
result = ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
306+
} else if (/bool/.test(text)) {
307+
result = ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
308+
} else if (/number/.test(text)) {
309+
result = ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
310+
} else if (/object/.test(text)) {
311+
result = ts.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
312+
} else if (/node/.test(text)) {
313+
result = ts.createTypeReferenceNode('React.ReactNode', []);
314+
} else if (/element/.test(text)) {
315+
result = ts.createTypeReferenceNode('JSX.Element', []);
316+
} else if (/func/.test(text)) {
317+
const arrayOfAny = ts.createParameter(
318+
[],
319+
[],
320+
ts.createToken(ts.SyntaxKind.DotDotDotToken),
321+
'args',
322+
undefined,
323+
ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)),
324+
undefined,
325+
);
326+
result = ts.createFunctionTypeNode(
327+
[],
328+
[arrayOfAny],
329+
ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
330+
);
331+
}
332+
} else if (ts.isCallExpression(node)) {
333+
/**
334+
* PropTypes.instanceOf(), (ignore)
335+
* PropTypes.oneOf(), // only support oneOf([1, 2]), oneOf(['a', 'b'])
336+
* PropTypes.oneOfType(),
337+
* PropTypes.arrayOf(),
338+
* PropTypes.objectOf(),
339+
* PropTypes.shape(),
340+
*/
341+
const text = node.expression.getText();
342+
if (/oneOf$/.test(text)) {
343+
const argument = node.arguments[0];
344+
if (ts.isArrayLiteralExpression(argument)) {
345+
if (argument.elements.every(elm => ts.isStringLiteral(elm) || ts.isNumericLiteral(elm))) {
346+
result = ts.createUnionTypeNode(
347+
(argument.elements as ts.NodeArray<ts.StringLiteral | ts.NumericLiteral>).map(elm =>
348+
ts.createLiteralTypeNode(elm)
349+
),
350+
)
351+
}
352+
}
353+
} else if (/oneOfType$/.test(text)) {
354+
const argument = node.arguments[0];
355+
if (ts.isArrayLiteralExpression(argument)) {
356+
result = ts.createUnionOrIntersectionTypeNode(
357+
ts.SyntaxKind.UnionType,
358+
argument.elements.map(elm => getTypeFromReactPropTypeExpression(elm)),
359+
);
360+
}
361+
} else if (/arrayOf$/.test(text)) {
362+
const argument = node.arguments[0];
363+
if (argument) {
364+
result = ts.createArrayTypeNode(
365+
getTypeFromReactPropTypeExpression(argument)
366+
)
367+
}
368+
} else if (/objectOf$/.test(text)) {
369+
const argument = node.arguments[0];
370+
if (argument) {
371+
result = ts.createTypeLiteralNode([
372+
ts.createIndexSignature(
373+
undefined,
374+
undefined,
375+
[
376+
ts.createParameter(
377+
undefined,
378+
undefined,
379+
undefined,
380+
'key',
381+
undefined,
382+
ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
383+
)
384+
],
385+
getTypeFromReactPropTypeExpression(argument),
386+
)
387+
])
388+
}
389+
} else if (/shape$/.test(text)) {
390+
const argument = node.arguments[0];
391+
if (ts.isObjectLiteralExpression(argument)) {
392+
return buildInterfaceFromPropTypeObjectLiteral(argument)
393+
}
394+
}
395+
}
396+
397+
/**
398+
* customProp,
399+
* anything others
400+
*/
401+
if (result === null) {
320402
result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
321403
}
322-
return result;
404+
405+
return result
323406
}
324407

325408
/**
326-
* Decide if node is optional
409+
* Decide if node is required
327410
* @param node React propTypes member node
328411
*/
329-
function isPropTypeOptional(node: ts.PropertyAccessExpression) {
412+
function isPropTypeRequired(node: ts.Expression) {
413+
if (!ts.isPropertyAccessExpression(node)) return false;
414+
330415
const text = node.getText().replace(/React\.PropTypes\./, '');
331-
return !/\.isRequired/.test(text)
416+
return /\.isRequired/.test(text);
332417
}

test/react-js-make-props-and-state-transform/static-proptypes-many-props/input.tsx

+22
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ export default class MyComponent extends React.Component {
1212
string: React.PropTypes.string,
1313
node: React.PropTypes.node,
1414
element: React.PropTypes.element,
15+
oneOf: React.PropTypes.oneOf(['a', 'b', 'c']),
16+
oneOfType: React.PropTypes.oneOfType([
17+
React.PropTypes.string,
18+
React.PropTypes.number,
19+
]),
20+
arrayOf: React.PropTypes.arrayOf(React.PropTypes.string),
21+
objectOf: React.PropTypes.objectOf(React.PropTypes.string),
22+
shape: React.PropTypes.shape({
23+
color: React.PropTypes.string,
24+
fontSize: React.PropTypes.number,
25+
}),
1526
anyRequired: React.PropTypes.any.isRequired,
1627
arrayRequired: React.PropTypes.array.isRequired,
1728
boolRequired: React.PropTypes.bool.isRequired,
@@ -21,6 +32,17 @@ export default class MyComponent extends React.Component {
2132
stringRequired: React.PropTypes.string.isRequired,
2233
nodeRequired: React.PropTypes.node.isRequired,
2334
elementRequired: React.PropTypes.element.isRequired,
35+
oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired,
36+
oneOfTypeRequired: React.PropTypes.oneOfType([
37+
React.PropTypes.string,
38+
React.PropTypes.number,
39+
]).isRequired,
40+
arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
41+
objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired,
42+
shapeRequired: React.PropTypes.shape({
43+
color: React.PropTypes.string,
44+
fontSize: React.PropTypes.number.isRequired,
45+
}).isRequired,
2446
};
2547
render() {
2648
return <div />;

test/react-js-make-props-and-state-transform/static-proptypes-many-props/output.tsx

+42
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ export default class MyComponent extends React.Component<{
99
string?: string;
1010
node?: React.ReactNode;
1111
element?: JSX.Element;
12+
oneOf?: 'a' | 'b' | 'c';
13+
oneOfType?: string | number;
14+
arrayOf?: string[];
15+
objectOf?: {
16+
[key: string]: string;
17+
};
18+
shape?: {
19+
color?: string;
20+
fontSize?: number;
21+
};
1222
anyRequired: any;
1323
arrayRequired: any[];
1424
boolRequired: boolean;
@@ -18,6 +28,16 @@ export default class MyComponent extends React.Component<{
1828
stringRequired: string;
1929
nodeRequired: React.ReactNode;
2030
elementRequired: JSX.Element;
31+
oneOfRequired: 'a' | 'b' | 'c';
32+
oneOfTypeRequired: string | number;
33+
arrayOfRequired: string[];
34+
objectOfRequired: {
35+
[key: string]: string;
36+
};
37+
shapeRequired: {
38+
color?: string;
39+
fontSize: number;
40+
};
2141
}, {
2242
}> {
2343
static propTypes = {
@@ -31,6 +51,17 @@ export default class MyComponent extends React.Component<{
3151
string: React.PropTypes.string,
3252
node: React.PropTypes.node,
3353
element: React.PropTypes.element,
54+
oneOf: React.PropTypes.oneOf(['a', 'b', 'c']),
55+
oneOfType: React.PropTypes.oneOfType([
56+
React.PropTypes.string,
57+
React.PropTypes.number,
58+
]),
59+
arrayOf: React.PropTypes.arrayOf(React.PropTypes.string),
60+
objectOf: React.PropTypes.objectOf(React.PropTypes.string),
61+
shape: React.PropTypes.shape({
62+
color: React.PropTypes.string,
63+
fontSize: React.PropTypes.number,
64+
}),
3465
anyRequired: React.PropTypes.any.isRequired,
3566
arrayRequired: React.PropTypes.array.isRequired,
3667
boolRequired: React.PropTypes.bool.isRequired,
@@ -40,6 +71,17 @@ export default class MyComponent extends React.Component<{
4071
stringRequired: React.PropTypes.string.isRequired,
4172
nodeRequired: React.PropTypes.node.isRequired,
4273
elementRequired: React.PropTypes.element.isRequired,
74+
oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired,
75+
oneOfTypeRequired: React.PropTypes.oneOfType([
76+
React.PropTypes.string,
77+
React.PropTypes.number,
78+
]).isRequired,
79+
arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
80+
objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired,
81+
shapeRequired: React.PropTypes.shape({
82+
color: React.PropTypes.string,
83+
fontSize: React.PropTypes.number.isRequired,
84+
}).isRequired,
4385
};
4486
render() {
4587
return <div />;

0 commit comments

Comments
 (0)