/** * 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 getOrdinalNumber(num) { switch (num) { case 1: return '1st'; case 2: return '2nd'; case 3: return '3rd'; } if (num <= 20) { return `${num}th`; } return 'unknown'; } const ProtocolTemplate = ({componentName, methods}) => ` @protocol RCT${componentName}ViewProtocol ${methods} @end `.trim(); const CommandHandlerIfCaseConvertArgTemplate = ({ componentName, expectedKind, argNumber, argNumberString, expectedKindString, argConversion, }) => ` NSObject *arg${argNumber} = args[${argNumber}]; #if RCT_DEBUG if (!RCTValidateTypeOfViewCommandArgument(arg${argNumber}, ${expectedKind}, @"${expectedKindString}", @"${componentName}", commandName, @"${argNumberString}")) { return; } #endif ${argConversion} `.trim(); const CommandHandlerIfCaseTemplate = ({ componentName, commandName, numArgs, convertArgs, commandCall, }) => ` if ([commandName isEqualToString:@"${commandName}"]) { #if RCT_DEBUG if ([args count] != ${numArgs}) { RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"${componentName}", commandName, (int)[args count], ${numArgs}); return; } #endif ${convertArgs} ${commandCall} return; } `.trim(); const CommandHandlerTemplate = ({componentName, ifCases}) => ` RCT_EXTERN inline void RCT${componentName}HandleCommand( id componentView, NSString const *commandName, NSArray const *args) { ${ifCases} #if RCT_DEBUG RCTLogError(@"%@ received command %@, which is not a supported command.", @"${componentName}", commandName); #endif } `.trim(); const FileTemplate = ({componentContent}) => ` /** * 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: GenerateComponentHObjCpp.js */ #import #import #import NS_ASSUME_NONNULL_BEGIN ${componentContent} NS_ASSUME_NONNULL_END `.trim(); function getObjCParamType(param) { const typeAnnotation = param.typeAnnotation; switch (typeAnnotation.type) { case 'ReservedTypeAnnotation': switch (typeAnnotation.name) { case 'RootTag': return 'double'; default: typeAnnotation.name; throw new Error(`Receieved invalid type: ${typeAnnotation.name}`); } case 'BooleanTypeAnnotation': return 'BOOL'; case 'DoubleTypeAnnotation': return 'double'; case 'FloatTypeAnnotation': return 'float'; case 'Int32TypeAnnotation': return 'NSInteger'; case 'StringTypeAnnotation': return 'NSString *'; default: typeAnnotation.type; throw new Error('Received invalid param type annotation'); } } function getObjCExpectedKindParamType(param) { const typeAnnotation = param.typeAnnotation; switch (typeAnnotation.type) { case 'ReservedTypeAnnotation': switch (typeAnnotation.name) { case 'RootTag': return '[NSNumber class]'; default: typeAnnotation.name; throw new Error(`Receieved invalid type: ${typeAnnotation.name}`); } case 'BooleanTypeAnnotation': return '[NSNumber class]'; case 'DoubleTypeAnnotation': return '[NSNumber class]'; case 'FloatTypeAnnotation': return '[NSNumber class]'; case 'Int32TypeAnnotation': return '[NSNumber class]'; case 'StringTypeAnnotation': return '[NSString class]'; default: typeAnnotation.type; throw new Error('Received invalid param type annotation'); } } function getReadableExpectedKindParamType(param) { const typeAnnotation = param.typeAnnotation; switch (typeAnnotation.type) { case 'ReservedTypeAnnotation': switch (typeAnnotation.name) { case 'RootTag': return 'double'; default: typeAnnotation.name; throw new Error(`Receieved invalid type: ${typeAnnotation.name}`); } case 'BooleanTypeAnnotation': return 'boolean'; case 'DoubleTypeAnnotation': return 'double'; case 'FloatTypeAnnotation': return 'float'; case 'Int32TypeAnnotation': return 'number'; case 'StringTypeAnnotation': return 'string'; default: typeAnnotation.type; throw new Error('Received invalid param type annotation'); } } function getObjCRightHandAssignmentParamType(param, index) { const typeAnnotation = param.typeAnnotation; switch (typeAnnotation.type) { case 'ReservedTypeAnnotation': switch (typeAnnotation.name) { case 'RootTag': return `[(NSNumber *)arg${index} doubleValue]`; default: typeAnnotation.name; throw new Error(`Receieved invalid type: ${typeAnnotation.name}`); } case 'BooleanTypeAnnotation': return `[(NSNumber *)arg${index} boolValue]`; case 'DoubleTypeAnnotation': return `[(NSNumber *)arg${index} doubleValue]`; case 'FloatTypeAnnotation': return `[(NSNumber *)arg${index} floatValue]`; case 'Int32TypeAnnotation': return `[(NSNumber *)arg${index} intValue]`; case 'StringTypeAnnotation': return `(NSString *)arg${index}`; default: typeAnnotation.type; throw new Error('Received invalid param type annotation'); } } function generateProtocol(component, componentName) { const methods = component.commands .map(command => { const params = command.typeAnnotation.params; const paramString = params.length === 0 ? '' : params .map((param, index) => { const objCType = getObjCParamType(param); return `${index === 0 ? '' : param.name}:(${objCType})${ param.name }`; }) .join(' '); return `- (void)${command.name}${paramString};`; }) .join('\n') .trim(); return ProtocolTemplate({ componentName, methods, }); } function generateConvertAndValidateParam(param, index, componentName) { const leftSideType = getObjCParamType(param); const expectedKind = getObjCExpectedKindParamType(param); const expectedKindString = getReadableExpectedKindParamType(param); const argConversion = `${leftSideType} ${ param.name } = ${getObjCRightHandAssignmentParamType(param, index)};`; return CommandHandlerIfCaseConvertArgTemplate({ componentName, argConversion, argNumber: index, argNumberString: getOrdinalNumber(index + 1), expectedKind, expectedKindString, }); } function generateCommandIfCase(command, componentName) { const params = command.typeAnnotation.params; const convertArgs = params .map((param, index) => generateConvertAndValidateParam(param, index, componentName), ) .join('\n\n') .trim(); const commandCallArgs = params.length === 0 ? '' : params .map((param, index) => { return `${index === 0 ? '' : param.name}:${param.name}`; }) .join(' '); const commandCall = `[componentView ${command.name}${commandCallArgs}];`; return CommandHandlerIfCaseTemplate({ componentName, commandName: command.name, numArgs: params.length, convertArgs, commandCall, }); } function generateCommandHandler(component, componentName) { if (component.commands.length === 0) { return null; } const ifCases = component.commands .map(command => generateCommandIfCase(command, componentName)) .join('\n\n'); return CommandHandlerTemplate({ componentName, ifCases, }); } module.exports = { generate(libraryName, schema, packageName, assumeNonnull = false) { const fileName = 'RCTComponentViewHelpers.h'; const componentContent = Object.keys(schema.modules) .map(moduleName => { const module = schema.modules[moduleName]; if (module.type !== 'Component') { return; } const components = module.components; // No components in this module if (components == null) { return null; } return Object.keys(components) .filter(componentName => { const component = components[componentName]; return !( component.excludedPlatforms && component.excludedPlatforms.includes('iOS') ); }) .map(componentName => { return [ generateProtocol(components[componentName], componentName), generateCommandHandler(components[componentName], componentName), ] .join('\n\n') .trim(); }) .join('\n\n'); }) .filter(Boolean) .join('\n\n'); const replacedTemplate = FileTemplate({ componentContent, }); return new Map([[fileName, replacedTemplate]]); }, };