react-native-codegen
Version:
⚛️ Code generation tools for React Native
469 lines (460 loc) • 14.6 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 _slicedToArray(arr, i) {
return (
_arrayWithHoles(arr) ||
_iterableToArrayLimit(arr, i) ||
_unsupportedIterableToArray(arr, i) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(o);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArrayLimit(r, l) {
var t =
null == r
? null
: ('undefined' != typeof Symbol && r[Symbol.iterator]) || r['@@iterator'];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (((i = (t = t.call(r)).next), 0 === l)) {
if (Object(t) !== t) return;
f = !1;
} else
for (
;
!(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l);
f = !0
);
} catch (r) {
(o = !0), (n = r);
} finally {
try {
if (!f && null != t.return && ((u = t.return()), Object(u) !== u))
return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
const invariant = require('invariant');
const _require = require('./StructCollector'),
StructCollector = _require.StructCollector;
const _require2 = require('./Utils'),
getNamespacedStructName = _require2.getNamespacedStructName;
const _require3 = require('../../Utils'),
capitalize = _require3.capitalize;
const _require4 = require('../../../parsers/flow/modules/utils'),
wrapNullable = _require4.wrapNullable,
unwrapNullable = _require4.unwrapNullable;
const ProtocolMethodTemplate = ({returnObjCType, methodName, params}) =>
`- (${returnObjCType})${methodName}${params};`;
function serializeMethod(
hasteModuleName,
property,
structCollector,
resolveAlias,
) {
const methodName = property.name,
nullableTypeAnnotation = property.typeAnnotation;
const _unwrapNullable = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable2 = _slicedToArray(_unwrapNullable, 1),
propertyTypeAnnotation = _unwrapNullable2[0];
const params = propertyTypeAnnotation.params;
if (methodName === 'getConstants') {
return serializeConstantsProtocolMethods(
hasteModuleName,
property,
structCollector,
resolveAlias,
);
}
const methodParams = [];
const structParamRecords = [];
params.forEach((param, index) => {
const structName = getParamStructName(methodName, param);
const _getParamObjCType = getParamObjCType(
hasteModuleName,
methodName,
param,
structName,
structCollector,
resolveAlias,
),
objCType = _getParamObjCType.objCType,
isStruct = _getParamObjCType.isStruct;
methodParams.push({
paramName: param.name,
objCType,
});
if (isStruct) {
structParamRecords.push({
paramIndex: index,
structName,
});
}
});
// Unwrap returnTypeAnnotation, so we check if the return type is Promise
// TODO(T76719514): Disallow nullable PromiseTypeAnnotations
const _unwrapNullable3 = unwrapNullable(
propertyTypeAnnotation.returnTypeAnnotation,
),
_unwrapNullable4 = _slicedToArray(_unwrapNullable3, 1),
returnTypeAnnotation = _unwrapNullable4[0];
if (returnTypeAnnotation.type === 'PromiseTypeAnnotation') {
methodParams.push(
{
paramName: 'resolve',
objCType: 'RCTPromiseResolveBlock',
},
{
paramName: 'reject',
objCType: 'RCTPromiseRejectBlock',
},
);
}
/**
* Build Protocol Method
**/
const returnObjCType = getReturnObjCType(
methodName,
propertyTypeAnnotation.returnTypeAnnotation,
);
const paddingMax = `- (${returnObjCType})${methodName}`.length;
const objCParams = methodParams.reduce(
($objCParams, {objCType, paramName}, i) => {
const rhs = `(${objCType})${paramName}`;
const padding = ' '.repeat(Math.max(0, paddingMax - paramName.length));
return i === 0
? `:${rhs}`
: `${$objCParams}\n${padding}${paramName}:${rhs}`;
},
'',
);
const protocolMethod = ProtocolMethodTemplate({
methodName,
returnObjCType,
params: objCParams,
});
/**
* Build ObjC Selector
*/
// $FlowFixMe[missing-type-arg]
const selector = methodParams
.map(({paramName}) => paramName)
.reduce(($selector, paramName, i) => {
return i === 0 ? `${$selector}:` : `${$selector}${paramName}:`;
}, methodName);
/**
* Build JS Return type
*/
const returnJSType = getReturnJSType(methodName, returnTypeAnnotation);
return [
{
methodName,
protocolMethod,
selector: `@selector(${selector})`,
structParamRecords,
returnJSType,
argCount: params.length,
},
];
}
function getParamStructName(methodName, param) {
const _unwrapNullable5 = unwrapNullable(param.typeAnnotation),
_unwrapNullable6 = _slicedToArray(_unwrapNullable5, 1),
typeAnnotation = _unwrapNullable6[0];
if (typeAnnotation.type === 'TypeAliasTypeAnnotation') {
return typeAnnotation.name;
}
return `Spec${capitalize(methodName)}${capitalize(param.name)}`;
}
function getParamObjCType(
hasteModuleName,
methodName,
param,
structName,
structCollector,
resolveAlias,
) {
const paramName = param.name,
nullableTypeAnnotation = param.typeAnnotation;
const _unwrapNullable7 = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable8 = _slicedToArray(_unwrapNullable7, 2),
typeAnnotation = _unwrapNullable8[0],
nullable = _unwrapNullable8[1];
const notRequired = param.optional || nullable;
function wrapIntoNullableIfNeeded(generatedType) {
return nullable ? `${generatedType} _Nullable` : generatedType;
}
const isStruct = objCType => ({
isStruct: true,
objCType,
});
const notStruct = objCType => ({
isStruct: false,
objCType,
});
// Handle types that can only be in parameters
switch (typeAnnotation.type) {
case 'FunctionTypeAnnotation': {
return notStruct('RCTResponseSenderBlock');
}
case 'ArrayTypeAnnotation': {
/**
* Array in params always codegen NSArray *
*
* TODO(T73933406): Support codegen for Arrays of structs and primitives
*
* For example:
* Array<number> => NSArray<NSNumber *>
* type Animal = {};
* Array<Animal> => NSArray<JS::NativeSampleTurboModule::Animal *>, etc.
*/
return notStruct(wrapIntoNullableIfNeeded('NSArray *'));
}
}
const _unwrapNullable9 = unwrapNullable(
structCollector.process(
structName,
'REGULAR',
resolveAlias,
wrapNullable(nullable, typeAnnotation),
),
),
_unwrapNullable10 = _slicedToArray(_unwrapNullable9, 1),
structTypeAnnotation = _unwrapNullable10[0];
invariant(
structTypeAnnotation.type !== 'ArrayTypeAnnotation',
'ArrayTypeAnnotations should have been processed earlier',
);
switch (structTypeAnnotation.type) {
case 'TypeAliasTypeAnnotation': {
/**
* TODO(T73943261): Support nullable object literals and aliases?
*/
return isStruct(
getNamespacedStructName(hasteModuleName, structTypeAnnotation.name) +
' &',
);
}
case 'ReservedTypeAnnotation':
switch (structTypeAnnotation.name) {
case 'RootTag':
return notStruct(notRequired ? 'NSNumber *' : 'double');
default:
structTypeAnnotation.name;
throw new Error(
`Unsupported type for param "${paramName}" in ${methodName}. Found: ${structTypeAnnotation.type}`,
);
}
case 'StringTypeAnnotation':
return notStruct(wrapIntoNullableIfNeeded('NSString *'));
case 'NumberTypeAnnotation':
return notStruct(notRequired ? 'NSNumber *' : 'double');
case 'FloatTypeAnnotation':
return notStruct(notRequired ? 'NSNumber *' : 'double');
case 'DoubleTypeAnnotation':
return notStruct(notRequired ? 'NSNumber *' : 'double');
case 'Int32TypeAnnotation':
return notStruct(notRequired ? 'NSNumber *' : 'double');
case 'BooleanTypeAnnotation':
return notStruct(notRequired ? 'NSNumber *' : 'BOOL');
case 'GenericObjectTypeAnnotation':
return notStruct(wrapIntoNullableIfNeeded('NSDictionary *'));
default:
structTypeAnnotation.type;
throw new Error(
`Unsupported type for param "${paramName}" in ${methodName}. Found: ${typeAnnotation.type}`,
);
}
}
function getReturnObjCType(methodName, nullableTypeAnnotation) {
const _unwrapNullable11 = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable12 = _slicedToArray(_unwrapNullable11, 2),
typeAnnotation = _unwrapNullable12[0],
nullable = _unwrapNullable12[1];
function wrapIntoNullableIfNeeded(generatedType) {
return nullable ? `${generatedType} _Nullable` : generatedType;
}
switch (typeAnnotation.type) {
case 'VoidTypeAnnotation':
return 'void';
case 'PromiseTypeAnnotation':
return 'void';
case 'ObjectTypeAnnotation':
return wrapIntoNullableIfNeeded('NSDictionary *');
case 'TypeAliasTypeAnnotation':
return wrapIntoNullableIfNeeded('NSDictionary *');
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType == null) {
return wrapIntoNullableIfNeeded('NSArray<id<NSObject>> *');
}
return wrapIntoNullableIfNeeded(
`NSArray<${getReturnObjCType(
methodName,
typeAnnotation.elementType,
)}> *`,
);
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapIntoNullableIfNeeded('NSNumber *');
default:
typeAnnotation.name;
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.name}`,
);
}
case 'StringTypeAnnotation':
// TODO: Can NSString * returns not be _Nullable?
// In the legacy codegen, we don't surround NSSTring * with _Nullable
return wrapIntoNullableIfNeeded('NSString *');
case 'NumberTypeAnnotation':
return wrapIntoNullableIfNeeded('NSNumber *');
case 'FloatTypeAnnotation':
return wrapIntoNullableIfNeeded('NSNumber *');
case 'DoubleTypeAnnotation':
return wrapIntoNullableIfNeeded('NSNumber *');
case 'Int32TypeAnnotation':
return wrapIntoNullableIfNeeded('NSNumber *');
case 'BooleanTypeAnnotation':
return wrapIntoNullableIfNeeded('NSNumber *');
case 'GenericObjectTypeAnnotation':
return wrapIntoNullableIfNeeded('NSDictionary *');
default:
typeAnnotation.type;
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
}
function getReturnJSType(methodName, nullableTypeAnnotation) {
const _unwrapNullable13 = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable14 = _slicedToArray(_unwrapNullable13, 1),
typeAnnotation = _unwrapNullable14[0];
switch (typeAnnotation.type) {
case 'VoidTypeAnnotation':
return 'VoidKind';
case 'PromiseTypeAnnotation':
return 'PromiseKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'TypeAliasTypeAnnotation':
return 'ObjectKind';
case 'ArrayTypeAnnotation':
return 'ArrayKind';
case 'ReservedTypeAnnotation':
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
return 'NumberKind';
case 'DoubleTypeAnnotation':
return 'NumberKind';
case 'Int32TypeAnnotation':
return 'NumberKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
default:
typeAnnotation.type;
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
}
function serializeConstantsProtocolMethods(
hasteModuleName,
property,
structCollector,
resolveAlias,
) {
const _unwrapNullable15 = unwrapNullable(property.typeAnnotation),
_unwrapNullable16 = _slicedToArray(_unwrapNullable15, 1),
propertyTypeAnnotation = _unwrapNullable16[0];
if (propertyTypeAnnotation.params.length !== 0) {
throw new Error(
`${hasteModuleName}.getConstants() may only accept 0 arguments.`,
);
}
const returnTypeAnnotation = propertyTypeAnnotation.returnTypeAnnotation;
if (returnTypeAnnotation.type !== 'ObjectTypeAnnotation') {
throw new Error(
`${hasteModuleName}.getConstants() may only return an object literal: {...}.`,
);
}
if (returnTypeAnnotation.properties.length === 0) {
return [];
}
const realTypeAnnotation = structCollector.process(
'Constants',
'CONSTANTS',
resolveAlias,
returnTypeAnnotation,
);
invariant(
realTypeAnnotation.type === 'TypeAliasTypeAnnotation',
"Unable to generate C++ struct from module's getConstants() method return type.",
);
const returnObjCType = `facebook::react::ModuleConstants<JS::${hasteModuleName}::Constants::Builder>`;
// $FlowFixMe[missing-type-arg]
return ['constantsToExport', 'getConstants'].map(methodName => {
const protocolMethod = ProtocolMethodTemplate({
methodName,
returnObjCType,
params: '',
});
return {
methodName,
protocolMethod,
returnJSType: 'ObjectKind',
selector: `@selector(${methodName})`,
structParamRecords: [],
argCount: 0,
};
});
}
module.exports = {
serializeMethod,
};