first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,620 @@
/**
* 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
*/
'use strict';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Cxx');
const {getEnumName, toPascalCase, toSafeCppString} = require('../Utils');
const {
createAliasResolver,
getModules,
isArrayRecursiveMember,
isDirectRecursiveMember,
} = require('./Utils');
function serializeArg(moduleName, arg, index, resolveAlias, enumMap) {
const {typeAnnotation: nullableTypeAnnotation, optional} = arg;
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
function wrap(callback) {
const val = `args[${index}]`;
const expression = callback(val);
// param?: T
if (optional && !nullable) {
// throw new Error('are we hitting this case? ' + moduleName);
return `count <= ${index} || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`;
}
// param: ?T
// param?: ?T
if (nullable || optional) {
return `count <= ${index} || ${val}.isNull() || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`;
}
// param: T
return `count <= ${index} ? throw jsi::JSError(rt, "Expected argument in position ${index} to be passed") : ${expression}`;
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrap(val => `${val}.asNumber()`);
default:
realTypeAnnotation.name;
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`,
);
}
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'StringLiteralTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'StringLiteralUnionTypeAnnotation':
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 'NumberLiteralTypeAnnotation':
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;
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
}
const ModuleSpecClassDeclarationTemplate = ({
hasteModuleName,
moduleName,
structs,
enums,
moduleEventEmitters,
moduleFunctions,
methods,
}) => {
return `${enums}${structs}
template <typename T>
class JSI_EXPORT ${hasteModuleName}CxxSpec : public TurboModule {
public:
static constexpr std::string_view kModuleName = "${moduleName}";
protected:
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{${hasteModuleName}CxxSpec::kModuleName}, jsInvoker) {
${methods
.map(({methodName, paramCount}) => {
return ` methodMap_["${methodName}"] = MethodMetadata {.argCount = ${paramCount}, .invoker = __${methodName}};`;
})
.join(
'\n',
)}${moduleEventEmitters.length > 0 ? '\n' : ''}${moduleEventEmitters.map(e => e.registerEventEmitter).join('\n')}
}
${moduleEventEmitters.map(e => e.emitFunction).join('\n')}
private:
${moduleFunctions.join('\n\n')}
};`;
};
const FileTemplate = ({modules}) => {
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
*/
#pragma once
#include <ReactCommon/TurboModule.h>
#include <react/bridging/Bridging.h>
namespace facebook::react {
${modules.join('\n\n')}
} // namespace facebook::react
`;
};
function translatePrimitiveJSTypeToCpp(
moduleName,
parentObjectAliasName,
nullableTypeAnnotation,
optional,
createErrorMessage,
resolveAlias,
enumMap,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRecursiveType = isDirectRecursiveMember(
parentObjectAliasName,
nullableTypeAnnotation,
);
const isRequired = (!optional && !nullable) || isRecursiveType;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('int', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('bool', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'GenericObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'ArrayTypeAnnotation':
return wrapOptional('jsi::Array', isRequired);
case 'FunctionTypeAnnotation':
return wrapOptional('jsi::Function', isRequired);
case 'PromiseTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'MixedTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function createStructsString(hasteModuleName, aliasMap, resolveAlias, enumMap) {
const getCppType = (parentObjectAlias, v) =>
translatePrimitiveJSTypeToCpp(
hasteModuleName,
parentObjectAlias,
v.typeAnnotation,
false,
typeName => `Unsupported type for param "${v.name}". Found: ${typeName}`,
resolveAlias,
enumMap,
);
return Object.keys(aliasMap)
.map(alias => {
const value = aliasMap[alias];
if (value.properties.length === 0) {
return '';
}
const structName = `${hasteModuleName}${alias}`;
const templateParameter = value.properties.filter(
v =>
!isDirectRecursiveMember(alias, v.typeAnnotation) &&
!isArrayRecursiveMember(alias, v.typeAnnotation),
);
const templateParameterWithTypename = templateParameter
.map((v, i) => `typename P${i}`)
.join(', ');
const templateParameterWithoutTypename = templateParameter
.map((v, i) => `P${i}`)
.join(', ');
let i = -1;
const templateMemberTypes = value.properties.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return `std::unique_ptr<${structName}<${templateParameterWithoutTypename}>> ${v.name}`;
} else if (isArrayRecursiveMember(alias, v.typeAnnotation)) {
const [nullable] = unwrapNullable(v.typeAnnotation);
return (
(nullable
? `std::optional<std::vector<${structName}<${templateParameterWithoutTypename}>>>`
: `std::vector<${structName}<${templateParameterWithoutTypename}>>`) +
` ${v.name}`
);
} else {
i++;
return `P${i} ${v.name}`;
}
});
const debugParameterConversion = value.properties
.map(
v => ` static ${getCppType(alias, v)} ${v.name}ToJs(jsi::Runtime &rt, decltype(types.${v.name}) value) {
return bridging::toJs(rt, value);
}`,
)
.join('\n');
return `
#pragma mark - ${structName}
template <${templateParameterWithTypename}>
struct ${structName} {
${templateMemberTypes.map(v => ' ' + v).join(';\n')};
bool operator==(const ${structName} &other) const {
return ${value.properties.map(v => `${v.name} == other.${v.name}`).join(' && ')};
}
};
template <typename T>
struct ${structName}Bridging {
static T types;
static T fromJs(
jsi::Runtime &rt,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
T result{
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` value.hasProperty(rt, "${v.name}") ? std::make_unique<T>(bridging::fromJs<T>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)) : nullptr`;
} else {
return ` bridging::fromJs<decltype(types.${v.name})>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`;
}
})
.join(',\n')}};
return result;
}
#ifdef DEBUG
${debugParameterConversion}
#endif
static jsi::Object toJs(
jsi::Runtime &rt,
const T &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
auto result = facebook::jsi::Object(rt);
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, *value.${v.name}, jsInvoker));
}`;
} else if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}, jsInvoker));`;
}
})
.join('\n')}
return result;
}
};
`;
})
.join('\n');
}
const EnumTemplate = ({
enumName,
values,
fromCases,
toCases,
nativeEnumMemberType,
}) => {
const [fromValue, fromValueConversion, toValue] =
nativeEnumMemberType === 'std::string'
? [
'const jsi::String &rawValue',
'std::string value = rawValue.utf8(rt);',
'jsi::String',
]
: [
'const jsi::Value &rawValue',
'double value = (double)rawValue.asNumber();',
'jsi::Value',
];
return `
#pragma mark - ${enumName}
enum class ${enumName} { ${values} };
template <>
struct Bridging<${enumName}> {
static ${enumName} fromJs(jsi::Runtime &rt, ${fromValue}) {
${fromValueConversion}
${fromCases}
}
static ${toValue} toJs(jsi::Runtime &rt, ${enumName} value) {
${toCases}
}
};`;
};
function getMemberValueAppearance(member) {
if (member.type === 'StringLiteralTypeAnnotation') {
return `"${member.value}"`;
} else {
return member.value;
}
}
function generateEnum(hasteModuleName, origEnumName, members, memberType) {
const enumName = getEnumName(hasteModuleName, origEnumName);
const nativeEnumMemberType =
memberType === 'StringTypeAnnotation' ? 'std::string' : 'int32_t';
const fromCases =
members
.map(
member => `if (value == ${getMemberValueAppearance(member.value)}) {
return ${enumName}::${toSafeCppString(member.name)};
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for value in ${enumName}");
}`;
const toCases =
members
.map(
member => `if (value == ${enumName}::${toSafeCppString(member.name)}) {
return bridging::toJs(rt, ${getMemberValueAppearance(member.value)});
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for enum value in ${enumName}");
}`;
return EnumTemplate({
enumName,
values: members.map(member => toSafeCppString(member.name)).join(', '),
fromCases,
toCases,
nativeEnumMemberType,
});
}
function createEnums(hasteModuleName, enumMap, resolveAlias) {
return Object.entries(enumMap)
.map(([enumName, enumNode]) => {
return generateEnum(
hasteModuleName,
enumName,
enumNode.members,
enumNode.memberType,
);
})
.filter(Boolean)
.join('\n');
}
function translateFunctionToCpp(
hasteModuleName,
prop,
resolveAlias,
enumMap,
args,
returnTypeAnnotation,
) {
const [propTypeAnnotation] = unwrapNullable(prop.typeAnnotation);
const isNullable = returnTypeAnnotation.type === 'NullableTypeAnnotation';
const isVoid = returnTypeAnnotation.type === 'VoidTypeAnnotation';
const paramTypes = propTypeAnnotation.params.map(param => {
const translatedParam = translatePrimitiveJSTypeToCpp(
hasteModuleName,
null,
param.typeAnnotation,
param.optional,
typeName =>
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
return `${translatedParam} ${param.name}`;
});
paramTypes.unshift('jsi::Runtime &rt');
const returnType = translatePrimitiveJSTypeToCpp(
hasteModuleName,
null,
propTypeAnnotation.returnTypeAnnotation,
false,
typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
let methodCallArgs = [...args].join(',\n ');
if (methodCallArgs.length > 0) {
methodCallArgs = `,\n ${methodCallArgs}`;
}
return ` static jsi::Value __${prop.name}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* ${args.length > 0 ? 'args' : '/*args*/'}, size_t ${args.length > 0 ? 'count' : '/*count*/'}) {
static_assert(
bridging::getParameterCount(&T::${prop.name}) == ${paramTypes.length},
"Expected ${prop.name}(...) to have ${paramTypes.length} parameters");
${!isVoid ? (!isNullable ? 'return ' : 'auto result = ') : ''}bridging::callFromJs<${returnType}>(rt, &T::${prop.name}, static_cast<${hasteModuleName}CxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule)${methodCallArgs});${!isVoid ? (!isNullable ? '' : 'return result ? jsi::Value(std::move(*result)) : jsi::Value::null();') : 'return jsi::Value::undefined();'}\n }`;
}
function translateEventEmitterToCpp(
moduleName,
eventEmitter,
resolveAlias,
enumMap,
) {
const isVoidTypeAnnotation =
eventEmitter.typeAnnotation.typeAnnotation.type === 'VoidTypeAnnotation';
const templateName = `${toPascalCase(eventEmitter.name)}Type`;
const jsiType = translatePrimitiveJSTypeToCpp(
moduleName,
null,
eventEmitter.typeAnnotation.typeAnnotation,
false,
typeName =>
`Unsupported type for eventEmitter "${eventEmitter.name}" in ${moduleName}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
const isArray = jsiType === 'jsi::Array';
return {
isVoidTypeAnnotation: isVoidTypeAnnotation,
templateName: isVoidTypeAnnotation ? `/*${templateName}*/` : templateName,
registerEventEmitter: ` eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<${isVoidTypeAnnotation ? '' : 'jsi::Value'}>>();`,
emitFunction: `
${isVoidTypeAnnotation ? '' : `template <typename ${templateName}> `}void emit${toPascalCase(eventEmitter.name)}(${isVoidTypeAnnotation ? '' : `${isArray ? `std::vector<${templateName}>` : templateName} value`}) {${
isVoidTypeAnnotation
? ''
: `
static_assert(bridging::supportsFromJs<${isArray ? `std::vector<${templateName}>` : templateName}, ${jsiType}>, "value cannnot be converted to ${jsiType}");`
}
static_cast<AsyncEventEmitter<${isVoidTypeAnnotation ? '' : 'jsi::Value'}>&>(*eventEmitterMap_["${eventEmitter.name}"]).emit(${
isVoidTypeAnnotation
? ''
: `[jsInvoker = jsInvoker_, eventValue = value](jsi::Runtime& rt) -> jsi::Value {
return bridging::toJs(rt, eventValue, jsInvoker);
}`
});
}`,
};
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
const nativeModule = nativeModules[hasteModuleName];
const {
aliasMap,
enumMap,
spec: {methods},
spec,
moduleName,
} = nativeModule;
const resolveAlias = createAliasResolver(aliasMap);
const structs = createStructsString(
hasteModuleName,
aliasMap,
resolveAlias,
enumMap,
);
const enums = createEnums(hasteModuleName, enumMap, resolveAlias);
return [
ModuleSpecClassDeclarationTemplate({
hasteModuleName,
moduleName,
structs,
enums,
moduleEventEmitters: spec.eventEmitters.map(eventEmitter =>
translateEventEmitterToCpp(
moduleName,
eventEmitter,
resolveAlias,
enumMap,
),
),
moduleFunctions: spec.methods.map(property => {
const [propertyTypeAnnotation] = unwrapNullable(
property.typeAnnotation,
);
return translateFunctionToCpp(
hasteModuleName,
property,
resolveAlias,
enumMap,
propertyTypeAnnotation.params.map((p, i) =>
serializeArg(moduleName, p, i, resolveAlias, enumMap),
),
propertyTypeAnnotation.returnTypeAnnotation,
);
}),
methods: methods.map(
({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => {
const [{params}] = unwrapNullable(nullableTypeAnnotation);
return {
methodName: propertyName,
paramCount: params.length,
};
},
),
}),
];
});
const fileName = `${libraryName}JSI.h`;
const replacedTemplate = FileTemplate({
modules,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,744 @@
/**
* 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 {
NamedShape,
NativeModuleAliasMap,
NativeModuleBaseTypeAnnotation,
NativeModuleEnumMap,
NativeModuleEnumMember,
NativeModuleEnumMemberType,
NativeModuleEventEmitterShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Cxx');
const {getEnumName, toPascalCase, toSafeCppString} = require('../Utils');
const {
createAliasResolver,
getModules,
isArrayRecursiveMember,
isDirectRecursiveMember,
} = require('./Utils');
type FilesOutput = Map<string, string>;
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function serializeArg(
moduleName: string,
arg: Param,
index: number,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): string {
const {typeAnnotation: nullableTypeAnnotation, optional} = arg;
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
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);
// param?: T
if (optional && !nullable) {
// throw new Error('are we hitting this case? ' + moduleName);
return `count <= ${index} || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`;
}
// param: ?T
// param?: ?T
if (nullable || optional) {
return `count <= ${index} || ${val}.isNull() || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`;
}
// param: T
return `count <= ${index} ? throw jsi::JSError(rt, "Expected argument in position ${index} to be passed") : ${expression}`;
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrap(val => `${val}.asNumber()`);
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 'StringLiteralTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'StringLiteralUnionTypeAnnotation':
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 'NumberLiteralTypeAnnotation':
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}"`,
);
}
}
const ModuleSpecClassDeclarationTemplate = ({
hasteModuleName,
moduleName,
structs,
enums,
moduleEventEmitters,
moduleFunctions,
methods,
}: $ReadOnly<{
hasteModuleName: string,
moduleName: string,
structs: string,
enums: string,
moduleEventEmitters: EventEmitterCpp[],
moduleFunctions: string[],
methods: $ReadOnlyArray<$ReadOnly<{methodName: string, paramCount: number}>>,
}>) => {
return `${enums}${structs}
template <typename T>
class JSI_EXPORT ${hasteModuleName}CxxSpec : public TurboModule {
public:
static constexpr std::string_view kModuleName = "${moduleName}";
protected:
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{${hasteModuleName}CxxSpec::kModuleName}, jsInvoker) {
${methods
.map(({methodName, paramCount}) => {
return ` methodMap_["${methodName}"] = MethodMetadata {.argCount = ${paramCount}, .invoker = __${methodName}};`;
})
.join(
'\n',
)}${moduleEventEmitters.length > 0 ? '\n' : ''}${moduleEventEmitters.map(e => e.registerEventEmitter).join('\n')}
}
${moduleEventEmitters.map(e => e.emitFunction).join('\n')}
private:
${moduleFunctions.join('\n\n')}
};`;
};
const FileTemplate = ({
modules,
}: $ReadOnly<{
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
*/
#pragma once
#include <ReactCommon/TurboModule.h>
#include <react/bridging/Bridging.h>
namespace facebook::react {
${modules.join('\n\n')}
} // namespace facebook::react
`;
};
function translatePrimitiveJSTypeToCpp(
moduleName: string,
parentObjectAliasName: ?string,
nullableTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
optional: boolean,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
) {
const [typeAnnotation, nullable] = unwrapNullable<NativeModuleTypeAnnotation>(
nullableTypeAnnotation,
);
const isRecursiveType = isDirectRecursiveMember(
parentObjectAliasName,
nullableTypeAnnotation,
);
const isRequired = (!optional && !nullable) || isRecursiveType;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('int', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('bool', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'GenericObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'ArrayTypeAnnotation':
return wrapOptional('jsi::Array', isRequired);
case 'FunctionTypeAnnotation':
return wrapOptional('jsi::Function', isRequired);
case 'PromiseTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'MixedTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
default:
(realTypeAnnotation.type: empty);
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function createStructsString(
hasteModuleName: string,
aliasMap: NativeModuleAliasMap,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): string {
const getCppType = (
parentObjectAlias: string,
v: NamedShape<Nullable<NativeModuleBaseTypeAnnotation>>,
) =>
translatePrimitiveJSTypeToCpp(
hasteModuleName,
parentObjectAlias,
v.typeAnnotation,
false,
typeName => `Unsupported type for param "${v.name}". Found: ${typeName}`,
resolveAlias,
enumMap,
);
return Object.keys(aliasMap)
.map(alias => {
const value = aliasMap[alias];
if (value.properties.length === 0) {
return '';
}
const structName = `${hasteModuleName}${alias}`;
const templateParameter = value.properties.filter(
v =>
!isDirectRecursiveMember(alias, v.typeAnnotation) &&
!isArrayRecursiveMember(alias, v.typeAnnotation),
);
const templateParameterWithTypename = templateParameter
.map((v, i) => `typename P${i}`)
.join(', ');
const templateParameterWithoutTypename = templateParameter
.map((v, i) => `P${i}`)
.join(', ');
let i = -1;
const templateMemberTypes = value.properties.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return `std::unique_ptr<${structName}<${templateParameterWithoutTypename}>> ${v.name}`;
} else if (isArrayRecursiveMember(alias, v.typeAnnotation)) {
const [nullable] = unwrapNullable<NativeModuleTypeAnnotation>(
v.typeAnnotation,
);
return (
(nullable
? `std::optional<std::vector<${structName}<${templateParameterWithoutTypename}>>>`
: `std::vector<${structName}<${templateParameterWithoutTypename}>>`) +
` ${v.name}`
);
} else {
i++;
return `P${i} ${v.name}`;
}
});
const debugParameterConversion = value.properties
.map(
v => ` static ${getCppType(alias, v)} ${
v.name
}ToJs(jsi::Runtime &rt, decltype(types.${v.name}) value) {
return bridging::toJs(rt, value);
}`,
)
.join('\n');
return `
#pragma mark - ${structName}
template <${templateParameterWithTypename}>
struct ${structName} {
${templateMemberTypes.map(v => ' ' + v).join(';\n')};
bool operator==(const ${structName} &other) const {
return ${value.properties
.map(v => `${v.name} == other.${v.name}`)
.join(' && ')};
}
};
template <typename T>
struct ${structName}Bridging {
static T types;
static T fromJs(
jsi::Runtime &rt,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
T result{
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` value.hasProperty(rt, "${v.name}") ? std::make_unique<T>(bridging::fromJs<T>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)) : nullptr`;
} else {
return ` bridging::fromJs<decltype(types.${v.name})>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`;
}
})
.join(',\n')}};
return result;
}
#ifdef DEBUG
${debugParameterConversion}
#endif
static jsi::Object toJs(
jsi::Runtime &rt,
const T &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
auto result = facebook::jsi::Object(rt);
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, *value.${v.name}, jsInvoker));
}`;
} else if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}, jsInvoker));`;
}
})
.join('\n')}
return result;
}
};
`;
})
.join('\n');
}
type NativeEnumMemberValueType = 'std::string' | 'int32_t';
const EnumTemplate = ({
enumName,
values,
fromCases,
toCases,
nativeEnumMemberType,
}: {
enumName: string,
values: string,
fromCases: string,
toCases: string,
nativeEnumMemberType: NativeEnumMemberValueType,
}) => {
const [fromValue, fromValueConversion, toValue] =
nativeEnumMemberType === 'std::string'
? [
'const jsi::String &rawValue',
'std::string value = rawValue.utf8(rt);',
'jsi::String',
]
: [
'const jsi::Value &rawValue',
'double value = (double)rawValue.asNumber();',
'jsi::Value',
];
return `
#pragma mark - ${enumName}
enum class ${enumName} { ${values} };
template <>
struct Bridging<${enumName}> {
static ${enumName} fromJs(jsi::Runtime &rt, ${fromValue}) {
${fromValueConversion}
${fromCases}
}
static ${toValue} toJs(jsi::Runtime &rt, ${enumName} value) {
${toCases}
}
};`;
};
function getMemberValueAppearance(member: NativeModuleEnumMember['value']) {
if (member.type === 'StringLiteralTypeAnnotation') {
return `"${member.value}"`;
} else {
return member.value;
}
}
function generateEnum(
hasteModuleName: string,
origEnumName: string,
members: $ReadOnlyArray<NativeModuleEnumMember>,
memberType: NativeModuleEnumMemberType,
): string {
const enumName = getEnumName(hasteModuleName, origEnumName);
const nativeEnumMemberType: NativeEnumMemberValueType =
memberType === 'StringTypeAnnotation' ? 'std::string' : 'int32_t';
const fromCases =
members
.map(
member => `if (value == ${getMemberValueAppearance(member.value)}) {
return ${enumName}::${toSafeCppString(member.name)};
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for value in ${enumName}");
}`;
const toCases =
members
.map(
member => `if (value == ${enumName}::${toSafeCppString(member.name)}) {
return bridging::toJs(rt, ${getMemberValueAppearance(member.value)});
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for enum value in ${enumName}");
}`;
return EnumTemplate({
enumName,
values: members.map(member => toSafeCppString(member.name)).join(', '),
fromCases,
toCases,
nativeEnumMemberType,
});
}
function createEnums(
hasteModuleName: string,
enumMap: NativeModuleEnumMap,
resolveAlias: AliasResolver,
): string {
return Object.entries(enumMap)
.map(([enumName, enumNode]) => {
return generateEnum(
hasteModuleName,
enumName,
enumNode.members,
enumNode.memberType,
);
})
.filter(Boolean)
.join('\n');
}
function translateFunctionToCpp(
hasteModuleName: string,
prop: NativeModulePropertyShape,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
args: Array<string>,
returnTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
): string {
const [propTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(prop.typeAnnotation);
const isNullable = returnTypeAnnotation.type === 'NullableTypeAnnotation';
const isVoid = returnTypeAnnotation.type === 'VoidTypeAnnotation';
const paramTypes = propTypeAnnotation.params.map(param => {
const translatedParam = translatePrimitiveJSTypeToCpp(
hasteModuleName,
null,
param.typeAnnotation,
param.optional,
typeName =>
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
return `${translatedParam} ${param.name}`;
});
paramTypes.unshift('jsi::Runtime &rt');
const returnType = translatePrimitiveJSTypeToCpp(
hasteModuleName,
null,
propTypeAnnotation.returnTypeAnnotation,
false,
typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
let methodCallArgs = [...args].join(',\n ');
if (methodCallArgs.length > 0) {
methodCallArgs = `,\n ${methodCallArgs}`;
}
return ` static jsi::Value __${prop.name}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* ${args.length > 0 ? 'args' : '/*args*/'}, size_t ${args.length > 0 ? 'count' : '/*count*/'}) {
static_assert(
bridging::getParameterCount(&T::${prop.name}) == ${paramTypes.length},
"Expected ${prop.name}(...) to have ${paramTypes.length} parameters");
${!isVoid ? (!isNullable ? 'return ' : 'auto result = ') : ''}bridging::callFromJs<${returnType}>(rt, &T::${prop.name}, static_cast<${hasteModuleName}CxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule)${methodCallArgs});${!isVoid ? (!isNullable ? '' : 'return result ? jsi::Value(std::move(*result)) : jsi::Value::null();') : 'return jsi::Value::undefined();'}\n }`;
}
type EventEmitterCpp = {
isVoidTypeAnnotation: boolean,
templateName: string,
registerEventEmitter: string,
emitFunction: string,
};
function translateEventEmitterToCpp(
moduleName: string,
eventEmitter: NativeModuleEventEmitterShape,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): EventEmitterCpp {
const isVoidTypeAnnotation =
eventEmitter.typeAnnotation.typeAnnotation.type === 'VoidTypeAnnotation';
const templateName = `${toPascalCase(eventEmitter.name)}Type`;
const jsiType = translatePrimitiveJSTypeToCpp(
moduleName,
null,
eventEmitter.typeAnnotation.typeAnnotation,
false,
typeName =>
`Unsupported type for eventEmitter "${eventEmitter.name}" in ${moduleName}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
const isArray = jsiType === 'jsi::Array';
return {
isVoidTypeAnnotation: isVoidTypeAnnotation,
templateName: isVoidTypeAnnotation ? `/*${templateName}*/` : templateName,
registerEventEmitter: ` eventEmitterMap_["${
eventEmitter.name
}"] = std::make_shared<AsyncEventEmitter<${
isVoidTypeAnnotation ? '' : 'jsi::Value'
}>>();`,
emitFunction: `
${
isVoidTypeAnnotation ? '' : `template <typename ${templateName}> `
}void emit${toPascalCase(eventEmitter.name)}(${
isVoidTypeAnnotation
? ''
: `${isArray ? `std::vector<${templateName}>` : templateName} value`
}) {${
isVoidTypeAnnotation
? ''
: `
static_assert(bridging::supportsFromJs<${
isArray ? `std::vector<${templateName}>` : templateName
}, ${jsiType}>, "value cannnot be converted to ${jsiType}");`
}
static_cast<AsyncEventEmitter<${
isVoidTypeAnnotation ? '' : 'jsi::Value'
}>&>(*eventEmitterMap_["${eventEmitter.name}"]).emit(${
isVoidTypeAnnotation
? ''
: `[jsInvoker = jsInvoker_, eventValue = value](jsi::Runtime& rt) -> jsi::Value {
return bridging::toJs(rt, eventValue, jsInvoker);
}`
});
}`,
};
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
const nativeModule = nativeModules[hasteModuleName];
const {
aliasMap,
enumMap,
spec: {methods},
spec,
moduleName,
} = nativeModule;
const resolveAlias = createAliasResolver(aliasMap);
const structs = createStructsString(
hasteModuleName,
aliasMap,
resolveAlias,
enumMap,
);
const enums = createEnums(hasteModuleName, enumMap, resolveAlias);
return [
ModuleSpecClassDeclarationTemplate({
hasteModuleName,
moduleName,
structs,
enums,
moduleEventEmitters: spec.eventEmitters.map(eventEmitter =>
translateEventEmitterToCpp(
moduleName,
eventEmitter,
resolveAlias,
enumMap,
),
),
moduleFunctions: spec.methods.map(property => {
const [propertyTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
property.typeAnnotation,
);
return translateFunctionToCpp(
hasteModuleName,
property,
resolveAlias,
enumMap,
propertyTypeAnnotation.params.map((p, i) =>
serializeArg(moduleName, p, i, resolveAlias, enumMap),
),
propertyTypeAnnotation.returnTypeAnnotation,
);
}),
methods: methods.map(
({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => {
const [{params}] = unwrapNullable(nullableTypeAnnotation);
return {
methodName: propertyName,
paramCount: params.length,
};
},
),
}),
];
});
const fileName = `${libraryName}JSI.h`;
const replacedTemplate = FileTemplate({modules});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,542 @@
/**
* 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
*/
'use strict';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Java');
const {toPascalCase} = require('../Utils');
const {createAliasResolver, getModules} = require('./Utils');
function FileTemplate(config) {
const {packageName, className, jsName, eventEmitters, methods, imports} =
config;
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: GenerateModuleJavaSpec.js
*
* ${'@'}nolint
*/
package ${packageName};
${imports}
public abstract class ${className} extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "${jsName}";
public ${className}(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
${eventEmitters}${eventEmitters.length > 0 ? '\n\n' : ''}${methods}
}
`;
}
function EventEmitterTemplate(eventEmitter, imports) {
return ` protected final void emit${toPascalCase(eventEmitter.name)}(${eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation' ? `${translateEventEmitterTypeToJavaType(eventEmitter, imports)} value` : ''}) {
mEventEmitterCallback.invoke("${eventEmitter.name}"${eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation' ? ', value' : ''});
}`;
}
function MethodTemplate(config) {
const {
abstract,
methodBody,
methodJavaAnnotation,
methodName,
translatedReturnType,
traversedArgs,
} = config;
const methodQualifier = abstract ? 'abstract ' : '';
const methodClosing = abstract
? ';'
: methodBody != null && methodBody.length > 0
? ` { ${methodBody} }`
: ' {}';
return ` ${methodJavaAnnotation}
public ${methodQualifier}${translatedReturnType} ${methodName}(${traversedArgs.join(', ')})${methodClosing}`;
}
function translateEventEmitterTypeToJavaType(eventEmitter, imports) {
const type = eventEmitter.typeAnnotation.typeAnnotation.type;
switch (type) {
case 'StringTypeAnnotation':
return 'String';
case 'StringLiteralTypeAnnotation':
return 'String';
case 'StringLiteralUnionTypeAnnotation':
return 'String';
case 'NumberTypeAnnotation':
case 'NumberLiteralTypeAnnotation':
case 'FloatTypeAnnotation':
case 'DoubleTypeAnnotation':
case 'Int32TypeAnnotation':
return 'double';
case 'BooleanTypeAnnotation':
return 'boolean';
case 'GenericObjectTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'TypeAliasTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return 'ReadableMap';
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return 'ReadableArray';
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'VoidTypeAnnotation':
// TODO: Add support for these types
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
default:
type;
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
}
}
function translateFunctionParamToJavaType(
param,
createErrorMessage,
resolveAlias,
imports,
) {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
if (!isRequired) {
imports.add('javax.annotation.Nullable');
}
// FIXME: support class alias for args
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'GenericObjectTypeAnnotation':
// Treat this the same as ObjectTypeAnnotation for now.
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return wrapOptional('ReadableArray', isRequired);
case 'FunctionTypeAnnotation':
imports.add('com.facebook.react.bridge.Callback');
return wrapOptional('Callback', isRequired);
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function translateFunctionReturnTypeToJavaType(
nullableReturnTypeAnnotation,
createErrorMessage,
resolveAlias,
imports,
) {
const [returnTypeAnnotation, nullable] = unwrapNullable(
nullableReturnTypeAnnotation,
);
if (nullable) {
imports.add('javax.annotation.Nullable');
}
const isRequired = !nullable;
// FIXME: support class alias for args
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'PromiseTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'GenericObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableArray');
return wrapOptional('WritableArray', isRequired);
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function getFalsyReturnStatementFromReturnType(
nullableReturnTypeAnnotation,
createErrorMessage,
resolveAlias,
) {
const [returnTypeAnnotation, nullable] = unwrapNullable(
nullableReturnTypeAnnotation,
);
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'return 0.0;';
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return '';
case 'PromiseTypeAnnotation':
return '';
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'NumberLiteralTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'FloatTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'DoubleTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'Int32TypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'BooleanTypeAnnotation':
return nullable ? 'return null;' : 'return false;';
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'StringLiteralTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'StringLiteralUnionTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'GenericObjectTypeAnnotation':
return 'return null;';
case 'ArrayTypeAnnotation':
return 'return null;';
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
// Build special-cased runtime check for getConstants().
function buildGetConstantsMethod(method, imports, resolveAlias) {
const [methodTypeAnnotation] = unwrapNullable(method.typeAnnotation);
let returnTypeAnnotation = methodTypeAnnotation.returnTypeAnnotation;
if (returnTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
// The return type is an alias, resolve it to get the expected undelying object literal type
returnTypeAnnotation = resolveAlias(returnTypeAnnotation.name);
}
if (returnTypeAnnotation.type === 'ObjectTypeAnnotation') {
const requiredProps = [];
const optionalProps = [];
const rawProperties = returnTypeAnnotation.properties || [];
rawProperties.forEach(p => {
if (p.optional || p.typeAnnotation.type === 'NullableTypeAnnotation') {
optionalProps.push(p.name);
} else {
requiredProps.push(p.name);
}
});
if (requiredProps.length === 0 && optionalProps.length === 0) {
// Nothing to validate during runtime.
return '';
}
imports.add('com.facebook.react.common.build.ReactBuildConfig');
imports.add('java.util.Arrays');
imports.add('java.util.HashSet');
imports.add('java.util.Map');
imports.add('java.util.Set');
imports.add('javax.annotation.Nullable');
const requiredPropsFragment =
requiredProps.length > 0
? `Arrays.asList(
${requiredProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
const optionalPropsFragment =
optionalProps.length > 0
? `Arrays.asList(
${optionalProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
return ` protected abstract Map<String, Object> getTypedExportedConstants();
@Override
@DoNotStrip
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> constants = getTypedExportedConstants();
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
Set<String> obligatoryFlowConstants = new HashSet<>(${requiredPropsFragment});
Set<String> optionalFlowConstants = new HashSet<>(${optionalPropsFragment});
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
undeclaredConstants.removeAll(obligatoryFlowConstants);
undeclaredConstants.removeAll(optionalFlowConstants);
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
}
undeclaredConstants = obligatoryFlowConstants;
undeclaredConstants.removeAll(constants.keySet());
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
}
}
return constants;
}`;
}
return '';
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const files = new Map();
const nativeModules = getModules(schema);
const normalizedPackageName =
packageName == null ? 'com.facebook.fbreact.specs' : packageName;
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
Object.keys(nativeModules).forEach(hasteModuleName => {
const {aliasMap, excludedPlatforms, moduleName, spec} =
nativeModules[hasteModuleName];
if (excludedPlatforms != null && excludedPlatforms.includes('android')) {
return;
}
const resolveAlias = createAliasResolver(aliasMap);
const className = `${hasteModuleName}Spec`;
const imports = new Set([
// Always required.
'com.facebook.react.bridge.ReactApplicationContext',
'com.facebook.react.bridge.ReactContextBaseJavaModule',
'com.facebook.react.bridge.ReactMethod',
'com.facebook.react.turbomodule.core.interfaces.TurboModule',
'com.facebook.proguard.annotations.DoNotStrip',
'javax.annotation.Nonnull',
]);
const methods = spec.methods.map(method => {
if (method.name === 'getConstants') {
return buildGetConstantsMethod(method, imports, resolveAlias);
}
const [methodTypeAnnotation] = unwrapNullable(method.typeAnnotation);
// Handle return type
const translatedReturnType = translateFunctionReturnTypeToJavaType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Unsupported return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
const returningPromise =
methodTypeAnnotation.returnTypeAnnotation.type ===
'PromiseTypeAnnotation';
const isSyncMethod =
methodTypeAnnotation.returnTypeAnnotation.type !==
'VoidTypeAnnotation' && !returningPromise;
// Handle method args
const traversedArgs = methodTypeAnnotation.params.map(param => {
const translatedParam = translateFunctionParamToJavaType(
param,
typeName =>
`Unsupported type for param "${param.name}" in ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
return `${translatedParam} ${param.name}`;
});
if (returningPromise) {
// Promise return type requires an extra arg at the end.
imports.add('com.facebook.react.bridge.Promise');
traversedArgs.push('Promise promise');
}
const methodJavaAnnotation = `@ReactMethod${isSyncMethod ? '(isBlockingSynchronousMethod = true)' : ''}\n @DoNotStrip`;
const methodBody = method.optional
? getFalsyReturnStatementFromReturnType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Cannot build falsy return statement for return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
)
: null;
return MethodTemplate({
abstract: !method.optional,
methodBody,
methodJavaAnnotation,
methodName: method.name,
translatedReturnType,
traversedArgs,
});
});
files.set(
`${outputDir}/${className}.java`,
FileTemplate({
packageName: normalizedPackageName,
className,
jsName: moduleName,
eventEmitters: spec.eventEmitters
.map(eventEmitter => EventEmitterTemplate(eventEmitter, imports))
.join('\n\n'),
methods: methods.filter(Boolean).join('\n\n'),
imports: Array.from(imports)
.sort()
.map(p => `import ${p};`)
.join('\n'),
}),
);
});
return files;
},
};

