UNPKG

create-expo-cljs-app

Version:

Create a react native application with Expo and Shadow-CLJS!

373 lines (327 loc) 10.1 kB
/** * 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 { 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 = ` @protocol RCT::_COMPONENT_NAME_::ViewProtocol <NSObject> ::_METHODS_:: @end `.trim(); const commandHandlerIfCaseConvertArgTemplate = ` NSObject *arg::_ARG_NUMBER_:: = args[::_ARG_NUMBER_::]; #if RCT_DEBUG 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 RCT::_COMPONENT_NAME_::HandleCommand( id<RCT::_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. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * ${'@'}generated by codegen project: GenerateComponentHObjCpp.js */ #import <Foundation/Foundation.h> #import <React/RCTDefines.h> #import <React/RCTLog.h> NS_ASSUME_NONNULL_BEGIN ::_COMPONENT_CONTENT_:: 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 commands = 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 .replace(/::_COMPONENT_NAME_::/g, componentName) .replace('::_METHODS_::', commands); } 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 .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: 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 .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, moduleSpecName: string, packageName?: string, ): 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 = template.replace( '::_COMPONENT_CONTENT_::', componentContent, ); return new Map([[fileName, replacedTemplate]]); }, };