From 2a3f97a0407d661268fca24591c9fdba95b7fa50 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Wed, 27 Feb 2019 15:27:57 -0700 Subject: [PATCH] Correctly process literal enum values in TS->propType conversion --- .../babel/proptypes-from-ts-props/index.js | 29 +++++++++++++++++-- .../proptypes-from-ts-props/index.test.js | 25 ++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/scripts/babel/proptypes-from-ts-props/index.js b/scripts/babel/proptypes-from-ts-props/index.js index ae2979fa938..65c54fbbf9f 100644 --- a/scripts/babel/proptypes-from-ts-props/index.js +++ b/scripts/babel/proptypes-from-ts-props/index.js @@ -247,6 +247,9 @@ function isPropTypeRequired(types, propType) { } function makePropTypeRequired(types, propType) { + // can't make literals required no matter how hard we try + if (types.isLiteral(propType) === true) return propType; + return types.memberExpression( propType, types.identifier('isRequired') @@ -275,6 +278,22 @@ function areExpressionsIdentical(a, b) { return aCode === bCode; } +/** + * Converts any literal node (StringLiteral, etc) into a PropTypes.oneOF([ literalNode ]) + * so it can be used in any proptype expression + */ +function convertLiteralToOneOf(types, literalNode) { + return types.callExpression( + types.memberExpression( + types.identifier('PropTypes'), + types.identifier('oneOf') + ), + [ + types.arrayExpression([ literalNode ]) + ] + ); +} + /** * Heavy lifter to generate the proptype AST for a node. Initially called by `processComponentDeclaration`, * its return value is set as the component's `propTypes` value. This function calls itself recursively to translate @@ -345,6 +364,12 @@ function getPropTypesForNode(node, optional, state) { ...((mergedProperties[typeProperty.key.name] ? mergedProperties[typeProperty.key.name].leadingComments : null) || []), ]; + let propTypeValue = typeProperty.value; + if (types.isLiteral(propTypeValue)) { + // can't use a literal straight, wrap it with PropTypes.oneOf([ the_literal ]) + propTypeValue = convertLiteralToOneOf(types, propTypeValue); + } + // if this property has already been found, the only action is to potentially change it to optional if (mergedProperties.hasOwnProperty(typeProperty.key.name)) { const existing = mergedProperties[typeProperty.key.name]; @@ -356,7 +381,7 @@ function getPropTypesForNode(node, optional, state) { ), [ types.arrayExpression( - [existing, typeProperty.value] + [existing, propTypeValue] ) ] ); @@ -367,7 +392,7 @@ function getPropTypesForNode(node, optional, state) { } } else { // property hasn't been seen yet, add it - mergedProperties[typeProperty.key.name] = typeProperty.value; + mergedProperties[typeProperty.key.name] = propTypeValue; } mergedProperties[typeProperty.key.name].leadingComments = leadingComments; diff --git a/scripts/babel/proptypes-from-ts-props/index.test.js b/scripts/babel/proptypes-from-ts-props/index.test.js index 005744d83dc..acd0dd839f4 100644 --- a/scripts/babel/proptypes-from-ts-props/index.test.js +++ b/scripts/babel/proptypes-from-ts-props/index.test.js @@ -1006,6 +1006,31 @@ FooComponent.propTypes = { };`); }); + it('intersects overlapping string enums in ExclusiveUnion', () => { + const result = transform( + ` +import React from 'react'; +interface IFooProps {type: 'foo', value: string} +interface IBarProps {type: 'bar', value: number} +const FooComponent: React.SFC> = () => { + return (
Hello World
); +}`, + babelOptions + ); + + expect(result.code).toBe(`import React from 'react'; +import PropTypes from "prop-types"; + +const FooComponent = () => { + return
Hello World
; +}; + +FooComponent.propTypes = { + type: PropTypes.oneOfType([PropTypes.oneOf(["foo"]), PropTypes.oneOf(["bar"])]), + value: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]).isRequired +};`); + }); + }); describe('array / arrayOf propTypes', () => {