View File

@@ -0,0 +1,633 @@
/**
* 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 {
NamedShape,
NativeModuleEventEmitterShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleReturnTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Java');
const {toPascalCase} = require('../Utils');
const {createAliasResolver, getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
function FileTemplate(
config: $ReadOnly<{
packageName: string,
className: string,
jsName: string,
eventEmitters: string,
methods: string,
imports: string,
}>,
): string {
const {packageName, className, jsName, eventEmitters, methods, imports} =
config;
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: GenerateModuleJavaSpec.js
*
* ${'@'}nolint
*/
package ${packageName};
${imports}
public abstract class ${className} extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "${jsName}";
public ${className}(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
${eventEmitters}${eventEmitters.length > 0 ? '\n\n' : ''}${methods}
}
`;
}
function EventEmitterTemplate(
eventEmitter: NativeModuleEventEmitterShape,
imports: Set<string>,
): string {
return ` protected final void emit${toPascalCase(eventEmitter.name)}(${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? `${translateEventEmitterTypeToJavaType(eventEmitter, imports)} value`
: ''
}) {
mEventEmitterCallback.invoke("${eventEmitter.name}"${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? ', value'
: ''
});
}`;
}
function MethodTemplate(
config: $ReadOnly<{
abstract: boolean,
methodBody: ?string,
methodJavaAnnotation: string,
methodName: string,
translatedReturnType: string,
traversedArgs: Array<string>,
}>,
): string {
const {
abstract,
methodBody,
methodJavaAnnotation,
methodName,
translatedReturnType,
traversedArgs,
} = config;
const methodQualifier = abstract ? 'abstract ' : '';
const methodClosing = abstract
? ';'
: methodBody != null && methodBody.length > 0
? ` { ${methodBody} }`
: ' {}';
return ` ${methodJavaAnnotation}
public ${methodQualifier}${translatedReturnType} ${methodName}(${traversedArgs.join(
', ',
)})${methodClosing}`;
}
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function translateEventEmitterTypeToJavaType(
eventEmitter: NativeModuleEventEmitterShape,
imports: Set<string>,
): string {
const type = eventEmitter.typeAnnotation.typeAnnotation.type;
switch (type) {
case 'StringTypeAnnotation':
return 'String';
case 'StringLiteralTypeAnnotation':
return 'String';
case 'StringLiteralUnionTypeAnnotation':
return 'String';
case 'NumberTypeAnnotation':
case 'NumberLiteralTypeAnnotation':
case 'FloatTypeAnnotation':
case 'DoubleTypeAnnotation':
case 'Int32TypeAnnotation':
return 'double';
case 'BooleanTypeAnnotation':
return 'boolean';
case 'GenericObjectTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'TypeAliasTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return 'ReadableMap';
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return 'ReadableArray';
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'VoidTypeAnnotation':
// TODO: Add support for these types
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
default:
(type: empty);
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
}
}
function translateFunctionParamToJavaType(
param: Param,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
imports: Set<string>,
): string {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
if (!isRequired) {
imports.add('javax.annotation.Nullable');
}
// FIXME: support class alias for args
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'GenericObjectTypeAnnotation':
// Treat this the same as ObjectTypeAnnotation for now.
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return wrapOptional('ReadableArray', isRequired);
case 'FunctionTypeAnnotation':
imports.add('com.facebook.react.bridge.Callback');
return wrapOptional('Callback', isRequired);
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function translateFunctionReturnTypeToJavaType(
nullableReturnTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
imports: Set<string>,
): string {
const [returnTypeAnnotation, nullable] =
unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableReturnTypeAnnotation,
);
if (nullable) {
imports.add('javax.annotation.Nullable');
}
const isRequired = !nullable;
// FIXME: support class alias for args
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'PromiseTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'GenericObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableArray');
return wrapOptional('WritableArray', isRequired);
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function getFalsyReturnStatementFromReturnType(
nullableReturnTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
): string {
const [returnTypeAnnotation, nullable] =
unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableReturnTypeAnnotation,
);
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'return 0.0;';
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return '';
case 'PromiseTypeAnnotation':
return '';
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'NumberLiteralTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'FloatTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'DoubleTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'Int32TypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'BooleanTypeAnnotation':
return nullable ? 'return null;' : 'return false;';
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'StringLiteralTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'StringLiteralUnionTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'GenericObjectTypeAnnotation':
return 'return null;';
case 'ArrayTypeAnnotation':
return 'return null;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
// Build special-cased runtime check for getConstants().
function buildGetConstantsMethod(
method: NativeModulePropertyShape,
imports: Set<string>,
resolveAlias: AliasResolver,
): string {
const [methodTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(method.typeAnnotation);
let returnTypeAnnotation = methodTypeAnnotation.returnTypeAnnotation;
if (returnTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
// The return type is an alias, resolve it to get the expected undelying object literal type
returnTypeAnnotation = resolveAlias(returnTypeAnnotation.name);
}
if (returnTypeAnnotation.type === 'ObjectTypeAnnotation') {
const requiredProps = [];
const optionalProps = [];
const rawProperties = returnTypeAnnotation.properties || [];
rawProperties.forEach(p => {
if (p.optional || p.typeAnnotation.type === 'NullableTypeAnnotation') {
optionalProps.push(p.name);
} else {
requiredProps.push(p.name);
}
});
if (requiredProps.length === 0 && optionalProps.length === 0) {
// Nothing to validate during runtime.
return '';
}
imports.add('com.facebook.react.common.build.ReactBuildConfig');
imports.add('java.util.Arrays');
imports.add('java.util.HashSet');
imports.add('java.util.Map');
imports.add('java.util.Set');
imports.add('javax.annotation.Nullable');
const requiredPropsFragment =
requiredProps.length > 0
? `Arrays.asList(
${requiredProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
const optionalPropsFragment =
optionalProps.length > 0
? `Arrays.asList(
${optionalProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
return ` protected abstract Map<String, Object> getTypedExportedConstants();
@Override
@DoNotStrip
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> constants = getTypedExportedConstants();
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
Set<String> obligatoryFlowConstants = new HashSet<>(${requiredPropsFragment});
Set<String> optionalFlowConstants = new HashSet<>(${optionalPropsFragment});
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
undeclaredConstants.removeAll(obligatoryFlowConstants);
undeclaredConstants.removeAll(optionalFlowConstants);
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
}
undeclaredConstants = obligatoryFlowConstants;
undeclaredConstants.removeAll(constants.keySet());
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
}
}
return constants;
}`;
}
return '';
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const files = new Map<string, string>();
const nativeModules = getModules(schema);
const normalizedPackageName =
packageName == null ? 'com.facebook.fbreact.specs' : packageName;
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
Object.keys(nativeModules).forEach(hasteModuleName => {
const {aliasMap, excludedPlatforms, moduleName, spec} =
nativeModules[hasteModuleName];
if (excludedPlatforms != null && excludedPlatforms.includes('android')) {
return;
}
const resolveAlias = createAliasResolver(aliasMap);
const className = `${hasteModuleName}Spec`;
const imports: Set<string> = new Set([
// Always required.
'com.facebook.react.bridge.ReactApplicationContext',
'com.facebook.react.bridge.ReactContextBaseJavaModule',
'com.facebook.react.bridge.ReactMethod',
'com.facebook.react.turbomodule.core.interfaces.TurboModule',
'com.facebook.proguard.annotations.DoNotStrip',
'javax.annotation.Nonnull',
]);
const methods = spec.methods.map(method => {
if (method.name === 'getConstants') {
return buildGetConstantsMethod(method, imports, resolveAlias);
}
const [methodTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
method.typeAnnotation,
);
// Handle return type
const translatedReturnType = translateFunctionReturnTypeToJavaType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Unsupported return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
const returningPromise =
methodTypeAnnotation.returnTypeAnnotation.type ===
'PromiseTypeAnnotation';
const isSyncMethod =
methodTypeAnnotation.returnTypeAnnotation.type !==
'VoidTypeAnnotation' && !returningPromise;
// Handle method args
const traversedArgs = methodTypeAnnotation.params.map(param => {
const translatedParam = translateFunctionParamToJavaType(
param,
typeName =>
`Unsupported type for param "${param.name}" in ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
return `${translatedParam} ${param.name}`;
});
if (returningPromise) {
// Promise return type requires an extra arg at the end.
imports.add('com.facebook.react.bridge.Promise');
traversedArgs.push('Promise promise');
}
const methodJavaAnnotation = `@ReactMethod${
isSyncMethod ? '(isBlockingSynchronousMethod = true)' : ''
}\n @DoNotStrip`;
const methodBody = method.optional
? getFalsyReturnStatementFromReturnType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Cannot build falsy return statement for return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
)
: null;
return MethodTemplate({
abstract: !method.optional,
methodBody,
methodJavaAnnotation,
methodName: method.name,
translatedReturnType,
traversedArgs,
});
});
files.set(
`${outputDir}/${className}.java`,
FileTemplate({
packageName: normalizedPackageName,
className,
jsName: moduleName,
eventEmitters: spec.eventEmitters
.map(eventEmitter => EventEmitterTemplate(eventEmitter, imports))
.join('\n\n'),
methods: methods.filter(Boolean).join('\n\n'),
imports: Array.from(imports)
.sort()
.map(p => `import ${p};`)
.join('\n'),
}),
);
});
return files;
},
};

