UNPKG

nitro-codegen

Version:

The code-generator for react-native-nitro-modules.

195 lines (174 loc) 6.05 kB
import { NitroConfig } from '../../config/NitroConfig.js' import { capitalizeName, indent } from '../../utils.js' import { includeHeader } from '../c++/includeNitroHeader.js' import { getReferencedTypes } from '../getReferencedTypes.js' import { createFileMetadataString, isNotDuplicate } from '../helpers.js' import type { SourceFile } from '../SourceFile.js' import type { StructType } from '../types/StructType.js' import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js' export function createKotlinStruct(structType: StructType): SourceFile[] { const packageName = NitroConfig.getAndroidPackage('java/kotlin') const values = structType.properties.map( (p) => `val ${p.escapedName}: ${p.getCode('kotlin')}` ) let secondaryConstructor: string const needsSpecialHandling = structType.properties.some( (p) => new KotlinCxxBridgedType(p).needsSpecialHandling ) if (needsSpecialHandling) { const params = structType.properties.map((p) => { const bridged = new KotlinCxxBridgedType(p) return `${p.escapedName}: ${bridged.getTypeCode('kotlin')}` }) const paramsForward = structType.properties.map((p) => { const bridged = new KotlinCxxBridgedType(p) if (bridged.needsSpecialHandling) { // We need special parsing for this type const parsed = bridged.parseFromCppToKotlin( p.escapedName, 'kotlin', false ) // we explicitly `as`-cast this to avoid ambiguous upcasts/cyclic this() calls. return `${parsed} as ${p.getCode('kotlin')}` } else { return p.escapedName } }) secondaryConstructor = ` @DoNotStrip @Keep @Suppress("unused") private constructor(${indent(params.join(', '), 20)}) : this(${indent(paramsForward.join(', '), 20)}) `.trim() } else { secondaryConstructor = `/* main constructor */` } const code = ` ${createFileMetadataString(`${structType.structName}.kt`)} package ${packageName} import androidx.annotation.Keep import com.facebook.proguard.annotations.DoNotStrip import com.margelo.nitro.core.* /** * Represents the JavaScript object/struct "${structType.structName}". */ @DoNotStrip @Keep data class ${structType.structName} @DoNotStrip @Keep constructor( ${indent(values.join(',\n'), ' ')} ) { ${indent(secondaryConstructor, ' ')} } `.trim() const cxxNamespace = NitroConfig.getCxxNamespace('c++') const jniClassDescriptor = NitroConfig.getAndroidPackage( 'c++/jni', structType.structName ) const jniStructInitializerBody = createJNIStructInitializer(structType) const cppStructInitializerBody = createCppStructInitializer( 'value', structType ) const imports = structType.properties .flatMap((p) => getReferencedTypes(p)) .map((t) => new KotlinCxxBridgedType(t)) .flatMap((t) => t.getRequiredImports()) const includes = imports .map((i) => includeHeader(i)) .filter(isNotDuplicate) .sort() const fbjniCode = ` ${createFileMetadataString(`J${structType.structName}.hpp`)} #pragma once #include <fbjni/fbjni.h> #include "${structType.declarationFile.name}" ${includes.join('\n')} namespace ${cxxNamespace} { using namespace facebook; /** * The C++ JNI bridge between the C++ struct "${structType.structName}" and the the Kotlin data class "${structType.structName}". */ struct J${structType.structName} final: public jni::JavaClass<J${structType.structName}> { public: static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};"; public: /** * Convert this Java/Kotlin-based struct to the C++ struct ${structType.structName} by copying all values to C++. */ [[maybe_unused]] [[nodiscard]] ${structType.structName} toCpp() const { ${indent(jniStructInitializerBody, ' ')} } public: /** * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. */ [[maybe_unused]] static jni::local_ref<J${structType.structName}::javaobject> fromCpp(const ${structType.structName}& value) { ${indent(cppStructInitializerBody, ' ')} } }; } // namespace ${cxxNamespace} `.trim() const files: SourceFile[] = [] files.push({ content: code, language: 'kotlin', name: `${structType.structName}.kt`, subdirectory: NitroConfig.getAndroidPackageDirectory(), platform: 'android', }) files.push({ content: fbjniCode, language: 'c++', name: `J${structType.structName}.hpp`, subdirectory: [], platform: 'android', }) return files } function createJNIStructInitializer(structType: StructType): string { const lines: string[] = ['static const auto clazz = javaClassStatic();'] for (const prop of structType.properties) { const fieldName = `field${capitalizeName(prop.escapedName)}` const jniType = new KotlinCxxBridgedType(prop) const signatureType = jniType.getTypeCode('c++') const valueType = jniType.asJniReferenceType('local') lines.push( `static const auto ${fieldName} = clazz->getField<${signatureType}>("${prop.escapedName}");` ) lines.push( `${valueType} ${prop.escapedName} = this->getFieldValue(${fieldName});` ) } const propsForward = structType.properties.map((p) => { const bridged = new KotlinCxxBridgedType(p) return bridged.parse(p.escapedName, 'kotlin', 'c++', 'c++') }) lines.push(`return ${structType.structName}(`) lines.push(` ${indent(propsForward.join(',\n'), ' ')}`) lines.push(`);`) return lines.join('\n') } function createCppStructInitializer( cppValueName: string, structType: StructType ): string { const lines: string[] = [] lines.push(`return newInstance(`) const names = structType.properties.map((p) => { const name = `${cppValueName}.${p.escapedName}` const bridge = new KotlinCxxBridgedType(p) return bridge.parse(name, 'c++', 'kotlin', 'c++') }) lines.push(` ${indent(names.join(',\n'), ' ')}`) lines.push(');') return lines.join('\n') }