diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js new file mode 100644 index 0000000000..70f6e8f49c --- /dev/null +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js @@ -0,0 +1,180 @@ +/** + * Copyright (c) Facebook, Inc. and its 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, + FunctionTypeAnnotationParamTypeAnnotation, + FunctionTypeAnnotationReturn, +} from '../../CodegenSchema'; + +type FilesOutput = Map; + +const moduleTemplate = ` +class JSI_EXPORT Native::_MODULE_NAME_::SpecJSI : public ObjCTurboModule { +public: + Native::_MODULE_NAME_::SpecJSI(id instance, std::shared_ptr jsInvoker); +};`; + +const protolocTemplate = ` +@protocol Native::_MODULE_NAME_::Spec +::_MODULE_PROPERTIES_:: +@end +`; + +const callbackArgs = prop => + prop.typeAnnotation.returnTypeAnnotation.type === + 'GenericPromiseTypeAnnotation' + ? `${ + prop.typeAnnotation.params.length === 0 ? '' : '\n resolve' + }:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject` + : ''; + +const template = ` +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// NOTE: This entire file should be codegen'ed. + +#import + +#import + +#import + +#import + +/** + * The ObjC protocol based on the JS Flow type for SampleTurboModule. + */ + +::_PROTOCOLS_:: + +namespace facebook { +namespace react { +::_MODULES_:: + +} // namespace react +} // namespace facebook +`; + +function translatePrimitiveJSTypeToObjCType( + type: + | FunctionTypeAnnotationParamTypeAnnotation + | FunctionTypeAnnotationReturn, + error: string, +) { + switch (type.type) { + case 'VoidTypeAnnotation': + case 'GenericPromiseTypeAnnotation': + return 'void'; + case 'StringTypeAnnotation': + return 'NSString *'; + case 'NumberTypeAnnotation': + case 'FloatTypeAnnotation': + case 'Int32TypeAnnotation': + return 'NSNumber *'; + case 'BooleanTypeAnnotation': + return 'BOOL'; + case 'GenericObjectTypeAnnotation': + case 'ObjectTypeAnnotation': + return 'NSDictionary *'; + case 'ArrayTypeAnnotation': + return 'NSArray> *'; + case 'FunctionTypeAnnotation': + return 'RCTResponseSenderBlock'; + + default: + throw new Error(error); + } +} +const methodImplementationTemplate = + '- (::_RETURN_VALUE_::) ::_PROPERTY_NAME_::::_ARGS_::;'; + +module.exports = { + generate(libraryName: string, schema: SchemaType): FilesOutput { + const nativeModules = Object.keys(schema.modules) + .map(moduleName => { + const modules = schema.modules[moduleName].nativeModules; + if (modules == null) { + return null; + } + + return modules; + }) + .filter(Boolean) + .reduce((acc, components) => Object.assign(acc, components), {}); + + const modules = Object.keys(nativeModules) + .map(name => moduleTemplate.replace(/::_MODULE_NAME_::/g, name)) + .join('\n'); + + const protocols = Object.keys(nativeModules) + .map(name => { + const {properties} = nativeModules[name]; + const implementations = properties + .map(prop => { + const nativeArgs = prop.typeAnnotation.params + .map((param, i) => { + const paramObjCType = translatePrimitiveJSTypeToObjCType( + param.typeAnnotation, + `Unspopported type for param "${param.name}" in ${ + prop.name + }. Found: ${param.typeAnnotation.type}`, + ); + return `${i === 0 ? '' : param.name}:(${paramObjCType})${ + param.name + }`; + }) + .join('\n ') + .concat(callbackArgs(prop)); + const implementation = methodImplementationTemplate + .replace('::_PROPERTY_NAME_::', prop.name) + .replace( + '::_RETURN_VALUE_::', + translatePrimitiveJSTypeToObjCType( + prop.typeAnnotation.returnTypeAnnotation, + `Unspopported return type for ${prop.name}. Found: ${ + prop.typeAnnotation.returnTypeAnnotation.type + }`, + ), + ) + .replace('::_ARGS_::', nativeArgs); + if (prop.name === 'getConstants') { + return ( + implementation + + '\n' + + implementation.replace('getConstants', 'constantsToExport') + ); + } + return implementation; + }) + .join('\n'); + return protolocTemplate + .replace(/::_MODULE_PROPERTIES_::/g, implementations) + .replace(/::_MODULE_NAME_::/g, name) + .replace('::_PROPERTIES_MAP_::', ''); + }) + .join('\n'); + + const fileName = 'RCTNativeModules.h'; + const replacedTemplate = template + .replace(/::_MODULES_::/g, modules) + .replace(/::_PROTOCOLS_::/g, protocols); + + return new Map([[fileName, replacedTemplate]]); + }, +}; diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js new file mode 100644 index 0000000000..ae8da6f16b --- /dev/null +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+react_native + * @flow strict-local + * @format + */ + +'use strict'; + +const fixtures = require('../__test_fixtures__/fixtures.js'); +const generator = require('../GenerateModuleHObjCpp.js'); + +describe('GenerateModuleHObjCpp', () => { + Object.keys(fixtures) + .sort() + .forEach(fixtureName => { + const fixture = fixtures[fixtureName]; + + it(`can generate fixture ${fixtureName}`, () => { + expect(generator.generate(fixtureName, fixture)).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap new file mode 100644 index 0000000000..61b296a781 --- /dev/null +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap @@ -0,0 +1,211 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GenerateModuleHObjCpp can generate fixture EMPTY_NATIVE_MODULES 1`] = ` +Map { + "RCTNativeModules.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// NOTE: This entire file should be codegen'ed. + +#import + +#import + +#import + +#import + +/** + * The ObjC protocol based on the JS Flow type for SampleTurboModule. + */ + + +@protocol NativeSampleTurboModuleSpec + +@end + + +namespace facebook { +namespace react { + +class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule { +public: + NativeSampleTurboModuleSpecJSI(id instance, std::shared_ptr jsInvoker); +}; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateModuleHObjCpp can generate fixture SIMPLE_NATIVE_MODULES 1`] = ` +Map { + "RCTNativeModules.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// NOTE: This entire file should be codegen'ed. + +#import + +#import + +#import + +#import + +/** + * The ObjC protocol based on the JS Flow type for SampleTurboModule. + */ + + +@protocol NativeSampleTurboModuleSpec +- (NSDictionary *) getConstants; +- (NSDictionary *) constantsToExport; +- (void) voidFunc; +- (BOOL) getBool:(BOOL)arg; +- (NSNumber *) getNumber:(NSNumber *)arg; +- (NSString *) getString:(NSString *)arg; +- (NSArray> *) getArray:(NSArray> *)arg; +- (NSDictionary *) getObject:(NSDictionary *)arg; +- (NSDictionary *) getValue:(NSNumber *)x + y:(NSString *)y + z:(NSDictionary *)z; +- (void) getValueWithCallback:(RCTResponseSenderBlock)callback; +- (void) getValueWithPromise:(BOOL)error + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; +@end + + +namespace facebook { +namespace react { + +class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule { +public: + NativeSampleTurboModuleSpecJSI(id instance, std::shared_ptr jsInvoker); +}; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateModuleHObjCpp can generate fixture TWO_MODULES_DIFFERENT_FILES 1`] = ` +Map { + "RCTNativeModules.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// NOTE: This entire file should be codegen'ed. + +#import + +#import + +#import + +#import + +/** + * The ObjC protocol based on the JS Flow type for SampleTurboModule. + */ + + +@protocol NativeSampleTurboModuleSpec +- (void) voidFunc; +@end + + +@protocol NativeSample2TurboModuleSpec +- (void) voidFunc; +@end + + +namespace facebook { +namespace react { + +class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule { +public: + NativeSampleTurboModuleSpecJSI(id instance, std::shared_ptr jsInvoker); +}; + +class JSI_EXPORT NativeSample2TurboModuleSpecJSI : public ObjCTurboModule { +public: + NativeSample2TurboModuleSpecJSI(id instance, std::shared_ptr jsInvoker); +}; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateModuleHObjCpp can generate fixture TWO_MODULES_SAME_FILE 1`] = ` +Map { + "RCTNativeModules.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// NOTE: This entire file should be codegen'ed. + +#import + +#import + +#import + +#import + +/** + * The ObjC protocol based on the JS Flow type for SampleTurboModule. + */ + + +@protocol NativeSampleTurboModuleSpec +- (void) voidFunc; +@end + + +@protocol NativeSample2TurboModuleSpec +- (void) voidFunc; +@end + + +namespace facebook { +namespace react { + +class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule { +public: + NativeSampleTurboModuleSpecJSI(id instance, std::shared_ptr jsInvoker); +}; + +class JSI_EXPORT NativeSample2TurboModuleSpecJSI : public ObjCTurboModule { +public: + NativeSample2TurboModuleSpecJSI(id instance, std::shared_ptr jsInvoker); +}; + +} // namespace react +} // namespace facebook +", +} +`;