View File

@@ -0,0 +1,456 @@
/**
* 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
*/
'use strict';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {createAliasResolver, getModules} = require('./Utils');
const HostFunctionTemplate = ({
hasteModuleName,
propertyName,
jniSignature,
jsReturnType,
}) => {
return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
}`;
};
const ModuleClassConstructorTemplate = ({
hasteModuleName,
eventEmitters,
methods,
}) => {
return `
${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params)
: JavaTurboModule(params) {
${methods
.map(({propertyName, argCount}) => {
return ` methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
})
.join('\n')}${
eventEmitters.length > 0
? eventEmitters
.map(eventEmitter => {
return `
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<folly::dynamic>>();`;
})
.join('')
: ''
}${
eventEmitters.length > 0
? `
configureEventEmitterCallback();`
: ''
}
}`.trim();
};
const ModuleLookupTemplate = ({moduleName, hasteModuleName}) => {
return ` if (moduleName == "${moduleName}") {
return std::make_shared<${hasteModuleName}SpecJSI>(params);
}`;
};
const FileTemplate = ({libraryName, include, modules, moduleLookups}) => {
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: GenerateModuleJniCpp.js
*/
#include ${include}
namespace facebook::react {
${modules}
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
${moduleLookups.map(ModuleLookupTemplate).join('\n')}
return nullptr;
}
} // namespace facebook::react
`;
};
function translateReturnTypeToKind(nullableTypeAnnotation, resolveAlias) {
const [typeAnnotation] = unwrapNullable(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'NumberKind';
default:
realTypeAnnotation.name;
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'VoidKind';
case 'StringTypeAnnotation':
return 'StringKind';
case 'StringLiteralTypeAnnotation':
return 'StringKind';
case 'StringLiteralUnionTypeAnnotation':
return 'StringKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unknown enum prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'NumberLiteralTypeAnnotation':
return 'NumberKind';
case 'DoubleTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
return 'NumberKind';
case 'Int32TypeAnnotation':
return 'NumberKind';
case 'PromiseTypeAnnotation':
return 'PromiseKind';
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'ArrayTypeAnnotation':
return 'ArrayKind';
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateParamTypeToJniType(param, resolveAlias) {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
default:
realTypeAnnotation.name;
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralUnionTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return !isRequired ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union prop value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'NumberLiteralTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableArray;';
case 'FunctionTypeAnnotation':
return 'Lcom/facebook/react/bridge/Callback;';
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateReturnTypeToJniType(nullableTypeAnnotation, resolveAlias) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return nullable ? 'Ljava/lang/Double;' : 'D';
default:
realTypeAnnotation.name;
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'V';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralUnionTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return nullable ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union member type, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'NumberLiteralTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'PromiseTypeAnnotation':
return 'Lcom/facebook/react/bridge/Promise;';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableArray;';
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateMethodTypeToJniSignature(property, resolveAlias) {
const {name, typeAnnotation} = property;
let [{returnTypeAnnotation, params}] = unwrapNullable(typeAnnotation);
params = [...params];
let processedReturnTypeAnnotation = returnTypeAnnotation;
const isPromiseReturn = returnTypeAnnotation.type === 'PromiseTypeAnnotation';
if (isPromiseReturn) {
processedReturnTypeAnnotation = {
type: 'VoidTypeAnnotation',
};
}
const argsSignatureParts = params.map(t => {
return translateParamTypeToJniType(t, resolveAlias);
});
if (isPromiseReturn) {
// Additional promise arg for this case.
argsSignatureParts.push(
translateReturnTypeToJniType(returnTypeAnnotation, resolveAlias),
);
}
const argsSignature = argsSignatureParts.join('');
const returnSignature =
name === 'getConstants'
? 'Ljava/util/Map;'
: translateReturnTypeToJniType(
processedReturnTypeAnnotation,
resolveAlias,
);
return `(${argsSignature})${returnSignature}`;
}
function translateMethodForImplementation(
hasteModuleName,
property,
resolveAlias,
) {
const [propertyTypeAnnotation] = unwrapNullable(property.typeAnnotation);
const {returnTypeAnnotation} = propertyTypeAnnotation;
if (
property.name === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties.length === 0
) {
return '';
}
return HostFunctionTemplate({
hasteModuleName,
propertyName: property.name,
jniSignature: translateMethodTypeToJniSignature(property, resolveAlias),
jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias),
});
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName => {
const {
aliasMap,
spec: {eventEmitters, methods},
} = nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliasMap);
const translatedMethods = methods
.map(property =>
translateMethodForImplementation(
hasteModuleName,
property,
resolveAlias,
),
)
.join('\n\n');
return (
translatedMethods +
'\n\n' +
ModuleClassConstructorTemplate({
hasteModuleName,
eventEmitters,
methods: methods
.map(({name: propertyName, typeAnnotation}) => {
const [{returnTypeAnnotation, params}] =
unwrapNullable(typeAnnotation);
if (
propertyName === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties &&
returnTypeAnnotation.properties.length === 0
) {
return null;
}
return {
propertyName,
argCount: params.length,
};
})
.filter(Boolean),
})
);
})
.join('\n');
const moduleLookups = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort((a, b) => {
const nameA = nativeModules[a].moduleName;
const nameB = nativeModules[b].moduleName;
if (nameA < nameB) {
return -1;
} else if (nameA > nameB) {
return 1;
}
return 0;
})
.map(hasteModuleName => ({
moduleName: nativeModules[hasteModuleName].moduleName,
hasteModuleName,
}));
const fileName = `${libraryName}-generated.cpp`;
const replacedTemplate = FileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
moduleLookups,
include: `"${libraryName}.h"`,
});
return new Map([[`jni/${fileName}`, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,558 @@
/**
* 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 {
NamedShape,
NativeModuleEventEmitterShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleReturnTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {createAliasResolver, getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
type JSReturnType =
| 'VoidKind'
| 'StringKind'
| 'BooleanKind'
| 'NumberKind'
| 'PromiseKind'
| 'ObjectKind'
| 'ArrayKind';
const HostFunctionTemplate = ({
hasteModuleName,
propertyName,
jniSignature,
jsReturnType,
}: $ReadOnly<{
hasteModuleName: string,
propertyName: string,
jniSignature: string,
jsReturnType: JSReturnType,
}>) => {
return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
}`;
};
const ModuleClassConstructorTemplate = ({
hasteModuleName,
eventEmitters,
methods,
}: $ReadOnly<{
hasteModuleName: string,
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
methods: $ReadOnlyArray<{
propertyName: string,
argCount: number,
}>,
}>) => {
return `
${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params)
: JavaTurboModule(params) {
${methods
.map(({propertyName, argCount}) => {
return ` methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
})
.join('\n')}${
eventEmitters.length > 0
? eventEmitters
.map(eventEmitter => {
return `
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<folly::dynamic>>();`;
})
.join('')
: ''
}${
eventEmitters.length > 0
? `
configureEventEmitterCallback();`
: ''
}
}`.trim();
};
const ModuleLookupTemplate = ({
moduleName,
hasteModuleName,
}: $ReadOnly<{moduleName: string, hasteModuleName: string}>) => {
return ` if (moduleName == "${moduleName}") {
return std::make_shared<${hasteModuleName}SpecJSI>(params);
}`;
};
const FileTemplate = ({
libraryName,
include,
modules,
moduleLookups,
}: $ReadOnly<{
libraryName: string,
include: string,
modules: string,
moduleLookups: $ReadOnlyArray<{
hasteModuleName: string,
moduleName: 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: GenerateModuleJniCpp.js
*/
#include ${include}
namespace facebook::react {
${modules}
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
${moduleLookups.map(ModuleLookupTemplate).join('\n')}
return nullptr;
}
} // namespace facebook::react
`;
};
function translateReturnTypeToKind(
nullableTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
resolveAlias: AliasResolver,
): JSReturnType {
const [typeAnnotation] = unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableTypeAnnotation,
);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'NumberKind';
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'VoidKind';
case 'StringTypeAnnotation':
return 'StringKind';
case 'StringLiteralTypeAnnotation':
return 'StringKind';
case 'StringLiteralUnionTypeAnnotation':
return 'StringKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unknown enum prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'NumberLiteralTypeAnnotation':
return 'NumberKind';
case 'DoubleTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
return 'NumberKind';
case 'Int32TypeAnnotation':
return 'NumberKind';
case 'PromiseTypeAnnotation':
return 'PromiseKind';
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'ArrayTypeAnnotation':
return 'ArrayKind';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unknown prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
}
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function translateParamTypeToJniType(
param: Param,
resolveAlias: AliasResolver,
): string {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralUnionTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return !isRequired ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union prop value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'NumberLiteralTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableArray;';
case 'FunctionTypeAnnotation':
return 'Lcom/facebook/react/bridge/Callback;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unknown prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateReturnTypeToJniType(
nullableTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
resolveAlias: AliasResolver,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return nullable ? 'Ljava/lang/Double;' : 'D';
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'V';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralUnionTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return nullable ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union member type, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'NumberLiteralTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'PromiseTypeAnnotation':
return 'Lcom/facebook/react/bridge/Promise;';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableArray;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unknown prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateMethodTypeToJniSignature(
property: NativeModulePropertyShape,
resolveAlias: AliasResolver,
): string {
const {name, typeAnnotation} = property;
let [{returnTypeAnnotation, params}] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(typeAnnotation);
params = [...params];
let processedReturnTypeAnnotation = returnTypeAnnotation;
const isPromiseReturn = returnTypeAnnotation.type === 'PromiseTypeAnnotation';
if (isPromiseReturn) {
processedReturnTypeAnnotation = {
type: 'VoidTypeAnnotation',
};
}
const argsSignatureParts = params.map(t => {
return translateParamTypeToJniType(t, resolveAlias);
});
if (isPromiseReturn) {
// Additional promise arg for this case.
argsSignatureParts.push(
translateReturnTypeToJniType(returnTypeAnnotation, resolveAlias),
);
}
const argsSignature = argsSignatureParts.join('');
const returnSignature =
name === 'getConstants'
? 'Ljava/util/Map;'
: translateReturnTypeToJniType(
processedReturnTypeAnnotation,
resolveAlias,
);
return `(${argsSignature})${returnSignature}`;
}
function translateMethodForImplementation(
hasteModuleName: string,
property: NativeModulePropertyShape,
resolveAlias: AliasResolver,
): string {
const [propertyTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(property.typeAnnotation);
const {returnTypeAnnotation} = propertyTypeAnnotation;
if (
property.name === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties.length === 0
) {
return '';
}
return HostFunctionTemplate({
hasteModuleName,
propertyName: property.name,
jniSignature: translateMethodTypeToJniSignature(property, resolveAlias),
jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias),
});
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName => {
const {
aliasMap,
spec: {eventEmitters, methods},
} = nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliasMap);
const translatedMethods = methods
.map(property =>
translateMethodForImplementation(
hasteModuleName,
property,
resolveAlias,
),
)
.join('\n\n');
return (
translatedMethods +
'\n\n' +
ModuleClassConstructorTemplate({
hasteModuleName,
eventEmitters,
methods: methods
.map(({name: propertyName, typeAnnotation}) => {
const [{returnTypeAnnotation, params}] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
typeAnnotation,
);
if (
propertyName === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties &&
returnTypeAnnotation.properties.length === 0
) {
return null;
}
return {
propertyName,
argCount: params.length,
};
})
.filter(Boolean),
})
);
})
.join('\n');
const moduleLookups: $ReadOnlyArray<{
hasteModuleName: string,
moduleName: string,
}> = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort((a, b) => {
const nameA = nativeModules[a].moduleName;
const nameB = nativeModules[b].moduleName;
if (nameA < nameB) {
return -1;
} else if (nameA > nameB) {
return 1;
}
return 0;
})
.map((hasteModuleName: string) => ({
moduleName: nativeModules[hasteModuleName].moduleName,
hasteModuleName,
}));
const fileName = `${libraryName}-generated.cpp`;
const replacedTemplate = FileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
moduleLookups,
include: `"${libraryName}.h"`,
});
return new Map([[`jni/${fileName}`, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,127 @@
/**
* 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
*/
'use strict';
const {getModules} = require('./Utils');
const ModuleClassDeclarationTemplate = ({hasteModuleName}) => {
return `/**
* JNI C++ class for module '${hasteModuleName}'
*/
class JSI_EXPORT ${hasteModuleName}SpecJSI : public JavaTurboModule {
public:
${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params);
};
`;
};
const HeaderFileTemplate = ({modules, libraryName}) => {
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: GenerateModuleJniH.js
*/
#pragma once
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
namespace facebook::react {
${modules}
JSI_EXPORT
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params);
} // namespace facebook::react
`;
};
// Note: this CMakeLists.txt template includes dependencies for both NativeModule and components.
const CMakeListsTemplate = ({libraryName, targetName}) => {
return `# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/${libraryName}/*.cpp)
add_library(
react_codegen_${targetName}
OBJECT
\${react_codegen_SRCS}
)
target_include_directories(react_codegen_${targetName} PUBLIC . react/renderer/components/${libraryName})
target_link_libraries(
react_codegen_${targetName}
fbjni
jsi
# We need to link different libraries based on whether we are building rncore or not, that's necessary
# because we want to break a circular dependency between react_codegen_rncore and reactnative
${targetName !== 'rncore' ? 'reactnative' : 'folly_runtime glog react_debug react_nativemodule_core react_renderer_componentregistry react_renderer_core react_renderer_debug react_renderer_graphics react_renderer_imagemanager react_renderer_mapbuffer react_utils rrc_image rrc_view turbomodulejsijni yoga'}
)
target_compile_reactnative_options(react_codegen_${targetName} PRIVATE)
`;
};
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName =>
ModuleClassDeclarationTemplate({
hasteModuleName,
}),
)
.join('\n');
const fileName = `${libraryName}.h`;
const replacedTemplate = HeaderFileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
});
// Use rncore as target name for backwards compat
const targetName =
libraryName === 'FBReactNativeSpec' ? 'rncore' : libraryName;
return new Map([
[`jni/${fileName}`, replacedTemplate],
[
'jni/CMakeLists.txt',
CMakeListsTemplate({
libraryName,
targetName,
}),
],
]);
},
};

View File

@@ -0,0 +1,137 @@
/**
* 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} from '../../CodegenSchema';
const {getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
const ModuleClassDeclarationTemplate = ({
hasteModuleName,
}: $ReadOnly<{hasteModuleName: string}>) => {
return `/**
* JNI C++ class for module '${hasteModuleName}'
*/
class JSI_EXPORT ${hasteModuleName}SpecJSI : public JavaTurboModule {
public:
${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params);
};
`;
};
const HeaderFileTemplate = ({
modules,
libraryName,
}: $ReadOnly<{modules: string, libraryName: 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: GenerateModuleJniH.js
*/
#pragma once
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
namespace facebook::react {
${modules}
JSI_EXPORT
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params);
} // namespace facebook::react
`;
};
// Note: this CMakeLists.txt template includes dependencies for both NativeModule and components.
const CMakeListsTemplate = ({
libraryName,
targetName,
}: $ReadOnly<{libraryName: string, targetName: string}>) => {
return `# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/${libraryName}/*.cpp)
add_library(
react_codegen_${targetName}
OBJECT
\${react_codegen_SRCS}
)
target_include_directories(react_codegen_${targetName} PUBLIC . react/renderer/components/${libraryName})
target_link_libraries(
react_codegen_${targetName}
fbjni
jsi
# We need to link different libraries based on whether we are building rncore or not, that's necessary
# because we want to break a circular dependency between react_codegen_rncore and reactnative
${
targetName !== 'rncore'
? 'reactnative'
: 'folly_runtime glog react_debug react_nativemodule_core react_renderer_componentregistry react_renderer_core react_renderer_debug react_renderer_graphics react_renderer_imagemanager react_renderer_mapbuffer react_utils rrc_image rrc_view turbomodulejsijni yoga'
}
)
target_compile_reactnative_options(react_codegen_${targetName} PRIVATE)
`;
};
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName => ModuleClassDeclarationTemplate({hasteModuleName}))
.join('\n');
const fileName = `${libraryName}.h`;
const replacedTemplate = HeaderFileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
});
// Use rncore as target name for backwards compat
const targetName =
libraryName === 'FBReactNativeSpec' ? 'rncore' : libraryName;
return new Map([
[`jni/${fileName}`, replacedTemplate],
['jni/CMakeLists.txt', CMakeListsTemplate({libraryName, targetName})],
]);
},
};

