first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

21
node_modules/@react-native/codegen/README.md generated vendored Normal file
View File

@@ -0,0 +1,21 @@
# @react-native/codegen
[![Version][version-badge]][package]
## Installation
```
yarn add --dev @react-native/codegen
```
*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like*
[version-badge]: https://img.shields.io/npm/v/@react-native/codegen?style=flat-square
[package]: https://www.npmjs.com/package/@react-native/codegen
## Testing
To run the tests in this package, run the following commands from the React Native root folder:
1. `yarn` to install the dependencies. You just need to run this once
2. `yarn jest packages/react-native-codegen`.

View File

@@ -0,0 +1,442 @@
/**
* 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.
*/
export type PlatformType =
| 'iOS'
| 'android';
export interface SchemaType {
readonly modules: {
[hasteModuleName: string]: ComponentSchema | NativeModuleSchema;
};
}
/**
* Component Type Annotations
*/
export interface DoubleTypeAnnotation {
readonly type: 'DoubleTypeAnnotation';
}
export interface FloatTypeAnnotation {
readonly type: 'FloatTypeAnnotation';
}
export interface BooleanTypeAnnotation {
readonly type: 'BooleanTypeAnnotation';
}
export interface Int32TypeAnnotation {
readonly type: 'Int32TypeAnnotation';
}
export interface StringTypeAnnotation {
readonly type: 'StringTypeAnnotation';
}
export interface VoidTypeAnnotation {
readonly type: 'VoidTypeAnnotation';
}
export interface ObjectTypeAnnotation<T> {
readonly type: 'ObjectTypeAnnotation';
readonly properties: readonly NamedShape<T>[];
// metadata for objects that generated from interfaces
readonly baseTypes?: readonly string[] | undefined;
}
export interface MixedTypeAnnotation {
readonly type: 'MixedTypeAnnotation';
}
export interface EventEmitterTypeAnnotation {
readonly type: 'EventEmitterTypeAnnotation';
readonly typeAnnotation: NativeModuleEventEmitterTypeAnnotation;
}
export interface FunctionTypeAnnotation<P, R> {
readonly type: 'FunctionTypeAnnotation';
readonly params: readonly NamedShape<P>[];
readonly returnTypeAnnotation: R;
}
export interface NamedShape<T> {
readonly name: string;
readonly optional: boolean;
readonly typeAnnotation: T;
}
export interface ComponentSchema {
readonly type: 'Component';
readonly components: {
[componentName: string]: ComponentShape;
};
}
export interface ComponentShape extends OptionsShape {
readonly extendsProps: readonly ExtendsPropsShape[];
readonly events: readonly EventTypeShape[];
readonly props: readonly NamedShape<PropTypeAnnotation>[];
readonly commands: readonly NamedShape<CommandTypeAnnotation>[];
readonly deprecatedViewConfigName?: string | undefined;
}
export interface OptionsShape {
readonly interfaceOnly?: boolean | undefined;
// Use for components with no current paper rename in progress
// Does not check for new name
readonly paperComponentName?: string | undefined;
// Use for components that are not used on other platforms.
readonly excludedPlatforms?: readonly PlatformType[] | undefined;
// Use for components currently being renamed in paper
// Will use new name if it is available and fallback to this name
readonly paperComponentNameDeprecated?: string | undefined;
}
export interface ExtendsPropsShape {
readonly type: 'ReactNativeBuiltInType';
readonly knownTypeName: 'ReactNativeCoreViewProps';
}
export interface EventTypeShape {
readonly name: string;
readonly bubblingType:
| 'direct'
| 'bubble';
readonly optional: boolean;
readonly paperTopLevelNameDeprecated?: string | undefined;
readonly typeAnnotation: {
readonly type: 'EventTypeAnnotation';
readonly argument?: ObjectTypeAnnotation<EventTypeAnnotation> | undefined;
};
}
export type EventTypeAnnotation =
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| MixedTypeAnnotation
| StringLiteralUnionTypeAnnotation
| ObjectTypeAnnotation<EventTypeAnnotation>
| ArrayTypeAnnotation<EventTypeAnnotation>
export type ComponentArrayTypeAnnotation = ArrayTypeAnnotation<
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| MixedTypeAnnotation
| {
readonly type: 'StringEnumTypeAnnotation';
readonly default: string;
readonly options: readonly string[];
}
| ObjectTypeAnnotation<PropTypeAnnotation>
| ReservedPropTypeAnnotation
| ArrayTypeAnnotation<ObjectTypeAnnotation<PropTypeAnnotation>>
>;
export type ComponentCommandArrayTypeAnnotation = ArrayTypeAnnotation<
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
// Mixed is not great. This generally means its a type alias to another type
// like an object or union. Ideally we'd encode that type in the schema so the compat-check can
// validate those deeper objects for breaking changes and the generators can do something smarter.
// As of now, the generators just create ReadableMap or (const NSArray *) which are untyped
| MixedTypeAnnotation
>;
export interface ArrayTypeAnnotation<T> {
readonly type: 'ArrayTypeAnnotation';
readonly elementType: T;
}
export type PropTypeAnnotation =
| {
readonly type: 'BooleanTypeAnnotation';
readonly default: boolean | null;
}
| {
readonly type: 'StringTypeAnnotation';
readonly default: string | null;
}
| {
readonly type: 'DoubleTypeAnnotation';
readonly default: number;
}
| {
readonly type: 'FloatTypeAnnotation';
readonly default: number | null;
}
| {
readonly type: 'Int32TypeAnnotation';
readonly default: number;
}
| {
readonly type: 'StringEnumTypeAnnotation';
readonly default: string;
readonly options: readonly string[];
}
| {
readonly type: 'Int32EnumTypeAnnotation';
readonly default: number;
readonly options: readonly number[];
}
| ReservedPropTypeAnnotation
| ObjectTypeAnnotation<PropTypeAnnotation>
| ComponentArrayTypeAnnotation
| MixedTypeAnnotation;
export interface ReservedPropTypeAnnotation {
readonly type: 'ReservedPropTypeAnnotation';
readonly name:
| 'ColorPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageRequestPrimitive'
| 'DimensionPrimitive';
}
export type CommandTypeAnnotation = FunctionTypeAnnotation<
CommandParamTypeAnnotation,
VoidTypeAnnotation
>;
export type CommandParamTypeAnnotation =
| ReservedTypeAnnotation
| BooleanTypeAnnotation
| Int32TypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| StringTypeAnnotation
| ComponentCommandArrayTypeAnnotation;
export interface ReservedTypeAnnotation {
readonly type: 'ReservedTypeAnnotation';
readonly name: 'RootTag'; // Union with more custom types.
}
/**
* NativeModule Types
*/
export type Nullable<T extends NativeModuleTypeAnnotation> =
| NullableTypeAnnotation<T>
| T;
export interface NullableTypeAnnotation<T extends NativeModuleTypeAnnotation> {
readonly type: 'NullableTypeAnnotation';
readonly typeAnnotation: T;
}
export interface NativeModuleSchema {
readonly type: 'NativeModule';
readonly aliasMap: NativeModuleAliasMap;
readonly enumMap: NativeModuleEnumMap;
readonly spec: NativeModuleSpec;
readonly moduleName: string;
// Use for modules that are not used on other platforms.
// TODO: It's clearer to define `restrictedToPlatforms` instead, but
// `excludedPlatforms` is used here to be consistent with ComponentSchema.
readonly excludedPlatforms?: readonly PlatformType[] | undefined;
}
export interface NativeModuleSpec {
readonly eventEmitters: readonly NativeModuleEventEmitterShape[];
readonly methods: readonly NativeModulePropertyShape[];
}
export type NativeModulePropertyShape = NamedShape<
Nullable<NativeModuleFunctionTypeAnnotation>
>;
export type NativeModuleEventEmitterShape = NamedShape<EventEmitterTypeAnnotation>;
export interface NativeModuleEnumMap {
readonly [enumName: string]: NativeModuleEnumDeclarationWithMembers;
}
export interface NativeModuleAliasMap {
readonly [aliasName: string]: NativeModuleObjectTypeAnnotation;
}
export type NativeModuleFunctionTypeAnnotation = FunctionTypeAnnotation<
Nullable<NativeModuleParamTypeAnnotation>,
Nullable<NativeModuleReturnTypeAnnotation>
>;
export type NativeModuleObjectTypeAnnotation = ObjectTypeAnnotation<
Nullable<NativeModuleBaseTypeAnnotation>
>;
/**
* TODO(T72031674): Migrate all our NativeModule specs to not use
* invalid Array ElementTypes. Then, make the elementType required.
*/
interface NativeModuleArrayTypeAnnotation<T> extends ArrayTypeAnnotation<T | UnsafeAnyTypeAnnotation> { }
export interface UnsafeAnyTypeAnnotation {
readonly type: 'AnyTypeAnnotation',
}
export interface NativeModuleNumberLiteralTypeAnnotation {
readonly type: 'NumberLiteralTypeAnnotation';
readonly value: number;
}
export interface NativeModuleStringTypeAnnotation {
readonly type: 'StringTypeAnnotation';
}
export interface NativeModuleStringLiteralTypeAnnotation {
readonly type: 'StringLiteralTypeAnnotation';
readonly value: string;
}
export interface StringLiteralUnionTypeAnnotation {
readonly type: 'StringLiteralUnionTypeAnnotation';
readonly types: NativeModuleStringLiteralTypeAnnotation[];
}
export interface NativeModuleNumberTypeAnnotation {
readonly type: 'NumberTypeAnnotation';
}
export interface NativeModuleInt32TypeAnnotation {
readonly type: 'Int32TypeAnnotation';
}
export interface NativeModuleDoubleTypeAnnotation {
readonly type: 'DoubleTypeAnnotation';
}
export interface NativeModuleFloatTypeAnnotation {
readonly type: 'FloatTypeAnnotation';
}
export interface NativeModuleBooleanTypeAnnotation {
readonly type: 'BooleanTypeAnnotation';
}
export type NativeModuleEnumMember = {
readonly name: string;
readonly value: NativeModuleStringLiteralTypeAnnotation | NativeModuleNumberLiteralTypeAnnotation,
};
export type NativeModuleEnumMemberType =
| 'NumberTypeAnnotation'
| 'StringTypeAnnotation';
export interface NativeModuleEnumDeclaration {
readonly name: string;
readonly type: 'EnumDeclaration';
readonly memberType: NativeModuleEnumMemberType;
}
export interface NativeModuleEnumDeclarationWithMembers {
name: string;
type: 'EnumDeclarationWithMembers';
memberType: NativeModuleEnumMemberType;
members: readonly NativeModuleEnumMember[];
}
export interface NativeModuleGenericObjectTypeAnnotation {
readonly type: 'GenericObjectTypeAnnotation';
// a dictionary type is codegen as "Object"
// but we know all its members are in the same type
// when it happens, the following field is non-null
readonly dictionaryValueType?: Nullable<NativeModuleTypeAnnotation> | undefined;
}
export interface NativeModuleTypeAliasTypeAnnotation {
readonly type: 'TypeAliasTypeAnnotation';
readonly name: string;
}
export interface NativeModulePromiseTypeAnnotation {
readonly type: 'PromiseTypeAnnotation';
readonly elementType: Nullable<NativeModuleBaseTypeAnnotation> | VoidTypeAnnotation;
}
export type UnionTypeAnnotationMemberType =
| 'NumberTypeAnnotation'
| 'ObjectTypeAnnotation'
| 'StringTypeAnnotation';
export interface NativeModuleUnionTypeAnnotation {
readonly type: 'UnionTypeAnnotation';
readonly memberType: UnionTypeAnnotationMemberType;
}
export interface NativeModuleMixedTypeAnnotation {
readonly type: 'MixedTypeAnnotation';
}
export type NativeModuleEventEmitterBaseTypeAnnotation =
| NativeModuleBooleanTypeAnnotation
| NativeModuleDoubleTypeAnnotation
| NativeModuleFloatTypeAnnotation
| NativeModuleInt32TypeAnnotation
| NativeModuleNumberTypeAnnotation
| NativeModuleNumberLiteralTypeAnnotation
| NativeModuleStringTypeAnnotation
| NativeModuleStringLiteralTypeAnnotation
| StringLiteralUnionTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleGenericObjectTypeAnnotation
| VoidTypeAnnotation;
export type NativeModuleEventEmitterTypeAnnotation =
| NativeModuleEventEmitterBaseTypeAnnotation
| ArrayTypeAnnotation<NativeModuleEventEmitterBaseTypeAnnotation>;
export type NativeModuleBaseTypeAnnotation =
NativeModuleStringTypeAnnotation
| NativeModuleStringLiteralTypeAnnotation
| StringLiteralUnionTypeAnnotation
| NativeModuleNumberTypeAnnotation
| NativeModuleNumberLiteralTypeAnnotation
| NativeModuleInt32TypeAnnotation
| NativeModuleDoubleTypeAnnotation
| NativeModuleFloatTypeAnnotation
| NativeModuleBooleanTypeAnnotation
| NativeModuleEnumDeclaration
| NativeModuleGenericObjectTypeAnnotation
| ReservedTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleObjectTypeAnnotation
| NativeModuleUnionTypeAnnotation
| NativeModuleMixedTypeAnnotation
| NativeModuleArrayTypeAnnotation<NativeModuleBaseTypeAnnotation>;
export type NativeModuleParamTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleParamOnlyTypeAnnotation;
export type NativeModuleReturnTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleReturnOnlyTypeAnnotation;
export type NativeModuleTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleParamOnlyTypeAnnotation
| NativeModuleReturnOnlyTypeAnnotation
| NativeModuleEventEmitterTypeAnnotation;
type NativeModuleParamOnlyTypeAnnotation = NativeModuleFunctionTypeAnnotation;
type NativeModuleReturnOnlyTypeAnnotation =
| NativeModuleFunctionTypeAnnotation
| NativeModulePromiseTypeAnnotation
| VoidTypeAnnotation;

View File

@@ -0,0 +1,11 @@
/**
* 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.
*
*
* @format
*/
'use strict';

View File

@@ -0,0 +1,453 @@
/**
* 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';
export type PlatformType = 'iOS' | 'android';
export type SchemaType = $ReadOnly<{
libraryName?: string,
modules: $ReadOnly<{
[hasteModuleName: string]: ComponentSchema | NativeModuleSchema,
}>,
}>;
/**
* Component Type Annotations
*/
export type DoubleTypeAnnotation = $ReadOnly<{
type: 'DoubleTypeAnnotation',
}>;
export type FloatTypeAnnotation = $ReadOnly<{
type: 'FloatTypeAnnotation',
}>;
export type BooleanTypeAnnotation = $ReadOnly<{
type: 'BooleanTypeAnnotation',
}>;
export type Int32TypeAnnotation = $ReadOnly<{
type: 'Int32TypeAnnotation',
}>;
export type NumberLiteralTypeAnnotation = $ReadOnly<{
type: 'NumberLiteralTypeAnnotation',
value: number,
}>;
export type StringTypeAnnotation = $ReadOnly<{
type: 'StringTypeAnnotation',
}>;
export type StringLiteralTypeAnnotation = $ReadOnly<{
type: 'StringLiteralTypeAnnotation',
value: string,
}>;
export type StringLiteralUnionTypeAnnotation = $ReadOnly<{
type: 'StringLiteralUnionTypeAnnotation',
types: $ReadOnlyArray<StringLiteralTypeAnnotation>,
}>;
export type VoidTypeAnnotation = $ReadOnly<{
type: 'VoidTypeAnnotation',
}>;
export type ObjectTypeAnnotation<+T> = $ReadOnly<{
type: 'ObjectTypeAnnotation',
properties: $ReadOnlyArray<NamedShape<T>>,
// metadata for objects that generated from interfaces
baseTypes?: $ReadOnlyArray<string>,
}>;
export type MixedTypeAnnotation = $ReadOnly<{
type: 'MixedTypeAnnotation',
}>;
export type EventEmitterTypeAnnotation = $ReadOnly<{
type: 'EventEmitterTypeAnnotation',
typeAnnotation: NativeModuleEventEmitterTypeAnnotation | $FlowFixMe,
}>;
type FunctionTypeAnnotation<+P, +R> = $ReadOnly<{
type: 'FunctionTypeAnnotation',
params: $ReadOnlyArray<NamedShape<P>>,
returnTypeAnnotation: R,
}>;
export type NamedShape<+T> = $ReadOnly<{
name: string,
optional: boolean,
typeAnnotation: T,
}>;
export type ComponentSchema = $ReadOnly<{
type: 'Component',
components: $ReadOnly<{
[componentName: string]: ComponentShape,
}>,
}>;
export type ComponentShape = $ReadOnly<{
...OptionsShape,
extendsProps: $ReadOnlyArray<ExtendsPropsShape>,
events: $ReadOnlyArray<EventTypeShape>,
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
commands: $ReadOnlyArray<NamedShape<CommandTypeAnnotation>>,
}>;
export type OptionsShape = $ReadOnly<{
// Use to generate only interfaces of components (C++ Props, C++ EventEmitters, JVM interfaces) and not their implementation.
// This is useful for components that have a custom implementation of ShadowNode, ComponentDescriptor, State.
interfaceOnly?: boolean,
// Use for components with no current paper rename in progress
// Does not check for new name
paperComponentName?: string,
// Use for components that are not used on other platforms.
excludedPlatforms?: $ReadOnlyArray<PlatformType>,
// Use for components currently being renamed in paper
// Will use new name if it is available and fallback to this name
paperComponentNameDeprecated?: string,
}>;
export type ExtendsPropsShape = $ReadOnly<{
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
}>;
export type EventTypeShape = $ReadOnly<{
name: string,
bubblingType: 'direct' | 'bubble',
optional: boolean,
paperTopLevelNameDeprecated?: string,
typeAnnotation: $ReadOnly<{
type: 'EventTypeAnnotation',
argument?: ObjectTypeAnnotation<EventTypeAnnotation>,
}>,
}>;
export type EventTypeAnnotation =
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| MixedTypeAnnotation
| StringLiteralUnionTypeAnnotation
| ObjectTypeAnnotation<EventTypeAnnotation>
| ArrayTypeAnnotation<EventTypeAnnotation>;
export type ComponentArrayTypeAnnotation = ArrayTypeAnnotation<
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| MixedTypeAnnotation
| $ReadOnly<{
type: 'StringEnumTypeAnnotation',
default: string,
options: $ReadOnlyArray<string>,
}>
| ObjectTypeAnnotation<PropTypeAnnotation>
| ReservedPropTypeAnnotation
| ArrayTypeAnnotation<ObjectTypeAnnotation<PropTypeAnnotation>>,
>;
export type ComponentCommandArrayTypeAnnotation = ArrayTypeAnnotation<
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
// Mixed is not great. This generally means its a type alias to another type
// like an object or union. Ideally we'd encode that type in the schema so the compat-check can
// validate those deeper objects for breaking changes and the generators can do something smarter.
// As of now, the generators just create ReadableMap or (const NSArray *) which are untyped
| MixedTypeAnnotation,
>;
export type ArrayTypeAnnotation<+T> = $ReadOnly<{
type: 'ArrayTypeAnnotation',
elementType: T,
}>;
export type PropTypeAnnotation =
| $ReadOnly<{
type: 'BooleanTypeAnnotation',
default: boolean | null,
}>
| $ReadOnly<{
type: 'StringTypeAnnotation',
default: string | null,
}>
| $ReadOnly<{
type: 'DoubleTypeAnnotation',
default: number,
}>
| $ReadOnly<{
type: 'FloatTypeAnnotation',
default: number | null,
}>
| $ReadOnly<{
type: 'Int32TypeAnnotation',
default: number,
}>
| $ReadOnly<{
type: 'StringEnumTypeAnnotation',
default: string,
options: $ReadOnlyArray<string>,
}>
| $ReadOnly<{
type: 'Int32EnumTypeAnnotation',
default: number,
options: $ReadOnlyArray<number>,
}>
| ReservedPropTypeAnnotation
| ObjectTypeAnnotation<PropTypeAnnotation>
| ComponentArrayTypeAnnotation
| MixedTypeAnnotation;
export type ReservedPropTypeAnnotation = $ReadOnly<{
type: 'ReservedPropTypeAnnotation',
name:
| 'ColorPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageRequestPrimitive'
| 'DimensionPrimitive',
}>;
export type CommandTypeAnnotation = FunctionTypeAnnotation<
CommandParamTypeAnnotation,
VoidTypeAnnotation,
>;
export type CommandParamTypeAnnotation =
| ReservedTypeAnnotation
| BooleanTypeAnnotation
| Int32TypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| StringTypeAnnotation
| ComponentCommandArrayTypeAnnotation;
export type ReservedTypeAnnotation = $ReadOnly<{
type: 'ReservedTypeAnnotation',
name: 'RootTag', // Union with more custom types.
}>;
/**
* NativeModule Types
*/
export type Nullable<+T: NativeModuleTypeAnnotation> =
| NullableTypeAnnotation<T>
| T;
export type NullableTypeAnnotation<+T: NativeModuleTypeAnnotation> = $ReadOnly<{
type: 'NullableTypeAnnotation',
typeAnnotation: T,
}>;
export type NativeModuleSchema = $ReadOnly<{
type: 'NativeModule',
aliasMap: NativeModuleAliasMap,
enumMap: NativeModuleEnumMap,
spec: NativeModuleSpec,
moduleName: string,
// Use for modules that are not used on other platforms.
// TODO: It's clearer to define `restrictedToPlatforms` instead, but
// `excludedPlatforms` is used here to be consistent with ComponentSchema.
excludedPlatforms?: $ReadOnlyArray<PlatformType>,
}>;
type NativeModuleSpec = $ReadOnly<{
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
methods: $ReadOnlyArray<NativeModulePropertyShape>,
}>;
export type NativeModuleEventEmitterShape =
NamedShape<EventEmitterTypeAnnotation>;
export type NativeModulePropertyShape = NamedShape<
Nullable<NativeModuleFunctionTypeAnnotation>,
>;
export type NativeModuleEnumMap = $ReadOnly<{
[enumName: string]: NativeModuleEnumDeclarationWithMembers,
}>;
export type NativeModuleAliasMap = $ReadOnly<{
[aliasName: string]: NativeModuleObjectTypeAnnotation,
}>;
export type NativeModuleFunctionTypeAnnotation = FunctionTypeAnnotation<
Nullable<NativeModuleParamTypeAnnotation>,
Nullable<NativeModuleReturnTypeAnnotation>,
>;
export type NativeModuleObjectTypeAnnotation = ObjectTypeAnnotation<
Nullable<NativeModuleBaseTypeAnnotation>,
>;
export type NativeModuleArrayTypeAnnotation<
+T: Nullable<NativeModuleBaseTypeAnnotation>,
> = ArrayTypeAnnotation<
| T
/**
* TODO(T72031674): Migrate all our NativeModule specs to not use
* invalid Array ElementTypes. Then, make the elementType required.
*/
| UnsafeAnyTypeAnnotation,
>;
export type UnsafeAnyTypeAnnotation = {
type: 'AnyTypeAnnotation',
};
export type NativeModuleNumberTypeAnnotation = $ReadOnly<{
type: 'NumberTypeAnnotation',
}>;
export type NativeModuleEnumMember = {
name: string,
value: StringLiteralTypeAnnotation | NumberLiteralTypeAnnotation,
};
export type NativeModuleEnumMemberType =
| 'NumberTypeAnnotation'
| 'StringTypeAnnotation';
export type NativeModuleEnumDeclaration = $ReadOnly<{
name: string,
type: 'EnumDeclaration',
memberType: NativeModuleEnumMemberType,
}>;
export type NativeModuleEnumDeclarationWithMembers = {
name: string,
type: 'EnumDeclarationWithMembers',
memberType: NativeModuleEnumMemberType,
members: $ReadOnlyArray<NativeModuleEnumMember>,
};
export type NativeModuleGenericObjectTypeAnnotation = $ReadOnly<{
type: 'GenericObjectTypeAnnotation',
// a dictionary type is codegen as "Object"
// but we know all its members are in the same type
// when it happens, the following field is non-null
dictionaryValueType?: Nullable<NativeModuleTypeAnnotation>,
}>;
export type NativeModuleTypeAliasTypeAnnotation = $ReadOnly<{
type: 'TypeAliasTypeAnnotation',
name: string,
}>;
export type NativeModulePromiseTypeAnnotation = $ReadOnly<{
type: 'PromiseTypeAnnotation',
elementType: VoidTypeAnnotation | Nullable<NativeModuleBaseTypeAnnotation>,
}>;
export type UnionTypeAnnotationMemberType =
| 'NumberTypeAnnotation'
| 'ObjectTypeAnnotation'
| 'StringTypeAnnotation';
export type NativeModuleUnionTypeAnnotation = $ReadOnly<{
type: 'UnionTypeAnnotation',
memberType: UnionTypeAnnotationMemberType,
}>;
export type NativeModuleMixedTypeAnnotation = $ReadOnly<{
type: 'MixedTypeAnnotation',
}>;
type NativeModuleEventEmitterBaseTypeAnnotation =
| BooleanTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| NativeModuleNumberTypeAnnotation
| NumberLiteralTypeAnnotation
| StringTypeAnnotation
| StringLiteralTypeAnnotation
| StringLiteralUnionTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleGenericObjectTypeAnnotation
| VoidTypeAnnotation;
export type NativeModuleEventEmitterTypeAnnotation =
| NativeModuleEventEmitterBaseTypeAnnotation
| ArrayTypeAnnotation<NativeModuleEventEmitterBaseTypeAnnotation>;
export type NativeModuleBaseTypeAnnotation =
| StringTypeAnnotation
| StringLiteralTypeAnnotation
| StringLiteralUnionTypeAnnotation
| NativeModuleNumberTypeAnnotation
| NumberLiteralTypeAnnotation
| Int32TypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| BooleanTypeAnnotation
| NativeModuleEnumDeclaration
| NativeModuleGenericObjectTypeAnnotation
| ReservedTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleArrayTypeAnnotation<Nullable<NativeModuleBaseTypeAnnotation>>
| NativeModuleObjectTypeAnnotation
| NativeModuleUnionTypeAnnotation
| NativeModuleMixedTypeAnnotation;
export type NativeModuleParamTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleParamOnlyTypeAnnotation;
export type NativeModuleReturnTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleReturnOnlyTypeAnnotation;
export type NativeModuleTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleParamOnlyTypeAnnotation
| NativeModuleReturnOnlyTypeAnnotation
| NativeModuleEventEmitterTypeAnnotation;
type NativeModuleParamOnlyTypeAnnotation = NativeModuleFunctionTypeAnnotation;
type NativeModuleReturnOnlyTypeAnnotation =
| NativeModulePromiseTypeAnnotation
| VoidTypeAnnotation;
// Add the allowed component reserved types to the native module union
export type CompleteReservedTypeAnnotation =
| ReservedTypeAnnotation
| {
type: 'ReservedTypeAnnotation',
name: ReservedPropTypeAnnotation['name'],
};
// Used by compatibility check which needs to handle all possible types
// This will eventually also include the union of all view manager types
export type CompleteTypeAnnotation =
| NativeModuleTypeAnnotation
| NativeModuleFunctionTypeAnnotation
| NullableTypeAnnotation<NativeModuleTypeAnnotation>
| EventEmitterTypeAnnotation
| NativeModuleEnumDeclarationWithMembers
| UnsafeAnyTypeAnnotation
| ArrayTypeAnnotation<CompleteTypeAnnotation>
| ObjectTypeAnnotation<CompleteTypeAnnotation>
// Components
| CommandTypeAnnotation
| CompleteReservedTypeAnnotation;

View File

@@ -0,0 +1,11 @@
/**
* 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.
*/
import type { SchemaType } from './CodegenSchema';
export declare function getErrors(schema: SchemaType): readonly string[];
export declare function validate(schema: SchemaType): void;

View File

@@ -0,0 +1,52 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const nullthrows = require('nullthrows');
function getErrors(schema) {
const errors = new Set();
// Map of component name -> Array of module names
const componentModules = new Map();
Object.keys(schema.modules).forEach(moduleName => {
const module = schema.modules[moduleName];
if (module.components == null) {
return;
}
Object.keys(module.components).forEach(componentName => {
if (module.components == null) {
return;
}
if (!componentModules.has(componentName)) {
componentModules.set(componentName, []);
}
nullthrows(componentModules.get(componentName)).push(moduleName);
});
});
componentModules.forEach((modules, componentName) => {
if (modules.length > 1) {
errors.add(
`Duplicate components found with name ${componentName}. Found in modules ${modules.join(', ')}`,
);
}
});
return Array.from(errors).sort();
}
function validate(schema) {
const errors = getErrors(schema);
if (errors.length !== 0) {
throw new Error('Errors found validating schema:\n' + errors.join('\n'));
}
}
module.exports = {
getErrors,
validate,
};

View File

@@ -0,0 +1,67 @@
/**
* 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-local
* @format
*/
'use strict';
import type {SchemaType} from './CodegenSchema';
const nullthrows = require('nullthrows');
function getErrors(schema: SchemaType): $ReadOnlyArray<string> {
const errors = new Set<string>();
// Map of component name -> Array of module names
const componentModules: Map<string, Array<string>> = new Map();
Object.keys(schema.modules).forEach(moduleName => {
const module = schema.modules[moduleName];
if (module.components == null) {
return;
}
Object.keys(module.components).forEach(componentName => {
if (module.components == null) {
return;
}
if (!componentModules.has(componentName)) {
componentModules.set(componentName, []);
}
nullthrows(componentModules.get(componentName)).push(moduleName);
});
});
componentModules.forEach((modules, componentName) => {
if (modules.length > 1) {
errors.add(
`Duplicate components found with name ${componentName}. Found in modules ${modules.join(
', ',
)}`,
);
}
});
return Array.from(errors).sort();
}
function validate(schema: SchemaType) {
const errors = getErrors(schema);
if (errors.length !== 0) {
throw new Error('Errors found validating schema:\n' + errors.join('\n'));
}
}
module.exports = {
getErrors,
validate,
};

View File

@@ -0,0 +1,48 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {
combineSchemasInFileListAndWriteToFile,
} = require('./combine-js-to-schema');
const yargs = require('yargs');
const argv = yargs
.usage('Usage: $0 <outfile> <file1> [<file2> ...]')
.option('p', {
describe:
'Platforms to generate schema for, this works on filenames: <filename>[.<platform>].(js|tsx?)',
alias: 'platform',
default: null,
})
.option('e', {
describe: 'Regular expression to exclude files from schema generation',
alias: 'exclude',
default: null,
})
.option('l', {
describe: 'Library name to use for schema generation',
alias: 'libraryName',
default: null,
})
.parseSync();
const [outfile, ...fileList] = argv._;
const platform = argv.platform;
const exclude = argv.exclude;
const excludeRegExp =
exclude != null && exclude !== '' ? new RegExp(exclude) : null;
const libraryName = argv.libraryName;
combineSchemasInFileListAndWriteToFile(
fileList,
platform != null ? platform.toLowerCase() : platform,
outfile,
excludeRegExp,
libraryName,
);

View File

@@ -0,0 +1,51 @@
/**
* 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-local
* @format
*/
'use strict';
const {
combineSchemasInFileListAndWriteToFile,
} = require('./combine-js-to-schema');
const yargs = require('yargs');
const argv = yargs
.usage('Usage: $0 <outfile> <file1> [<file2> ...]')
.option('p', {
describe:
'Platforms to generate schema for, this works on filenames: <filename>[.<platform>].(js|tsx?)',
alias: 'platform',
default: null,
})
.option('e', {
describe: 'Regular expression to exclude files from schema generation',
alias: 'exclude',
default: null,
})
.option('l', {
describe: 'Library name to use for schema generation',
alias: 'libraryName',
default: null,
})
.parseSync();
const [outfile, ...fileList] = argv._;
const platform: ?string = argv.platform;
const exclude: string = argv.exclude;
const excludeRegExp: ?RegExp =
exclude != null && exclude !== '' ? new RegExp(exclude) : null;
const libraryName: ?string = argv.libraryName;
combineSchemasInFileListAndWriteToFile(
fileList,
platform != null ? platform.toLowerCase() : platform,
outfile,
excludeRegExp,
libraryName,
);

View File

@@ -0,0 +1,103 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {FlowParser} = require('../../parsers/flow/parser');
const {TypeScriptParser} = require('../../parsers/typescript/parser');
const {filterJSFile} = require('./combine-utils');
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function combineSchemas(files, libraryName) {
const combined = files.reduce(
(merged, filename) => {
const contents = fs.readFileSync(filename, 'utf8');
if (
contents &&
(/export\s+default\s+\(?codegenNativeComponent</.test(contents) ||
/extends TurboModule/.test(contents))
) {
const isTypeScript =
path.extname(filename) === '.ts' || path.extname(filename) === '.tsx';
const parser = isTypeScript ? typescriptParser : flowParser;
const schema = parser.parseFile(filename);
if (schema && schema.modules) {
merged.modules = {
...merged.modules,
...schema.modules,
};
}
}
return merged;
},
{
modules: {} /*:: as SchemaType['modules'] */,
},
);
return {
libraryName: libraryName || '',
modules: combined.modules,
};
}
function expandDirectoriesIntoFiles(fileList, platform, exclude) {
return fileList
.flatMap(file => {
if (!fs.lstatSync(file).isDirectory()) {
return [file];
}
const filePattern = path.sep === '\\' ? file.replace(/\\/g, '/') : file;
return glob.sync(`${filePattern}/**/*{,.fb}.{js,ts,tsx}`, {
nodir: true,
// TODO: This will remove the need of slash substitution above for Windows,
// but it requires glob@v9+; with the package currenlty relying on
// glob@7.1.1; and flow-typed repo not having definitions for glob@9+.
// windowsPathsNoEscape: true,
});
})
.filter(element => filterJSFile(element, platform, exclude));
}
function combineSchemasInFileList(fileList, platform, exclude, libraryName) {
const expandedFileList = expandDirectoriesIntoFiles(
fileList,
platform,
exclude,
);
const combined = combineSchemas(expandedFileList, libraryName);
if (Object.keys(combined.modules).length === 0) {
console.error(
'No modules to process in combine-js-to-schema-cli. If this is unexpected, please check if you set up your NativeComponent correctly. See combine-js-to-schema.js for how codegen finds modules.',
);
}
return combined;
}
function combineSchemasInFileListAndWriteToFile(
fileList,
platform,
outfile,
exclude,
libraryName,
) {
const combined = combineSchemasInFileList(
fileList,
platform,
exclude,
libraryName,
);
const formattedSchema = JSON.stringify(combined);
fs.writeFileSync(outfile, formattedSchema);
}
module.exports = {
combineSchemas,
combineSchemasInFileList,
combineSchemasInFileListAndWriteToFile,
};

View File

@@ -0,0 +1,122 @@
/**
* 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-local
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema.js';
const {FlowParser} = require('../../parsers/flow/parser');
const {TypeScriptParser} = require('../../parsers/typescript/parser');
const {filterJSFile} = require('./combine-utils');
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function combineSchemas(
files: Array<string>,
libraryName: ?string,
): SchemaType {
const combined = files.reduce(
(merged, filename) => {
const contents = fs.readFileSync(filename, 'utf8');
if (
contents &&
(/export\s+default\s+\(?codegenNativeComponent</.test(contents) ||
/extends TurboModule/.test(contents))
) {
const isTypeScript =
path.extname(filename) === '.ts' || path.extname(filename) === '.tsx';
const parser = isTypeScript ? typescriptParser : flowParser;
const schema = parser.parseFile(filename);
if (schema && schema.modules) {
merged.modules = {...merged.modules, ...schema.modules};
}
}
return merged;
},
{modules: {} /*:: as SchemaType['modules'] */},
);
return {
libraryName: libraryName || '',
modules: combined.modules,
};
}
function expandDirectoriesIntoFiles(
fileList: Array<string>,
platform: ?string,
exclude: ?RegExp,
): Array<string> {
return fileList
.flatMap(file => {
if (!fs.lstatSync(file).isDirectory()) {
return [file];
}
const filePattern = path.sep === '\\' ? file.replace(/\\/g, '/') : file;
return glob.sync(`${filePattern}/**/*{,.fb}.{js,ts,tsx}`, {
nodir: true,
// TODO: This will remove the need of slash substitution above for Windows,
// but it requires glob@v9+; with the package currenlty relying on
// glob@7.1.1; and flow-typed repo not having definitions for glob@9+.
// windowsPathsNoEscape: true,
});
})
.filter(element => filterJSFile(element, platform, exclude));
}
function combineSchemasInFileList(
fileList: Array<string>,
platform: ?string,
exclude: ?RegExp,
libraryName: ?string,
): SchemaType {
const expandedFileList = expandDirectoriesIntoFiles(
fileList,
platform,
exclude,
);
const combined = combineSchemas(expandedFileList, libraryName);
if (Object.keys(combined.modules).length === 0) {
console.error(
'No modules to process in combine-js-to-schema-cli. If this is unexpected, please check if you set up your NativeComponent correctly. See combine-js-to-schema.js for how codegen finds modules.',
);
}
return combined;
}
function combineSchemasInFileListAndWriteToFile(
fileList: Array<string>,
platform: ?string,
outfile: string,
exclude: ?RegExp,
libraryName: ?string,
): void {
const combined = combineSchemasInFileList(
fileList,
platform,
exclude,
libraryName,
);
const formattedSchema = JSON.stringify(combined);
fs.writeFileSync(outfile, formattedSchema);
}
module.exports = {
combineSchemas,
combineSchemasInFileList,
combineSchemasInFileListAndWriteToFile,
};

View File

@@ -0,0 +1,108 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const assert = require('assert');
const fs = require('fs');
const yargs = require('yargs');
const argv = yargs
.option('p', {
alias: 'platform',
type: 'string',
demandOption: true,
})
.option('o', {
alias: 'output',
})
.option('s', {
alias: 'schema-query',
})
.parseSync();
const platform = argv.platform.toLowerCase();
const output = argv.output;
const schemaQuery = argv.s;
if (!['ios', 'android'].includes(platform)) {
throw new Error(`Invalid platform ${platform}`);
}
if (!schemaQuery.startsWith('@')) {
throw new Error(
"The argument provided to --schema-query must be a filename that starts with '@'.",
);
}
const schemaQueryOutputFile = schemaQuery.replace(/^@/, '');
const schemaQueryOutput = fs.readFileSync(schemaQueryOutputFile, 'utf8');
const modules = {};
const specNameToFile = {};
const schemaFiles =
schemaQueryOutput.length > 0 ? schemaQueryOutput.split(' ') : [];
for (const file of schemaFiles) {
const schema = JSON.parse(fs.readFileSync(file, 'utf8'));
if (schema.modules) {
for (const specName in schema.modules) {
var _module$excludedPlatf;
const module = schema.modules[specName];
if (modules[specName]) {
assert.deepEqual(
module,
modules[specName],
`App contained two specs with the same file name '${specName}'. Schemas: ${specNameToFile[specName]}, ${file}. Please rename one of the specs.`,
);
}
const excludedPlatforms =
(_module$excludedPlatf = module.excludedPlatforms) === null ||
_module$excludedPlatf === void 0
? void 0
: _module$excludedPlatf.map(excludedPlatform =>
excludedPlatform.toLowerCase(),
);
if (excludedPlatforms != null) {
const cxxOnlyModule =
excludedPlatforms.includes('ios') &&
excludedPlatforms.includes('android');
if (!cxxOnlyModule && excludedPlatforms.includes(platform)) {
continue;
}
}
if (module.type === 'Component') {
const components = module.components || {};
const isExcludedForPlatform = Object.values(components).some(
component => {
var _component$excludedPl;
return (_component$excludedPl = component.excludedPlatforms) ===
null || _component$excludedPl === void 0
? void 0
: _component$excludedPl
.map(p => p.toLowerCase())
.includes(platform);
},
);
if (isExcludedForPlatform) {
continue;
}
}
if (
module.type === 'Component' &&
schema.libraryName === 'FBReactNativeSpec'
) {
continue;
} else {
modules[specName] = module;
specNameToFile[specName] = file;
}
}
}
}
fs.writeFileSync(
output,
JSON.stringify({
modules,
}),
);

View File

@@ -0,0 +1,116 @@
/**
* 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-local
* @format
*/
'use strict';
import type {
ComponentSchema,
NativeModuleSchema,
SchemaType,
} from '../../CodegenSchema.js';
const assert = require('assert');
const fs = require('fs');
const yargs = require('yargs');
const argv = yargs
.option('p', {
alias: 'platform',
type: 'string',
demandOption: true,
})
.option('o', {
alias: 'output',
})
.option('s', {
alias: 'schema-query',
})
.parseSync();
const platform: string = argv.platform.toLowerCase();
const output: string = argv.output;
const schemaQuery: string = argv.s;
if (!['ios', 'android'].includes(platform)) {
throw new Error(`Invalid platform ${platform}`);
}
if (!schemaQuery.startsWith('@')) {
throw new Error(
"The argument provided to --schema-query must be a filename that starts with '@'.",
);
}
const schemaQueryOutputFile = schemaQuery.replace(/^@/, '');
const schemaQueryOutput = fs.readFileSync(schemaQueryOutputFile, 'utf8');
const modules: {
[hasteModuleName: string]: NativeModuleSchema | ComponentSchema,
} = {};
const specNameToFile: {[hasteModuleName: string]: string} = {};
const schemaFiles =
schemaQueryOutput.length > 0 ? schemaQueryOutput.split(' ') : [];
for (const file of schemaFiles) {
const schema: SchemaType = JSON.parse(fs.readFileSync(file, 'utf8'));
if (schema.modules) {
for (const specName in schema.modules) {
const module = schema.modules[specName];
if (modules[specName]) {
assert.deepEqual(
module,
modules[specName],
`App contained two specs with the same file name '${specName}'. Schemas: ${specNameToFile[specName]}, ${file}. Please rename one of the specs.`,
);
}
const excludedPlatforms = module.excludedPlatforms?.map(
excludedPlatform => excludedPlatform.toLowerCase(),
);
if (excludedPlatforms != null) {
const cxxOnlyModule =
excludedPlatforms.includes('ios') &&
excludedPlatforms.includes('android');
if (!cxxOnlyModule && excludedPlatforms.includes(platform)) {
continue;
}
}
if (module.type === 'Component') {
const components = module.components || {};
const isExcludedForPlatform = Object.values(components).some(
component =>
component.excludedPlatforms
?.map(p => p.toLowerCase())
.includes(platform),
);
if (isExcludedForPlatform) {
continue;
}
}
if (
module.type === 'Component' &&
schema.libraryName === 'FBReactNativeSpec'
) {
continue;
} else {
modules[specName] = module;
specNameToFile[specName] = file;
}
}
}
}
fs.writeFileSync(output, JSON.stringify({modules}));

View File

@@ -0,0 +1,59 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const path = require('path');
/**
* This function is used by the CLI to decide whether a JS/TS file has to be
* processed or not by the Codegen.
*
* Parameters:
* - originalFilePath: the path to the file
* - currentPlatform: the platform for which we are creating the specs
* Returns: `true` if the file can be used to generate code; `false` otherwise
*/
function filterJSFile(originalFilePath, currentPlatform, excludeRegExp) {
// Remove `.fb` if it exists (see `react-native.cconf`).
const filePath = originalFilePath.replace(/\.fb(\.|$)/, '$1');
const basename = path.basename(filePath);
const isSpecFile = /^(Native.+|.+NativeComponent)/.test(basename);
const isNotNativeUIManager = !filePath.endsWith('NativeUIManager.js');
const isNotTest = !filePath.includes('__tests');
const isNotExcluded = excludeRegExp == null || !excludeRegExp.test(filePath);
const isNotTSTypeDefinition = !filePath.endsWith('.d.ts');
const isValidCandidate =
isSpecFile &&
isNotNativeUIManager &&
isNotExcluded &&
isNotTest &&
isNotTSTypeDefinition;
const filenameComponents = basename.split('.');
const isPlatformAgnostic = filenameComponents.length === 2;
if (currentPlatform == null) {
// need to accept only files that are platform agnostic
return isValidCandidate && isPlatformAgnostic;
}
// If a platform is passed, accept both platform agnostic specs...
if (isPlatformAgnostic) {
return isValidCandidate;
}
// ...and specs that share the same platform as the one passed.
// specfiles must follow the pattern: <filename>[.<platform>].(js|ts|tsx)
const filePlatform =
filenameComponents.length > 2 ? filenameComponents[1] : 'unknown';
return isValidCandidate && currentPlatform === filePlatform;
}
module.exports = {
filterJSFile,
};

View File

@@ -0,0 +1,68 @@
/**
* 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-local
* @format
*/
'use strict';
const path = require('path');
/**
* This function is used by the CLI to decide whether a JS/TS file has to be
* processed or not by the Codegen.
*
* Parameters:
* - originalFilePath: the path to the file
* - currentPlatform: the platform for which we are creating the specs
* Returns: `true` if the file can be used to generate code; `false` otherwise
*/
function filterJSFile(
originalFilePath: string,
currentPlatform: ?string,
excludeRegExp: ?RegExp,
): boolean {
// Remove `.fb` if it exists (see `react-native.cconf`).
const filePath = originalFilePath.replace(/\.fb(\.|$)/, '$1');
const basename = path.basename(filePath);
const isSpecFile = /^(Native.+|.+NativeComponent)/.test(basename);
const isNotNativeUIManager = !filePath.endsWith('NativeUIManager.js');
const isNotTest = !filePath.includes('__tests');
const isNotExcluded = excludeRegExp == null || !excludeRegExp.test(filePath);
const isNotTSTypeDefinition = !filePath.endsWith('.d.ts');
const isValidCandidate =
isSpecFile &&
isNotNativeUIManager &&
isNotExcluded &&
isNotTest &&
isNotTSTypeDefinition;
const filenameComponents = basename.split('.');
const isPlatformAgnostic = filenameComponents.length === 2;
if (currentPlatform == null) {
// need to accept only files that are platform agnostic
return isValidCandidate && isPlatformAgnostic;
}
// If a platform is passed, accept both platform agnostic specs...
if (isPlatformAgnostic) {
return isValidCandidate;
}
// ...and specs that share the same platform as the one passed.
// specfiles must follow the pattern: <filename>[.<platform>].(js|ts|tsx)
const filePlatform =
filenameComponents.length > 2 ? filenameComponents[1] : 'unknown';
return isValidCandidate && currentPlatform === filePlatform;
}
module.exports = {
filterJSFile,
};

View File

@@ -0,0 +1,67 @@
/**
* 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.
*
*
* @format
*/
/**
* This generates all possible outputs by executing all available generators.
*/
'use strict';
const RNCodegen = require('../../generators/RNCodegen.js');
const fs = require('fs');
const args = process.argv.slice(2);
if (args.length < 3) {
throw new Error(
`Expected to receive path to schema, library name, output directory and module spec name. Received ${args.join(', ')}`,
);
}
const schemaPath = args[0];
const libraryName = args[1];
const outputDirectory = args[2];
const packageName = args[3];
const assumeNonnull = args[4] === 'true' || args[4] === 'True';
const schemaText = fs.readFileSync(schemaPath, 'utf-8');
if (schemaText == null) {
throw new Error(`Can't find schema at ${schemaPath}`);
}
fs.mkdirSync(outputDirectory, {
recursive: true,
});
let schema;
try {
schema = JSON.parse(schemaText);
} catch (err) {
throw new Error(`Can't parse schema to JSON. ${schemaPath}`);
}
const includeGetDebugPropsImplementation =
libraryName.includes('FBReactNativeSpec');
RNCodegen.generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull,
includeGetDebugPropsImplementation,
},
{
generators: [
'descriptors',
'events',
'props',
'states',
'tests',
'shadow-nodes',
'modulesAndroid',
'modulesCxx',
'modulesIOS',
],
},
);

View File

@@ -0,0 +1,73 @@
/**
* 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
* @format
*/
/**
* This generates all possible outputs by executing all available generators.
*/
'use strict';
const RNCodegen = require('../../generators/RNCodegen.js');
const fs = require('fs');
const args = process.argv.slice(2);
if (args.length < 3) {
throw new Error(
`Expected to receive path to schema, library name, output directory and module spec name. Received ${args.join(
', ',
)}`,
);
}
const schemaPath = args[0];
const libraryName = args[1];
const outputDirectory = args[2];
const packageName = args[3];
const assumeNonnull = args[4] === 'true' || args[4] === 'True';
const schemaText = fs.readFileSync(schemaPath, 'utf-8');
if (schemaText == null) {
throw new Error(`Can't find schema at ${schemaPath}`);
}
fs.mkdirSync(outputDirectory, {recursive: true});
let schema;
try {
schema = JSON.parse(schemaText);
} catch (err) {
throw new Error(`Can't parse schema to JSON. ${schemaPath}`);
}
const includeGetDebugPropsImplementation: boolean =
libraryName.includes('FBReactNativeSpec');
RNCodegen.generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull,
includeGetDebugPropsImplementation,
},
{
generators: [
'descriptors',
'events',
'props',
'states',
'tests',
'shadow-nodes',
'modulesAndroid',
'modulesCxx',
'modulesIOS',
],
},
);

View File

@@ -0,0 +1,15 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const parseFiles = require('./parser.js');
const [...fileList] = process.argv.slice(2);
parseFiles(fileList);

View File

@@ -0,0 +1,17 @@
/**
* 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-local
* @format
*/
'use strict';
const parseFiles = require('./parser.js');
const [...fileList] = process.argv.slice(2);
parseFiles(fileList);

View File

@@ -0,0 +1,26 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {FlowParser} = require('../../parsers/flow/parser');
const {TypeScriptParser} = require('../../parsers/typescript/parser');
const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function parseFiles(files) {
files.forEach(filename => {
const isTypeScript =
path.extname(filename) === '.ts' || path.extname(filename) === '.tsx';
const parser = isTypeScript ? typescriptParser : flowParser;
console.log(filename, JSON.stringify(parser.parseFile(filename), null, 2));
});
}
module.exports = parseFiles;

View File

@@ -0,0 +1,31 @@
/**
* 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-local
* @format
*/
'use strict';
const {FlowParser} = require('../../parsers/flow/parser');
const {TypeScriptParser} = require('../../parsers/typescript/parser');
const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function parseFiles(files: Array<string>) {
files.forEach(filename => {
const isTypeScript =
path.extname(filename) === '.ts' || path.extname(filename) === '.tsx';
const parser = isTypeScript ? typescriptParser : flowParser;
console.log(filename, JSON.stringify(parser.parseFile(filename), null, 2));
});
}
module.exports = parseFiles;

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# 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.
set -e
set -u
THIS_DIR=$(cd -P "$(dirname "$(realpath "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
# shellcheck source=xplat/js/env-utils/setup_env_vars.sh
source "$THIS_DIR/../../../../../../env-utils/setup_env_vars.sh"
exec "$FLOW_NODE_BINARY" "$THIS_DIR/parser.js" "$@"

View File

@@ -0,0 +1,99 @@
/**
* 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.
*/
import type { SchemaType } from '../CodegenSchema';
export type FilesOutput = Map<string, string>;
export type LibraryGeneratorFunction = (libraryName: string, schema: SchemaType, packageName: string | undefined, assumeNonnull: boolean) => FilesOutput;
export type SchemaGeneratorFunction = (schemas: { [key: string]: SchemaType }) => FilesOutput;
export type ViewGeneratorFunction = (libraryName: string, schema: SchemaType) => FilesOutput;
type LibraryGeneratorNames =
| 'generateComponentDescriptorH'
| 'generateComponentDescriptorCpp'
| 'generateComponentHObjCpp'
| 'generateEventEmitterCpp'
| 'generateEventEmitterH'
| 'generatePropsCpp'
| 'generatePropsH'
| 'generateStateCpp'
| 'generateStateH'
| 'generateModuleH'
| 'generateModuleCpp'
| 'generateModuleObjCpp'
| 'generateModuleJavaSpec'
| 'generateModuleJniCpp'
| 'generateModuleJniH'
| 'generatePropsJavaInterface'
| 'generatePropsJavaDelegate'
| 'generateTests'
| 'generateShadowNodeCpp'
| 'generateShadowNodeH'
;
type SchemaGeneratorNames =
| 'generateThirdPartyFabricComponentsProviderObjCpp'
| 'generateThirdPartyFabricComponentsProviderH'
;
type ViewGeneratorNames =
| 'generateViewConfigJs'
;
export type AllGenerators =
& { readonly [key in LibraryGeneratorNames]: LibraryGeneratorFunction; }
& { readonly [key in SchemaGeneratorNames]: SchemaGeneratorFunction; }
& { readonly [key in ViewGeneratorNames]: ViewGeneratorFunction; }
;
export type LibraryGenerators =
| 'componentsAndroid'
| 'componentsIOS'
| 'descriptors'
| 'events'
| 'props'
| 'states'
| 'tests'
| 'shadow-nodes'
| 'modulesAndroid'
| 'modulesCxx'
| 'modulesIOS'
;
export type SchemaGenerators =
| 'providerIOS'
;
export interface LibraryOptions {
libraryName: string;
schema: SchemaType;
outputDirectory: string;
packageName?: string | undefined;
assumeNonnull: boolean;
}
export interface LibraryConfig {
generators: LibraryGenerators[];
test?: boolean | undefined;
}
export interface SchemasOptions {
schemas: { [key: string]: SchemaType };
outputDirectory: string;
}
export interface SchemasConfig {
generators: SchemaGenerators[];
test?: boolean | undefined;
}
export declare const allGenerators: AllGenerators;
export declare const libraryGenerators: { readonly [key in LibraryGenerators]: LibraryGeneratorFunction };
export declare const schemaGenerators: { readonly [key in SchemaGenerators]: SchemaGeneratorFunction };
export declare function generate(options: LibraryOptions, config: LibraryConfig): boolean;
export declare function generateFromSchemas(options: SchemasOptions, config: SchemasConfig): boolean;
export declare function generateViewConfig(options: LibraryOptions): string;

View File

@@ -0,0 +1,265 @@
/**
* 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.
*
*
* @format
*/
'use strict';
/*
TODO:
- ViewConfigs should spread in View's valid attributes
*/
const schemaValidator = require('../SchemaValidator.js');
const generateComponentDescriptorCpp = require('./components/GenerateComponentDescriptorCpp.js');
const generateComponentDescriptorH = require('./components/GenerateComponentDescriptorH.js');
const generateComponentHObjCpp = require('./components/GenerateComponentHObjCpp.js');
const generateEventEmitterCpp = require('./components/GenerateEventEmitterCpp.js');
const generateEventEmitterH = require('./components/GenerateEventEmitterH.js');
const generatePropsCpp = require('./components/GeneratePropsCpp.js');
const generatePropsH = require('./components/GeneratePropsH.js');
const generatePropsJavaDelegate = require('./components/GeneratePropsJavaDelegate.js');
const generatePropsJavaInterface = require('./components/GeneratePropsJavaInterface.js');
const generateShadowNodeCpp = require('./components/GenerateShadowNodeCpp.js');
const generateShadowNodeH = require('./components/GenerateShadowNodeH.js');
const generateStateCpp = require('./components/GenerateStateCpp.js');
const generateStateH = require('./components/GenerateStateH.js');
const generateTests = require('./components/GenerateTests.js');
const generateThirdPartyFabricComponentsProviderH = require('./components/GenerateThirdPartyFabricComponentsProviderH.js');
const generateThirdPartyFabricComponentsProviderObjCpp = require('./components/GenerateThirdPartyFabricComponentsProviderObjCpp.js');
const generateViewConfigJs = require('./components/GenerateViewConfigJs.js');
const generateModuleH = require('./modules/GenerateModuleH.js');
const generateModuleJavaSpec = require('./modules/GenerateModuleJavaSpec.js');
const generateModuleJniCpp = require('./modules/GenerateModuleJniCpp.js');
const generateModuleJniH = require('./modules/GenerateModuleJniH.js');
const generateModuleObjCpp = require('./modules/GenerateModuleObjCpp');
const fs = require('fs');
const path = require('path');
const ALL_GENERATORS = {
generateComponentDescriptorH: generateComponentDescriptorH.generate,
generateComponentDescriptorCpp: generateComponentDescriptorCpp.generate,
generateComponentHObjCpp: generateComponentHObjCpp.generate,
generateEventEmitterCpp: generateEventEmitterCpp.generate,
generateEventEmitterH: generateEventEmitterH.generate,
generatePropsCpp: generatePropsCpp.generate,
generatePropsH: generatePropsH.generate,
generateStateCpp: generateStateCpp.generate,
generateStateH: generateStateH.generate,
generateModuleH: generateModuleH.generate,
generateModuleObjCpp: generateModuleObjCpp.generate,
generateModuleJavaSpec: generateModuleJavaSpec.generate,
generateModuleJniCpp: generateModuleJniCpp.generate,
generateModuleJniH: generateModuleJniH.generate,
generatePropsJavaInterface: generatePropsJavaInterface.generate,
generatePropsJavaDelegate: generatePropsJavaDelegate.generate,
generateTests: generateTests.generate,
generateShadowNodeCpp: generateShadowNodeCpp.generate,
generateShadowNodeH: generateShadowNodeH.generate,
generateThirdPartyFabricComponentsProviderObjCpp:
generateThirdPartyFabricComponentsProviderObjCpp.generate,
generateThirdPartyFabricComponentsProviderH:
generateThirdPartyFabricComponentsProviderH.generate,
generateViewConfigJs: generateViewConfigJs.generate,
};
const LIBRARY_GENERATORS = {
descriptors: [
generateComponentDescriptorCpp.generate,
generateComponentDescriptorH.generate,
],
events: [generateEventEmitterCpp.generate, generateEventEmitterH.generate],
states: [generateStateCpp.generate, generateStateH.generate],
props: [
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
// TODO: Refactor this to consolidate various C++ output variation instead of forking per platform.
componentsAndroid: [
// JNI/C++ files
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
// Java files
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
componentsIOS: [
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
modulesAndroid: [
generateModuleJniCpp.generate,
generateModuleJniH.generate,
generateModuleJavaSpec.generate,
],
modulesCxx: [generateModuleH.generate],
modulesIOS: [generateModuleObjCpp.generate],
tests: [generateTests.generate],
'shadow-nodes': [
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
};
const SCHEMAS_GENERATORS = {
providerIOS: [
generateThirdPartyFabricComponentsProviderObjCpp.generate,
generateThirdPartyFabricComponentsProviderH.generate,
],
};
function writeMapToFiles(map) {
let success = true;
map.forEach(file => {
try {
const location = path.join(file.outputDir, file.name);
const dirName = path.dirname(location);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, {
recursive: true,
});
}
fs.writeFileSync(location, file.content);
} catch (error) {
success = false;
console.error(`Failed to write ${file.name} to ${file.outputDir}`, error);
}
});
return success;
}
function checkFilesForChanges(generated) {
let hasChanged = false;
generated.forEach(file => {
const location = path.join(file.outputDir, file.name);
const currentContents = fs.readFileSync(location, 'utf8');
if (currentContents !== file.content) {
console.error(`- ${file.name} has changed`);
hasChanged = true;
}
});
return !hasChanged;
}
function checkOrWriteFiles(generatedFiles, test) {
if (test === true) {
return checkFilesForChanges(generatedFiles);
}
return writeMapToFiles(generatedFiles);
}
module.exports = {
allGenerators: ALL_GENERATORS,
generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull,
useLocalIncludePaths,
includeGetDebugPropsImplementation = false,
libraryGenerators = LIBRARY_GENERATORS,
},
{generators, test},
) {
schemaValidator.validate(schema);
const defaultHeaderPrefix = 'react/renderer/components';
const headerPrefix =
useLocalIncludePaths === true
? ''
: `${defaultHeaderPrefix}/${libraryName}/`;
function composePath(intermediate) {
return path.join(outputDirectory, intermediate, libraryName);
}
const componentIOSOutput = composePath(
useLocalIncludePaths === true ? '' : defaultHeaderPrefix,
);
const modulesIOSOutput = composePath('./');
const outputFoldersForGenerators = {
componentsIOS: componentIOSOutput,
modulesIOS: modulesIOSOutput,
descriptors: outputDirectory,
events: outputDirectory,
props: outputDirectory,
states: outputDirectory,
componentsAndroid: outputDirectory,
modulesAndroid: outputDirectory,
modulesCxx: outputDirectory,
tests: outputDirectory,
'shadow-nodes': outputDirectory,
};
const generatedFiles = [];
for (const name of generators) {
for (const generator of libraryGenerators[name]) {
generator(
libraryName,
schema,
packageName,
assumeNonnull,
headerPrefix,
includeGetDebugPropsImplementation,
).forEach((contents, fileName) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputFoldersForGenerators[name],
});
});
}
}
return checkOrWriteFiles(generatedFiles, test);
},
generateFromSchemas(
{schemas, outputDirectory, supportedApplePlatforms},
{generators, test},
) {
Object.keys(schemas).forEach(libraryName =>
schemaValidator.validate(schemas[libraryName]),
);
const generatedFiles = [];
for (const name of generators) {
for (const generator of SCHEMAS_GENERATORS[name]) {
generator(schemas, supportedApplePlatforms).forEach(
(contents, fileName) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputDirectory,
});
},
);
}
}
return checkOrWriteFiles(generatedFiles, test);
},
generateViewConfig({libraryName, schema}) {
schemaValidator.validate(schema);
const result = generateViewConfigJs
.generate(libraryName, schema)
.values()
.next();
if (typeof result.value !== 'string') {
throw new Error(`Failed to generate view config for ${libraryName}`);
}
return result.value;
},
};

View File

@@ -0,0 +1,357 @@
/**
* 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-local
* @format
*/
'use strict';
/*
TODO:
- ViewConfigs should spread in View's valid attributes
*/
import type {SchemaType} from '../CodegenSchema';
const schemaValidator = require('../SchemaValidator.js');
const generateComponentDescriptorCpp = require('./components/GenerateComponentDescriptorCpp.js');
const generateComponentDescriptorH = require('./components/GenerateComponentDescriptorH.js');
const generateComponentHObjCpp = require('./components/GenerateComponentHObjCpp.js');
const generateEventEmitterCpp = require('./components/GenerateEventEmitterCpp.js');
const generateEventEmitterH = require('./components/GenerateEventEmitterH.js');
const generatePropsCpp = require('./components/GeneratePropsCpp.js');
const generatePropsH = require('./components/GeneratePropsH.js');
const generatePropsJavaDelegate = require('./components/GeneratePropsJavaDelegate.js');
const generatePropsJavaInterface = require('./components/GeneratePropsJavaInterface.js');
const generateShadowNodeCpp = require('./components/GenerateShadowNodeCpp.js');
const generateShadowNodeH = require('./components/GenerateShadowNodeH.js');
const generateStateCpp = require('./components/GenerateStateCpp.js');
const generateStateH = require('./components/GenerateStateH.js');
const generateTests = require('./components/GenerateTests.js');
const generateThirdPartyFabricComponentsProviderH = require('./components/GenerateThirdPartyFabricComponentsProviderH.js');
const generateThirdPartyFabricComponentsProviderObjCpp = require('./components/GenerateThirdPartyFabricComponentsProviderObjCpp.js');
const generateViewConfigJs = require('./components/GenerateViewConfigJs.js');
const generateModuleH = require('./modules/GenerateModuleH.js');
const generateModuleJavaSpec = require('./modules/GenerateModuleJavaSpec.js');
const generateModuleJniCpp = require('./modules/GenerateModuleJniCpp.js');
const generateModuleJniH = require('./modules/GenerateModuleJniH.js');
const generateModuleObjCpp = require('./modules/GenerateModuleObjCpp');
const fs = require('fs');
const path = require('path');
const ALL_GENERATORS = {
generateComponentDescriptorH: generateComponentDescriptorH.generate,
generateComponentDescriptorCpp: generateComponentDescriptorCpp.generate,
generateComponentHObjCpp: generateComponentHObjCpp.generate,
generateEventEmitterCpp: generateEventEmitterCpp.generate,
generateEventEmitterH: generateEventEmitterH.generate,
generatePropsCpp: generatePropsCpp.generate,
generatePropsH: generatePropsH.generate,
generateStateCpp: generateStateCpp.generate,
generateStateH: generateStateH.generate,
generateModuleH: generateModuleH.generate,
generateModuleObjCpp: generateModuleObjCpp.generate,
generateModuleJavaSpec: generateModuleJavaSpec.generate,
generateModuleJniCpp: generateModuleJniCpp.generate,
generateModuleJniH: generateModuleJniH.generate,
generatePropsJavaInterface: generatePropsJavaInterface.generate,
generatePropsJavaDelegate: generatePropsJavaDelegate.generate,
generateTests: generateTests.generate,
generateShadowNodeCpp: generateShadowNodeCpp.generate,
generateShadowNodeH: generateShadowNodeH.generate,
generateThirdPartyFabricComponentsProviderObjCpp:
generateThirdPartyFabricComponentsProviderObjCpp.generate,
generateThirdPartyFabricComponentsProviderH:
generateThirdPartyFabricComponentsProviderH.generate,
generateViewConfigJs: generateViewConfigJs.generate,
};
export type FilesOutput = Map<string, string>;
export type GenerateFunction = (
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean,
) => FilesOutput;
export type LibraryGeneratorsFunctions = $ReadOnly<{
[string]: Array<GenerateFunction>,
}>;
export type LibraryOptions = $ReadOnly<{
libraryName: string,
schema: SchemaType,
outputDirectory: string,
packageName?: string, // Some platforms have a notion of package, which should be configurable.
assumeNonnull: boolean,
useLocalIncludePaths?: boolean,
includeGetDebugPropsImplementation?: boolean,
libraryGenerators?: LibraryGeneratorsFunctions,
}>;
export type SchemasOptions = $ReadOnly<{
schemas: {[string]: SchemaType},
outputDirectory: string,
supportedApplePlatforms?: {[string]: {[string]: boolean}},
}>;
export type LibraryGenerators =
| 'componentsAndroid'
| 'componentsIOS'
| 'descriptors'
| 'events'
| 'props'
| 'states'
| 'tests'
| 'shadow-nodes'
| 'modulesAndroid'
| 'modulesCxx'
| 'modulesIOS';
export type SchemasGenerators = 'providerIOS';
export type LibraryConfig = $ReadOnly<{
generators: Array<LibraryGenerators>,
test?: boolean,
}>;
export type SchemasConfig = $ReadOnly<{
generators: Array<SchemasGenerators>,
test?: boolean,
}>;
const LIBRARY_GENERATORS: LibraryGeneratorsFunctions = {
descriptors: [
generateComponentDescriptorCpp.generate,
generateComponentDescriptorH.generate,
],
events: [generateEventEmitterCpp.generate, generateEventEmitterH.generate],
states: [generateStateCpp.generate, generateStateH.generate],
props: [
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
// TODO: Refactor this to consolidate various C++ output variation instead of forking per platform.
componentsAndroid: [
// JNI/C++ files
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
// Java files
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
componentsIOS: [
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
modulesAndroid: [
generateModuleJniCpp.generate,
generateModuleJniH.generate,
generateModuleJavaSpec.generate,
],
modulesCxx: [generateModuleH.generate],
modulesIOS: [generateModuleObjCpp.generate],
tests: [generateTests.generate],
'shadow-nodes': [
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
};
const SCHEMAS_GENERATORS = {
providerIOS: [
generateThirdPartyFabricComponentsProviderObjCpp.generate,
generateThirdPartyFabricComponentsProviderH.generate,
],
};
type CodeGenFile = {
name: string,
content: string,
outputDir: string,
};
function writeMapToFiles(map: Array<CodeGenFile>) {
let success = true;
map.forEach(file => {
try {
const location = path.join(file.outputDir, file.name);
const dirName = path.dirname(location);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, {recursive: true});
}
fs.writeFileSync(location, file.content);
} catch (error) {
success = false;
console.error(`Failed to write ${file.name} to ${file.outputDir}`, error);
}
});
return success;
}
function checkFilesForChanges(generated: Array<CodeGenFile>): boolean {
let hasChanged = false;
generated.forEach(file => {
const location = path.join(file.outputDir, file.name);
const currentContents = fs.readFileSync(location, 'utf8');
if (currentContents !== file.content) {
console.error(`- ${file.name} has changed`);
hasChanged = true;
}
});
return !hasChanged;
}
function checkOrWriteFiles(
generatedFiles: Array<CodeGenFile>,
test: void | boolean,
): boolean {
if (test === true) {
return checkFilesForChanges(generatedFiles);
}
return writeMapToFiles(generatedFiles);
}
module.exports = {
allGenerators: ALL_GENERATORS,
generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull,
useLocalIncludePaths,
includeGetDebugPropsImplementation = false,
libraryGenerators = LIBRARY_GENERATORS,
}: LibraryOptions,
{generators, test}: LibraryConfig,
): boolean {
schemaValidator.validate(schema);
const defaultHeaderPrefix = 'react/renderer/components';
const headerPrefix =
useLocalIncludePaths === true
? ''
: `${defaultHeaderPrefix}/${libraryName}/`;
function composePath(intermediate: string) {
return path.join(outputDirectory, intermediate, libraryName);
}
const componentIOSOutput = composePath(
useLocalIncludePaths === true ? '' : defaultHeaderPrefix,
);
const modulesIOSOutput = composePath('./');
const outputFoldersForGenerators = {
componentsIOS: componentIOSOutput,
modulesIOS: modulesIOSOutput,
descriptors: outputDirectory,
events: outputDirectory,
props: outputDirectory,
states: outputDirectory,
componentsAndroid: outputDirectory,
modulesAndroid: outputDirectory,
modulesCxx: outputDirectory,
tests: outputDirectory,
'shadow-nodes': outputDirectory,
};
const generatedFiles: Array<CodeGenFile> = [];
for (const name of generators) {
for (const generator of libraryGenerators[name]) {
generator(
libraryName,
schema,
packageName,
assumeNonnull,
headerPrefix,
includeGetDebugPropsImplementation,
).forEach((contents: string, fileName: string) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputFoldersForGenerators[name],
});
});
}
}
return checkOrWriteFiles(generatedFiles, test);
},
generateFromSchemas(
{schemas, outputDirectory, supportedApplePlatforms}: SchemasOptions,
{generators, test}: SchemasConfig,
): boolean {
Object.keys(schemas).forEach(libraryName =>
schemaValidator.validate(schemas[libraryName]),
);
const generatedFiles: Array<CodeGenFile> = [];
for (const name of generators) {
for (const generator of SCHEMAS_GENERATORS[name]) {
generator(schemas, supportedApplePlatforms).forEach(
(contents: string, fileName: string) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputDirectory,
});
},
);
}
}
return checkOrWriteFiles(generatedFiles, test);
},
generateViewConfig({
libraryName,
schema,
}: Pick<LibraryOptions, 'libraryName' | 'schema'>): string {
schemaValidator.validate(schema);
const result = generateViewConfigJs
.generate(libraryName, schema)
.values()
.next();
if (typeof result.value !== 'string') {
throw new Error(`Failed to generate view config for ${libraryName}`);
}
return result.value;
},
};

View File

@@ -0,0 +1,16 @@
/**
* 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.
*
*
* @format
*/
function wrapOptional(type, isRequired) {
return isRequired ? type : `std::optional<${type}>`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,17 @@
/**
* 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
*/
function wrapOptional(type: string, isRequired: boolean): string {
return isRequired ? type : `std::optional<${type}>`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,26 @@
/**
* 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.
*
*
* @format
*/
const objectTypeForPrimitiveType = {
boolean: 'Boolean',
double: 'Double',
float: 'Float',
int: 'Integer',
};
function wrapOptional(type, isRequired) {
var _objectTypeForPrimiti;
return isRequired
? type
: // $FlowFixMe[invalid-computed-prop]
`@Nullable ${(_objectTypeForPrimiti = objectTypeForPrimitiveType[type]) !== null && _objectTypeForPrimiti !== void 0 ? _objectTypeForPrimiti : type}`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,27 @@
/**
* 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
*/
const objectTypeForPrimitiveType = {
boolean: 'Boolean',
double: 'Double',
float: 'Float',
int: 'Integer',
};
function wrapOptional(type: string, isRequired: boolean): string {
return isRequired
? type
: // $FlowFixMe[invalid-computed-prop]
`@Nullable ${objectTypeForPrimitiveType[type] ?? type}`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,16 @@
/**
* 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.
*
*
* @format
*/
function wrapOptional(type, isRequired) {
return isRequired ? type : `${type} _Nullable`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,17 @@
/**
* 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
*/
function wrapOptional(type: string, isRequired: boolean): string {
return isRequired ? type : `${type} _Nullable`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,47 @@
/**
* 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.
*
*
* @format
*/
'use strict';
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function indent(nice, spaces) {
return nice
.split('\n')
.map((line, index) => {
if (line.length === 0 || index === 0) {
return line;
}
const emptySpaces = new Array(spaces + 1).join(' ');
return emptySpaces + line;
})
.join('\n');
}
function toPascalCase(inString) {
if (inString.length === 0) {
return inString;
}
return inString[0].toUpperCase() + inString.slice(1);
}
function toSafeCppString(input) {
return input.split('-').map(toPascalCase).join('');
}
function getEnumName(moduleName, origEnumName) {
const uppercasedPropName = toSafeCppString(origEnumName);
return `${moduleName}${uppercasedPropName}`;
}
module.exports = {
capitalize,
indent,
toPascalCase,
toSafeCppString,
getEnumName,
};

View File

@@ -0,0 +1,53 @@
/**
* 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';
function capitalize(string: string): string {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function indent(nice: string, spaces: number): string {
return nice
.split('\n')
.map((line, index) => {
if (line.length === 0 || index === 0) {
return line;
}
const emptySpaces = new Array<mixed>(spaces + 1).join(' ');
return emptySpaces + line;
})
.join('\n');
}
function toPascalCase(inString: string): string {
if (inString.length === 0) {
return inString;
}
return inString[0].toUpperCase() + inString.slice(1);
}
function toSafeCppString(input: string): string {
return input.split('-').map(toPascalCase).join('');
}
function getEnumName(moduleName: string, origEnumName: string): string {
const uppercasedPropName = toSafeCppString(origEnumName);
return `${moduleName}${uppercasedPropName}`;
}
module.exports = {
capitalize,
indent,
toPascalCase,
toSafeCppString,
getEnumName,
};

View File

@@ -0,0 +1,229 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {getEnumName} = require('../Utils');
const {
generateStructName,
getCppTypeForAnnotation,
getEnumMaskName,
getImports,
} = require('./CppHelpers.js');
function getNativeTypeFromAnnotation(componentName, prop, nameParts) {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return getCppTypeForAnnotation(typeAnnotation.type);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return 'SharedColor';
case 'ImageSourcePrimitive':
return 'ImageSource';
case 'ImageRequestPrimitive':
return 'ImageRequest';
case 'PointPrimitive':
return 'Point';
case 'EdgeInsetsPrimitive':
return 'EdgeInsets';
case 'DimensionPrimitive':
return 'YGValue';
default:
typeAnnotation.name;
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
const arrayType = typeAnnotation.elementType.type;
if (arrayType === 'ArrayTypeAnnotation') {
return `std::vector<${getNativeTypeFromAnnotation(
componentName,
{
typeAnnotation: typeAnnotation.elementType,
name: '',
},
nameParts.concat([prop.name]),
)}>`;
}
if (arrayType === 'ObjectTypeAnnotation') {
const structName = generateStructName(
componentName,
nameParts.concat([prop.name]),
);
return `std::vector<${structName}>`;
}
if (arrayType === 'StringEnumTypeAnnotation') {
const enumName = getEnumName(componentName, prop.name);
return getEnumMaskName(enumName);
}
const itemAnnotation = getNativeTypeFromAnnotation(
componentName,
{
typeAnnotation: typeAnnotation.elementType,
name: componentName,
},
nameParts.concat([prop.name]),
);
return `std::vector<${itemAnnotation}>`;
}
case 'ObjectTypeAnnotation': {
return generateStructName(componentName, nameParts.concat([prop.name]));
}
case 'StringEnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'Int32EnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'MixedTypeAnnotation':
return 'folly::dynamic';
default:
typeAnnotation;
throw new Error(
`Received invalid typeAnnotation for ${componentName} prop ${prop.name}, received ${typeAnnotation.type}`,
);
}
}
/// This function process some types if we need to customize them
/// For example, the ImageSource and the reserved types could be trasformed into
/// const address instead of using them as plain types.
function convertTypesToConstAddressIfNeeded(type, convertibleTypes) {
if (convertibleTypes.has(type)) {
return `${type} const &`;
}
return type;
}
function convertValueToSharedPointerWithMove(type, value, convertibleTypes) {
if (convertibleTypes.has(type)) {
return `std::make_shared<${type}>(std::move(${value}))`;
}
return value;
}
function convertVariableToSharedPointer(type, convertibleTypes) {
if (convertibleTypes.has(type)) {
return `std::shared_ptr<${type}>`;
}
return type;
}
function convertVariableToPointer(type, value, convertibleTypes) {
if (convertibleTypes.has(type)) {
return `*${value}`;
}
return value;
}
const convertCtorParamToAddressType = type => {
const typesToConvert = new Set();
typesToConvert.add('ImageSource');
return convertTypesToConstAddressIfNeeded(type, typesToConvert);
};
const convertCtorInitToSharedPointers = (type, value) => {
const typesToConvert = new Set();
typesToConvert.add('ImageRequest');
return convertValueToSharedPointerWithMove(type, value, typesToConvert);
};
const convertGettersReturnTypeToAddressType = type => {
const typesToConvert = new Set();
typesToConvert.add('ImageRequest');
return convertTypesToConstAddressIfNeeded(type, typesToConvert);
};
const convertVarTypeToSharedPointer = type => {
const typesToConvert = new Set();
typesToConvert.add('ImageRequest');
return convertVariableToSharedPointer(type, typesToConvert);
};
const convertVarValueToPointer = (type, value) => {
const typesToConvert = new Set();
typesToConvert.add('ImageRequest');
return convertVariableToPointer(type, value, typesToConvert);
};
function getLocalImports(properties) {
const imports = new Set();
function addImportsForNativeName(name) {
switch (name) {
case 'ColorPrimitive':
imports.add('#include <react/renderer/graphics/Color.h>');
return;
case 'ImageSourcePrimitive':
imports.add('#include <react/renderer/imagemanager/primitives.h>');
return;
case 'ImageRequestPrimitive':
imports.add('#include <react/renderer/imagemanager/ImageRequest.h>');
return;
case 'PointPrimitive':
imports.add('#include <react/renderer/graphics/Point.h>');
return;
case 'EdgeInsetsPrimitive':
imports.add('#include <react/renderer/graphics/RectangleEdges.h>');
return;
case 'DimensionPrimitive':
imports.add('#include <yoga/Yoga.h>');
imports.add('#include <react/renderer/core/graphicsConversions.h>');
return;
default:
name;
throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`);
}
}
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (typeAnnotation.type === 'ArrayTypeAnnotation') {
imports.add('#include <vector>');
if (typeAnnotation.elementType.type === 'StringEnumTypeAnnotation') {
imports.add('#include <cinttypes>');
}
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation'
) {
addImportsForNativeName(typeAnnotation.elementType.name);
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
imports.add('#include <react/renderer/core/propsConversions.h>');
const objectProps = typeAnnotation.elementType.properties;
// $FlowFixMe[incompatible-type] the type is guaranteed to be ObjectTypeAnnotation<PropTypeAnnotation>
const objectImports = getImports(objectProps);
// $FlowFixMe[incompatible-type] the type is guaranteed to be ObjectTypeAnnotation<PropTypeAnnotation>
const localImports = getLocalImports(objectProps);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
localImports.forEach(imports.add, imports);
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
imports.add('#include <react/renderer/core/propsConversions.h>');
const objectImports = getImports(typeAnnotation.properties);
const localImports = getLocalImports(typeAnnotation.properties);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
localImports.forEach(imports.add, imports);
}
});
return imports;
}
module.exports = {
getNativeTypeFromAnnotation,
convertCtorParamToAddressType,
convertGettersReturnTypeToAddressType,
convertCtorInitToSharedPointers,
convertVarTypeToSharedPointer,
convertVarValueToPointer,
getLocalImports,
};

View File

@@ -0,0 +1,315 @@
/**
* 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, PropTypeAnnotation} from '../../CodegenSchema';
import type {
BooleanTypeAnnotation,
DoubleTypeAnnotation,
FloatTypeAnnotation,
Int32TypeAnnotation,
ObjectTypeAnnotation,
ReservedPropTypeAnnotation,
StringTypeAnnotation,
} from '../../CodegenSchema';
const {getEnumName} = require('../Utils');
const {
generateStructName,
getCppTypeForAnnotation,
getEnumMaskName,
getImports,
} = require('./CppHelpers.js');
function getNativeTypeFromAnnotation(
componentName: string,
prop:
| NamedShape<PropTypeAnnotation>
| {
name: string,
typeAnnotation:
| $FlowFixMe
| DoubleTypeAnnotation
| FloatTypeAnnotation
| BooleanTypeAnnotation
| Int32TypeAnnotation
| StringTypeAnnotation
| ObjectTypeAnnotation<PropTypeAnnotation>
| ReservedPropTypeAnnotation
| {
+default: string,
+options: $ReadOnlyArray<string>,
+type: 'StringEnumTypeAnnotation',
}
| {
+elementType: ObjectTypeAnnotation<PropTypeAnnotation>,
+type: 'ArrayTypeAnnotation',
},
},
nameParts: $ReadOnlyArray<string>,
): string {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return getCppTypeForAnnotation(typeAnnotation.type);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return 'SharedColor';
case 'ImageSourcePrimitive':
return 'ImageSource';
case 'ImageRequestPrimitive':
return 'ImageRequest';
case 'PointPrimitive':
return 'Point';
case 'EdgeInsetsPrimitive':
return 'EdgeInsets';
case 'DimensionPrimitive':
return 'YGValue';
default:
(typeAnnotation.name: empty);
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
const arrayType = typeAnnotation.elementType.type;
if (arrayType === 'ArrayTypeAnnotation') {
return `std::vector<${getNativeTypeFromAnnotation(
componentName,
{typeAnnotation: typeAnnotation.elementType, name: ''},
nameParts.concat([prop.name]),
)}>`;
}
if (arrayType === 'ObjectTypeAnnotation') {
const structName = generateStructName(
componentName,
nameParts.concat([prop.name]),
);
return `std::vector<${structName}>`;
}
if (arrayType === 'StringEnumTypeAnnotation') {
const enumName = getEnumName(componentName, prop.name);
return getEnumMaskName(enumName);
}
const itemAnnotation = getNativeTypeFromAnnotation(
componentName,
{
typeAnnotation: typeAnnotation.elementType,
name: componentName,
},
nameParts.concat([prop.name]),
);
return `std::vector<${itemAnnotation}>`;
}
case 'ObjectTypeAnnotation': {
return generateStructName(componentName, nameParts.concat([prop.name]));
}
case 'StringEnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'Int32EnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'MixedTypeAnnotation':
return 'folly::dynamic';
default:
(typeAnnotation: empty);
throw new Error(
`Received invalid typeAnnotation for ${componentName} prop ${prop.name}, received ${typeAnnotation.type}`,
);
}
}
/// This function process some types if we need to customize them
/// For example, the ImageSource and the reserved types could be trasformed into
/// const address instead of using them as plain types.
function convertTypesToConstAddressIfNeeded(
type: string,
convertibleTypes: Set<string>,
): string {
if (convertibleTypes.has(type)) {
return `${type} const &`;
}
return type;
}
function convertValueToSharedPointerWithMove(
type: string,
value: string,
convertibleTypes: Set<string>,
): string {
if (convertibleTypes.has(type)) {
return `std::make_shared<${type}>(std::move(${value}))`;
}
return value;
}
function convertVariableToSharedPointer(
type: string,
convertibleTypes: Set<string>,
): string {
if (convertibleTypes.has(type)) {
return `std::shared_ptr<${type}>`;
}
return type;
}
function convertVariableToPointer(
type: string,
value: string,
convertibleTypes: Set<string>,
): string {
if (convertibleTypes.has(type)) {
return `*${value}`;
}
return value;
}
const convertCtorParamToAddressType = (type: string): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageSource');
return convertTypesToConstAddressIfNeeded(type, typesToConvert);
};
const convertCtorInitToSharedPointers = (
type: string,
value: string,
): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageRequest');
return convertValueToSharedPointerWithMove(type, value, typesToConvert);
};
const convertGettersReturnTypeToAddressType = (type: string): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageRequest');
return convertTypesToConstAddressIfNeeded(type, typesToConvert);
};
const convertVarTypeToSharedPointer = (type: string): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageRequest');
return convertVariableToSharedPointer(type, typesToConvert);
};
const convertVarValueToPointer = (type: string, value: string): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageRequest');
return convertVariableToPointer(type, value, typesToConvert);
};
function getLocalImports(
properties: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
): Set<string> {
const imports: Set<string> = new Set();
function addImportsForNativeName(
name:
| 'ColorPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'ImageRequestPrimitive'
| 'DimensionPrimitive',
) {
switch (name) {
case 'ColorPrimitive':
imports.add('#include <react/renderer/graphics/Color.h>');
return;
case 'ImageSourcePrimitive':
imports.add('#include <react/renderer/imagemanager/primitives.h>');
return;
case 'ImageRequestPrimitive':
imports.add('#include <react/renderer/imagemanager/ImageRequest.h>');
return;
case 'PointPrimitive':
imports.add('#include <react/renderer/graphics/Point.h>');
return;
case 'EdgeInsetsPrimitive':
imports.add('#include <react/renderer/graphics/RectangleEdges.h>');
return;
case 'DimensionPrimitive':
imports.add('#include <yoga/Yoga.h>');
imports.add('#include <react/renderer/core/graphicsConversions.h>');
return;
default:
(name: empty);
throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`);
}
}
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (typeAnnotation.type === 'ArrayTypeAnnotation') {
imports.add('#include <vector>');
if (typeAnnotation.elementType.type === 'StringEnumTypeAnnotation') {
imports.add('#include <cinttypes>');
}
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation'
) {
addImportsForNativeName(typeAnnotation.elementType.name);
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
imports.add('#include <react/renderer/core/propsConversions.h>');
const objectProps = typeAnnotation.elementType.properties;
// $FlowFixMe[incompatible-type] the type is guaranteed to be ObjectTypeAnnotation<PropTypeAnnotation>
const objectImports = getImports(objectProps);
// $FlowFixMe[incompatible-type] the type is guaranteed to be ObjectTypeAnnotation<PropTypeAnnotation>
const localImports = getLocalImports(objectProps);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
localImports.forEach(imports.add, imports);
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
imports.add('#include <react/renderer/core/propsConversions.h>');
const objectImports = getImports(typeAnnotation.properties);
const localImports = getLocalImports(typeAnnotation.properties);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
localImports.forEach(imports.add, imports);
}
});
return imports;
}
module.exports = {
getNativeTypeFromAnnotation,
convertCtorParamToAddressType,
convertGettersReturnTypeToAddressType,
convertCtorInitToSharedPointers,
convertVarTypeToSharedPointer,
convertVarValueToPointer,
getLocalImports,
};

View File

@@ -0,0 +1,56 @@
/**
* 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.
*
*
* @format
*/
const APPLE_PLATFORMS_MACRO_MAP = {
ios: 'TARGET_OS_IOS',
macos: 'TARGET_OS_OSX',
tvos: 'TARGET_OS_TV',
visionos: 'TARGET_OS_VISION',
};
/**
* Adds compiler macros to the file template to exclude unsupported platforms.
*/
function generateSupportedApplePlatformsMacro(
fileTemplate,
supportedPlatformsMap,
) {
if (!supportedPlatformsMap) {
return fileTemplate;
}
// According to Podspec Syntax Reference, when `platform` or `deployment_target` is not specified, it defaults to all platforms.
// https://guides.cocoapods.org/syntax/podspec.html#platform
const everyPlatformIsUnsupported = Object.keys(supportedPlatformsMap).every(
platform => supportedPlatformsMap[platform] === false,
);
if (everyPlatformIsUnsupported) {
return fileTemplate;
}
const compilerMacroString = Object.keys(supportedPlatformsMap)
.reduce((acc, platform) => {
if (!supportedPlatformsMap[platform]) {
// $FlowFixMe[invalid-computed-prop]
return [...acc, `!${APPLE_PLATFORMS_MACRO_MAP[platform]}`];
}
return acc;
}, [])
.join(' && ');
if (!compilerMacroString) {
return fileTemplate;
}
return `#if ${compilerMacroString}
${fileTemplate}
#endif
`;
}
module.exports = {
generateSupportedApplePlatformsMacro,
};

View File

@@ -0,0 +1,61 @@
/**
* 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
*/
const APPLE_PLATFORMS_MACRO_MAP = {
ios: 'TARGET_OS_IOS',
macos: 'TARGET_OS_OSX',
tvos: 'TARGET_OS_TV',
visionos: 'TARGET_OS_VISION',
};
/**
* Adds compiler macros to the file template to exclude unsupported platforms.
*/
function generateSupportedApplePlatformsMacro(
fileTemplate: string,
supportedPlatformsMap: ?{[string]: boolean},
): string {
if (!supportedPlatformsMap) {
return fileTemplate;
}
// According to Podspec Syntax Reference, when `platform` or `deployment_target` is not specified, it defaults to all platforms.
// https://guides.cocoapods.org/syntax/podspec.html#platform
const everyPlatformIsUnsupported = Object.keys(supportedPlatformsMap).every(
platform => supportedPlatformsMap[platform] === false,
);
if (everyPlatformIsUnsupported) {
return fileTemplate;
}
const compilerMacroString = Object.keys(supportedPlatformsMap)
.reduce((acc: string[], platform) => {
if (!supportedPlatformsMap[platform]) {
// $FlowFixMe[invalid-computed-prop]
return [...acc, `!${APPLE_PLATFORMS_MACRO_MAP[platform]}`];
}
return acc;
}, [])
.join(' && ');
if (!compilerMacroString) {
return fileTemplate;
}
return `#if ${compilerMacroString}
${fileTemplate}
#endif
`;
}
module.exports = {
generateSupportedApplePlatformsMacro,
};

View File

@@ -0,0 +1,247 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {getEnumName, toSafeCppString} = require('../Utils');
function toIntEnumValueName(propName, value) {
return `${toSafeCppString(propName)}${value}`;
}
function getCppTypeForAnnotation(type) {
switch (type) {
case 'BooleanTypeAnnotation':
return 'bool';
case 'StringTypeAnnotation':
return 'std::string';
case 'Int32TypeAnnotation':
return 'int';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'Float';
case 'MixedTypeAnnotation':
return 'folly::dynamic';
default:
type;
throw new Error(`Received invalid typeAnnotation ${type}`);
}
}
function getCppArrayTypeForAnnotation(typeElement, structParts) {
switch (typeElement.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'MixedTypeAnnotation':
return `std::vector<${getCppTypeForAnnotation(typeElement.type)}>`;
case 'StringLiteralUnionTypeAnnotation':
case 'ObjectTypeAnnotation':
if (!structParts) {
throw new Error(
`Trying to generate the event emitter for an Array of ${typeElement.type} without informations to generate the generic type`,
);
}
return `std::vector<${generateEventStructName(structParts)}>`;
case 'ArrayTypeAnnotation':
return `std::vector<${getCppArrayTypeForAnnotation(typeElement.elementType, structParts)}>`;
default:
throw new Error(
`Can't determine array type with typeElement: ${JSON.stringify(typeElement, null, 2)}`,
);
}
}
function getImports(properties) {
const imports = new Set();
function addImportsForNativeName(name) {
switch (name) {
case 'ColorPrimitive':
return;
case 'PointPrimitive':
return;
case 'EdgeInsetsPrimitive':
return;
case 'ImageRequestPrimitive':
return;
case 'ImageSourcePrimitive':
imports.add('#include <react/renderer/components/image/conversions.h>');
return;
case 'DimensionPrimitive':
imports.add('#include <react/renderer/components/view/conversions.h>');
return;
default:
name;
throw new Error(`Invalid name, got ${name}`);
}
}
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation'
) {
addImportsForNativeName(typeAnnotation.elementType.name);
}
if (typeAnnotation.type === 'MixedTypeAnnotation') {
imports.add('#include <folly/dynamic.h>');
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
const objectImports = getImports(typeAnnotation.properties);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
}
});
return imports;
}
function generateEventStructName(parts = []) {
return parts.map(toSafeCppString).join('');
}
function generateStructName(componentName, parts = []) {
const additional = parts.map(toSafeCppString).join('');
return `${componentName}${additional}Struct`;
}
function getEnumMaskName(enumName) {
return `${enumName}Mask`;
}
function getDefaultInitializerString(componentName, prop) {
const defaultValue = convertDefaultTypeToString(componentName, prop);
return `{${defaultValue}}`;
}
function convertDefaultTypeToString(componentName, prop, fromBuilder = false) {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
if (typeAnnotation.default == null) {
return '';
}
return String(typeAnnotation.default);
case 'StringTypeAnnotation':
if (typeAnnotation.default == null) {
return '';
}
return `std::string{"${typeAnnotation.default}"}`;
case 'Int32TypeAnnotation':
return String(typeAnnotation.default);
case 'DoubleTypeAnnotation':
const defaultDoubleVal = typeAnnotation.default;
return parseInt(defaultDoubleVal, 10) === defaultDoubleVal
? typeAnnotation.default.toFixed(1)
: String(typeAnnotation.default);
case 'FloatTypeAnnotation':
const defaultFloatVal = typeAnnotation.default;
if (defaultFloatVal == null) {
return '';
}
return parseInt(defaultFloatVal, 10) === defaultFloatVal
? defaultFloatVal.toFixed(1)
: String(typeAnnotation.default);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return '';
case 'ImageSourcePrimitive':
return '';
case 'ImageRequestPrimitive':
return '';
case 'PointPrimitive':
return '';
case 'EdgeInsetsPrimitive':
return '';
case 'DimensionPrimitive':
return '';
default:
typeAnnotation.name;
throw new Error(
`Unsupported type annotation: ${typeAnnotation.name}`,
);
}
case 'ArrayTypeAnnotation': {
const elementType = typeAnnotation.elementType;
switch (elementType.type) {
case 'StringEnumTypeAnnotation':
if (elementType.default == null) {
throw new Error(
'A default is required for array StringEnumTypeAnnotation',
);
}
const enumName = getEnumName(componentName, prop.name);
const enumMaskName = getEnumMaskName(enumName);
const defaultValue = `${enumName}::${toSafeCppString(elementType.default)}`;
if (fromBuilder) {
return `${enumMaskName}Wrapped{ .value = static_cast<${enumMaskName}>(${defaultValue})}`;
}
return `static_cast<${enumMaskName}>(${defaultValue})`;
default:
return '';
}
}
case 'ObjectTypeAnnotation': {
return '';
}
case 'StringEnumTypeAnnotation':
return `${getEnumName(componentName, prop.name)}::${toSafeCppString(typeAnnotation.default)}`;
case 'Int32EnumTypeAnnotation':
return `${getEnumName(componentName, prop.name)}::${toIntEnumValueName(prop.name, typeAnnotation.default)}`;
case 'MixedTypeAnnotation':
return '';
default:
typeAnnotation;
throw new Error(`Unsupported type annotation: ${typeAnnotation.type}`);
}
}
function getSourceProp(componentName, prop) {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'ArrayTypeAnnotation':
const elementType = typeAnnotation.elementType;
switch (elementType.type) {
case 'StringEnumTypeAnnotation':
const enumName = getEnumName(componentName, prop.name);
const enumMaskName = getEnumMaskName(enumName);
return `${enumMaskName}Wrapped{ .value = sourceProps.${prop.name} }`;
}
}
return `sourceProps.${prop.name}`;
}
function isWrappedPropType(prop) {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'ArrayTypeAnnotation':
const elementType = typeAnnotation.elementType;
switch (elementType.type) {
case 'StringEnumTypeAnnotation':
return true;
}
}
return false;
}
const IncludeTemplate = ({headerPrefix, file}) => {
if (headerPrefix === '') {
return `#include "${file}"`;
}
return `#include <${headerPrefix}${file}>`;
};
module.exports = {
getDefaultInitializerString,
convertDefaultTypeToString,
getCppArrayTypeForAnnotation,
getCppTypeForAnnotation,
getEnumMaskName,
getImports,
toIntEnumValueName,
generateStructName,
generateEventStructName,
IncludeTemplate,
getSourceProp,
isWrappedPropType,
};

View File

@@ -0,0 +1,328 @@
/**
* 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 {
EventTypeAnnotation,
NamedShape,
PropTypeAnnotation,
} from '../../CodegenSchema';
const {getEnumName, toSafeCppString} = require('../Utils');
function toIntEnumValueName(propName: string, value: number): string {
return `${toSafeCppString(propName)}${value}`;
}
function getCppTypeForAnnotation(
type:
| 'BooleanTypeAnnotation'
| 'StringTypeAnnotation'
| 'Int32TypeAnnotation'
| 'DoubleTypeAnnotation'
| 'FloatTypeAnnotation'
| 'MixedTypeAnnotation',
): string {
switch (type) {
case 'BooleanTypeAnnotation':
return 'bool';
case 'StringTypeAnnotation':
return 'std::string';
case 'Int32TypeAnnotation':
return 'int';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'Float';
case 'MixedTypeAnnotation':
return 'folly::dynamic';
default:
(type: empty);
throw new Error(`Received invalid typeAnnotation ${type}`);
}
}
function getCppArrayTypeForAnnotation(
typeElement: EventTypeAnnotation,
structParts?: string[],
): string {
switch (typeElement.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'MixedTypeAnnotation':
return `std::vector<${getCppTypeForAnnotation(typeElement.type)}>`;
case 'StringLiteralUnionTypeAnnotation':
case 'ObjectTypeAnnotation':
if (!structParts) {
throw new Error(
`Trying to generate the event emitter for an Array of ${typeElement.type} without informations to generate the generic type`,
);
}
return `std::vector<${generateEventStructName(structParts)}>`;
case 'ArrayTypeAnnotation':
return `std::vector<${getCppArrayTypeForAnnotation(
typeElement.elementType,
structParts,
)}>`;
default:
throw new Error(
`Can't determine array type with typeElement: ${JSON.stringify(
typeElement,
null,
2,
)}`,
);
}
}
function getImports(
properties:
| $ReadOnlyArray<NamedShape<PropTypeAnnotation>>
| $ReadOnlyArray<NamedShape<EventTypeAnnotation>>,
): Set<string> {
const imports: Set<string> = new Set();
function addImportsForNativeName(
name:
| 'ColorPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageRequestPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'DimensionPrimitive',
) {
switch (name) {
case 'ColorPrimitive':
return;
case 'PointPrimitive':
return;
case 'EdgeInsetsPrimitive':
return;
case 'ImageRequestPrimitive':
return;
case 'ImageSourcePrimitive':
imports.add('#include <react/renderer/components/image/conversions.h>');
return;
case 'DimensionPrimitive':
imports.add('#include <react/renderer/components/view/conversions.h>');
return;
default:
(name: empty);
throw new Error(`Invalid name, got ${name}`);
}
}
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation'
) {
addImportsForNativeName(typeAnnotation.elementType.name);
}
if (typeAnnotation.type === 'MixedTypeAnnotation') {
imports.add('#include <folly/dynamic.h>');
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
const objectImports = getImports(typeAnnotation.properties);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
}
});
return imports;
}
function generateEventStructName(parts: $ReadOnlyArray<string> = []): string {
return parts.map(toSafeCppString).join('');
}
function generateStructName(
componentName: string,
parts: $ReadOnlyArray<string> = [],
): string {
const additional = parts.map(toSafeCppString).join('');
return `${componentName}${additional}Struct`;
}
function getEnumMaskName(enumName: string): string {
return `${enumName}Mask`;
}
function getDefaultInitializerString(
componentName: string,
prop: NamedShape<PropTypeAnnotation>,
): string {
const defaultValue = convertDefaultTypeToString(componentName, prop);
return `{${defaultValue}}`;
}
function convertDefaultTypeToString(
componentName: string,
prop: NamedShape<PropTypeAnnotation>,
fromBuilder: boolean = false,
): string {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
if (typeAnnotation.default == null) {
return '';
}
return String(typeAnnotation.default);
case 'StringTypeAnnotation':
if (typeAnnotation.default == null) {
return '';
}
return `std::string{"${typeAnnotation.default}"}`;
case 'Int32TypeAnnotation':
return String(typeAnnotation.default);
case 'DoubleTypeAnnotation':
const defaultDoubleVal = typeAnnotation.default;
return parseInt(defaultDoubleVal, 10) === defaultDoubleVal
? typeAnnotation.default.toFixed(1)
: String(typeAnnotation.default);
case 'FloatTypeAnnotation':
const defaultFloatVal = typeAnnotation.default;
if (defaultFloatVal == null) {
return '';
}
return parseInt(defaultFloatVal, 10) === defaultFloatVal
? defaultFloatVal.toFixed(1)
: String(typeAnnotation.default);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return '';
case 'ImageSourcePrimitive':
return '';
case 'ImageRequestPrimitive':
return '';
case 'PointPrimitive':
return '';
case 'EdgeInsetsPrimitive':
return '';
case 'DimensionPrimitive':
return '';
default:
(typeAnnotation.name: empty);
throw new Error(
`Unsupported type annotation: ${typeAnnotation.name}`,
);
}
case 'ArrayTypeAnnotation': {
const elementType = typeAnnotation.elementType;
switch (elementType.type) {
case 'StringEnumTypeAnnotation':
if (elementType.default == null) {
throw new Error(
'A default is required for array StringEnumTypeAnnotation',
);
}
const enumName = getEnumName(componentName, prop.name);
const enumMaskName = getEnumMaskName(enumName);
const defaultValue = `${enumName}::${toSafeCppString(
elementType.default,
)}`;
if (fromBuilder) {
return `${enumMaskName}Wrapped{ .value = static_cast<${enumMaskName}>(${defaultValue})}`;
}
return `static_cast<${enumMaskName}>(${defaultValue})`;
default:
return '';
}
}
case 'ObjectTypeAnnotation': {
return '';
}
case 'StringEnumTypeAnnotation':
return `${getEnumName(componentName, prop.name)}::${toSafeCppString(
typeAnnotation.default,
)}`;
case 'Int32EnumTypeAnnotation':
return `${getEnumName(componentName, prop.name)}::${toIntEnumValueName(
prop.name,
typeAnnotation.default,
)}`;
case 'MixedTypeAnnotation':
return '';
default:
(typeAnnotation: empty);
throw new Error(`Unsupported type annotation: ${typeAnnotation.type}`);
}
}
function getSourceProp(
componentName: string,
prop: NamedShape<PropTypeAnnotation>,
): string {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'ArrayTypeAnnotation':
const elementType = typeAnnotation.elementType;
switch (elementType.type) {
case 'StringEnumTypeAnnotation':
const enumName = getEnumName(componentName, prop.name);
const enumMaskName = getEnumMaskName(enumName);
return `${enumMaskName}Wrapped{ .value = sourceProps.${prop.name} }`;
}
}
return `sourceProps.${prop.name}`;
}
function isWrappedPropType(prop: NamedShape<PropTypeAnnotation>): boolean {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'ArrayTypeAnnotation':
const elementType = typeAnnotation.elementType;
switch (elementType.type) {
case 'StringEnumTypeAnnotation':
return true;
}
}
return false;
}
const IncludeTemplate = ({
headerPrefix,
file,
}: {
headerPrefix: string,
file: string,
}): string => {
if (headerPrefix === '') {
return `#include "${file}"`;
}
return `#include <${headerPrefix}${file}>`;
};
module.exports = {
getDefaultInitializerString,
convertDefaultTypeToString,
getCppArrayTypeForAnnotation,
getCppTypeForAnnotation,
getEnumMaskName,
getImports,
toIntEnumValueName,
generateStructName,
generateEventStructName,
IncludeTemplate,
getSourceProp,
isWrappedPropType,
};

View File

@@ -0,0 +1,89 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
const FileTemplate = ({libraryName, componentRegistrations, headerPrefix}) => `
/**
* 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: GenerateComponentDescriptorCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'ComponentDescriptors.h',
})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
void ${libraryName}_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
${componentRegistrations}
}
} // namespace facebook::react
`;
const ComponentRegistrationTemplate = ({className}) =>
`
registry->add(concreteComponentDescriptorProvider<${className}ComponentDescriptor>());
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const fileName = 'ComponentDescriptors.cpp';
const componentRegistrations = 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)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
return ComponentRegistrationTemplate({
className: componentName,
});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
libraryName,
componentRegistrations,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,102 @@
/**
* 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 {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
libraryName,
componentRegistrations,
headerPrefix,
}: {
libraryName: string,
componentRegistrations: string,
headerPrefix: 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: GenerateComponentDescriptorCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'ComponentDescriptors.h'})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
void ${libraryName}_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
${componentRegistrations}
}
} // namespace facebook::react
`;
const ComponentRegistrationTemplate = ({className}: {className: string}) =>
`
registry->add(concreteComponentDescriptorProvider<${className}ComponentDescriptor>());
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const fileName = 'ComponentDescriptors.cpp';
const componentRegistrations = 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)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
return ComponentRegistrationTemplate({className: componentName});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
libraryName,
componentRegistrations,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,91 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
const FileTemplate = ({libraryName, componentDefinitions, headerPrefix}) => `
/**
* 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: GenerateComponentDescriptorH.js
*/
#pragma once
${IncludeTemplate({
headerPrefix,
file: 'ShadowNodes.h',
})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
${componentDefinitions}
void ${libraryName}_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);
} // namespace facebook::react
`;
const ComponentDefinitionTemplate = ({className}) =>
`
using ${className}ComponentDescriptor = ConcreteComponentDescriptor<${className}ShadowNode>;
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const fileName = 'ComponentDescriptors.h';
const componentDefinitions = 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)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
return ComponentDefinitionTemplate({
className: componentName,
});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
libraryName,
componentDefinitions,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,104 @@
/**
* 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 {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
libraryName,
componentDefinitions,
headerPrefix,
}: {
libraryName: string,
componentDefinitions: string,
headerPrefix: 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: GenerateComponentDescriptorH.js
*/
#pragma once
${IncludeTemplate({headerPrefix, file: 'ShadowNodes.h'})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
${componentDefinitions}
void ${libraryName}_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);
} // namespace facebook::react
`;
const ComponentDefinitionTemplate = ({className}: {className: string}) =>
`
using ${className}ComponentDescriptor = ConcreteComponentDescriptor<${className}ShadowNode>;
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const fileName = 'ComponentDescriptors.h';
const componentDefinitions = 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)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
return ComponentDefinitionTemplate({className: componentName});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
libraryName,
componentDefinitions,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,338 @@
/**
* 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.
*
*
* @format
*/
'use strict';
function getOrdinalNumber(num) {
switch (num) {
case 1:
return '1st';
case 2:
return '2nd';
case 3:
return '3rd';
}
if (num <= 20) {
return `${num}th`;
}
return 'unknown';
}
const ProtocolTemplate = ({componentName, methods}) =>
`
@protocol RCT${componentName}ViewProtocol <NSObject>
${methods}
@end
`.trim();
const CommandHandlerIfCaseConvertArgTemplate = ({
componentName,
expectedKind,
argNumber,
argNumberString,
expectedKindString,
argConversion,
}) =>
`
NSObject *arg${argNumber} = args[${argNumber}];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg${argNumber}, ${expectedKind}, @"${expectedKindString}", @"${componentName}", commandName, @"${argNumberString}")) {
return;
}
#endif
${argConversion}
`.trim();
const CommandHandlerIfCaseTemplate = ({
componentName,
commandName,
numArgs,
convertArgs,
commandCall,
}) =>
`
if ([commandName isEqualToString:@"${commandName}"]) {
#if RCT_DEBUG
if ([args count] != ${numArgs}) {
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"${componentName}", commandName, (int)[args count], ${numArgs});
return;
}
#endif
${convertArgs}
${commandCall}
return;
}
`.trim();
const CommandHandlerTemplate = ({componentName, ifCases}) =>
`
RCT_EXTERN inline void RCT${componentName}HandleCommand(
id<RCT${componentName}ViewProtocol> componentView,
NSString const *commandName,
NSArray const *args)
{
${ifCases}
#if RCT_DEBUG
RCTLogError(@"%@ received command %@, which is not a supported command.", @"${componentName}", commandName);
#endif
}
`.trim();
const FileTemplate = ({componentContent}) =>
`
/**
* 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: GenerateComponentHObjCpp.js
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
${componentContent}
NS_ASSUME_NONNULL_END
`.trim();
function getObjCParamType(param) {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
typeAnnotation.name;
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 *';
case 'ArrayTypeAnnotation':
return 'const NSArray *';
default:
typeAnnotation.type;
throw new Error('Received invalid param type annotation');
}
}
function getObjCExpectedKindParamType(param) {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return '[NSNumber class]';
default:
typeAnnotation.name;
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]';
case 'ArrayTypeAnnotation':
return '[NSArray class]';
default:
typeAnnotation.type;
throw new Error('Received invalid param type annotation');
}
}
function getReadableExpectedKindParamType(param) {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
typeAnnotation.name;
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';
case 'ArrayTypeAnnotation':
return 'array';
default:
typeAnnotation.type;
throw new Error('Received invalid param type annotation');
}
}
function getObjCRightHandAssignmentParamType(param, index) {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return `[(NSNumber *)arg${index} doubleValue]`;
default:
typeAnnotation.name;
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}`;
case 'ArrayTypeAnnotation':
return `(NSArray *)arg${index}`;
default:
typeAnnotation.type;
throw new Error('Received invalid param type annotation');
}
}
function generateProtocol(component, componentName) {
const methods = 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({
componentName,
methods,
});
}
function generateConvertAndValidateParam(param, index, componentName) {
const leftSideType = getObjCParamType(param);
const expectedKind = getObjCExpectedKindParamType(param);
const expectedKindString = getReadableExpectedKindParamType(param);
const argConversion = `${leftSideType} ${param.name} = ${getObjCRightHandAssignmentParamType(param, index)};`;
return CommandHandlerIfCaseConvertArgTemplate({
componentName,
argConversion,
argNumber: index,
argNumberString: getOrdinalNumber(index + 1),
expectedKind,
expectedKindString,
});
}
function generateCommandIfCase(command, componentName) {
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({
componentName,
commandName: command.name,
numArgs: params.length,
convertArgs,
commandCall,
});
}
function generateCommandHandler(component, componentName) {
if (component.commands.length === 0) {
return null;
}
const ifCases = component.commands
.map(command => generateCommandIfCase(command, componentName))
.join('\n\n');
return CommandHandlerTemplate({
componentName,
ifCases,
});
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
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 = FileTemplate({
componentContent,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,428 @@
/**
* 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,
CommandTypeAnnotation,
ComponentShape,
NamedShape,
SchemaType,
} 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 = ({
componentName,
methods,
}: {
componentName: string,
methods: string,
}) =>
`
@protocol RCT${componentName}ViewProtocol <NSObject>
${methods}
@end
`.trim();
const CommandHandlerIfCaseConvertArgTemplate = ({
componentName,
expectedKind,
argNumber,
argNumberString,
expectedKindString,
argConversion,
}: {
componentName: string,
expectedKind: string,
argNumber: number,
argNumberString: string,
expectedKindString: string,
argConversion: string,
}) =>
`
NSObject *arg${argNumber} = args[${argNumber}];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg${argNumber}, ${expectedKind}, @"${expectedKindString}", @"${componentName}", commandName, @"${argNumberString}")) {
return;
}
#endif
${argConversion}
`.trim();
const CommandHandlerIfCaseTemplate = ({
componentName,
commandName,
numArgs,
convertArgs,
commandCall,
}: {
componentName: string,
commandName: string,
numArgs: number,
convertArgs: string,
commandCall: string,
}) =>
`
if ([commandName isEqualToString:@"${commandName}"]) {
#if RCT_DEBUG
if ([args count] != ${numArgs}) {
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"${componentName}", commandName, (int)[args count], ${numArgs});
return;
}
#endif
${convertArgs}
${commandCall}
return;
}
`.trim();
const CommandHandlerTemplate = ({
componentName,
ifCases,
}: {
componentName: string,
ifCases: string,
}) =>
`
RCT_EXTERN inline void RCT${componentName}HandleCommand(
id<RCT${componentName}ViewProtocol> componentView,
NSString const *commandName,
NSArray const *args)
{
${ifCases}
#if RCT_DEBUG
RCTLogError(@"%@ received command %@, which is not a supported command.", @"${componentName}", commandName);
#endif
}
`.trim();
const FileTemplate = ({componentContent}: {componentContent: 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: GenerateComponentHObjCpp.js
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
${componentContent}
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 *';
case 'ArrayTypeAnnotation':
return 'const NSArray *';
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]';
case 'ArrayTypeAnnotation':
return '[NSArray 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';
case 'ArrayTypeAnnotation':
return 'array';
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}`;
case 'ArrayTypeAnnotation':
return `(NSArray *)arg${index}`;
default:
(typeAnnotation.type: empty);
throw new Error('Received invalid param type annotation');
}
}
function generateProtocol(
component: ComponentShape,
componentName: string,
): string {
const methods = 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({
componentName,
methods,
});
}
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({
componentName,
argConversion,
argNumber: index,
argNumberString: getOrdinalNumber(index + 1),
expectedKind,
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({
componentName,
commandName: command.name,
numArgs: params.length,
convertArgs,
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({
componentName,
ifCases,
});
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): 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 = FileTemplate({
componentContent,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,358 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {indent} = require('../Utils');
const {IncludeTemplate, generateEventStructName} = require('./CppHelpers');
// File path -> contents
const FileTemplate = ({events, extraIncludes, headerPrefix}) => `
/**
* 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: GenerateEventEmitterCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'EventEmitters.h',
})}
${[...extraIncludes].join('\n')}
namespace facebook::react {
${events}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
eventName,
structName,
dispatchEventName,
implementation,
}) => {
const capture = implementation.includes('event')
? 'event=std::move(event)'
: '';
return `
void ${className}EventEmitter::${eventName}(${structName} event) const {
dispatchEvent("${dispatchEventName}", [${capture}](jsi::Runtime &runtime) {
${implementation}
});
}
`;
};
const BasicComponentTemplate = ({className, eventName, dispatchEventName}) =>
`
void ${className}EventEmitter::${eventName}() const {
dispatchEvent("${dispatchEventName}");
}
`.trim();
function generateSetter(
variableName,
propertyName,
propertyParts,
usingEvent,
valueMapper = value => value,
) {
const eventChain = usingEvent
? `event.${[...propertyParts, propertyName].join('.')}`
: [...propertyParts, propertyName].join('.');
return `${variableName}.setProperty(runtime, "${propertyName}", ${valueMapper(eventChain)});`;
}
function generateObjectSetter(
variableName,
propertyName,
propertyParts,
typeAnnotation,
extraIncludes,
usingEvent,
) {
return `
{
auto ${propertyName} = jsi::Object(runtime);
${indent(generateSetters(propertyName, typeAnnotation.properties, propertyParts.concat([propertyName]), extraIncludes, usingEvent), 2)}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
}
`.trim();
}
function setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
mappingFunction = value => value,
) {
return `${propertyName}.setValueAtIndex(runtime, ${indexVariable}++, ${mappingFunction(loopLocalVariable)});`;
}
function generateArraySetter(
variableName,
propertyName,
propertyParts,
elementType,
extraIncludes,
usingEvent,
) {
const eventChain = usingEvent
? `event.${[...propertyParts, propertyName].join('.')}`
: [...propertyParts, propertyName].join('.');
const indexVar = `${propertyName}Index`;
const innerLoopVar = `${propertyName}Value`;
return `
auto ${propertyName} = jsi::Array(runtime, ${eventChain}.size());
size_t ${indexVar} = 0;
for (auto ${innerLoopVar} : ${eventChain}) {
${handleArrayElementType(elementType, propertyName, indexVar, innerLoopVar, propertyParts, extraIncludes, usingEvent)}
}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
`;
}
function handleArrayElementType(
elementType,
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
extraIncludes,
usingEvent,
) {
switch (elementType.type) {
case 'BooleanTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `(bool)${val}`,
);
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return setValueAtIndex(propertyName, indexVariable, loopLocalVariable);
case 'MixedTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `jsi::valueFromDynamic(runtime, ${val})`,
);
case 'StringLiteralUnionTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `toString(${val})`,
);
case 'ObjectTypeAnnotation':
return convertObjectTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
elementType,
extraIncludes,
);
case 'ArrayTypeAnnotation':
return convertArrayTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
elementType,
extraIncludes,
usingEvent,
);
default:
throw new Error(
`Received invalid elementType for array ${elementType.type}`,
);
}
}
function convertObjectTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
objectTypeAnnotation,
extraIncludes,
) {
return `auto ${propertyName}Object = jsi::Object(runtime);
${generateSetters(`${propertyName}Object`, objectTypeAnnotation.properties, [].concat([loopLocalVariable]), extraIncludes, false)}
${setValueAtIndex(propertyName, indexVariable, `${propertyName}Object`)}`;
}
function convertArrayTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
eventTypeAnnotation,
extraIncludes,
usingEvent,
) {
if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') {
throw new Error(
`Inconsistent eventTypeAnnotation received. Expected type = 'ArrayTypeAnnotation'; received = ${eventTypeAnnotation.type}`,
);
}
return `auto ${propertyName}Array = jsi::Array(runtime, ${loopLocalVariable}.size());
size_t ${indexVariable}Internal = 0;
for (auto ${loopLocalVariable}Internal : ${loopLocalVariable}) {
${handleArrayElementType(eventTypeAnnotation.elementType, `${propertyName}Array`, `${indexVariable}Internal`, `${loopLocalVariable}Internal`, propertyParts, extraIncludes, usingEvent)}
}
${setValueAtIndex(propertyName, indexVariable, `${propertyName}Array`)}`;
}
function generateSetters(
parentPropertyName,
properties,
propertyParts,
extraIncludes,
usingEvent = true,
) {
const propSetters = properties
.map(eventProperty => {
const {typeAnnotation} = eventProperty;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
);
case 'MixedTypeAnnotation':
extraIncludes.add('#include <jsi/JSIDynamic.h>');
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `jsi::valueFromDynamic(runtime, ${prop})`,
);
case 'StringLiteralUnionTypeAnnotation':
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `toString(${prop})`,
);
case 'ObjectTypeAnnotation':
return generateObjectSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
typeAnnotation,
extraIncludes,
usingEvent,
);
case 'ArrayTypeAnnotation':
return generateArraySetter(
parentPropertyName,
eventProperty.name,
propertyParts,
typeAnnotation.elementType,
extraIncludes,
usingEvent,
);
default:
typeAnnotation.type;
throw new Error(
`Received invalid event property type ${typeAnnotation.type}`,
);
}
})
.join('\n');
return propSetters;
}
function generateEvent(componentName, event, extraIncludes) {
// This is a gross hack necessary because native code is sending
// events named things like topChange to JS which is then converted back to
// call the onChange prop. We should be consistent throughout the system.
// In order to migrate to this new system we have to support the current
// naming scheme. We should delete this once we are able to control this name
// throughout the system.
const dispatchEventName =
event.paperTopLevelNameDeprecated != null
? event.paperTopLevelNameDeprecated
: `${event.name[2].toLowerCase()}${event.name.slice(3)}`;
if (event.typeAnnotation.argument) {
const implementation = `
auto payload = jsi::Object(runtime);
${generateSetters('payload', event.typeAnnotation.argument.properties, [], extraIncludes)}
return payload;
`.trim();
if (!event.name.startsWith('on')) {
throw new Error('Expected the event name to start with `on`');
}
return ComponentTemplate({
className: componentName,
eventName: event.name,
dispatchEventName,
structName: generateEventStructName([event.name]),
implementation,
});
}
return BasicComponentTemplate({
className: componentName,
eventName: event.name,
dispatchEventName,
});
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const moduleComponents = 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 components;
})
.filter(Boolean)
// $FlowFixMe[unsafe-object-assign]
.reduce((acc, components) => Object.assign(acc, components), {});
const extraIncludes = new Set();
const componentEmitters = Object.keys(moduleComponents)
.map(componentName => {
const component = moduleComponents[componentName];
return component.events
.map(event => generateEvent(componentName, event, extraIncludes))
.join('\n');
})
.join('\n');
const fileName = 'EventEmitters.cpp';
const replacedTemplate = FileTemplate({
events: componentEmitters,
extraIncludes,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,456 @@
/**
* 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 {EventTypeShape} from '../../CodegenSchema';
import type {
ComponentShape,
EventTypeAnnotation,
NamedShape,
ObjectTypeAnnotation,
SchemaType,
} from '../../CodegenSchema';
const {indent} = require('../Utils');
const {IncludeTemplate, generateEventStructName} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
type ComponentCollection = $ReadOnly<{
[component: string]: ComponentShape,
...
}>;
const FileTemplate = ({
events,
extraIncludes,
headerPrefix,
}: {
events: string,
extraIncludes: Set<string>,
headerPrefix: 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: GenerateEventEmitterCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'EventEmitters.h'})}
${[...extraIncludes].join('\n')}
namespace facebook::react {
${events}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
eventName,
structName,
dispatchEventName,
implementation,
}: {
className: string,
eventName: string,
structName: string,
dispatchEventName: string,
implementation: string,
}) => {
const capture = implementation.includes('event')
? 'event=std::move(event)'
: '';
return `
void ${className}EventEmitter::${eventName}(${structName} event) const {
dispatchEvent("${dispatchEventName}", [${capture}](jsi::Runtime &runtime) {
${implementation}
});
}
`;
};
const BasicComponentTemplate = ({
className,
eventName,
dispatchEventName,
}: {
className: string,
eventName: string,
dispatchEventName: string,
}) =>
`
void ${className}EventEmitter::${eventName}() const {
dispatchEvent("${dispatchEventName}");
}
`.trim();
function generateSetter(
variableName: string,
propertyName: string,
propertyParts: $ReadOnlyArray<string>,
usingEvent: boolean,
valueMapper: string => string = value => value,
) {
const eventChain = usingEvent
? `event.${[...propertyParts, propertyName].join('.')}`
: [...propertyParts, propertyName].join('.');
return `${variableName}.setProperty(runtime, "${propertyName}", ${valueMapper(
eventChain,
)});`;
}
function generateObjectSetter(
variableName: string,
propertyName: string,
propertyParts: $ReadOnlyArray<string>,
typeAnnotation: ObjectTypeAnnotation<EventTypeAnnotation>,
extraIncludes: Set<string>,
usingEvent: boolean,
) {
return `
{
auto ${propertyName} = jsi::Object(runtime);
${indent(
generateSetters(
propertyName,
typeAnnotation.properties,
propertyParts.concat([propertyName]),
extraIncludes,
usingEvent,
),
2,
)}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
}
`.trim();
}
function setValueAtIndex(
propertyName: string,
indexVariable: string,
loopLocalVariable: string,
mappingFunction: string => string = value => value,
) {
return `${propertyName}.setValueAtIndex(runtime, ${indexVariable}++, ${mappingFunction(
loopLocalVariable,
)});`;
}
function generateArraySetter(
variableName: string,
propertyName: string,
propertyParts: $ReadOnlyArray<string>,
elementType: EventTypeAnnotation,
extraIncludes: Set<string>,
usingEvent: boolean,
): string {
const eventChain = usingEvent
? `event.${[...propertyParts, propertyName].join('.')}`
: [...propertyParts, propertyName].join('.');
const indexVar = `${propertyName}Index`;
const innerLoopVar = `${propertyName}Value`;
return `
auto ${propertyName} = jsi::Array(runtime, ${eventChain}.size());
size_t ${indexVar} = 0;
for (auto ${innerLoopVar} : ${eventChain}) {
${handleArrayElementType(
elementType,
propertyName,
indexVar,
innerLoopVar,
propertyParts,
extraIncludes,
usingEvent,
)}
}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
`;
}
function handleArrayElementType(
elementType: EventTypeAnnotation,
propertyName: string,
indexVariable: string,
loopLocalVariable: string,
propertyParts: $ReadOnlyArray<string>,
extraIncludes: Set<string>,
usingEvent: boolean,
): string {
switch (elementType.type) {
case 'BooleanTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `(bool)${val}`,
);
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return setValueAtIndex(propertyName, indexVariable, loopLocalVariable);
case 'MixedTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `jsi::valueFromDynamic(runtime, ${val})`,
);
case 'StringLiteralUnionTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `toString(${val})`,
);
case 'ObjectTypeAnnotation':
return convertObjectTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
elementType,
extraIncludes,
);
case 'ArrayTypeAnnotation':
return convertArrayTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
elementType,
extraIncludes,
usingEvent,
);
default:
throw new Error(
`Received invalid elementType for array ${elementType.type}`,
);
}
}
function convertObjectTypeArray(
propertyName: string,
indexVariable: string,
loopLocalVariable: string,
propertyParts: $ReadOnlyArray<string>,
objectTypeAnnotation: ObjectTypeAnnotation<EventTypeAnnotation>,
extraIncludes: Set<string>,
): string {
return `auto ${propertyName}Object = jsi::Object(runtime);
${generateSetters(
`${propertyName}Object`,
objectTypeAnnotation.properties,
[].concat([loopLocalVariable]),
extraIncludes,
false,
)}
${setValueAtIndex(propertyName, indexVariable, `${propertyName}Object`)}`;
}
function convertArrayTypeArray(
propertyName: string,
indexVariable: string,
loopLocalVariable: string,
propertyParts: $ReadOnlyArray<string>,
eventTypeAnnotation: EventTypeAnnotation,
extraIncludes: Set<string>,
usingEvent: boolean,
): string {
if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') {
throw new Error(
`Inconsistent eventTypeAnnotation received. Expected type = 'ArrayTypeAnnotation'; received = ${eventTypeAnnotation.type}`,
);
}
return `auto ${propertyName}Array = jsi::Array(runtime, ${loopLocalVariable}.size());
size_t ${indexVariable}Internal = 0;
for (auto ${loopLocalVariable}Internal : ${loopLocalVariable}) {
${handleArrayElementType(
eventTypeAnnotation.elementType,
`${propertyName}Array`,
`${indexVariable}Internal`,
`${loopLocalVariable}Internal`,
propertyParts,
extraIncludes,
usingEvent,
)}
}
${setValueAtIndex(propertyName, indexVariable, `${propertyName}Array`)}`;
}
function generateSetters(
parentPropertyName: string,
properties: $ReadOnlyArray<NamedShape<EventTypeAnnotation>>,
propertyParts: $ReadOnlyArray<string>,
extraIncludes: Set<string>,
usingEvent: boolean = true,
): string {
const propSetters = properties
.map(eventProperty => {
const {typeAnnotation} = eventProperty;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
);
case 'MixedTypeAnnotation':
extraIncludes.add('#include <jsi/JSIDynamic.h>');
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `jsi::valueFromDynamic(runtime, ${prop})`,
);
case 'StringLiteralUnionTypeAnnotation':
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `toString(${prop})`,
);
case 'ObjectTypeAnnotation':
return generateObjectSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
typeAnnotation,
extraIncludes,
usingEvent,
);
case 'ArrayTypeAnnotation':
return generateArraySetter(
parentPropertyName,
eventProperty.name,
propertyParts,
typeAnnotation.elementType,
extraIncludes,
usingEvent,
);
default:
(typeAnnotation.type: empty);
throw new Error(
`Received invalid event property type ${typeAnnotation.type}`,
);
}
})
.join('\n');
return propSetters;
}
function generateEvent(
componentName: string,
event: EventTypeShape,
extraIncludes: Set<string>,
): string {
// This is a gross hack necessary because native code is sending
// events named things like topChange to JS which is then converted back to
// call the onChange prop. We should be consistent throughout the system.
// In order to migrate to this new system we have to support the current
// naming scheme. We should delete this once we are able to control this name
// throughout the system.
const dispatchEventName =
event.paperTopLevelNameDeprecated != null
? event.paperTopLevelNameDeprecated
: `${event.name[2].toLowerCase()}${event.name.slice(3)}`;
if (event.typeAnnotation.argument) {
const implementation = `
auto payload = jsi::Object(runtime);
${generateSetters(
'payload',
event.typeAnnotation.argument.properties,
[],
extraIncludes,
)}
return payload;
`.trim();
if (!event.name.startsWith('on')) {
throw new Error('Expected the event name to start with `on`');
}
return ComponentTemplate({
className: componentName,
eventName: event.name,
dispatchEventName,
structName: generateEventStructName([event.name]),
implementation,
});
}
return BasicComponentTemplate({
className: componentName,
eventName: event.name,
dispatchEventName,
});
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const moduleComponents: ComponentCollection = 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 components;
})
.filter(Boolean)
// $FlowFixMe[unsafe-object-assign]
.reduce((acc, components) => Object.assign(acc, components), {});
const extraIncludes = new Set<string>();
const componentEmitters = Object.keys(moduleComponents)
.map(componentName => {
const component = moduleComponents[componentName];
return component.events
.map(event => generateEvent(componentName, event, extraIncludes))
.join('\n');
})
.join('\n');
const fileName = 'EventEmitters.cpp';
const replacedTemplate = FileTemplate({
events: componentEmitters,
extraIncludes,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,284 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {indent, toSafeCppString} = require('../Utils');
const {
generateEventStructName,
getCppArrayTypeForAnnotation,
getCppTypeForAnnotation,
getImports,
} = require('./CppHelpers');
const nullthrows = require('nullthrows');
// File path -> contents
const FileTemplate = ({componentEmitters, extraIncludes}) => `
/**
* 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: GenerateEventEmitterH.js
*/
#pragma once
#include <react/renderer/components/view/ViewEventEmitter.h>
${[...extraIncludes].join('\n')}
namespace facebook::react {
${componentEmitters}
} // namespace facebook::react
`;
const ComponentTemplate = ({className, structs, events}) =>
`
class ${className}EventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
${structs}
${events}
};
`.trim();
const StructTemplate = ({structName, fields}) =>
`
struct ${structName} {
${fields}
};
`.trim();
const EnumTemplate = ({enumName, values, toCases}) =>
`enum class ${enumName} {
${values}
};
static char const *toString(const ${enumName} value) {
switch (value) {
${toCases}
}
}
`.trim();
function getNativeTypeFromAnnotation(componentName, eventProperty, nameParts) {
const {type} = eventProperty.typeAnnotation;
switch (type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return getCppTypeForAnnotation(type);
case 'StringLiteralUnionTypeAnnotation':
case 'ObjectTypeAnnotation':
return generateEventStructName([...nameParts, eventProperty.name]);
case 'ArrayTypeAnnotation':
const eventTypeAnnotation = eventProperty.typeAnnotation;
if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') {
throw new Error(
"Inconsistent Codegen state: type was ArrayTypeAnnotation at the beginning of the body and now it isn't",
);
}
return getCppArrayTypeForAnnotation(eventTypeAnnotation.elementType, [
...nameParts,
eventProperty.name,
]);
default:
type;
throw new Error(`Received invalid event property type ${type}`);
}
}
function generateEnum(structs, options, nameParts) {
const structName = generateEventStructName(nameParts);
const fields = options
.map((option, index) => `${toSafeCppString(option)}`)
.join(',\n ');
const toCases = options
.map(
option =>
`case ${structName}::${toSafeCppString(option)}: return "${option}";`,
)
.join('\n' + ' ');
structs.set(
structName,
EnumTemplate({
enumName: structName,
values: fields,
toCases: toCases,
}),
);
}
function handleGenerateStructForArray(
structs,
name,
componentName,
elementType,
nameParts,
) {
if (elementType.type === 'ObjectTypeAnnotation') {
generateStruct(
structs,
componentName,
nameParts.concat([name]),
nullthrows(elementType.properties),
);
} else if (elementType.type === 'StringLiteralUnionTypeAnnotation') {
generateEnum(
structs,
elementType.types.map(literal => literal.value),
nameParts.concat([name]),
);
} else if (elementType.type === 'ArrayTypeAnnotation') {
handleGenerateStructForArray(
structs,
name,
componentName,
elementType.elementType,
nameParts,
);
}
}
function generateStruct(structs, componentName, nameParts, properties) {
const structNameParts = nameParts;
const structName = generateEventStructName(structNameParts);
const fields = properties
.map(property => {
return `${getNativeTypeFromAnnotation(componentName, property, structNameParts)} ${property.name};`;
})
.join('\n' + ' ');
properties.forEach(property => {
const {name, typeAnnotation} = property;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return;
case 'ArrayTypeAnnotation':
handleGenerateStructForArray(
structs,
name,
componentName,
typeAnnotation.elementType,
nameParts,
);
return;
case 'ObjectTypeAnnotation':
generateStruct(
structs,
componentName,
nameParts.concat([name]),
nullthrows(typeAnnotation.properties),
);
return;
case 'StringLiteralUnionTypeAnnotation':
generateEnum(
structs,
typeAnnotation.types.map(literal => literal.value),
nameParts.concat([name]),
);
return;
default:
typeAnnotation.type;
throw new Error(
`Received invalid event property type ${typeAnnotation.type}`,
);
}
});
structs.set(
structName,
StructTemplate({
structName,
fields,
}),
);
}
function generateStructs(componentName, component) {
const structs = new Map();
component.events.forEach(event => {
if (event.typeAnnotation.argument) {
generateStruct(
structs,
componentName,
[event.name],
event.typeAnnotation.argument.properties,
);
}
});
return Array.from(structs.values()).join('\n\n');
}
function generateEvent(componentName, event) {
if (event.typeAnnotation.argument) {
const structName = generateEventStructName([event.name]);
return `void ${event.name}(${structName} value) const;`;
}
return `void ${event.name}() const;`;
}
function generateEvents(componentName, component) {
return component.events
.map(event => generateEvent(componentName, event))
.join('\n\n' + ' ');
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const moduleComponents = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return null;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return components;
})
.filter(Boolean)
// $FlowFixMe[unsafe-object-assign]
.reduce((acc, components) => Object.assign(acc, components), {});
const extraIncludes = new Set();
const componentEmitters = Object.keys(moduleComponents)
.map(componentName => {
const component = moduleComponents[componentName];
component.events.forEach(event => {
if (event.typeAnnotation.argument) {
const argIncludes = getImports(
event.typeAnnotation.argument.properties,
);
// $FlowFixMe[method-unbinding]
argIncludes.forEach(extraIncludes.add, extraIncludes);
}
});
const replacedTemplate = ComponentTemplate({
className: componentName,
structs: indent(generateStructs(componentName, component), 2),
events: generateEvents(componentName, component),
});
return replacedTemplate;
})
.join('\n');
const fileName = 'EventEmitters.h';
const replacedTemplate = FileTemplate({
componentEmitters,
extraIncludes,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,377 @@
/**
* 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-local
* @format
*/
'use strict';
import type {
ComponentShape,
EventTypeAnnotation,
EventTypeShape,
NamedShape,
SchemaType,
} from '../../CodegenSchema';
const {indent, toSafeCppString} = require('../Utils');
const {
generateEventStructName,
getCppArrayTypeForAnnotation,
getCppTypeForAnnotation,
getImports,
} = require('./CppHelpers');
const nullthrows = require('nullthrows');
// File path -> contents
type FilesOutput = Map<string, string>;
type StructsMap = Map<string, string>;
type ComponentCollection = $ReadOnly<{
[component: string]: ComponentShape,
...
}>;
const FileTemplate = ({
componentEmitters,
extraIncludes,
}: {
componentEmitters: string,
extraIncludes: Set<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: GenerateEventEmitterH.js
*/
#pragma once
#include <react/renderer/components/view/ViewEventEmitter.h>
${[...extraIncludes].join('\n')}
namespace facebook::react {
${componentEmitters}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
structs,
events,
}: {
className: string,
structs: string,
events: string,
}) =>
`
class ${className}EventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
${structs}
${events}
};
`.trim();
const StructTemplate = ({
structName,
fields,
}: {
structName: string,
fields: string,
}) =>
`
struct ${structName} {
${fields}
};
`.trim();
const EnumTemplate = ({
enumName,
values,
toCases,
}: {
enumName: string,
values: string,
toCases: string,
}) =>
`enum class ${enumName} {
${values}
};
static char const *toString(const ${enumName} value) {
switch (value) {
${toCases}
}
}
`.trim();
function getNativeTypeFromAnnotation(
componentName: string,
eventProperty: NamedShape<EventTypeAnnotation>,
nameParts: $ReadOnlyArray<string>,
): string {
const {type} = eventProperty.typeAnnotation;
switch (type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return getCppTypeForAnnotation(type);
case 'StringLiteralUnionTypeAnnotation':
case 'ObjectTypeAnnotation':
return generateEventStructName([...nameParts, eventProperty.name]);
case 'ArrayTypeAnnotation':
const eventTypeAnnotation = eventProperty.typeAnnotation;
if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') {
throw new Error(
"Inconsistent Codegen state: type was ArrayTypeAnnotation at the beginning of the body and now it isn't",
);
}
return getCppArrayTypeForAnnotation(eventTypeAnnotation.elementType, [
...nameParts,
eventProperty.name,
]);
default:
(type: empty);
throw new Error(`Received invalid event property type ${type}`);
}
}
function generateEnum(
structs: StructsMap,
options: $ReadOnlyArray<string>,
nameParts: Array<string>,
) {
const structName = generateEventStructName(nameParts);
const fields = options
.map((option, index) => `${toSafeCppString(option)}`)
.join(',\n ');
const toCases = options
.map(
option =>
`case ${structName}::${toSafeCppString(option)}: return "${option}";`,
)
.join('\n' + ' ');
structs.set(
structName,
EnumTemplate({
enumName: structName,
values: fields,
toCases: toCases,
}),
);
}
function handleGenerateStructForArray(
structs: StructsMap,
name: string,
componentName: string,
elementType: EventTypeAnnotation,
nameParts: $ReadOnlyArray<string>,
): void {
if (elementType.type === 'ObjectTypeAnnotation') {
generateStruct(
structs,
componentName,
nameParts.concat([name]),
nullthrows(elementType.properties),
);
} else if (elementType.type === 'StringLiteralUnionTypeAnnotation') {
generateEnum(
structs,
elementType.types.map(literal => literal.value),
nameParts.concat([name]),
);
} else if (elementType.type === 'ArrayTypeAnnotation') {
handleGenerateStructForArray(
structs,
name,
componentName,
elementType.elementType,
nameParts,
);
}
}
function generateStruct(
structs: StructsMap,
componentName: string,
nameParts: $ReadOnlyArray<string>,
properties: $ReadOnlyArray<NamedShape<EventTypeAnnotation>>,
): void {
const structNameParts = nameParts;
const structName = generateEventStructName(structNameParts);
const fields = properties
.map(property => {
return `${getNativeTypeFromAnnotation(
componentName,
property,
structNameParts,
)} ${property.name};`;
})
.join('\n' + ' ');
properties.forEach(property => {
const {name, typeAnnotation} = property;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return;
case 'ArrayTypeAnnotation':
handleGenerateStructForArray(
structs,
name,
componentName,
typeAnnotation.elementType,
nameParts,
);
return;
case 'ObjectTypeAnnotation':
generateStruct(
structs,
componentName,
nameParts.concat([name]),
nullthrows(typeAnnotation.properties),
);
return;
case 'StringLiteralUnionTypeAnnotation':
generateEnum(
structs,
typeAnnotation.types.map(literal => literal.value),
nameParts.concat([name]),
);
return;
default:
(typeAnnotation.type: empty);
throw new Error(
`Received invalid event property type ${typeAnnotation.type}`,
);
}
});
structs.set(
structName,
StructTemplate({
structName,
fields,
}),
);
}
function generateStructs(
componentName: string,
component: ComponentShape,
): string {
const structs: StructsMap = new Map();
component.events.forEach(event => {
if (event.typeAnnotation.argument) {
generateStruct(
structs,
componentName,
[event.name],
event.typeAnnotation.argument.properties,
);
}
});
return Array.from(structs.values()).join('\n\n');
}
function generateEvent(componentName: string, event: EventTypeShape): string {
if (event.typeAnnotation.argument) {
const structName = generateEventStructName([event.name]);
return `void ${event.name}(${structName} value) const;`;
}
return `void ${event.name}() const;`;
}
function generateEvents(
componentName: string,
component: ComponentShape,
): string {
return component.events
.map(event => generateEvent(componentName, event))
.join('\n\n' + ' ');
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const moduleComponents: ComponentCollection = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return null;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return components;
})
.filter(Boolean)
// $FlowFixMe[unsafe-object-assign]
.reduce((acc, components) => Object.assign(acc, components), {});
const extraIncludes = new Set<string>();
const componentEmitters = Object.keys(moduleComponents)
.map(componentName => {
const component = moduleComponents[componentName];
component.events.forEach(event => {
if (event.typeAnnotation.argument) {
const argIncludes = getImports(
event.typeAnnotation.argument.properties,
);
// $FlowFixMe[method-unbinding]
argIncludes.forEach(extraIncludes.add, extraIncludes);
}
});
const replacedTemplate = ComponentTemplate({
className: componentName,
structs: indent(generateStructs(componentName, component), 2),
events: generateEvents(componentName, component),
});
return replacedTemplate;
})
.join('\n');
const fileName = 'EventEmitters.h';
const replacedTemplate = FileTemplate({
componentEmitters,
extraIncludes,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,266 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {
IncludeTemplate,
convertDefaultTypeToString,
getImports,
getSourceProp,
isWrappedPropType,
} = require('./CppHelpers');
// File path -> contents
const FileTemplate = ({imports, componentClasses, headerPrefix}) => `
/**
* 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: GeneratePropsCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'Props.h',
})}
${imports}
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ComponentTemplate = ({className, extendClasses, props, diffProps}) =>
`
${className}::${className}(
const PropsParserContext &context,
const ${className} &sourceProps,
const RawProps &rawProps):${extendClasses}
${props} {}
${diffProps}
`.trim();
function generatePropsDiffString(
className,
componentName,
component,
debugProps = '',
includeGetDebugPropsImplementation = false,
) {
const diffProps = component.props
.map(prop => {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'BooleanTypeAnnotation':
case 'MixedTypeAnnotation':
return `
if (${prop.name} != oldProps->${prop.name}) {
result["${prop.name}"] = ${prop.name};
}`;
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return `
if ((${prop.name} != oldProps->${prop.name}) && !(std::isnan(${prop.name}) && std::isnan(oldProps->${prop.name}))) {
result["${prop.name}"] = ${prop.name};
}`;
case 'ArrayTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'StringEnumTypeAnnotation':
case 'Int32EnumTypeAnnotation':
return `
if (${prop.name} != oldProps->${prop.name}) {
result["${prop.name}"] = toDynamic(${prop.name});
}`;
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return `
if (${prop.name} != oldProps->${prop.name}) {
result["${prop.name}"] = *${prop.name};
}`;
case 'ImageSourcePrimitive':
case 'PointPrimitive':
case 'EdgeInsetsPrimitive':
case 'DimensionPrimitive':
return `
if (${prop.name} != oldProps->${prop.name}) {
result["${prop.name}"] = toDynamic(${prop.name});
}`;
case 'ImageRequestPrimitive':
// Shouldn't be used in props
throw new Error(
'ImageRequestPrimitive should not be used in Props',
);
default:
typeAnnotation.name;
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
default:
return '';
}
})
.join('\n' + ' ');
const getDebugPropsString = `#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList ${className}::getDebugProps() const {
return ViewProps::getDebugProps()${debugProps && debugProps.length > 0 ? ` +\n\t\tSharedDebugStringConvertibleList{${debugProps}\n\t}` : ''};
}
#endif`;
return `
#ifdef RN_SERIALIZABLE_STATE
ComponentName ${className}::getDiffPropsImplementationTarget() const {
return "${componentName}";
}
folly::dynamic ${className}::getDiffProps(
const Props* prevProps) const {
static const auto defaultProps = ${className}();
const ${className}* oldProps = prevProps == nullptr
? &defaultProps
: static_cast<const ${className}*>(prevProps);
if (this == oldProps) {
return folly::dynamic::object();
}
folly::dynamic result = HostPlatformViewProps::getDiffProps(prevProps);
${diffProps}
return result;
}
#endif
${includeGetDebugPropsImplementation ? getDebugPropsString : ''}
`;
}
function generatePropsString(componentName, component) {
return component.props
.map(prop => {
const sourceProp = getSourceProp(componentName, prop);
const defaultValue = convertDefaultTypeToString(componentName, prop);
const isWrappedProp = isWrappedPropType(prop);
let convertRawProp = `convertRawProp(context, rawProps, "${prop.name}", ${sourceProp}, {${defaultValue}})`;
if (isWrappedProp) {
convertRawProp += '.value';
}
return `${prop.name}(${convertRawProp})`;
})
.join(',\n' + ' ');
}
function generateDebugPropsString(componentName, component) {
return component.props
.map(prop => {
if (prop.typeAnnotation.type === 'ObjectTypeAnnotation') {
// Skip ObjectTypeAnnotation because there is no generic `toString`
// method for it. We would have to define an interface that the structs implement.
return '';
}
const defaultValue = convertDefaultTypeToString(componentName, prop);
return `\n\t\t\tdebugStringConvertibleItem("${prop.name}", ${prop.name}${defaultValue ? `, ${defaultValue}` : ''})`;
})
.join(',');
}
function getClassExtendString(component) {
const extendString =
' ' +
component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'ViewProps(context, sourceProps, rawProps)';
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
})
.join(', ') +
`${component.props.length > 0 ? ',' : ''}`;
return extendString;
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const fileName = 'Props.cpp';
const allImports = new Set([
'#include <react/renderer/core/propsConversions.h>',
'#include <react/renderer/core/PropsParserContext.h>',
]);
if (includeGetDebugPropsImplementation) {
allImports.add('#include <react/renderer/core/graphicsConversions.h>');
allImports.add(
'#include <react/renderer/debug/debugStringConvertibleUtils.h>',
);
}
const componentProps = 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)
.map(componentName => {
const component = components[componentName];
const newName = `${componentName}Props`;
const propsString = generatePropsString(componentName, component);
const extendString = getClassExtendString(component);
const debugProps = includeGetDebugPropsImplementation
? generateDebugPropsString(componentName, component)
: '';
const diffPropsString = generatePropsDiffString(
newName,
componentName,
component,
debugProps,
includeGetDebugPropsImplementation,
);
const imports = getImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
const replacedTemplate = ComponentTemplate({
className: newName,
extendClasses: extendString,
props: propsString,
diffProps: diffPropsString,
});
return replacedTemplate;
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
componentClasses: componentProps,
imports: Array.from(allImports).sort().join('\n').trim(),
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,306 @@
/**
* 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 {ComponentShape, SchemaType} from '../../CodegenSchema';
const {
IncludeTemplate,
convertDefaultTypeToString,
getImports,
getSourceProp,
isWrappedPropType,
} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
imports,
componentClasses,
headerPrefix,
}: {
imports: string,
componentClasses: string,
headerPrefix: 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: GeneratePropsCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'Props.h'})}
${imports}
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
extendClasses,
props,
diffProps,
}: {
className: string,
extendClasses: string,
props: string,
diffProps: string,
}) =>
`
${className}::${className}(
const PropsParserContext &context,
const ${className} &sourceProps,
const RawProps &rawProps):${extendClasses}
${props} {}
${diffProps}
`.trim();
function generatePropsDiffString(
className: string,
componentName: string,
component: ComponentShape,
debugProps: string = '',
includeGetDebugPropsImplementation?: boolean = false,
) {
const diffProps = component.props
.map(prop => {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'BooleanTypeAnnotation':
case 'MixedTypeAnnotation':
return `
if (${prop.name} != oldProps->${prop.name}) {
result["${prop.name}"] = ${prop.name};
}`;
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return `
if ((${prop.name} != oldProps->${prop.name}) && !(std::isnan(${prop.name}) && std::isnan(oldProps->${prop.name}))) {
result["${prop.name}"] = ${prop.name};
}`;
case 'ArrayTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'StringEnumTypeAnnotation':
case 'Int32EnumTypeAnnotation':
return `
if (${prop.name} != oldProps->${prop.name}) {
result["${prop.name}"] = toDynamic(${prop.name});
}`;
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return `
if (${prop.name} != oldProps->${prop.name}) {
result["${prop.name}"] = *${prop.name};
}`;
case 'ImageSourcePrimitive':
case 'PointPrimitive':
case 'EdgeInsetsPrimitive':
case 'DimensionPrimitive':
return `
if (${prop.name} != oldProps->${prop.name}) {
result["${prop.name}"] = toDynamic(${prop.name});
}`;
case 'ImageRequestPrimitive':
// Shouldn't be used in props
throw new Error(
'ImageRequestPrimitive should not be used in Props',
);
default:
(typeAnnotation.name: empty);
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
default:
return '';
}
})
.join('\n' + ' ');
const getDebugPropsString = `#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList ${className}::getDebugProps() const {
return ViewProps::getDebugProps()${debugProps && debugProps.length > 0 ? ` +\n\t\tSharedDebugStringConvertibleList{${debugProps}\n\t}` : ''};
}
#endif`;
return `
#ifdef RN_SERIALIZABLE_STATE
ComponentName ${className}::getDiffPropsImplementationTarget() const {
return "${componentName}";
}
folly::dynamic ${className}::getDiffProps(
const Props* prevProps) const {
static const auto defaultProps = ${className}();
const ${className}* oldProps = prevProps == nullptr
? &defaultProps
: static_cast<const ${className}*>(prevProps);
if (this == oldProps) {
return folly::dynamic::object();
}
folly::dynamic result = HostPlatformViewProps::getDiffProps(prevProps);
${diffProps}
return result;
}
#endif
${includeGetDebugPropsImplementation ? getDebugPropsString : ''}
`;
}
function generatePropsString(componentName: string, component: ComponentShape) {
return component.props
.map(prop => {
const sourceProp = getSourceProp(componentName, prop);
const defaultValue = convertDefaultTypeToString(componentName, prop);
const isWrappedProp = isWrappedPropType(prop);
let convertRawProp = `convertRawProp(context, rawProps, "${prop.name}", ${sourceProp}, {${defaultValue}})`;
if (isWrappedProp) {
convertRawProp += '.value';
}
return `${prop.name}(${convertRawProp})`;
})
.join(',\n' + ' ');
}
function generateDebugPropsString(
componentName: string,
component: ComponentShape,
) {
return component.props
.map(prop => {
if (prop.typeAnnotation.type === 'ObjectTypeAnnotation') {
// Skip ObjectTypeAnnotation because there is no generic `toString`
// method for it. We would have to define an interface that the structs implement.
return '';
}
const defaultValue = convertDefaultTypeToString(componentName, prop);
return `\n\t\t\tdebugStringConvertibleItem("${prop.name}", ${prop.name}${defaultValue ? `, ${defaultValue}` : ''})`;
})
.join(',');
}
function getClassExtendString(component: ComponentShape): string {
const extendString =
' ' +
component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'ViewProps(context, sourceProps, rawProps)';
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
})
.join(', ') +
`${component.props.length > 0 ? ',' : ''}`;
return extendString;
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const fileName = 'Props.cpp';
const allImports: Set<string> = new Set([
'#include <react/renderer/core/propsConversions.h>',
'#include <react/renderer/core/PropsParserContext.h>',
]);
if (includeGetDebugPropsImplementation) {
allImports.add('#include <react/renderer/core/graphicsConversions.h>');
allImports.add(
'#include <react/renderer/debug/debugStringConvertibleUtils.h>',
);
}
const componentProps = 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)
.map(componentName => {
const component = components[componentName];
const newName = `${componentName}Props`;
const propsString = generatePropsString(componentName, component);
const extendString = getClassExtendString(component);
const debugProps = includeGetDebugPropsImplementation
? generateDebugPropsString(componentName, component)
: '';
const diffPropsString = generatePropsDiffString(
newName,
componentName,
component,
debugProps,
includeGetDebugPropsImplementation,
);
const imports = getImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
const replacedTemplate = ComponentTemplate({
className: newName,
extendClasses: extendString,
props: propsString,
diffProps: diffPropsString,
});
return replacedTemplate;
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
componentClasses: componentProps,
imports: Array.from(allImports).sort().join('\n').trim(),
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,693 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {getEnumName, toSafeCppString} = require('../Utils');
const {
getLocalImports,
getNativeTypeFromAnnotation,
} = require('./ComponentsGeneratorUtils.js');
const {
generateStructName,
getDefaultInitializerString,
getEnumMaskName,
toIntEnumValueName,
} = require('./CppHelpers.js');
// File path -> contents
const FileTemplate = ({imports, componentClasses}) => `
/**
* 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: GeneratePropsH.js
*/
#pragma once
${imports}
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ClassTemplate = ({
enums,
structs,
className,
props,
extendClasses,
includeGetDebugPropsImplementation,
}) => {
const getDebugPropsString = `#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif`;
return `
${enums}
${structs}
class ${className} final${extendClasses} {
public:
${className}() = default;
${className}(const PropsParserContext& context, const ${className} &sourceProps, const RawProps &rawProps);
#pragma mark - Props
${props}
#ifdef RN_SERIALIZABLE_STATE
ComponentName getDiffPropsImplementationTarget() const override;
folly::dynamic getDiffProps(const Props* prevProps) const override;
#endif
${includeGetDebugPropsImplementation ? getDebugPropsString : ''}
};
`.trim();
};
const EnumTemplate = ({enumName, values, fromCases, toCases}) =>
`
enum class ${enumName} { ${values} };
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) {
auto string = (std::string)value;
${fromCases}
abort();
}
static inline std::string toString(const ${enumName} &value) {
switch (value) {
${toCases}
}
}
#ifdef RN_SERIALIZABLE_STATE
static inline folly::dynamic toDynamic(const ${enumName} &value) {
return toString(value);
}
#endif
`.trim();
const IntEnumTemplate = ({
enumName,
values,
fromCases,
toCases,
toDynamicCases,
}) =>
`
enum class ${enumName} { ${values} };
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) {
assert(value.hasType<int>());
auto integerValue = (int)value;
switch (integerValue) {${fromCases}
}
abort();
}
static inline std::string toString(const ${enumName} &value) {
switch (value) {
${toCases}
}
}
#ifdef RN_SERIALIZABLE_STATE
static inline folly::dynamic toDynamic(const ${enumName} &value) {
switch (value) {
${toDynamicCases}
}
}
#endif
`.trim();
const StructTemplate = ({structName, fields, fromCases, toDynamicCases}) =>
`struct ${structName} {
${fields}
#ifdef RN_SERIALIZABLE_STATE
bool operator==(const ${structName}&) const = default;
folly::dynamic toDynamic() const {
folly::dynamic result = folly::dynamic::object();
${toDynamicCases}
return result;
}
#endif
};
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${structName} &result) {
auto map = (std::unordered_map<std::string, RawValue>)value;
${fromCases}
}
static inline std::string toString(const ${structName} &value) {
return "[Object ${structName}]";
}
#ifdef RN_SERIALIZABLE_STATE
static inline folly::dynamic toDynamic(const ${structName} &value) {
return value.toDynamic();
}
#endif
`.trim();
const ArrayConversionFunctionTemplate = ({
structName,
}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<${structName}> &result) {
auto items = (std::vector<RawValue>)value;
for (const auto &item : items) {
${structName} newItem;
fromRawValue(context, item, newItem);
result.emplace_back(newItem);
}
}
`;
const DoubleArrayConversionFunctionTemplate = ({
structName,
}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<std::vector<${structName}>> &result) {
auto items = (std::vector<std::vector<RawValue>>)value;
for (const std::vector<RawValue> &item : items) {
auto nestedArray = std::vector<${structName}>{};
for (const RawValue &nestedItem : item) {
${structName} newItem;
fromRawValue(context, nestedItem, newItem);
nestedArray.emplace_back(newItem);
}
result.emplace_back(nestedArray);
}
}
`;
const ArrayEnumTemplate = ({enumName, enumMask, values, fromCases, toCases}) =>
`
using ${enumMask} = uint32_t;
struct ${enumMask}Wrapped {
${enumMask} value;
};
enum class ${enumName}: ${enumMask} {
${values}
};
constexpr bool operator&(
${enumMask} const lhs,
enum ${enumName} const rhs) {
return lhs & static_cast<${enumMask}>(rhs);
}
constexpr ${enumMask} operator|(
${enumMask} const lhs,
enum ${enumName} const rhs) {
return lhs | static_cast<${enumMask}>(rhs);
}
constexpr void operator|=(
${enumMask} &lhs,
enum ${enumName} const rhs) {
lhs = lhs | static_cast<${enumMask}>(rhs);
}
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumMask}Wrapped &wrapped) {
auto items = std::vector<std::string>{value};
for (const auto &item : items) {
${fromCases}
abort();
}
}
static inline std::string toString(const ${enumMask}Wrapped &wrapped) {
auto result = std::string{};
auto separator = std::string{", "};
${toCases}
if (!result.empty()) {
result.erase(result.length() - separator.length());
}
return result;
}
`.trim();
function getClassExtendString(component) {
if (component.extendsProps.length === 0) {
throw new Error('Invalid: component.extendsProps is empty');
}
const extendString =
' : ' +
component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'public ViewProps';
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
})
.join(' ');
return extendString;
}
function convertValueToEnumOption(value) {
return toSafeCppString(value);
}
function generateArrayEnumString(componentName, name, options) {
const enumName = getEnumName(componentName, name);
const values = options
.map((option, index) => `${toSafeCppString(option)} = 1 << ${index}`)
.join(',\n ');
const fromCases = options
.map(
option => `if (item == "${option}") {
wrapped.value |= ${enumName}::${toSafeCppString(option)};
continue;
}`,
)
.join('\n ');
const toCases = options
.map(
option => `if (wrapped.value & ${enumName}::${toSafeCppString(option)}) {
result += "${option}" + separator;
}`,
)
.join('\n' + ' ');
return ArrayEnumTemplate({
enumName,
enumMask: getEnumMaskName(enumName),
values,
fromCases,
toCases,
});
}
function generateStringEnum(componentName, prop) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
const values = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value =>
`if (string == "${value}") { result = ${enumName}::${convertValueToEnumOption(value)}; return; }`,
)
.join('\n' + ' ');
const toCases = values
.map(
value =>
`case ${enumName}::${convertValueToEnumOption(value)}: return "${value}";`,
)
.join('\n' + ' ');
return EnumTemplate({
enumName,
values: values.map(toSafeCppString).join(', '),
fromCases: fromCases,
toCases: toCases,
});
}
return '';
}
function generateIntEnum(componentName, prop) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'Int32EnumTypeAnnotation') {
const values = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value => `
case ${value}:
result = ${enumName}::${toIntEnumValueName(prop.name, value)};
return;`,
)
.join('');
const toCases = values
.map(
value =>
`case ${enumName}::${toIntEnumValueName(prop.name, value)}: return "${value}";`,
)
.join('\n' + ' ');
const toDynamicCases = values
.map(
value =>
`case ${enumName}::${toIntEnumValueName(prop.name, value)}: return ${value};`,
)
.join('\n' + ' ');
const valueVariables = values
.map(val => `${toIntEnumValueName(prop.name, val)} = ${val}`)
.join(', ');
return IntEnumTemplate({
enumName,
values: valueVariables,
fromCases,
toCases,
toDynamicCases,
});
}
return '';
}
function generateEnumString(componentName, component) {
return component.props
.map(prop => {
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'StringEnumTypeAnnotation'
) {
return generateArrayEnumString(
componentName,
prop.name,
prop.typeAnnotation.elementType.options,
);
}
if (prop.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'Int32EnumTypeAnnotation') {
return generateIntEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'ObjectTypeAnnotation') {
return prop.typeAnnotation.properties
.map(property => {
if (property.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, property);
} else if (
property.typeAnnotation.type === 'Int32EnumTypeAnnotation'
) {
return generateIntEnum(componentName, property);
}
return null;
})
.filter(Boolean)
.join('\n');
}
})
.filter(Boolean)
.join('\n');
}
function generatePropsString(componentName, props, nameParts) {
return props
.map(prop => {
const nativeType = getNativeTypeFromAnnotation(
componentName,
prop,
nameParts,
);
const defaultInitializer = getDefaultInitializerString(
componentName,
prop,
);
return `${nativeType} ${prop.name}${defaultInitializer};`;
})
.join('\n' + ' ');
}
function getExtendsImports(extendsProps) {
const imports = new Set();
imports.add('#include <react/renderer/core/PropsParserContext.h>');
imports.add('#include <react/renderer/debug/DebugStringConvertible.h>');
extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
'#include <react/renderer/components/view/ViewProps.h>',
);
return;
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
});
return imports;
}
function generateStructsForComponent(componentName, component) {
const structs = generateStructs(componentName, component.props, []);
const structArray = Array.from(structs.values());
if (structArray.length < 1) {
return '';
}
return structArray.join('\n\n');
}
function generateStructs(componentName, properties, nameParts) {
const structs = new Map();
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = typeAnnotation.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
typeAnnotation.properties,
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = prop.typeAnnotation.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
// Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
);
// Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join('')}ArrayStruct`,
ArrayConversionFunctionTemplate({
structName: generateStructName(
componentName,
nameParts.concat([prop.name]),
),
}),
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.elementType.type ===
'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties =
prop.typeAnnotation.elementType.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
// Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
);
// Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join('')}ArrayArrayStruct`,
DoubleArrayConversionFunctionTemplate({
structName: generateStructName(
componentName,
nameParts.concat([prop.name]),
),
}),
);
}
});
return structs;
}
function generateStruct(structs, componentName, nameParts, properties) {
const structNameParts = nameParts;
const structName = generateStructName(componentName, structNameParts);
const fields = generatePropsString(
componentName,
properties,
structNameParts,
);
properties.forEach(property => {
const name = property.name;
switch (property.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return;
case 'StringTypeAnnotation':
return;
case 'Int32TypeAnnotation':
return;
case 'DoubleTypeAnnotation':
return;
case 'FloatTypeAnnotation':
return;
case 'ReservedPropTypeAnnotation':
return;
case 'ArrayTypeAnnotation':
return;
case 'StringEnumTypeAnnotation':
return;
case 'Int32EnumTypeAnnotation':
return;
case 'ObjectTypeAnnotation':
const props = property.typeAnnotation.properties;
if (props == null) {
throw new Error(
`Properties are expected for ObjectTypeAnnotation (see ${name} in ${componentName})`,
);
}
generateStruct(structs, componentName, nameParts.concat([name]), props);
return;
case 'MixedTypeAnnotation':
return;
default:
property.typeAnnotation.type;
throw new Error(
`Received invalid component property type ${property.typeAnnotation.type}`,
);
}
});
const fromCases = properties
.map(property => {
const variable = 'tmp_' + property.name;
return `auto ${variable} = map.find("${property.name}");
if (${variable} != map.end()) {
fromRawValue(context, ${variable}->second, result.${property.name});
}`;
})
.join('\n ');
const toDynamicCases = properties
.map(property => {
const name = property.name;
switch (property.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return `result["${name}"] = ${name};`;
default:
return `result["${name}"] = ::facebook::react::toDynamic(${name});`;
}
})
.join('\n ');
structs.set(
structName,
StructTemplate({
structName,
fields,
fromCases,
toDynamicCases,
}),
);
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const fileName = 'Props.h';
const allImports = new Set();
const componentClasses = 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)
.map(componentName => {
const component = components[componentName];
const newName = `${componentName}Props`;
const structString = generateStructsForComponent(
componentName,
component,
);
const enumString = generateEnumString(componentName, component);
const propsString = generatePropsString(
componentName,
component.props,
[],
);
const extendString = getClassExtendString(component);
const extendsImports = getExtendsImports(component.extendsProps);
const imports = getLocalImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
extendsImports.forEach(allImports.add, allImports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
const replacedTemplate = ClassTemplate({
enums: enumString,
structs: structString,
className: newName,
extendClasses: extendString,
props: propsString,
includeGetDebugPropsImplementation,
});
return replacedTemplate;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentClasses,
imports: Array.from(allImports).sort().join('\n'),
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,863 @@
/**
* 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 {ComponentShape} from '../../CodegenSchema';
import type {
ExtendsPropsShape,
NamedShape,
PropTypeAnnotation,
SchemaType,
} from '../../CodegenSchema';
const {getEnumName, toSafeCppString} = require('../Utils');
const {
getLocalImports,
getNativeTypeFromAnnotation,
} = require('./ComponentsGeneratorUtils.js');
const {
generateStructName,
getDefaultInitializerString,
getEnumMaskName,
toIntEnumValueName,
} = require('./CppHelpers.js');
// File path -> contents
type FilesOutput = Map<string, string>;
type StructsMap = Map<string, string>;
const FileTemplate = ({
imports,
componentClasses,
}: {
imports: string,
componentClasses: 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: GeneratePropsH.js
*/
#pragma once
${imports}
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ClassTemplate = ({
enums,
structs,
className,
props,
extendClasses,
includeGetDebugPropsImplementation,
}: {
enums: string,
structs: string,
className: string,
props: string,
extendClasses: string,
includeGetDebugPropsImplementation: boolean,
}) => {
const getDebugPropsString = `#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif`;
return `
${enums}
${structs}
class ${className} final${extendClasses} {
public:
${className}() = default;
${className}(const PropsParserContext& context, const ${className} &sourceProps, const RawProps &rawProps);
#pragma mark - Props
${props}
#ifdef RN_SERIALIZABLE_STATE
ComponentName getDiffPropsImplementationTarget() const override;
folly::dynamic getDiffProps(const Props* prevProps) const override;
#endif
${includeGetDebugPropsImplementation ? getDebugPropsString : ''}
};
`.trim();
};
const EnumTemplate = ({
enumName,
values,
fromCases,
toCases,
}: {
enumName: string,
values: string,
fromCases: string,
toCases: string,
}) =>
`
enum class ${enumName} { ${values} };
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) {
auto string = (std::string)value;
${fromCases}
abort();
}
static inline std::string toString(const ${enumName} &value) {
switch (value) {
${toCases}
}
}
#ifdef RN_SERIALIZABLE_STATE
static inline folly::dynamic toDynamic(const ${enumName} &value) {
return toString(value);
}
#endif
`.trim();
const IntEnumTemplate = ({
enumName,
values,
fromCases,
toCases,
toDynamicCases,
}: {
enumName: string,
values: string,
fromCases: string,
toCases: string,
toDynamicCases: string,
}) =>
`
enum class ${enumName} { ${values} };
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) {
assert(value.hasType<int>());
auto integerValue = (int)value;
switch (integerValue) {${fromCases}
}
abort();
}
static inline std::string toString(const ${enumName} &value) {
switch (value) {
${toCases}
}
}
#ifdef RN_SERIALIZABLE_STATE
static inline folly::dynamic toDynamic(const ${enumName} &value) {
switch (value) {
${toDynamicCases}
}
}
#endif
`.trim();
const StructTemplate = ({
structName,
fields,
fromCases,
toDynamicCases,
}: {
structName: string,
fields: string,
fromCases: string,
toDynamicCases: string,
}) =>
`struct ${structName} {
${fields}
#ifdef RN_SERIALIZABLE_STATE
bool operator==(const ${structName}&) const = default;
folly::dynamic toDynamic() const {
folly::dynamic result = folly::dynamic::object();
${toDynamicCases}
return result;
}
#endif
};
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${structName} &result) {
auto map = (std::unordered_map<std::string, RawValue>)value;
${fromCases}
}
static inline std::string toString(const ${structName} &value) {
return "[Object ${structName}]";
}
#ifdef RN_SERIALIZABLE_STATE
static inline folly::dynamic toDynamic(const ${structName} &value) {
return value.toDynamic();
}
#endif
`.trim();
const ArrayConversionFunctionTemplate = ({
structName,
}: {
structName: string,
}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<${structName}> &result) {
auto items = (std::vector<RawValue>)value;
for (const auto &item : items) {
${structName} newItem;
fromRawValue(context, item, newItem);
result.emplace_back(newItem);
}
}
`;
const DoubleArrayConversionFunctionTemplate = ({
structName,
}: {
structName: string,
}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<std::vector<${structName}>> &result) {
auto items = (std::vector<std::vector<RawValue>>)value;
for (const std::vector<RawValue> &item : items) {
auto nestedArray = std::vector<${structName}>{};
for (const RawValue &nestedItem : item) {
${structName} newItem;
fromRawValue(context, nestedItem, newItem);
nestedArray.emplace_back(newItem);
}
result.emplace_back(nestedArray);
}
}
`;
const ArrayEnumTemplate = ({
enumName,
enumMask,
values,
fromCases,
toCases,
}: {
enumName: string,
enumMask: string,
values: string,
fromCases: string,
toCases: string,
}) =>
`
using ${enumMask} = uint32_t;
struct ${enumMask}Wrapped {
${enumMask} value;
};
enum class ${enumName}: ${enumMask} {
${values}
};
constexpr bool operator&(
${enumMask} const lhs,
enum ${enumName} const rhs) {
return lhs & static_cast<${enumMask}>(rhs);
}
constexpr ${enumMask} operator|(
${enumMask} const lhs,
enum ${enumName} const rhs) {
return lhs | static_cast<${enumMask}>(rhs);
}
constexpr void operator|=(
${enumMask} &lhs,
enum ${enumName} const rhs) {
lhs = lhs | static_cast<${enumMask}>(rhs);
}
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumMask}Wrapped &wrapped) {
auto items = std::vector<std::string>{value};
for (const auto &item : items) {
${fromCases}
abort();
}
}
static inline std::string toString(const ${enumMask}Wrapped &wrapped) {
auto result = std::string{};
auto separator = std::string{", "};
${toCases}
if (!result.empty()) {
result.erase(result.length() - separator.length());
}
return result;
}
`.trim();
function getClassExtendString(component: ComponentShape): string {
if (component.extendsProps.length === 0) {
throw new Error('Invalid: component.extendsProps is empty');
}
const extendString =
' : ' +
component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'public ViewProps';
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
})
.join(' ');
return extendString;
}
function convertValueToEnumOption(value: string): string {
return toSafeCppString(value);
}
function generateArrayEnumString(
componentName: string,
name: string,
options: $ReadOnlyArray<string>,
): string {
const enumName = getEnumName(componentName, name);
const values = options
.map((option, index) => `${toSafeCppString(option)} = 1 << ${index}`)
.join(',\n ');
const fromCases = options
.map(
option =>
`if (item == "${option}") {
wrapped.value |= ${enumName}::${toSafeCppString(option)};
continue;
}`,
)
.join('\n ');
const toCases = options
.map(
option =>
`if (wrapped.value & ${enumName}::${toSafeCppString(option)}) {
result += "${option}" + separator;
}`,
)
.join('\n' + ' ');
return ArrayEnumTemplate({
enumName,
enumMask: getEnumMaskName(enumName),
values,
fromCases,
toCases,
});
}
function generateStringEnum(
componentName: string,
prop: NamedShape<PropTypeAnnotation>,
) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
const values: $ReadOnlyArray<string> = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value =>
`if (string == "${value}") { result = ${enumName}::${convertValueToEnumOption(
value,
)}; return; }`,
)
.join('\n' + ' ');
const toCases = values
.map(
value =>
`case ${enumName}::${convertValueToEnumOption(
value,
)}: return "${value}";`,
)
.join('\n' + ' ');
return EnumTemplate({
enumName,
values: values.map(toSafeCppString).join(', '),
fromCases: fromCases,
toCases: toCases,
});
}
return '';
}
function generateIntEnum(
componentName: string,
prop: NamedShape<PropTypeAnnotation>,
) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'Int32EnumTypeAnnotation') {
const values: $ReadOnlyArray<number> = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value =>
`
case ${value}:
result = ${enumName}::${toIntEnumValueName(prop.name, value)};
return;`,
)
.join('');
const toCases = values
.map(
value =>
`case ${enumName}::${toIntEnumValueName(
prop.name,
value,
)}: return "${value}";`,
)
.join('\n' + ' ');
const toDynamicCases = values
.map(
value =>
`case ${enumName}::${toIntEnumValueName(
prop.name,
value,
)}: return ${value};`,
)
.join('\n' + ' ');
const valueVariables = values
.map(val => `${toIntEnumValueName(prop.name, val)} = ${val}`)
.join(', ');
return IntEnumTemplate({
enumName,
values: valueVariables,
fromCases,
toCases,
toDynamicCases,
});
}
return '';
}
function generateEnumString(
componentName: string,
component: ComponentShape,
): string {
return component.props
.map(prop => {
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'StringEnumTypeAnnotation'
) {
return generateArrayEnumString(
componentName,
prop.name,
prop.typeAnnotation.elementType.options,
);
}
if (prop.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'Int32EnumTypeAnnotation') {
return generateIntEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'ObjectTypeAnnotation') {
return prop.typeAnnotation.properties
.map(property => {
if (property.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, property);
} else if (
property.typeAnnotation.type === 'Int32EnumTypeAnnotation'
) {
return generateIntEnum(componentName, property);
}
return null;
})
.filter(Boolean)
.join('\n');
}
})
.filter(Boolean)
.join('\n');
}
function generatePropsString(
componentName: string,
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
nameParts: $ReadOnlyArray<string>,
) {
return props
.map(prop => {
const nativeType = getNativeTypeFromAnnotation(
componentName,
prop,
nameParts,
);
const defaultInitializer = getDefaultInitializerString(
componentName,
prop,
);
return `${nativeType} ${prop.name}${defaultInitializer};`;
})
.join('\n' + ' ');
}
function getExtendsImports(
extendsProps: $ReadOnlyArray<ExtendsPropsShape>,
): Set<string> {
const imports: Set<string> = new Set();
imports.add('#include <react/renderer/core/PropsParserContext.h>');
imports.add('#include <react/renderer/debug/DebugStringConvertible.h>');
extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
'#include <react/renderer/components/view/ViewProps.h>',
);
return;
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
});
return imports;
}
function generateStructsForComponent(
componentName: string,
component: ComponentShape,
): string {
const structs = generateStructs(componentName, component.props, []);
const structArray = Array.from(structs.values());
if (structArray.length < 1) {
return '';
}
return structArray.join('\n\n');
}
function generateStructs(
componentName: string,
properties: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
nameParts: Array<string>,
): StructsMap {
const structs: StructsMap = new Map();
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = typeAnnotation.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
typeAnnotation.properties,
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = prop.typeAnnotation.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
// Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
);
// Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join(
'',
)}ArrayStruct`,
ArrayConversionFunctionTemplate({
structName: generateStructName(
componentName,
nameParts.concat([prop.name]),
),
}),
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.elementType.type ===
'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties =
prop.typeAnnotation.elementType.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
// Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
);
// Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join(
'',
)}ArrayArrayStruct`,
DoubleArrayConversionFunctionTemplate({
structName: generateStructName(
componentName,
nameParts.concat([prop.name]),
),
}),
);
}
});
return structs;
}
function generateStruct(
structs: StructsMap,
componentName: string,
nameParts: $ReadOnlyArray<string>,
properties: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
): void {
const structNameParts = nameParts;
const structName = generateStructName(componentName, structNameParts);
const fields = generatePropsString(
componentName,
properties,
structNameParts,
);
properties.forEach((property: NamedShape<PropTypeAnnotation>) => {
const name = property.name;
switch (property.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return;
case 'StringTypeAnnotation':
return;
case 'Int32TypeAnnotation':
return;
case 'DoubleTypeAnnotation':
return;
case 'FloatTypeAnnotation':
return;
case 'ReservedPropTypeAnnotation':
return;
case 'ArrayTypeAnnotation':
return;
case 'StringEnumTypeAnnotation':
return;
case 'Int32EnumTypeAnnotation':
return;
case 'ObjectTypeAnnotation':
const props = property.typeAnnotation.properties;
if (props == null) {
throw new Error(
`Properties are expected for ObjectTypeAnnotation (see ${name} in ${componentName})`,
);
}
generateStruct(structs, componentName, nameParts.concat([name]), props);
return;
case 'MixedTypeAnnotation':
return;
default:
(property.typeAnnotation.type: empty);
throw new Error(
`Received invalid component property type ${property.typeAnnotation.type}`,
);
}
});
const fromCases = properties
.map(property => {
const variable = 'tmp_' + property.name;
return `auto ${variable} = map.find("${property.name}");
if (${variable} != map.end()) {
fromRawValue(context, ${variable}->second, result.${property.name});
}`;
})
.join('\n ');
const toDynamicCases = properties
.map((property: NamedShape<PropTypeAnnotation>) => {
const name = property.name;
switch (property.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return `result["${name}"] = ${name};`;
default:
return `result["${name}"] = ::facebook::react::toDynamic(${name});`;
}
})
.join('\n ');
structs.set(
structName,
StructTemplate({
structName,
fields,
fromCases,
toDynamicCases,
}),
);
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const fileName = 'Props.h';
const allImports: Set<string> = new Set();
const componentClasses = 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)
.map(componentName => {
const component = components[componentName];
const newName = `${componentName}Props`;
const structString = generateStructsForComponent(
componentName,
component,
);
const enumString = generateEnumString(componentName, component);
const propsString = generatePropsString(
componentName,
component.props,
[],
);
const extendString = getClassExtendString(component);
const extendsImports = getExtendsImports(component.extendsProps);
const imports = getLocalImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
extendsImports.forEach(allImports.add, allImports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
const replacedTemplate = ClassTemplate({
enums: enumString,
structs: structString,
className: newName,
extendClasses: extendString,
props: propsString,
includeGetDebugPropsImplementation,
});
return replacedTemplate;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentClasses,
imports: Array.from(allImports).sort().join('\n'),
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,298 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {
getDelegateJavaClassName,
getImports,
getInterfaceJavaClassName,
toSafeJavaString,
} = require('./JavaHelpers');
// File path -> contents
const FileTemplate = ({
packageName,
imports,
className,
extendClasses,
interfaceClassName,
methods,
}) => `/**
* 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}
@SuppressWarnings("deprecation")
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}) =>
`
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
${propCases}
}
`.trim();
const CommandsTemplate = ({commandCases}) =>
`
@Override
public void receiveCommand(T view, String commandName, ReadableArray args) {
switch (commandName) {
${commandCases}
}
}
`.trim();
function getJavaValueForProp(prop, componentName) {
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;
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;
throw new Error('Received invalid typeAnnotation');
}
}
function generatePropCasesString(component, componentName) {
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, index) {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return `args.getDouble(${index})`;
default:
typeAnnotation.name;
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;
throw new Error(`Receieved invalid type: ${typeAnnotation.type}`);
}
}
function getCommandArguments(command) {
return [
'view',
...command.typeAnnotation.params.map(getCommandArgJavaType),
].join(', ');
}
function generateCommandCasesString(component, componentName) {
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) {
const extendString = component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'View';
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
})
.join('');
return extendString;
}
function getDelegateImports(component) {
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, commandsString) {
return [
PropSetterTemplate({
propCases: propsString,
}),
commandsString != null
? CommandsTemplate({
commandCases: commandsString,
})
: '',
]
.join('\n\n ')
.trimRight();
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
// 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 = 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;
},
};

View File

@@ -0,0 +1,358 @@
/**
* 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}
@SuppressWarnings("deprecation")
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, 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,
includeGetDebugPropsImplementation?: 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;
},
};

View File

@@ -0,0 +1,247 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {
getImports,
getInterfaceJavaClassName,
toSafeJavaString,
} = require('./JavaHelpers');
// File path -> contents
const FileTemplate = ({
packageName,
imports,
className,
extendClasses,
methods,
}) => `/**
* 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}> extends ViewManagerWithGeneratedInterface {
${methods}
}
`;
function addNullable(imports) {
imports.add('import androidx.annotation.Nullable;');
}
function getJavaValueForProp(prop, imports) {
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 'ImageRequestPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'PointPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'EdgeInsetsPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'DimensionPrimitive':
addNullable(imports);
return '@Nullable YogaValue value';
default:
typeAnnotation.name;
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';
case 'MixedTypeAnnotation':
return 'Dynamic value';
default:
typeAnnotation;
throw new Error('Received invalid typeAnnotation');
}
}
function generatePropsString(component, 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;
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';
case 'ArrayTypeAnnotation':
return 'ReadableArray';
default:
typeAnnotation.type;
throw new Error('Receieved invalid typeAnnotation');
}
}
function getCommandArguments(command, componentName) {
return [
'T view',
...command.typeAnnotation.params.map(param => {
const commandArgJavaType = getCommandArgJavaType(param);
return `${commandArgJavaType} ${param.name}`;
}),
].join(', ');
}
function generateCommandsString(component, componentName) {
return component.commands
.map(command => {
const safeJavaName = toSafeJavaString(command.name, false);
return `void ${safeJavaName}(${getCommandArguments(command, componentName)});`;
})
.join('\n' + ' ');
}
function getClassExtendString(component) {
const extendString = component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'View';
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
})
.join('');
return extendString;
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
// 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;
},
};

View File

@@ -0,0 +1,297 @@
/**
* 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 {
getImports,
getInterfaceJavaClassName,
toSafeJavaString,
} = 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}> extends ViewManagerWithGeneratedInterface {
${methods}
}
`;
function addNullable(imports: Set<string>) {
imports.add('import androidx.annotation.Nullable;');
}
function getJavaValueForProp(
prop: NamedShape<PropTypeAnnotation>,
imports: Set<string>,
): 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 'ImageRequestPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'PointPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'EdgeInsetsPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'DimensionPrimitive':
addNullable(imports);
return '@Nullable YogaValue 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';
case 'MixedTypeAnnotation':
return 'Dynamic value';
default:
(typeAnnotation: empty);
throw new Error('Received invalid typeAnnotation');
}
}
function generatePropsString(component: ComponentShape, imports: Set<string>) {
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: NamedShape<CommandParamTypeAnnotation>) {
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';
case 'ArrayTypeAnnotation':
return 'ReadableArray';
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: 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;
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: 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 = 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;
},
};

View File

@@ -0,0 +1,120 @@
/**
* 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.
*
*
* @format
*/
'use strict';
function _defineProperty(e, r, t) {
return (
(r = _toPropertyKey(r)) in e
? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0,
})
: (e[r] = t),
e
);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, 'string');
return 'symbol' == typeof i ? i : i + '';
}
function _toPrimitive(t, r) {
if ('object' != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || 'default');
if ('object' != typeof i) return i;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return ('string' === r ? String : Number)(t);
}
const {capitalize} = require('../../Utils');
class PojoCollector {
constructor() {
_defineProperty(this, '_pojos', new Map());
}
process(namespace, pojoName, typeAnnotation) {
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertPojo(namespace, pojoName, typeAnnotation);
return {
type: 'PojoTypeAliasTypeAnnotation',
name: pojoName,
};
}
case 'ArrayTypeAnnotation': {
const arrayTypeAnnotation = typeAnnotation;
const elementType = arrayTypeAnnotation.elementType;
const pojoElementType = (() => {
switch (elementType.type) {
case 'ObjectTypeAnnotation': {
this._insertPojo(namespace, `${pojoName}Element`, elementType);
return {
type: 'PojoTypeAliasTypeAnnotation',
name: `${pojoName}Element`,
};
}
case 'ArrayTypeAnnotation': {
const {elementType: objectTypeAnnotation} = elementType;
this._insertPojo(
namespace,
`${pojoName}ElementElement`,
objectTypeAnnotation,
);
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'PojoTypeAliasTypeAnnotation',
name: `${pojoName}ElementElement`,
},
};
}
default: {
return elementType;
}
}
})();
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return {
type: 'ArrayTypeAnnotation',
elementType: pojoElementType,
};
}
default:
return typeAnnotation;
}
}
_insertPojo(namespace, pojoName, objectTypeAnnotation) {
const properties = objectTypeAnnotation.properties.map(property => {
const propertyPojoName = pojoName + capitalize(property.name);
return {
...property,
typeAnnotation: this.process(
namespace,
propertyPojoName,
property.typeAnnotation,
),
};
});
this._pojos.set(pojoName, {
name: pojoName,
namespace,
properties,
});
}
getAllPojos() {
return [...this._pojos.values()];
}
}
module.exports = PojoCollector;

View File

@@ -0,0 +1,190 @@
/**
* 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 {
BooleanTypeAnnotation,
ComponentArrayTypeAnnotation,
DoubleTypeAnnotation,
FloatTypeAnnotation,
Int32TypeAnnotation,
MixedTypeAnnotation,
NamedShape,
ObjectTypeAnnotation,
PropTypeAnnotation,
ReservedPropTypeAnnotation,
StringTypeAnnotation,
} from '../../../CodegenSchema';
const {capitalize} = require('../../Utils');
export type Pojo = {
name: string,
namespace: string,
properties: $ReadOnlyArray<PojoProperty>,
};
export type PojoProperty = NamedShape<PojoTypeAnnotation>;
export type PojoTypeAliasAnnotation = {
type: 'PojoTypeAliasTypeAnnotation',
name: string,
};
export type PojoTypeAnnotation =
| $ReadOnly<{
type: 'BooleanTypeAnnotation',
default: boolean | null,
}>
| $ReadOnly<{
type: 'StringTypeAnnotation',
default: string | null,
}>
| $ReadOnly<{
type: 'DoubleTypeAnnotation',
default: number,
}>
| $ReadOnly<{
type: 'FloatTypeAnnotation',
default: number | null,
}>
| $ReadOnly<{
type: 'Int32TypeAnnotation',
default: number,
}>
| $ReadOnly<{
type: 'StringEnumTypeAnnotation',
default: string,
options: $ReadOnlyArray<string>,
}>
| $ReadOnly<{
type: 'Int32EnumTypeAnnotation',
default: number,
options: $ReadOnlyArray<number>,
}>
| ReservedPropTypeAnnotation
| PojoTypeAliasAnnotation
| $ReadOnly<{
type: 'ArrayTypeAnnotation',
elementType:
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| MixedTypeAnnotation
| $ReadOnly<{
type: 'StringEnumTypeAnnotation',
default: string,
options: $ReadOnlyArray<string>,
}>
| PojoTypeAliasAnnotation
| ReservedPropTypeAnnotation
| $ReadOnly<{
type: 'ArrayTypeAnnotation',
elementType: PojoTypeAliasAnnotation,
}>,
}>
| MixedTypeAnnotation;
class PojoCollector {
_pojos: Map<string, Pojo> = new Map();
process(
namespace: string,
pojoName: string,
typeAnnotation: PropTypeAnnotation,
): PojoTypeAnnotation {
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertPojo(namespace, pojoName, typeAnnotation);
return {
type: 'PojoTypeAliasTypeAnnotation',
name: pojoName,
};
}
case 'ArrayTypeAnnotation': {
const arrayTypeAnnotation = typeAnnotation;
const elementType: ComponentArrayTypeAnnotation['elementType'] =
arrayTypeAnnotation.elementType;
const pojoElementType = (() => {
switch (elementType.type) {
case 'ObjectTypeAnnotation': {
this._insertPojo(namespace, `${pojoName}Element`, elementType);
return {
type: 'PojoTypeAliasTypeAnnotation',
name: `${pojoName}Element`,
};
}
case 'ArrayTypeAnnotation': {
const {elementType: objectTypeAnnotation} = elementType;
this._insertPojo(
namespace,
`${pojoName}ElementElement`,
objectTypeAnnotation,
);
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'PojoTypeAliasTypeAnnotation',
name: `${pojoName}ElementElement`,
},
};
}
default: {
return elementType;
}
}
})();
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return {
type: 'ArrayTypeAnnotation',
elementType: pojoElementType,
};
}
default:
return typeAnnotation;
}
}
_insertPojo(
namespace: string,
pojoName: string,
objectTypeAnnotation: ObjectTypeAnnotation<PropTypeAnnotation>,
) {
const properties = objectTypeAnnotation.properties.map(property => {
const propertyPojoName = pojoName + capitalize(property.name);
return {
...property,
typeAnnotation: this.process(
namespace,
propertyPojoName,
property.typeAnnotation,
),
};
});
this._pojos.set(pojoName, {
name: pojoName,
namespace,
properties,
});
}
getAllPojos(): $ReadOnlyArray<Pojo> {
return [...this._pojos.values()];
}
}
module.exports = PojoCollector;

View File

@@ -0,0 +1,64 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {capitalize} = require('../../Utils');
const PojoCollector = require('./PojoCollector');
const {serializePojo} = require('./serializePojo');
module.exports = {
generate(libraryName, schema, packageName) {
const pojoCollector = new PojoCollector();
const basePackageName = 'com.facebook.react.viewmanagers';
Object.keys(schema.modules).forEach(hasteModuleName => {
const module = schema.modules[hasteModuleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('android')
);
})
.forEach(componentName => {
const component = components[componentName];
if (component == null) {
return;
}
const {props} = component;
pojoCollector.process(
capitalize(hasteModuleName),
`${capitalize(componentName)}Props`,
{
type: 'ObjectTypeAnnotation',
properties: props,
},
);
});
});
const pojoDir = basePackageName.split('.').join('/');
return new Map(
pojoCollector.getAllPojos().map(pojo => {
return [
`java/${pojoDir}/${pojo.namespace}/${pojo.name}.java`,
serializePojo(pojo, basePackageName),
];
}),
);
},
};

View File

@@ -0,0 +1,80 @@
/**
* 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 {SchemaType} from '../../../CodegenSchema';
const {capitalize} = require('../../Utils');
const PojoCollector = require('./PojoCollector');
const {serializePojo} = require('./serializePojo');
type FilesOutput = Map<string, string>;
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
): FilesOutput {
const pojoCollector = new PojoCollector();
const basePackageName = 'com.facebook.react.viewmanagers';
Object.keys(schema.modules).forEach(hasteModuleName => {
const module = schema.modules[hasteModuleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('android')
);
})
.forEach(componentName => {
const component = components[componentName];
if (component == null) {
return;
}
const {props} = component;
pojoCollector.process(
capitalize(hasteModuleName),
`${capitalize(componentName)}Props`,
{
type: 'ObjectTypeAnnotation',
properties: props,
},
);
});
});
const pojoDir = basePackageName.split('.').join('/');
return new Map(
pojoCollector.getAllPojos().map(pojo => {
return [
`java/${pojoDir}/${pojo.namespace}/${pojo.name}.java`,
serializePojo(pojo, basePackageName),
];
}),
);
},
};

View File

@@ -0,0 +1,289 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {capitalize} = require('../../Utils');
function toJavaType(typeAnnotation, addImport) {
const importNullable = () => addImport('androidx.annotation.Nullable');
const importReadableMap = () =>
addImport('com.facebook.react.bridge.ReadableMap');
const importArrayList = () => addImport('java.util.ArrayList');
const importYogaValue = () => addImport('com.facebook.yoga.YogaValue');
const importDynamic = () => addImport('com.facebook.react.bridge.Dynamic');
switch (typeAnnotation.type) {
/**
* Primitives
*/
case 'BooleanTypeAnnotation': {
if (typeAnnotation.default === null) {
importNullable();
return '@Nullable Boolean';
} else {
return 'boolean';
}
}
case 'StringTypeAnnotation': {
importNullable();
return '@Nullable String';
}
case 'DoubleTypeAnnotation': {
return 'double';
}
case 'FloatTypeAnnotation': {
if (typeAnnotation.default === null) {
importNullable();
return '@Nullable Float';
} else {
return 'float';
}
}
case 'Int32TypeAnnotation': {
return 'int';
}
/**
* Enums
*/
// TODO: Make StringEnumTypeAnnotation type-safe?
case 'StringEnumTypeAnnotation':
importNullable();
return '@Nullable String';
// TODO: Make Int32EnumTypeAnnotation type-safe?
case 'Int32EnumTypeAnnotation':
importNullable();
return '@Nullable Integer';
/**
* Reserved types
*/
case 'ReservedPropTypeAnnotation': {
switch (typeAnnotation.name) {
case 'ColorPrimitive':
importNullable();
return '@Nullable Integer';
// TODO: Make ImageSourcePrimitive type-safe
case 'ImageSourcePrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make ImageRequestPrimitive type-safe
case 'ImageRequestPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make PointPrimitive type-safe
case 'PointPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make EdgeInsetsPrimitive type-safe
case 'EdgeInsetsPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
case 'DimensionPrimitive':
importNullable();
importYogaValue();
return '@Nullable YogaValue';
default:
typeAnnotation.name;
throw new Error(
`Received unknown ReservedPropTypeAnnotation ${typeAnnotation.name}`,
);
}
}
/**
* Other Pojo objects
*/
case 'PojoTypeAliasTypeAnnotation': {
return typeAnnotation.name;
}
/**
* Arrays
*/
case 'ArrayTypeAnnotation': {
const {elementType} = typeAnnotation;
const elementTypeString = (() => {
switch (elementType.type) {
/**
* Primitives
*/
case 'BooleanTypeAnnotation': {
return 'Boolean';
}
case 'StringTypeAnnotation': {
return 'String';
}
case 'DoubleTypeAnnotation': {
return 'Double';
}
case 'FloatTypeAnnotation': {
return 'Float';
}
case 'Int32TypeAnnotation': {
return 'Integer';
}
case 'MixedTypeAnnotation': {
importDynamic();
return 'Dynamic';
}
/**
* Enums
*/
// TODO: Make StringEnums type-safe in Pojos
case 'StringEnumTypeAnnotation': {
return 'String';
}
/**
* Other Pojo objects
*/
case 'PojoTypeAliasTypeAnnotation': {
return elementType.name;
}
/**
* Reserved types
*/
case 'ReservedPropTypeAnnotation': {
switch (elementType.name) {
case 'ColorPrimitive':
return 'Integer';
// TODO: Make ImageSourcePrimitive type-safe
case 'ImageSourcePrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make ImageRequestPrimitive type-safe
case 'ImageRequestPrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make PointPrimitive type-safe
case 'PointPrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make EdgeInsetsPrimitive type-safe
case 'EdgeInsetsPrimitive':
importReadableMap();
return 'ReadableMap';
case 'DimensionPrimitive':
importYogaValue();
return 'YogaValue';
default:
elementType.name;
throw new Error(
`Received unknown ReservedPropTypeAnnotation ${elementType.name}`,
);
}
}
// Arrays
case 'ArrayTypeAnnotation': {
const {elementType: pojoTypeAliasTypeAnnotation} = elementType;
importArrayList();
return `ArrayList<${pojoTypeAliasTypeAnnotation.name}>`;
}
default: {
elementType.type;
throw new Error(
`Unrecognized PojoTypeAnnotation Array element type annotation '${elementType.type}'`,
);
}
}
})();
importArrayList();
return `ArrayList<${elementTypeString}>`;
}
case 'MixedTypeAnnotation': {
importDynamic();
return 'Dynamic';
}
default: {
typeAnnotation.type;
throw new Error(
`Unrecognized PojoTypeAnnotation '${typeAnnotation.type}'`,
);
}
}
}
function toJavaMemberName(property) {
return `m${capitalize(property.name)}`;
}
function toJavaMemberDeclaration(property, addImport) {
const type = toJavaType(property.typeAnnotation, addImport);
const memberName = toJavaMemberName(property);
return `private ${type} ${memberName};`;
}
function toJavaGetter(property, addImport) {
const type = toJavaType(property.typeAnnotation, addImport);
const getterName = `get${capitalize(property.name)}`;
const memberName = toJavaMemberName(property);
addImport('com.facebook.proguard.annotations.DoNotStrip');
return `@DoNotStrip
public ${type} ${getterName}() {
return ${memberName};
}`;
}
function serializePojo(pojo, basePackageName) {
const importSet = new Set();
const addImport = $import => {
importSet.add($import);
};
addImport('com.facebook.proguard.annotations.DoNotStrip');
const indent = ' '.repeat(2);
const members = pojo.properties
.map(property => toJavaMemberDeclaration(property, addImport))
.map(member => `${indent}${member}`)
.join('\n');
const getters = pojo.properties
.map(property => toJavaGetter(property, addImport))
.map(getter =>
getter
.split('\n')
.map(line => `${indent}${line}`)
.join('\n'),
)
.join('\n');
const imports = [...importSet]
.map($import => `import ${$import};`)
.sort()
.join('\n');
return `/**
* 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.
*
* ${'@'}generated by codegen project: GeneratePropsJavaPojo.js
*/
package ${basePackageName}.${pojo.namespace};
${imports === '' ? '' : `\n${imports}\n`}
@DoNotStrip
public class ${pojo.name} {
${members}
${getters}
}
`;
}
module.exports = {
serializePojo,
};

View File

@@ -0,0 +1,319 @@
/**
* 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 {Pojo, PojoProperty, PojoTypeAnnotation} from './PojoCollector';
const {capitalize} = require('../../Utils');
type ImportCollector = ($import: string) => void;
function toJavaType(
typeAnnotation: PojoTypeAnnotation,
addImport: ImportCollector,
): string {
const importNullable = () => addImport('androidx.annotation.Nullable');
const importReadableMap = () =>
addImport('com.facebook.react.bridge.ReadableMap');
const importArrayList = () => addImport('java.util.ArrayList');
const importYogaValue = () => addImport('com.facebook.yoga.YogaValue');
const importDynamic = () => addImport('com.facebook.react.bridge.Dynamic');
switch (typeAnnotation.type) {
/**
* Primitives
*/
case 'BooleanTypeAnnotation': {
if (typeAnnotation.default === null) {
importNullable();
return '@Nullable Boolean';
} else {
return 'boolean';
}
}
case 'StringTypeAnnotation': {
importNullable();
return '@Nullable String';
}
case 'DoubleTypeAnnotation': {
return 'double';
}
case 'FloatTypeAnnotation': {
if (typeAnnotation.default === null) {
importNullable();
return '@Nullable Float';
} else {
return 'float';
}
}
case 'Int32TypeAnnotation': {
return 'int';
}
/**
* Enums
*/
// TODO: Make StringEnumTypeAnnotation type-safe?
case 'StringEnumTypeAnnotation':
importNullable();
return '@Nullable String';
// TODO: Make Int32EnumTypeAnnotation type-safe?
case 'Int32EnumTypeAnnotation':
importNullable();
return '@Nullable Integer';
/**
* Reserved types
*/
case 'ReservedPropTypeAnnotation': {
switch (typeAnnotation.name) {
case 'ColorPrimitive':
importNullable();
return '@Nullable Integer';
// TODO: Make ImageSourcePrimitive type-safe
case 'ImageSourcePrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make ImageRequestPrimitive type-safe
case 'ImageRequestPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make PointPrimitive type-safe
case 'PointPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make EdgeInsetsPrimitive type-safe
case 'EdgeInsetsPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
case 'DimensionPrimitive':
importNullable();
importYogaValue();
return '@Nullable YogaValue';
default:
(typeAnnotation.name: empty);
throw new Error(
`Received unknown ReservedPropTypeAnnotation ${typeAnnotation.name}`,
);
}
}
/**
* Other Pojo objects
*/
case 'PojoTypeAliasTypeAnnotation': {
return typeAnnotation.name;
}
/**
* Arrays
*/
case 'ArrayTypeAnnotation': {
const {elementType} = typeAnnotation;
const elementTypeString = (() => {
switch (elementType.type) {
/**
* Primitives
*/
case 'BooleanTypeAnnotation': {
return 'Boolean';
}
case 'StringTypeAnnotation': {
return 'String';
}
case 'DoubleTypeAnnotation': {
return 'Double';
}
case 'FloatTypeAnnotation': {
return 'Float';
}
case 'Int32TypeAnnotation': {
return 'Integer';
}
case 'MixedTypeAnnotation': {
importDynamic();
return 'Dynamic';
}
/**
* Enums
*/
// TODO: Make StringEnums type-safe in Pojos
case 'StringEnumTypeAnnotation': {
return 'String';
}
/**
* Other Pojo objects
*/
case 'PojoTypeAliasTypeAnnotation': {
return elementType.name;
}
/**
* Reserved types
*/
case 'ReservedPropTypeAnnotation': {
switch (elementType.name) {
case 'ColorPrimitive':
return 'Integer';
// TODO: Make ImageSourcePrimitive type-safe
case 'ImageSourcePrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make ImageRequestPrimitive type-safe
case 'ImageRequestPrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make PointPrimitive type-safe
case 'PointPrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make EdgeInsetsPrimitive type-safe
case 'EdgeInsetsPrimitive':
importReadableMap();
return 'ReadableMap';
case 'DimensionPrimitive':
importYogaValue();
return 'YogaValue';
default:
(elementType.name: empty);
throw new Error(
`Received unknown ReservedPropTypeAnnotation ${elementType.name}`,
);
}
}
// Arrays
case 'ArrayTypeAnnotation': {
const {elementType: pojoTypeAliasTypeAnnotation} = elementType;
importArrayList();
return `ArrayList<${pojoTypeAliasTypeAnnotation.name}>`;
}
default: {
(elementType.type: empty);
throw new Error(
`Unrecognized PojoTypeAnnotation Array element type annotation '${elementType.type}'`,
);
}
}
})();
importArrayList();
return `ArrayList<${elementTypeString}>`;
}
case 'MixedTypeAnnotation': {
importDynamic();
return 'Dynamic';
}
default: {
(typeAnnotation.type: empty);
throw new Error(
`Unrecognized PojoTypeAnnotation '${typeAnnotation.type}'`,
);
}
}
}
function toJavaMemberName(property: PojoProperty): string {
return `m${capitalize(property.name)}`;
}
function toJavaMemberDeclaration(
property: PojoProperty,
addImport: ImportCollector,
): string {
const type = toJavaType(property.typeAnnotation, addImport);
const memberName = toJavaMemberName(property);
return `private ${type} ${memberName};`;
}
function toJavaGetter(property: PojoProperty, addImport: ImportCollector) {
const type = toJavaType(property.typeAnnotation, addImport);
const getterName = `get${capitalize(property.name)}`;
const memberName = toJavaMemberName(property);
addImport('com.facebook.proguard.annotations.DoNotStrip');
return `@DoNotStrip
public ${type} ${getterName}() {
return ${memberName};
}`;
}
function serializePojo(pojo: Pojo, basePackageName: string): string {
const importSet: Set<string> = new Set();
const addImport = ($import: string) => {
importSet.add($import);
};
addImport('com.facebook.proguard.annotations.DoNotStrip');
const indent = ' '.repeat(2);
const members = pojo.properties
.map(property => toJavaMemberDeclaration(property, addImport))
.map(member => `${indent}${member}`)
.join('\n');
const getters = pojo.properties
.map(property => toJavaGetter(property, addImport))
.map(getter =>
getter
.split('\n')
.map(line => `${indent}${line}`)
.join('\n'),
)
.join('\n');
const imports = [...importSet]
.map($import => `import ${$import};`)
.sort()
.join('\n');
return `/**
* 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.
*
* ${'@'}generated by codegen project: GeneratePropsJavaPojo.js
*/
package ${basePackageName}.${pojo.namespace};
${imports === '' ? '' : `\n${imports}\n`}
@DoNotStrip
public class ${pojo.name} {
${members}
${getters}
}
`;
}
module.exports = {serializePojo};

View File

@@ -0,0 +1,84 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
const FileTemplate = ({componentNames, headerPrefix}) => `
/**
* 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: GenerateShadowNodeCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'ShadowNodes.h',
})}
namespace facebook::react {
${componentNames}
} // namespace facebook::react
`;
const ComponentTemplate = ({className}) =>
`
extern const char ${className}ComponentName[] = "${className}";
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const fileName = 'ShadowNodes.cpp';
const componentNames = 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)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
const replacedTemplate = ComponentTemplate({
className: componentName,
});
return replacedTemplate;
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
componentNames,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,97 @@
/**
* 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 {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
componentNames,
headerPrefix,
}: {
componentNames: string,
headerPrefix: 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: GenerateShadowNodeCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'ShadowNodes.h'})}
namespace facebook::react {
${componentNames}
} // namespace facebook::react
`;
const ComponentTemplate = ({className}: {className: string}) =>
`
extern const char ${className}ComponentName[] = "${className}";
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const fileName = 'ShadowNodes.cpp';
const componentNames = 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)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
const replacedTemplate = ComponentTemplate({
className: componentName,
});
return replacedTemplate;
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
componentNames,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,107 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
const FileTemplate = ({componentClasses, headerPrefix}) => `
/**
* 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: GenerateShadowNodeH.js
*/
#pragma once
${IncludeTemplate({
headerPrefix,
file: 'EventEmitters.h',
})}
${IncludeTemplate({
headerPrefix,
file: 'Props.h',
})}
${IncludeTemplate({
headerPrefix,
file: 'States.h',
})}
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <jsi/jsi.h>
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ComponentTemplate = ({className, eventEmitter}) =>
`
JSI_EXPORT extern const char ${className}ComponentName[];
/*
* \`ShadowNode\` for <${className}> component.
*/
using ${className}ShadowNode = ConcreteViewShadowNode<
${className}ComponentName,
${className}Props${eventEmitter},
${className}State>;
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const fileName = 'ShadowNodes.h';
const moduleResults = 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)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return;
}
const eventEmitter = `,\n ${componentName}EventEmitter`;
const replacedTemplate = ComponentTemplate({
className: componentName,
eventEmitter,
});
return replacedTemplate;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentClasses: moduleResults,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,122 @@
/**
* 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 {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
componentClasses,
headerPrefix,
}: {
componentClasses: string,
headerPrefix: 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: GenerateShadowNodeH.js
*/
#pragma once
${IncludeTemplate({headerPrefix, file: 'EventEmitters.h'})}
${IncludeTemplate({headerPrefix, file: 'Props.h'})}
${IncludeTemplate({headerPrefix, file: 'States.h'})}
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <jsi/jsi.h>
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
eventEmitter,
}: {
className: string,
eventEmitter: string,
}) =>
`
JSI_EXPORT extern const char ${className}ComponentName[];
/*
* \`ShadowNode\` for <${className}> component.
*/
using ${className}ShadowNode = ConcreteViewShadowNode<
${className}ComponentName,
${className}Props${eventEmitter},
${className}State>;
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const fileName = 'ShadowNodes.h';
const moduleResults = 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)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return;
}
const eventEmitter = `,\n ${componentName}EventEmitter`;
const replacedTemplate = ComponentTemplate({
className: componentName,
eventEmitter,
});
return replacedTemplate;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentClasses: moduleResults,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,81 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
const FileTemplate = ({stateClasses, headerPrefix}) => `
/**
* 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: GenerateStateCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'States.h',
})}
namespace facebook::react {
${stateClasses}
} // namespace facebook::react
`;
const StateTemplate = ({stateName}) => '';
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const fileName = 'States.cpp';
const stateClasses = 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)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return null;
}
return StateTemplate({
stateName: `${componentName}State`,
});
})
.filter(Boolean)
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
stateClasses,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,94 @@
/**
* 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 {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
stateClasses,
headerPrefix,
}: {
stateClasses: string,
headerPrefix: 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: GenerateStateCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'States.h'})}
namespace facebook::react {
${stateClasses}
} // namespace facebook::react
`;
const StateTemplate = ({stateName}: {stateName: string}) => '';
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const fileName = 'States.cpp';
const stateClasses = 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)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return null;
}
return StateTemplate({
stateName: `${componentName}State`,
});
})
.filter(Boolean)
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
stateClasses,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,84 @@
/**
* 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.
*
*
* @format
*/
'use strict';
// File path -> contents
const FileTemplate = ({libraryName, stateClasses}) =>
`
/**
* 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: GenerateStateH.js
*/
#pragma once
#include <react/renderer/core/StateData.h>
#ifdef RN_SERIALIZABLE_STATE
#include <folly/dynamic.h>
#endif
namespace facebook::react {
${stateClasses}
} // namespace facebook::react
`.trim();
const StateTemplate = ({stateName}) =>
`
using ${stateName}State = StateData;
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
includeGetDebugPropsImplementation = false,
) {
const fileName = 'States.h';
const stateClasses = 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)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return null;
}
return StateTemplate({
stateName: componentName,
});
})
.filter(Boolean)
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const template = FileTemplate({
libraryName,
stateClasses,
});
return new Map([[fileName, template]]);
},
};

View File

@@ -0,0 +1,97 @@
/**
* 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 {SchemaType} from '../../CodegenSchema';
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
libraryName,
stateClasses,
}: {
libraryName: string,
stateClasses: 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: GenerateStateH.js
*/
#pragma once
#include <react/renderer/core/StateData.h>
#ifdef RN_SERIALIZABLE_STATE
#include <folly/dynamic.h>
#endif
namespace facebook::react {
${stateClasses}
} // namespace facebook::react
`.trim();
const StateTemplate = ({stateName}: {stateName: string}) =>
`
using ${stateName}State = StateData;
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
includeGetDebugPropsImplementation?: boolean = false,
): FilesOutput {
const fileName = 'States.h';
const stateClasses = 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)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return null;
}
return StateTemplate({stateName: componentName});
})
.filter(Boolean)
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const template = FileTemplate({
libraryName,
stateClasses,
});
return new Map([[fileName, template]]);
},
};

View File

@@ -0,0 +1,172 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {toSafeCppString} = require('../Utils');
const {getImports} = require('./CppHelpers');
const FileTemplate = ({libraryName, imports, componentTests}) =>
`
/**
* 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: GenerateTests.js
* */
#include <gtest/gtest.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/components/${libraryName}/Props.h>
${imports}
using namespace facebook::react;
${componentTests}
`.trim();
const TestTemplate = ({componentName, testName, propName, propValue}) => `
TEST(${componentName}_${testName}, etc) {
RawPropsParser propParser{};
propParser.prepare<${componentName}>();
${componentName} sourceProps{};
RawProps rawProps(folly::dynamic::object("${propName}", ${propValue}));
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
rawProps.parse(propParser);
${componentName}(parserContext, sourceProps, rawProps);
}
`;
function getTestCasesForProp(propName, typeAnnotation) {
const cases = [];
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
typeAnnotation.options.forEach(option =>
cases.push({
propName,
testName: `${propName}_${toSafeCppString(option)}`,
propValue: option,
}),
);
} else if (typeAnnotation.type === 'StringTypeAnnotation') {
cases.push({
propName,
propValue:
typeAnnotation.default != null && typeAnnotation.default !== ''
? typeAnnotation.default
: 'foo',
});
} else if (typeAnnotation.type === 'BooleanTypeAnnotation') {
cases.push({
propName: propName,
propValue: typeAnnotation.default != null ? typeAnnotation.default : true,
});
// $FlowFixMe[incompatible-type]
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/4oq3zi07. */
} else if (typeAnnotation.type === 'IntegerTypeAnnotation') {
cases.push({
propName,
propValue: typeAnnotation.default || 10,
});
} else if (typeAnnotation.type === 'FloatTypeAnnotation') {
cases.push({
propName,
propValue: typeAnnotation.default != null ? typeAnnotation.default : 0.1,
});
} else if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
if (typeAnnotation.name === 'ColorPrimitive') {
cases.push({
propName,
propValue: 1,
});
} else if (typeAnnotation.name === 'PointPrimitive') {
cases.push({
propName,
propValue: 'folly::dynamic::object("x", 1)("y", 1)',
raw: true,
});
} else if (typeAnnotation.name === 'ImageSourcePrimitive') {
cases.push({
propName,
propValue: 'folly::dynamic::object("url", "testurl")',
raw: true,
});
}
}
return cases;
}
function generateTestsString(name, component) {
function createTest({testName, propName, propValue, raw = false}) {
const value =
!raw && typeof propValue === 'string' ? `"${propValue}"` : propValue;
return TestTemplate({
componentName: name,
testName: testName != null ? testName : propName,
propName,
propValue: String(value),
});
}
const testCases = component.props.reduce((cases, prop) => {
return cases.concat(getTestCasesForProp(prop.name, prop.typeAnnotation));
}, []);
const baseTest = {
testName: 'DoesNotDie',
propName: 'xx_invalid_xx',
propValue: 'xx_invalid_xx',
};
return [baseTest, ...testCases].map(createTest).join('');
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'Tests.cpp';
const allImports = new Set([
'#include <react/renderer/core/propsConversions.h>',
'#include <react/renderer/core/RawProps.h>',
'#include <react/renderer/core/RawPropsParser.h>',
]);
const componentTests = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
const name = `${componentName}Props`;
const imports = getImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
return generateTestsString(name, component);
})
.join('');
})
.filter(Boolean)
.join('');
const imports = Array.from(allImports).sort().join('\n').trim();
const replacedTemplate = FileTemplate({
imports,
libraryName,
componentTests,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,223 @@
/**
* 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 {ComponentShape, PropTypeAnnotation} from '../../CodegenSchema';
import type {SchemaType} from '../../CodegenSchema';
const {toSafeCppString} = require('../Utils');
const {getImports} = require('./CppHelpers');
type FilesOutput = Map<string, string>;
type PropValueType = string | number | boolean;
type TestCase = $ReadOnly<{
propName: string,
propValue: ?PropValueType,
testName?: string,
raw?: boolean,
}>;
const FileTemplate = ({
libraryName,
imports,
componentTests,
}: {
libraryName: string,
imports: string,
componentTests: 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: GenerateTests.js
* */
#include <gtest/gtest.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/components/${libraryName}/Props.h>
${imports}
using namespace facebook::react;
${componentTests}
`.trim();
const TestTemplate = ({
componentName,
testName,
propName,
propValue,
}: {
componentName: string,
testName: string,
propName: string,
propValue: string,
}) => `
TEST(${componentName}_${testName}, etc) {
RawPropsParser propParser{};
propParser.prepare<${componentName}>();
${componentName} sourceProps{};
RawProps rawProps(folly::dynamic::object("${propName}", ${propValue}));
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
rawProps.parse(propParser);
${componentName}(parserContext, sourceProps, rawProps);
}
`;
function getTestCasesForProp(
propName: string,
typeAnnotation: PropTypeAnnotation,
): Array<TestCase> {
const cases: Array<TestCase> = [];
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
typeAnnotation.options.forEach(option =>
cases.push({
propName,
testName: `${propName}_${toSafeCppString(option)}`,
propValue: option,
}),
);
} else if (typeAnnotation.type === 'StringTypeAnnotation') {
cases.push({
propName,
propValue:
typeAnnotation.default != null && typeAnnotation.default !== ''
? typeAnnotation.default
: 'foo',
});
} else if (typeAnnotation.type === 'BooleanTypeAnnotation') {
cases.push({
propName: propName,
propValue: typeAnnotation.default != null ? typeAnnotation.default : true,
});
// $FlowFixMe[incompatible-type]
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/4oq3zi07. */
} else if (typeAnnotation.type === 'IntegerTypeAnnotation') {
cases.push({
propName,
propValue: typeAnnotation.default || 10,
});
} else if (typeAnnotation.type === 'FloatTypeAnnotation') {
cases.push({
propName,
propValue: typeAnnotation.default != null ? typeAnnotation.default : 0.1,
});
} else if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
if (typeAnnotation.name === 'ColorPrimitive') {
cases.push({
propName,
propValue: 1,
});
} else if (typeAnnotation.name === 'PointPrimitive') {
cases.push({
propName,
propValue: 'folly::dynamic::object("x", 1)("y", 1)',
raw: true,
});
} else if (typeAnnotation.name === 'ImageSourcePrimitive') {
cases.push({
propName,
propValue: 'folly::dynamic::object("url", "testurl")',
raw: true,
});
}
}
return cases;
}
function generateTestsString(name: string, component: ComponentShape) {
function createTest({testName, propName, propValue, raw = false}: TestCase) {
const value =
!raw && typeof propValue === 'string' ? `"${propValue}"` : propValue;
return TestTemplate({
componentName: name,
testName: testName != null ? testName : propName,
propName,
propValue: String(value),
});
}
const testCases = component.props.reduce((cases: Array<TestCase>, prop) => {
return cases.concat(getTestCasesForProp(prop.name, prop.typeAnnotation));
}, []);
const baseTest = {
testName: 'DoesNotDie',
propName: 'xx_invalid_xx',
propValue: 'xx_invalid_xx',
};
return [baseTest, ...testCases].map(createTest).join('');
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'Tests.cpp';
const allImports = new Set([
'#include <react/renderer/core/propsConversions.h>',
'#include <react/renderer/core/RawProps.h>',
'#include <react/renderer/core/RawPropsParser.h>',
]);
const componentTests = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
const name = `${componentName}Props`;
const imports = getImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
return generateTestsString(name, component);
})
.join('');
})
.filter(Boolean)
.join('');
const imports = Array.from(allImports).sort().join('\n').trim();
const replacedTemplate = FileTemplate({
imports,
libraryName,
componentTests,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,108 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {
generateSupportedApplePlatformsMacro,
} = require('./ComponentsProviderUtils');
// File path -> contents
const FileTemplate = ({lookupFuncs}) => `
/*
* 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 GenerateRCTThirdPartyFabricComponentsProviderH
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#import <React/RCTComponentViewProtocol.h>
#ifdef __cplusplus
extern "C" {
#endif
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name);
#if RCT_NEW_ARCH_ENABLED
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupFuncs}
#endif
#endif
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
`;
const LookupFuncTemplate = ({className, libraryName}) =>
`
Class<RCTComponentViewProtocol> ${className}Cls(void) __attribute__((used)); // ${libraryName}
`.trim();
module.exports = {
generate(schemas, supportedApplePlatforms) {
const fileName = 'RCTThirdPartyFabricComponentsProvider.h';
const lookupFuncs = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
const librarySupportedApplePlatforms =
supportedApplePlatforms === null || supportedApplePlatforms === void 0
? void 0
: supportedApplePlatforms[libraryName];
const generatedLookup = 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 LookupFuncTemplate({
className: componentName,
libraryName,
});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
return generateSupportedApplePlatformsMacro(
generatedLookup,
librarySupportedApplePlatforms,
);
})
.join('\n');
const replacedTemplate = FileTemplate({
lookupFuncs,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,126 @@
/**
* 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 {SchemaType} from '../../CodegenSchema';
const {
generateSupportedApplePlatformsMacro,
} = require('./ComponentsProviderUtils');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({lookupFuncs}: {lookupFuncs: 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 GenerateRCTThirdPartyFabricComponentsProviderH
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#import <React/RCTComponentViewProtocol.h>
#ifdef __cplusplus
extern "C" {
#endif
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name);
#if RCT_NEW_ARCH_ENABLED
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupFuncs}
#endif
#endif
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
`;
const LookupFuncTemplate = ({
className,
libraryName,
}: {
className: string,
libraryName: string,
}) =>
`
Class<RCTComponentViewProtocol> ${className}Cls(void) __attribute__((used)); // ${libraryName}
`.trim();
module.exports = {
generate(
schemas: {[string]: SchemaType},
supportedApplePlatforms?: {[string]: {[string]: boolean}},
): FilesOutput {
const fileName = 'RCTThirdPartyFabricComponentsProvider.h';
const lookupFuncs = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
const librarySupportedApplePlatforms =
supportedApplePlatforms?.[libraryName];
const generatedLookup = 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 LookupFuncTemplate({
className: componentName,
libraryName,
});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
return generateSupportedApplePlatformsMacro(
generatedLookup,
librarySupportedApplePlatforms,
);
})
.join('\n');
const replacedTemplate = FileTemplate({
lookupFuncs,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,106 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {
generateSupportedApplePlatformsMacro,
} = require('./ComponentsProviderUtils');
// File path -> contents
const FileTemplate = ({lookupMap}) => `
/**
* 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 GenerateRCTThirdPartyFabricComponentsProviderCpp
*/
// OSS-compatibility layer
#import "RCTThirdPartyFabricComponentsProvider.h"
#import <string>
#import <unordered_map>
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name) {
static std::unordered_map<std::string, Class (*)(void)> sFabricComponentsClassMap = {
#if RCT_NEW_ARCH_ENABLED
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupMap}
#endif
#endif
};
auto p = sFabricComponentsClassMap.find(name);
if (p != sFabricComponentsClassMap.end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
`;
const LookupMapTemplate = ({className, libraryName}) => `
{"${className}", ${className}Cls}, // ${libraryName}`;
module.exports = {
generate(schemas, supportedApplePlatforms) {
const fileName = 'RCTThirdPartyFabricComponentsProvider.mm';
const lookupMap = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
const librarySupportedApplePlatforms =
supportedApplePlatforms === null || supportedApplePlatforms === void 0
? void 0
: supportedApplePlatforms[libraryName];
const generatedLookup = 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;
}
const componentTemplates = Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
const replacedTemplate = LookupMapTemplate({
className: componentName,
libraryName,
});
return replacedTemplate;
});
return componentTemplates.length > 0 ? componentTemplates : null;
})
.filter(Boolean)
.join('\n');
return generateSupportedApplePlatformsMacro(
generatedLookup,
librarySupportedApplePlatforms,
);
})
.join('\n');
const replacedTemplate = FileTemplate({
lookupMap,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,125 @@
/**
* 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 {SchemaType} from '../../CodegenSchema';
const {
generateSupportedApplePlatformsMacro,
} = require('./ComponentsProviderUtils');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({lookupMap}: {lookupMap: 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 GenerateRCTThirdPartyFabricComponentsProviderCpp
*/
// OSS-compatibility layer
#import "RCTThirdPartyFabricComponentsProvider.h"
#import <string>
#import <unordered_map>
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name) {
static std::unordered_map<std::string, Class (*)(void)> sFabricComponentsClassMap = {
#if RCT_NEW_ARCH_ENABLED
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupMap}
#endif
#endif
};
auto p = sFabricComponentsClassMap.find(name);
if (p != sFabricComponentsClassMap.end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
`;
const LookupMapTemplate = ({
className,
libraryName,
}: {
className: string,
libraryName: string,
}) => `
{"${className}", ${className}Cls}, // ${libraryName}`;
module.exports = {
generate(
schemas: {[string]: SchemaType},
supportedApplePlatforms?: {[string]: {[string]: boolean}},
): FilesOutput {
const fileName = 'RCTThirdPartyFabricComponentsProvider.mm';
const lookupMap = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
const librarySupportedApplePlatforms =
supportedApplePlatforms?.[libraryName];
const generatedLookup = 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;
}
const componentTemplates = Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
const replacedTemplate = LookupMapTemplate({
className: componentName,
libraryName,
});
return replacedTemplate;
});
return componentTemplates.length > 0 ? componentTemplates : null;
})
.filter(Boolean)
.join('\n');
return generateSupportedApplePlatformsMacro(
generatedLookup,
librarySupportedApplePlatforms,
);
})
.join('\n');
const replacedTemplate = FileTemplate({lookupMap});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,408 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const core = require('@babel/core');
const t = core.types;
// File path -> contents
const FileTemplate = ({imports, componentConfig}) => `
/**
* 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.
*
* @flow
*
* ${'@'}generated by codegen project: GenerateViewConfigJs.js
*/
'use strict';
${imports}
${componentConfig}
`;
// We use this to add to a set. Need to make sure we aren't importing
// this multiple times.
const UIMANAGER_IMPORT = 'const {UIManager} = require("react-native")';
function expression(input) {
return core.template.expression(input)();
}
function getReactDiffProcessValue(typeAnnotation) {
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'StringEnumTypeAnnotation':
case 'Int32EnumTypeAnnotation':
case 'MixedTypeAnnotation':
return t.booleanLiteral(true);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return expression(
"{ process: require('react-native/Libraries/StyleSheet/processColor').default }",
);
case 'ImageSourcePrimitive':
return expression(
"{ process: ((req) => 'default' in req ? req.default : req)(require('react-native/Libraries/Image/resolveAssetSource')) }",
);
case 'ImageRequestPrimitive':
throw new Error('ImageRequest should not be used in props');
case 'PointPrimitive':
return expression(
"{ diff: ((req) => 'default' in req ? req.default : req)(require('react-native/Libraries/Utilities/differ/pointsDiffer')) }",
);
case 'EdgeInsetsPrimitive':
return expression(
"{ diff: ((req) => 'default' in req ? req.default : req)(require('react-native/Libraries/Utilities/differ/insetsDiffer')) }",
);
case 'DimensionPrimitive':
return t.booleanLiteral(true);
default:
typeAnnotation.name;
throw new Error(
`Received unknown native typeAnnotation: "${typeAnnotation.name}"`,
);
}
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation') {
switch (typeAnnotation.elementType.name) {
case 'ColorPrimitive':
return expression(
"{ process: ((req) => 'default' in req ? req.default : req)(require('react-native/Libraries/StyleSheet/processColorArray')) }",
);
case 'ImageSourcePrimitive':
case 'PointPrimitive':
case 'EdgeInsetsPrimitive':
case 'DimensionPrimitive':
return t.booleanLiteral(true);
default:
throw new Error(
`Received unknown array native typeAnnotation: "${typeAnnotation.elementType.name}"`,
);
}
}
return t.booleanLiteral(true);
default:
typeAnnotation;
throw new Error(
`Received unknown typeAnnotation: "${typeAnnotation.type}"`,
);
}
}
const ComponentTemplate = ({
componentName,
paperComponentName,
paperComponentNameDeprecated,
}) => {
const nativeComponentName =
paperComponentName !== null && paperComponentName !== void 0
? paperComponentName
: componentName;
return `
let nativeComponentName = '${nativeComponentName}';
${
paperComponentNameDeprecated != null
? DeprecatedComponentNameCheckTemplate({
componentName,
paperComponentNameDeprecated,
})
: ''
}
export const __INTERNAL_VIEW_CONFIG = %%VIEW_CONFIG%%;
export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);
`.trim();
};
// Check whether the native component exists in the app binary.
// Old getViewManagerConfig() checks for the existance of the native Paper view manager. Not available in Bridgeless.
// New hasViewManagerConfig() queries Fabrics native component registry directly.
const DeprecatedComponentNameCheckTemplate = ({
componentName,
paperComponentNameDeprecated,
}) =>
`
if (UIManager.hasViewManagerConfig('${componentName}')) {
nativeComponentName = '${componentName}';
} else if (UIManager.hasViewManagerConfig('${paperComponentNameDeprecated}')) {
nativeComponentName = '${paperComponentNameDeprecated}';
} else {
throw new Error('Failed to find native component for either "${componentName}" or "${paperComponentNameDeprecated}"');
}
`.trim();
// Replicates the behavior of RCTNormalizeInputEventName in RCTEventDispatcher.m
function normalizeInputEventName(name) {
if (name.startsWith('on')) {
return name.replace(/^on/, 'top');
} else if (!name.startsWith('top')) {
return `top${name[0].toUpperCase()}${name.slice(1)}`;
}
return name;
}
// Replicates the behavior of viewConfig in RCTComponentData.m
function getValidAttributesForEvents(events, imports) {
imports.add(
"const {ConditionallyIgnoredEventHandlers} = require('react-native/Libraries/NativeComponent/ViewConfigIgnore');",
);
const validAttributes = t.objectExpression(
events.map(eventType => {
return t.objectProperty(
t.identifier(eventType.name),
t.booleanLiteral(true),
);
}),
);
return t.callExpression(t.identifier('ConditionallyIgnoredEventHandlers'), [
validAttributes,
]);
}
function generateBubblingEventInfo(event, nameOveride) {
return t.objectProperty(
t.identifier(normalizeInputEventName(nameOveride || event.name)),
t.objectExpression([
t.objectProperty(
t.identifier('phasedRegistrationNames'),
t.objectExpression([
t.objectProperty(
t.identifier('captured'),
t.stringLiteral(`${event.name}Capture`),
),
t.objectProperty(
t.identifier('bubbled'),
t.stringLiteral(event.name),
),
]),
),
]),
);
}
function generateDirectEventInfo(event, nameOveride) {
return t.objectProperty(
t.identifier(normalizeInputEventName(nameOveride || event.name)),
t.objectExpression([
t.objectProperty(
t.identifier('registrationName'),
t.stringLiteral(event.name),
),
]),
);
}
function buildViewConfig(schema, componentName, component, imports) {
const componentProps = component.props;
const componentEvents = component.events;
component.extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
"const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry');",
);
return;
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
});
const validAttributes = t.objectExpression([
...componentProps.map(schemaProp => {
return t.objectProperty(
t.identifier(schemaProp.name),
getReactDiffProcessValue(schemaProp.typeAnnotation),
);
}),
...(componentEvents.length > 0
? [t.spreadElement(getValidAttributesForEvents(componentEvents, imports))]
: []),
]);
const bubblingEventNames = component.events
.filter(event => event.bubblingType === 'bubble')
.reduce((bubblingEvents, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
bubblingEvents.push(
generateBubblingEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
bubblingEvents.push(generateBubblingEventInfo(event));
}
return bubblingEvents;
}, []);
const directEventNames = component.events
.filter(event => event.bubblingType === 'direct')
.reduce((directEvents, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
directEvents.push(
generateDirectEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
directEvents.push(generateDirectEventInfo(event));
}
return directEvents;
}, []);
const properties = [
t.objectProperty(
t.identifier('uiViewClassName'),
t.stringLiteral(componentName),
),
];
if (bubblingEventNames.length > 0) {
properties.push(
t.objectProperty(
t.identifier('bubblingEventTypes'),
t.objectExpression(bubblingEventNames),
),
);
}
if (directEventNames.length > 0) {
properties.push(
t.objectProperty(
t.identifier('directEventTypes'),
t.objectExpression(directEventNames),
),
);
}
properties.push(
t.objectProperty(t.identifier('validAttributes'), validAttributes),
);
return t.objectExpression(properties);
}
function buildCommands(schema, componentName, component, imports) {
const commands = component.commands;
if (commands.length === 0) {
return null;
}
imports.add(
'const {dispatchCommand} = require("react-native/Libraries/ReactNative/RendererProxy");',
);
const commandsObject = t.objectExpression(
commands.map(command => {
const commandName = command.name;
const params = command.typeAnnotation.params;
const dispatchCommandCall = t.callExpression(
t.identifier('dispatchCommand'),
[
t.identifier('ref'),
t.stringLiteral(commandName),
t.arrayExpression(params.map(param => t.identifier(param.name))),
],
);
return t.objectMethod(
'method',
t.identifier(commandName),
[t.identifier('ref'), ...params.map(param => t.identifier(param.name))],
t.blockStatement([t.expressionStatement(dispatchCommandCall)]),
);
}),
);
return t.exportNamedDeclaration(
t.variableDeclaration('const', [
t.variableDeclarator(t.identifier('Commands'), commandsObject),
]),
);
}
module.exports = {
generate(libraryName, schema) {
try {
const fileName = `${libraryName}NativeViewConfig.js`;
const imports = new Set();
const moduleResults = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
return Object.keys(components)
.map(componentName => {
var _component$paperCompo;
const component = components[componentName];
if (component.paperComponentNameDeprecated) {
imports.add(UIMANAGER_IMPORT);
}
const replacedTemplate = ComponentTemplate({
componentName,
paperComponentName: component.paperComponentName,
paperComponentNameDeprecated:
component.paperComponentNameDeprecated,
});
const paperComponentName =
(_component$paperCompo = component.paperComponentName) !==
null && _component$paperCompo !== void 0
? _component$paperCompo
: componentName;
const replacedSourceRoot = core.template.program(
replacedTemplate,
)({
VIEW_CONFIG: buildViewConfig(
schema,
paperComponentName,
component,
imports,
),
});
const commandsExport = buildCommands(
schema,
paperComponentName,
component,
imports,
);
if (commandsExport) {
replacedSourceRoot.body.push(commandsExport);
}
const replacedSource = core.transformFromAstSync(
replacedSourceRoot,
undefined,
{
babelrc: false,
browserslistConfigFile: false,
configFile: false,
},
);
return replacedSource.code;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentConfig: moduleResults,
imports: Array.from(imports).sort().join('\n'),
});
return new Map([[fileName, replacedTemplate]]);
} catch (error) {
console.error(`\nError parsing schema for ${libraryName}\n`);
console.error(JSON.stringify(schema));
throw error;
}
},
};

View File

@@ -0,0 +1,480 @@
/**
* 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
* @format
*/
'use strict';
import type {
ComponentShape,
EventTypeShape,
PropTypeAnnotation,
} from '../../CodegenSchema';
import type {SchemaType} from '../../CodegenSchema';
const core = require('@babel/core');
const t = core.types;
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
imports,
componentConfig,
}: {
imports: string,
componentConfig: 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.
*
* @flow
*
* ${'@'}generated by codegen project: GenerateViewConfigJs.js
*/
'use strict';
${imports}
${componentConfig}
`;
// We use this to add to a set. Need to make sure we aren't importing
// this multiple times.
const UIMANAGER_IMPORT = 'const {UIManager} = require("react-native")';
function expression(input: string) {
return core.template.expression(input)();
}
function getReactDiffProcessValue(typeAnnotation: PropTypeAnnotation) {
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'StringEnumTypeAnnotation':
case 'Int32EnumTypeAnnotation':
case 'MixedTypeAnnotation':
return t.booleanLiteral(true);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return expression(
"{ process: require('react-native/Libraries/StyleSheet/processColor').default }",
);
case 'ImageSourcePrimitive':
return expression(
"{ process: ((req) => 'default' in req ? req.default : req)(require('react-native/Libraries/Image/resolveAssetSource')) }",
);
case 'ImageRequestPrimitive':
throw new Error('ImageRequest should not be used in props');
case 'PointPrimitive':
return expression(
"{ diff: ((req) => 'default' in req ? req.default : req)(require('react-native/Libraries/Utilities/differ/pointsDiffer')) }",
);
case 'EdgeInsetsPrimitive':
return expression(
"{ diff: ((req) => 'default' in req ? req.default : req)(require('react-native/Libraries/Utilities/differ/insetsDiffer')) }",
);
case 'DimensionPrimitive':
return t.booleanLiteral(true);
default:
(typeAnnotation.name: empty);
throw new Error(
`Received unknown native typeAnnotation: "${typeAnnotation.name}"`,
);
}
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation') {
switch (typeAnnotation.elementType.name) {
case 'ColorPrimitive':
return expression(
"{ process: ((req) => 'default' in req ? req.default : req)(require('react-native/Libraries/StyleSheet/processColorArray')) }",
);
case 'ImageSourcePrimitive':
case 'PointPrimitive':
case 'EdgeInsetsPrimitive':
case 'DimensionPrimitive':
return t.booleanLiteral(true);
default:
throw new Error(
`Received unknown array native typeAnnotation: "${typeAnnotation.elementType.name}"`,
);
}
}
return t.booleanLiteral(true);
default:
(typeAnnotation: empty);
throw new Error(
`Received unknown typeAnnotation: "${typeAnnotation.type}"`,
);
}
}
const ComponentTemplate = ({
componentName,
paperComponentName,
paperComponentNameDeprecated,
}: {
componentName: string,
paperComponentName: ?string,
paperComponentNameDeprecated: ?string,
}) => {
const nativeComponentName = paperComponentName ?? componentName;
return `
let nativeComponentName = '${nativeComponentName}';
${
paperComponentNameDeprecated != null
? DeprecatedComponentNameCheckTemplate({
componentName,
paperComponentNameDeprecated,
})
: ''
}
export const __INTERNAL_VIEW_CONFIG = %%VIEW_CONFIG%%;
export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);
`.trim();
};
// Check whether the native component exists in the app binary.
// Old getViewManagerConfig() checks for the existance of the native Paper view manager. Not available in Bridgeless.
// New hasViewManagerConfig() queries Fabrics native component registry directly.
const DeprecatedComponentNameCheckTemplate = ({
componentName,
paperComponentNameDeprecated,
}: {
componentName: string,
paperComponentNameDeprecated: string,
}) =>
`
if (UIManager.hasViewManagerConfig('${componentName}')) {
nativeComponentName = '${componentName}';
} else if (UIManager.hasViewManagerConfig('${paperComponentNameDeprecated}')) {
nativeComponentName = '${paperComponentNameDeprecated}';
} else {
throw new Error('Failed to find native component for either "${componentName}" or "${paperComponentNameDeprecated}"');
}
`.trim();
// Replicates the behavior of RCTNormalizeInputEventName in RCTEventDispatcher.m
function normalizeInputEventName(name: string) {
if (name.startsWith('on')) {
return name.replace(/^on/, 'top');
} else if (!name.startsWith('top')) {
return `top${name[0].toUpperCase()}${name.slice(1)}`;
}
return name;
}
// Replicates the behavior of viewConfig in RCTComponentData.m
function getValidAttributesForEvents(
events: $ReadOnlyArray<EventTypeShape>,
imports: Set<string>,
) {
imports.add(
"const {ConditionallyIgnoredEventHandlers} = require('react-native/Libraries/NativeComponent/ViewConfigIgnore');",
);
const validAttributes = t.objectExpression(
events.map(eventType => {
return t.objectProperty(
t.identifier(eventType.name),
t.booleanLiteral(true),
);
}),
);
return t.callExpression(t.identifier('ConditionallyIgnoredEventHandlers'), [
validAttributes,
]);
}
function generateBubblingEventInfo(
event: EventTypeShape,
nameOveride: void | string,
) {
return t.objectProperty(
t.identifier(normalizeInputEventName(nameOveride || event.name)),
t.objectExpression([
t.objectProperty(
t.identifier('phasedRegistrationNames'),
t.objectExpression([
t.objectProperty(
t.identifier('captured'),
t.stringLiteral(`${event.name}Capture`),
),
t.objectProperty(
t.identifier('bubbled'),
t.stringLiteral(event.name),
),
]),
),
]),
);
}
function generateDirectEventInfo(
event: EventTypeShape,
nameOveride: void | string,
) {
return t.objectProperty(
t.identifier(normalizeInputEventName(nameOveride || event.name)),
t.objectExpression([
t.objectProperty(
t.identifier('registrationName'),
t.stringLiteral(event.name),
),
]),
);
}
function buildViewConfig(
schema: SchemaType,
componentName: string,
component: ComponentShape,
imports: Set<string>,
) {
const componentProps = component.props;
const componentEvents = component.events;
component.extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
"const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry');",
);
return;
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
});
const validAttributes = t.objectExpression([
...componentProps.map(schemaProp => {
return t.objectProperty(
t.identifier(schemaProp.name),
getReactDiffProcessValue(schemaProp.typeAnnotation),
);
}),
...(componentEvents.length > 0
? [t.spreadElement(getValidAttributesForEvents(componentEvents, imports))]
: []),
]);
const bubblingEventNames = component.events
.filter(event => event.bubblingType === 'bubble')
.reduce((bubblingEvents: Array<any>, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
bubblingEvents.push(
generateBubblingEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
bubblingEvents.push(generateBubblingEventInfo(event));
}
return bubblingEvents;
}, []);
const directEventNames = component.events
.filter(event => event.bubblingType === 'direct')
.reduce((directEvents: Array<any>, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
directEvents.push(
generateDirectEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
directEvents.push(generateDirectEventInfo(event));
}
return directEvents;
}, []);
const properties: Array<
BabelNodeObjectMethod | BabelNodeObjectProperty | BabelNodeSpreadElement,
> = [
t.objectProperty(
t.identifier('uiViewClassName'),
t.stringLiteral(componentName),
),
];
if (bubblingEventNames.length > 0) {
properties.push(
t.objectProperty(
t.identifier('bubblingEventTypes'),
t.objectExpression(bubblingEventNames),
),
);
}
if (directEventNames.length > 0) {
properties.push(
t.objectProperty(
t.identifier('directEventTypes'),
t.objectExpression(directEventNames),
),
);
}
properties.push(
t.objectProperty(t.identifier('validAttributes'), validAttributes),
);
return t.objectExpression(properties);
}
function buildCommands(
schema: SchemaType,
componentName: string,
component: ComponentShape,
imports: Set<string>,
) {
const commands = component.commands;
if (commands.length === 0) {
return null;
}
imports.add(
'const {dispatchCommand} = require("react-native/Libraries/ReactNative/RendererProxy");',
);
const commandsObject = t.objectExpression(
commands.map(command => {
const commandName = command.name;
const params = command.typeAnnotation.params;
const dispatchCommandCall = t.callExpression(
t.identifier('dispatchCommand'),
[
t.identifier('ref'),
t.stringLiteral(commandName),
t.arrayExpression(params.map(param => t.identifier(param.name))),
],
);
return t.objectMethod(
'method',
t.identifier(commandName),
[t.identifier('ref'), ...params.map(param => t.identifier(param.name))],
t.blockStatement([t.expressionStatement(dispatchCommandCall)]),
);
}),
);
return t.exportNamedDeclaration(
t.variableDeclaration('const', [
t.variableDeclarator(t.identifier('Commands'), commandsObject),
]),
);
}
module.exports = {
generate(libraryName: string, schema: SchemaType): FilesOutput {
try {
const fileName = `${libraryName}NativeViewConfig.js`;
const imports: Set<string> = new Set();
const moduleResults = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
return Object.keys(components)
.map((componentName: string) => {
const component = components[componentName];
if (component.paperComponentNameDeprecated) {
imports.add(UIMANAGER_IMPORT);
}
const replacedTemplate = ComponentTemplate({
componentName,
paperComponentName: component.paperComponentName,
paperComponentNameDeprecated:
component.paperComponentNameDeprecated,
});
const paperComponentName =
component.paperComponentName ?? componentName;
const replacedSourceRoot = core.template.program(
replacedTemplate,
)({
VIEW_CONFIG: buildViewConfig(
schema,
paperComponentName,
component,
imports,
),
});
const commandsExport = buildCommands(
schema,
paperComponentName,
component,
imports,
);
if (commandsExport) {
replacedSourceRoot.body.push(commandsExport);
}
const replacedSource = core.transformFromAstSync(
replacedSourceRoot,
undefined,
{
babelrc: false,
browserslistConfigFile: false,
configFile: false,
},
);
return replacedSource.code;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentConfig: moduleResults,
imports: Array.from(imports).sort().join('\n'),
});
return new Map([[fileName, replacedTemplate]]);
} catch (error) {
console.error(`\nError parsing schema for ${libraryName}\n`);
console.error(JSON.stringify(schema));
throw error;
}
},
};

View File

@@ -0,0 +1,117 @@
/**
* 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.
*
*
* @format
*/
'use strict';
function upperCaseFirst(inString) {
return inString[0].toUpperCase() + inString.slice(1);
}
function getInterfaceJavaClassName(componentName) {
return `${componentName.replace(/^RCT/, '')}ManagerInterface`;
}
function getDelegateJavaClassName(componentName) {
return `${componentName.replace(/^RCT/, '')}ManagerDelegate`;
}
function toSafeJavaString(input, shouldUpperCaseFirst) {
const parts = input.split('-');
if (shouldUpperCaseFirst === false) {
return parts.join('');
}
return parts.map(upperCaseFirst).join('');
}
function getImports(component, type) {
const imports = new Set();
if (type === 'interface') {
imports.add(
'import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;',
);
}
component.extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add('import android.view.View;');
return;
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
});
function addImportsForNativeName(name) {
switch (name) {
case 'ColorPrimitive':
if (type === 'delegate') {
imports.add('import com.facebook.react.bridge.ColorPropConverter;');
}
return;
case 'ImageSourcePrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'PointPrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'EdgeInsetsPrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'DimensionPrimitive':
if (type === 'delegate') {
imports.add(
'import com.facebook.react.bridge.DimensionPropConverter;',
);
} else {
imports.add('import com.facebook.yoga.YogaValue;');
}
return;
default:
name;
throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`);
}
}
component.props.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
// $FlowFixMe[incompatible-type]
addImportsForNativeName(typeAnnotation.name);
}
if (typeAnnotation.type === 'ArrayTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableMap;');
}
if (typeAnnotation.type === 'MixedTypeAnnotation') {
if (type === 'delegate') {
imports.add('import com.facebook.react.bridge.DynamicFromObject;');
} else {
imports.add('import com.facebook.react.bridge.Dynamic;');
}
}
});
component.commands.forEach(command => {
command.typeAnnotation.params.forEach(param => {
const cmdParamType = param.typeAnnotation.type;
if (cmdParamType === 'ArrayTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
});
});
return imports;
}
module.exports = {
getInterfaceJavaClassName,
getDelegateJavaClassName,
toSafeJavaString,
getImports,
};

View File

@@ -0,0 +1,149 @@
/**
* 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 {ComponentShape} from '../../CodegenSchema';
function upperCaseFirst(inString: string): string {
return inString[0].toUpperCase() + inString.slice(1);
}
function getInterfaceJavaClassName(componentName: string): string {
return `${componentName.replace(/^RCT/, '')}ManagerInterface`;
}
function getDelegateJavaClassName(componentName: string): string {
return `${componentName.replace(/^RCT/, '')}ManagerDelegate`;
}
function toSafeJavaString(
input: string,
shouldUpperCaseFirst?: boolean,
): string {
const parts = input.split('-');
if (shouldUpperCaseFirst === false) {
return parts.join('');
}
return parts.map(upperCaseFirst).join('');
}
function getImports(
component: ComponentShape,
type: 'interface' | 'delegate',
): Set<string> {
const imports: Set<string> = new Set();
if (type === 'interface') {
imports.add(
'import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;',
);
}
component.extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add('import android.view.View;');
return;
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
});
function addImportsForNativeName(
name:
| 'ColorPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'DimensionPrimitive',
) {
switch (name) {
case 'ColorPrimitive':
if (type === 'delegate') {
imports.add('import com.facebook.react.bridge.ColorPropConverter;');
}
return;
case 'ImageSourcePrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'PointPrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'EdgeInsetsPrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'DimensionPrimitive':
if (type === 'delegate') {
imports.add(
'import com.facebook.react.bridge.DimensionPropConverter;',
);
} else {
imports.add('import com.facebook.yoga.YogaValue;');
}
return;
default:
(name: empty);
throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`);
}
}
component.props.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
// $FlowFixMe[incompatible-type]
addImportsForNativeName(typeAnnotation.name);
}
if (typeAnnotation.type === 'ArrayTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableMap;');
}
if (typeAnnotation.type === 'MixedTypeAnnotation') {
if (type === 'delegate') {
imports.add('import com.facebook.react.bridge.DynamicFromObject;');
} else {
imports.add('import com.facebook.react.bridge.Dynamic;');
}
}
});
component.commands.forEach(command => {
command.typeAnnotation.params.forEach(param => {
const cmdParamType = param.typeAnnotation.type;
if (cmdParamType === 'ArrayTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
});
});
return imports;
}
module.exports = {
getInterfaceJavaClassName,
getDelegateJavaClassName,
toSafeJavaString,
getImports,
};

View File

@@ -0,0 +1,620 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Cxx');
const {getEnumName, toPascalCase, toSafeCppString} = require('../Utils');
const {
createAliasResolver,
getModules,
isArrayRecursiveMember,
isDirectRecursiveMember,
} = require('./Utils');
function serializeArg(moduleName, arg, index, resolveAlias, enumMap) {
const {typeAnnotation: nullableTypeAnnotation, optional} = arg;
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
function wrap(callback) {
const val = `args[${index}]`;
const expression = callback(val);
// param?: T
if (optional && !nullable) {
// throw new Error('are we hitting this case? ' + moduleName);
return `count <= ${index} || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`;
}
// param: ?T
// param?: ?T
if (nullable || optional) {
return `count <= ${index} || ${val}.isNull() || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`;
}
// param: T
return `count <= ${index} ? throw jsi::JSError(rt, "Expected argument in position ${index} to be passed") : ${expression}`;
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrap(val => `${val}.asNumber()`);
default:
realTypeAnnotation.name;
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`,
);
}
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'StringLiteralTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'StringLiteralUnionTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'BooleanTypeAnnotation':
return wrap(val => `${val}.asBool()`);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
default:
throw new Error(
`Unknown enum type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'FloatTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'DoubleTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'Int32TypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'NumberLiteralTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'ArrayTypeAnnotation':
return wrap(val => `${val}.asObject(rt).asArray(rt)`);
case 'FunctionTypeAnnotation':
return wrap(val => `${val}.asObject(rt).asFunction(rt)`);
case 'GenericObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'ObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
default:
throw new Error(
`Unsupported union member type for param "${arg.name}, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'MixedTypeAnnotation':
return wrap(val => `jsi::Value(rt, ${val})`);
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
}
const ModuleSpecClassDeclarationTemplate = ({
hasteModuleName,
moduleName,
structs,
enums,
moduleEventEmitters,
moduleFunctions,
methods,
}) => {
return `${enums}${structs}
template <typename T>
class JSI_EXPORT ${hasteModuleName}CxxSpec : public TurboModule {
public:
static constexpr std::string_view kModuleName = "${moduleName}";
protected:
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{${hasteModuleName}CxxSpec::kModuleName}, jsInvoker) {
${methods
.map(({methodName, paramCount}) => {
return ` methodMap_["${methodName}"] = MethodMetadata {.argCount = ${paramCount}, .invoker = __${methodName}};`;
})
.join(
'\n',
)}${moduleEventEmitters.length > 0 ? '\n' : ''}${moduleEventEmitters.map(e => e.registerEventEmitter).join('\n')}
}
${moduleEventEmitters.map(e => e.emitFunction).join('\n')}
private:
${moduleFunctions.join('\n\n')}
};`;
};
const FileTemplate = ({modules}) => {
return `/**
* 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: GenerateModuleH.js
*/
#pragma once
#include <ReactCommon/TurboModule.h>
#include <react/bridging/Bridging.h>
namespace facebook::react {
${modules.join('\n\n')}
} // namespace facebook::react
`;
};
function translatePrimitiveJSTypeToCpp(
moduleName,
parentObjectAliasName,
nullableTypeAnnotation,
optional,
createErrorMessage,
resolveAlias,
enumMap,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRecursiveType = isDirectRecursiveMember(
parentObjectAliasName,
nullableTypeAnnotation,
);
const isRequired = (!optional && !nullable) || isRecursiveType;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('int', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('bool', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'GenericObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'ArrayTypeAnnotation':
return wrapOptional('jsi::Array', isRequired);
case 'FunctionTypeAnnotation':
return wrapOptional('jsi::Function', isRequired);
case 'PromiseTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'MixedTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function createStructsString(hasteModuleName, aliasMap, resolveAlias, enumMap) {
const getCppType = (parentObjectAlias, v) =>
translatePrimitiveJSTypeToCpp(
hasteModuleName,
parentObjectAlias,
v.typeAnnotation,
false,
typeName => `Unsupported type for param "${v.name}". Found: ${typeName}`,
resolveAlias,
enumMap,
);
return Object.keys(aliasMap)
.map(alias => {
const value = aliasMap[alias];
if (value.properties.length === 0) {
return '';
}
const structName = `${hasteModuleName}${alias}`;
const templateParameter = value.properties.filter(
v =>
!isDirectRecursiveMember(alias, v.typeAnnotation) &&
!isArrayRecursiveMember(alias, v.typeAnnotation),
);
const templateParameterWithTypename = templateParameter
.map((v, i) => `typename P${i}`)
.join(', ');
const templateParameterWithoutTypename = templateParameter
.map((v, i) => `P${i}`)
.join(', ');
let i = -1;
const templateMemberTypes = value.properties.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return `std::unique_ptr<${structName}<${templateParameterWithoutTypename}>> ${v.name}`;
} else if (isArrayRecursiveMember(alias, v.typeAnnotation)) {
const [nullable] = unwrapNullable(v.typeAnnotation);
return (
(nullable
? `std::optional<std::vector<${structName}<${templateParameterWithoutTypename}>>>`
: `std::vector<${structName}<${templateParameterWithoutTypename}>>`) +
` ${v.name}`
);
} else {
i++;
return `P${i} ${v.name}`;
}
});
const debugParameterConversion = value.properties
.map(
v => ` static ${getCppType(alias, v)} ${v.name}ToJs(jsi::Runtime &rt, decltype(types.${v.name}) value) {
return bridging::toJs(rt, value);
}`,
)
.join('\n');
return `
#pragma mark - ${structName}
template <${templateParameterWithTypename}>
struct ${structName} {
${templateMemberTypes.map(v => ' ' + v).join(';\n')};
bool operator==(const ${structName} &other) const {
return ${value.properties.map(v => `${v.name} == other.${v.name}`).join(' && ')};
}
};
template <typename T>
struct ${structName}Bridging {
static T types;
static T fromJs(
jsi::Runtime &rt,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
T result{
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` value.hasProperty(rt, "${v.name}") ? std::make_unique<T>(bridging::fromJs<T>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)) : nullptr`;
} else {
return ` bridging::fromJs<decltype(types.${v.name})>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`;
}
})
.join(',\n')}};
return result;
}
#ifdef DEBUG
${debugParameterConversion}
#endif
static jsi::Object toJs(
jsi::Runtime &rt,
const T &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
auto result = facebook::jsi::Object(rt);
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, *value.${v.name}, jsInvoker));
}`;
} else if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}, jsInvoker));`;
}
})
.join('\n')}
return result;
}
};
`;
})
.join('\n');
}
const EnumTemplate = ({
enumName,
values,
fromCases,
toCases,
nativeEnumMemberType,
}) => {
const [fromValue, fromValueConversion, toValue] =
nativeEnumMemberType === 'std::string'
? [
'const jsi::String &rawValue',
'std::string value = rawValue.utf8(rt);',
'jsi::String',
]
: [
'const jsi::Value &rawValue',
'double value = (double)rawValue.asNumber();',
'jsi::Value',
];
return `
#pragma mark - ${enumName}
enum class ${enumName} { ${values} };
template <>
struct Bridging<${enumName}> {
static ${enumName} fromJs(jsi::Runtime &rt, ${fromValue}) {
${fromValueConversion}
${fromCases}
}
static ${toValue} toJs(jsi::Runtime &rt, ${enumName} value) {
${toCases}
}
};`;
};
function getMemberValueAppearance(member) {
if (member.type === 'StringLiteralTypeAnnotation') {
return `"${member.value}"`;
} else {
return member.value;
}
}
function generateEnum(hasteModuleName, origEnumName, members, memberType) {
const enumName = getEnumName(hasteModuleName, origEnumName);
const nativeEnumMemberType =
memberType === 'StringTypeAnnotation' ? 'std::string' : 'int32_t';
const fromCases =
members
.map(
member => `if (value == ${getMemberValueAppearance(member.value)}) {
return ${enumName}::${toSafeCppString(member.name)};
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for value in ${enumName}");
}`;
const toCases =
members
.map(
member => `if (value == ${enumName}::${toSafeCppString(member.name)}) {
return bridging::toJs(rt, ${getMemberValueAppearance(member.value)});
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for enum value in ${enumName}");
}`;
return EnumTemplate({
enumName,
values: members.map(member => toSafeCppString(member.name)).join(', '),
fromCases,
toCases,
nativeEnumMemberType,
});
}
function createEnums(hasteModuleName, enumMap, resolveAlias) {
return Object.entries(enumMap)
.map(([enumName, enumNode]) => {
return generateEnum(
hasteModuleName,
enumName,
enumNode.members,
enumNode.memberType,
);
})
.filter(Boolean)
.join('\n');
}
function translateFunctionToCpp(
hasteModuleName,
prop,
resolveAlias,
enumMap,
args,
returnTypeAnnotation,
) {
const [propTypeAnnotation] = unwrapNullable(prop.typeAnnotation);
const isNullable = returnTypeAnnotation.type === 'NullableTypeAnnotation';
const isVoid = returnTypeAnnotation.type === 'VoidTypeAnnotation';
const paramTypes = propTypeAnnotation.params.map(param => {
const translatedParam = translatePrimitiveJSTypeToCpp(
hasteModuleName,
null,
param.typeAnnotation,
param.optional,
typeName =>
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
return `${translatedParam} ${param.name}`;
});
paramTypes.unshift('jsi::Runtime &rt');
const returnType = translatePrimitiveJSTypeToCpp(
hasteModuleName,
null,
propTypeAnnotation.returnTypeAnnotation,
false,
typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
let methodCallArgs = [...args].join(',\n ');
if (methodCallArgs.length > 0) {
methodCallArgs = `,\n ${methodCallArgs}`;
}
return ` static jsi::Value __${prop.name}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* ${args.length > 0 ? 'args' : '/*args*/'}, size_t ${args.length > 0 ? 'count' : '/*count*/'}) {
static_assert(
bridging::getParameterCount(&T::${prop.name}) == ${paramTypes.length},
"Expected ${prop.name}(...) to have ${paramTypes.length} parameters");
${!isVoid ? (!isNullable ? 'return ' : 'auto result = ') : ''}bridging::callFromJs<${returnType}>(rt, &T::${prop.name}, static_cast<${hasteModuleName}CxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule)${methodCallArgs});${!isVoid ? (!isNullable ? '' : 'return result ? jsi::Value(std::move(*result)) : jsi::Value::null();') : 'return jsi::Value::undefined();'}\n }`;
}
function translateEventEmitterToCpp(
moduleName,
eventEmitter,
resolveAlias,
enumMap,
) {
const isVoidTypeAnnotation =
eventEmitter.typeAnnotation.typeAnnotation.type === 'VoidTypeAnnotation';
const templateName = `${toPascalCase(eventEmitter.name)}Type`;
const jsiType = translatePrimitiveJSTypeToCpp(
moduleName,
null,
eventEmitter.typeAnnotation.typeAnnotation,
false,
typeName =>
`Unsupported type for eventEmitter "${eventEmitter.name}" in ${moduleName}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
const isArray = jsiType === 'jsi::Array';
return {
isVoidTypeAnnotation: isVoidTypeAnnotation,
templateName: isVoidTypeAnnotation ? `/*${templateName}*/` : templateName,
registerEventEmitter: ` eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<${isVoidTypeAnnotation ? '' : 'jsi::Value'}>>();`,
emitFunction: `
${isVoidTypeAnnotation ? '' : `template <typename ${templateName}> `}void emit${toPascalCase(eventEmitter.name)}(${isVoidTypeAnnotation ? '' : `${isArray ? `std::vector<${templateName}>` : templateName} value`}) {${
isVoidTypeAnnotation
? ''
: `
static_assert(bridging::supportsFromJs<${isArray ? `std::vector<${templateName}>` : templateName}, ${jsiType}>, "value cannnot be converted to ${jsiType}");`
}
static_cast<AsyncEventEmitter<${isVoidTypeAnnotation ? '' : 'jsi::Value'}>&>(*eventEmitterMap_["${eventEmitter.name}"]).emit(${
isVoidTypeAnnotation
? ''
: `[jsInvoker = jsInvoker_, eventValue = value](jsi::Runtime& rt) -> jsi::Value {
return bridging::toJs(rt, eventValue, jsInvoker);
}`
});
}`,
};
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
const nativeModule = nativeModules[hasteModuleName];
const {
aliasMap,
enumMap,
spec: {methods},
spec,
moduleName,
} = nativeModule;
const resolveAlias = createAliasResolver(aliasMap);
const structs = createStructsString(
hasteModuleName,
aliasMap,
resolveAlias,
enumMap,
);
const enums = createEnums(hasteModuleName, enumMap, resolveAlias);
return [
ModuleSpecClassDeclarationTemplate({
hasteModuleName,
moduleName,
structs,
enums,
moduleEventEmitters: spec.eventEmitters.map(eventEmitter =>
translateEventEmitterToCpp(
moduleName,
eventEmitter,
resolveAlias,
enumMap,
),
),
moduleFunctions: spec.methods.map(property => {
const [propertyTypeAnnotation] = unwrapNullable(
property.typeAnnotation,
);
return translateFunctionToCpp(
hasteModuleName,
property,
resolveAlias,
enumMap,
propertyTypeAnnotation.params.map((p, i) =>
serializeArg(moduleName, p, i, resolveAlias, enumMap),
),
propertyTypeAnnotation.returnTypeAnnotation,
);
}),
methods: methods.map(
({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => {
const [{params}] = unwrapNullable(nullableTypeAnnotation);
return {
methodName: propertyName,
paramCount: params.length,
};
},
),
}),
];
});
const fileName = `${libraryName}JSI.h`;
const replacedTemplate = FileTemplate({
modules,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,744 @@
/**
* 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,
NativeModuleAliasMap,
NativeModuleBaseTypeAnnotation,
NativeModuleEnumMap,
NativeModuleEnumMember,
NativeModuleEnumMemberType,
NativeModuleEventEmitterShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Cxx');
const {getEnumName, toPascalCase, toSafeCppString} = require('../Utils');
const {
createAliasResolver,
getModules,
isArrayRecursiveMember,
isDirectRecursiveMember,
} = require('./Utils');
type FilesOutput = Map<string, string>;
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function serializeArg(
moduleName: string,
arg: Param,
index: number,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): string {
const {typeAnnotation: nullableTypeAnnotation, optional} = arg;
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
function wrap(callback: (val: string) => string) {
const val = `args[${index}]`;
const expression = callback(val);
// param?: T
if (optional && !nullable) {
// throw new Error('are we hitting this case? ' + moduleName);
return `count <= ${index} || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`;
}
// param: ?T
// param?: ?T
if (nullable || optional) {
return `count <= ${index} || ${val}.isNull() || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`;
}
// param: T
return `count <= ${index} ? throw jsi::JSError(rt, "Expected argument in position ${index} to be passed") : ${expression}`;
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrap(val => `${val}.asNumber()`);
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`,
);
}
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'StringLiteralTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'StringLiteralUnionTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'BooleanTypeAnnotation':
return wrap(val => `${val}.asBool()`);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
default:
throw new Error(
`Unknown enum type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'FloatTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'DoubleTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'Int32TypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'NumberLiteralTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'ArrayTypeAnnotation':
return wrap(val => `${val}.asObject(rt).asArray(rt)`);
case 'FunctionTypeAnnotation':
return wrap(val => `${val}.asObject(rt).asFunction(rt)`);
case 'GenericObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'ObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
default:
throw new Error(
`Unsupported union member type for param "${arg.name}, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'MixedTypeAnnotation':
return wrap(val => `jsi::Value(rt, ${val})`);
default:
(realTypeAnnotation.type: empty);
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
}
const ModuleSpecClassDeclarationTemplate = ({
hasteModuleName,
moduleName,
structs,
enums,
moduleEventEmitters,
moduleFunctions,
methods,
}: $ReadOnly<{
hasteModuleName: string,
moduleName: string,
structs: string,
enums: string,
moduleEventEmitters: EventEmitterCpp[],
moduleFunctions: string[],
methods: $ReadOnlyArray<$ReadOnly<{methodName: string, paramCount: number}>>,
}>) => {
return `${enums}${structs}
template <typename T>
class JSI_EXPORT ${hasteModuleName}CxxSpec : public TurboModule {
public:
static constexpr std::string_view kModuleName = "${moduleName}";
protected:
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{${hasteModuleName}CxxSpec::kModuleName}, jsInvoker) {
${methods
.map(({methodName, paramCount}) => {
return ` methodMap_["${methodName}"] = MethodMetadata {.argCount = ${paramCount}, .invoker = __${methodName}};`;
})
.join(
'\n',
)}${moduleEventEmitters.length > 0 ? '\n' : ''}${moduleEventEmitters.map(e => e.registerEventEmitter).join('\n')}
}
${moduleEventEmitters.map(e => e.emitFunction).join('\n')}
private:
${moduleFunctions.join('\n\n')}
};`;
};
const FileTemplate = ({
modules,
}: $ReadOnly<{
modules: string[],
}>) => {
return `/**
* 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: GenerateModuleH.js
*/
#pragma once
#include <ReactCommon/TurboModule.h>
#include <react/bridging/Bridging.h>
namespace facebook::react {
${modules.join('\n\n')}
} // namespace facebook::react
`;
};
function translatePrimitiveJSTypeToCpp(
moduleName: string,
parentObjectAliasName: ?string,
nullableTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
optional: boolean,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
) {
const [typeAnnotation, nullable] = unwrapNullable<NativeModuleTypeAnnotation>(
nullableTypeAnnotation,
);
const isRecursiveType = isDirectRecursiveMember(
parentObjectAliasName,
nullableTypeAnnotation,
);
const isRequired = (!optional && !nullable) || isRecursiveType;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('int', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('bool', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'GenericObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'ArrayTypeAnnotation':
return wrapOptional('jsi::Array', isRequired);
case 'FunctionTypeAnnotation':
return wrapOptional('jsi::Function', isRequired);
case 'PromiseTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'MixedTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
default:
(realTypeAnnotation.type: empty);
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function createStructsString(
hasteModuleName: string,
aliasMap: NativeModuleAliasMap,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): string {
const getCppType = (
parentObjectAlias: string,
v: NamedShape<Nullable<NativeModuleBaseTypeAnnotation>>,
) =>
translatePrimitiveJSTypeToCpp(
hasteModuleName,
parentObjectAlias,
v.typeAnnotation,
false,
typeName => `Unsupported type for param "${v.name}". Found: ${typeName}`,
resolveAlias,
enumMap,
);
return Object.keys(aliasMap)
.map(alias => {
const value = aliasMap[alias];
if (value.properties.length === 0) {
return '';
}
const structName = `${hasteModuleName}${alias}`;
const templateParameter = value.properties.filter(
v =>
!isDirectRecursiveMember(alias, v.typeAnnotation) &&
!isArrayRecursiveMember(alias, v.typeAnnotation),
);
const templateParameterWithTypename = templateParameter
.map((v, i) => `typename P${i}`)
.join(', ');
const templateParameterWithoutTypename = templateParameter
.map((v, i) => `P${i}`)
.join(', ');
let i = -1;
const templateMemberTypes = value.properties.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return `std::unique_ptr<${structName}<${templateParameterWithoutTypename}>> ${v.name}`;
} else if (isArrayRecursiveMember(alias, v.typeAnnotation)) {
const [nullable] = unwrapNullable<NativeModuleTypeAnnotation>(
v.typeAnnotation,
);
return (
(nullable
? `std::optional<std::vector<${structName}<${templateParameterWithoutTypename}>>>`
: `std::vector<${structName}<${templateParameterWithoutTypename}>>`) +
` ${v.name}`
);
} else {
i++;
return `P${i} ${v.name}`;
}
});
const debugParameterConversion = value.properties
.map(
v => ` static ${getCppType(alias, v)} ${
v.name
}ToJs(jsi::Runtime &rt, decltype(types.${v.name}) value) {
return bridging::toJs(rt, value);
}`,
)
.join('\n');
return `
#pragma mark - ${structName}
template <${templateParameterWithTypename}>
struct ${structName} {
${templateMemberTypes.map(v => ' ' + v).join(';\n')};
bool operator==(const ${structName} &other) const {
return ${value.properties
.map(v => `${v.name} == other.${v.name}`)
.join(' && ')};
}
};
template <typename T>
struct ${structName}Bridging {
static T types;
static T fromJs(
jsi::Runtime &rt,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
T result{
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` value.hasProperty(rt, "${v.name}") ? std::make_unique<T>(bridging::fromJs<T>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)) : nullptr`;
} else {
return ` bridging::fromJs<decltype(types.${v.name})>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`;
}
})
.join(',\n')}};
return result;
}
#ifdef DEBUG
${debugParameterConversion}
#endif
static jsi::Object toJs(
jsi::Runtime &rt,
const T &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
auto result = facebook::jsi::Object(rt);
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, *value.${v.name}, jsInvoker));
}`;
} else if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}, jsInvoker));`;
}
})
.join('\n')}
return result;
}
};
`;
})
.join('\n');
}
type NativeEnumMemberValueType = 'std::string' | 'int32_t';
const EnumTemplate = ({
enumName,
values,
fromCases,
toCases,
nativeEnumMemberType,
}: {
enumName: string,
values: string,
fromCases: string,
toCases: string,
nativeEnumMemberType: NativeEnumMemberValueType,
}) => {
const [fromValue, fromValueConversion, toValue] =
nativeEnumMemberType === 'std::string'
? [
'const jsi::String &rawValue',
'std::string value = rawValue.utf8(rt);',
'jsi::String',
]
: [
'const jsi::Value &rawValue',
'double value = (double)rawValue.asNumber();',
'jsi::Value',
];
return `
#pragma mark - ${enumName}
enum class ${enumName} { ${values} };
template <>
struct Bridging<${enumName}> {
static ${enumName} fromJs(jsi::Runtime &rt, ${fromValue}) {
${fromValueConversion}
${fromCases}
}
static ${toValue} toJs(jsi::Runtime &rt, ${enumName} value) {
${toCases}
}
};`;
};
function getMemberValueAppearance(member: NativeModuleEnumMember['value']) {
if (member.type === 'StringLiteralTypeAnnotation') {
return `"${member.value}"`;
} else {
return member.value;
}
}
function generateEnum(
hasteModuleName: string,
origEnumName: string,
members: $ReadOnlyArray<NativeModuleEnumMember>,
memberType: NativeModuleEnumMemberType,
): string {
const enumName = getEnumName(hasteModuleName, origEnumName);
const nativeEnumMemberType: NativeEnumMemberValueType =
memberType === 'StringTypeAnnotation' ? 'std::string' : 'int32_t';
const fromCases =
members
.map(
member => `if (value == ${getMemberValueAppearance(member.value)}) {
return ${enumName}::${toSafeCppString(member.name)};
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for value in ${enumName}");
}`;
const toCases =
members
.map(
member => `if (value == ${enumName}::${toSafeCppString(member.name)}) {
return bridging::toJs(rt, ${getMemberValueAppearance(member.value)});
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for enum value in ${enumName}");
}`;
return EnumTemplate({
enumName,
values: members.map(member => toSafeCppString(member.name)).join(', '),
fromCases,
toCases,
nativeEnumMemberType,
});
}
function createEnums(
hasteModuleName: string,
enumMap: NativeModuleEnumMap,
resolveAlias: AliasResolver,
): string {
return Object.entries(enumMap)
.map(([enumName, enumNode]) => {
return generateEnum(
hasteModuleName,
enumName,
enumNode.members,
enumNode.memberType,
);
})
.filter(Boolean)
.join('\n');
}
function translateFunctionToCpp(
hasteModuleName: string,
prop: NativeModulePropertyShape,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
args: Array<string>,
returnTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
): string {
const [propTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(prop.typeAnnotation);
const isNullable = returnTypeAnnotation.type === 'NullableTypeAnnotation';
const isVoid = returnTypeAnnotation.type === 'VoidTypeAnnotation';
const paramTypes = propTypeAnnotation.params.map(param => {
const translatedParam = translatePrimitiveJSTypeToCpp(
hasteModuleName,
null,
param.typeAnnotation,
param.optional,
typeName =>
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
return `${translatedParam} ${param.name}`;
});
paramTypes.unshift('jsi::Runtime &rt');
const returnType = translatePrimitiveJSTypeToCpp(
hasteModuleName,
null,
propTypeAnnotation.returnTypeAnnotation,
false,
typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
let methodCallArgs = [...args].join(',\n ');
if (methodCallArgs.length > 0) {
methodCallArgs = `,\n ${methodCallArgs}`;
}
return ` static jsi::Value __${prop.name}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* ${args.length > 0 ? 'args' : '/*args*/'}, size_t ${args.length > 0 ? 'count' : '/*count*/'}) {
static_assert(
bridging::getParameterCount(&T::${prop.name}) == ${paramTypes.length},
"Expected ${prop.name}(...) to have ${paramTypes.length} parameters");
${!isVoid ? (!isNullable ? 'return ' : 'auto result = ') : ''}bridging::callFromJs<${returnType}>(rt, &T::${prop.name}, static_cast<${hasteModuleName}CxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule)${methodCallArgs});${!isVoid ? (!isNullable ? '' : 'return result ? jsi::Value(std::move(*result)) : jsi::Value::null();') : 'return jsi::Value::undefined();'}\n }`;
}
type EventEmitterCpp = {
isVoidTypeAnnotation: boolean,
templateName: string,
registerEventEmitter: string,
emitFunction: string,
};
function translateEventEmitterToCpp(
moduleName: string,
eventEmitter: NativeModuleEventEmitterShape,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): EventEmitterCpp {
const isVoidTypeAnnotation =
eventEmitter.typeAnnotation.typeAnnotation.type === 'VoidTypeAnnotation';
const templateName = `${toPascalCase(eventEmitter.name)}Type`;
const jsiType = translatePrimitiveJSTypeToCpp(
moduleName,
null,
eventEmitter.typeAnnotation.typeAnnotation,
false,
typeName =>
`Unsupported type for eventEmitter "${eventEmitter.name}" in ${moduleName}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
const isArray = jsiType === 'jsi::Array';
return {
isVoidTypeAnnotation: isVoidTypeAnnotation,
templateName: isVoidTypeAnnotation ? `/*${templateName}*/` : templateName,
registerEventEmitter: ` eventEmitterMap_["${
eventEmitter.name
}"] = std::make_shared<AsyncEventEmitter<${
isVoidTypeAnnotation ? '' : 'jsi::Value'
}>>();`,
emitFunction: `
${
isVoidTypeAnnotation ? '' : `template <typename ${templateName}> `
}void emit${toPascalCase(eventEmitter.name)}(${
isVoidTypeAnnotation
? ''
: `${isArray ? `std::vector<${templateName}>` : templateName} value`
}) {${
isVoidTypeAnnotation
? ''
: `
static_assert(bridging::supportsFromJs<${
isArray ? `std::vector<${templateName}>` : templateName
}, ${jsiType}>, "value cannnot be converted to ${jsiType}");`
}
static_cast<AsyncEventEmitter<${
isVoidTypeAnnotation ? '' : 'jsi::Value'
}>&>(*eventEmitterMap_["${eventEmitter.name}"]).emit(${
isVoidTypeAnnotation
? ''
: `[jsInvoker = jsInvoker_, eventValue = value](jsi::Runtime& rt) -> jsi::Value {
return bridging::toJs(rt, eventValue, jsInvoker);
}`
});
}`,
};
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
const nativeModule = nativeModules[hasteModuleName];
const {
aliasMap,
enumMap,
spec: {methods},
spec,
moduleName,
} = nativeModule;
const resolveAlias = createAliasResolver(aliasMap);
const structs = createStructsString(
hasteModuleName,
aliasMap,
resolveAlias,
enumMap,
);
const enums = createEnums(hasteModuleName, enumMap, resolveAlias);
return [
ModuleSpecClassDeclarationTemplate({
hasteModuleName,
moduleName,
structs,
enums,
moduleEventEmitters: spec.eventEmitters.map(eventEmitter =>
translateEventEmitterToCpp(
moduleName,
eventEmitter,
resolveAlias,
enumMap,
),
),
moduleFunctions: spec.methods.map(property => {
const [propertyTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
property.typeAnnotation,
);
return translateFunctionToCpp(
hasteModuleName,
property,
resolveAlias,
enumMap,
propertyTypeAnnotation.params.map((p, i) =>
serializeArg(moduleName, p, i, resolveAlias, enumMap),
),
propertyTypeAnnotation.returnTypeAnnotation,
);
}),
methods: methods.map(
({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => {
const [{params}] = unwrapNullable(nullableTypeAnnotation);
return {
methodName: propertyName,
paramCount: params.length,
};
},
),
}),
];
});
const fileName = `${libraryName}JSI.h`;
const replacedTemplate = FileTemplate({modules});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,542 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Java');
const {toPascalCase} = require('../Utils');
const {createAliasResolver, getModules} = require('./Utils');
function FileTemplate(config) {
const {packageName, className, jsName, eventEmitters, methods, imports} =
config;
return `
/**
* 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: GenerateModuleJavaSpec.js
*
* ${'@'}nolint
*/
package ${packageName};
${imports}
public abstract class ${className} extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "${jsName}";
public ${className}(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
${eventEmitters}${eventEmitters.length > 0 ? '\n\n' : ''}${methods}
}
`;
}
function EventEmitterTemplate(eventEmitter, imports) {
return ` protected final void emit${toPascalCase(eventEmitter.name)}(${eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation' ? `${translateEventEmitterTypeToJavaType(eventEmitter, imports)} value` : ''}) {
mEventEmitterCallback.invoke("${eventEmitter.name}"${eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation' ? ', value' : ''});
}`;
}
function MethodTemplate(config) {
const {
abstract,
methodBody,
methodJavaAnnotation,
methodName,
translatedReturnType,
traversedArgs,
} = config;
const methodQualifier = abstract ? 'abstract ' : '';
const methodClosing = abstract
? ';'
: methodBody != null && methodBody.length > 0
? ` { ${methodBody} }`
: ' {}';
return ` ${methodJavaAnnotation}
public ${methodQualifier}${translatedReturnType} ${methodName}(${traversedArgs.join(', ')})${methodClosing}`;
}
function translateEventEmitterTypeToJavaType(eventEmitter, imports) {
const type = eventEmitter.typeAnnotation.typeAnnotation.type;
switch (type) {
case 'StringTypeAnnotation':
return 'String';
case 'StringLiteralTypeAnnotation':
return 'String';
case 'StringLiteralUnionTypeAnnotation':
return 'String';
case 'NumberTypeAnnotation':
case 'NumberLiteralTypeAnnotation':
case 'FloatTypeAnnotation':
case 'DoubleTypeAnnotation':
case 'Int32TypeAnnotation':
return 'double';
case 'BooleanTypeAnnotation':
return 'boolean';
case 'GenericObjectTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'TypeAliasTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return 'ReadableMap';
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return 'ReadableArray';
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'VoidTypeAnnotation':
// TODO: Add support for these types
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
default:
type;
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
}
}
function translateFunctionParamToJavaType(
param,
createErrorMessage,
resolveAlias,
imports,
) {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
if (!isRequired) {
imports.add('javax.annotation.Nullable');
}
// FIXME: support class alias for args
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'GenericObjectTypeAnnotation':
// Treat this the same as ObjectTypeAnnotation for now.
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return wrapOptional('ReadableArray', isRequired);
case 'FunctionTypeAnnotation':
imports.add('com.facebook.react.bridge.Callback');
return wrapOptional('Callback', isRequired);
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function translateFunctionReturnTypeToJavaType(
nullableReturnTypeAnnotation,
createErrorMessage,
resolveAlias,
imports,
) {
const [returnTypeAnnotation, nullable] = unwrapNullable(
nullableReturnTypeAnnotation,
);
if (nullable) {
imports.add('javax.annotation.Nullable');
}
const isRequired = !nullable;
// FIXME: support class alias for args
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'PromiseTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'GenericObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableArray');
return wrapOptional('WritableArray', isRequired);
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function getFalsyReturnStatementFromReturnType(
nullableReturnTypeAnnotation,
createErrorMessage,
resolveAlias,
) {
const [returnTypeAnnotation, nullable] = unwrapNullable(
nullableReturnTypeAnnotation,
);
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'return 0.0;';
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return '';
case 'PromiseTypeAnnotation':
return '';
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'NumberLiteralTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'FloatTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'DoubleTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'Int32TypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'BooleanTypeAnnotation':
return nullable ? 'return null;' : 'return false;';
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'StringLiteralTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'StringLiteralUnionTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'GenericObjectTypeAnnotation':
return 'return null;';
case 'ArrayTypeAnnotation':
return 'return null;';
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
// Build special-cased runtime check for getConstants().
function buildGetConstantsMethod(method, imports, resolveAlias) {
const [methodTypeAnnotation] = unwrapNullable(method.typeAnnotation);
let returnTypeAnnotation = methodTypeAnnotation.returnTypeAnnotation;
if (returnTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
// The return type is an alias, resolve it to get the expected undelying object literal type
returnTypeAnnotation = resolveAlias(returnTypeAnnotation.name);
}
if (returnTypeAnnotation.type === 'ObjectTypeAnnotation') {
const requiredProps = [];
const optionalProps = [];
const rawProperties = returnTypeAnnotation.properties || [];
rawProperties.forEach(p => {
if (p.optional || p.typeAnnotation.type === 'NullableTypeAnnotation') {
optionalProps.push(p.name);
} else {
requiredProps.push(p.name);
}
});
if (requiredProps.length === 0 && optionalProps.length === 0) {
// Nothing to validate during runtime.
return '';
}
imports.add('com.facebook.react.common.build.ReactBuildConfig');
imports.add('java.util.Arrays');
imports.add('java.util.HashSet');
imports.add('java.util.Map');
imports.add('java.util.Set');
imports.add('javax.annotation.Nullable');
const requiredPropsFragment =
requiredProps.length > 0
? `Arrays.asList(
${requiredProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
const optionalPropsFragment =
optionalProps.length > 0
? `Arrays.asList(
${optionalProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
return ` protected abstract Map<String, Object> getTypedExportedConstants();
@Override
@DoNotStrip
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> constants = getTypedExportedConstants();
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
Set<String> obligatoryFlowConstants = new HashSet<>(${requiredPropsFragment});
Set<String> optionalFlowConstants = new HashSet<>(${optionalPropsFragment});
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
undeclaredConstants.removeAll(obligatoryFlowConstants);
undeclaredConstants.removeAll(optionalFlowConstants);
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
}
undeclaredConstants = obligatoryFlowConstants;
undeclaredConstants.removeAll(constants.keySet());
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
}
}
return constants;
}`;
}
return '';
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const files = new Map();
const nativeModules = getModules(schema);
const normalizedPackageName =
packageName == null ? 'com.facebook.fbreact.specs' : packageName;
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
Object.keys(nativeModules).forEach(hasteModuleName => {
const {aliasMap, excludedPlatforms, moduleName, spec} =
nativeModules[hasteModuleName];
if (excludedPlatforms != null && excludedPlatforms.includes('android')) {
return;
}
const resolveAlias = createAliasResolver(aliasMap);
const className = `${hasteModuleName}Spec`;
const imports = new Set([
// Always required.
'com.facebook.react.bridge.ReactApplicationContext',
'com.facebook.react.bridge.ReactContextBaseJavaModule',
'com.facebook.react.bridge.ReactMethod',
'com.facebook.react.turbomodule.core.interfaces.TurboModule',
'com.facebook.proguard.annotations.DoNotStrip',
'javax.annotation.Nonnull',
]);
const methods = spec.methods.map(method => {
if (method.name === 'getConstants') {
return buildGetConstantsMethod(method, imports, resolveAlias);
}
const [methodTypeAnnotation] = unwrapNullable(method.typeAnnotation);
// Handle return type
const translatedReturnType = translateFunctionReturnTypeToJavaType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Unsupported return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
const returningPromise =
methodTypeAnnotation.returnTypeAnnotation.type ===
'PromiseTypeAnnotation';
const isSyncMethod =
methodTypeAnnotation.returnTypeAnnotation.type !==
'VoidTypeAnnotation' && !returningPromise;
// Handle method args
const traversedArgs = methodTypeAnnotation.params.map(param => {
const translatedParam = translateFunctionParamToJavaType(
param,
typeName =>
`Unsupported type for param "${param.name}" in ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
return `${translatedParam} ${param.name}`;
});
if (returningPromise) {
// Promise return type requires an extra arg at the end.
imports.add('com.facebook.react.bridge.Promise');
traversedArgs.push('Promise promise');
}
const methodJavaAnnotation = `@ReactMethod${isSyncMethod ? '(isBlockingSynchronousMethod = true)' : ''}\n @DoNotStrip`;
const methodBody = method.optional
? getFalsyReturnStatementFromReturnType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Cannot build falsy return statement for return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
)
: null;
return MethodTemplate({
abstract: !method.optional,
methodBody,
methodJavaAnnotation,
methodName: method.name,
translatedReturnType,
traversedArgs,
});
});
files.set(
`${outputDir}/${className}.java`,
FileTemplate({
packageName: normalizedPackageName,
className,
jsName: moduleName,
eventEmitters: spec.eventEmitters
.map(eventEmitter => EventEmitterTemplate(eventEmitter, imports))
.join('\n\n'),
methods: methods.filter(Boolean).join('\n\n'),
imports: Array.from(imports)
.sort()
.map(p => `import ${p};`)
.join('\n'),
}),
);
});
return files;
},
};

View File

@@ -0,0 +1,633 @@
/**
* 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,
NativeModuleEventEmitterShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleReturnTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Java');
const {toPascalCase} = require('../Utils');
const {createAliasResolver, getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
function FileTemplate(
config: $ReadOnly<{
packageName: string,
className: string,
jsName: string,
eventEmitters: string,
methods: string,
imports: string,
}>,
): string {
const {packageName, className, jsName, eventEmitters, methods, imports} =
config;
return `
/**
* 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: GenerateModuleJavaSpec.js
*
* ${'@'}nolint
*/
package ${packageName};
${imports}
public abstract class ${className} extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "${jsName}";
public ${className}(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
${eventEmitters}${eventEmitters.length > 0 ? '\n\n' : ''}${methods}
}
`;
}
function EventEmitterTemplate(
eventEmitter: NativeModuleEventEmitterShape,
imports: Set<string>,
): string {
return ` protected final void emit${toPascalCase(eventEmitter.name)}(${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? `${translateEventEmitterTypeToJavaType(eventEmitter, imports)} value`
: ''
}) {
mEventEmitterCallback.invoke("${eventEmitter.name}"${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? ', value'
: ''
});
}`;
}
function MethodTemplate(
config: $ReadOnly<{
abstract: boolean,
methodBody: ?string,
methodJavaAnnotation: string,
methodName: string,
translatedReturnType: string,
traversedArgs: Array<string>,
}>,
): string {
const {
abstract,
methodBody,
methodJavaAnnotation,
methodName,
translatedReturnType,
traversedArgs,
} = config;
const methodQualifier = abstract ? 'abstract ' : '';
const methodClosing = abstract
? ';'
: methodBody != null && methodBody.length > 0
? ` { ${methodBody} }`
: ' {}';
return ` ${methodJavaAnnotation}
public ${methodQualifier}${translatedReturnType} ${methodName}(${traversedArgs.join(
', ',
)})${methodClosing}`;
}
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function translateEventEmitterTypeToJavaType(
eventEmitter: NativeModuleEventEmitterShape,
imports: Set<string>,
): string {
const type = eventEmitter.typeAnnotation.typeAnnotation.type;
switch (type) {
case 'StringTypeAnnotation':
return 'String';
case 'StringLiteralTypeAnnotation':
return 'String';
case 'StringLiteralUnionTypeAnnotation':
return 'String';
case 'NumberTypeAnnotation':
case 'NumberLiteralTypeAnnotation':
case 'FloatTypeAnnotation':
case 'DoubleTypeAnnotation':
case 'Int32TypeAnnotation':
return 'double';
case 'BooleanTypeAnnotation':
return 'boolean';
case 'GenericObjectTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'TypeAliasTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return 'ReadableMap';
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return 'ReadableArray';
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'VoidTypeAnnotation':
// TODO: Add support for these types
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
default:
(type: empty);
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
}
}
function translateFunctionParamToJavaType(
param: Param,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
imports: Set<string>,
): string {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
if (!isRequired) {
imports.add('javax.annotation.Nullable');
}
// FIXME: support class alias for args
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'GenericObjectTypeAnnotation':
// Treat this the same as ObjectTypeAnnotation for now.
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return wrapOptional('ReadableArray', isRequired);
case 'FunctionTypeAnnotation':
imports.add('com.facebook.react.bridge.Callback');
return wrapOptional('Callback', isRequired);
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function translateFunctionReturnTypeToJavaType(
nullableReturnTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
imports: Set<string>,
): string {
const [returnTypeAnnotation, nullable] =
unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableReturnTypeAnnotation,
);
if (nullable) {
imports.add('javax.annotation.Nullable');
}
const isRequired = !nullable;
// FIXME: support class alias for args
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'PromiseTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralUnionTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'GenericObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableArray');
return wrapOptional('WritableArray', isRequired);
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function getFalsyReturnStatementFromReturnType(
nullableReturnTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
): string {
const [returnTypeAnnotation, nullable] =
unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableReturnTypeAnnotation,
);
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'return 0.0;';
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return '';
case 'PromiseTypeAnnotation':
return '';
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'NumberLiteralTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'FloatTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'DoubleTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'Int32TypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'BooleanTypeAnnotation':
return nullable ? 'return null;' : 'return false;';
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'StringLiteralTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'StringLiteralUnionTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'GenericObjectTypeAnnotation':
return 'return null;';
case 'ArrayTypeAnnotation':
return 'return null;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
// Build special-cased runtime check for getConstants().
function buildGetConstantsMethod(
method: NativeModulePropertyShape,
imports: Set<string>,
resolveAlias: AliasResolver,
): string {
const [methodTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(method.typeAnnotation);
let returnTypeAnnotation = methodTypeAnnotation.returnTypeAnnotation;
if (returnTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
// The return type is an alias, resolve it to get the expected undelying object literal type
returnTypeAnnotation = resolveAlias(returnTypeAnnotation.name);
}
if (returnTypeAnnotation.type === 'ObjectTypeAnnotation') {
const requiredProps = [];
const optionalProps = [];
const rawProperties = returnTypeAnnotation.properties || [];
rawProperties.forEach(p => {
if (p.optional || p.typeAnnotation.type === 'NullableTypeAnnotation') {
optionalProps.push(p.name);
} else {
requiredProps.push(p.name);
}
});
if (requiredProps.length === 0 && optionalProps.length === 0) {
// Nothing to validate during runtime.
return '';
}
imports.add('com.facebook.react.common.build.ReactBuildConfig');
imports.add('java.util.Arrays');
imports.add('java.util.HashSet');
imports.add('java.util.Map');
imports.add('java.util.Set');
imports.add('javax.annotation.Nullable');
const requiredPropsFragment =
requiredProps.length > 0
? `Arrays.asList(
${requiredProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
const optionalPropsFragment =
optionalProps.length > 0
? `Arrays.asList(
${optionalProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
return ` protected abstract Map<String, Object> getTypedExportedConstants();
@Override
@DoNotStrip
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> constants = getTypedExportedConstants();
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
Set<String> obligatoryFlowConstants = new HashSet<>(${requiredPropsFragment});
Set<String> optionalFlowConstants = new HashSet<>(${optionalPropsFragment});
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
undeclaredConstants.removeAll(obligatoryFlowConstants);
undeclaredConstants.removeAll(optionalFlowConstants);
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
}
undeclaredConstants = obligatoryFlowConstants;
undeclaredConstants.removeAll(constants.keySet());
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
}
}
return constants;
}`;
}
return '';
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const files = new Map<string, string>();
const nativeModules = getModules(schema);
const normalizedPackageName =
packageName == null ? 'com.facebook.fbreact.specs' : packageName;
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
Object.keys(nativeModules).forEach(hasteModuleName => {
const {aliasMap, excludedPlatforms, moduleName, spec} =
nativeModules[hasteModuleName];
if (excludedPlatforms != null && excludedPlatforms.includes('android')) {
return;
}
const resolveAlias = createAliasResolver(aliasMap);
const className = `${hasteModuleName}Spec`;
const imports: Set<string> = new Set([
// Always required.
'com.facebook.react.bridge.ReactApplicationContext',
'com.facebook.react.bridge.ReactContextBaseJavaModule',
'com.facebook.react.bridge.ReactMethod',
'com.facebook.react.turbomodule.core.interfaces.TurboModule',
'com.facebook.proguard.annotations.DoNotStrip',
'javax.annotation.Nonnull',
]);
const methods = spec.methods.map(method => {
if (method.name === 'getConstants') {
return buildGetConstantsMethod(method, imports, resolveAlias);
}
const [methodTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
method.typeAnnotation,
);
// Handle return type
const translatedReturnType = translateFunctionReturnTypeToJavaType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Unsupported return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
const returningPromise =
methodTypeAnnotation.returnTypeAnnotation.type ===
'PromiseTypeAnnotation';
const isSyncMethod =
methodTypeAnnotation.returnTypeAnnotation.type !==
'VoidTypeAnnotation' && !returningPromise;
// Handle method args
const traversedArgs = methodTypeAnnotation.params.map(param => {
const translatedParam = translateFunctionParamToJavaType(
param,
typeName =>
`Unsupported type for param "${param.name}" in ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
return `${translatedParam} ${param.name}`;
});
if (returningPromise) {
// Promise return type requires an extra arg at the end.
imports.add('com.facebook.react.bridge.Promise');
traversedArgs.push('Promise promise');
}
const methodJavaAnnotation = `@ReactMethod${
isSyncMethod ? '(isBlockingSynchronousMethod = true)' : ''
}\n @DoNotStrip`;
const methodBody = method.optional
? getFalsyReturnStatementFromReturnType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Cannot build falsy return statement for return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
)
: null;
return MethodTemplate({
abstract: !method.optional,
methodBody,
methodJavaAnnotation,
methodName: method.name,
translatedReturnType,
traversedArgs,
});
});
files.set(
`${outputDir}/${className}.java`,
FileTemplate({
packageName: normalizedPackageName,
className,
jsName: moduleName,
eventEmitters: spec.eventEmitters
.map(eventEmitter => EventEmitterTemplate(eventEmitter, imports))
.join('\n\n'),
methods: methods.filter(Boolean).join('\n\n'),
imports: Array.from(imports)
.sort()
.map(p => `import ${p};`)
.join('\n'),
}),
);
});
return files;
},
};

View File

@@ -0,0 +1,456 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {createAliasResolver, getModules} = require('./Utils');
const HostFunctionTemplate = ({
hasteModuleName,
propertyName,
jniSignature,
jsReturnType,
}) => {
return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
}`;
};
const ModuleClassConstructorTemplate = ({
hasteModuleName,
eventEmitters,
methods,
}) => {
return `
${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params)
: JavaTurboModule(params) {
${methods
.map(({propertyName, argCount}) => {
return ` methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
})
.join('\n')}${
eventEmitters.length > 0
? eventEmitters
.map(eventEmitter => {
return `
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<folly::dynamic>>();`;
})
.join('')
: ''
}${
eventEmitters.length > 0
? `
configureEventEmitterCallback();`
: ''
}
}`.trim();
};
const ModuleLookupTemplate = ({moduleName, hasteModuleName}) => {
return ` if (moduleName == "${moduleName}") {
return std::make_shared<${hasteModuleName}SpecJSI>(params);
}`;
};
const FileTemplate = ({libraryName, include, modules, moduleLookups}) => {
return `
/**
* 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: GenerateModuleJniCpp.js
*/
#include ${include}
namespace facebook::react {
${modules}
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
${moduleLookups.map(ModuleLookupTemplate).join('\n')}
return nullptr;
}
} // namespace facebook::react
`;
};
function translateReturnTypeToKind(nullableTypeAnnotation, resolveAlias) {
const [typeAnnotation] = unwrapNullable(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'NumberKind';
default:
realTypeAnnotation.name;
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'VoidKind';
case 'StringTypeAnnotation':
return 'StringKind';
case 'StringLiteralTypeAnnotation':
return 'StringKind';
case 'StringLiteralUnionTypeAnnotation':
return 'StringKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unknown enum prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'NumberLiteralTypeAnnotation':
return 'NumberKind';
case 'DoubleTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
return 'NumberKind';
case 'Int32TypeAnnotation':
return 'NumberKind';
case 'PromiseTypeAnnotation':
return 'PromiseKind';
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'ArrayTypeAnnotation':
return 'ArrayKind';
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateParamTypeToJniType(param, resolveAlias) {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
default:
realTypeAnnotation.name;
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralUnionTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return !isRequired ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union prop value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'NumberLiteralTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableArray;';
case 'FunctionTypeAnnotation':
return 'Lcom/facebook/react/bridge/Callback;';
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateReturnTypeToJniType(nullableTypeAnnotation, resolveAlias) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return nullable ? 'Ljava/lang/Double;' : 'D';
default:
realTypeAnnotation.name;
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'V';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralUnionTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return nullable ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union member type, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'NumberLiteralTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'PromiseTypeAnnotation':
return 'Lcom/facebook/react/bridge/Promise;';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableArray;';
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateMethodTypeToJniSignature(property, resolveAlias) {
const {name, typeAnnotation} = property;
let [{returnTypeAnnotation, params}] = unwrapNullable(typeAnnotation);
params = [...params];
let processedReturnTypeAnnotation = returnTypeAnnotation;
const isPromiseReturn = returnTypeAnnotation.type === 'PromiseTypeAnnotation';
if (isPromiseReturn) {
processedReturnTypeAnnotation = {
type: 'VoidTypeAnnotation',
};
}
const argsSignatureParts = params.map(t => {
return translateParamTypeToJniType(t, resolveAlias);
});
if (isPromiseReturn) {
// Additional promise arg for this case.
argsSignatureParts.push(
translateReturnTypeToJniType(returnTypeAnnotation, resolveAlias),
);
}
const argsSignature = argsSignatureParts.join('');
const returnSignature =
name === 'getConstants'
? 'Ljava/util/Map;'
: translateReturnTypeToJniType(
processedReturnTypeAnnotation,
resolveAlias,
);
return `(${argsSignature})${returnSignature}`;
}
function translateMethodForImplementation(
hasteModuleName,
property,
resolveAlias,
) {
const [propertyTypeAnnotation] = unwrapNullable(property.typeAnnotation);
const {returnTypeAnnotation} = propertyTypeAnnotation;
if (
property.name === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties.length === 0
) {
return '';
}
return HostFunctionTemplate({
hasteModuleName,
propertyName: property.name,
jniSignature: translateMethodTypeToJniSignature(property, resolveAlias),
jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias),
});
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName => {
const {
aliasMap,
spec: {eventEmitters, methods},
} = nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliasMap);
const translatedMethods = methods
.map(property =>
translateMethodForImplementation(
hasteModuleName,
property,
resolveAlias,
),
)
.join('\n\n');
return (
translatedMethods +
'\n\n' +
ModuleClassConstructorTemplate({
hasteModuleName,
eventEmitters,
methods: methods
.map(({name: propertyName, typeAnnotation}) => {
const [{returnTypeAnnotation, params}] =
unwrapNullable(typeAnnotation);
if (
propertyName === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties &&
returnTypeAnnotation.properties.length === 0
) {
return null;
}
return {
propertyName,
argCount: params.length,
};
})
.filter(Boolean),
})
);
})
.join('\n');
const moduleLookups = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort((a, b) => {
const nameA = nativeModules[a].moduleName;
const nameB = nativeModules[b].moduleName;
if (nameA < nameB) {
return -1;
} else if (nameA > nameB) {
return 1;
}
return 0;
})
.map(hasteModuleName => ({
moduleName: nativeModules[hasteModuleName].moduleName,
hasteModuleName,
}));
const fileName = `${libraryName}-generated.cpp`;
const replacedTemplate = FileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
moduleLookups,
include: `"${libraryName}.h"`,
});
return new Map([[`jni/${fileName}`, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,558 @@
/**
* 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,
NativeModuleEventEmitterShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleReturnTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {createAliasResolver, getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
type JSReturnType =
| 'VoidKind'
| 'StringKind'
| 'BooleanKind'
| 'NumberKind'
| 'PromiseKind'
| 'ObjectKind'
| 'ArrayKind';
const HostFunctionTemplate = ({
hasteModuleName,
propertyName,
jniSignature,
jsReturnType,
}: $ReadOnly<{
hasteModuleName: string,
propertyName: string,
jniSignature: string,
jsReturnType: JSReturnType,
}>) => {
return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
}`;
};
const ModuleClassConstructorTemplate = ({
hasteModuleName,
eventEmitters,
methods,
}: $ReadOnly<{
hasteModuleName: string,
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
methods: $ReadOnlyArray<{
propertyName: string,
argCount: number,
}>,
}>) => {
return `
${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params)
: JavaTurboModule(params) {
${methods
.map(({propertyName, argCount}) => {
return ` methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
})
.join('\n')}${
eventEmitters.length > 0
? eventEmitters
.map(eventEmitter => {
return `
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<folly::dynamic>>();`;
})
.join('')
: ''
}${
eventEmitters.length > 0
? `
configureEventEmitterCallback();`
: ''
}
}`.trim();
};
const ModuleLookupTemplate = ({
moduleName,
hasteModuleName,
}: $ReadOnly<{moduleName: string, hasteModuleName: string}>) => {
return ` if (moduleName == "${moduleName}") {
return std::make_shared<${hasteModuleName}SpecJSI>(params);
}`;
};
const FileTemplate = ({
libraryName,
include,
modules,
moduleLookups,
}: $ReadOnly<{
libraryName: string,
include: string,
modules: string,
moduleLookups: $ReadOnlyArray<{
hasteModuleName: string,
moduleName: string,
}>,
}>) => {
return `
/**
* 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: GenerateModuleJniCpp.js
*/
#include ${include}
namespace facebook::react {
${modules}
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
${moduleLookups.map(ModuleLookupTemplate).join('\n')}
return nullptr;
}
} // namespace facebook::react
`;
};
function translateReturnTypeToKind(
nullableTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
resolveAlias: AliasResolver,
): JSReturnType {
const [typeAnnotation] = unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableTypeAnnotation,
);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'NumberKind';
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'VoidKind';
case 'StringTypeAnnotation':
return 'StringKind';
case 'StringLiteralTypeAnnotation':
return 'StringKind';
case 'StringLiteralUnionTypeAnnotation':
return 'StringKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unknown enum prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'NumberLiteralTypeAnnotation':
return 'NumberKind';
case 'DoubleTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
return 'NumberKind';
case 'Int32TypeAnnotation':
return 'NumberKind';
case 'PromiseTypeAnnotation':
return 'PromiseKind';
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'ArrayTypeAnnotation':
return 'ArrayKind';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unknown prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
}
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function translateParamTypeToJniType(
param: Param,
resolveAlias: AliasResolver,
): string {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralUnionTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return !isRequired ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union prop value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'NumberLiteralTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableArray;';
case 'FunctionTypeAnnotation':
return 'Lcom/facebook/react/bridge/Callback;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unknown prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateReturnTypeToJniType(
nullableTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
resolveAlias: AliasResolver,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return nullable ? 'Ljava/lang/Double;' : 'D';
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'V';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralUnionTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return nullable ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union member type, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'NumberLiteralTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'PromiseTypeAnnotation':
return 'Lcom/facebook/react/bridge/Promise;';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableArray;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unknown prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateMethodTypeToJniSignature(
property: NativeModulePropertyShape,
resolveAlias: AliasResolver,
): string {
const {name, typeAnnotation} = property;
let [{returnTypeAnnotation, params}] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(typeAnnotation);
params = [...params];
let processedReturnTypeAnnotation = returnTypeAnnotation;
const isPromiseReturn = returnTypeAnnotation.type === 'PromiseTypeAnnotation';
if (isPromiseReturn) {
processedReturnTypeAnnotation = {
type: 'VoidTypeAnnotation',
};
}
const argsSignatureParts = params.map(t => {
return translateParamTypeToJniType(t, resolveAlias);
});
if (isPromiseReturn) {
// Additional promise arg for this case.
argsSignatureParts.push(
translateReturnTypeToJniType(returnTypeAnnotation, resolveAlias),
);
}
const argsSignature = argsSignatureParts.join('');
const returnSignature =
name === 'getConstants'
? 'Ljava/util/Map;'
: translateReturnTypeToJniType(
processedReturnTypeAnnotation,
resolveAlias,
);
return `(${argsSignature})${returnSignature}`;
}
function translateMethodForImplementation(
hasteModuleName: string,
property: NativeModulePropertyShape,
resolveAlias: AliasResolver,
): string {
const [propertyTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(property.typeAnnotation);
const {returnTypeAnnotation} = propertyTypeAnnotation;
if (
property.name === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties.length === 0
) {
return '';
}
return HostFunctionTemplate({
hasteModuleName,
propertyName: property.name,
jniSignature: translateMethodTypeToJniSignature(property, resolveAlias),
jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias),
});
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName => {
const {
aliasMap,
spec: {eventEmitters, methods},
} = nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliasMap);
const translatedMethods = methods
.map(property =>
translateMethodForImplementation(
hasteModuleName,
property,
resolveAlias,
),
)
.join('\n\n');
return (
translatedMethods +
'\n\n' +
ModuleClassConstructorTemplate({
hasteModuleName,
eventEmitters,
methods: methods
.map(({name: propertyName, typeAnnotation}) => {
const [{returnTypeAnnotation, params}] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
typeAnnotation,
);
if (
propertyName === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties &&
returnTypeAnnotation.properties.length === 0
) {
return null;
}
return {
propertyName,
argCount: params.length,
};
})
.filter(Boolean),
})
);
})
.join('\n');
const moduleLookups: $ReadOnlyArray<{
hasteModuleName: string,
moduleName: string,
}> = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort((a, b) => {
const nameA = nativeModules[a].moduleName;
const nameB = nativeModules[b].moduleName;
if (nameA < nameB) {
return -1;
} else if (nameA > nameB) {
return 1;
}
return 0;
})
.map((hasteModuleName: string) => ({
moduleName: nativeModules[hasteModuleName].moduleName,
hasteModuleName,
}));
const fileName = `${libraryName}-generated.cpp`;
const replacedTemplate = FileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
moduleLookups,
include: `"${libraryName}.h"`,
});
return new Map([[`jni/${fileName}`, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,127 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {getModules} = require('./Utils');
const ModuleClassDeclarationTemplate = ({hasteModuleName}) => {
return `/**
* JNI C++ class for module '${hasteModuleName}'
*/
class JSI_EXPORT ${hasteModuleName}SpecJSI : public JavaTurboModule {
public:
${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params);
};
`;
};
const HeaderFileTemplate = ({modules, libraryName}) => {
return `
/**
* 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: GenerateModuleJniH.js
*/
#pragma once
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
namespace facebook::react {
${modules}
JSI_EXPORT
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params);
} // namespace facebook::react
`;
};
// Note: this CMakeLists.txt template includes dependencies for both NativeModule and components.
const CMakeListsTemplate = ({libraryName, targetName}) => {
return `# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/${libraryName}/*.cpp)
add_library(
react_codegen_${targetName}
OBJECT
\${react_codegen_SRCS}
)
target_include_directories(react_codegen_${targetName} PUBLIC . react/renderer/components/${libraryName})
target_link_libraries(
react_codegen_${targetName}
fbjni
jsi
# We need to link different libraries based on whether we are building rncore or not, that's necessary
# because we want to break a circular dependency between react_codegen_rncore and reactnative
${targetName !== 'rncore' ? 'reactnative' : 'folly_runtime glog react_debug react_nativemodule_core react_renderer_componentregistry react_renderer_core react_renderer_debug react_renderer_graphics react_renderer_imagemanager react_renderer_mapbuffer react_utils rrc_image rrc_view turbomodulejsijni yoga'}
)
target_compile_reactnative_options(react_codegen_${targetName} PRIVATE)
`;
};
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName =>
ModuleClassDeclarationTemplate({
hasteModuleName,
}),
)
.join('\n');
const fileName = `${libraryName}.h`;
const replacedTemplate = HeaderFileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
});
// Use rncore as target name for backwards compat
const targetName =
libraryName === 'FBReactNativeSpec' ? 'rncore' : libraryName;
return new Map([
[`jni/${fileName}`, replacedTemplate],
[
'jni/CMakeLists.txt',
CMakeListsTemplate({
libraryName,
targetName,
}),
],
]);
},
};

View File

@@ -0,0 +1,137 @@
/**
* 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 {SchemaType} from '../../CodegenSchema';
const {getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
const ModuleClassDeclarationTemplate = ({
hasteModuleName,
}: $ReadOnly<{hasteModuleName: string}>) => {
return `/**
* JNI C++ class for module '${hasteModuleName}'
*/
class JSI_EXPORT ${hasteModuleName}SpecJSI : public JavaTurboModule {
public:
${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params);
};
`;
};
const HeaderFileTemplate = ({
modules,
libraryName,
}: $ReadOnly<{modules: string, libraryName: string}>) => {
return `
/**
* 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: GenerateModuleJniH.js
*/
#pragma once
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
namespace facebook::react {
${modules}
JSI_EXPORT
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params);
} // namespace facebook::react
`;
};
// Note: this CMakeLists.txt template includes dependencies for both NativeModule and components.
const CMakeListsTemplate = ({
libraryName,
targetName,
}: $ReadOnly<{libraryName: string, targetName: string}>) => {
return `# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/${libraryName}/*.cpp)
add_library(
react_codegen_${targetName}
OBJECT
\${react_codegen_SRCS}
)
target_include_directories(react_codegen_${targetName} PUBLIC . react/renderer/components/${libraryName})
target_link_libraries(
react_codegen_${targetName}
fbjni
jsi
# We need to link different libraries based on whether we are building rncore or not, that's necessary
# because we want to break a circular dependency between react_codegen_rncore and reactnative
${
targetName !== 'rncore'
? 'reactnative'
: 'folly_runtime glog react_debug react_nativemodule_core react_renderer_componentregistry react_renderer_core react_renderer_debug react_renderer_graphics react_renderer_imagemanager react_renderer_mapbuffer react_utils rrc_image rrc_view turbomodulejsijni yoga'
}
)
target_compile_reactnative_options(react_codegen_${targetName} PRIVATE)
`;
};
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName => ModuleClassDeclarationTemplate({hasteModuleName}))
.join('\n');
const fileName = `${libraryName}.h`;
const replacedTemplate = HeaderFileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
});
// Use rncore as target name for backwards compat
const targetName =
libraryName === 'FBReactNativeSpec' ? 'rncore' : libraryName;
return new Map([
[`jni/${fileName}`, replacedTemplate],
['jni/CMakeLists.txt', CMakeListsTemplate({libraryName, targetName})],
]);
},
};

View File

@@ -0,0 +1,177 @@
/**
* 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.
*
*
* @format
*/
'use strict';
function _defineProperty(e, r, t) {
return (
(r = _toPropertyKey(r)) in e
? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0,
})
: (e[r] = t),
e
);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, 'string');
return 'symbol' == typeof i ? i : i + '';
}
function _toPrimitive(t, r) {
if ('object' != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || 'default');
if ('object' != typeof i) return i;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return ('string' === r ? String : Number)(t);
}
const {
unwrapNullable,
wrapNullable,
} = require('../../../parsers/parsers-commons');
const {capitalize} = require('../../Utils');
class StructCollector {
constructor() {
_defineProperty(this, '_structs', new Map());
}
process(structName, structContext, resolveAlias, nullableTypeAnnotation) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertStruct(
structName,
structContext,
resolveAlias,
typeAnnotation,
);
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: structName,
});
}
case 'ArrayTypeAnnotation': {
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'AnyTypeAnnotation',
},
});
}
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: this.process(
structName + 'Element',
structContext,
resolveAlias,
typeAnnotation.elementType,
),
});
}
case 'TypeAliasTypeAnnotation': {
this._insertAlias(typeAnnotation.name, structContext, resolveAlias);
return wrapNullable(nullable, typeAnnotation);
}
case 'EnumDeclaration':
return wrapNullable(nullable, typeAnnotation);
case 'MixedTypeAnnotation':
throw new Error('Mixed types are unsupported in structs');
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'StringTypeAnnotation':
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',
});
case 'NumberTypeAnnotation':
return wrapNullable(nullable, {
type: 'NumberTypeAnnotation',
});
case 'ObjectTypeAnnotation':
// This isn't smart enough to actually know how to generate the
// options on the native side. So we just treat it as an unknown object type
return wrapNullable(nullable, {
type: 'GenericObjectTypeAnnotation',
});
default:
typeAnnotation.memberType;
throw new Error(
'Union types are unsupported in structs' +
JSON.stringify(typeAnnotation),
);
}
default: {
return wrapNullable(nullable, typeAnnotation);
}
}
}
_insertAlias(aliasName, structContext, resolveAlias) {
const usedStruct = this._structs.get(aliasName);
if (usedStruct == null) {
this._insertStruct(
aliasName,
structContext,
resolveAlias,
resolveAlias(aliasName),
);
} else if (usedStruct.context !== structContext) {
throw new Error(
`Tried to use alias '${aliasName}' in a getConstants() return type and inside a regular struct.`,
);
}
}
_insertStruct(structName, structContext, resolveAlias, objectTypeAnnotation) {
// $FlowFixMe[missing-type-arg]
const properties = objectTypeAnnotation.properties.map(property => {
const propertyStructName = structName + capitalize(property.name);
return {
...property,
typeAnnotation: this.process(
propertyStructName,
structContext,
resolveAlias,
property.typeAnnotation,
),
};
});
switch (structContext) {
case 'REGULAR':
this._structs.set(structName, {
name: structName,
context: 'REGULAR',
properties: properties,
});
break;
case 'CONSTANTS':
this._structs.set(structName, {
name: structName,
context: 'CONSTANTS',
properties: properties,
});
break;
default:
structContext;
throw new Error(`Detected an invalid struct context: ${structContext}`);
}
}
getAllStructs() {
return [...this._structs.values()];
}
getStruct(name) {
return this._structs.get(name);
}
}
module.exports = {
StructCollector,
};

View File

@@ -0,0 +1,237 @@
/**
* 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 {
BooleanTypeAnnotation,
DoubleTypeAnnotation,
FloatTypeAnnotation,
Int32TypeAnnotation,
NativeModuleArrayTypeAnnotation,
NativeModuleBaseTypeAnnotation,
NativeModuleEnumDeclaration,
NativeModuleGenericObjectTypeAnnotation,
NativeModuleNumberTypeAnnotation,
NativeModuleObjectTypeAnnotation,
NativeModuleTypeAliasTypeAnnotation,
Nullable,
NumberLiteralTypeAnnotation,
ReservedTypeAnnotation,
StringLiteralTypeAnnotation,
StringLiteralUnionTypeAnnotation,
StringTypeAnnotation,
} from '../../../CodegenSchema';
import type {AliasResolver} from '../Utils';
const {
unwrapNullable,
wrapNullable,
} = require('../../../parsers/parsers-commons');
const {capitalize} = require('../../Utils');
type StructContext = 'CONSTANTS' | 'REGULAR';
export type RegularStruct = $ReadOnly<{
context: 'REGULAR',
name: string,
properties: $ReadOnlyArray<StructProperty>,
}>;
export type ConstantsStruct = $ReadOnly<{
context: 'CONSTANTS',
name: string,
properties: $ReadOnlyArray<StructProperty>,
}>;
export type Struct = RegularStruct | ConstantsStruct;
export type StructProperty = $ReadOnly<{
name: string,
optional: boolean,
typeAnnotation: Nullable<StructTypeAnnotation>,
}>;
export type StructTypeAnnotation =
| StringTypeAnnotation
| StringLiteralTypeAnnotation
| StringLiteralUnionTypeAnnotation
| NativeModuleNumberTypeAnnotation
| NumberLiteralTypeAnnotation
| Int32TypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| BooleanTypeAnnotation
| NativeModuleEnumDeclaration
| NativeModuleGenericObjectTypeAnnotation
| ReservedTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleArrayTypeAnnotation<Nullable<StructTypeAnnotation>>;
class StructCollector {
_structs: Map<string, Struct> = new Map();
process(
structName: string,
structContext: StructContext,
resolveAlias: AliasResolver,
nullableTypeAnnotation: Nullable<NativeModuleBaseTypeAnnotation>,
): Nullable<StructTypeAnnotation> {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertStruct(
structName,
structContext,
resolveAlias,
typeAnnotation,
);
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: structName,
});
}
case 'ArrayTypeAnnotation': {
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'AnyTypeAnnotation',
},
});
}
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: this.process(
structName + 'Element',
structContext,
resolveAlias,
typeAnnotation.elementType,
),
});
}
case 'TypeAliasTypeAnnotation': {
this._insertAlias(typeAnnotation.name, structContext, resolveAlias);
return wrapNullable(nullable, typeAnnotation);
}
case 'EnumDeclaration':
return wrapNullable(nullable, typeAnnotation);
case 'MixedTypeAnnotation':
throw new Error('Mixed types are unsupported in structs');
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'StringTypeAnnotation':
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',
});
case 'NumberTypeAnnotation':
return wrapNullable(nullable, {
type: 'NumberTypeAnnotation',
});
case 'ObjectTypeAnnotation':
// This isn't smart enough to actually know how to generate the
// options on the native side. So we just treat it as an unknown object type
return wrapNullable(nullable, {
type: 'GenericObjectTypeAnnotation',
});
default:
(typeAnnotation.memberType: empty);
throw new Error(
'Union types are unsupported in structs' +
JSON.stringify(typeAnnotation),
);
}
default: {
return wrapNullable(nullable, typeAnnotation);
}
}
}
_insertAlias(
aliasName: string,
structContext: StructContext,
resolveAlias: AliasResolver,
): void {
const usedStruct = this._structs.get(aliasName);
if (usedStruct == null) {
this._insertStruct(
aliasName,
structContext,
resolveAlias,
resolveAlias(aliasName),
);
} else if (usedStruct.context !== structContext) {
throw new Error(
`Tried to use alias '${aliasName}' in a getConstants() return type and inside a regular struct.`,
);
}
}
_insertStruct(
structName: string,
structContext: StructContext,
resolveAlias: AliasResolver,
objectTypeAnnotation: NativeModuleObjectTypeAnnotation,
): void {
// $FlowFixMe[missing-type-arg]
const properties = objectTypeAnnotation.properties.map<
$ReadOnly<{
name: string,
optional: boolean,
typeAnnotation: Nullable<StructTypeAnnotation>,
}>,
>(property => {
const propertyStructName = structName + capitalize(property.name);
return {
...property,
typeAnnotation: this.process(
propertyStructName,
structContext,
resolveAlias,
property.typeAnnotation,
),
};
});
switch (structContext) {
case 'REGULAR':
this._structs.set(structName, {
name: structName,
context: 'REGULAR',
properties: properties,
});
break;
case 'CONSTANTS':
this._structs.set(structName, {
name: structName,
context: 'CONSTANTS',
properties: properties,
});
break;
default:
(structContext: empty);
throw new Error(`Detected an invalid struct context: ${structContext}`);
}
}
getAllStructs(): $ReadOnlyArray<Struct> {
return [...this._structs.values()];
}
getStruct(name: string): ?Struct {
return this._structs.get(name);
}
}
module.exports = {
StructCollector,
};

View File

@@ -0,0 +1,25 @@
/**
* 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.
*
*
* @format
*/
'use strict';
function getSafePropertyName(property) {
if (property.name === 'id') {
return `${property.name}_`;
}
return property.name;
}
function getNamespacedStructName(hasteModuleName, structName) {
return `JS::${hasteModuleName}::${structName}`;
}
module.exports = {
getSafePropertyName,
getNamespacedStructName,
};

View File

@@ -0,0 +1,32 @@
/**
* 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 {StructProperty} from './StructCollector';
function getSafePropertyName(property: StructProperty): string {
if (property.name === 'id') {
return `${property.name}_`;
}
return property.name;
}
function getNamespacedStructName(
hasteModuleName: string,
structName: string,
): string {
return `JS::${hasteModuleName}::${structName}`;
}
module.exports = {
getSafePropertyName,
getNamespacedStructName,
};

View File

@@ -0,0 +1,265 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {unwrapNullable} = require('../../../../parsers/parsers-commons');
const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');
const StructTemplate = ({
hasteModuleName,
structName,
builderInputProps,
}) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
struct Builder {
struct Input {
${builderInputProps}
};
/** Initialize with a set of values */
Builder(const Input i);
/** Initialize with an existing ${structName} */
Builder(${structName} i);
/** Builds the object. Generally used only by the infrastructure. */
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
private:
NSDictionary *(^_factory)(void);
};
static ${structName} fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
NSDictionary *unsafeRawValue() const { return _v; }
private:
${structName}(NSDictionary *const v) : _v(v) {}
NSDictionary *_v;
};
}
}`;
const MethodTemplate = ({
hasteModuleName,
structName,
properties,
}) => `inline JS::${hasteModuleName}::${structName}::Builder::Builder(const Input i) : _factory(^{
NSMutableDictionary *d = [NSMutableDictionary new];
${properties}
return d;
}) {}
inline JS::${hasteModuleName}::${structName}::Builder::Builder(${structName} i) : _factory(^{
return i.unsafeRawValue();
}) {}`;
function toObjCType(
hasteModuleName,
nullableTypeAnnotation,
isOptional = false,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
typeAnnotation.name;
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`std::vector<${toObjCType(hasteModuleName, typeAnnotation.elementType)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(`${namespacedStructName}::Builder`, isRequired);
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName,
nullableTypeAnnotation,
value,
depth,
isOptional = false,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
function wrapPrimitive(type) {
return !isRequired
? `${value}.has_value() ? @((${type})${value}.value()) : nil`
: `@(${value})`;
}
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapPrimitive('double');
default:
typeAnnotation.name;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return value;
case 'StringLiteralTypeAnnotation':
return value;
case 'StringLiteralUnionTypeAnnotation':
return value;
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'NumberLiteralTypeAnnotation':
return wrapPrimitive('double');
case 'FloatTypeAnnotation':
return wrapPrimitive('double');
case 'Int32TypeAnnotation':
return wrapPrimitive('double');
case 'DoubleTypeAnnotation':
return wrapPrimitive('double');
case 'BooleanTypeAnnotation':
return wrapPrimitive('BOOL');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'StringTypeAnnotation':
return value;
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const {elementType} = typeAnnotation;
if (elementType.type === 'AnyTypeAnnotation') {
return value;
}
const localVarName = `el${'_'.repeat(depth + 1)}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
const RCTConvertVecToArray = transformer => {
return `RCTConvert${!isRequired ? 'Optional' : ''}VecToArray(${value}, ${transformer})`;
};
return RCTConvertVecToArray(
`^id(${elementObjCType} ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
return !isRequired
? `${value}.has_value() ? ${value}.value().buildUnsafeRawValue() : nil`
: `${value}.buildUnsafeRawValue()`;
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeConstantsStruct(hasteModuleName, struct) {
const declaration = StructTemplate({
hasteModuleName,
structName: struct.name,
builderInputProps: struct.properties
.map(property => {
const {typeAnnotation, optional} = property;
const safePropName = getSafePropertyName(property);
const objCType = toObjCType(hasteModuleName, typeAnnotation, optional);
if (!optional) {
return `RCTRequired<${objCType}> ${safePropName};`;
}
const space = ' '.repeat(objCType.endsWith('*') ? 0 : 1);
return `${objCType}${space}${safePropName};`;
})
.join('\n '),
});
const methods = MethodTemplate({
hasteModuleName,
structName: struct.name,
properties: struct.properties
.map(property => {
const {typeAnnotation, optional, name: propName} = property;
const safePropName = getSafePropertyName(property);
const objCValue = toObjCValue(
hasteModuleName,
typeAnnotation,
safePropName,
0,
optional,
);
let varDecl = `auto ${safePropName} = i.${safePropName}`;
if (!optional) {
varDecl += '.get()';
}
const assignment = `d[@"${propName}"] = ` + objCValue;
return ` ${varDecl};\n ${assignment};`;
})
.join('\n'),
});
return {
declaration,
methods,
};
}
module.exports = {
serializeConstantsStruct,
};

View File

@@ -0,0 +1,301 @@
/**
* 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 {Nullable} from '../../../../CodegenSchema';
import type {ConstantsStruct, StructTypeAnnotation} from '../StructCollector';
import type {StructSerilizationOutput} from './serializeStruct';
const {unwrapNullable} = require('../../../../parsers/parsers-commons');
const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');
const StructTemplate = ({
hasteModuleName,
structName,
builderInputProps,
}: $ReadOnly<{
hasteModuleName: string,
structName: string,
builderInputProps: string,
}>) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
struct Builder {
struct Input {
${builderInputProps}
};
/** Initialize with a set of values */
Builder(const Input i);
/** Initialize with an existing ${structName} */
Builder(${structName} i);
/** Builds the object. Generally used only by the infrastructure. */
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
private:
NSDictionary *(^_factory)(void);
};
static ${structName} fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
NSDictionary *unsafeRawValue() const { return _v; }
private:
${structName}(NSDictionary *const v) : _v(v) {}
NSDictionary *_v;
};
}
}`;
const MethodTemplate = ({
hasteModuleName,
structName,
properties,
}: $ReadOnly<{
hasteModuleName: string,
structName: string,
properties: string,
}>) => `inline JS::${hasteModuleName}::${structName}::Builder::Builder(const Input i) : _factory(^{
NSMutableDictionary *d = [NSMutableDictionary new];
${properties}
return d;
}) {}
inline JS::${hasteModuleName}::${structName}::Builder::Builder(${structName} i) : _factory(^{
return i.unsafeRawValue();
}) {}`;
function toObjCType(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
(typeAnnotation.name: empty);
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`std::vector<${toObjCType(
hasteModuleName,
typeAnnotation.elementType,
)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(`${namespacedStructName}::Builder`, isRequired);
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
value: string,
depth: number,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
function wrapPrimitive(type: string) {
return !isRequired
? `${value}.has_value() ? @((${type})${value}.value()) : nil`
: `@(${value})`;
}
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapPrimitive('double');
default:
(typeAnnotation.name: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return value;
case 'StringLiteralTypeAnnotation':
return value;
case 'StringLiteralUnionTypeAnnotation':
return value;
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'NumberLiteralTypeAnnotation':
return wrapPrimitive('double');
case 'FloatTypeAnnotation':
return wrapPrimitive('double');
case 'Int32TypeAnnotation':
return wrapPrimitive('double');
case 'DoubleTypeAnnotation':
return wrapPrimitive('double');
case 'BooleanTypeAnnotation':
return wrapPrimitive('BOOL');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'StringTypeAnnotation':
return value;
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const {elementType} = typeAnnotation;
if (elementType.type === 'AnyTypeAnnotation') {
return value;
}
const localVarName = `el${'_'.repeat(depth + 1)}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
const RCTConvertVecToArray = (transformer: string) => {
return `RCTConvert${
!isRequired ? 'Optional' : ''
}VecToArray(${value}, ${transformer})`;
};
return RCTConvertVecToArray(
`^id(${elementObjCType} ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
return !isRequired
? `${value}.has_value() ? ${value}.value().buildUnsafeRawValue() : nil`
: `${value}.buildUnsafeRawValue()`;
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeConstantsStruct(
hasteModuleName: string,
struct: ConstantsStruct,
): StructSerilizationOutput {
const declaration = StructTemplate({
hasteModuleName,
structName: struct.name,
builderInputProps: struct.properties
.map(property => {
const {typeAnnotation, optional} = property;
const safePropName = getSafePropertyName(property);
const objCType = toObjCType(hasteModuleName, typeAnnotation, optional);
if (!optional) {
return `RCTRequired<${objCType}> ${safePropName};`;
}
const space = ' '.repeat(objCType.endsWith('*') ? 0 : 1);
return `${objCType}${space}${safePropName};`;
})
.join('\n '),
});
const methods = MethodTemplate({
hasteModuleName,
structName: struct.name,
properties: struct.properties
.map(property => {
const {typeAnnotation, optional, name: propName} = property;
const safePropName = getSafePropertyName(property);
const objCValue = toObjCValue(
hasteModuleName,
typeAnnotation,
safePropName,
0,
optional,
);
let varDecl = `auto ${safePropName} = i.${safePropName}`;
if (!optional) {
varDecl += '.get()';
}
const assignment = `d[@"${propName}"] = ` + objCValue;
return ` ${varDecl};\n ${assignment};`;
})
.join('\n'),
});
return {declaration, methods};
}
module.exports = {
serializeConstantsStruct,
};

View File

@@ -0,0 +1,260 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {unwrapNullable} = require('../../../../parsers/parsers-commons');
const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');
const StructTemplate = ({
hasteModuleName,
structName,
structProperties,
}) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
${structProperties}
${structName}(NSDictionary *const v) : _v(v) {}
private:
NSDictionary *_v;
};
}
}
@interface RCTCxxConvert (${hasteModuleName}_${structName})
+ (RCTManagedPointer *)JS_${hasteModuleName}_${structName}:(id)json;
@end`;
const MethodTemplate = ({
returnType,
returnValue,
hasteModuleName,
structName,
propertyName,
safePropertyName,
}) => `inline ${returnType}JS::${hasteModuleName}::${structName}::${safePropertyName}() const
{
id const p = _v[@"${propertyName}"];
return ${returnValue};
}`;
function toObjCType(
hasteModuleName,
nullableTypeAnnotation,
isOptional = false,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
typeAnnotation.name;
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`facebook::react::LazyVector<${toObjCType(hasteModuleName, typeAnnotation.elementType)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(namespacedStructName, isRequired);
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName,
nullableTypeAnnotation,
value,
depth,
isOptional = false,
) {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
const RCTBridgingTo = (type, arg) => {
const args = [value, arg].filter(Boolean).join(', ');
return isRequired
? `RCTBridgingTo${type}(${args})`
: `RCTBridgingToOptional${type}(${args})`;
};
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return RCTBridgingTo('Double');
default:
typeAnnotation.name;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return RCTBridgingTo('String');
case 'StringLiteralTypeAnnotation':
return RCTBridgingTo('String');
case 'StringLiteralUnionTypeAnnotation':
return RCTBridgingTo('String');
case 'NumberTypeAnnotation':
return RCTBridgingTo('Double');
case 'NumberLiteralTypeAnnotation':
return RCTBridgingTo('Double');
case 'FloatTypeAnnotation':
return RCTBridgingTo('Double');
case 'Int32TypeAnnotation':
return RCTBridgingTo('Double');
case 'DoubleTypeAnnotation':
return RCTBridgingTo('Double');
case 'BooleanTypeAnnotation':
return RCTBridgingTo('Bool');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return RCTBridgingTo('Double');
case 'StringTypeAnnotation':
return RCTBridgingTo('String');
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const {elementType} = typeAnnotation;
if (elementType.type === 'AnyTypeAnnotation') {
return value;
}
const localVarName = `itemValue_${depth}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
return RCTBridgingTo(
'Vec',
`^${elementObjCType}(id ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return !isRequired
? `(${value} == nil ? std::nullopt : std::make_optional(${namespacedStructName}(${value})))`
: `${namespacedStructName}(${value})`;
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeRegularStruct(hasteModuleName, struct) {
const declaration = StructTemplate({
hasteModuleName: hasteModuleName,
structName: struct.name,
structProperties: struct.properties
.map(property => {
const {typeAnnotation, optional} = property;
const safePropName = getSafePropertyName(property);
const returnType = toObjCType(
hasteModuleName,
typeAnnotation,
optional,
);
const padding = ' '.repeat(returnType.endsWith('*') ? 0 : 1);
return `${returnType}${padding}${safePropName}() const;`;
})
.join('\n '),
});
// $FlowFixMe[missing-type-arg]
const methods = struct.properties
.map(property => {
const {typeAnnotation, optional, name: propName} = property;
const safePropertyName = getSafePropertyName(property);
const returnType = toObjCType(hasteModuleName, typeAnnotation, optional);
const returnValue = toObjCValue(
hasteModuleName,
typeAnnotation,
'p',
0,
optional,
);
const padding = ' '.repeat(returnType.endsWith('*') ? 0 : 1);
return MethodTemplate({
hasteModuleName,
structName: struct.name,
returnType: returnType + padding,
returnValue: returnValue,
propertyName: propName,
safePropertyName,
});
})
.join('\n');
return {
methods,
declaration,
};
}
module.exports = {
serializeRegularStruct,
};

View File

@@ -0,0 +1,292 @@
/**
* 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 {Nullable} from '../../../../CodegenSchema';
import type {RegularStruct, StructTypeAnnotation} from '../StructCollector';
import type {StructSerilizationOutput} from './serializeStruct';
const {unwrapNullable} = require('../../../../parsers/parsers-commons');
const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');
const StructTemplate = ({
hasteModuleName,
structName,
structProperties,
}: $ReadOnly<{
hasteModuleName: string,
structName: string,
structProperties: string,
}>) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
${structProperties}
${structName}(NSDictionary *const v) : _v(v) {}
private:
NSDictionary *_v;
};
}
}
@interface RCTCxxConvert (${hasteModuleName}_${structName})
+ (RCTManagedPointer *)JS_${hasteModuleName}_${structName}:(id)json;
@end`;
const MethodTemplate = ({
returnType,
returnValue,
hasteModuleName,
structName,
propertyName,
safePropertyName,
}: $ReadOnly<{
returnType: string,
returnValue: string,
hasteModuleName: string,
structName: string,
propertyName: string,
safePropertyName: string,
}>) => `inline ${returnType}JS::${hasteModuleName}::${structName}::${safePropertyName}() const
{
id const p = _v[@"${propertyName}"];
return ${returnValue};
}`;
function toObjCType(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
(typeAnnotation.name: empty);
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'StringLiteralUnionTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'NumberLiteralTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`facebook::react::LazyVector<${toObjCType(
hasteModuleName,
typeAnnotation.elementType,
)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(namespacedStructName, isRequired);
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
value: string,
depth: number,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
const RCTBridgingTo = (type: string, arg?: string) => {
const args = [value, arg].filter(Boolean).join(', ');
return isRequired
? `RCTBridgingTo${type}(${args})`
: `RCTBridgingToOptional${type}(${args})`;
};
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return RCTBridgingTo('Double');
default:
(typeAnnotation.name: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return RCTBridgingTo('String');
case 'StringLiteralTypeAnnotation':
return RCTBridgingTo('String');
case 'StringLiteralUnionTypeAnnotation':
return RCTBridgingTo('String');
case 'NumberTypeAnnotation':
return RCTBridgingTo('Double');
case 'NumberLiteralTypeAnnotation':
return RCTBridgingTo('Double');
case 'FloatTypeAnnotation':
return RCTBridgingTo('Double');
case 'Int32TypeAnnotation':
return RCTBridgingTo('Double');
case 'DoubleTypeAnnotation':
return RCTBridgingTo('Double');
case 'BooleanTypeAnnotation':
return RCTBridgingTo('Bool');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return RCTBridgingTo('Double');
case 'StringTypeAnnotation':
return RCTBridgingTo('String');
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const {elementType} = typeAnnotation;
if (elementType.type === 'AnyTypeAnnotation') {
return value;
}
const localVarName = `itemValue_${depth}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
return RCTBridgingTo(
'Vec',
`^${elementObjCType}(id ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return !isRequired
? `(${value} == nil ? std::nullopt : std::make_optional(${namespacedStructName}(${value})))`
: `${namespacedStructName}(${value})`;
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeRegularStruct(
hasteModuleName: string,
struct: RegularStruct,
): StructSerilizationOutput {
const declaration = StructTemplate({
hasteModuleName: hasteModuleName,
structName: struct.name,
structProperties: struct.properties
.map(property => {
const {typeAnnotation, optional} = property;
const safePropName = getSafePropertyName(property);
const returnType = toObjCType(
hasteModuleName,
typeAnnotation,
optional,
);
const padding = ' '.repeat(returnType.endsWith('*') ? 0 : 1);
return `${returnType}${padding}${safePropName}() const;`;
})
.join('\n '),
});
// $FlowFixMe[missing-type-arg]
const methods = struct.properties
.map<string>(property => {
const {typeAnnotation, optional, name: propName} = property;
const safePropertyName = getSafePropertyName(property);
const returnType = toObjCType(hasteModuleName, typeAnnotation, optional);
const returnValue = toObjCValue(
hasteModuleName,
typeAnnotation,
'p',
0,
optional,
);
const padding = ' '.repeat(returnType.endsWith('*') ? 0 : 1);
return MethodTemplate({
hasteModuleName,
structName: struct.name,
returnType: returnType + padding,
returnValue: returnValue,
propertyName: propName,
safePropertyName,
});
})
.join('\n');
return {methods, declaration};
}
module.exports = {
serializeRegularStruct,
};

View File

@@ -0,0 +1,23 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {serializeConstantsStruct} = require('./serializeConstantsStruct');
const {serializeRegularStruct} = require('./serializeRegularStruct');
function serializeStruct(hasteModuleName, struct) {
if (struct.context === 'REGULAR') {
return serializeRegularStruct(hasteModuleName, struct);
}
return serializeConstantsStruct(hasteModuleName, struct);
}
module.exports = {
serializeStruct,
};

View File

@@ -0,0 +1,35 @@
/**
* 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 {Struct} from '../StructCollector';
const {serializeConstantsStruct} = require('./serializeConstantsStruct');
const {serializeRegularStruct} = require('./serializeRegularStruct');
export type StructSerilizationOutput = $ReadOnly<{
methods: string,
declaration: string,
}>;
function serializeStruct(
hasteModuleName: string,
struct: Struct,
): StructSerilizationOutput {
if (struct.context === 'REGULAR') {
return serializeRegularStruct(hasteModuleName, struct);
}
return serializeConstantsStruct(hasteModuleName, struct);
}
module.exports = {
serializeStruct,
};

View File

@@ -0,0 +1,203 @@
/**
* 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.
*
*
* @format
*/
'use strict';
const {createAliasResolver, getModules} = require('../Utils');
const {serializeStruct} = require('./header/serializeStruct');
const {EventEmitterHeaderTemplate} = require('./serializeEventEmitter');
const {serializeMethod} = require('./serializeMethod');
const {serializeModuleSource} = require('./source/serializeModule');
const {StructCollector} = require('./StructCollector');
const ModuleDeclarationTemplate = ({
hasteModuleName,
structDeclarations,
eventEmitters,
protocolMethods,
}) => `${structDeclarations}
@protocol ${hasteModuleName}Spec <RCTBridgeModule, RCTTurboModule>
${protocolMethods}
@end
@interface ${hasteModuleName}SpecBase : NSObject {
@protected
facebook::react::EventEmitterCallback _eventEmitterCallback;
}
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper;
${eventEmitters}
@end
namespace facebook::react {
/**
* ObjC++ class for module '${hasteModuleName}'
*/
class JSI_EXPORT ${hasteModuleName}SpecJSI : public ObjCTurboModule {
public:
${hasteModuleName}SpecJSI(const ObjCTurboModule::InitParams &params);
};
} // namespace facebook::react`;
const HeaderFileTemplate = ({
headerFileName,
moduleDeclarations,
structInlineMethods,
assumeNonnull,
}) => {
const headerFileNameWithNoExt = headerFileName.replace(/\.h$/, '');
return (
`/**
* 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: GenerateModuleObjCpp
*
* We create an umbrella header (and corresponding implementation) here since
* Cxx compilation in BUCK has a limitation: source-code producing genrule()s
* must have a single output. More files => more genrule()s => slower builds.
*/
#ifndef __cplusplus
#error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm.
#endif
// Avoid multiple includes of ${headerFileNameWithNoExt} symbols
#ifndef ${headerFileNameWithNoExt}_H
#define ${headerFileNameWithNoExt}_H
#import <Foundation/Foundation.h>
#import <RCTRequired/RCTRequired.h>
#import <RCTTypeSafety/RCTConvertHelpers.h>
#import <RCTTypeSafety/RCTTypedModuleConstants.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTCxxConvert.h>
#import <React/RCTManagedPointer.h>
#import <ReactCommon/RCTTurboModule.h>
#import <optional>
#import <vector>
` +
(assumeNonnull ? '\nNS_ASSUME_NONNULL_BEGIN\n' : '') +
moduleDeclarations +
'\n' +
structInlineMethods +
(assumeNonnull ? '\nNS_ASSUME_NONNULL_END\n' : '\n') +
`#endif // ${headerFileNameWithNoExt}_H` +
'\n'
);
};
const SourceFileTemplate = ({headerFileName, moduleImplementations}) => `/**
* 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: GenerateModuleObjCpp
*
* We create an umbrella header (and corresponding implementation) here since
* Cxx compilation in BUCK has a limitation: source-code producing genrule()s
* must have a single output. More files => more genrule()s => slower builds.
*/
#import "${headerFileName}"
${moduleImplementations}
`;
module.exports = {
generate(libraryName, schema, packageName, assumeNonnull, headerPrefix) {
const nativeModules = getModules(schema);
const moduleDeclarations = [];
const structInlineMethods = [];
const moduleImplementations = [];
const hasteModuleNames = Object.keys(nativeModules).sort();
for (const hasteModuleName of hasteModuleNames) {
const {aliasMap, excludedPlatforms, spec} =
nativeModules[hasteModuleName];
if (excludedPlatforms != null && excludedPlatforms.includes('iOS')) {
continue;
}
const resolveAlias = createAliasResolver(aliasMap);
const structCollector = new StructCollector();
const methodSerializations = [];
const serializeProperty = property => {
methodSerializations.push(
...serializeMethod(
hasteModuleName,
property,
structCollector,
resolveAlias,
),
);
};
/**
* Note: As we serialize NativeModule methods, we insert structs into
* StructCollector, as we encounter them.
*/
spec.methods
.filter(property => property.name !== 'getConstants')
.forEach(serializeProperty);
spec.methods
.filter(property => property.name === 'getConstants')
.forEach(serializeProperty);
const generatedStructs = structCollector.getAllStructs();
const structStrs = [];
const methodStrs = [];
for (const struct of generatedStructs) {
const {methods, declaration} = serializeStruct(hasteModuleName, struct);
structStrs.push(declaration);
methodStrs.push(methods);
}
moduleDeclarations.push(
ModuleDeclarationTemplate({
hasteModuleName: hasteModuleName,
structDeclarations: structStrs.join('\n'),
eventEmitters: spec.eventEmitters
.map(eventEmitter => EventEmitterHeaderTemplate(eventEmitter))
.join('\n'),
protocolMethods: methodSerializations
.map(({protocolMethod}) => protocolMethod)
.join('\n'),
}),
);
structInlineMethods.push(methodStrs.join('\n'));
moduleImplementations.push(
serializeModuleSource(
hasteModuleName,
generatedStructs,
hasteModuleName,
spec.eventEmitters,
methodSerializations.filter(
({selector}) => selector !== '@selector(constantsToExport)',
),
),
);
}
const headerFileName = `${libraryName}.h`;
const headerFile = HeaderFileTemplate({
headerFileName,
moduleDeclarations: moduleDeclarations.join('\n'),
structInlineMethods: structInlineMethods.join('\n'),
assumeNonnull,
});
const sourceFileName = `${libraryName}-generated.mm`;
const sourceFile = SourceFileTemplate({
headerFileName,
moduleImplementations: moduleImplementations.join('\n'),
});
return new Map([
[headerFileName, headerFile],
[sourceFileName, sourceFile],
]);
},
};

Some files were not shown because too many files have changed in this diff Show More