mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
450967938a
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/32840 Closes #31516 I've cherry-picked the original PR that had merge conficts + updated all the headers as the one for the TurboModule generators were not handled. Original Commit Message from acoates The codegen generates a Facebook copyright notice at the top of the generated files. While this might make sense on the core files, this codegen will be run on external components too. The notice also refers to a LICENSE file in the root of this project, which might not be there if this is run on another project. I did a quick look at some of the codegen that we ship within windows dev tools, and it looks like we normally just have comments saying the file was codegen'd and so the file shouldn't be manually edited. Open to suggestions on what the comment header should say. Changelog: [General] [Changed] - Do not include Facebook license on users codegen'd code Reviewed By: ShikaSD Differential Revision: D33455176 fbshipit-source-id: b247e72efb242e79d99b388c80e4126633e5234d
285 lines
7.4 KiB
JavaScript
285 lines
7.4 KiB
JavaScript
/**
|
|
* 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,
|
|
PropTypeAnnotation,
|
|
SchemaType,
|
|
} from '../../CodegenSchema';
|
|
const {
|
|
getImports,
|
|
toSafeJavaString,
|
|
getInterfaceJavaClassName,
|
|
} = require('./JavaHelpers');
|
|
|
|
// File path -> contents
|
|
type FilesOutput = Map<string, string>;
|
|
|
|
const FileTemplate = ({
|
|
packageName,
|
|
imports,
|
|
className,
|
|
extendClasses,
|
|
methods,
|
|
}: {
|
|
packageName: string,
|
|
imports: string,
|
|
className: string,
|
|
extendClasses: 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: GeneratePropsJavaInterface.js
|
|
*/
|
|
|
|
package ${packageName};
|
|
|
|
${imports}
|
|
|
|
public interface ${className}<T extends ${extendClasses}> {
|
|
${methods}
|
|
}
|
|
`;
|
|
|
|
function addNullable(imports) {
|
|
imports.add('import androidx.annotation.Nullable;');
|
|
}
|
|
|
|
function getJavaValueForProp(
|
|
prop: NamedShape<PropTypeAnnotation>,
|
|
imports,
|
|
): string {
|
|
const typeAnnotation = prop.typeAnnotation;
|
|
|
|
switch (typeAnnotation.type) {
|
|
case 'BooleanTypeAnnotation':
|
|
if (typeAnnotation.default === null) {
|
|
addNullable(imports);
|
|
return '@Nullable Boolean value';
|
|
} else {
|
|
return 'boolean value';
|
|
}
|
|
case 'StringTypeAnnotation':
|
|
addNullable(imports);
|
|
return '@Nullable String value';
|
|
case 'Int32TypeAnnotation':
|
|
return 'int value';
|
|
case 'DoubleTypeAnnotation':
|
|
return 'double value';
|
|
case 'FloatTypeAnnotation':
|
|
if (typeAnnotation.default === null) {
|
|
addNullable(imports);
|
|
return '@Nullable Float value';
|
|
} else {
|
|
return 'float value';
|
|
}
|
|
case 'ReservedPropTypeAnnotation':
|
|
switch (typeAnnotation.name) {
|
|
case 'ColorPrimitive':
|
|
addNullable(imports);
|
|
return '@Nullable Integer value';
|
|
case 'ImageSourcePrimitive':
|
|
addNullable(imports);
|
|
return '@Nullable ReadableMap value';
|
|
case 'PointPrimitive':
|
|
addNullable(imports);
|
|
return '@Nullable ReadableMap value';
|
|
case 'EdgeInsetsPrimitive':
|
|
addNullable(imports);
|
|
return '@Nullable ReadableMap value';
|
|
default:
|
|
(typeAnnotation.name: empty);
|
|
throw new Error('Received unknown ReservedPropTypeAnnotation');
|
|
}
|
|
case 'ArrayTypeAnnotation': {
|
|
addNullable(imports);
|
|
return '@Nullable ReadableArray value';
|
|
}
|
|
case 'ObjectTypeAnnotation': {
|
|
addNullable(imports);
|
|
return '@Nullable ReadableMap value';
|
|
}
|
|
case 'StringEnumTypeAnnotation':
|
|
addNullable(imports);
|
|
return '@Nullable String value';
|
|
case 'Int32EnumTypeAnnotation':
|
|
addNullable(imports);
|
|
return '@Nullable Integer value';
|
|
default:
|
|
(typeAnnotation: empty);
|
|
throw new Error('Received invalid typeAnnotation');
|
|
}
|
|
}
|
|
|
|
function generatePropsString(component: ComponentShape, imports) {
|
|
if (component.props.length === 0) {
|
|
return '// No props';
|
|
}
|
|
|
|
return component.props
|
|
.map(prop => {
|
|
return `void set${toSafeJavaString(
|
|
prop.name,
|
|
)}(T view, ${getJavaValueForProp(prop, imports)});`;
|
|
})
|
|
.join('\n' + ' ');
|
|
}
|
|
|
|
function getCommandArgJavaType(param) {
|
|
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 'int';
|
|
case 'StringTypeAnnotation':
|
|
return 'String';
|
|
default:
|
|
(typeAnnotation.type: empty);
|
|
throw new Error('Receieved invalid typeAnnotation');
|
|
}
|
|
}
|
|
|
|
function getCommandArguments(
|
|
command: NamedShape<CommandTypeAnnotation>,
|
|
componentName: string,
|
|
): string {
|
|
return [
|
|
'T view',
|
|
...command.typeAnnotation.params.map(param => {
|
|
const commandArgJavaType = getCommandArgJavaType(param);
|
|
|
|
return `${commandArgJavaType} ${param.name}`;
|
|
}),
|
|
].join(', ');
|
|
}
|
|
|
|
function generateCommandsString(
|
|
component: ComponentShape,
|
|
componentName: string,
|
|
) {
|
|
return component.commands
|
|
.map(command => {
|
|
const safeJavaName = toSafeJavaString(command.name, false);
|
|
|
|
return `void ${safeJavaName}(${getCommandArguments(
|
|
command,
|
|
componentName,
|
|
)});`;
|
|
})
|
|
.join('\n' + ' ');
|
|
}
|
|
|
|
function getClassExtendString(component): 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;
|
|
}
|
|
|
|
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();
|
|
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 = getInterfaceJavaClassName(componentName);
|
|
|
|
const imports = getImports(component, 'interface');
|
|
const propsString = generatePropsString(component, imports);
|
|
const commandsString = generateCommandsString(
|
|
component,
|
|
componentName,
|
|
);
|
|
const extendString = getClassExtendString(component);
|
|
|
|
const replacedTemplate = FileTemplate({
|
|
imports: Array.from(imports).sort().join('\n'),
|
|
packageName: normalizedPackageName,
|
|
className,
|
|
extendClasses: extendString,
|
|
methods: [propsString, commandsString]
|
|
.join('\n' + ' ')
|
|
.trimRight(),
|
|
});
|
|
|
|
files.set(`${outputDir}/${className}.java`, replacedTemplate);
|
|
});
|
|
});
|
|
|
|
return files;
|
|
},
|
|
};
|