nitro-codegen
Version:
The code-generator for react-native-nitro-modules.
166 lines (141 loc) • 5.09 kB
text/typescript
import type { SourceFile } from '../SourceFile.js'
import { createFileMetadataString, isNotDuplicate } from '../helpers.js'
import { indent } from '../../utils.js'
import type { HybridObjectSpec } from '../HybridObjectSpec.js'
import { includeHeader, includeNitroHeader } from './includeNitroHeader.js'
import { NitroConfig } from '../../config/NitroConfig.js'
import { getHybridObjectName } from '../getHybridObjectName.js'
import { HybridObjectType } from '../types/HybridObjectType.js'
export function createCppHybridObject(spec: HybridObjectSpec): SourceFile[] {
// Extra includes
const extraFiles = [
...spec.properties.flatMap((p) => p.getExtraFiles()),
...spec.methods.flatMap((m) => m.getExtraFiles()),
]
const extraIncludes = [
...spec.properties.flatMap((p) => p.getRequiredImports()),
...spec.methods.flatMap((m) => m.getRequiredImports()),
]
const cppForwardDeclarations = extraIncludes
.map((i) => i.forwardDeclaration)
.filter((v) => v != null)
.filter(isNotDuplicate)
const cppExtraIncludes = extraIncludes
.map((i) => includeHeader(i))
.filter(isNotDuplicate)
const cxxNamespace = NitroConfig.getCxxNamespace('c++')
const name = getHybridObjectName(spec.name)
const bases = ['public virtual HybridObject']
for (const base of spec.baseTypes) {
const hybridObject = new HybridObjectType(base)
bases.push(`public virtual ${getHybridObjectName(base.name).HybridTSpec}`)
const imports = hybridObject.getRequiredImports()
cppForwardDeclarations.push(
...imports.map((i) => i.forwardDeclaration).filter((f) => f != null)
)
cppExtraIncludes.push(...imports.map((i) => includeHeader(i)))
}
// Generate the full header / code
const cppHeaderCode = `
${createFileMetadataString(`${name.HybridTSpec}.hpp`)}
#pragma once
${includeNitroHeader('HybridObject.hpp')}
${cppForwardDeclarations.join('\n')}
${cppExtraIncludes.join('\n')}
namespace ${cxxNamespace} {
using namespace margelo::nitro;
/**
* An abstract base class for \`${spec.name}\`
* Inherit this class to create instances of \`${name.HybridTSpec}\` in C++.
* You must explicitly call \`HybridObject\`'s constructor yourself, because it is virtual.
* @example
* \`\`\`cpp
* class ${name.HybridT}: public ${name.HybridTSpec} {
* public:
* ${name.HybridT}(...): HybridObject(TAG) { ... }
* // ...
* };
* \`\`\`
*/
class ${name.HybridTSpec}: ${bases.join(', ')} {
public:
// Constructor
explicit ${name.HybridTSpec}(): HybridObject(TAG) { }
// Destructor
~${name.HybridTSpec}() override = default;
public:
// Properties
${indent(spec.properties.map((p) => p.getCode('c++', { virtual: true })).join('\n'), ' ')}
public:
// Methods
${indent(spec.methods.map((m) => m.getCode('c++', { virtual: true })).join('\n'), ' ')}
protected:
// Hybrid Setup
void loadHybridMethods() override;
protected:
// Tag for logging
static constexpr auto TAG = "${spec.name}";
};
} // namespace ${cxxNamespace}
`
// Each C++ method needs to be registered in the HybridObject - that's getters, setters and normal methods.
const registrations: string[] = []
for (const property of spec.properties) {
const getterMethod = property.getGetterName('other')
const setterMethod = property.getSetterName('other')
// getter
registrations.push(
`prototype.registerHybridGetter("${property.name}", &${name.HybridTSpec}::${getterMethod});`
)
if (!property.isReadonly) {
// setter
registrations.push(
`prototype.registerHybridSetter("${property.name}", &${name.HybridTSpec}::${setterMethod});`
)
}
}
for (const method of spec.methods) {
// method
registrations.push(
`prototype.registerHybridMethod("${method.name}", &${name.HybridTSpec}::${method.name});`
)
}
const basePrototypeRegistrations = ['HybridObject::loadHybridMethods();']
for (const base of spec.baseTypes) {
const hybridObjectName = getHybridObjectName(base.name)
basePrototypeRegistrations.push(
`${hybridObjectName.HybridTSpec}::loadHybridMethods();`
)
}
const cppBodyCode = `
${createFileMetadataString(`${name.HybridTSpec}.cpp`)}
#include "${name.HybridTSpec}.hpp"
namespace ${cxxNamespace} {
void ${name.HybridTSpec}::loadHybridMethods() {
// load base methods/properties
${indent(basePrototypeRegistrations.join('\n'), ' ')}
// load custom methods/properties
registerHybrids(this, [](Prototype& prototype) {
${indent(registrations.join('\n'), ' ')}
});
}
} // namespace ${cxxNamespace}
`
const files: SourceFile[] = []
files.push({
content: cppHeaderCode,
name: `${name.HybridTSpec}.hpp`,
subdirectory: [],
language: 'c++',
platform: 'shared',
})
files.push({
content: cppBodyCode,
name: `${name.HybridTSpec}.cpp`,
subdirectory: [],
language: 'c++',
platform: 'shared',
})
files.push(...extraFiles)
return files
}