View File

@@ -0,0 +1,177 @@
/**
* 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
*/
'use strict';
function _defineProperty(e, r, t) {
return (
(r = _toPropertyKey(r)) in e
? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0,
})
: (e[r] = t),
e
);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, 'string');
return 'symbol' == typeof i ? i : i + '';
}
function _toPrimitive(t, r) {
if ('object' != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || 'default');
if ('object' != typeof i) return i;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return ('string' === r ? String : Number)(t);
}
const {
unwrapNullable,
wrapNullable,
} = require('../../../parsers/parsers-commons');
const {capitalize} = require('../../Utils');
class StructCollector {
constructor() {
_defineProperty(this, '_structs', new Map());
}
process(structName, structContext, resolveAlias, nullableTypeAnnotation) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertStruct(
structName,
structContext,
resolveAlias,
typeAnnotation,
);
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: structName,
});
}
case 'ArrayTypeAnnotation': {
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'AnyTypeAnnotation',
},
});
}
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: this.process(
structName + 'Element',
structContext,
resolveAlias,
typeAnnotation.elementType,
),
});
}
case 'TypeAliasTypeAnnotation': {
this._insertAlias(typeAnnotation.name, structContext, resolveAlias);
return wrapNullable(nullable, typeAnnotation);
}
case 'EnumDeclaration':
return wrapNullable(nullable, typeAnnotation);
case 'MixedTypeAnnotation':
throw new Error('Mixed types are unsupported in structs');
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'StringTypeAnnotation':
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',
});
case 'NumberTypeAnnotation':
return wrapNullable(nullable, {
type: 'NumberTypeAnnotation',
});
case 'ObjectTypeAnnotation':
// This isn't smart enough to actually know how to generate the
// options on the native side. So we just treat it as an unknown object type
return wrapNullable(nullable, {
type: 'GenericObjectTypeAnnotation',
});
default:
typeAnnotation.memberType;
throw new Error(
'Union types are unsupported in structs' +
JSON.stringify(typeAnnotation),
);
}
default: {
return wrapNullable(nullable, typeAnnotation);
}
}
}
_insertAlias(aliasName, structContext, resolveAlias) {
const usedStruct = this._structs.get(aliasName);
if (usedStruct == null) {
this._insertStruct(
aliasName,
structContext,
resolveAlias,
resolveAlias(aliasName),
);
} else if (usedStruct.context !== structContext) {
throw new Error(
`Tried to use alias '${aliasName}' in a getConstants() return type and inside a regular struct.`,
);
}
}
_insertStruct(structName, structContext, resolveAlias, objectTypeAnnotation) {
// $FlowFixMe[missing-type-arg]
const properties = objectTypeAnnotation.properties.map(property => {
const propertyStructName = structName + capitalize(property.name);
return {
...property,
typeAnnotation: this.process(
propertyStructName,
structContext,
resolveAlias,
property.typeAnnotation,
),
};
});
switch (structContext) {
case 'REGULAR':
this._structs.set(structName, {
name: structName,
context: 'REGULAR',
properties: properties,
});
break;
case 'CONSTANTS':
this._structs.set(structName, {
name: structName,
context: 'CONSTANTS',
properties: properties,
});
break;
default:
structContext;
throw new Error(`Detected an invalid struct context: ${structContext}`);
}
}
getAllStructs() {
return [...this._structs.values()];
}
getStruct(name) {
return this._structs.get(name);
}
}
module.exports = {
StructCollector,
};

