babel-plugin-typescript-to-proptypes
Version:
Generate React PropTypes from TypeScript prop interfaces.
372 lines (269 loc) • 15.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
const core = require('@babel/core');
const types = require('@babel/types');
const extractEnumValues = require('./extractEnumValues.js');
const getTypeName = require('./getTypeName.js');
const propTypes = require('./propTypes.js');
/* eslint-disable @typescript-eslint/no-use-before-define */
const NATIVE_BUILT_INS = ['Date', 'Error', 'RegExp', 'Map', 'WeakMap', 'Set', 'WeakSet', 'Promise'];
const PROP_TYPES_15_7 = 15.7; // eslint-disable-next-line complexity
function convert(type, state, depth) {
if (!type) {
return null;
}
const reactImportedName = state.reactImportedName,
propTypes$1 = state.propTypes;
const propTypesImportedName = propTypes$1.defaultImport;
const isMaxDepth = depth >= state.options.maxDepth; // Remove wrapping parens
if (core.types.isTSParenthesizedType(type)) {
type = type.typeAnnotation;
}
state.propTypes.count += 1; // any -> PropTypes.any
// unknown -> PropTypes.any
if (core.types.isTSAnyKeyword(type) || core.types.isTSVoidKeyword(type) || core.types.isTSUnknownKeyword(type)) {
return propTypes.createMember(core.types.identifier('any'), propTypesImportedName);
} // string -> PropTypes.string
if (core.types.isTSStringKeyword(type)) {
return propTypes.createMember(core.types.identifier('string'), propTypesImportedName);
} // number -> PropTypes.number
if (core.types.isTSNumberKeyword(type)) {
return propTypes.createMember(core.types.identifier('number'), propTypesImportedName);
} // boolean -> PropTypes.bool
if (core.types.isTSBooleanKeyword(type)) {
return propTypes.createMember(core.types.identifier('bool'), propTypesImportedName);
} // symbol -> PropTypes.symbol
if (core.types.isTSSymbolKeyword(type)) {
return propTypes.createMember(core.types.identifier('symbol'), propTypesImportedName);
} // object -> PropTypes.object
if (core.types.isTSObjectKeyword(type)) {
return propTypes.createMember(core.types.identifier('object'), propTypesImportedName);
} // null -> PropTypes.oneOf([null])
if (core.types.isTSNullKeyword(type)) {
return propTypes.createCall(core.types.identifier('oneOf'), [core.types.arrayExpression([core.types.nullLiteral()])], propTypesImportedName);
} // 'foo' -> PropTypes.oneOf(['foo'])
if (core.types.isTSLiteralType(type)) {
return propTypes.createCall(core.types.identifier('oneOf'), [core.types.arrayExpression([type.literal])], propTypesImportedName);
} // enum Foo {} -> PropTypes.oneOf
if (core.types.isTSEnumDeclaration(type)) {
return propTypes.createCall(core.types.identifier('oneOf'), [core.types.arrayExpression(extractEnumValues.extractEnumValues(type).map(value => {
if (typeof value === 'number') {
return core.types.numericLiteral(value);
}
return core.types.stringLiteral(value);
}))], propTypesImportedName);
} // Foo.VALUE -> *
if (core.types.isTSEnumMember(type)) {
if (type.initializer) {
return type.initializer;
} // (() => void) -> PropTypes.func
} else if (core.types.isTSFunctionType(type)) {
return propTypes.createMember(core.types.identifier('func'), propTypesImportedName); // React.ReactNode -> PropTypes.node
// React.ReactElement -> PropTypes.element
// React.MouseEvent -> PropTypes.object
// React.MouseEventHandler -> PropTypes.func
// React.Ref -> PropTypes.oneOfType()
// JSX.Element -> PropTypes.element
// FooShape, FooPropType -> FooShape, FooPropType
// Date, Error, RegExp -> Date, Error, RegExp
// CustomType -> PropTypes.any
} else if (core.types.isTSTypeReference(type)) {
const name = getTypeName.getTypeName(type.typeName); // Array<*>
if (name === 'Array') {
var _type$typeParameters;
const val = (_type$typeParameters = type.typeParameters) === null || _type$typeParameters === void 0 ? void 0 : _type$typeParameters.params[0];
const args = convertArray(val ? [val] : [], state, depth);
if (args.length === 0) {
return null;
}
return propTypes.createCall(core.types.identifier('arrayOf'), args, propTypesImportedName);
} // Record<string, string> -> PropTypes.objectOf(PropTypes.string)
if (name === 'Record') {
var _type$typeParameters2;
const result = convert((_type$typeParameters2 = type.typeParameters) === null || _type$typeParameters2 === void 0 ? void 0 : _type$typeParameters2.params[1], state, depth);
return result ? propTypes.createCall(core.types.identifier('objectOf'), [result], propTypesImportedName) : null;
} // node
if (propTypes.isReactTypeMatch(name, 'ReactText', reactImportedName) || propTypes.isReactTypeMatch(name, 'ReactNode', reactImportedName) || propTypes.isReactTypeMatch(name, 'ReactType', reactImportedName) || propTypes.isReactTypeMatch(name, 'ElementType', reactImportedName)) {
return propTypes.createMember(core.types.identifier('node'), propTypesImportedName);
} // function
if (propTypes.isReactTypeMatch(name, 'ComponentType', reactImportedName) || propTypes.isReactTypeMatch(name, 'ComponentClass', reactImportedName) || propTypes.isReactTypeMatch(name, 'StatelessComponent', reactImportedName) || propTypes.isReactTypeMatch(name, 'ElementType', reactImportedName)) {
return propTypes.getInstalledPropTypesVersion() >= PROP_TYPES_15_7 ? propTypes.createMember(core.types.identifier('elementType'), propTypesImportedName) : propTypes.createMember(core.types.identifier('func'), propTypesImportedName);
} // element
if (propTypes.isReactTypeMatch(name, 'Element', 'JSX') || propTypes.isReactTypeMatch(name, 'ReactElement', reactImportedName) || propTypes.isReactTypeMatch(name, 'ComponentElement', reactImportedName) || propTypes.isReactTypeMatch(name, 'FunctionComponentElement', reactImportedName) || propTypes.isReactTypeMatch(name, 'DOMElement', reactImportedName) || propTypes.isReactTypeMatch(name, 'SFCElement', reactImportedName)) {
return propTypes.createMember(core.types.identifier('element'), propTypesImportedName);
} // oneOfType
if (propTypes.isReactTypeMatch(name, 'Ref', reactImportedName)) {
return propTypes.createCall(core.types.identifier('oneOfType'), [core.types.arrayExpression([propTypes.createMember(core.types.identifier('string'), propTypesImportedName), propTypes.createMember(core.types.identifier('func'), propTypesImportedName), propTypes.createMember(core.types.identifier('object'), propTypesImportedName)])], propTypesImportedName);
} // function
if (name.endsWith('Handler')) {
return propTypes.createMember(core.types.identifier('func'), propTypesImportedName);
} // object
if (name.endsWith('Event')) {
return propTypes.createMember(core.types.identifier('object'), propTypesImportedName);
} // native built-ins
if (NATIVE_BUILT_INS.includes(name)) {
return propTypes.createCall(core.types.identifier('instanceOf'), [core.types.identifier(name)], propTypesImportedName);
} // inline references
if (state.referenceTypes[name]) {
return convert(state.referenceTypes[name], state, depth);
} // custom prop type variables
if (propTypes.hasCustomPropTypeSuffix(name, state.options.customPropTypeSuffixes)) {
return core.types.identifier(name);
} // Nothing found. If explicitly requested, return a prop type with "any",
// otherwise omit the prop.
return state.options.mapUnknownReferenceTypesToAny ? propTypes.createMember(core.types.identifier('any'), propTypesImportedName) : null; // [] -> PropTypes.arrayOf(), PropTypes.array
} else if (core.types.isTSArrayType(type)) {
const args = convertArray([type.elementType], state, depth);
return args.length > 0 ? propTypes.createCall(core.types.identifier('arrayOf'), args, propTypesImportedName) : propTypes.createMember(core.types.identifier('array'), propTypesImportedName); // {} -> PropTypes.object
// { [key: string]: string } -> PropTypes.objectOf(PropTypes.string)
// { foo: string } -> PropTypes.shape({ foo: PropTypes.string })
} else if (core.types.isTSTypeLiteral(type)) {
// object
if (type.members.length === 0 || isMaxDepth) {
return propTypes.createMember(core.types.identifier('object'), propTypesImportedName);
} // func
if (type.members.some(member => core.types.isTSCallSignatureDeclaration(member))) {
return propTypes.createMember(core.types.identifier('func'), propTypesImportedName);
} // objectOf
if (type.members.length === 1 && core.types.isTSIndexSignature(type.members[0])) {
var _index$typeAnnotation;
const index = type.members[0];
if ((_index$typeAnnotation = index.typeAnnotation) !== null && _index$typeAnnotation !== void 0 && _index$typeAnnotation.typeAnnotation) {
const result = convert(index.typeAnnotation.typeAnnotation, state, depth);
if (result) {
return propTypes.createCall(core.types.identifier('objectOf'), [result], propTypesImportedName);
}
} // shape
} else {
return propTypes.createCall(core.types.identifier('shape'), [core.types.objectExpression(convertListToProps(type.members.filter(member => core.types.isTSPropertySignature(member)), state, [], depth + 1))], propTypesImportedName);
} // { [K in Type]: string } -> PropTypes.objectOf(PropTypes.string)
} else if (core.types.isTSMappedType(type)) {
const result = convert(type.typeAnnotation, state, depth);
if (result) {
return propTypes.createCall(core.types.identifier('objectOf'), [result], propTypesImportedName);
} // string | number -> PropTypes.oneOfType([PropTypes.string, PropTypes.number])
// 'foo' | 'bar' -> PropTypes.oneOf(['foo', 'bar'])
} else if (core.types.isTSUnionType(type) || core.types.isTSIntersectionType(type)) {
const isAllLiterals = type.types.every(param => core.types.isTSLiteralType(param));
const containsAny = type.types.some(param => core.types.isTSAnyKeyword(param));
let label;
let args;
if (isAllLiterals) {
args = type.types.map(param => param.literal);
label = core.types.identifier('oneOf');
if (state.options.maxSize) {
args = args.slice(0, state.options.maxSize);
}
} else if (containsAny) {
return propTypes.createMember(core.types.identifier('any'), propTypesImportedName);
} else {
args = convertArray(type.types, state, depth);
label = core.types.identifier('oneOfType'); // Contained unresolved references, so just omit for now
if (args.length !== type.types.length) {
return null;
}
}
if (label && args.length > 0) {
return propTypes.createCall(label, [core.types.arrayExpression(args)], propTypesImportedName);
} // interface Foo {}
} else if (core.types.isTSInterfaceDeclaration(type)) {
// object
if (type.body.body.length === 0 || isMaxDepth) {
return propTypes.createMember(core.types.identifier('object'), propTypesImportedName);
} // func
if (type.body.body.some(member => core.types.isTSCallSignatureDeclaration(member))) {
return propTypes.createMember(core.types.identifier('func'), propTypesImportedName);
} // shape
return propTypes.createCall(core.types.identifier('shape'), [core.types.objectExpression(convertListToProps(type.body.body.filter(property => core.types.isTSPropertySignature(property)), state, [], depth + 1))], propTypesImportedName); // type Foo = {};
} else if (core.types.isTSTypeAliasDeclaration(type)) {
return convert(type.typeAnnotation, state, depth); // Type['prop']
} else if (core.types.isTSIndexedAccessType(type)) {
const _type = type,
objectType = _type.objectType,
indexType = _type.indexType;
if (core.types.isTSTypeReference(objectType) && core.types.isTSLiteralType(indexType)) {
const ref = state.referenceTypes[objectType.typeName.name];
let properties;
if (core.types.isTSInterfaceDeclaration(ref)) {
properties = ref.body.body;
} else if (core.types.isTSTypeAliasDeclaration(ref) && core.types.isTSTypeLiteral(ref.typeAnnotation)) {
properties = ref.typeAnnotation.members;
} else {
return null;
}
const property = properties.find(prop => core.types.isTSPropertySignature(prop) && prop.key.name === indexType.literal.value);
return property ? convert(property.typeAnnotation.typeAnnotation, state, depth) : null;
} // typeof foo
} else if (core.types.isTSTypeQuery(type)) {
return propTypes.createMember(core.types.identifier('any'), propTypesImportedName); // keyof foo
} else if (core.types.isTSTypeOperator(type) && type.operator === 'keyof') {
return propTypes.createMember(core.types.identifier('any'), propTypesImportedName);
}
state.propTypes.count -= 1;
return null;
}
function mustBeOptional(type) {
// Unions that contain undefined or null cannot be required by design
if (core.types.isTSUnionType(type)) {
return type.types.some(value => core.types.isTSAnyKeyword(value) || core.types.isTSNullKeyword(value) || core.types.isTSUndefinedKeyword(value));
}
return false;
}
function convertArray(types, state, depth) {
const propTypes = [];
types.forEach(type => {
const prop = convert(type, state, depth);
if (prop) {
propTypes.push(prop);
}
});
return propTypes;
}
function convertListToProps(properties, state, defaultProps, depth) {
const propTypes$1 = [];
let hasChildren = false;
let size = 0;
properties.some(property => {
if (state.options.maxSize && size === state.options.maxSize) {
return true;
}
if (!property.typeAnnotation) {
return false;
}
const type = property.typeAnnotation.typeAnnotation;
const propType = convert(type, state, depth);
const name = property.key.name;
if (propType) {
const objProperty = core.types.objectProperty(property.key, propTypes.wrapIsRequired(propType, !state.options.strict || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
property.optional || defaultProps.includes(name) || mustBeOptional(type)));
if (state.options.comments && property.leadingComments) {
property.leadingComments.forEach(comment => {
types.addComment(objProperty, 'leading', comment.value);
});
}
propTypes$1.push(objProperty);
if (name === 'children') {
hasChildren = true;
}
size += 1;
}
return false;
}); // Only append implicit children when the root list is being created
if (!hasChildren && depth === 0 && propTypes$1.length > 0 && state.options.implicitChildren) {
propTypes$1.push(core.types.objectProperty(core.types.identifier('children'), propTypes.createMember(core.types.identifier('node'), state.propTypes.defaultImport)));
}
return propTypes$1;
}
function convertToPropTypes(types, typeNames, state, defaultProps) {
const properties = [];
typeNames.forEach(typeName => {
if (types[typeName]) {
properties.push(...convertListToProps(types[typeName], state, defaultProps, 0));
}
});
return properties;
}
exports.convertToPropTypes = convertToPropTypes;
//# sourceMappingURL=convertBabelToPropTypes.js.map