amis-rpc-design/node_modules/@react-native/codegen/lib/generators/components/GeneratePropsJavaDelegate.js.flow

353 lines
10 KiB
Plaintext
Raw Normal View History

2023-10-07 19:42:30 +08:00
/**
* 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 {
NamedShape,
CommandTypeAnnotation,
ComponentShape,
PropTypeAnnotation,
SchemaType,
} from '../../CodegenSchema';
const {
getImports,
toSafeJavaString,
getInterfaceJavaClassName,
getDelegateJavaClassName,
} = 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 BaseViewManagerInterface<T> & ${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, 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})`;
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.BaseViewManagerInterface;');
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,
): 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;
},
};