From 2130995c9cc2f9b7cf40b01d74e390a788fa7aa6 Mon Sep 17 00:00:00 2001 From: VincentBel Date: Sat, 20 Jan 2018 15:39:09 +0800 Subject: [PATCH] support more prop types --- ...react-js-make-props-and-state-transform.ts | 185 +++++++++++++----- .../static-proptypes-many-props/input.tsx | 22 +++ .../static-proptypes-many-props/output.tsx | 42 ++++ 3 files changed, 199 insertions(+), 50 deletions(-) diff --git a/src/transforms/react-js-make-props-and-state-transform.ts b/src/transforms/react-js-make-props-and-state-transform.ts index 613c342..e6dd233 100644 --- a/src/transforms/react-js-make-props-and-state-transform.ts +++ b/src/transforms/react-js-make-props-and-state-transform.ts @@ -251,24 +251,22 @@ function buildInterfaceFromPropTypeObjectLiteral(objectLiteral: ts.ObjectLiteral // get d() {}, // AccessorDeclaration // } .filter(ts.isPropertyAssignment) - .filter(property => { - return ( - // Ignore children, React types have it - property.name.getText() !== 'children' && - ts.isPropertyAccessExpression(property.initializer) - ) - }) + // Ignore children, React types have it + .filter(property => property.name.getText() !== 'children') .map(propertyAssignment => { const name = propertyAssignment.name.getText(); - // We have guarantee this in the previous `filter` - const initializer = propertyAssignment.initializer as ts.PropertyAccessExpression - const typeValue = getTypeFromReactPropTypeExpression(initializer); - const isOptional = isPropTypeOptional(initializer); + const initializer = propertyAssignment.initializer; + const isRequired = isPropTypeRequired(initializer); + const typeExpression = isRequired + // We have guaranteed the type in `isPropTypeRequired()` + ? (initializer as ts.PropertyAccessExpression).expression + : initializer; + const typeValue = getTypeFromReactPropTypeExpression(typeExpression); return ts.createPropertySignature( [], name, - isOptional ? ts.createToken(ts.SyntaxKind.QuestionToken) : undefined, + isRequired ? undefined : ts.createToken(ts.SyntaxKind.QuestionToken), typeValue, undefined, ); @@ -282,51 +280,138 @@ function buildInterfaceFromPropTypeObjectLiteral(objectLiteral: ts.ObjectLiteral * * @param node React propTypes value */ -function getTypeFromReactPropTypeExpression(node: ts.PropertyAccessExpression) { - const text = node.getText().replace(/React\.PropTypes\./, ''); +function getTypeFromReactPropTypeExpression(node: ts.Expression): ts.TypeNode { let result = null; - if (/string/.test(text)) { - result = ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); - } else if (/any/.test(text)) { - result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword); - } else if (/array/.test(text)) { - result = ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)); - } else if (/bool/.test(text)) { - result = ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); - } else if (/number/.test(text)) { - result = ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); - } else if (/object/.test(text)) { - result = ts.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword); - } else if (/node/.test(text)) { - result = ts.createTypeReferenceNode('React.ReactNode', []); - } else if (/element/.test(text)) { - result = ts.createTypeReferenceNode('JSX.Element', []); - } else if (/func/.test(text)) { - const arrayOfAny = ts.createParameter( - [], - [], - ts.createToken(ts.SyntaxKind.DotDotDotToken), - 'args', - undefined, - ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)), - undefined, - ); - result = ts.createFunctionTypeNode( - [], - [arrayOfAny], - ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), - ); - } else { + if (ts.isPropertyAccessExpression(node)) { + /** + * PropTypes.array, + * PropTypes.bool, + * PropTypes.func, + * PropTypes.number, + * PropTypes.object, + * PropTypes.string, + * PropTypes.symbol, (ignore) + * PropTypes.node, + * PropTypes.element, + * PropTypes.any, + */ + const text = node.getText().replace(/React\.PropTypes\./, ''); + + if (/string/.test(text)) { + result = ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); + } else if (/any/.test(text)) { + result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword); + } else if (/array/.test(text)) { + result = ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)); + } else if (/bool/.test(text)) { + result = ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); + } else if (/number/.test(text)) { + result = ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); + } else if (/object/.test(text)) { + result = ts.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword); + } else if (/node/.test(text)) { + result = ts.createTypeReferenceNode('React.ReactNode', []); + } else if (/element/.test(text)) { + result = ts.createTypeReferenceNode('JSX.Element', []); + } else if (/func/.test(text)) { + const arrayOfAny = ts.createParameter( + [], + [], + ts.createToken(ts.SyntaxKind.DotDotDotToken), + 'args', + undefined, + ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)), + undefined, + ); + result = ts.createFunctionTypeNode( + [], + [arrayOfAny], + ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + ); + } + } else if (ts.isCallExpression(node)) { + /** + * PropTypes.instanceOf(), (ignore) + * PropTypes.oneOf(), // only support oneOf([1, 2]), oneOf(['a', 'b']) + * PropTypes.oneOfType(), + * PropTypes.arrayOf(), + * PropTypes.objectOf(), + * PropTypes.shape(), + */ + const text = node.expression.getText(); + if (/oneOf$/.test(text)) { + const argument = node.arguments[0]; + if (ts.isArrayLiteralExpression(argument)) { + if (argument.elements.every(elm => ts.isStringLiteral(elm) || ts.isNumericLiteral(elm))) { + result = ts.createUnionTypeNode( + (argument.elements as ts.NodeArray).map(elm => + ts.createLiteralTypeNode(elm) + ), + ) + } + } + } else if (/oneOfType$/.test(text)) { + const argument = node.arguments[0]; + if (ts.isArrayLiteralExpression(argument)) { + result = ts.createUnionOrIntersectionTypeNode( + ts.SyntaxKind.UnionType, + argument.elements.map(elm => getTypeFromReactPropTypeExpression(elm)), + ); + } + } else if (/arrayOf$/.test(text)) { + const argument = node.arguments[0]; + if (argument) { + result = ts.createArrayTypeNode( + getTypeFromReactPropTypeExpression(argument) + ) + } + } else if (/objectOf$/.test(text)) { + const argument = node.arguments[0]; + if (argument) { + result = ts.createTypeLiteralNode([ + ts.createIndexSignature( + undefined, + undefined, + [ + ts.createParameter( + undefined, + undefined, + undefined, + 'key', + undefined, + ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ) + ], + getTypeFromReactPropTypeExpression(argument), + ) + ]) + } + } else if (/shape$/.test(text)) { + const argument = node.arguments[0]; + if (ts.isObjectLiteralExpression(argument)) { + return buildInterfaceFromPropTypeObjectLiteral(argument) + } + } + } + + /** + * customProp, + * anything others + */ + if (result === null) { result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword); } - return result; + + return result } /** - * Decide if node is optional + * Decide if node is required * @param node React propTypes member node */ -function isPropTypeOptional(node: ts.PropertyAccessExpression) { +function isPropTypeRequired(node: ts.Expression) { + if (!ts.isPropertyAccessExpression(node)) return false; + const text = node.getText().replace(/React\.PropTypes\./, ''); - return !/\.isRequired/.test(text) + return /\.isRequired/.test(text); } diff --git a/test/react-js-make-props-and-state-transform/static-proptypes-many-props/input.tsx b/test/react-js-make-props-and-state-transform/static-proptypes-many-props/input.tsx index 1e491c3..e54d2f4 100644 --- a/test/react-js-make-props-and-state-transform/static-proptypes-many-props/input.tsx +++ b/test/react-js-make-props-and-state-transform/static-proptypes-many-props/input.tsx @@ -12,6 +12,17 @@ export default class MyComponent extends React.Component { string: React.PropTypes.string, node: React.PropTypes.node, element: React.PropTypes.element, + oneOf: React.PropTypes.oneOf(['a', 'b', 'c']), + oneOfType: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + ]), + arrayOf: React.PropTypes.arrayOf(React.PropTypes.string), + objectOf: React.PropTypes.objectOf(React.PropTypes.string), + shape: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: React.PropTypes.number, + }), anyRequired: React.PropTypes.any.isRequired, arrayRequired: React.PropTypes.array.isRequired, boolRequired: React.PropTypes.bool.isRequired, @@ -21,6 +32,17 @@ export default class MyComponent extends React.Component { stringRequired: React.PropTypes.string.isRequired, nodeRequired: React.PropTypes.node.isRequired, elementRequired: React.PropTypes.element.isRequired, + oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired, + oneOfTypeRequired: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + ]).isRequired, + arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, + objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired, + shapeRequired: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: React.PropTypes.number.isRequired, + }).isRequired, }; render() { return
; diff --git a/test/react-js-make-props-and-state-transform/static-proptypes-many-props/output.tsx b/test/react-js-make-props-and-state-transform/static-proptypes-many-props/output.tsx index 7df6548..b7d105d 100644 --- a/test/react-js-make-props-and-state-transform/static-proptypes-many-props/output.tsx +++ b/test/react-js-make-props-and-state-transform/static-proptypes-many-props/output.tsx @@ -9,6 +9,16 @@ export default class MyComponent extends React.Component<{ string?: string; node?: React.ReactNode; element?: JSX.Element; + oneOf?: 'a' | 'b' | 'c'; + oneOfType?: string | number; + arrayOf?: string[]; + objectOf?: { + [key: string]: string; + }; + shape?: { + color?: string; + fontSize?: number; + }; anyRequired: any; arrayRequired: any[]; boolRequired: boolean; @@ -18,6 +28,16 @@ export default class MyComponent extends React.Component<{ stringRequired: string; nodeRequired: React.ReactNode; elementRequired: JSX.Element; + oneOfRequired: 'a' | 'b' | 'c'; + oneOfTypeRequired: string | number; + arrayOfRequired: string[]; + objectOfRequired: { + [key: string]: string; + }; + shapeRequired: { + color?: string; + fontSize: number; + }; }, { }> { static propTypes = { @@ -31,6 +51,17 @@ export default class MyComponent extends React.Component<{ string: React.PropTypes.string, node: React.PropTypes.node, element: React.PropTypes.element, + oneOf: React.PropTypes.oneOf(['a', 'b', 'c']), + oneOfType: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + ]), + arrayOf: React.PropTypes.arrayOf(React.PropTypes.string), + objectOf: React.PropTypes.objectOf(React.PropTypes.string), + shape: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: React.PropTypes.number, + }), anyRequired: React.PropTypes.any.isRequired, arrayRequired: React.PropTypes.array.isRequired, boolRequired: React.PropTypes.bool.isRequired, @@ -40,6 +71,17 @@ export default class MyComponent extends React.Component<{ stringRequired: React.PropTypes.string.isRequired, nodeRequired: React.PropTypes.node.isRequired, elementRequired: React.PropTypes.element.isRequired, + oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired, + oneOfTypeRequired: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + ]).isRequired, + arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, + objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired, + shapeRequired: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: React.PropTypes.number.isRequired, + }).isRequired, }; render() { return
;