/** * 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. * * @flow strict * @format */ 'use strict'; import type { SchemaType, Nullable, NamedShape, NativeModulePropertyShape, NativeModuleFunctionTypeAnnotation, NativeModuleParamTypeAnnotation, NativeModuleTypeAnnotation, NativeModuleEnumMap, } from '../../CodegenSchema'; import type {AliasResolver} from './Utils'; const {createAliasResolver, getModules} = require('./Utils'); const {unwrapNullable} = require('../../parsers/parsers-commons'); type FilesOutput = Map; const HostFunctionTemplate = ({ hasteModuleName, methodName, returnTypeAnnotation, args, }: $ReadOnly<{ hasteModuleName: string, methodName: string, returnTypeAnnotation: Nullable, args: Array, }>) => { const isNullable = returnTypeAnnotation.type === 'NullableTypeAnnotation'; const isVoid = returnTypeAnnotation.type === 'VoidTypeAnnotation'; const methodCallArgs = ['rt', ...args].join(', '); const methodCall = `static_cast<${hasteModuleName}CxxSpecJSI *>(&turboModule)->${methodName}(${methodCallArgs})`; return `static jsi::Value __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {${ isVoid ? `\n ${methodCall};` : isNullable ? `\n auto result = ${methodCall};` : '' } return ${ isVoid ? 'jsi::Value::undefined()' : isNullable ? 'result ? jsi::Value(std::move(*result)) : jsi::Value::null()' : methodCall }; }`; }; const ModuleTemplate = ({ hasteModuleName, hostFunctions, moduleName, methods, }: $ReadOnly<{ hasteModuleName: string, hostFunctions: $ReadOnlyArray, moduleName: string, methods: $ReadOnlyArray<$ReadOnly<{methodName: string, paramCount: number}>>, }>) => { return `${hostFunctions.join('\n')} ${hasteModuleName}CxxSpecJSI::${hasteModuleName}CxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule("${moduleName}", jsInvoker) { ${methods .map(({methodName, paramCount}) => { return ` methodMap_["${methodName}"] = MethodMetadata {${paramCount}, __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}};`; }) .join('\n')} }`; }; const FileTemplate = ({ libraryName, modules, }: $ReadOnly<{ libraryName: string, modules: string, }>) => { return `/** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * * Do not edit this file as changes may cause incorrect behavior and will be lost * once the code is regenerated. * * ${'@'}generated by codegen project: GenerateModuleH.js */ #include "${libraryName}JSI.h" namespace facebook { namespace react { ${modules} } // namespace react } // namespace facebook `; }; type Param = NamedShape>; function serializeArg( moduleName: string, arg: Param, index: number, resolveAlias: AliasResolver, enumMap: NativeModuleEnumMap, ): string { const {typeAnnotation: nullableTypeAnnotation, optional} = arg; const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); const isRequired = !optional && !nullable; let realTypeAnnotation = typeAnnotation; if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') { realTypeAnnotation = resolveAlias(realTypeAnnotation.name); } function wrap(callback: (val: string) => string) { const val = `args[${index}]`; const expression = callback(val); if (isRequired) { return expression; } else { let condition = `${val}.isNull() || ${val}.isUndefined()`; if (optional) { condition = `count < ${index} || ${condition}`; } return `${condition} ? std::nullopt : std::make_optional(${expression})`; } } switch (realTypeAnnotation.type) { case 'ReservedTypeAnnotation': switch (realTypeAnnotation.name) { case 'RootTag': return wrap(val => `${val}.getNumber()`); default: (realTypeAnnotation.name: empty); throw new Error( `Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`, ); } case 'StringTypeAnnotation': return wrap(val => `${val}.asString(rt)`); case 'BooleanTypeAnnotation': return wrap(val => `${val}.asBool()`); case 'EnumDeclaration': switch (realTypeAnnotation.memberType) { case 'NumberTypeAnnotation': return wrap(val => `${val}.asNumber()`); case 'StringTypeAnnotation': return wrap(val => `${val}.asString(rt)`); default: throw new Error( `Unknown enum type for "${arg.name}, found: ${realTypeAnnotation.type}"`, ); } case 'NumberTypeAnnotation': return wrap(val => `${val}.asNumber()`); case 'FloatTypeAnnotation': return wrap(val => `${val}.asNumber()`); case 'DoubleTypeAnnotation': return wrap(val => `${val}.asNumber()`); case 'Int32TypeAnnotation': return wrap(val => `${val}.asNumber()`); case 'ArrayTypeAnnotation': return wrap(val => `${val}.asObject(rt).asArray(rt)`); case 'FunctionTypeAnnotation': return wrap(val => `${val}.asObject(rt).asFunction(rt)`); case 'GenericObjectTypeAnnotation': return wrap(val => `${val}.asObject(rt)`); case 'UnionTypeAnnotation': switch (typeAnnotation.memberType) { case 'NumberTypeAnnotation': return wrap(val => `${val}.asNumber()`); case 'ObjectTypeAnnotation': return wrap(val => `${val}.asObject(rt)`); case 'StringTypeAnnotation': return wrap(val => `${val}.asString(rt)`); default: throw new Error( `Unsupported union member type for param "${arg.name}, found: ${realTypeAnnotation.memberType}"`, ); } case 'ObjectTypeAnnotation': return wrap(val => `${val}.asObject(rt)`); case 'MixedTypeAnnotation': return wrap(val => `jsi::Value(rt, ${val})`); default: (realTypeAnnotation.type: empty); throw new Error( `Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`, ); } } function serializePropertyIntoHostFunction( moduleName: string, hasteModuleName: string, property: NativeModulePropertyShape, resolveAlias: AliasResolver, enumMap: NativeModuleEnumMap, ): string { const [propertyTypeAnnotation] = unwrapNullable(property.typeAnnotation); return HostFunctionTemplate({ hasteModuleName, methodName: property.name, returnTypeAnnotation: propertyTypeAnnotation.returnTypeAnnotation, args: propertyTypeAnnotation.params.map((p, i) => serializeArg(moduleName, p, i, resolveAlias, enumMap), ), }); } module.exports = { generate( libraryName: string, schema: SchemaType, packageName?: string, assumeNonnull: boolean = false, ): FilesOutput { const nativeModules = getModules(schema); const modules = Object.keys(nativeModules) .map((hasteModuleName: string) => { const nativeModule = nativeModules[hasteModuleName]; const { aliasMap, enumMap, spec: {properties}, moduleName, } = nativeModule; const resolveAlias = createAliasResolver(aliasMap); const hostFunctions = properties.map(property => serializePropertyIntoHostFunction( moduleName, hasteModuleName, property, resolveAlias, enumMap, ), ); return ModuleTemplate({ hasteModuleName, hostFunctions, moduleName, methods: properties.map( ({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => { const [{params}] = unwrapNullable(nullableTypeAnnotation); return { methodName: propertyName, paramCount: params.length, }; }, ), }); }) .join('\n'); const fileName = `${libraryName}JSI-generated.cpp`; const replacedTemplate = FileTemplate({ modules, libraryName, }); return new Map([[fileName, replacedTemplate]]); }, };