diff --git a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js new file mode 100644 index 00000000000000..a5e49fb51675fd --- /dev/null +++ b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js @@ -0,0 +1,177 @@ +/** + * 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 {ObjectParamTypeAnnotation} from '../../../CodegenSchema'; +import {flatObjects, capitalizeFirstLetter} from './Utils'; + +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. + */ + +#import +::_STRUCTS_:: + +::_INLINES_:: +`; + +const structTemplate = ` +namespace JS { + namespace Native::_MODULE_NAME_:: { + struct Spec::_STRUCT_NAME_:: { + ::_STRUCT_PROPERTIES_:: + + Spec::_STRUCT_NAME_::(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (Native::_MODULE_NAME_::_Spec::_STRUCT_NAME_::) ++ (RCTManagedPointer *)JS_Native::_MODULE_NAME_::_Spec::_STRUCT_NAME_:::(id)json; +@end +`; + +const inlineTemplate = ` +inline ::_RETURN_TYPE_:: *JS::Native::_MODULE_NAME_::::Spec::_STRUCT_NAME_::::::_PROPERTY_NAME_::() const +{ + id const p = _v[@"a"]; + return ::_RETURN_VALUE_::; +} +`; + +function getInlineMethodSignature( + property: ObjectParamTypeAnnotation, + name: string, +): string { + const {typeAnnotation} = property; + switch (typeAnnotation.type) { + case 'StringTypeAnnotation': + return `NSString *${property.name}() const;`; + case 'NumberTypeAnnotation': + case 'FloatTypeAnnotation': + case 'Int32TypeAnnotation': + return `double ${property.name}() const;`; + case 'BooleanTypeAnnotation': + return `bool ${property.name}() const;`; + case 'ObjectTypeAnnotation': + return `JS::Native::_MODULE_NAME_::::Spec${name}${capitalizeFirstLetter( + property.name, + )} ${property.name}() const;`; + case 'GenericObjectTypeAnnotation': + case 'AnyTypeAnnotation': + return `id ${property.name}() const;`; + case 'FunctionTypeAnnotation': + case 'ArrayTypeAnnotation': + default: + throw new Error(`Unknown prop type, found: ${typeAnnotation.type}"`); + } +} + +function getInlineMethodImplementation( + property: ObjectParamTypeAnnotation, + name: string, +): string { + const {typeAnnotation} = property; + switch (typeAnnotation.type) { + case 'StringTypeAnnotation': + return inlineTemplate + .replace(/::_RETURN_TYPE_::/, 'NSString') + .replace(/::_RETURN_VALUE_::/, 'RCTBridgingToString(p)'); + case 'NumberTypeAnnotation': + case 'FloatTypeAnnotation': + case 'Int32TypeAnnotation': + return inlineTemplate + .replace(/::_RETURN_TYPE_::/, 'double') + .replace(/::_RETURN_VALUE_::/, 'RCTBridgingToDouble(p)'); + case 'BooleanTypeAnnotation': + return inlineTemplate + .replace(/::_RETURN_TYPE_::/, 'bool') + .replace(/::_RETURN_VALUE_::/, 'RCTBridgingToBool(p)'); + case 'GenericObjectTypeAnnotation': + case 'AnyTypeAnnotation': + return inlineTemplate + .replace(/::_RETURN_TYPE_::/, 'id') + .replace(/::_RETURN_VALUE_::/, 'p'); + case 'ObjectTypeAnnotation': + return inlineTemplate + .replace( + /::_RETURN_TYPE_::/, + `JS::Native::_MODULE_NAME_::::Spec${name}${capitalizeFirstLetter( + property.name, + )}`, + ) + .replace( + /::_RETURN_VALUE_::/, + `JS::Native::_MODULE_NAME_::::Spec${name}${capitalizeFirstLetter( + property.name, + )}(p)`, + ); + case 'FunctionTypeAnnotation': + case 'ArrayTypeAnnotation': + default: + throw new Error(`Unknown prop type, found: ${typeAnnotation.type}"`); + } +} + +function translateObjectsForStructs( + annotations: $ReadOnlyArray< + $ReadOnly<{| + name: string, + object: $ReadOnly<{| + type: 'ObjectTypeAnnotation', + properties: $ReadOnlyArray, + |}>, + |}>, + >, +): string { + const flattenObjects = flatObjects(annotations); + + const translatedInlineMethods = flattenObjects + .reduce( + (acc, object) => + acc.concat( + object.properties.map(property => + getInlineMethodImplementation(property, object.name) + .replace(/::_PROPERTY_NAME_::/g, property.name) + .replace(/::_STRUCT_NAME_::/g, object.name), + ), + ), + [], + ) + .join('\n'); + + const translatedStructs = flattenObjects + .map(object => + structTemplate + .replace( + /::_STRUCT_PROPERTIES_::/g, + object.properties + .map(property => getInlineMethodSignature(property, object.name)) + .join('\n '), + ) + .replace(/::_STRUCT_NAME_::/g, object.name), + ) + .join('\n'); + + return template + .replace(/::_STRUCTS_::/, translatedStructs) + .replace(/::_INLINES_::/, translatedInlineMethods); +} +module.exports = { + translateObjectsForStructs, + capitalizeFirstLetter, +}; diff --git a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js new file mode 100644 index 00000000000000..acd95b79eaf537 --- /dev/null +++ b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js @@ -0,0 +1,80 @@ +/** + * 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 {ObjectParamTypeAnnotation} from '../../../CodegenSchema'; + +function capitalizeFirstLetter(string: string): string { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +function flatObjects( + annotations: $ReadOnlyArray< + $ReadOnly<{| + name: string, + object: $ReadOnly<{| + type: 'ObjectTypeAnnotation', + properties: $ReadOnlyArray, + |}>, + |}>, + >, +): $ReadOnlyArray< + $ReadOnly<{| + name: string, + properties: $ReadOnlyArray, + |}>, +> { + let objectTypesToFlatten: Array<{| + properties: $ReadOnlyArray, + name: string, + |}> = annotations.map(annotation => ({ + name: annotation.name, + properties: annotation.object.properties, + })); + + let flattenObjects: Array<{| + properties: $ReadOnlyArray, + name: string, + |}> = []; + + while (objectTypesToFlatten.length !== 0) { + const oldObjectTypesToFlatten = objectTypesToFlatten; + objectTypesToFlatten = []; + flattenObjects = flattenObjects.concat( + oldObjectTypesToFlatten.map(object => { + const {properties} = object; + if (properties !== undefined) { + objectTypesToFlatten = objectTypesToFlatten.concat( + properties.reduce((acc, curr) => { + if ( + curr.typeAnnotation.type === 'ObjectTypeAnnotation' && + curr.typeAnnotation.properties + ) { + return acc.concat({ + properties: curr.typeAnnotation.properties, + name: object.name + capitalizeFirstLetter(curr.name), + }); + } + return acc; + }, []), + ); + } + return object; + }), + ); + } + + return flattenObjects; +} +module.exports = { + flatObjects, + capitalizeFirstLetter, +}; diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/structFixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/structFixtures.js new file mode 100644 index 00000000000000..d73174ff9ed272 --- /dev/null +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/structFixtures.js @@ -0,0 +1,108 @@ +/** + * 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-local + * @format + */ + +'use strict'; + +import type {ObjectParamTypeAnnotation} from '../../../CodegenSchema.js'; +const SIMPLE_STRUCT: $ReadOnlyArray< + $ReadOnly<{| + name: string, + object: $ReadOnly<{| + type: 'ObjectTypeAnnotation', + properties: $ReadOnlyArray, + |}>, + |}>, +> = [ + { + name: 'SampleFuncReturnType', + object: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'a', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + { + optional: false, + name: 'b', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'c', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: false, + name: 'd', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'e', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + { + optional: false, + name: 'f', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'g', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'h', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + { + optional: false, + name: 'i', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'j', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, +]; +module.exports = { + SIMPLE_STRUCT, +}; diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js new file mode 100644 index 00000000000000..ef865d1ef80c8c --- /dev/null +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js @@ -0,0 +1,31 @@ +/** + * 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__/structFixtures.js'); +const generator = require('../ObjCppUtils/GenerateStructs.js'); + +describe('GenerateStructs', () => { + Object.keys(fixtures) + .sort() + .forEach(fixtureName => { + const fixture = fixtures[fixtureName]; + + it(`can generate fixture ${fixtureName}`, () => { + expect( + generator + .translateObjectsForStructs(fixture) + .replace(/::_MODULE_NAME_::/g, 'SampleTurboModule'), + ).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateStructs-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateStructs-test.js.snap new file mode 100644 index 00000000000000..bbd51b201ab534 --- /dev/null +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateStructs-test.js.snap @@ -0,0 +1,143 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GenerateStructs can generate fixture SIMPLE_STRUCT 1`] = ` +" +/** + * 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. + */ + +#import + +namespace JS { + namespace NativeSampleTurboModule { + struct SpecSampleFuncReturnType { + bool a() const; + double b() const; + NSString *c() const; + JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeD d() const; + + SpecSampleFuncReturnType(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeSampleTurboModule_SpecSampleFuncReturnType) ++ (RCTManagedPointer *)JS_NativeSampleTurboModule_SpecSampleFuncReturnType:(id)json; +@end + + +namespace JS { + namespace NativeSampleTurboModule { + struct SpecSampleFuncReturnTypeD { + bool e() const; + double f() const; + JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeDG g() const; + + SpecSampleFuncReturnTypeD(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeSampleTurboModule_SpecSampleFuncReturnTypeD) ++ (RCTManagedPointer *)JS_NativeSampleTurboModule_SpecSampleFuncReturnTypeD:(id)json; +@end + + +namespace JS { + namespace NativeSampleTurboModule { + struct SpecSampleFuncReturnTypeDG { + bool h() const; + double i() const; + NSString *j() const; + + SpecSampleFuncReturnTypeDG(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeSampleTurboModule_SpecSampleFuncReturnTypeDG) ++ (RCTManagedPointer *)JS_NativeSampleTurboModule_SpecSampleFuncReturnTypeDG:(id)json; +@end + + + +inline bool *JS::NativeSampleTurboModule::SpecSampleFuncReturnType::a() const +{ + id const p = _v[@\\"a\\"]; + return RCTBridgingToBool(p); +} + + +inline double *JS::NativeSampleTurboModule::SpecSampleFuncReturnType::b() const +{ + id const p = _v[@\\"a\\"]; + return RCTBridgingToDouble(p); +} + + +inline NSString *JS::NativeSampleTurboModule::SpecSampleFuncReturnType::c() const +{ + id const p = _v[@\\"a\\"]; + return RCTBridgingToString(p); +} + + +inline JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeD *JS::NativeSampleTurboModule::SpecSampleFuncReturnType::d() const +{ + id const p = _v[@\\"a\\"]; + return JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeD(p); +} + + +inline bool *JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeD::e() const +{ + id const p = _v[@\\"a\\"]; + return RCTBridgingToBool(p); +} + + +inline double *JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeD::f() const +{ + id const p = _v[@\\"a\\"]; + return RCTBridgingToDouble(p); +} + + +inline JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeDG *JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeD::g() const +{ + id const p = _v[@\\"a\\"]; + return JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeDG(p); +} + + +inline bool *JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeDG::h() const +{ + id const p = _v[@\\"a\\"]; + return RCTBridgingToBool(p); +} + + +inline double *JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeDG::i() const +{ + id const p = _v[@\\"a\\"]; + return RCTBridgingToDouble(p); +} + + +inline NSString *JS::NativeSampleTurboModule::SpecSampleFuncReturnTypeDG::j() const +{ + id const p = _v[@\\"a\\"]; + return RCTBridgingToString(p); +} + +" +`;