View File

@@ -0,0 +1,237 @@
/**
* 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 {
BooleanTypeAnnotation,
DoubleTypeAnnotation,
FloatTypeAnnotation,
Int32TypeAnnotation,
NativeModuleArrayTypeAnnotation,
NativeModuleBaseTypeAnnotation,
NativeModuleEnumDeclaration,
NativeModuleGenericObjectTypeAnnotation,
NativeModuleNumberTypeAnnotation,
NativeModuleObjectTypeAnnotation,
NativeModuleTypeAliasTypeAnnotation,
Nullable,
NumberLiteralTypeAnnotation,
ReservedTypeAnnotation,
StringLiteralTypeAnnotation,
StringLiteralUnionTypeAnnotation,
StringTypeAnnotation,
} from '../../../CodegenSchema';
import type {AliasResolver} from '../Utils';
const {
unwrapNullable,
wrapNullable,
} = require('../../../parsers/parsers-commons');
const {capitalize} = require('../../Utils');
type StructContext = 'CONSTANTS' | 'REGULAR';
export type RegularStruct = $ReadOnly<{
context: 'REGULAR',
name: string,
properties: $ReadOnlyArray<StructProperty>,
}>;
export type ConstantsStruct = $ReadOnly<{
context: 'CONSTANTS',
name: string,
properties: $ReadOnlyArray<StructProperty>,
}>;
export type Struct = RegularStruct | ConstantsStruct;
export type StructProperty = $ReadOnly<{
name: string,
optional: boolean,
typeAnnotation: Nullable<StructTypeAnnotation>,
}>;
export type StructTypeAnnotation =
| StringTypeAnnotation
| StringLiteralTypeAnnotation
| StringLiteralUnionTypeAnnotation
| NativeModuleNumberTypeAnnotation
| NumberLiteralTypeAnnotation
| Int32TypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| BooleanTypeAnnotation
| NativeModuleEnumDeclaration
| NativeModuleGenericObjectTypeAnnotation
| ReservedTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleArrayTypeAnnotation<Nullable<StructTypeAnnotation>>;
class StructCollector {
_structs: Map<string, Struct> = new Map();
process(
structName: string,
structContext: StructContext,
resolveAlias: AliasResolver,
nullableTypeAnnotation: Nullable<NativeModuleBaseTypeAnnotation>,
): Nullable<StructTypeAnnotation> {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertStruct(
structName,
structContext,
resolveAlias,
typeAnnotation,
);
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: structName,
});
}
case 'ArrayTypeAnnotation': {
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'AnyTypeAnnotation',
},
});
}
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: this.process(
structName + 'Element',
structContext,
resolveAlias,
typeAnnotation.elementType,
),
});
}
case 'TypeAliasTypeAnnotation': {
this._insertAlias(typeAnnotation.name, structContext, resolveAlias);
return wrapNullable(nullable, typeAnnotation);
}
case 'EnumDeclaration':
return wrapNullable(nullable, typeAnnotation);
case 'MixedTypeAnnotation':
throw new Error('Mixed types are unsupported in structs');
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'StringTypeAnnotation':
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',
});
case 'NumberTypeAnnotation':
return wrapNullable(nullable, {
type: 'NumberTypeAnnotation',
});
case 'ObjectTypeAnnotation':
// This isn't smart enough to actually know how to generate the
// options on the native side. So we just treat it as an unknown object type
return wrapNullable(nullable, {
type: 'GenericObjectTypeAnnotation',
});
default:
(typeAnnotation.memberType: empty);
throw new Error(
'Union types are unsupported in structs' +
JSON.stringify(typeAnnotation),
);
}
default: {
return wrapNullable(nullable, typeAnnotation);
}
}
}
_insertAlias(
aliasName: string,
structContext: StructContext,
resolveAlias: AliasResolver,
): void {
const usedStruct = this._structs.get(aliasName);
if (usedStruct == null) {
this._insertStruct(
aliasName,
structContext,
resolveAlias,
resolveAlias(aliasName),
);
} else if (usedStruct.context !== structContext) {
throw new Error(
`Tried to use alias '${aliasName}' in a getConstants() return type and inside a regular struct.`,
);
}
}
_insertStruct(
structName: string,
structContext: StructContext,
resolveAlias: AliasResolver,
objectTypeAnnotation: NativeModuleObjectTypeAnnotation,
): void {
// $FlowFixMe[missing-type-arg]
const properties = objectTypeAnnotation.properties.map<
$ReadOnly<{
name: string,
optional: boolean,
typeAnnotation: Nullable<StructTypeAnnotation>,
}>,
>(property => {
const propertyStructName = structName + capitalize(property.name);
return {
...property,
typeAnnotation: this.process(
propertyStructName,
structContext,
resolveAlias,
property.typeAnnotation,
),
};
});
switch (structContext) {
case 'REGULAR':
this._structs.set(structName, {
name: structName,
context: 'REGULAR',
properties: properties,
});
break;
case 'CONSTANTS':
this._structs.set(structName, {
name: structName,
context: 'CONSTANTS',
properties: properties,
});
break;
default:
(structContext: empty);
throw new Error(`Detected an invalid struct context: ${structContext}`);
}
}
getAllStructs(): $ReadOnlyArray<Struct> {
return [...this._structs.values()];
}
getStruct(name: string): ?Struct {
return this._structs.get(name);
}
}
module.exports = {
StructCollector,
};

View File

@@ -0,0 +1,25 @@
/**
* 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
*/
'use strict';
function getSafePropertyName(property) {
if (property.name === 'id') {
return `${property.name}_`;
}
return property.name;
}
function getNamespacedStructName(hasteModuleName, structName) {
return `JS::${hasteModuleName}::${structName}`;
}
module.exports = {
getSafePropertyName,
getNamespacedStructName,
};

View File

