@react-native/codegen
Version:
Code generation tools for React Native
220 lines (217 loc) • 6.21 kB
JavaScript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
;
function getValueFromTypes(value, types) {
switch (value.type) {
case 'TSTypeReference':
if (types[value.typeName.name]) {
return getValueFromTypes(types[value.typeName.name], types);
} else {
return value;
}
case 'TSTypeAliasDeclaration':
return getValueFromTypes(value.typeAnnotation, types);
default:
return value;
}
}
function isNull(t) {
return t.type === 'TSNullKeyword' || t.type === 'TSUndefinedKeyword';
}
function isNullOrVoid(t) {
return isNull(t) || t.type === 'TSVoidKeyword';
}
function couldBeNumericLiteral(type) {
return type === 'Literal' || type === 'NumericLiteral';
}
function couldBeSimpleLiteral(type) {
return (
couldBeNumericLiteral(type) ||
type === 'StringLiteral' ||
type === 'BooleanLiteral'
);
}
function evaluateLiteral(literalNode) {
const valueType = literalNode.type;
if (valueType === 'TSLiteralType') {
const literal = literalNode.literal;
if (couldBeSimpleLiteral(literal.type)) {
if (
typeof literal.value === 'string' ||
typeof literal.value === 'number' ||
typeof literal.value === 'boolean'
) {
return literal.value;
}
} else if (
literal.type === 'UnaryExpression' &&
literal.operator === '-' &&
couldBeNumericLiteral(literal.argument.type) &&
typeof literal.argument.value === 'number'
) {
return -literal.argument.value;
}
} else if (isNull(literalNode)) {
return null;
}
throw new Error(
'The default value in WithDefault must be string, number, boolean or null .',
);
}
function handleUnionAndParen(type, result, parser, knownTypes) {
switch (type.type) {
case 'TSParenthesizedType': {
handleUnionAndParen(type.typeAnnotation, result, parser, knownTypes);
break;
}
case 'TSUnionType': {
// the order is important
// result.optional must be set first
for (const t of type.types) {
if (isNullOrVoid(t)) {
result.optional = true;
}
}
for (const t of type.types) {
if (!isNullOrVoid(t)) {
handleUnionAndParen(t, result, parser, knownTypes);
}
}
break;
}
case 'TSTypeReference':
if (type.typeName.name === 'Readonly') {
handleUnionAndParen(
type.typeParameters.params[0],
result,
parser,
knownTypes,
);
} else if (parser.getTypeAnnotationName(type) === 'WithDefault') {
if (result.optional) {
throw new Error(
'WithDefault<> is optional and does not need to be marked as optional. Please remove the union of undefined and/or null',
);
}
if (type.typeParameters.params.length !== 2) {
throw new Error(
'WithDefault requires two parameters: type and default value.',
);
}
if (result.defaultValue !== undefined) {
throw new Error(
'Multiple WithDefault is not allowed nested or in a union type.',
);
}
result.optional = true;
result.defaultValue = evaluateLiteral(type.typeParameters.params[1]);
handleUnionAndParen(
type.typeParameters.params[0],
result,
parser,
knownTypes,
);
} else if (!knownTypes) {
result.unions.push(type);
} else {
const resolvedType = getValueFromTypes(type, knownTypes);
if (
resolvedType.type === 'TSTypeReference' &&
resolvedType.typeName.name === type.typeName.name
) {
result.unions.push(type);
} else {
handleUnionAndParen(resolvedType, result, parser, knownTypes);
}
}
break;
default:
result.unions.push(type);
}
}
function parseTopLevelType(type, parser, knownTypes) {
let result = {
unions: [],
optional: false,
};
handleUnionAndParen(type, result, parser, knownTypes);
if (result.unions.length === 0) {
throw new Error('Union type could not be just null or undefined.');
} else if (result.unions.length === 1) {
return {
type: result.unions[0],
optional: result.optional,
defaultValue: result.defaultValue,
};
} else {
return {
type: {
type: 'TSUnionType',
types: result.unions,
},
optional: result.optional,
defaultValue: result.defaultValue,
};
}
}
function handleIntersectionAndParen(type, result, parser, knownTypes) {
switch (type.type) {
case 'TSParenthesizedType': {
handleIntersectionAndParen(
type.typeAnnotation,
result,
parser,
knownTypes,
);
break;
}
case 'TSIntersectionType': {
for (const t of type.types) {
handleIntersectionAndParen(t, result, parser, knownTypes);
}
break;
}
case 'TSTypeReference':
if (type.typeName.name === 'Readonly') {
handleIntersectionAndParen(
type.typeParameters.params[0],
result,
parser,
knownTypes,
);
} else if (parser.getTypeAnnotationName(type) === 'WithDefault') {
throw new Error('WithDefault<> is now allowed in intersection types.');
} else if (!knownTypes) {
result.push(type);
} else {
const resolvedType = getValueFromTypes(type, knownTypes);
if (
resolvedType.type === 'TSTypeReference' &&
resolvedType.typeName.name === type.typeName.name
) {
result.push(type);
} else {
handleIntersectionAndParen(resolvedType, result, parser, knownTypes);
}
}
break;
default:
result.push(type);
}
}
function flattenIntersectionType(type, parser, knownTypes) {
const result = [];
handleIntersectionAndParen(type, result, parser, knownTypes);
return result;
}
module.exports = {
parseTopLevelType,
flattenIntersectionType,
};