Generate objc handler for commands

Summary:
These functions will be called by components like this:
```
- (void)handleCommand:(NSString const *)commandName args:(NSArray const *)args
{
  ViewNativeComponentHandleCommand(self, commandName, args);
}
```

Codegen currently supports commands with ints and bools as arguments. Will add more types in follow up diffs.

Reviewed By: JoshuaGross

Differential Revision: D16509123

fbshipit-source-id: c3071ce3b5da215bb8747216e57026a69a89eff0
This commit is contained in:
Eli White
2019-07-29 14:41:31 -07:00
committed by Facebook Github Bot
parent 47365da367
commit 88641f850a
2 changed files with 294 additions and 1 deletions
@@ -11,6 +11,7 @@
'use strict';
import type {
CommandTypeShape,
ComponentShape,
SchemaType,
CommandsFunctionTypeParamAnnotation,
@@ -18,12 +19,69 @@ import type {
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 = `
@protocol ::_COMPONENT_NAME_::ViewProtocol <NSObject>
::_METHODS_::
@end
`.trim();
const commandHandlerIfCaseConvertArgTemplate = `
#if RCT_DEBUG
NSObject *arg::_ARG_NUMBER_:: = args[::_ARG_NUMBER_::];
if (!RCTValidateTypeOfViewCommandArgument(arg::_ARG_NUMBER_::, ::_EXPECTED_KIND_::, @"::_EXPECTED_KIND_STRING_::", @"::_COMPONENT_NAME_::", commandName, @"::_ARG_NUMBER_STR_::")) {
return;
}
#endif
::_ARG_CONVERSION_::
`.trim();
const commandHandlerIfCaseTemplate = `
if ([commandName isEqualToString:@"::_COMMAND_NAME_::"]) {
#if RCT_DEBUG
if ([args count] != ::_NUM_ARGS_::) {
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"::_COMPONENT_NAME_::", commandName, (int)[args count], ::_NUM_ARGS_::);
return;
}
#endif
::_CONVERT_ARGS_::
::_COMMAND_CALL_::
return;
}
`.trim();
const commandHandlerTemplate = `
RCT_EXTERN inline void ::_COMPONENT_NAME_::HandleCommand(
id<::_COMPONENT_NAME_::ViewProtocol> componentView,
NSString const *commandName,
NSArray const *args)
{
::_IF_CASES_::
#if RCT_DEBUG
RCTLogError(@"%@ received command %@, which is not a supported command.", @"::_COMPONENT_NAME_::", commandName);
#endif
}
`.trim();
const template = `
/**
* Copyright (c) Facebook, Inc. and its affiliates.
@@ -55,6 +113,49 @@ function getObjCParamType(param: CommandsFunctionTypeParamAnnotation): string {
}
}
function getObjCExpectedKindParamType(
param: CommandsFunctionTypeParamAnnotation,
): string {
switch (param.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return '[NSNumber class]';
case 'Int32TypeAnnotation':
return '[NSNumber class]';
default:
(param.typeAnnotation.type: empty);
throw new Error('Received invalid param type annotation');
}
}
function getReadableExpectedKindParamType(
param: CommandsFunctionTypeParamAnnotation,
): string {
switch (param.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return 'boolean';
case 'Int32TypeAnnotation':
return 'number';
default:
(param.typeAnnotation.type: empty);
throw new Error('Received invalid param type annotation');
}
}
function getObjCRightHandAssignmentParamType(
param: CommandsFunctionTypeParamAnnotation,
index: number,
): string {
switch (param.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return `[(NSNumber *)arg${index} boolValue]`;
case 'Int32TypeAnnotation':
return `[(NSNumber *)arg${index} intValue]`;
default:
(param.typeAnnotation.type: empty);
throw new Error('Received invalid param type annotation');
}
}
function generateProtocol(
component: ComponentShape,
componentName: string,
@@ -84,6 +185,75 @@ function generateProtocol(
.replace('::_METHODS_::', commands);
}
function generateConvertAndValidateParam(
param: CommandsFunctionTypeParamAnnotation,
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
.replace(/::_COMPONENT_NAME_::/g, componentName)
.replace('::_ARG_CONVERSION_::', argConversion)
.replace(/::_ARG_NUMBER_::/g, '' + index)
.replace('::_ARG_NUMBER_STR_::', getOrdinalNumber(index + 1))
.replace('::_EXPECTED_KIND_::', expectedKind)
.replace('::_EXPECTED_KIND_STRING_::', expectedKindString);
}
function generateCommandIfCase(
command: CommandTypeShape,
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
.replace(/::_COMPONENT_NAME_::/g, componentName)
.replace(/::_COMMAND_NAME_::/g, command.name)
.replace(/::_NUM_ARGS_::/g, '' + params.length)
.replace('::_CONVERT_ARGS_::', convertArgs)
.replace('::_COMMAND_CALL_::', 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
.replace(/::_COMPONENT_NAME_::/g, componentName)
.replace('::_IF_CASES_::', ifCases);
}
module.exports = {
generate(libraryName: string, schema: SchemaType): FilesOutput {
const fileName = 'ComponentViewHelpers.h';
@@ -98,7 +268,12 @@ module.exports = {
return Object.keys(components)
.map(componentName => {
return generateProtocol(components[componentName], componentName);
return [
generateProtocol(components[componentName], componentName),
generateCommandHandler(components[componentName], componentName),
]
.join('\n\n')
.trim();
})
.join('\n\n');
})
@@ -90,6 +90,86 @@ NS_ASSUME_NONNULL_BEGIN
- (void)scrollTo:(NSInteger)y animated:(BOOL)animated;
@end
RCT_EXTERN inline void CommandNativeComponentHandleCommand(
id<CommandNativeComponentViewProtocol> componentView,
NSString const *commandName,
NSArray const *args)
{
if ([commandName isEqualToString:@\\"flashScrollIndicators\\"]) {
#if RCT_DEBUG
if ([args count] != 0) {
RCTLogError(@\\"%@ command %@ received %d arguments, expected %d.\\", @\\"CommandNativeComponent\\", commandName, (int)[args count], 0);
return;
}
#endif
[componentView flashScrollIndicators]
return;
}
if ([commandName isEqualToString:@\\"hotspotUpdate\\"]) {
#if RCT_DEBUG
if ([args count] != 2) {
RCTLogError(@\\"%@ command %@ received %d arguments, expected %d.\\", @\\"CommandNativeComponent\\", commandName, (int)[args count], 2);
return;
}
#endif
#if RCT_DEBUG
NSObject *arg0 = args[0];
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @\\"number\\", @\\"CommandNativeComponent\\", commandName, @\\"1st\\")) {
return;
}
#endif
NSInteger x = [(NSNumber *)arg0 intValue];
#if RCT_DEBUG
NSObject *arg1 = args[1];
if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSNumber class], @\\"number\\", @\\"CommandNativeComponent\\", commandName, @\\"2nd\\")) {
return;
}
#endif
NSInteger y = [(NSNumber *)arg1 intValue];
[componentView hotspotUpdate:x y:y]
return;
}
if ([commandName isEqualToString:@\\"scrollTo\\"]) {
#if RCT_DEBUG
if ([args count] != 2) {
RCTLogError(@\\"%@ command %@ received %d arguments, expected %d.\\", @\\"CommandNativeComponent\\", commandName, (int)[args count], 2);
return;
}
#endif
#if RCT_DEBUG
NSObject *arg0 = args[0];
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @\\"number\\", @\\"CommandNativeComponent\\", commandName, @\\"1st\\")) {
return;
}
#endif
NSInteger y = [(NSNumber *)arg0 intValue];
#if RCT_DEBUG
NSObject *arg1 = args[1];
if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSNumber class], @\\"boolean\\", @\\"CommandNativeComponent\\", commandName, @\\"2nd\\")) {
return;
}
#endif
BOOL animated = [(NSNumber *)arg1 boolValue];
[componentView scrollTo:y animated:animated]
return;
}
#if RCT_DEBUG
RCTLogError(@\\"%@ received command %@, which is not a supported command.\\", @\\"CommandNativeComponent\\", commandName);
#endif
}
NS_ASSUME_NONNULL_END",
}
`;
@@ -113,6 +193,44 @@ NS_ASSUME_NONNULL_BEGIN
- (void)hotspotUpdate:(NSInteger)x y:(NSInteger)y;
@end
RCT_EXTERN inline void CommandNativeComponentHandleCommand(
id<CommandNativeComponentViewProtocol> componentView,
NSString const *commandName,
NSArray const *args)
{
if ([commandName isEqualToString:@\\"hotspotUpdate\\"]) {
#if RCT_DEBUG
if ([args count] != 2) {
RCTLogError(@\\"%@ command %@ received %d arguments, expected %d.\\", @\\"CommandNativeComponent\\", commandName, (int)[args count], 2);
return;
}
#endif
#if RCT_DEBUG
NSObject *arg0 = args[0];
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @\\"number\\", @\\"CommandNativeComponent\\", commandName, @\\"1st\\")) {
return;
}
#endif
NSInteger x = [(NSNumber *)arg0 intValue];
#if RCT_DEBUG
NSObject *arg1 = args[1];
if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSNumber class], @\\"number\\", @\\"CommandNativeComponent\\", commandName, @\\"2nd\\")) {
return;
}
#endif
NSInteger y = [(NSNumber *)arg1 intValue];
[componentView hotspotUpdate:x y:y]
return;
}
#if RCT_DEBUG
RCTLogError(@\\"%@ received command %@, which is not a supported command.\\", @\\"CommandNativeComponent\\", commandName);
#endif
}
NS_ASSUME_NONNULL_END",
}
`;