@@ -0,0 +1,32 @@
/**
* 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 {StructProperty} from './StructCollector';
function getSafePropertyName(property: StructProperty): string {
if (property.name === 'id') {
return `${property.name}_`;
}
return property.name;
}
function getNamespacedStructName(
hasteModuleName: string,
structName: string,
): string {
return `JS::${hasteModuleName}::${structName}`;
}
module.exports = {
getSafePropertyName,
getNamespacedStructName,
};

View File

@@ -0,0 +1,265 @@
/**
* 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
*/
'use strict';
const {unwrapNullable} = require('../../../../parsers/parsers-commons');
const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');
const StructTemplate = ({
hasteModuleName,
structName,
builderInputProps,
}) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
struct Builder {
struct Input {
${builderInputProps}
};
/** Initialize with a set of values */
Builder(const Input i);
/** Initialize with an existing ${structName} */
Builder(${structName} i);
/** Builds the object. Generally used only by the infrastructure. */
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
private:
NSDictionary *(^_factory)(void);
};
static ${structName} fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
NSDictionary *unsafeRawValue() const { return _v; }
private:
${structName}(NSDictionary *const v) : _v(v) {}
NSDictionary *_v;
};
}
}`;
const MethodTemplate = ({
hasteModuleName,
structName,
properties,
}) => `inline JS::${hasteModuleName}::${structName}::Builder::Builder(const Input i) : _factory(^{
NSMutableDictionary *d = [NSMutableDictionary new];
${properties}
return d;
}) {}
inline JS::${hasteModuleName}::${structName}::Builder::Builder(${structName} i) : _factory(^{
return i.unsafeRawValue();
}) {}`;
function toObjCType(
hasteModuleName,
nullableTypeAnnotation,
isOptional = false,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
typeAnnotation.name;
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`std::vector<${toObjCType(hasteModuleName, typeAnnotation.elementType)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(`${namespacedStructName}::Builder`, isRequired);
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName,
nullableTypeAnnotation,
value,
depth,
isOptional = false,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
function wrapPrimitive(type) {
return !isRequired
? `${value}.has_value() ? @((${type})${value}.value()) : nil`
: `@(${value})`;
}
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapPrimitive('double');
default:
typeAnnotation.name;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return value;
case 'StringLiteralTypeAnnotation':
return value;
case 'StringLiteralUnionTypeAnnotation':
return value;
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'NumberLiteralTypeAnnotation':
return wrapPrimitive('double');
case 'FloatTypeAnnotation':
return wrapPrimitive('double');
case 'Int32TypeAnnotation':
return wrapPrimitive('double');
case 'DoubleTypeAnnotation':
return wrapPrimitive('double');
case 'BooleanTypeAnnotation':
return wrapPrimitive('BOOL');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'StringTypeAnnotation':
return value;
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const {elementType} = typeAnnotation;
if (elementType.type === 'AnyTypeAnnotation') {
return value;
}
const localVarName = `el${'_'.repeat(depth + 1)}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
const RCTConvertVecToArray = transformer => {
return `RCTConvert${!isRequired ? 'Optional' : ''}VecToArray(${value}, ${transformer})`;
};
return RCTConvertVecToArray(
`^id(${elementObjCType} ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
return !isRequired
? `${value}.has_value() ? ${value}.value().buildUnsafeRawValue() : nil`
: `${value}.buildUnsafeRawValue()`;
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeConstantsStruct(hasteModuleName, struct) {
const declaration = StructTemplate({
hasteModuleName,
structName: struct.name,
builderInputProps: struct.properties
.map(property => {
const {typeAnnotation, optional} = property;
const safePropName = getSafePropertyName(property);
const objCType = toObjCType(hasteModuleName, typeAnnotation, optional);
if (!optional) {
return `RCTRequired<${objCType}> ${safePropName};`;
}
const space = ' '.repeat(objCType.endsWith('*') ? 0 : 1);
return `${objCType}${space}${safePropName};`;
})
.join('\n '),
});
const methods = MethodTemplate({
hasteModuleName,
structName: struct.name,
properties: struct.properties
.map(property => {
const {typeAnnotation, optional, name: propName} = property;
const safePropName = getSafePropertyName(property);
const objCValue = toObjCValue(
hasteModuleName,
typeAnnotation,
safePropName,
0,
optional,
);
let varDecl = `auto ${safePropName} = i.${safePropName}`;
if (!optional) {
varDecl += '.get()';
}
const assignment = `d[@"${propName}"] = ` + objCValue;
return ` ${varDecl};\n ${assignment};`;
})
.join('\n'),
});
return {
declaration,
methods,
};
}
module.exports = {
serializeConstantsStruct,
};

View File

@@ -0,0 +1,301 @@
/**
* 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 {Nullable} from '../../../../CodegenSchema';
import type {ConstantsStruct, StructTypeAnnotation} from '../StructCollector';
import type {StructSerilizationOutput} from './serializeStruct';
const {unwrapNullable} = require('../../../../parsers/parsers-commons');
const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');
const StructTemplate = ({
hasteModuleName,
structName,
builderInputProps,
}: $ReadOnly<{
hasteModuleName: string,
structName: string,
builderInputProps: string,
}>) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
struct Builder {
struct Input {
${builderInputProps}
};
/** Initialize with a set of values */
Builder(const Input i);
/** Initialize with an existing ${structName} */
Builder(${structName} i);
/** Builds the object. Generally used only by the infrastructure. */
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
private:
NSDictionary *(^_factory)(void);
};
static ${structName} fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
NSDictionary *unsafeRawValue() const { return _v; }
private:
${structName}(NSDictionary *const v) : _v(v) {}
NSDictionary *_v;
};
}
}`;
const MethodTemplate = ({
hasteModuleName,
structName,
properties,
}: $ReadOnly<{
hasteModuleName: string,
structName: string,
properties: string,
}>) => `inline JS::${hasteModuleName}::${structName}::Builder::Builder(const Input i) : _factory(^{
NSMutableDictionary *d = [NSMutableDictionary new];
${properties}
return d;
}) {}
inline JS::${hasteModuleName}::${structName}::Builder::Builder(${structName} i) : _factory(^{
return i.unsafeRawValue();
}) {}`;
function toObjCType(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
(typeAnnotation.name: empty);
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`std::vector<${toObjCType(
hasteModuleName,
typeAnnotation.elementType,
)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(`${namespacedStructName}::Builder`, isRequired);
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
value: string,
depth: number,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
function wrapPrimitive(type: string) {
return !isRequired
? `${value}.has_value() ? @((${type})${value}.value()) : nil`
: `@(${value})`;
}
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapPrimitive('double');
default:
(typeAnnotation.name: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return value;
case 'StringLiteralTypeAnnotation':
return value;
case 'StringLiteralUnionTypeAnnotation':
return value;
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'NumberLiteralTypeAnnotation':
return wrapPrimitive('double');
case 'FloatTypeAnnotation':
return wrapPrimitive('double');
case 'Int32TypeAnnotation':
return wrapPrimitive('double');
case 'DoubleTypeAnnotation':
return wrapPrimitive('double');
case 'BooleanTypeAnnotation':
return wrapPrimitive('BOOL');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'StringTypeAnnotation':
return value;
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const {elementType} = typeAnnotation;
if (elementType.type === 'AnyTypeAnnotation') {
return value;
}
const localVarName = `el${'_'.repeat(depth + 1)}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
const RCTConvertVecToArray = (transformer: string) => {
return `RCTConvert${
!isRequired ? 'Optional' : ''
}VecToArray(${value}, ${transformer})`;
};
return RCTConvertVecToArray(
`^id(${elementObjCType} ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
return !isRequired
? `${value}.has_value() ? ${value}.value().buildUnsafeRawValue() : nil`
: `${value}.buildUnsafeRawValue()`;
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeConstantsStruct(
hasteModuleName: string,
struct: ConstantsStruct,
): StructSerilizationOutput {
const declaration = StructTemplate({
hasteModuleName,
structName: struct.name,
builderInputProps: struct.properties
.map(property => {
const {typeAnnotation, optional} = property;
const safePropName = getSafePropertyName(property);
const objCType = toObjCType(hasteModuleName, typeAnnotation, optional);
if (!optional) {
return `RCTRequired<${objCType}> ${safePropName};`;
}
const space = ' '.repeat(objCType.endsWith('*') ? 0 : 1);
return `${objCType}${space}${safePropName};`;
})
.join('\n '),
});
const methods = MethodTemplate({
hasteModuleName,
structName: struct.name,
properties: struct.properties
.map(property => {
const {typeAnnotation, optional, name: propName} = property;
const safePropName = getSafePropertyName(property);
const objCValue = toObjCValue(
hasteModuleName,
typeAnnotation,
safePropName,
0,
optional,
);
let varDecl = `auto ${safePropName} = i.${safePropName}`;
if (!optional) {
varDecl += '.get()';
}
const assignment = `d[@"${propName}"] = ` + objCValue;
return ` ${varDecl};\n ${assignment};`;
})
.join('\n'),
});
return {declaration, methods};
}
module.exports = {
serializeConstantsStruct,
};

View File

@@ -0,0 +1,260 @@
/**
* 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
*/
'use strict';
const {unwrapNullable} = require('../../../../parsers/parsers-commons');
const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');
const StructTemplate = ({
hasteModuleName,
structName,
structProperties,
}) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
${structProperties}
${structName}(NSDictionary *const v) : _v(v) {}
private:
NSDictionary *_v;
};
}
}
@interface RCTCxxConvert (${hasteModuleName}_${structName})
+ (RCTManagedPointer *)JS_${hasteModuleName}_${structName}:(id)json;
@end`;
const MethodTemplate = ({
returnType,
returnValue,
hasteModuleName,
structName,
propertyName,
safePropertyName,
}) => `inline ${returnType}JS::${hasteModuleName}::${structName}::${safePropertyName}() const
{
id const p = _v[@"${propertyName}"];
return ${returnValue};
}`;
function toObjCType(
hasteModuleName,
nullableTypeAnnotation,
isOptional = false,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
typeAnnotation.name;
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`facebook::react::LazyVector<${toObjCType(hasteModuleName, typeAnnotation.elementType)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(namespacedStructName, isRequired);
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName,
nullableTypeAnnotation,
value,
depth,
isOptional = false,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
const RCTBridgingTo = (type, arg) => {
const args = [value, arg].filter(Boolean).join(', ');
return isRequired
? `RCTBridgingTo${type}(${args})`
: `RCTBridgingToOptional${type}(${args})`;
};
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return RCTBridgingTo('Double');
default:
typeAnnotation.name;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return RCTBridgingTo('String');
case 'StringLiteralTypeAnnotation':
return RCTBridgingTo('String');
case 'StringLiteralUnionTypeAnnotation':
return RCTBridgingTo('String');
case 'NumberTypeAnnotation':
return RCTBridgingTo('Double');
case 'NumberLiteralTypeAnnotation':
return RCTBridgingTo('Double');
case 'FloatTypeAnnotation':
return RCTBridgingTo('Double');
case 'Int32TypeAnnotation':
return RCTBridgingTo('Double');
case 'DoubleTypeAnnotation':
return RCTBridgingTo('Double');
case 'BooleanTypeAnnotation':
return RCTBridgingTo('Bool');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return RCTBridgingTo('Double');
case 'StringTypeAnnotation':
return RCTBridgingTo('String');
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const {elementType} = typeAnnotation;
if (elementType.type === 'AnyTypeAnnotation') {
return value;
}
const localVarName = `itemValue_${depth}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
return RCTBridgingTo(
'Vec',
`^${elementObjCType}(id ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return !isRequired
? `(${value} == nil ? std::nullopt : std::make_optional(${namespacedStructName}(${value})))`
: `${namespacedStructName}(${value})`;
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeRegularStruct(hasteModuleName, struct) {
const declaration = StructTemplate({
hasteModuleName: hasteModuleName,
structName: struct.name,
structProperties: struct.properties
.map(property => {
const {typeAnnotation, optional} = property;
const safePropName = getSafePropertyName(property);
const returnType = toObjCType(
hasteModuleName,
typeAnnotation,
optional,
);
const padding = ' '.repeat(returnType.endsWith('*') ? 0 : 1);
return `${returnType}${padding}${safePropName}() const;`;
})
.join('\n '),
});
// $FlowFixMe[missing-type-arg]
const methods = struct.properties
.map(property => {
const {typeAnnotation, optional, name: propName} = property;
const safePropertyName = getSafePropertyName(property);
const returnType = toObjCType(hasteModuleName, typeAnnotation, optional);
const returnValue = toObjCValue(
hasteModuleName,
typeAnnotation,
'p',
0,
optional,
);
const padding = ' '.repeat(returnType.endsWith('*') ? 0 : 1);
return MethodTemplate({
hasteModuleName,
structName: struct.name,
returnType: returnType + padding,
returnValue: returnValue,
propertyName: propName,
safePropertyName,
});
})
.join('\n');
return {
methods,
declaration,
};
}
module.exports = {
serializeRegularStruct,
};

View File

@@ -0,0 +1,292 @@
/**
* 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 {Nullable} from '../../../../CodegenSchema';
import type {RegularStruct, StructTypeAnnotation} from '../StructCollector';
import type {StructSerilizationOutput} from './serializeStruct';
const {unwrapNullable} = require('../../../../parsers/parsers-commons');
const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');
const StructTemplate = ({
hasteModuleName,
structName,
structProperties,
}: $ReadOnly<{
hasteModuleName: string,
structName: string,
structProperties: string,
}>) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
${structProperties}
${structName}(NSDictionary *const v) : _v(v) {}
private:
NSDictionary *_v;
};
}
}
@interface RCTCxxConvert (${hasteModuleName}_${structName})
+ (RCTManagedPointer *)JS_${hasteModuleName}_${structName}:(id)json;
@end`;
const MethodTemplate = ({
returnType,
returnValue,
hasteModuleName,
structName,
propertyName,
safePropertyName,
}: $ReadOnly<{
returnType: string,
returnValue: string,
hasteModuleName: string,
structName: string,
propertyName: string,
safePropertyName: string,
}>) => `inline ${returnType}JS::${hasteModuleName}::${structName}::${safePropertyName}() const
{
id const p = _v[@"${propertyName}"];
return ${returnValue};
}`;
function toObjCType(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
(typeAnnotation.name: empty);
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`facebook::react::LazyVector<${toObjCType(
hasteModuleName,
typeAnnotation.elementType,
)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(namespacedStructName, isRequired);
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
value: string,
depth: number,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
const RCTBridgingTo = (type: string, arg?: string) => {
const args = [value, arg].filter(Boolean).join(', ');
return isRequired
? `RCTBridgingTo${type}(${args})`
: `RCTBridgingToOptional${type}(${args})`;
};
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return RCTBridgingTo('Double');
default:
(typeAnnotation.name: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return RCTBridgingTo('String');
case 'StringLiteralTypeAnnotation':
return RCTBridgingTo('String');
case 'StringLiteralUnionTypeAnnotation':
return RCTBridgingTo('String');
case 'NumberTypeAnnotation':
return RCTBridgingTo('Double');
case 'NumberLiteralTypeAnnotation':
return RCTBridgingTo('Double');
case 'FloatTypeAnnotation':
return RCTBridgingTo('Double');
case 'Int32TypeAnnotation':
return RCTBridgingTo('Double');
case 'DoubleTypeAnnotation':
return RCTBridgingTo('Double');
case 'BooleanTypeAnnotation':
return RCTBridgingTo('Bool');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return RCTBridgingTo('Double');
case 'StringTypeAnnotation':
return RCTBridgingTo('String');
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const {elementType} = typeAnnotation;
if (elementType.type === 'AnyTypeAnnotation') {
return value;
}
const localVarName = `itemValue_${depth}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
return RCTBridgingTo(
'Vec',
`^${elementObjCType}(id ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return !isRequired
? `(${value} == nil ? std::nullopt : std::make_optional(${namespacedStructName}(${value})))`
: `${namespacedStructName}(${value})`;
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeRegularStruct(
hasteModuleName: string,
struct: RegularStruct,
): StructSerilizationOutput {
const declaration = StructTemplate({
hasteModuleName: hasteModuleName,
structName: struct.name,
structProperties: struct.properties
.map(property => {
const {typeAnnotation, optional} = property;
const safePropName = getSafePropertyName(property);
const returnType = toObjCType(
hasteModuleName,
typeAnnotation,
optional,
);
const padding = ' '.repeat(returnType.endsWith('*') ? 0 : 1);
return `${returnType}${padding}${safePropName}() const;`;
})
.join('\n '),
});
// $FlowFixMe[missing-type-arg]
const methods = struct.properties
.map<string>(property => {
const {typeAnnotation, optional, name: propName} = property;
const safePropertyName = getSafePropertyName(property);
const returnType = toObjCType(hasteModuleName, typeAnnotation, optional);
const returnValue = toObjCValue(
hasteModuleName,
typeAnnotation,
'p',
0,
optional,
);
const padding = ' '.repeat(returnType.endsWith('*') ? 0 : 1);
return MethodTemplate({
hasteModuleName,
structName: struct.name,
returnType: returnType + padding,
returnValue: returnValue,
propertyName: propName,
safePropertyName,
});
})
.join('\n');
return {methods, declaration};
}
module.exports = {
serializeRegularStruct,
};

View File

@@ -0,0 +1,23 @@
/**
* 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
*/
'use strict';
const {serializeConstantsStruct} = require('./serializeConstantsStruct');
const {serializeRegularStruct} = require('./serializeRegularStruct');
function serializeStruct(hasteModuleName, struct) {
if (struct.context === 'REGULAR') {
return serializeRegularStruct(hasteModuleName, struct);
}
return serializeConstantsStruct(hasteModuleName, struct);
}
module.exports = {
serializeStruct,
};

View File

@@ -0,0 +1,35 @@
/**
* 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 {Struct} from '../StructCollector';
const {serializeConstantsStruct} = require('./serializeConstantsStruct');
const {serializeRegularStruct} = require('./serializeRegularStruct');
export type StructSerilizationOutput = $ReadOnly<{
methods: string,
declaration: string,
}>;
function serializeStruct(
hasteModuleName: string,
struct: Struct,
): StructSerilizationOutput {
if (struct.context === 'REGULAR') {
return serializeRegularStruct(hasteModuleName, struct);
}
return serializeConstantsStruct(hasteModuleName, struct);
}
module.exports = {
serializeStruct,
};

View File

@@ -0,0 +1,203 @@
/**
* 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
*/
'use strict';
const {createAliasResolver, getModules} = require('../Utils');
const {serializeStruct} = require('./header/serializeStruct');
const {EventEmitterHeaderTemplate} = require('./serializeEventEmitter');
const {serializeMethod} = require('./serializeMethod');
const {serializeModuleSource} = require('./source/serializeModule');
const {StructCollector} = require('./StructCollector');
const ModuleDeclarationTemplate = ({
hasteModuleName,
structDeclarations,
eventEmitters,
protocolMethods,
}) => `${structDeclarations}
@protocol ${hasteModuleName}Spec <RCTBridgeModule, RCTTurboModule>
${protocolMethods}
@end
@interface ${hasteModuleName}SpecBase : NSObject {
@protected
facebook::react::EventEmitterCallback _eventEmitterCallback;
}
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper;
${eventEmitters}
@end
namespace facebook::react {
/**
* ObjC++ class for module '${hasteModuleName}'
*/
class JSI_EXPORT ${hasteModuleName}SpecJSI : public ObjCTurboModule {
public:
${hasteModuleName}SpecJSI(const ObjCTurboModule::InitParams &params);
};
} // namespace facebook::react`;
const HeaderFileTemplate = ({
headerFileName,
moduleDeclarations,
structInlineMethods,
assumeNonnull,
}) => {
const headerFileNameWithNoExt = headerFileName.replace(/\.h$/, '');
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: GenerateModuleObjCpp
*
* We create an umbrella header (and corresponding implementation) here since
* Cxx compilation in BUCK has a limitation: source-code producing genrule()s
* must have a single output. More files => more genrule()s => slower builds.
*/
#ifndef __cplusplus
#error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm.
#endif
// Avoid multiple includes of ${headerFileNameWithNoExt} symbols
#ifndef ${headerFileNameWithNoExt}_H
#define ${headerFileNameWithNoExt}_H
#import <Foundation/Foundation.h>
#import <RCTRequired/RCTRequired.h>
#import <RCTTypeSafety/RCTConvertHelpers.h>
#import <RCTTypeSafety/RCTTypedModuleConstants.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTCxxConvert.h>
#import <React/RCTManagedPointer.h>
#import <ReactCommon/RCTTurboModule.h>
#import <optional>
#import <vector>
` +
(assumeNonnull ? '\nNS_ASSUME_NONNULL_BEGIN\n' : '') +
moduleDeclarations +
'\n' +
structInlineMethods +
(assumeNonnull ? '\nNS_ASSUME_NONNULL_END\n' : '\n') +
`#endif // ${headerFileNameWithNoExt}_H` +
'\n'
);
};
const SourceFileTemplate = ({headerFileName, moduleImplementations}) => `/**
* 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: GenerateModuleObjCpp
*
* We create an umbrella header (and corresponding implementation) here since
* Cxx compilation in BUCK has a limitation: source-code producing genrule()s
* must have a single output. More files => more genrule()s => slower builds.
*/
#import "${headerFileName}"
${moduleImplementations}
`;
module.exports = {
generate(libraryName, schema, packageName, assumeNonnull, headerPrefix) {
const nativeModules = getModules(schema);
const moduleDeclarations = [];
const structInlineMethods = [];
const moduleImplementations = [];
const hasteModuleNames = Object.keys(nativeModules).sort();
for (const hasteModuleName of hasteModuleNames) {
const {aliasMap, excludedPlatforms, spec} =
nativeModules[hasteModuleName];
if (excludedPlatforms != null && excludedPlatforms.includes('iOS')) {
continue;
}
const resolveAlias = createAliasResolver(aliasMap);
const structCollector = new StructCollector();
const methodSerializations = [];
const serializeProperty = property => {
methodSerializations.push(
...serializeMethod(
hasteModuleName,
property,
structCollector,
resolveAlias,
),
);
};
/**
* Note: As we serialize NativeModule methods, we insert structs into
* StructCollector, as we encounter them.
*/
spec.methods
.filter(property => property.name !== 'getConstants')
.forEach(serializeProperty);
spec.methods
.filter(property => property.name === 'getConstants')
.forEach(serializeProperty);
const generatedStructs = structCollector.getAllStructs();
const structStrs = [];
const methodStrs = [];
for (const struct of generatedStructs) {
const {methods, declaration} = serializeStruct(hasteModuleName, struct);
structStrs.push(declaration);
methodStrs.push(methods);
}
moduleDeclarations.push(
ModuleDeclarationTemplate({
hasteModuleName: hasteModuleName,
structDeclarations: structStrs.join('\n'),
eventEmitters: spec.eventEmitters
.map(eventEmitter => EventEmitterHeaderTemplate(eventEmitter))
.join('\n'),
protocolMethods: methodSerializations
.map(({protocolMethod}) => protocolMethod)
.join('\n'),
}),
);
structInlineMethods.push(methodStrs.join('\n'));
moduleImplementations.push(
serializeModuleSource(
hasteModuleName,
generatedStructs,
hasteModuleName,
spec.eventEmitters,
methodSerializations.filter(
({selector}) => selector !== '@selector(constantsToExport)',
),
),
);
}
const headerFileName = `${libraryName}.h`;
const headerFile = HeaderFileTemplate({
headerFileName,
moduleDeclarations: moduleDeclarations.join('\n'),
structInlineMethods: structInlineMethods.join('\n'),
assumeNonnull,
});
const sourceFileName = `${libraryName}-generated.mm`;
const sourceFile = SourceFileTemplate({
headerFileName,
moduleImplementations: moduleImplementations.join('\n'),
});
return new Map([
[headerFileName, headerFile],
[sourceFileName, sourceFile],
]);
},
};

View File

@@ -0,0 +1,246 @@
/**
* 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 {NativeModulePropertyShape} from '../../../CodegenSchema';
import type {SchemaType} from '../../../CodegenSchema';
import type {MethodSerializationOutput} from './serializeMethod';
const {createAliasResolver, getModules} = require('../Utils');
const {serializeStruct} = require('./header/serializeStruct');
const {EventEmitterHeaderTemplate} = require('./serializeEventEmitter');
const {serializeMethod} = require('./serializeMethod');
const {serializeModuleSource} = require('./source/serializeModule');
const {StructCollector} = require('./StructCollector');
type FilesOutput = Map<string, string>;
const ModuleDeclarationTemplate = ({
hasteModuleName,
structDeclarations,
eventEmitters,
protocolMethods,
}: $ReadOnly<{
hasteModuleName: string,
structDeclarations: string,
eventEmitters: string,
protocolMethods: string,
}>) => `${structDeclarations}
@protocol ${hasteModuleName}Spec <RCTBridgeModule, RCTTurboModule>
${protocolMethods}
@end
@interface ${hasteModuleName}SpecBase : NSObject {
@protected
facebook::react::EventEmitterCallback _eventEmitterCallback;
}
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper;
${eventEmitters}
@end
namespace facebook::react {
/**
* ObjC++ class for module '${hasteModuleName}'
*/
class JSI_EXPORT ${hasteModuleName}SpecJSI : public ObjCTurboModule {
public:
${hasteModuleName}SpecJSI(const ObjCTurboModule::InitParams &params);
};
} // namespace facebook::react`;
const HeaderFileTemplate = ({
headerFileName,
moduleDeclarations,
structInlineMethods,
assumeNonnull,
}: $ReadOnly<{
headerFileName: string,
moduleDeclarations: string,
structInlineMethods: string,
assumeNonnull: boolean,
}>) => {
const headerFileNameWithNoExt = headerFileName.replace(/\.h$/, '');
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: GenerateModuleObjCpp
*
* We create an umbrella header (and corresponding implementation) here since
* Cxx compilation in BUCK has a limitation: source-code producing genrule()s
* must have a single output. More files => more genrule()s => slower builds.
*/
#ifndef __cplusplus
#error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm.
#endif
// Avoid multiple includes of ${headerFileNameWithNoExt} symbols
#ifndef ${headerFileNameWithNoExt}_H
#define ${headerFileNameWithNoExt}_H
#import <Foundation/Foundation.h>
#import <RCTRequired/RCTRequired.h>
#import <RCTTypeSafety/RCTConvertHelpers.h>
#import <RCTTypeSafety/RCTTypedModuleConstants.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTCxxConvert.h>
#import <React/RCTManagedPointer.h>
#import <ReactCommon/RCTTurboModule.h>
#import <optional>
#import <vector>
` +
(assumeNonnull ? '\nNS_ASSUME_NONNULL_BEGIN\n' : '') +
moduleDeclarations +
'\n' +
structInlineMethods +
(assumeNonnull ? '\nNS_ASSUME_NONNULL_END\n' : '\n') +
`#endif // ${headerFileNameWithNoExt}_H` +
'\n'
);
};
const SourceFileTemplate = ({
headerFileName,
moduleImplementations,
}: $ReadOnly<{
headerFileName: string,
moduleImplementations: string,
}>) => `/**
* 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: GenerateModuleObjCpp
*
* We create an umbrella header (and corresponding implementation) here since
* Cxx compilation in BUCK has a limitation: source-code producing genrule()s
* must have a single output. More files => more genrule()s => slower builds.
*/
#import "${headerFileName}"
${moduleImplementations}
`;
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const moduleDeclarations: Array<string> = [];
const structInlineMethods: Array<string> = [];
const moduleImplementations: Array<string> = [];
const hasteModuleNames: Array<string> = Object.keys(nativeModules).sort();
for (const hasteModuleName of hasteModuleNames) {
const {aliasMap, excludedPlatforms, spec} =
nativeModules[hasteModuleName];
if (excludedPlatforms != null && excludedPlatforms.includes('iOS')) {
continue;
}
const resolveAlias = createAliasResolver(aliasMap);
const structCollector = new StructCollector();
const methodSerializations: Array<MethodSerializationOutput> = [];
const serializeProperty = (property: NativeModulePropertyShape) => {
methodSerializations.push(
...serializeMethod(
hasteModuleName,
property,
structCollector,
resolveAlias,
),
);
};
/**
* Note: As we serialize NativeModule methods, we insert structs into
* StructCollector, as we encounter them.
*/
spec.methods
.filter(property => property.name !== 'getConstants')
.forEach(serializeProperty);
spec.methods
.filter(property => property.name === 'getConstants')
.forEach(serializeProperty);
const generatedStructs = structCollector.getAllStructs();
const structStrs = [];
const methodStrs = [];
for (const struct of generatedStructs) {
const {methods, declaration} = serializeStruct(hasteModuleName, struct);
structStrs.push(declaration);
methodStrs.push(methods);
}
moduleDeclarations.push(
ModuleDeclarationTemplate({
hasteModuleName: hasteModuleName,
structDeclarations: structStrs.join('\n'),
eventEmitters: spec.eventEmitters
.map(eventEmitter => EventEmitterHeaderTemplate(eventEmitter))
.join('\n'),
protocolMethods: methodSerializations
.map(({protocolMethod}) => protocolMethod)
.join('\n'),
}),
);
structInlineMethods.push(methodStrs.join('\n'));
moduleImplementations.push(
serializeModuleSource(
hasteModuleName,
generatedStructs,
hasteModuleName,
spec.eventEmitters,
methodSerializations.filter(
({selector}) => selector !== '@selector(constantsToExport)',
),
),
);
}
const headerFileName = `${libraryName}.h`;
const headerFile = HeaderFileTemplate({
headerFileName,
moduleDeclarations: moduleDeclarations.join('\n'),
structInlineMethods: structInlineMethods.join('\n'),
assumeNonnull,
});
const sourceFileName = `${libraryName}-generated.mm`;
const sourceFile = SourceFileTemplate({
headerFileName,
moduleImplementations: moduleImplementations.join('\n'),
});
return new Map([
[headerFileName, headerFile],
[sourceFileName, sourceFile],
]);
},
};

