amis-rpc-design/node_modules/@react-native/codegen/lib/generators/components/GenerateComponentHObjCpp.js.flow
2023-10-07 19:42:30 +08:00

419 lines
10 KiB
Plaintext

/**
* 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,
CommandTypeAnnotation,
ComponentShape,
SchemaType,
CommandParamTypeAnnotation,
} from '../../CodegenSchema';
type FilesOutput = Map<string, string>;
function getOrdinalNumber(num: number): string {
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,
}: {
componentName: string,
methods: string,
}) =>
`
@protocol RCT${componentName}ViewProtocol <NSObject>
${methods}
@end
`.trim();
const CommandHandlerIfCaseConvertArgTemplate = ({
componentName,
expectedKind,
argNumber,
argNumberString,
expectedKindString,
argConversion,
}: {
componentName: string,
expectedKind: string,
argNumber: number,
argNumberString: string,
expectedKindString: string,
argConversion: string,
}) =>
`
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,
}: {
componentName: string,
commandName: string,
numArgs: number,
convertArgs: string,
commandCall: string,
}) =>
`
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,
}: {
componentName: string,
ifCases: string,
}) =>
`
RCT_EXTERN inline void RCT${componentName}HandleCommand(
id<RCT${componentName}ViewProtocol> 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}: {componentContent: 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: GenerateComponentHObjCpp.js
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
${componentContent}
NS_ASSUME_NONNULL_END
`.trim();
type Param = NamedShape<CommandParamTypeAnnotation>;
function getObjCParamType(param: Param): string {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
(typeAnnotation.name: empty);
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: empty);
throw new Error('Received invalid param type annotation');
}
}
function getObjCExpectedKindParamType(param: Param): string {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return '[NSNumber class]';
default:
(typeAnnotation.name: empty);
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: empty);
throw new Error('Received invalid param type annotation');
}
}
function getReadableExpectedKindParamType(param: Param): string {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
(typeAnnotation.name: empty);
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: empty);
throw new Error('Received invalid param type annotation');
}
}
function getObjCRightHandAssignmentParamType(
param: Param,
index: number,
): string {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return `[(NSNumber *)arg${index} doubleValue]`;
default:
(typeAnnotation.name: empty);
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: empty);
throw new Error('Received invalid param type annotation');
}
}
function generateProtocol(
component: ComponentShape,
componentName: string,
): string {
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: Param,
index: number,
componentName: string,
): string {
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: NamedShape<CommandTypeAnnotation>,
componentName: string,
) {
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: ComponentShape,
componentName: string,
): ?string {
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: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
): FilesOutput {
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;
// 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]]);
},
};