UNPKG

@react-native/codegen

Version:
357 lines (323 loc) • 10.5 kB
/** * 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 {CommandParamTypeAnnotation} from '../../CodegenSchema'; import type { CommandTypeAnnotation, ComponentShape, NamedShape, PropTypeAnnotation, SchemaType, } from '../../CodegenSchema'; const { getDelegateJavaClassName, getImports, getInterfaceJavaClassName, toSafeJavaString, } = require('./JavaHelpers'); // File path -> contents type FilesOutput = Map<string, string>; const FileTemplate = ({ packageName, imports, className, extendClasses, interfaceClassName, methods, }: { packageName: string, imports: string, className: string, extendClasses: string, interfaceClassName: string, methods: 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: GeneratePropsJavaDelegate.js */ package ${packageName}; ${imports} public class ${className}<T extends ${extendClasses}, U extends BaseViewManager<T, ? extends LayoutShadowNode> & ${interfaceClassName}<T>> extends BaseViewManagerDelegate<T, U> { public ${className}(U viewManager) { super(viewManager); } ${methods} } `; const PropSetterTemplate = ({propCases}: {propCases: string}) => ` @Override public void setProperty(T view, String propName, @Nullable Object value) { ${propCases} } `.trim(); const CommandsTemplate = ({commandCases}: {commandCases: string}) => ` @Override public void receiveCommand(T view, String commandName, @Nullable ReadableArray args) { switch (commandName) { ${commandCases} } } `.trim(); function getJavaValueForProp( prop: NamedShape<PropTypeAnnotation>, componentName: string, ): string { const typeAnnotation = prop.typeAnnotation; switch (typeAnnotation.type) { case 'BooleanTypeAnnotation': if (typeAnnotation.default === null) { return 'value == null ? null : (Boolean) value'; } else { return `value == null ? ${typeAnnotation.default.toString()} : (boolean) value`; } case 'StringTypeAnnotation': const defaultValueString = typeAnnotation.default === null ? 'null' : `"${typeAnnotation.default}"`; return `value == null ? ${defaultValueString} : (String) value`; case 'Int32TypeAnnotation': return `value == null ? ${typeAnnotation.default} : ((Double) value).intValue()`; case 'DoubleTypeAnnotation': if (prop.optional) { return `value == null ? ${typeAnnotation.default}f : ((Double) value).doubleValue()`; } else { return 'value == null ? Double.NaN : ((Double) value).doubleValue()'; } case 'FloatTypeAnnotation': if (typeAnnotation.default === null) { return 'value == null ? null : ((Double) value).floatValue()'; } else if (prop.optional) { return `value == null ? ${typeAnnotation.default}f : ((Double) value).floatValue()`; } else { return 'value == null ? Float.NaN : ((Double) value).floatValue()'; } case 'ReservedPropTypeAnnotation': switch (typeAnnotation.name) { case 'ColorPrimitive': return 'ColorPropConverter.getColor(value, view.getContext())'; case 'ImageSourcePrimitive': return '(ReadableMap) value'; case 'ImageRequestPrimitive': return '(ReadableMap) value'; case 'PointPrimitive': return '(ReadableMap) value'; case 'EdgeInsetsPrimitive': return '(ReadableMap) value'; case 'DimensionPrimitive': return 'DimensionPropConverter.getDimension(value)'; default: (typeAnnotation.name: empty); throw new Error('Received unknown ReservedPropTypeAnnotation'); } case 'ArrayTypeAnnotation': { return '(ReadableArray) value'; } case 'ObjectTypeAnnotation': { return '(ReadableMap) value'; } case 'StringEnumTypeAnnotation': return '(String) value'; case 'Int32EnumTypeAnnotation': return `value == null ? ${typeAnnotation.default} : ((Double) value).intValue()`; case 'MixedTypeAnnotation': return 'new DynamicFromObject(value)'; default: (typeAnnotation: empty); throw new Error('Received invalid typeAnnotation'); } } function generatePropCasesString( component: ComponentShape, componentName: string, ) { if (component.props.length === 0) { return 'super.setProperty(view, propName, value);'; } const cases = component.props .map(prop => { return `case "${prop.name}": mViewManager.set${toSafeJavaString( prop.name, )}(view, ${getJavaValueForProp(prop, componentName)}); break;`; }) .join('\n' + ' '); return `switch (propName) { ${cases} default: super.setProperty(view, propName, value); }`; } function getCommandArgJavaType( param: NamedShape<CommandParamTypeAnnotation>, index: number, ) { const {typeAnnotation} = param; switch (typeAnnotation.type) { case 'ReservedTypeAnnotation': switch (typeAnnotation.name) { case 'RootTag': return `args.getDouble(${index})`; default: (typeAnnotation.name: empty); throw new Error(`Receieved invalid type: ${typeAnnotation.name}`); } case 'BooleanTypeAnnotation': return `args.getBoolean(${index})`; case 'DoubleTypeAnnotation': return `args.getDouble(${index})`; case 'FloatTypeAnnotation': return `(float) args.getDouble(${index})`; case 'Int32TypeAnnotation': return `args.getInt(${index})`; case 'StringTypeAnnotation': return `args.getString(${index})`; case 'ArrayTypeAnnotation': return `args.getArray(${index})`; default: (typeAnnotation.type: empty); throw new Error(`Receieved invalid type: ${typeAnnotation.type}`); } } function getCommandArguments( command: NamedShape<CommandTypeAnnotation>, ): string { return [ 'view', ...command.typeAnnotation.params.map(getCommandArgJavaType), ].join(', '); } function generateCommandCasesString( component: ComponentShape, componentName: string, ) { if (component.commands.length === 0) { return null; } const commandMethods = component.commands .map(command => { return `case "${command.name}": mViewManager.${toSafeJavaString( command.name, false, )}(${getCommandArguments(command)}); break;`; }) .join('\n' + ' '); return commandMethods; } function getClassExtendString(component: ComponentShape): string { const extendString = component.extendsProps .map(extendProps => { switch (extendProps.type) { case 'ReactNativeBuiltInType': switch (extendProps.knownTypeName) { case 'ReactNativeCoreViewProps': return 'View'; default: (extendProps.knownTypeName: empty); throw new Error('Invalid knownTypeName'); } default: (extendProps.type: empty); throw new Error('Invalid extended type'); } }) .join(''); return extendString; } function getDelegateImports(component: ComponentShape) { const imports = getImports(component, 'delegate'); // The delegate needs ReadableArray for commands always. // The interface doesn't always need it if (component.commands.length > 0) { imports.add('import com.facebook.react.bridge.ReadableArray;'); } imports.add('import androidx.annotation.Nullable;'); imports.add('import com.facebook.react.uimanager.BaseViewManagerDelegate;'); imports.add('import com.facebook.react.uimanager.BaseViewManager;'); imports.add('import com.facebook.react.uimanager.LayoutShadowNode;'); return imports; } function generateMethods( propsString: string, commandsString: null | string, ): string { return [ PropSetterTemplate({propCases: propsString}), commandsString != null ? CommandsTemplate({commandCases: commandsString}) : '', ] .join('\n\n ') .trimRight(); } module.exports = { generate( libraryName: string, schema: SchemaType, packageName?: string, assumeNonnull: boolean = false, headerPrefix?: string, ): FilesOutput { // TODO: This doesn't support custom package name yet. const normalizedPackageName = 'com.facebook.react.viewmanagers'; const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`; const files = new Map<string, string>(); Object.keys(schema.modules).forEach(moduleName => { const module = schema.modules[moduleName]; if (module.type !== 'Component') { return; } const {components} = module; // No components in this module if (components == null) { return; } return Object.keys(components) .filter(componentName => { const component = components[componentName]; return !( component.excludedPlatforms && component.excludedPlatforms.includes('android') ); }) .forEach(componentName => { const component = components[componentName]; const className = getDelegateJavaClassName(componentName); const interfaceClassName = getInterfaceJavaClassName(componentName); const imports = getDelegateImports(component); const propsString = generatePropCasesString(component, componentName); const commandsString = generateCommandCasesString( component, componentName, ); const extendString = getClassExtendString(component); const replacedTemplate = FileTemplate({ imports: Array.from(imports).sort().join('\n'), packageName: normalizedPackageName, className, extendClasses: extendString, methods: generateMethods(propsString, commandsString), interfaceClassName: interfaceClassName, }); files.set(`${outputDir}/${className}.java`, replacedTemplate); }); }); return files; }, };