View File

@@ -0,0 +1,59 @@
/**
* 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
*/
const {toPascalCase} = require('../../Utils');
function getEventEmitterTypeObjCType(eventEmitter) {
const type = eventEmitter.typeAnnotation.typeAnnotation.type;
switch (type) {
case 'StringTypeAnnotation':
return 'NSString *_Nonnull';
case 'StringLiteralTypeAnnotation':
return 'NSString *_Nonnull';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *_Nonnull';
case 'NumberTypeAnnotation':
case 'NumberLiteralTypeAnnotation':
return 'NSNumber *_Nonnull';
case 'BooleanTypeAnnotation':
return 'BOOL';
case 'GenericObjectTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'TypeAliasTypeAnnotation':
return 'NSDictionary *';
case 'ArrayTypeAnnotation':
return 'NSArray<id<NSObject>> *';
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'VoidTypeAnnotation':
// TODO: Add support for these types
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
default:
type;
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
}
}
function EventEmitterHeaderTemplate(eventEmitter) {
return `- (void)emit${toPascalCase(eventEmitter.name)}${eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation' ? `:(${getEventEmitterTypeObjCType(eventEmitter)})value` : ''};`;
}
function EventEmitterImplementationTemplate(eventEmitter) {
return `- (void)emit${toPascalCase(eventEmitter.name)}${eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation' ? `:(${getEventEmitterTypeObjCType(eventEmitter)})value` : ''}
{
_eventEmitterCallback("${eventEmitter.name}", ${eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation' ? (eventEmitter.typeAnnotation.typeAnnotation.type !== 'BooleanTypeAnnotation' ? 'value' : '[NSNumber numberWithBool:value]') : 'nil'});
}`;
}
module.exports = {
EventEmitterHeaderTemplate,
EventEmitterImplementationTemplate,
};

View File

@@ -0,0 +1,87 @@
/**
* 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
*/
import type {NativeModuleEventEmitterShape} from '../../../CodegenSchema';
const {toPascalCase} = require('../../Utils');
function getEventEmitterTypeObjCType(
eventEmitter: NativeModuleEventEmitterShape,
): string {
const type = eventEmitter.typeAnnotation.typeAnnotation.type;
switch (type) {
case 'StringTypeAnnotation':
return 'NSString *_Nonnull';
case 'StringLiteralTypeAnnotation':
return 'NSString *_Nonnull';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *_Nonnull';
case 'NumberTypeAnnotation':
case 'NumberLiteralTypeAnnotation':
return 'NSNumber *_Nonnull';
case 'BooleanTypeAnnotation':
return 'BOOL';
case 'GenericObjectTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'TypeAliasTypeAnnotation':
return 'NSDictionary *';
case 'ArrayTypeAnnotation':
return 'NSArray<id<NSObject>> *';
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'VoidTypeAnnotation':
// TODO: Add support for these types
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
default:
(type: empty);
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
}
}
function EventEmitterHeaderTemplate(
eventEmitter: NativeModuleEventEmitterShape,
): string {
return `- (void)emit${toPascalCase(eventEmitter.name)}${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? `:(${getEventEmitterTypeObjCType(eventEmitter)})value`
: ''
};`;
}
function EventEmitterImplementationTemplate(
eventEmitter: NativeModuleEventEmitterShape,
): string {
return `- (void)emit${toPascalCase(eventEmitter.name)}${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? `:(${getEventEmitterTypeObjCType(eventEmitter)})value`
: ''
}
{
_eventEmitterCallback("${eventEmitter.name}", ${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? eventEmitter.typeAnnotation.typeAnnotation.type !==
'BooleanTypeAnnotation'
? 'value'
: '[NSNumber numberWithBool:value]'
: 'nil'
});
}`;
}
module.exports = {
EventEmitterHeaderTemplate,
EventEmitterImplementationTemplate,
};

View File

@@ -0,0 +1,462 @@
/**
* 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
*/
'use strict';
const {
unwrapNullable,
wrapNullable,
} = require('../../../parsers/parsers-commons');
const {wrapOptional} = require('../../TypeUtils/Objective-C');
const {capitalize} = require('../../Utils');
const {getNamespacedStructName} = require('./Utils');
const invariant = require('invariant');
const ProtocolMethodTemplate = ({returnObjCType, methodName, params}) =>
`- (${returnObjCType})${methodName}${params};`;
function serializeMethod(
hasteModuleName,
property,
structCollector,
resolveAlias,
) {
const {name: methodName, typeAnnotation: nullableTypeAnnotation} = property;
const [propertyTypeAnnotation] = unwrapNullable(nullableTypeAnnotation);
const {params} = propertyTypeAnnotation;
if (methodName === 'getConstants') {
return serializeConstantsProtocolMethods(
hasteModuleName,
property,
structCollector,
resolveAlias,
);
}
const methodParams = [];
const structParamRecords = [];
params.forEach((param, index) => {
const structName = getParamStructName(methodName, param);
const {objCType, isStruct} = getParamObjCType(
hasteModuleName,
methodName,
param,
structName,
structCollector,
resolveAlias,
);
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 [returnTypeAnnotation] = unwrapNullable(
propertyTypeAnnotation.returnTypeAnnotation,
);
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 [typeAnnotation] = unwrapNullable(param.typeAnnotation);
if (typeAnnotation.type === 'TypeAliasTypeAnnotation') {
return typeAnnotation.name;
}
return `Spec${capitalize(methodName)}${capitalize(param.name)}`;
}
function getParamObjCType(
hasteModuleName,
methodName,
param,
structName,
structCollector,
resolveAlias,
) {
const {name: paramName, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !param.optional && !nullable;
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(wrapOptional('NSArray *', !nullable));
}
}
const [structTypeAnnotation] = unwrapNullable(
structCollector.process(
structName,
'REGULAR',
resolveAlias,
wrapNullable(nullable, typeAnnotation),
),
);
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(isRequired ? 'double' : 'NSNumber *');
default:
structTypeAnnotation.name;
throw new Error(
`Unsupported type for param "${paramName}" in ${methodName}. Found: ${structTypeAnnotation.type}`,
);
}
case 'StringTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
case 'StringLiteralTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
case 'StringLiteralUnionTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
case 'NumberTypeAnnotation':
return notStruct(isRequired ? 'double' : 'NSNumber *');
case 'NumberLiteralTypeAnnotation':
return notStruct(isRequired ? 'double' : 'NSNumber *');
case 'FloatTypeAnnotation':
return notStruct(isRequired ? 'float' : 'NSNumber *');
case 'DoubleTypeAnnotation':
return notStruct(isRequired ? 'double' : 'NSNumber *');
case 'Int32TypeAnnotation':
return notStruct(isRequired ? 'NSInteger' : 'NSNumber *');
case 'BooleanTypeAnnotation':
return notStruct(isRequired ? 'BOOL' : 'NSNumber *');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return notStruct(isRequired ? 'double' : 'NSNumber *');
case 'StringTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
default:
throw new Error(
`Unsupported enum type for param "${paramName}" in ${methodName}. Found: ${typeAnnotation.type}`,
);
}
case 'GenericObjectTypeAnnotation':
return notStruct(wrapOptional('NSDictionary *', !nullable));
default:
structTypeAnnotation.type;
throw new Error(
`Unsupported type for param "${paramName}" in ${methodName}. Found: ${typeAnnotation.type}`,
);
}
}
function getReturnObjCType(methodName, nullableTypeAnnotation) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable;
switch (typeAnnotation.type) {
case 'VoidTypeAnnotation':
return 'void';
case 'PromiseTypeAnnotation':
return 'void';
case 'ObjectTypeAnnotation':
return wrapOptional('NSDictionary *', isRequired);
case 'TypeAliasTypeAnnotation':
return wrapOptional('NSDictionary *', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapOptional('NSArray<id<NSObject>> *', isRequired);
}
return wrapOptional(
`NSArray<${getReturnObjCType(methodName, typeAnnotation.elementType)}> *`,
isRequired,
);
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapOptional('NSNumber *', isRequired);
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 wrapOptional('NSString *', isRequired);
case 'StringLiteralTypeAnnotation':
// TODO: Can NSString * returns not be _Nullable?
// In the legacy codegen, we don't surround NSSTring * with _Nullable
return wrapOptional('NSString *', isRequired);
case 'StringLiteralUnionTypeAnnotation':
// TODO: Can NSString * returns not be _Nullable?
// In the legacy codegen, we don't surround NSSTring * with _Nullable
return wrapOptional('NSString *', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('NSString *', isRequired);
default:
throw new Error(
`Unsupported enum return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'ObjectTypeAnnotation':
return wrapOptional('NSDictionary *', isRequired);
case 'StringTypeAnnotation':
// TODO: Can NSString * returns not be _Nullable?
// In the legacy codegen, we don't surround NSSTring * with _Nullable
return wrapOptional('NSString *', isRequired);
default:
throw new Error(
`Unsupported union return type for ${methodName}, found: ${typeAnnotation.memberType}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapOptional('NSDictionary *', isRequired);
default:
typeAnnotation.type;
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
}
function getReturnJSType(methodName, nullableTypeAnnotation) {
const [typeAnnotation] = unwrapNullable(nullableTypeAnnotation);
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 'StringLiteralTypeAnnotation':
return 'StringKind';
case 'StringLiteralUnionTypeAnnotation':
return 'StringKind';
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'NumberLiteralTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
return 'NumberKind';
case 'DoubleTypeAnnotation':
return 'NumberKind';
case 'Int32TypeAnnotation':
return 'NumberKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
default:
typeAnnotation.type;
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
}
function serializeConstantsProtocolMethods(
hasteModuleName,
property,
structCollector,
resolveAlias,
) {
const [propertyTypeAnnotation] = unwrapNullable(property.typeAnnotation);
if (propertyTypeAnnotation.params.length !== 0) {
throw new Error(
`${hasteModuleName}.getConstants() may only accept 0 arguments.`,
);
}
let {returnTypeAnnotation} = propertyTypeAnnotation;
if (returnTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
// The return type is an alias, resolve it to get the expected undelying object literal type
returnTypeAnnotation = resolveAlias(returnTypeAnnotation.name);
}
if (returnTypeAnnotation.type !== 'ObjectTypeAnnotation') {
throw new Error(
`${hasteModuleName}.getConstants() may only return an object literal: {...}` +
` or a type alias of such. Got '${propertyTypeAnnotation.returnTypeAnnotation.type}'.`,
);
}
if (
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
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,
};

View File

@@ -0,0 +1,536 @@
/**
* 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 {
NamedShape,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleReturnTypeAnnotation,
Nullable,
} from '../../../CodegenSchema';
import type {AliasResolver} from '../Utils';
import type {StructCollector} from './StructCollector';
const {
unwrapNullable,
wrapNullable,
} = require('../../../parsers/parsers-commons');
const {wrapOptional} = require('../../TypeUtils/Objective-C');
const {capitalize} = require('../../Utils');
const {getNamespacedStructName} = require('./Utils');
const invariant = require('invariant');
const ProtocolMethodTemplate = ({
returnObjCType,
methodName,
params,
}: $ReadOnly<{
returnObjCType: string,
methodName: string,
params: string,
}>) => `- (${returnObjCType})${methodName}${params};`;
export type StructParameterRecord = $ReadOnly<{
paramIndex: number,
structName: string,
}>;
type ReturnJSType =
| 'VoidKind'
| 'BooleanKind'
| 'PromiseKind'
| 'ObjectKind'
| 'ArrayKind'
| 'NumberKind'
| 'StringKind';
export type MethodSerializationOutput = $ReadOnly<{
methodName: string,
protocolMethod: string,
selector: string,
structParamRecords: $ReadOnlyArray<StructParameterRecord>,
returnJSType: ReturnJSType,
argCount: number,
}>;
function serializeMethod(
hasteModuleName: string,
property: NativeModulePropertyShape,
structCollector: StructCollector,
resolveAlias: AliasResolver,
): $ReadOnlyArray<MethodSerializationOutput> {
const {name: methodName, typeAnnotation: nullableTypeAnnotation} = property;
const [propertyTypeAnnotation] = unwrapNullable(nullableTypeAnnotation);
const {params} = propertyTypeAnnotation;
if (methodName === 'getConstants') {
return serializeConstantsProtocolMethods(
hasteModuleName,
property,
structCollector,
resolveAlias,
);
}
const methodParams: Array<{paramName: string, objCType: string}> = [];
const structParamRecords: Array<StructParameterRecord> = [];
params.forEach((param, index) => {
const structName = getParamStructName(methodName, param);
const {objCType, isStruct} = getParamObjCType(
hasteModuleName,
methodName,
param,
structName,
structCollector,
resolveAlias,
);
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 [returnTypeAnnotation] = unwrapNullable(
propertyTypeAnnotation.returnTypeAnnotation,
);
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<string>(({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,
},
];
}
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function getParamStructName(methodName: string, param: Param): string {
const [typeAnnotation] = unwrapNullable(param.typeAnnotation);
if (typeAnnotation.type === 'TypeAliasTypeAnnotation') {
return typeAnnotation.name;
}
return `Spec${capitalize(methodName)}${capitalize(param.name)}`;
}
function getParamObjCType(
hasteModuleName: string,
methodName: string,
param: Param,
structName: string,
structCollector: StructCollector,
resolveAlias: AliasResolver,
): $ReadOnly<{objCType: string, isStruct: boolean}> {
const {name: paramName, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !param.optional && !nullable;
const isStruct = (objCType: string) => ({
isStruct: true,
objCType,
});
const notStruct = (objCType: string) => ({
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(wrapOptional('NSArray *', !nullable));
}
}
const [structTypeAnnotation] = unwrapNullable(
structCollector.process(
structName,
'REGULAR',
resolveAlias,
wrapNullable(nullable, typeAnnotation),
),
);
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(isRequired ? 'double' : 'NSNumber *');
default:
(structTypeAnnotation.name: empty);
throw new Error(
`Unsupported type for param "${paramName}" in ${methodName}. Found: ${structTypeAnnotation.type}`,
);
}
case 'StringTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
case 'StringLiteralTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
case 'StringLiteralUnionTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
case 'NumberTypeAnnotation':
return notStruct(isRequired ? 'double' : 'NSNumber *');
case 'NumberLiteralTypeAnnotation':
return notStruct(isRequired ? 'double' : 'NSNumber *');
case 'FloatTypeAnnotation':
return notStruct(isRequired ? 'float' : 'NSNumber *');
case 'DoubleTypeAnnotation':
return notStruct(isRequired ? 'double' : 'NSNumber *');
case 'Int32TypeAnnotation':
return notStruct(isRequired ? 'NSInteger' : 'NSNumber *');
case 'BooleanTypeAnnotation':
return notStruct(isRequired ? 'BOOL' : 'NSNumber *');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return notStruct(isRequired ? 'double' : 'NSNumber *');
case 'StringTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
default:
throw new Error(
`Unsupported enum type for param "${paramName}" in ${methodName}. Found: ${typeAnnotation.type}`,
);
}
case 'GenericObjectTypeAnnotation':
return notStruct(wrapOptional('NSDictionary *', !nullable));
default:
(structTypeAnnotation.type: empty);
throw new Error(
`Unsupported type for param "${paramName}" in ${methodName}. Found: ${typeAnnotation.type}`,
);
}
}
function getReturnObjCType(
methodName: string,
nullableTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable;
switch (typeAnnotation.type) {
case 'VoidTypeAnnotation':
return 'void';
case 'PromiseTypeAnnotation':
return 'void';
case 'ObjectTypeAnnotation':
return wrapOptional('NSDictionary *', isRequired);
case 'TypeAliasTypeAnnotation':
return wrapOptional('NSDictionary *', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapOptional('NSArray<id<NSObject>> *', isRequired);
}
return wrapOptional(
`NSArray<${getReturnObjCType(
methodName,
typeAnnotation.elementType,
)}> *`,
isRequired,
);
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapOptional('NSNumber *', isRequired);
default:
(typeAnnotation.name: empty);
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 wrapOptional('NSString *', isRequired);
case 'StringLiteralTypeAnnotation':
// TODO: Can NSString * returns not be _Nullable?
// In the legacy codegen, we don't surround NSSTring * with _Nullable
return wrapOptional('NSString *', isRequired);
case 'StringLiteralUnionTypeAnnotation':
// TODO: Can NSString * returns not be _Nullable?
// In the legacy codegen, we don't surround NSSTring * with _Nullable
return wrapOptional('NSString *', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('NSString *', isRequired);
default:
throw new Error(
`Unsupported enum return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'ObjectTypeAnnotation':
return wrapOptional('NSDictionary *', isRequired);
case 'StringTypeAnnotation':
// TODO: Can NSString * returns not be _Nullable?
// In the legacy codegen, we don't surround NSSTring * with _Nullable
return wrapOptional('NSString *', isRequired);
default:
throw new Error(
`Unsupported union return type for ${methodName}, found: ${typeAnnotation.memberType}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapOptional('NSDictionary *', isRequired);
default:
(typeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
}
function getReturnJSType(
methodName: string,
nullableTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
): ReturnJSType {
const [typeAnnotation] = unwrapNullable(nullableTypeAnnotation);
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 'StringLiteralTypeAnnotation':
return 'StringKind';
case 'StringLiteralUnionTypeAnnotation':
return 'StringKind';
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'NumberLiteralTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
return 'NumberKind';
case 'DoubleTypeAnnotation':
return 'NumberKind';
case 'Int32TypeAnnotation':
return 'NumberKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
default:
(typeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
}
}
function serializeConstantsProtocolMethods(
hasteModuleName: string,
property: NativeModulePropertyShape,
structCollector: StructCollector,
resolveAlias: AliasResolver,
): $ReadOnlyArray<MethodSerializationOutput> {
const [propertyTypeAnnotation] = unwrapNullable(property.typeAnnotation);
if (propertyTypeAnnotation.params.length !== 0) {
throw new Error(
`${hasteModuleName}.getConstants() may only accept 0 arguments.`,
);
}
let {returnTypeAnnotation} = propertyTypeAnnotation;
if (returnTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
// The return type is an alias, resolve it to get the expected undelying object literal type
returnTypeAnnotation = resolveAlias(returnTypeAnnotation.name);
}
if (returnTypeAnnotation.type !== 'ObjectTypeAnnotation') {
throw new Error(
`${hasteModuleName}.getConstants() may only return an object literal: {...}` +
` or a type alias of such. Got '${propertyTypeAnnotation.returnTypeAnnotation.type}'.`,
);
}
if (
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
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<MethodSerializationOutput>(
methodName => {
const protocolMethod = ProtocolMethodTemplate({
methodName,
returnObjCType,
params: '',
});
return {
methodName,
protocolMethod,
returnJSType: 'ObjectKind',
selector: `@selector(${methodName})`,
structParamRecords: [],
argCount: 0,
};
},
);
}
module.exports = {
serializeMethod,
};

View File

@@ -0,0 +1,129 @@
/**
* 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
*/
'use strict';
const {
EventEmitterImplementationTemplate,
} = require('./../serializeEventEmitter');
const ModuleTemplate = ({
hasteModuleName,
structs,
moduleName,
eventEmitters,
methodSerializationOutputs,
}) => `
@implementation ${hasteModuleName}SpecBase
${eventEmitters.map(eventEmitter => EventEmitterImplementationTemplate(eventEmitter)).join('\n')}
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper
{
_eventEmitterCallback = std::move(eventEmitterCallbackWrapper->_eventEmitterCallback);
}
@end
${structs
.map(struct =>
RCTCxxConvertCategoryTemplate({
hasteModuleName,
structName: struct.name,
}),
)
.join('\n')}
namespace facebook::react {
${methodSerializationOutputs
.map(serializedMethodParts =>
InlineHostFunctionTemplate({
hasteModuleName,
methodName: serializedMethodParts.methodName,
returnJSType: serializedMethodParts.returnJSType,
selector: serializedMethodParts.selector,
}),
)
.join('\n')}
${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const ObjCTurboModule::InitParams &params)
: ObjCTurboModule(params) {
${methodSerializationOutputs
.map(({methodName, structParamRecords, argCount}) =>
MethodMapEntryTemplate({
hasteModuleName,
methodName,
structParamRecords,
argCount,
}),
)
.join('\n' + ' '.repeat(8))}${
eventEmitters.length > 0
? eventEmitters
.map(eventEmitter => {
return `
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<id>>();`;
})
.join('')
: ''
}${
eventEmitters.length > 0
? `
setEventEmitterCallback([&](const std::string &name, id value) {
static_cast<AsyncEventEmitter<id> &>(*eventEmitterMap_[name]).emit(value);
});`
: ''
}
}
} // namespace facebook::react`;
const RCTCxxConvertCategoryTemplate = ({
hasteModuleName,
structName,
}) => `@implementation RCTCxxConvert (${hasteModuleName}_${structName})
+ (RCTManagedPointer *)JS_${hasteModuleName}_${structName}:(id)json
{
return facebook::react::managedPointer<JS::${hasteModuleName}::${structName}>(json);
}
@end`;
const InlineHostFunctionTemplate = ({
hasteModuleName,
methodName,
returnJSType,
selector,
}) => `
static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${methodName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, ${returnJSType}, "${methodName}", ${selector}, args, count);
}`;
const MethodMapEntryTemplate = ({
hasteModuleName,
methodName,
structParamRecords,
argCount,
}) => `
methodMap_["${methodName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${methodName}};
${structParamRecords
.map(({paramIndex, structName}) => {
return `setMethodArgConversionSelector(@"${methodName}", ${paramIndex}, @"JS_${hasteModuleName}_${structName}:");`;
})
.join('\n' + ' '.repeat(8))}`;
function serializeModuleSource(
hasteModuleName,
structs,
moduleName,
eventEmitters,
methodSerializationOutputs,
) {
return ModuleTemplate({
hasteModuleName,
structs: structs.filter(({context}) => context !== 'CONSTANTS'),
moduleName,
eventEmitters,
methodSerializationOutputs,
});
}
module.exports = {
serializeModuleSource,
};

View File

@@ -0,0 +1,160 @@
/**
* 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 {NativeModuleEventEmitterShape} from '../../../../CodegenSchema';
import type {
MethodSerializationOutput,
StructParameterRecord,
} from '../serializeMethod';
import type {Struct} from '../StructCollector';
const {
EventEmitterImplementationTemplate,
} = require('./../serializeEventEmitter');
const ModuleTemplate = ({
hasteModuleName,
structs,
moduleName,
eventEmitters,
methodSerializationOutputs,
}: $ReadOnly<{
hasteModuleName: string,
structs: $ReadOnlyArray<Struct>,
moduleName: string,
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
methodSerializationOutputs: $ReadOnlyArray<MethodSerializationOutput>,
}>) => `
@implementation ${hasteModuleName}SpecBase
${eventEmitters
.map(eventEmitter => EventEmitterImplementationTemplate(eventEmitter))
.join('\n')}
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper
{
_eventEmitterCallback = std::move(eventEmitterCallbackWrapper->_eventEmitterCallback);
}
@end
${structs
.map(struct =>
RCTCxxConvertCategoryTemplate({hasteModuleName, structName: struct.name}),
)
.join('\n')}
namespace facebook::react {
${methodSerializationOutputs
.map(serializedMethodParts =>
InlineHostFunctionTemplate({
hasteModuleName,
methodName: serializedMethodParts.methodName,
returnJSType: serializedMethodParts.returnJSType,
selector: serializedMethodParts.selector,
}),
)
.join('\n')}
${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const ObjCTurboModule::InitParams &params)
: ObjCTurboModule(params) {
${methodSerializationOutputs
.map(({methodName, structParamRecords, argCount}) =>
MethodMapEntryTemplate({
hasteModuleName,
methodName,
structParamRecords,
argCount,
}),
)
.join('\n' + ' '.repeat(8))}${
eventEmitters.length > 0
? eventEmitters
.map(eventEmitter => {
return `
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<id>>();`;
})
.join('')
: ''
}${
eventEmitters.length > 0
? `
setEventEmitterCallback([&](const std::string &name, id value) {
static_cast<AsyncEventEmitter<id> &>(*eventEmitterMap_[name]).emit(value);
});`
: ''
}
}
} // namespace facebook::react`;
const RCTCxxConvertCategoryTemplate = ({
hasteModuleName,
structName,
}: $ReadOnly<{
hasteModuleName: string,
structName: string,
}>) => `@implementation RCTCxxConvert (${hasteModuleName}_${structName})
+ (RCTManagedPointer *)JS_${hasteModuleName}_${structName}:(id)json
{
return facebook::react::managedPointer<JS::${hasteModuleName}::${structName}>(json);
}
@end`;
const InlineHostFunctionTemplate = ({
hasteModuleName,
methodName,
returnJSType,
selector,
}: $ReadOnly<{
hasteModuleName: string,
methodName: string,
returnJSType: string,
selector: string,
}>) => `
static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${methodName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, ${returnJSType}, "${methodName}", ${selector}, args, count);
}`;
const MethodMapEntryTemplate = ({
hasteModuleName,
methodName,
structParamRecords,
argCount,
}: $ReadOnly<{
hasteModuleName: string,
methodName: string,
structParamRecords: $ReadOnlyArray<StructParameterRecord>,
argCount: number,
}>) => `
methodMap_["${methodName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${methodName}};
${structParamRecords
.map(({paramIndex, structName}) => {
return `setMethodArgConversionSelector(@"${methodName}", ${paramIndex}, @"JS_${hasteModuleName}_${structName}:");`;
})
.join('\n' + ' '.repeat(8))}`;
function serializeModuleSource(
hasteModuleName: string,
structs: $ReadOnlyArray<Struct>,
moduleName: string,
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
methodSerializationOutputs: $ReadOnlyArray<MethodSerializationOutput>,
): string {
return ModuleTemplate({
hasteModuleName,
structs: structs.filter(({context}) => context !== 'CONSTANTS'),
moduleName,
eventEmitters,
methodSerializationOutputs,
});
}
module.exports = {
serializeModuleSource,
};

View File

@@ -0,0 +1,59 @@
/**
* 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
*/
'use strict';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const invariant = require('invariant');
function createAliasResolver(aliasMap) {
return aliasName => {
const alias = aliasMap[aliasName];
invariant(alias != null, `Unable to resolve type alias '${aliasName}'.`);
return alias;
};
}
function getModules(schema) {
return Object.keys(schema.modules).reduce((modules, hasteModuleName) => {
const module = schema.modules[hasteModuleName];
if (module == null || module.type === 'Component') {
return modules;
}
modules[hasteModuleName] = module;
return modules;
}, {});
}
function isDirectRecursiveMember(
parentObjectAliasName,
nullableTypeAnnotation,
) {
const [typeAnnotation] = unwrapNullable(nullableTypeAnnotation);
return (
parentObjectAliasName !== undefined &&
typeAnnotation.name === parentObjectAliasName
);
}
function isArrayRecursiveMember(parentObjectAliasName, nullableTypeAnnotation) {
var _typeAnnotation$eleme;
const [typeAnnotation] = unwrapNullable(nullableTypeAnnotation);
return (
parentObjectAliasName !== undefined &&
typeAnnotation.type === 'ArrayTypeAnnotation' &&
((_typeAnnotation$eleme = typeAnnotation.elementType) === null ||
_typeAnnotation$eleme === void 0
? void 0
: _typeAnnotation$eleme.name) === parentObjectAliasName
);
}
module.exports = {
createAliasResolver,
getModules,
isDirectRecursiveMember,
isArrayRecursiveMember,
};

View File

@@ -0,0 +1,85 @@
/**
* 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 {
NativeModuleAliasMap,
NativeModuleObjectTypeAnnotation,
NativeModuleSchema,
NativeModuleTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const invariant = require('invariant');
export type AliasResolver = (
aliasName: string,
) => NativeModuleObjectTypeAnnotation;
function createAliasResolver(aliasMap: NativeModuleAliasMap): AliasResolver {
return (aliasName: string) => {
const alias = aliasMap[aliasName];
invariant(alias != null, `Unable to resolve type alias '${aliasName}'.`);
return alias;
};
}
function getModules(
schema: SchemaType,
): $ReadOnly<{[hasteModuleName: string]: NativeModuleSchema}> {
return Object.keys(schema.modules).reduce<{[string]: NativeModuleSchema}>(
(modules, hasteModuleName: string) => {
const module = schema.modules[hasteModuleName];
if (module == null || module.type === 'Component') {
return modules;
}
modules[hasteModuleName] = module;
return modules;
},
{},
);
}
function isDirectRecursiveMember(
parentObjectAliasName: ?string,
nullableTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
): boolean {
const [typeAnnotation] = unwrapNullable<NativeModuleTypeAnnotation>(
nullableTypeAnnotation,
);
return (
parentObjectAliasName !== undefined &&
typeAnnotation.name === parentObjectAliasName
);
}
function isArrayRecursiveMember(
parentObjectAliasName: ?string,
nullableTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
): boolean {
const [typeAnnotation] = unwrapNullable<NativeModuleTypeAnnotation>(
nullableTypeAnnotation,
);
return (
parentObjectAliasName !== undefined &&
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType?.name === parentObjectAliasName
);
}
module.exports = {
createAliasResolver,
getModules,
isDirectRecursiveMember,
isArrayRecursiveMember,
};